root/libcore/movie_root.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. clear
  2. _movieLoader
  3. disableScripts
  4. init
  5. setRootMovie
  6. handleActionLimitHit
  7. cleanupAndCollect
  8. setLevel
  9. swapLevels
  10. dropLevel
  11. replaceLevel
  12. getLevel
  13. reset
  14. setDimensions
  15. mouseMoved
  16. keyEvent
  17. mouseWheel
  18. mouseClick
  19. fire_mouse_event
  20. mousePosition
  21. setDragState
  22. doMouseDrag
  23. addIntervalTimer
  24. clearIntervalTimer
  25. advance
  26. advanceMovie
  27. timeToNextFrame
  28. display
  29. notify_mouse_listeners
  30. getFocus
  31. setFocus
  32. getActiveEntityUnderPointer
  33. getDraggingCharacter
  34. getEntityUnderPointer
  35. setQuality
  36. getStageWidth
  37. getStageHeight
  38. setStageAlignment
  39. setAllowScriptAccess
  40. getAllowScriptAccess
  41. getStageAlignment
  42. getShowMenuState
  43. setShowMenuState
  44. getStageAlignMode
  45. setStageScaleMode
  46. setStageDisplayState
  47. add_invalidated_bounds
  48. minPopulatedPriorityQueue
  49. processActionQueue
  50. flushHigherPriorityActionQueues
  51. addLoadableObject
  52. addAdvanceCallback
  53. removeAdvanceCallback
  54. processActionQueue
  55. removeQueuedConstructor
  56. pushAction
  57. pushAction
  58. executeAdvanceCallbacks
  59. processInvoke
  60. executeTimers
  61. markReachableResources
  62. getTopmostMouseEntity
  63. findDropTarget
  64. addExternalCallback
  65. callExternalJavascript
  66. callExternalCallback
  67. remove_key_listener
  68. add_key_listener
  69. cleanupDisplayList
  70. advanceLiveChars
  71. set_background_color
  72. set_background_alpha
  73. findCharacterByTarget
  74. getURL
  75. setScriptLimits
  76. getMovieInfo
  77. getCharacterTree
  78. handleFsCommand
  79. isLevelTarget
  80. stringToStageAlign
  81. setReachable
  82. processLoad
  83. callInterface
  84. testInvariant
  85. generate_mouse_button_events
  86. getNearestObject
  87. getBuiltinObject
  88. advanceLiveChar

// movie_root.cpp:  The root movie, for Gnash.
// 
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
//   2011 Free Software Foundation, Inc
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
// 

#include "movie_root.h"

#include <utility>
#include <string>
#include <sstream>
#include <map>
#include <bitset>
#include <cassert>
#include <functional>
#include <boost/algorithm/string/erase.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/ptr_container/ptr_deque.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/bind.hpp>

#include "GnashSystemIOHeaders.h" // write()
#include "log.h"
#include "MovieClip.h"
#include "Movie.h" 
#include "VM.h"
#include "ExecutableCode.h"
#include "URL.h"
#include "namedStrings.h"
#include "GnashException.h"
#include "sound_handler.h"
#include "Timers.h"
#include "GnashKey.h"
#include "MovieFactory.h"
#include "GnashAlgorithm.h"
#include "GnashNumeric.h"
#include "Global_as.h"
#include "utf8.h"
#include "IOChannel.h"
#include "RunResources.h"
#include "Renderer.h"
#include "ExternalInterface.h"
#include "TextField.h"
#include "Button.h"
#include "Transform.h"
#include "StreamProvider.h"

#ifdef USE_SWFTREE
# include "tree.hh"
#endif

//#define GNASH_DEBUG 1
//#define GNASH_DEBUG_LOADMOVIE_REQUESTS_PROCESSING 1
//#define GNASH_DEBUG_TIMERS_EXPIRATION 1

// Defining the macro below prints info about
// cleanup of live chars (advanceable + key/mouse listeners)
// Is useful in particular to check for cost of multiple scans
// when a movie destruction destrois more elements.
//
// NOTE: I think the whole confusion here was introduced
//       by zou making it "optional" to ::unload() childs
//       when being unloaded. Zou was trying to avoid
//       queuing an onUnload event, which I suggested we'd
//       do by having unload() take an additional argument
//       or similar. Failing to tag childs as unloaded
//       will result in tagging them later (in ::destroy)
//       which will require scanning the lists again
//       (key/mouse + advanceable).
//       See https://savannah.gnu.org/bugs/index.php?21804
//
//#define GNASH_DEBUG_DLIST_CLEANUP 1

namespace gnash {

// Forward declarations
namespace {
    bool generate_mouse_button_events(movie_root& mr, MouseButtonState& ms);
    const DisplayObject* getNearestObject(const DisplayObject* o);
    as_object* getBuiltinObject(movie_root& mr, const ObjectURI& cl);
    void advanceLiveChar(MovieClip* ch);
}

// Utility classes
namespace {

/// Execute an ActiveRelay if the object has that type.
struct ExecuteCallback
{
    void operator()(const as_object* o) const {
        ActiveRelay* a;
        if (isNativeType(o, a)) {
            a->update();
        }
    }
};

/// Identify and delete ExecutableCode that matches a particular target.
class RemoveTargetCode
{
public:
    RemoveTargetCode(DisplayObject* target) : _target(target) {}
    bool operator()(const ExecutableCode& c) const {
        return _target == c.target();
    }
private:
    DisplayObject* _target;
};

void
clear(movie_root::ActionQueue& aq)
{
    std::for_each(aq.begin(), aq.end(), 
            boost::mem_fn(&movie_root::ActionQueue::value_type::clear));
}

} // anonymous namespace


movie_root::movie_root(const movie_definition& def,
        VirtualClock& clock, const RunResources& runResources)
    :
    _gc(*this),
    _runResources(runResources),
    _vm(def.get_version(), *this, clock),
    _interfaceHandler(0),
    _fsCommandHandler(0),
    _stageWidth(1),
    _stageHeight(1),
    m_background_color(255, 255, 255, 255),
    m_background_color_set(false),
    _mouseX(0),
    _mouseY(0),
    _lastTimerId(0),
    _lastKeyEvent(key::INVALID),
    _currentFocus(0),
    _dragState(0),
    _movies(),
    _rootMovie(0),
    _invalidated(true),
    _disableScripts(false),
    _processingActionLevel(PRIORITY_SIZE),
    _hostfd(-1),
    _controlfd(-1),
    _quality(QUALITY_HIGH),
    _alignMode(0),
    _allowScriptAccess(SCRIPT_ACCESS_SAME_DOMAIN),
    _showMenu(true),
    _scaleMode(SCALEMODE_SHOWALL),
    _displayState(DISPLAYSTATE_NORMAL),
    _recursionLimit(256),
    _timeoutLimit(15),
    _movieAdvancementDelay(83), // ~12 fps by default
    _lastMovieAdvancement(0),
    _unnamedInstance(0),
    _movieLoader(*this)
{
    // This takes care of informing the renderer (if present) too.
    setQuality(QUALITY_HIGH);
}

void
movie_root::disableScripts()
{
    _disableScripts = true;

    // NOTE: we won't clear the action queue now
    // to avoid invalidating iterators as we've
    // been probably called during processing
    // of the queue.
}

movie_root::~movie_root()
{
    clear(_actionQueue);
    _intervalTimers.clear();
    _movieLoader.clear();

    assert(testInvariant());
}

Movie*
movie_root::init(movie_definition* def, const MovieClip::MovieVariables& vars)
{
    Movie* mr = def->createMovie(*_vm.getGlobal());
    mr->setVariables(vars);
    setRootMovie(mr);
    return mr;
}

void
movie_root::setRootMovie(Movie* movie)
{
    _rootMovie = movie;

    const movie_definition* md = movie->definition();
    float fps = md->get_frame_rate();
    _movieAdvancementDelay = static_cast<int>(1000/fps);

    _lastMovieAdvancement = _vm.getTime();

    _stageWidth = static_cast<int>(md->get_width_pixels());
    _stageHeight = static_cast<int>(md->get_height_pixels());

    // assert(movie->get_depth() == 0); ?
    movie->set_depth(DisplayObject::staticDepthOffset);

    try {
        setLevel(0, movie);

        // actions in first frame of _level0 must execute now,
        // before next advance,
        // or they'll be executed with _currentframe being set to 2
        processActionQueue();
    }
    catch (const ActionLimitException& al) {
        boost::format fmt = boost::format(_("ActionLimits hit during "
                    "setRootMovie: %s. Disable scripts?")) % al.what();
        handleActionLimitHit(fmt.str());
    }
    catch (const ActionParserException& e) {
        log_error("ActionParserException thrown during setRootMovie: %s",
                e.what());
    }

    cleanupAndCollect();
}

void
movie_root::handleActionLimitHit(const std::string& msg)
{
    bool disable = true;
    if (_interfaceHandler) {
        disable = callInterface<bool>(HostMessage(HostMessage::QUERY, msg));
    }
    else {
        log_error("No user interface registered, assuming 'Yes' answer to "
            "question: %s", msg);
    }
    if (disable) {
        disableScripts();
        clear(_actionQueue);
    }
}

void
movie_root::cleanupAndCollect()
{
    // Cleanup the stack.
    _vm.getStack().clear();

    cleanupDisplayList();
    _gc.fuzzyCollect();
}

/* private */
void
movie_root::setLevel(unsigned int num, Movie* movie)
{
    assert(movie != NULL);
    assert(static_cast<unsigned int>(movie->get_depth()) ==
                            num + DisplayObject::staticDepthOffset);


    Levels::iterator it = _movies.find(movie->get_depth());
    if (it == _movies.end()) {
        _movies[movie->get_depth()] = movie; 
    }
    else {
        // don't leak overloaded levels

        MovieClip* lm = it->second;
        if (lm == _rootMovie) {
            // NOTE: this is not enough to trigger
            //       an application reset. Was tested
            //       but not automated. If curious
            //       use swapDepths against _level0
            //       and load into the new target while
            //       a timeout/interval is active.
            log_debug("Replacing starting movie");
        }

        if (num == 0) {

            log_debug("Loading into _level0");

            // NOTE: this was tested but not automated, the
            //       test sets an interval and then loads something
            //       in _level0. The result is the interval is disabled.
            _intervalTimers.clear();

            // TODO: check what else we should do in these cases 
            //       (like, unregistering all childs etc...)
            //       Tested, but not automated, is that other
            //       levels should be maintained alive.
            // Sat Nov 14 10:31:19 CET 2009
            // ^^^ not confirmed in this date, I think other levels 
            //     are dropped too! (strk)

            _stageWidth = movie->widthPixels();
            _stageHeight = movie->heightPixels();

            // notify  stage replacement
            if (_interfaceHandler) {
                const HostMessage e(HostMessage::RESIZE_STAGE,
                        std::make_pair(_stageWidth, _stageHeight));
                _interfaceHandler->call(e);
            }
        }

        it->second->destroy();
        it->second = movie;
    }

    movie->set_invalidated();
    
    /// Notify placement 
    movie->construct();

    assert(testInvariant());
}

void
movie_root::swapLevels(MovieClip* movie, int depth)
{
    assert(movie);

//#define GNASH_DEBUG_LEVELS_SWAPPING 1

    const int oldDepth = movie->get_depth();

#ifdef GNASH_DEBUG_LEVELS_SWAPPING
    log_debug("Before swapLevels (source depth %d, target depth %d) "
            "levels are: ", oldDepth, depth);
    for (Levels::const_iterator i=_movies.begin(), e=_movies.end(); i!=e; ++i) {
        log_debug(" %d: %p (%s @ depth %d)", i->first,
                (void*)(i->second), i->second->getTarget(),
                i->second->get_depth());
    }
#endif
    // should include _level0 !
    if (oldDepth < DisplayObject::staticDepthOffset) {
        IF_VERBOSE_ASCODING_ERRORS(
        log_aserror(_("%s.swapDepth(%d): movie has a depth (%d) below "
                "static depth zone (%d), won't swap its depth"),
                movie->getTarget(), depth, oldDepth,
                DisplayObject::staticDepthOffset);
        );
        return;
    }

    if (oldDepth >= 0) {
        IF_VERBOSE_ASCODING_ERRORS(
        log_aserror(_("%s.swapDepth(%d): movie has a depth (%d) below "
                "static depth zone (%d), won't swap its depth"),
                movie->getTarget(), depth, oldDepth,
                DisplayObject::staticDepthOffset);
        );
        return;
    }

    const int oldNum = oldDepth; 
    Levels::iterator oldIt = _movies.find(oldNum);
    if (oldIt == _movies.end()) {
        log_debug("%s.swapDepth(%d): target depth (%d) contains no movie",
            movie->getTarget(), depth, oldNum);
        return;
    }

    const int newNum = depth; 
    movie->set_depth(depth);
    Levels::iterator targetIt = _movies.find(newNum);
    if (targetIt == _movies.end()) {
        _movies.erase(oldIt);
        _movies[newNum] = movie;
    }
    else {
        MovieClip* otherMovie = targetIt->second;
        otherMovie->set_depth(oldDepth);
        oldIt->second = otherMovie;
        targetIt->second = movie;
    }
    
#ifdef GNASH_DEBUG_LEVELS_SWAPPING
    log_debug("After swapLevels levels are: ");
    for (Levels::const_iterator i=_movies.begin(), e=_movies.end(); i!=e; ++i) {
        log_debug(" %d: %p (%s @ depth %d)", i->first, 
                (void*)(i->second), i->second->getTarget(),
                i->second->get_depth());
    }
#endif
    
    // TODO: invalidate self, not the movie
    //       movie_root::setInvalidated() seems
    //       to do just that, if anyone feels
    //       like more closely research on this
    //       (does level swapping require full redraw always?)
    movie->set_invalidated();
    
    assert(testInvariant());
}

void
movie_root::dropLevel(int depth)
{
    // should be checked by caller
    // TODO: don't use a magic number! See MovieClip::removeMovieClip().
    assert(depth >= 0 && depth <= 1048575);

    Levels::iterator it = _movies.find(depth);
    if (it == _movies.end()) {
        log_error("movie_root::dropLevel called against a movie not "
                "found in the levels container");
        return;
    }

    MovieClip* mo = it->second;
    if (mo == _rootMovie) {
        IF_VERBOSE_ASCODING_ERRORS(
            log_aserror(_("Original root movie can't be removed"));
        );
        return;
    }

    // TOCHECK: safe to erase here ?
    mo->unload();
    mo->destroy();
    _movies.erase(it);

    assert(testInvariant());
}

void
movie_root::replaceLevel(unsigned int num, Movie* extern_movie)
{
    extern_movie->set_depth(num + DisplayObject::staticDepthOffset);
    Levels::iterator it = _movies.find(extern_movie->get_depth());
    if (it == _movies.end()) {
        log_error("TESTME: loadMovie called on level %d which is not "
                "available at load time, skipped placement for now");
        return; 
    }

    // TODO: rework this to avoid the double scan 
    setLevel(num, extern_movie);
}

MovieClip*
movie_root::getLevel(unsigned int num) const
{
    Levels::const_iterator i =
        _movies.find(num + DisplayObject::staticDepthOffset);

    if (i == _movies.end()) return 0;

    return i->second;
}

void
movie_root::reset()
{
    sound::sound_handler* sh = _runResources.soundHandler();
    if (sh) sh->reset();

    // reset background color, to allow 
    // next load to set it again.
    m_background_color.set(255, 255, 255, 255);
    m_background_color_set = false;

    // wipe out live chars
    _liveChars.clear();

    // wipe out queued actions
    clear(_actionQueue);

    // wipe out all levels
    _movies.clear();

    // remove all intervals
    _intervalTimers.clear();

    // remove all loadMovie requests
    _movieLoader.clear();

    // remove key listeners
    _keyListeners.clear();

    // Cleanup the stack.
    _vm.getStack().clear();

    // Run the garbage collector again
    _gc.fuzzyCollect();

    setInvalidated();

    _disableScripts = false;
}

void
movie_root::setDimensions(size_t w, size_t h)
{
    assert(testInvariant());

    _stageWidth = w;
    _stageHeight = h;

    if (_scaleMode == SCALEMODE_NOSCALE) {
        as_object* stage = getBuiltinObject(*this,
            getURI(_vm, NSV::CLASS_STAGE));
        if (stage) {
            callMethod(stage, getURI(_vm, NSV::PROP_BROADCAST_MESSAGE),
                "onResize");
        }

    }

    assert(testInvariant());
}

bool
movie_root::mouseMoved(boost::int32_t x, boost::int32_t y)
{
    assert(testInvariant());

    _mouseX = x;
    _mouseY = y;
    return notify_mouse_listeners(event_id(event_id::MOUSE_MOVE));
}


bool
movie_root::keyEvent(key::code k, bool down)
{
    _lastKeyEvent = k;
    const size_t keycode = key::codeMap[k][key::KEY];
    if (keycode < key::KEYCOUNT) {
        _unreleasedKeys.set(keycode, down);
    }

    LiveChars copy = _liveChars;
    for (LiveChars::iterator iter = copy.begin(), itEnd=copy.end();
            iter != itEnd; ++iter) {

        // sprite, button & input_edit_text DisplayObjects
        InteractiveObject* const ch = *iter;
        if (ch->unloaded()) continue;

        if (down) {
            ch->notifyEvent(event_id(event_id::KEY_DOWN, key::INVALID)); 
            ch->notifyEvent(event_id(event_id::KEY_PRESS, k));
        }
        else {
            ch->notifyEvent(event_id(event_id::KEY_UP, key::INVALID));   
        }
    }

    // Broadcast event to Key._listeners.
    as_object* key = getBuiltinObject(*this, getURI(_vm, NSV::CLASS_KEY));
    if (key) {

        try {
            // Can throw an action limit exception if the stack limit is 0 or 1,
            // i.e. if the stack is at the limit before it contains anything.
            // A stack limit like that is hardly of any use, but could be used
            // maliciously to crash Gnash.
            if (down) {
                callMethod(key, getURI(_vm, NSV::PROP_BROADCAST_MESSAGE), "onKeyDown");
            }
            else {
                callMethod(key, getURI(_vm,NSV::PROP_BROADCAST_MESSAGE), "onKeyUp");
            }
        }
        catch (const ActionLimitException &e) {
            log_error(_("ActionLimits hit notifying key listeners: %s."),
                    e.what());
            clear(_actionQueue);
        }
    }
    
    // Then any button keys are notified.
    Listeners lcopy = _keyListeners;
    for (Listeners::iterator iter = lcopy.begin(), itEnd = lcopy.end();
            iter != itEnd; ++iter) {

        // sprite, button & input_edit_text DisplayObjects
        Button* const ch = *iter;
        if (ch->unloaded()) continue;

        if (down) {
            ch->notifyEvent(event_id(event_id::KEY_DOWN, key::INVALID)); 
            ch->notifyEvent(event_id(event_id::KEY_PRESS, k));
        }
        else {
            ch->notifyEvent(event_id(event_id::KEY_UP, key::INVALID));   
        }
    }


    // If we're focused on an editable text field, finally the text is updated
    if (down) {
        TextField* tf = dynamic_cast<TextField*>(_currentFocus);
        if (tf) tf->notifyEvent(event_id(event_id::KEY_PRESS, k));
    }

    processActionQueue();

    return false; 
}

bool
movie_root::mouseWheel(int delta)
{
    as_object* mouseObj = 
        getBuiltinObject(*this, getURI(_vm, NSV::CLASS_MOUSE));
    if (!mouseObj) return false;
    
    const boost::int32_t x = pixelsToTwips(_mouseX);
    const boost::int32_t y = pixelsToTwips(_mouseY);

    DisplayObject* i = getTopmostMouseEntity(x, y);

    // Always called with two arguments.
    callMethod(mouseObj, getURI(_vm,NSV::PROP_BROADCAST_MESSAGE), "onMouseWheel",
            delta, i ? getObject(i) : as_value());

    return true;
}

bool
movie_root::mouseClick(bool mouse_pressed)
{
    assert(testInvariant());

    _mouseButtonState.isDown = mouse_pressed;

    if (mouse_pressed) {
        return notify_mouse_listeners(event_id(event_id::MOUSE_DOWN));
    }
    return notify_mouse_listeners(event_id(event_id::MOUSE_UP));
}


bool
movie_root::fire_mouse_event()
{

    assert(testInvariant());

    boost::int32_t x = pixelsToTwips(_mouseX);
    boost::int32_t y = pixelsToTwips(_mouseY);

    // Generate a mouse event
    _mouseButtonState.topmostEntity = getTopmostMouseEntity(x, y);

    // Set _droptarget if dragging a sprite
    MovieClip* dragging = 0;
    DisplayObject* draggingChar = getDraggingCharacter();
    if (draggingChar) dragging = draggingChar->to_movie();
    if (dragging) {
        // TODO: optimize making findDropTarget and getTopmostMouseEntity
        //       use a single scan.
        const DisplayObject* dropChar = findDropTarget(x, y, dragging);
        if (dropChar) {
            // Use target of closest script DisplayObject containing this
            dropChar = getNearestObject(dropChar);
            dragging->setDropTarget(dropChar->getTargetPath());
        }
        else dragging->setDropTarget("");

    }

    bool need_redraw = false;

    // FIXME: need_redraw might also depend on actual
    //        actions execution (consider updateAfterEvent).

    try {
        need_redraw = generate_mouse_button_events(*this, _mouseButtonState);
        processActionQueue();
    }
    catch (const ActionLimitException& al) {
        boost::format fmt = boost::format(_("ActionLimits hit during mouse "
                    "event processing: %s. Disable scripts ?")) % al.what();
        handleActionLimitHit(fmt.str());
    }

    return need_redraw;

}

std::pair<boost::int32_t, boost::int32_t>
movie_root::mousePosition() const
{
    assert(testInvariant());
    return std::make_pair(_mouseX, _mouseY);
}

void
movie_root::setDragState(const DragState& st)
{
    _dragState = st;
    DisplayObject* ch = st.getCharacter();
    if (ch && !st.isLockCentered()) {
        // Get coordinates of the DisplayObject's origin
        point origin(0, 0);
        SWFMatrix chmat = getWorldMatrix(*ch);
        point world_origin;
        chmat.transform(&world_origin, origin);

        // Get current mouse coordinates
        point world_mouse(pixelsToTwips(_mouseX), pixelsToTwips(_mouseY));

        boost::int32_t xoffset = world_mouse.x - world_origin.x;
        boost::int32_t yoffset = world_mouse.y - world_origin.y;

        _dragState.setOffset(xoffset, yoffset);
    }
    assert(testInvariant());
}

void
movie_root::doMouseDrag()
{
    DisplayObject* dragChar = getDraggingCharacter(); 
    if (!dragChar) return; // nothing to do

    if (dragChar->unloaded()) {
        // Reset drag state if dragging char was unloaded
        _dragState.reset();
        return; 
    }

    point world_mouse(pixelsToTwips(_mouseX), pixelsToTwips(_mouseY));

    SWFMatrix parent_world_mat;
    DisplayObject* p = dragChar->parent();
    if (p) {
        parent_world_mat = getWorldMatrix(*p);
    }

    if (!_dragState.isLockCentered()) {
        world_mouse.x -= _dragState.xOffset();
        world_mouse.y -= _dragState.yOffset();
    }

    if (_dragState.hasBounds()) {
        SWFRect bounds;
        // bounds are in local coordinate space
        bounds.enclose_transformed_rect(parent_world_mat,
                _dragState.getBounds());
        // Clamp mouse coords within a defined SWFRect.
        bounds.clamp(world_mouse);
    }

    parent_world_mat.invert().transform(world_mouse);            
    // Place our origin so that it coincides with the mouse coords
    // in our parent frame.
    // TODO: add a DisplayObject::set_translation ?
    SWFMatrix local = getMatrix(*dragChar);
    local.set_translation(world_mouse.x, world_mouse.y);
     
    // no need to update caches when only changing translation
    dragChar->setMatrix(local);
}

boost::uint32_t
movie_root::addIntervalTimer(std::auto_ptr<Timer> timer)
{
    assert(timer.get());
    assert(testInvariant());
            
    const size_t id = ++_lastTimerId;

    assert(_intervalTimers.find(id) == _intervalTimers.end());

    boost::shared_ptr<Timer> t(timer);
    _intervalTimers.insert(std::make_pair(id, t));

    return id;
}
    
bool
movie_root::clearIntervalTimer(boost::uint32_t x)
{
    TimerMap::iterator it = _intervalTimers.find(x);
    if (it == _intervalTimers.end()) return false;

    // We do not remove the element here because
    // we might have been called during execution
    // of another timer, thus during a scan of the _intervalTimers
    // container. If we use erase() here, the iterators in executeTimers
    // would be invalidated. Rather, executeTimers() would check container
    // elements for being still active and remove the cleared one in a safe way
    // at each iteration.
    it->second->clearInterval();

    return true;

}

bool
movie_root::advance()
{
    // We can't actually rely on now being later than _lastMovieAdvancement,
    // so we will have to check. Otherwise we risk elapsed being
    // contructed from a negative value.
    const size_t now = std::max<size_t>(_vm.getTime(), _lastMovieAdvancement);

    bool advanced = false;

    try {

        const size_t elapsed = now - _lastMovieAdvancement;
        if (elapsed >= _movieAdvancementDelay)
        {
            advanced = true;
            advanceMovie();

            // To catch-up lateness we pretend we advanced when 
            // was time for it. 
            // NOTE:
            //   now - _lastMovieAdvancement
            // gives you actual lateness in milliseconds
            //
            // TODO: make 'catchup' setting user-settable
            //       as it helps A/V sync but sacrifices 
            //       smoothness of animation which is very
            //       important for games.
            static const bool catchup = true;
            if (catchup) {
                _lastMovieAdvancement += _movieAdvancementDelay;
            } else {
                _lastMovieAdvancement = now;
            }

        }

        //log_debug("Lateness: %d", now-_lastMovieAdvancement);
        
        executeAdvanceCallbacks();
        
        executeTimers();
    
    }
    catch (const ActionLimitException& al) {
        // The PP does not disable scripts when the stack limit is reached,
        // but rather struggles on. 
        log_error(_("Action limit hit during advance: %s"), al.what());
        clear(_actionQueue);
    }
    catch (const ActionParserException& e) {
        log_error(_("Buffer overread during advance: %s"), e.what());
        clear(_actionQueue);
    }

    return advanced;
}
    
void
movie_root::advanceMovie()
{

    // Do mouse drag, if needed
    doMouseDrag();

    // Advance all non-unloaded DisplayObjects in the LiveChars list
    // in reverse order (last added, first advanced)
    // NOTE: can throw ActionLimitException
    advanceLiveChars(); 

    // Process loadMovie requests
    // 
    // NOTE: should be done before executing timers,
    //      see swfdec's test/trace/loadmovie-case-{5,6}.swf 
    // NOTE: processing loadMovie requests after advanceLiveChars
    //       is known to fix more tests in misc-mtasc.all/levels.swf
    //       to be checked if it keeps the swfdec testsuite safe
    //
    _movieLoader.processCompletedRequests();

    // Process queued actions
    // NOTE: can throw ActionLimitException
    processActionQueue();

    cleanupAndCollect();

    assert(testInvariant());
}

int
movie_root::timeToNextFrame() const
{
    unsigned int now = _vm.getTime();
    const int elapsed = now - _lastMovieAdvancement;
    return _movieAdvancementDelay - elapsed;
}

void
movie_root::display()
{
//    GNASH_REPORT_FUNCTION;

    assert(testInvariant());

    clearInvalidated();

    // TODO: should we consider the union of all levels bounds ?
    const SWFRect& frame_size = _rootMovie->get_frame_size();
    if ( frame_size.is_null() )
    {
        // TODO: check what we should do if other levels
        //       have valid bounds
        log_debug("original root movie had null bounds, not displaying");
        return;
    }

    Renderer* renderer = _runResources.renderer();
    if (!renderer) return;

    Renderer::External ex(*renderer, m_background_color,
            _stageWidth, _stageHeight,
            frame_size.get_x_min(), frame_size.get_x_max(),
            frame_size.get_y_min(), frame_size.get_y_max());

    for (Levels::iterator i=_movies.begin(), e=_movies.end(); i!=e; ++i) {
        MovieClip* movie = i->second;

        movie->clear_invalidated();

        if (movie->visible() == false) continue;

        // null frame size ? don't display !
        const SWFRect& sub_frame_size = movie->get_frame_size();

        if (sub_frame_size.is_null()) {
            log_debug("_level%u has null frame size, skipping", i->first);
            continue;
        }

        movie->display(*renderer, Transform());

    }
}

bool
movie_root::notify_mouse_listeners(const event_id& event)
{

    LiveChars copy = _liveChars;
    for (LiveChars::iterator iter = copy.begin(), itEnd=copy.end();
            iter != itEnd; ++iter)
    {
        MovieClip* const ch = *iter;
        if (!ch->unloaded()) {
            ch->mouseEvent(event);
        }
    }

    const ObjectURI& propMouse = getURI(_vm, NSV::CLASS_MOUSE);
    const ObjectURI& propBroadcastMessage =
        getURI(_vm, NSV::PROP_BROADCAST_MESSAGE);

    as_object* mouseObj = getBuiltinObject(*this, propMouse);
    if (mouseObj) {

        // Can throw an action limit exception if the stack limit is 0 or 1.
        // A stack limit like that is hardly of any use, but could be used
        // maliciously to crash Gnash.
        try {
            callMethod(mouseObj, propBroadcastMessage, event.functionName());
        }
        catch (ActionLimitException &e) {
            log_error(_("ActionLimits hit notifying mouse events: %s."),
                    e.what());
            clear(_actionQueue);
        }
        
    }

    assert(testInvariant());

    if (!copy.empty()) {
        // process actions queued in the above step
        processActionQueue();
    }
    return fire_mouse_event();
}

DisplayObject*
movie_root::getFocus()
{
    assert(testInvariant());
    return _currentFocus;
}

bool
movie_root::setFocus(DisplayObject* to)
{

    // Nothing to do if current focus is the same as the new focus. 
    // _level0 also seems unable to receive focus under any circumstances
    // TODO: what about _level1 etc ?
    if (to == _currentFocus ||
            to == static_cast<DisplayObject*>(_rootMovie)) {
        return false;
    }

    if (to && !to->handleFocus()) {
        // TODO: not clear whether to remove focus in this case.
        return false;
    }

    // Undefined or NULL DisplayObject removes current focus. Otherwise, try
    // setting focus to the new DisplayObject. If it fails, remove current
    // focus anyway.

    // Store previous focus, as the focus needs to change before onSetFocus
    // is called and listeners are notified.
    DisplayObject* from = _currentFocus;

    if (from) {
        // Perform any actions required on killing focus (only TextField).
        from->killFocus();

        /// A valid focus must have an associated object.
        assert(getObject(from));
        callMethod(getObject(from), NSV::PROP_ON_KILL_FOCUS, getObject(to));
    }

    _currentFocus = to;

    if (to) {
        assert(getObject(to));
        callMethod(getObject(to), NSV::PROP_ON_SET_FOCUS, getObject(from));
    }

    as_object* sel = getBuiltinObject(*this, NSV::CLASS_SELECTION);

    // Notify Selection listeners with previous and new focus as arguments.
    // Either argument may be null.
    if (sel) {
        callMethod(sel, NSV::PROP_BROADCAST_MESSAGE, "onSetFocus",
                getObject(from), getObject(to));
    }

    assert(testInvariant());

    return true;
}

DisplayObject*
movie_root::getActiveEntityUnderPointer() const
{
    return _mouseButtonState.activeEntity;
}

DisplayObject*
movie_root::getDraggingCharacter() const
{
    return _dragState.getCharacter();
}

const DisplayObject*
movie_root::getEntityUnderPointer() const
{
    const boost::int32_t x = pixelsToTwips(_mouseX);
    const boost::int32_t y = pixelsToTwips(_mouseY);
    return findDropTarget(x, y, getDraggingCharacter()); 
}


void
movie_root::setQuality(Quality q)
{
    gnash::RcInitFile& rcfile = gnash::RcInitFile::getDefaultInstance();

    /// Overridden quality if not negative.
    if (rcfile.qualityLevel() >= 0) {
        int ql = rcfile.qualityLevel();
        ql = std::min<int>(ql, QUALITY_BEST);
        q = static_cast<Quality>(ql);
    }

    if ( _quality != q )
    {
        // Force a redraw if quality changes
        //
        // redraw should only happen on next
        // frame advancement (tested)
        //
        setInvalidated();

        _quality = q;
    }

    // We always tell the renderer, because it could
    // be the first time we do
    Renderer* renderer = _runResources.renderer();
    if (renderer) renderer->setQuality(_quality);

}

/// Get actionscript width of stage, in pixels. The width
/// returned depends on the scale mode.
size_t
movie_root::getStageWidth() const
{
    if (_scaleMode == SCALEMODE_NOSCALE) {
        return _stageWidth;    
    }

    // If scaling is allowed, always return the original movie size.
    if (_rootMovie) {
        return static_cast<size_t>(_rootMovie->widthPixels());
    } else {
        return 0;
    }
}

/// Get actionscript height of stage, in pixels. The height
/// returned depends on the scale mode.
size_t
movie_root::getStageHeight() const
{
    if (_scaleMode == SCALEMODE_NOSCALE) {
        return _stageHeight;    
    }

    // If scaling is allowed, always return the original movie size.
    if (_rootMovie) {
        return static_cast<size_t>(_rootMovie->heightPixels());
    } else {
        return 0;
    }
}

/// Takes a short int bitfield: the four bits correspond
/// to the AlignMode enum 
void
movie_root::setStageAlignment(short s)
{
    _alignMode = s;
    callInterface(HostMessage(HostMessage::UPDATE_STAGE));
}

/// The mode is one of never, always, with sameDomain the default
void
movie_root::setAllowScriptAccess(AllowScriptAccessMode mode)
{
    _allowScriptAccess = mode;
}

movie_root::AllowScriptAccessMode
movie_root::getAllowScriptAccess()
{
    return _allowScriptAccess;
}

/// Returns a pair of enum values giving the actual alignment
/// of the stage after align mode flags are evaluated.
movie_root::StageAlign
movie_root::getStageAlignment() const
{
    /// L takes precedence over R. Default is centred.
    StageHorizontalAlign ha = STAGE_H_ALIGN_C;
    if (_alignMode.test(STAGE_ALIGN_L)) ha = STAGE_H_ALIGN_L;
    else if (_alignMode.test(STAGE_ALIGN_R)) ha = STAGE_H_ALIGN_R;

    /// T takes precedence over B. Default is centred.
    StageVerticalAlign va = STAGE_V_ALIGN_C;
    if (_alignMode.test(STAGE_ALIGN_T)) va = STAGE_V_ALIGN_T;
    else if (_alignMode.test(STAGE_ALIGN_B)) va = STAGE_V_ALIGN_B;

    return std::make_pair(ha, va);
}

/// Returns a string that represents the boolean state of the _showMenu
/// variable
bool
movie_root::getShowMenuState() const
{
    return _showMenu;
}

/// Sets the value of _showMenu and calls the gui handler to process the 
/// fscommand to change the display of the context menu
void
movie_root::setShowMenuState(bool state)
{
    _showMenu = state;
    //FIXME: The gui code for show menu is semantically different than what
    //   ActionScript expects it to be. In gtk.cpp the showMenu function hides
    //   or shows the menubar. Flash expects this option to disable some 
    //   context menu items.
    // callInterface is the proper handler for this
    callInterface(HostMessage(HostMessage::SHOW_MENU, _showMenu)); 
}

/// Returns the string representation of the current align mode,
/// which must always be in the order: LTRB
std::string
movie_root::getStageAlignMode() const
{
    std::string align;
    if (_alignMode.test(STAGE_ALIGN_L)) align.push_back('L');
    if (_alignMode.test(STAGE_ALIGN_T)) align.push_back('T');
    if (_alignMode.test(STAGE_ALIGN_R)) align.push_back('R');
    if (_alignMode.test(STAGE_ALIGN_B)) align.push_back('B');
    
    return align;
}

void
movie_root::setStageScaleMode(ScaleMode sm)
{
    if (_scaleMode == sm) return; // nothing to do

    bool notifyResize = false;
    
    // If we go from or to noScale, we notify a resize
    // if and only if display viewport is != then actual
    // movie size. If there is not yet a _rootMovie (when scaleMode
    // is passed as a parameter to the player), we also don't notify a 
    // resize.
    if (_rootMovie && 
            (sm == SCALEMODE_NOSCALE || _scaleMode == SCALEMODE_NOSCALE)) {

        const movie_definition* md = _rootMovie->definition();
        log_debug("Going to or from scaleMode=noScale. Viewport:%dx%d "
                "Def:%dx%d", _stageWidth, _stageHeight,
                md->get_width_pixels(), md->get_height_pixels());

        if ( _stageWidth != md->get_width_pixels()
             || _stageHeight != md->get_height_pixels() )
        {
            notifyResize = true;
        }
    }

    _scaleMode = sm;
    callInterface(HostMessage(HostMessage::UPDATE_STAGE));

    if (notifyResize) {
        as_object* stage = getBuiltinObject(*this, NSV::CLASS_STAGE);
        if (stage) {
            callMethod(stage, NSV::PROP_BROADCAST_MESSAGE, "onResize");
        }
    }
}

void
movie_root::setStageDisplayState(const DisplayState ds)
{
    _displayState = ds;

    as_object* stage = getBuiltinObject(*this, NSV::CLASS_STAGE);
    if (stage) {
        const bool fs = _displayState == DISPLAYSTATE_FULLSCREEN;
        callMethod(stage, NSV::PROP_BROADCAST_MESSAGE, "onFullScreen", fs);
    }

    if (!_interfaceHandler) return; // No registered callback
    
    HostMessage e(HostMessage::SET_DISPLAYSTATE, _displayState);
    callInterface(e);
}

void
movie_root::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
{
    if (isInvalidated()) {
        ranges.setWorld();
        return;
    }

    for (Levels::reverse_iterator i=_movies.rbegin(), e=_movies.rend(); i!=e;
                        ++i) {
        i->second->add_invalidated_bounds(ranges, force);
    }

}

size_t
movie_root::minPopulatedPriorityQueue() const
{
    for (size_t l = 0; l < PRIORITY_SIZE; ++l) {
        if (!_actionQueue[l].empty()) return l;
    }
    return PRIORITY_SIZE;
}

size_t
movie_root::processActionQueue(size_t lvl)
{
    ActionQueue::value_type& q = _actionQueue[lvl];

    assert(minPopulatedPriorityQueue() == lvl);

#ifdef GNASH_DEBUG
    static unsigned calls=0;
    ++calls;
    bool actionsToProcess = !q.empty();
    if (actionsToProcess) {
        log_debug(" Processing %d actions in priority queue %d (call %u)",
                    q.size(), lvl, calls);
    }
#endif

    // _actionQueue may be changed due to actions (appended-to)
    // this loop might be optimized by using an iterator
    // and a final call to .clear() 
    while (!q.empty()) {

        std::auto_ptr<ExecutableCode> code(q.pop_front().release());
        code->execute();

        size_t minLevel = minPopulatedPriorityQueue();
        if (minLevel < lvl) {
#ifdef GNASH_DEBUG
            log_debug(" Actions pushed in priority %d (< "
                    "%d), restarting the scan (call"
                    " %u)", minLevel, lvl, calls);
#endif
            return minLevel;
        }
    }

    assert(q.empty());

#ifdef GNASH_DEBUG
    if (actionsToProcess) {
        log_debug(" Done processing actions in priority queue "
                "%d (call %u)", lvl, calls);
    }
#endif

    return minPopulatedPriorityQueue();
}

void
movie_root::flushHigherPriorityActionQueues()
{
    if (!processingActions()) {
        // only flush the actions queue when we are 
        // processing the queue.
        // ie. we don't want to flush the queue 
        // during executing user event handlers,
        // which are not pushed at the moment.
        return;
    }

    if (_disableScripts) {
        /// cleanup anything pushed later..
        clear(_actionQueue);
        return;
    }

    int lvl=minPopulatedPriorityQueue();
    while (lvl < _processingActionLevel) {
        lvl = processActionQueue(lvl);
    }

}

void
movie_root::addLoadableObject(as_object* obj, std::auto_ptr<IOChannel> str)
{
    boost::shared_ptr<IOChannel> io(str.release());
    _loadCallbacks.push_back(LoadCallback(io, obj));
}

void
movie_root::addAdvanceCallback(ActiveRelay* obj)
{
    _objectCallbacks.insert(obj);
}

void
movie_root::removeAdvanceCallback(ActiveRelay* obj)
{
    _objectCallbacks.erase(obj);
}

void
movie_root::processActionQueue()
{
    if (_disableScripts) {
        /// cleanup anything pushed later..
        clear(_actionQueue);
        return;
    }

    _processingActionLevel = minPopulatedPriorityQueue();

    while (_processingActionLevel < PRIORITY_SIZE) {
        _processingActionLevel = processActionQueue(_processingActionLevel);
    }

    // Cleanup the stack.
    _vm.getStack().clear();

}

void
movie_root::removeQueuedConstructor(DisplayObject* target)
{
    ActionQueue::value_type& pr = _actionQueue[PRIORITY_CONSTRUCT];
    pr.erase_if(RemoveTargetCode(target));
}

void
movie_root::pushAction(std::auto_ptr<ExecutableCode> code, size_t lvl)
{
    assert(lvl < PRIORITY_SIZE);
    _actionQueue[lvl].push_back(code);
}

void
movie_root::pushAction(const action_buffer& buf, DisplayObject* target)
{
#ifdef GNASH_DEBUG
    log_debug("Pushed action buffer for target %s", 
            target->getTargetPath());
#endif

    std::auto_ptr<ExecutableCode> code(new GlobalCode(buf, target));

    _actionQueue[PRIORITY_DOACTION].push_back(code);
}

void
movie_root::executeAdvanceCallbacks()
{

    if (!_objectCallbacks.empty()) {

        // We have two considerations:
        // 1. any update can change the active callbacks by removing or
        //    adding to the original callbacks list.
        // 2. Additionally, an as_object may destroy its own Relay. This can
        //    happen if the callback itself calls a native constructor on
        //    an object that already has a Relay. If this is an ActiveRelay
        //    registered with movie_root, a pointer to the destroyed object
        //    will still be held, resulting in memory corruption. This is an
        //    *extremely* unlikely case, but we are very careful!
        //
        // By copying to a new container we avoid errors caused by changes to
        // the original set (such as infinite recursions or invalidated
        // iterators). We also know that no as_object will be destroyed
        // during processing, even though its Relay may be.
        std::vector<as_object*> currentCallbacks;

        std::transform(_objectCallbacks.begin(), _objectCallbacks.end(),
            std::back_inserter(currentCallbacks),
            boost::bind(CreatePointer<as_object>(),
                boost::bind(std::mem_fun(&ActiveRelay::owner), _1)));

        std::for_each(currentCallbacks.begin(), currentCallbacks.end(),
                ExecuteCallback());
    }

    if (!_loadCallbacks.empty()) {
        _loadCallbacks.remove_if(
                std::mem_fun_ref(&movie_root::LoadCallback::processLoad));
    }

    // _controlfd is set when running as a child process of a hosting
    // application. If it is set, we have to check the socket connection
    // for XML messages.
    if (_controlfd) {
    boost::shared_ptr<ExternalInterface::invoke_t> invoke = 
        ExternalInterface::ExternalEventCheck(_controlfd);
        if (invoke) {
            if (processInvoke(invoke.get()) == false) {
                if (!invoke->name.empty()) {
                    log_error("Couldn't process ExternalInterface Call %s",
                          invoke->name);
                }
            }
        }    
    }
    
    processActionQueue();
}

bool
movie_root::processInvoke(ExternalInterface::invoke_t *invoke)
{
    GNASH_REPORT_FUNCTION;

    if (!invoke || invoke->name.empty()) return false;

    log_debug("Processing %s call from the Browser.", invoke->name);

    std::stringstream ss;       // ss is the response string

    // These are the default methods used by ExternalInterface
    if (invoke->name == "Quit") {
        // Leave to the hosting application. If there isn't one or it 
        // chooses not to exit, that's fine.
        if (_interfaceHandler) _interfaceHandler->exit();

    } else if (invoke->name == "SetVariable") {
        MovieClip *mc = getLevel(0);
        as_object *obj = getObject(mc);
        VM &vm = getVM();
        std::string var = invoke->args[0].to_string();
        as_value &val = invoke->args[1] ;
        obj->set_member(getURI(vm, var), val);
    // SetVariable doesn't send a response
    } else if (invoke->name == "GetVariable") {
        MovieClip *mc = getLevel(0);
        as_object *obj = getObject(mc);
        VM &vm = getVM();
        std::string var = invoke->args[0].to_string();
        as_value val;
        obj->get_member(getURI(vm, var), &val);
    // GetVariable sends the value of the variable
    ss << ExternalInterface::toXML(val);
    } else if (invoke->name == "GotoFrame") {
        log_unimpl("ExternalInterface::GotoFrame()");
    // GotoFrame doesn't send a response
    } else if (invoke->name == "IsPlaying") {
        const bool result = 
            callInterface<bool>(HostMessage(HostMessage::EXTERNALINTERFACE_ISPLAYING));
        as_value val(result);
        ss << ExternalInterface::toXML(val);    
    } else if (invoke->name == "LoadMovie") {
        log_unimpl("ExternalInterface::LoadMovie()");
    // LoadMovie doesn't send a response
    } else if (invoke->name == "Pan") {
        std::string arg = invoke->args[0].to_string();
        arg += ":";
        arg += invoke->args[0].to_string();
        arg += ":";
        arg += invoke->args[1].to_string();
        arg += ":";
        arg += invoke->args[2].to_string();
        callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_PAN, arg));
    // Pan doesn't send a response
    } else if (invoke->name == "PercentLoaded") {
        MovieClip *mc = getLevel(0);
        int loaded = mc->get_bytes_loaded();
        int total = mc->get_bytes_total();
        as_value val((loaded/total) * 100);
        // PercentLoaded sends the percentage
        ss << ExternalInterface::toXML(val);    
    } else if (invoke->name == "Play") {
        callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_PLAY));
    // Play doesn't send a response
    } else if (invoke->name == "Rewind") {
        callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_REWIND));
    // Rewind doesn't send a response
    } else if (invoke->name == "SetZoomRect") {
        std::string arg = invoke->args[0].to_string();
        arg += ":";
        arg += invoke->args[0].to_string();
        arg += ":";
        arg += invoke->args[1].to_string();
        arg += ":";
        arg += invoke->args[2].to_string();
        arg += ":";
        arg += invoke->args[3].to_string();
        callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_SETZOOMRECT, arg));
    // SetZoomRect doesn't send a response
    } else if (invoke->name == "StopPlay") {
        callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_STOPPLAY));
    // StopPlay doesn't send a response
    } else if (invoke->name == "Zoom") {
        std::string var = invoke->args[0].to_string();
        callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_ZOOM, var));
    // Zoom doesn't send a response
    } else if (invoke->name == "TotalFrames") {
        MovieClip *mc = getLevel(0);
        as_value val(mc->get_loaded_frames());
    // TotalFrames sends the number of frames in the movie
        ss << ExternalInterface::toXML(val);
    } else {
        std::string result = callExternalCallback(invoke->name, invoke->args);
        if (result == ExternalInterface::makeString("Error")) {
            return false;
        } else if (result == ExternalInterface::makeString("SecurityError")) {
            return false;
        }
        return true;
    }

    if (!ss.str().empty()) {
        if (_hostfd >= 0) {
            log_debug(_("Attempt to write response to ExternalInterface "
                        "requests fd %d"), _hostfd);
            int ret = write(_hostfd, ss.str().c_str(), ss.str().size());
            if (ret == -1) {
            log_error(_("Could not write to user-provided host requests "
                    "fd %d: %s"), _hostfd, std::strerror(errno));
            }
        }
    } else {
        log_debug("No response needed for %s request", invoke->name);
    }

    return true;
}

void
movie_root::executeTimers()
{
#ifdef GNASH_DEBUG_TIMERS_EXPIRATION
        log_debug("Checking %d timers for expiry", _intervalTimers.size());
#endif

    unsigned long now = _vm.getTime();

    typedef std::multimap<unsigned int, boost::shared_ptr<Timer> >
        ExpiredTimers;

    ExpiredTimers expiredTimers;

    for (TimerMap::iterator it = _intervalTimers.begin(),
            itEnd = _intervalTimers.end(); it != itEnd; ) {

        TimerMap::iterator nextIterator = it;
        ++nextIterator;

        boost::shared_ptr<Timer> timer(it->second);

        if (timer->cleared()) {
            // this timer was cleared, erase it
            _intervalTimers.erase(it);
        }
        else {
            unsigned long elapsed;
            if (timer->expired(now, elapsed)) {
                expiredTimers.insert(std::make_pair(elapsed, timer));
            }
        }

        it = nextIterator;
    }

    foreachSecond(expiredTimers.begin(), expiredTimers.end(),
                  &Timer::executeAndReset);

    if (!expiredTimers.empty()) processActionQueue();

}

void
movie_root::markReachableResources() const
{
    _vm.markReachableResources();

    foreachSecond(_movies.rbegin(), _movies.rend(), &MovieClip::setReachable);

    // Mark original top-level movie
    // This should always be in _movies, but better make sure
    if ( _rootMovie ) _rootMovie->setReachable();

    // Mark mouse entities 
    _mouseButtonState.markReachableResources();
    
    // Mark timer targets
    foreachSecond(_intervalTimers.begin(), _intervalTimers.end(),
                  &Timer::markReachableResources);

    std::for_each(_objectCallbacks.begin(), _objectCallbacks.end(),
            std::mem_fun(&ActiveRelay::setReachable));

    std::for_each(_loadCallbacks.begin(), _loadCallbacks.end(),
            std::mem_fun_ref(&movie_root::LoadCallback::setReachable));

    // Mark LoadMovieRequest handlers as reachable
    _movieLoader.setReachable();

    // Mark resources reachable by queued action code
    for (size_t lvl = 0; lvl < PRIORITY_SIZE; ++lvl)
    {
        const ActionQueue::value_type& q = _actionQueue[lvl];
        std::for_each(q.begin(), q.end(),
                std::mem_fun_ref(&ExecutableCode::markReachableResources));
    }

    if (_currentFocus) _currentFocus->setReachable();

    // Mark DisplayObject being dragged, if any
    _dragState.markReachableResources();

    // NOTE: cleanupDisplayList() should have cleaned up all
    // unloaded live characters. The remaining ones should be marked
    // by their parents.
#if ( GNASH_PARANOIA_LEVEL > 1 ) || defined(ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION)
    for (LiveChars::const_iterator i=_liveChars.begin(), e=_liveChars.end();
            i!=e; ++i) {
#ifdef ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION
        (*i)->setReachable();
#else
        assert((*i)->isReachable());
#endif
    }
#endif
    
#if ( GNASH_PARANOIA_LEVEL > 1 ) || defined(ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION)
    for (Listeners::const_iterator i=_keyListeners.begin(),
            e=_keyListeners.end(); i!=e; ++i) {
#ifdef ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION
        (*i)->setReachable();
#else
        assert((*i)->isReachable());
#endif
    }
#endif

}

InteractiveObject*
movie_root::getTopmostMouseEntity(boost::int32_t x, boost::int32_t y) const
{

    for (Levels::const_reverse_iterator i=_movies.rbegin(), e=_movies.rend();
            i != e; ++i)
    {
        InteractiveObject* ret = i->second->topmostMouseEntity(x, y);
        if (ret) return ret;
    }

    return 0;
}

const DisplayObject *
movie_root::findDropTarget(boost::int32_t x, boost::int32_t y,
        DisplayObject* dragging) const
{

    for (Levels::const_reverse_iterator i=_movies.rbegin(), e=_movies.rend();
            i!=e; ++i) {
        
        const DisplayObject* ret = i->second->findDropTarget(x, y, dragging);
        if (ret) return ret;
    }
    return 0;
}

/// This should store a callback object in movie_root.
//
/// TODO: currently it doesn't.
void
movie_root::addExternalCallback(const std::string& name, as_object* callback)
{
    UNUSED(callback);

    // When an external callback is added, we have to notify the plugin
    // that this method is available.
    if (_hostfd >= 0) {
        std::vector<as_value> fnargs;
        fnargs.push_back(name);
        std::string msg = ExternalInterface::makeInvoke("addMethod", fnargs);
        
        const size_t ret = ExternalInterface::writeBrowser(_hostfd, msg);
        if (ret != msg.size()) {
            log_error(_("Could not write to browser fd #%d: %s"),
                      _hostfd, std::strerror(errno));
        }
    }
}    

/// This calls a JavaScript method in the web page
///
/// @example "ExternalInterace::call message"
///
/// <pre>
/// <invoke name="methodname" returntype="xml">
///      <arguments></arguments>
///             ...
///      <arguments></arguments>
/// </invoke>
///
/// May return any supported type like Number or String in XML format.
///
/// </pre>
std::string
movie_root::callExternalJavascript(const std::string &name, 
                                   const std::vector<as_value> &fnargs)
{
    // GNASH_REPORT_FUNCTION;
    std::string result;
    // If the browser is connected, we send an Invoke message to the
    // browser.
    if (_controlfd >= 0 && _hostfd >= 0) {
        std::string msg = ExternalInterface::makeInvoke(name, fnargs);
        
        const size_t ret = ExternalInterface::writeBrowser(_hostfd, msg);
        if (ret != msg.size()) {
            log_error(_("Could not write to browser fd #%d: %s"),
                      _hostfd, std::strerror(errno));
        } else {
            // Now read the response from the browser after it's exectuted
            // the JavaScript function.
            result = ExternalInterface::readBrowser(_controlfd);
        }
    }

    return result;
}

// Call one of the registered callbacks, and return the result to
// Javascript in the browser.
std::string
movie_root::callExternalCallback(const std::string &name, 
                 const std::vector<as_value> &fnargs)
{
    // GNASH_REPORT_FUNCTION;

    MovieClip *mc = getLevel(0);
    as_object *obj = getObject(mc);

    const ObjectURI& key = getURI(getVM(), name);
    // FIXME: there has got to be a better way of handling the variable
    // length arg list
    as_value val;
    switch (fnargs.size()) {
      case 0:
          val = callMethod(obj, key);
          break;
      case 1:
          val = callMethod(obj, key, fnargs[0]);
          break;
      case 2:
          val = callMethod(obj, key, fnargs[0], fnargs[1]);
          break;
      case 3:
          val = callMethod(obj, key, fnargs[0], fnargs[1], fnargs[2]);
          break;
      default:
          val = callMethod(obj, key);
          break;
    }

    std::string result;
    if (val.is_null()) {
        // Return an error
        result = ExternalInterface::makeString("Error");
    } else {
        result = ExternalInterface::toXML(val);
    }
        
    // If the browser is connected, we send an Invoke message to the
    // browser.
    if (_hostfd >= 0) {
        const size_t ret = ExternalInterface::writeBrowser(_hostfd, result);
        if (ret != result.size()) {
            log_error(_("Could not write to browser fd #%d: %s"),
                      _hostfd, std::strerror(errno));
        }
    }

    return result;
}

void
movie_root::remove_key_listener(Button* listener)
{
    _keyListeners.remove_if(std::bind2nd(std::equal_to<Button*>(), listener));
}

void
movie_root::add_key_listener(Button* listener)
{
    assert(listener);

    if (std::find(_keyListeners.begin(), _keyListeners.end(), listener)
            != _keyListeners.end()) return;

    _keyListeners.push_front(listener);
}

void
movie_root::cleanupDisplayList()
{

#define GNASH_DEBUG_INSTANCE_LIST 1

#ifdef GNASH_DEBUG_INSTANCE_LIST
    static size_t maxLiveChars = 0;
#endif

    // Let every sprite cleanup the local DisplayList
    //
    // TODO: we might skip this additional scan by delegating
    //       cleanup of the local DisplayLists in the ::display
    //       method of each sprite, but that will introduce 
    //       problems when we implement skipping ::display()
    //       when late on FPS. Alternatively we may have the
    //       MovieClip::markReachableResources take care
    //       of cleaning up unloaded... but that will likely
    //       introduce problems when allowing the GC to run
    //       at arbitrary times.
    //       The invariant to keep is that cleanup of unloaded DisplayObjects
    //       in local display lists must happen at the *end* of global action
    //       queue processing.
    foreachSecond(_movies.rbegin(), _movies.rend(),
                  &MovieClip::cleanupDisplayList);

    // Now remove from the instance list any unloaded DisplayObject
    // Note that some DisplayObjects may be unloaded but not yet destroyed,
    // in this case we'll also destroy them, which in turn might unload
    // further DisplayObjects, maybe already scanned, so we keep scanning
    // the list until no more unloaded-but-non-destroyed DisplayObjects
    // are found.
    // Keeping unloaded-but-non-destroyed DisplayObjects wouldn't really hurt
    // in that ::advanceLiveChars would skip any unloaded DisplayObjects.
    // Still, the more we remove the less work GC has to do...
    //

    bool needScan;
#ifdef GNASH_DEBUG_DLIST_CLEANUP
    int scansCount = 0;
#endif
    do {
#ifdef GNASH_DEBUG_DLIST_CLEANUP
        scansCount++;
        int cleaned =0;
#endif
        needScan=false;

        // Remove unloaded MovieClips from the _liveChars list
        for (LiveChars::iterator i = _liveChars.begin(), e = _liveChars.end();
                i != e;) {
            MovieClip* ch = *i;
            if (ch->unloaded()) {
                // the sprite might have been destroyed already
                // by effect of an unload() call with no onUnload
                // handlers available either in self or child
                // DisplayObjects
                if (!ch->isDestroyed()) {

#ifdef GNASH_DEBUG_DLIST_CLEANUP
                    cout << ch->getTarget() << "(" << typeName(*ch) <<
                        ") was unloaded but not destroyed, destroying now" <<
                        endl;
#endif
                    ch->destroy();
                    // destroy() might mark already-scanned chars as unloaded
                    needScan = true; 
                }
#ifdef GNASH_DEBUG_DLIST_CLEANUP
                else {
                    cout << ch->getTarget() << "(" << typeName(*ch) <<
                        ") was unloaded and destroyed" << endl;
                }
#endif

                i = _liveChars.erase(i);

#ifdef GNASH_DEBUG_DLIST_CLEANUP
                cleaned++;
#endif
            }
            else ++i; 
        }

#ifdef GNASH_DEBUG_DLIST_CLEANUP
        cout << " Scan " << scansCount << " cleaned " << cleaned <<
            " instances" << endl;
#endif
    } while (needScan);

#ifdef GNASH_DEBUG_INSTANCE_LIST
    if (_liveChars.size() > maxLiveChars) {
        maxLiveChars = _liveChars.size();
        log_debug("Global instance list grew to %d entries", maxLiveChars);
    }
#endif

}

void
movie_root::advanceLiveChars()
{

#ifdef GNASH_DEBUG
    log_debug("---- movie_root::advance: %d live DisplayObjects in "
            "the global list", _liveChars.size());
#endif

    std::for_each(_liveChars.begin(), _liveChars.end(),
            boost::bind(advanceLiveChar, _1));
}

void
movie_root::set_background_color(const rgba& color)
{
    if (m_background_color_set) return;
    m_background_color_set = true;
    
    rgba newcolor = color;
    newcolor.m_a = m_background_color.m_a;

    if (m_background_color != color) {
        setInvalidated();
        m_background_color = color;
    }
}

void
movie_root::set_background_alpha(float alpha)
{

    boost::uint8_t newAlpha = clamp<int>(frnd(alpha * 255.0f), 0, 255);

    if (m_background_color.m_a != newAlpha) {
        setInvalidated();
        m_background_color.m_a = newAlpha;
    }
}

DisplayObject*
movie_root::findCharacterByTarget(const std::string& tgtstr) const
{
    if (tgtstr.empty()) return 0;

    // NOTE: getRootMovie() would be problematic in case the original
    //       root movie is replaced by a load to _level0... 
    //       (but I guess we'd also drop loadMovie requests in that
    //       case... just not tested)
    as_object* o = getObject(_movies.begin()->second);
    assert(o);

    std::string::size_type from = 0;
    while (std::string::size_type to = tgtstr.find('.', from)) {
        std::string part(tgtstr, from, to - from);

        // TODO: there is surely a cleaner way to implement path finding.
        const ObjectURI& uri = getURI(_vm, part);
        o = o->displayObject() ?
            o->displayObject()->pathElement(uri) :
            getPathElement(*o, uri);

        if (!o) {
#ifdef GNASH_DEBUG_TARGET_RESOLUTION
            log_debug("Evaluating DisplayObject target path: element "
                    "'%s' of path '%s' not found", part, tgtstr);
#endif
            return NULL;
        }
        if (to == std::string::npos) break;
        from = to + 1;
    }
    return get<DisplayObject>(o);
}

void
movie_root::getURL(const std::string& urlstr, const std::string& target,
        const std::string& data, MovieClip::VariablesMethod method)
{

    log_network("%s: HOSTFD is %d",  __FUNCTION__, _hostfd);
    
    if (_hostfd < 0) {
        /// If there is no hosting application, call the URL launcher. For
        /// safety, we resolve the URL against the base URL for this run.
        /// The data is not sent at all.
        URL url(urlstr, _runResources.streamProvider().baseURL());

        gnash::RcInitFile& rcfile = gnash::RcInitFile::getDefaultInstance();
        std::string command = rcfile.getURLOpenerFormat();

        /// Try to avoid letting flash movies execute
        /// arbitrary commands (sic)
        ///
        /// Maybe we should exec here, but if we do we might have problems
        /// with complex urlOpenerFormats like:
        ///    firefox -remote 'openurl(%u)'
        ///
        std::string safeurl = url.encode(urlstr);
        boost::replace_all(command, "%u", safeurl);
        
        log_debug (_("Launching URL: %s"), command);
        const int ret = std::system(command.c_str());
        if (ret == -1) {
            log_error(_("Fork failed launching url opener '%s'"), command);
        }
        return;
    }

    /// This is when there is a hosting application.
    std::vector<as_value> fnargs;
    // The first argument we push on the stack is the URL
    fnargs.push_back(as_value(urlstr));
    
    // The second argument we push is the method
    switch (method) {
      case MovieClip::METHOD_POST:
          fnargs.push_back(as_value("POST"));
          break;
      case MovieClip::METHOD_GET:
          fnargs.push_back(as_value("GET"));
          break;
      case MovieClip::METHOD_NONE:
      default:
          fnargs.push_back(as_value("GET"));
          break;
    }

    // The third argument is the target, which is something like _blank
    // or _self.
    if (!target.empty()) {
        fnargs.push_back(as_value(target));
    }
    // Add any data as the optional 4th argument
    if (!data.empty()) {
        // We have to write a value here so the data field is the fourth
        if (target.empty()) {
            fnargs.push_back(as_value("none"));
        }
        fnargs.push_back(as_value(data));
    }

    // TODO: should mutex-protect this ?
    // NOTE: we are assuming the hostfd is set in blocking mode here..

    log_debug(_("Attempt to write geturl requests fd #%d"), _hostfd);

    std::string msg = ExternalInterface::makeInvoke("getURL", fnargs);

    const size_t ret = ExternalInterface::writeBrowser(_hostfd, msg);
    if (ret < msg.size()) {
        log_error(_("Could only write %d bytes to fd #%d"),
          ret, _hostfd);
    }
}

void
movie_root::setScriptLimits(boost::uint16_t recursion, boost::uint16_t timeout)
{
    
    if ( recursion == _recursionLimit && _timeoutLimit == timeout ) {
        // avoid the debug log...
        return;
    }

    // This tag reported in some sources to be ignored for movies
    // below SWF7. However, on Linux with PP version 9, the tag
    // takes effect on SWFs of any version.
    log_debug(_("Setting script limits: max recursion %d, "
            "timeout %d seconds"), recursion, timeout);

    _recursionLimit = recursion;
    _timeoutLimit = timeout;
    
}


#ifdef USE_SWFTREE
void
movie_root::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
{

    // Stage
    const movie_definition* def = _rootMovie->definition();
    assert(def);

    it = tr.insert(it, std::make_pair("Stage Properties", ""));

    InfoTree::iterator localIter =  tr.append_child(it,
            std::make_pair("Root VM version",
                def->isAS3() ? "AVM2 (unsupported)" : "AVM1"));
    
    std::ostringstream os;
    os << "SWF " << def->get_version();
    localIter = tr.append_child(it, std::make_pair("Root SWF version",
                os.str()));
    localIter = tr.append_child(it, std::make_pair("URL", def->get_url()));

    // TODO: format this better?
    localIter = tr.append_child(it, std::make_pair("Descriptive metadata",
                                        def->getDescriptiveMetadata()));
 
    /// Stage: real dimensions.
    os.str("");
    os << def->get_width_pixels() <<
        "x" << def->get_height_pixels();
    localIter = tr.append_child(it, std::make_pair("Real dimensions",
                os.str()));

    /// Stage: rendered dimensions.
    os.str("");
    os << _stageWidth << "x" << _stageHeight;
    localIter = tr.append_child(it, std::make_pair("Rendered dimensions",
                os.str()));

    // Stage: scripts state (enabled/disabled)
    localIter = tr.append_child(it, std::make_pair("Scripts",
                _disableScripts ? " disabled" : "enabled"));
     
    getCharacterTree(tr, it);    
}

void
movie_root::getCharacterTree(InfoTree& tr, InfoTree::iterator it)
{

    InfoTree::iterator localIter;

    /// Stage: number of live MovieClips.
    std::ostringstream os;
    os << _liveChars.size();
    localIter = tr.append_child(it, std::make_pair(_("Live MovieClips"),
                os.str()));

    /// DisplayObject tree
    for (Levels::const_iterator i = _movies.begin(), e = _movies.end();
            i != e; ++i) {
        i->second->getMovieInfo(tr, localIter);
    }

}

#endif

void
movie_root::handleFsCommand(const std::string& cmd, const std::string& arg)
    const
{
    if (_fsCommandHandler) _fsCommandHandler->notify(cmd, arg);
}

bool
isLevelTarget(int version, const std::string& name, unsigned int& levelno)
{
    if (version > 6) {
        if (name.compare(0, 6, "_level")) return false;
    }
    else {
        StringNoCaseEqual noCaseCmp;
        if (!noCaseCmp(name.substr(0, 6), "_level")) return false;
    }

    if (name.find_first_not_of("0123456789", 7) != std::string::npos) {
        return false;
    }
    // getting 0 here for "_level" is intentional
    levelno = std::strtoul(name.c_str() + 6, NULL, 0); 
    return true;

}

short
stringToStageAlign(const std::string& str)
{
    short am = 0;

    // Easy enough to do bitwise - std::bitset is not
    // really necessary!
    if (str.find_first_of("lL") != std::string::npos) {
        am |= 1 << movie_root::STAGE_ALIGN_L;
    } 

    if (str.find_first_of("tT") != std::string::npos) {
        am |= 1 << movie_root::STAGE_ALIGN_T;
    } 

    if (str.find_first_of("rR") != std::string::npos) {
        am |= 1 << movie_root::STAGE_ALIGN_R;
    } 

    if (str.find_first_of("bB") != std::string::npos) {
        am |= 1 << movie_root::STAGE_ALIGN_B;
    }

    return am;

}

void
movie_root::LoadCallback::setReachable() const
{
    _obj->setReachable();
}

bool
movie_root::LoadCallback::processLoad()
{

    if (!_stream) {
        callMethod(_obj, NSV::PROP_ON_DATA, as_value());
        return true;
    }

    const size_t chunksize = 65535;
    boost::uint8_t chunk[chunksize];

    size_t actuallyRead = _stream->readNonBlocking(chunk, chunksize);

    // We must still call onData if the stream is in error condition, e.g.
    // when an HTTP 404 error is returned.
    if (_stream->bad()) {
        callMethod(_obj, NSV::PROP_ON_DATA, as_value());
        return true;
    }

    if (actuallyRead) {

        // set total size only on first read
        if (_buf.empty()) {
            _obj->set_member(NSV::PROP_uBYTES_TOTAL, _stream->size());
        }

        _buf.append(chunk, actuallyRead);

        _obj->set_member(NSV::PROP_uBYTES_LOADED, _buf.size());

        log_debug("LoadableObject Loaded %d bytes, reaching %d/%d",
            actuallyRead, _buf.size(), _stream->size());
    }

    // We haven't finished till EOF 
    if (!_stream->eof()) return false;

    log_debug("LoadableObject reached EOF (%d/%d loaded)",
                _buf.size(), _stream->size());

    // got nothing, won't bother BOFs of nulls
    if (_buf.empty()) {
        callMethod(_obj, NSV::PROP_ON_DATA, as_value());
        return true;
    }

    // Terminate the string
    _buf.appendByte('\0');

    // Strip BOM, if any.
    // See http://savannah.gnu.org/bugs/?19915
    utf8::TextEncoding encoding;
    size_t size = _buf.size();

    // NOTE: the call below will possibly change 'size' parameter
    char* bufptr = utf8::stripBOM((char*)_buf.data(), size, encoding);
    if (encoding != utf8::encUTF8 && encoding != utf8::encUNSPECIFIED) {
        log_unimpl("%s to utf8 conversion in LoadableObject input parsing", 
                utf8::textEncodingName(encoding));
    }

    // NOTE: Data copy here !!
    as_value dataVal(bufptr); 

    // NOTE: we could release memory associated
    // with the buffer here, before invoking a new method,
    // but at the time of writing there's no method of SimpleBuffer
    // providing memory release except destruction. Will be
    // destroyed as soon as we return though...

    // NOTE: Another data copy here !
    callMethod(_obj, NSV::PROP_ON_DATA, dataVal);

    return true;
}

void
movie_root::callInterface(const HostInterface::Message& e) const
{
    if (!_interfaceHandler) {
        log_error("Hosting application registered no callback for events/queries"
            ", can't call %s(%s)");
        return;
    }
    _interfaceHandler->call(e);
}


inline bool
movie_root::testInvariant() const
{
    // TODO: fill this function !
    // The _movies map can not invariantably
    // be non-empty as the stage is autonomous
    // itself
    //assert( ! _movies.empty() );

    return true;
}

namespace {

// Return whether any action triggered by this event requires display redraw.
//
/// TODO: make this code more readable !
bool
generate_mouse_button_events(movie_root& mr, MouseButtonState& ms)
{
    // Did this event trigger any action that needs redisplay ?
    bool need_redisplay = false;

    // TODO: have mouseEvent return
    // whether the action must trigger
    // a redraw.

    if (ms.wasDown) {
        // TODO: Handle trackAsMenu dragOver
        // Handle onDragOut, onDragOver
        if (!ms.wasInsideActiveEntity) {

            if (ms.topmostEntity == ms.activeEntity) {

                // onDragOver
                if (ms.activeEntity) {
                    ms.activeEntity->mouseEvent(event_id(event_id::DRAG_OVER));
                    need_redisplay=true;
                }
                ms.wasInsideActiveEntity = true;
            }
        }
        else if (ms.topmostEntity != ms.activeEntity) {
            // onDragOut
            if (ms.activeEntity) {
                ms.activeEntity->mouseEvent(event_id(event_id::DRAG_OUT));
                need_redisplay=true;
            }
            ms.wasInsideActiveEntity = false;
        }

        // Handle onRelease, onReleaseOutside
        if (!ms.isDown) {
            // Mouse button just went up.
            ms.wasDown = false;

            if (ms.activeEntity) {
                if (ms.wasInsideActiveEntity) {
                    // onRelease
                    ms.activeEntity->mouseEvent(event_id(event_id::RELEASE));
                    need_redisplay = true;
                }
                else {
                    // TODO: Handle trackAsMenu 
                    // onReleaseOutside
                    ms.activeEntity->mouseEvent(
                            event_id(event_id::RELEASE_OUTSIDE));
                    // We got out of active entity
                    ms.activeEntity = 0; // so we don't get RollOut next...
                    need_redisplay = true;
                }
            }
        }
        return need_redisplay;
    }

    else {
        // New active entity is whatever is below the mouse right now.
        if (ms.topmostEntity != ms.activeEntity) {
            // onRollOut
            if (ms.activeEntity) {
                ms.activeEntity->mouseEvent(event_id(event_id::ROLL_OUT));
                need_redisplay=true;
            }

            ms.activeEntity = ms.topmostEntity;

            // onRollOver
            if (ms.activeEntity) {
                ms.activeEntity->mouseEvent(event_id(event_id::ROLL_OVER));
                need_redisplay=true;
            }

            ms.wasInsideActiveEntity = true;
        }

        // mouse button press
        if (ms.isDown) {
            // onPress

            // Try setting focus on the new DisplayObject. This will handle
            // all necessary events and removal of current focus.
            // Do not set focus to NULL.
            if (ms.activeEntity) {
                mr.setFocus(ms.activeEntity);

                ms.activeEntity->mouseEvent(event_id(event_id::PRESS));
                need_redisplay = true;
            }

            ms.wasInsideActiveEntity = true;
            ms.wasDown = true;
        }
    
    }
    return need_redisplay;

}

const DisplayObject*
getNearestObject(const DisplayObject* o)
{
    while (1) {
        assert(o);
        if (isReferenceable(*o)) return o;
        o = o->parent();
    }
}

as_object*
getBuiltinObject(movie_root& mr, const ObjectURI& cl)
{
    Global_as& gl = *mr.getVM().getGlobal();

    as_value val;
    if (!gl.get_member(cl, &val)) return 0;
    return toObject(val, mr.getVM());
}

void
advanceLiveChar(MovieClip* mo)
{
    if (!mo->unloaded()) {
#ifdef GNASH_DEBUG
        log_debug("    advancing DisplayObject %s", ch->getTarget());
#endif
        mo->advance();
    }
#ifdef GNASH_DEBUG
    else {
        log_debug("    DisplayObject %s is unloaded, not advancing it",
                mo->getTarget());
    }
#endif
}

} // anonymous namespace
} // namespace gnash

// local Variables:
// mode: C++
// indent-tabs-mode: nil
// End:

/* [<][>][^][v][top][bottom][index][help] */