1 // movie_root.cpp: The root movie, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "movie_root.h"
30 #include <boost/algorithm/string/replace.hpp>
31 #include <boost/ptr_container/ptr_deque.hpp>
32 #include <boost/algorithm/string/case_conv.hpp>
35 #include "GnashSystemIOHeaders.h" // write()
37 #include "MovieClip.h"
40 #include "ExecutableCode.h"
42 #include "namedStrings.h"
43 #include "GnashException.h"
44 #include "sound_handler.h"
47 #include "GnashAlgorithm.h"
48 #include "GnashNumeric.h"
49 #include "Global_as.h"
51 #include "IOChannel.h"
52 #include "RunResources.h"
54 #include "ExternalInterface.h"
55 #include "TextField.h"
57 #include "Transform.h"
58 #include "StreamProvider.h"
59 #include "SystemClock.h"
60 #include "as_function.h"
66 //#define GNASH_DEBUG 1
67 //#define GNASH_DEBUG_LOADMOVIE_REQUESTS_PROCESSING 1
68 //#define GNASH_DEBUG_TIMERS_EXPIRATION 1
70 // Defining the macro below prints info about
71 // cleanup of live chars (advanceable + key/mouse listeners)
72 // Is useful in particular to check for cost of multiple scans
73 // when a movie destruction destrois more elements.
75 // NOTE: I think the whole confusion here was introduced
76 // by zou making it "optional" to ::unload() childs
77 // when being unloaded. Zou was trying to avoid
78 // queuing an onUnload event, which I suggested we'd
79 // do by having unload() take an additional argument
80 // or similar. Failing to tag childs as unloaded
81 // will result in tagging them later (in ::destroy)
82 // which will require scanning the lists again
83 // (key/mouse + advanceable).
84 // See https://savannah.gnu.org/bugs/index.php?21804
86 //#define GNASH_DEBUG_DLIST_CLEANUP 1
90 // Forward declarations
92 bool generate_mouse_button_events(movie_root
& mr
, MouseButtonState
& ms
);
93 const DisplayObject
* getNearestObject(const DisplayObject
* o
);
94 as_object
* getBuiltinObject(movie_root
& mr
, const ObjectURI
& cl
);
95 void advanceLiveChar(MovieClip
* ch
);
96 void notifyLoad(MovieClip
* ch
);
102 /// Execute an ActiveRelay if the object has that type.
103 struct ExecuteCallback
105 void operator()(const as_object
* o
) const {
107 if (isNativeType(o
, a
)) {
113 /// Identify and delete ExecutableCode that matches a particular target.
114 class RemoveTargetCode
117 RemoveTargetCode(DisplayObject
* target
) : _target(target
) {}
118 bool operator()(const ExecutableCode
& c
) const {
119 return _target
== c
.target();
122 DisplayObject
* _target
;
126 clear(movie_root::ActionQueue
& aq
)
128 std::for_each(aq
.begin(), aq
.end(),
129 std::mem_fn(&movie_root::ActionQueue::value_type::clear
));
132 } // anonymous namespace
135 movie_root::movie_root(VirtualClock
& clock
, const RunResources
& runResources
)
138 _runResources(runResources
),
140 _interfaceHandler(nullptr),
141 _fsCommandHandler(nullptr),
144 m_background_color(255, 255, 255, 255),
145 m_background_color_set(false),
149 _lastKeyEvent(key::INVALID
),
150 _currentFocus(nullptr),
154 _disableScripts(false),
155 _processingActionLevel(PRIORITY_SIZE
),
158 _quality(QUALITY_HIGH
),
160 _allowScriptAccess(SCRIPT_ACCESS_SAME_DOMAIN
),
162 _scaleMode(SCALEMODE_SHOWALL
),
163 _displayState(DISPLAYSTATE_NORMAL
),
164 _recursionLimit(), // set in ctor body
165 _timeoutLimit(), // set in ctor body
166 _movieAdvancementDelay(83), // ~12 fps by default
167 _lastMovieAdvancement(0),
171 // This takes care of informing the renderer (if present) too.
172 setQuality(QUALITY_HIGH
);
174 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
175 _recursionLimit
= rcfile
.getScriptsRecursionLimit();
176 _timeoutLimit
= rcfile
.getScriptsTimeout();
180 movie_root::disableScripts()
182 _disableScripts
= true;
184 // NOTE: we won't clear the action queue now
185 // to avoid invalidating iterators as we've
186 // been probably called during processing
190 movie_root::~movie_root()
193 _intervalTimers
.clear();
194 _movieLoader
.clear();
196 assert(testInvariant());
200 movie_root::init(movie_definition
* def
, const MovieClip::MovieVariables
& vars
)
202 _vm
.setSWFVersion(def
->get_version());
204 Movie
* mr
= def
->createMovie(*_vm
.getGlobal());
205 mr
->setVariables(vars
);
211 movie_root::setRootMovie(Movie
* movie
)
215 const movie_definition
* md
= movie
->definition();
216 float fps
= md
->get_frame_rate();
217 _movieAdvancementDelay
= static_cast<int>(1000/fps
);
219 _lastMovieAdvancement
= _vm
.getTime();
221 _stageWidth
= static_cast<int>(md
->get_width_pixels());
222 _stageHeight
= static_cast<int>(md
->get_height_pixels());
224 movie
->set_depth(DisplayObject::staticDepthOffset
);
229 // actions in first frame of _level0 must execute now,
230 // before next advance,
231 // or they'll be executed with _currentframe being set to 2
232 processActionQueue();
234 catch (const ActionLimitException
& al
) {
235 handleActionLimitHit(al
.what());
237 catch (const ActionParserException
& e
) {
238 log_error(_("ActionParserException thrown during setRootMovie: %s"),
246 movie_root::queryInterface(const std::string
& what
) const
249 if (_interfaceHandler
) {
250 disable
= callInterface
<bool>(HostMessage(HostMessage::QUERY
, what
));
253 log_error(_("No user interface registered, assuming 'Yes' answer to question: %s"), what
);
259 movie_root::setStreamBlock(int id
, int block
)
261 if (!_timelineSound
) {
262 _timelineSound
= SoundStream(id
, block
);
266 // Don't replace timeline stream.
267 if (_timelineSound
->id
!= id
) return;
269 _timelineSound
->block
= block
;
273 movie_root::stopStream(int id
)
275 if (!_timelineSound
) return;
276 if (_timelineSound
->id
== id
) _timelineSound
.reset();
280 movie_root::registerClass(const SWF::DefinitionTag
* sprite
, as_function
* cls
)
282 _registeredClasses
[sprite
] = cls
;
286 movie_root::getRegisteredClass(const SWF::DefinitionTag
* sprite
) const
288 RegisteredClasses::const_iterator it
= _registeredClasses
.find(sprite
);
289 if (it
== _registeredClasses
.end()) return nullptr;
294 movie_root::handleActionLimitHit(const std::string
& msg
)
296 log_debug("Disabling scripts: %1%", msg
);
302 movie_root::cleanupAndCollect()
304 // Cleanup the stack.
305 _vm
.getStack().clear();
307 // Reset the constant pool
308 _vm
.setConstantPool(nullptr);
310 cleanupDisplayList();
316 movie_root::setLevel(unsigned int num
, Movie
* movie
)
318 assert(movie
!= nullptr);
319 assert(static_cast<unsigned int>(movie
->get_depth()) ==
320 num
+ DisplayObject::staticDepthOffset
);
323 Levels::iterator it
= _movies
.find(movie
->get_depth());
324 if (it
== _movies
.end()) {
325 _movies
[movie
->get_depth()] = movie
;
328 // don't leak overloaded levels
330 MovieClip
* lm
= it
->second
;
331 if (lm
== _rootMovie
) {
332 // NOTE: this is not enough to trigger
333 // an application reset. Was tested
334 // but not automated. If curious
335 // use swapDepths against _level0
336 // and load into the new target while
337 // a timeout/interval is active.
338 log_debug("Replacing starting movie");
343 log_debug("Loading into _level0");
345 // NOTE: this was tested but not automated, the
346 // test sets an interval and then loads something
347 // in _level0. The result is the interval is disabled.
348 _intervalTimers
.clear();
350 // TODO: check what else we should do in these cases
351 // (like, unregistering all childs etc...)
352 // Tested, but not automated, is that other
353 // levels should be maintained alive.
354 // Sat Nov 14 10:31:19 CET 2009
355 // ^^^ not confirmed in this date, I think other levels
356 // are dropped too! (strk)
358 _stageWidth
= movie
->widthPixels();
359 _stageHeight
= movie
->heightPixels();
361 // notify stage replacement
362 if (_interfaceHandler
) {
363 const HostMessage
e(HostMessage::RESIZE_STAGE
,
364 std::make_pair(_stageWidth
, _stageHeight
));
365 _interfaceHandler
->call(e
);
369 it
->second
->destroy();
373 movie
->set_invalidated();
378 assert(testInvariant());
382 movie_root::swapLevels(MovieClip
* movie
, int depth
)
386 //#define GNASH_DEBUG_LEVELS_SWAPPING 1
388 const int oldDepth
= movie
->get_depth();
390 #ifdef GNASH_DEBUG_LEVELS_SWAPPING
391 log_debug("Before swapLevels (source depth %d, target depth %d) levels are: ",
393 for (Levels::const_iterator i
=_movies
.begin(), e
=_movies
.end(); i
!=e
; ++i
) {
394 log_debug(" %d: %p (%s @ depth %d)", i
->first
,
395 (void*)(i
->second
), i
->second
->getTarget(),
396 i
->second
->get_depth());
399 // should include _level0 !
400 if (oldDepth
< DisplayObject::staticDepthOffset
) {
401 IF_VERBOSE_ASCODING_ERRORS(
402 log_aserror(_("%s.swapDepth(%d): movie has a depth (%d) below "
403 "static depth zone (%d), won't swap its depth"),
404 movie
->getTarget(), depth
, oldDepth
,
405 DisplayObject::staticDepthOffset
);
411 IF_VERBOSE_ASCODING_ERRORS(
412 log_aserror(_("%s.swapDepth(%d): movie has a depth (%d) below "
413 "static depth zone (%d), won't swap its depth"),
414 movie
->getTarget(), depth
, oldDepth
,
415 DisplayObject::staticDepthOffset
);
420 const int oldNum
= oldDepth
;
421 Levels::iterator oldIt
= _movies
.find(oldNum
);
422 if (oldIt
== _movies
.end()) {
423 log_debug("%s.swapDepth(%d): target depth (%d) contains no movie",
424 movie
->getTarget(), depth
, oldNum
);
428 const int newNum
= depth
;
429 movie
->set_depth(depth
);
430 Levels::iterator targetIt
= _movies
.find(newNum
);
431 if (targetIt
== _movies
.end()) {
432 _movies
.erase(oldIt
);
433 _movies
[newNum
] = movie
;
436 MovieClip
* otherMovie
= targetIt
->second
;
437 otherMovie
->set_depth(oldDepth
);
438 oldIt
->second
= otherMovie
;
439 targetIt
->second
= movie
;
442 #ifdef GNASH_DEBUG_LEVELS_SWAPPING
443 log_debug("After swapLevels levels are: ");
444 for (Levels::const_iterator i
=_movies
.begin(), e
=_movies
.end(); i
!=e
; ++i
) {
445 log_debug(" %d: %p (%s @ depth %d)", i
->first
,
446 (void*)(i
->second
), i
->second
->getTarget(),
447 i
->second
->get_depth());
451 // TODO: invalidate self, not the movie
452 // movie_root::setInvalidated() seems
453 // to do just that, if anyone feels
454 // like more closely research on this
455 // (does level swapping require full redraw always?)
456 movie
->set_invalidated();
458 assert(testInvariant());
462 movie_root::dropLevel(int depth
)
464 // should be checked by caller
465 // TODO: don't use a magic number! See MovieClip::removeMovieClip().
466 assert(depth
>= 0 && depth
<= 1048575);
468 Levels::iterator it
= _movies
.find(depth
);
469 if (it
== _movies
.end()) {
470 log_error(_("movie_root::dropLevel called against a movie not found in the levels container"));
474 MovieClip
* mo
= it
->second
;
475 if (mo
== _rootMovie
) {
476 IF_VERBOSE_ASCODING_ERRORS(
477 log_aserror(_("Original root movie can't be removed"));
482 // TOCHECK: safe to erase here ?
484 // Ignoring return value of unload(), because the only way to handle
485 // an unload failure is to call destroy, which is done anyway.
490 assert(testInvariant());
494 movie_root::replaceLevel(unsigned int num
, Movie
* extern_movie
)
496 extern_movie
->set_depth(num
+ DisplayObject::staticDepthOffset
);
497 Levels::iterator it
= _movies
.find(extern_movie
->get_depth());
498 if (it
== _movies
.end()) {
499 log_error(_("TESTME: loadMovie called on level %d which is not available at load time, skipped placement for now"));
503 // TODO: rework this to avoid the double scan
504 setLevel(num
, extern_movie
);
508 movie_root::getLevel(unsigned int num
) const
510 Levels::const_iterator i
=
511 _movies
.find(num
+ DisplayObject::staticDepthOffset
);
513 if (i
== _movies
.end()) return nullptr;
521 sound::sound_handler
* sh
= _runResources
.soundHandler();
524 // reset background color, to allow
525 // next load to set it again.
526 m_background_color
= rgba(255, 255, 255, 255);
527 m_background_color_set
= false;
529 // wipe out live chars
532 // wipe out queued actions
535 // wipe out all levels
538 // remove all intervals
539 _intervalTimers
.clear();
541 // remove all loadMovie requests
542 _movieLoader
.clear();
544 // Remove button key events.
545 _buttonListeners
.clear();
547 // Cleanup the stack.
548 _vm
.getStack().clear();
550 // Run the garbage collector again
555 _disableScripts
= false;
557 _timelineSound
.reset();
561 movie_root::setDimensions(size_t w
, size_t h
)
563 assert(testInvariant());
568 if (_scaleMode
== SCALEMODE_NOSCALE
) {
569 as_object
* stage
= getBuiltinObject(*this,
570 getURI(_vm
, NSV::CLASS_STAGE
));
572 callMethod(stage
, getURI(_vm
, NSV::PROP_BROADCAST_MESSAGE
),
578 assert(testInvariant());
582 movie_root::mouseMoved(std::int32_t x
, std::int32_t y
)
584 assert(testInvariant());
588 return notify_mouse_listeners(event_id(event_id::MOUSE_MOVE
));
593 movie_root::keyEvent(key::code k
, bool down
)
596 const size_t keycode
= key::codeMap
[k
][key::KEY
];
597 if (keycode
< key::KEYCOUNT
) {
598 _unreleasedKeys
.set(keycode
, down
);
601 LiveChars copy
= _liveChars
;
602 for (MovieClip
* const ch
: copy
) {
604 if (ch
->unloaded()) continue;
607 ch
->notifyEvent(event_id(event_id::KEY_DOWN
, key::INVALID
));
608 ch
->notifyEvent(event_id(event_id::KEY_PRESS
, k
));
611 ch
->notifyEvent(event_id(event_id::KEY_UP
, key::INVALID
));
615 // Broadcast event to Key._listeners.
616 as_object
* key
= getBuiltinObject(*this, getURI(_vm
, NSV::CLASS_KEY
));
620 // Can throw an action limit exception if the stack limit is 0 or 1,
621 // i.e. if the stack is at the limit before it contains anything.
622 // A stack limit like that is hardly of any use, but could be used
623 // maliciously to crash Gnash.
624 callMethod(key
, getURI(_vm
, NSV::PROP_BROADCAST_MESSAGE
),
625 down
? "onKeyDown" : "onKeyUp");
627 catch (const ActionLimitException
&e
) {
628 log_error(_("ActionLimits hit notifying key listeners: %s."),
636 // NB: Button handling is not correct, as only one button should
637 // respond to any key. A test is in misc-ming.all/KeyEventOrder.c.
638 // However, the previous attempt to fix caused real-life failures:
640 ButtonListeners copy
= _buttonListeners
;
641 for (Button
* button
: copy
) {
642 if (!button
->unloaded()) {
647 // If we're focused on an editable text field, finally the text
649 TextField
* tf
= dynamic_cast<TextField
*>(_currentFocus
);
650 if (tf
) tf
->keyInput(k
);
653 processActionQueue();
659 movie_root::mouseWheel(int delta
)
661 as_object
* mouseObj
=
662 getBuiltinObject(*this, getURI(_vm
, NSV::CLASS_MOUSE
));
663 if (!mouseObj
) return false;
665 const std::int32_t x
= pixelsToTwips(_mouseX
);
666 const std::int32_t y
= pixelsToTwips(_mouseY
);
668 DisplayObject
* i
= getTopmostMouseEntity(x
, y
);
670 // Always called with two arguments.
671 callMethod(mouseObj
, getURI(_vm
,NSV::PROP_BROADCAST_MESSAGE
), "onMouseWheel",
672 delta
, i
? getObject(i
) : as_value());
678 movie_root::mouseClick(bool mouse_pressed
)
680 assert(testInvariant());
682 _mouseButtonState
.isDown
= mouse_pressed
;
685 return notify_mouse_listeners(event_id(event_id::MOUSE_DOWN
));
687 return notify_mouse_listeners(event_id(event_id::MOUSE_UP
));
692 movie_root::fire_mouse_event()
694 assert(testInvariant());
696 std::int32_t x
= pixelsToTwips(_mouseX
);
697 std::int32_t y
= pixelsToTwips(_mouseY
);
699 // Generate a mouse event
700 _mouseButtonState
.topmostEntity
= getTopmostMouseEntity(x
, y
);
702 // Set _droptarget if dragging a sprite
703 DisplayObject
* draggingChar
= getDraggingCharacter();
705 MovieClip
* dragging
= draggingChar
->to_movie();
707 // TODO: optimize making findDropTarget and getTopmostMouseEntity
708 // use a single scan.
709 const DisplayObject
* dropChar
= findDropTarget(x
, y
, dragging
);
711 // Use target of closest script DisplayObject containing this
712 dropChar
= getNearestObject(dropChar
);
713 dragging
->setDropTarget(dropChar
->getTargetPath());
715 else dragging
->setDropTarget("");
719 bool need_redraw
= false;
721 // FIXME: need_redraw might also depend on actual
722 // actions execution (consider updateAfterEvent).
725 need_redraw
= generate_mouse_button_events(*this, _mouseButtonState
);
726 processActionQueue();
728 catch (const ActionLimitException
& al
) {
729 handleActionLimitHit(al
.what());
735 std::pair
<std::int32_t, std::int32_t>
736 movie_root::mousePosition() const
738 assert(testInvariant());
739 return std::make_pair(_mouseX
, _mouseY
);
743 movie_root::setDragState(const DragState
& st
)
747 DisplayObject
* ch
= _dragState
->getCharacter();
749 if (ch
&& !_dragState
->isLockCentered()) {
750 // Get coordinates of the DisplayObject's origin
752 SWFMatrix chmat
= getWorldMatrix(*ch
);
754 chmat
.transform(&world_origin
, origin
);
756 // Get current mouse coordinates
757 const point
world_mouse(pixelsToTwips(_mouseX
), pixelsToTwips(_mouseY
));
759 std::int32_t xoffset
= world_mouse
.x
- world_origin
.x
;
760 std::int32_t yoffset
= world_mouse
.y
- world_origin
.y
;
762 _dragState
->setOffset(xoffset
, yoffset
);
764 assert(testInvariant());
768 movie_root::doMouseDrag()
770 DisplayObject
* dragChar
= getDraggingCharacter();
771 if (!dragChar
) return; // nothing to do
773 if (dragChar
->unloaded()) {
774 // Reset drag state if dragging char was unloaded
779 point
world_mouse(pixelsToTwips(_mouseX
), pixelsToTwips(_mouseY
));
781 SWFMatrix parent_world_mat
;
782 DisplayObject
* p
= dragChar
->parent();
784 parent_world_mat
= getWorldMatrix(*p
);
787 if (!_dragState
->isLockCentered()) {
788 world_mouse
.x
-= _dragState
->xOffset();
789 world_mouse
.y
-= _dragState
->yOffset();
792 if (_dragState
->hasBounds()) {
794 // bounds are in local coordinate space
795 bounds
.enclose_transformed_rect(parent_world_mat
,
796 _dragState
->getBounds());
797 // Clamp mouse coords within a defined SWFRect.
798 bounds
.clamp(world_mouse
);
801 parent_world_mat
.invert().transform(world_mouse
);
802 // Place our origin so that it coincides with the mouse coords
803 // in our parent frame.
804 // TODO: add a DisplayObject::set_translation ?
805 SWFMatrix local
= getMatrix(*dragChar
);
806 local
.set_translation(world_mouse
.x
, world_mouse
.y
);
808 // no need to update caches when only changing translation
809 dragChar
->setMatrix(local
);
813 movie_root::addIntervalTimer(std::unique_ptr
<Timer
> timer
)
816 assert(testInvariant());
818 const size_t id
= ++_lastTimerId
;
820 assert(_intervalTimers
.find(id
) == _intervalTimers
.end());
822 _intervalTimers
.insert(std::make_pair(id
, std::move(timer
)));
828 movie_root::clearIntervalTimer(std::uint32_t x
)
830 TimerMap::iterator it
= _intervalTimers
.find(x
);
831 if (it
== _intervalTimers
.end()) {
835 // We do not remove the element here because
836 // we might have been called during execution
837 // of another timer, thus during a scan of the _intervalTimers
838 // container. If we use erase() here, the iterators in executeTimers
839 // would be invalidated. Rather, executeTimers() would check container
840 // elements for being still active and remove the cleared one in a safe way
841 // at each iteration.
842 it
->second
->clearInterval();
848 movie_root::advance()
850 // We can't actually rely on now being later than _lastMovieAdvancement,
851 // so we will have to check. Otherwise we risk elapsed being
852 // contructed from a negative value.
853 const size_t now
= std::max
<size_t>(_vm
.getTime(), _lastMovieAdvancement
);
855 bool advanced
= false;
860 sound::sound_handler
* s
= _runResources
.soundHandler();
862 if (s
&& _timelineSound
) {
864 if (!s
->streamingSound()) {
865 log_error(_("movie_root tracking a streaming sound, but the sound handler is not streaming!"));
867 // Give up; we've probably failed to catch up.
868 _timelineSound
.reset();
871 // -1 for bad result, 0 for first block.
872 // Get the stream block we are currently at.
873 int block
= s
->getStreamBlock(_timelineSound
->id
);
875 const int startBlock
= _timelineSound
->block
;
877 const size_t maxTime
= getTimeoutLimit() * 1000;
880 // If we're behind, we should skip; if we're ahead
881 // (_timelineSound->block > block) we should not advance,
882 // if we're ahead, skip.
883 while (block
!= -1 && block
> _timelineSound
->block
) {
887 // Movie advance can cause streaming sound to be reset or,
888 // if a MovieClip loops, the current timeline sound block
889 // to be moved earlier. In the latter case we break to
890 // avoid catching up to the old sound position.
891 if (!_timelineSound
|| _timelineSound
->block
< startBlock
) {
895 if (clock
.elapsed() > maxTime
) {
897 boost::format(_("Time exceeded (%1% secs) while "
898 "attempting to catch up to streaming "
899 "sound. Give up on synchronization?"))
902 // We'll start synchronizing again anyway when the
903 // next stream block arrives, but this will at least
904 // unblock the user interface.
905 if (queryInterface(fmt
.str())) {
906 _timelineSound
.reset();
911 // Note: advancing the current sound block here makes
912 // it possible that Gnash will never catch up, if e.g.
913 // executing ActionScript causes the frame rate to drop
914 // even further. Not advancing the sound block means
915 // that Gnash will always catch up to the point we
916 // stored at the start of the loop, but then the audio
917 // stream may restart if it has finished before Gnash
918 // reaches the new position.
919 block
= s
->getStreamBlock(_timelineSound
->id
);
923 _lastMovieAdvancement
= now
;
929 // Driven by frame rate
930 const size_t elapsed
= now
- _lastMovieAdvancement
;
931 if (elapsed
>= _movieAdvancementDelay
) {
934 _lastMovieAdvancement
= now
;
941 executeAdvanceCallbacks();
945 catch (const ActionLimitException
& al
) {
946 // The PP does not disable scripts when the stack limit is reached,
947 // but rather struggles on.
948 // TODO: find a test case for this, if confirmed fix accordingly
949 handleActionLimitHit(al
.what());
951 catch (const ActionParserException
& e
) {
952 log_error(_("Buffer overread during advance: %s"), e
.what());
960 movie_root::advanceMovie()
962 // Do mouse drag, if needed
965 // Advance all non-unloaded DisplayObjects in the LiveChars list
966 // in reverse order (last added, first advanced)
967 // NOTE: can throw ActionLimitException
970 // Process loadMovie requests
972 // NOTE: should be done before executing timers,
973 // see swfdec's test/trace/loadmovie-case-{5,6}.swf
974 // NOTE: processing loadMovie requests after advanceLiveChars
975 // is known to fix more tests in misc-mtasc.all/levels.swf
976 // to be checked if it keeps the swfdec testsuite safe
978 _movieLoader
.processCompletedRequests();
980 // Process queued actions
981 // NOTE: can throw ActionLimitException
982 processActionQueue();
986 assert(testInvariant());
990 movie_root::timeToNextFrame() const
992 unsigned int now
= _vm
.getTime();
993 const int elapsed
= now
- _lastMovieAdvancement
;
994 return _movieAdvancementDelay
- elapsed
;
998 movie_root::display()
1000 // GNASH_REPORT_FUNCTION;
1002 assert(testInvariant());
1006 // TODO: should we consider the union of all levels bounds ?
1007 const SWFRect
& frame_size
= _rootMovie
->get_frame_size();
1008 if ( frame_size
.is_null() )
1010 // TODO: check what we should do if other levels
1011 // have valid bounds
1012 log_debug("original root movie had null bounds, not displaying");
1016 Renderer
* renderer
= _runResources
.renderer();
1017 if (!renderer
) return;
1019 Renderer::External
ex(*renderer
, m_background_color
,
1020 _stageWidth
, _stageHeight
,
1021 frame_size
.get_x_min(), frame_size
.get_x_max(),
1022 frame_size
.get_y_min(), frame_size
.get_y_max());
1024 for (auto& elem
: _movies
) {
1025 MovieClip
* movie
= elem
.second
;
1027 movie
->clear_invalidated();
1029 if (movie
->visible() == false) continue;
1031 // null frame size ? don't display !
1032 const SWFRect
& sub_frame_size
= movie
->get_frame_size();
1034 if (sub_frame_size
.is_null()) {
1035 log_debug("_level%u has null frame size, skipping", elem
.first
);
1039 movie
->display(*renderer
, Transform());
1044 movie_root::notify_mouse_listeners(const event_id
& event
)
1046 LiveChars copy
= _liveChars
;
1047 for (MovieClip
* const ch
: copy
)
1049 if (!ch
->unloaded()) {
1050 ch
->mouseEvent(event
);
1054 const ObjectURI
& propMouse
= getURI(_vm
, NSV::CLASS_MOUSE
);
1055 const ObjectURI
& propBroadcastMessage
=
1056 getURI(_vm
, NSV::PROP_BROADCAST_MESSAGE
);
1058 as_object
* mouseObj
= getBuiltinObject(*this, propMouse
);
1061 // Can throw an action limit exception if the stack limit is 0 or 1.
1062 // A stack limit like that is hardly of any use, but could be used
1063 // maliciously to crash Gnash.
1065 callMethod(mouseObj
, propBroadcastMessage
, event
.functionName());
1067 catch (const ActionLimitException
& e
) {
1068 log_error(_("ActionLimits hit notifying mouse events: %s."),
1070 clear(_actionQueue
);
1074 assert(testInvariant());
1076 if (!copy
.empty()) {
1077 // process actions queued in the above step
1078 processActionQueue();
1080 return fire_mouse_event();
1084 movie_root::getFocus()
1086 return _currentFocus
;
1090 movie_root::setFocus(DisplayObject
* to
)
1092 // Nothing to do if current focus is the same as the new focus.
1093 // _level0 also seems unable to receive focus under any circumstances
1094 // TODO: what about _level1 etc ?
1095 if (to
== _currentFocus
|| to
== _rootMovie
) {
1099 if (to
&& !to
->handleFocus()) {
1100 // TODO: not clear whether to remove focus in this case.
1104 // Undefined or NULL DisplayObject removes current focus. Otherwise, try
1105 // setting focus to the new DisplayObject. If it fails, remove current
1108 // Store previous focus, as the focus needs to change before onSetFocus
1109 // is called and listeners are notified.
1110 DisplayObject
* from
= _currentFocus
;
1113 // Perform any actions required on killing focus (only TextField).
1116 /// A valid focus must have an associated object.
1117 assert(getObject(from
));
1118 callMethod(getObject(from
), NSV::PROP_ON_KILL_FOCUS
, getObject(to
));
1124 assert(getObject(to
));
1125 callMethod(getObject(to
), NSV::PROP_ON_SET_FOCUS
, getObject(from
));
1128 as_object
* sel
= getBuiltinObject(*this, NSV::CLASS_SELECTION
);
1130 // Notify Selection listeners with previous and new focus as arguments.
1131 // Either argument may be null.
1133 callMethod(sel
, NSV::PROP_BROADCAST_MESSAGE
, "onSetFocus",
1134 getObject(from
), getObject(to
));
1137 assert(testInvariant());
1143 movie_root::getActiveEntityUnderPointer() const
1145 return _mouseButtonState
.activeEntity
;
1149 movie_root::getDraggingCharacter() const
1151 return _dragState
? _dragState
->getCharacter() : nullptr;
1154 const DisplayObject
*
1155 movie_root::getEntityUnderPointer() const
1157 const std::int32_t x
= pixelsToTwips(_mouseX
);
1158 const std::int32_t y
= pixelsToTwips(_mouseY
);
1159 return findDropTarget(x
, y
, getDraggingCharacter());
1164 movie_root::setQuality(Quality q
)
1166 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
1168 /// Overridden quality if not negative.
1169 if (rcfile
.qualityLevel() >= 0) {
1170 int ql
= rcfile
.qualityLevel();
1171 ql
= std::min
<int>(ql
, QUALITY_BEST
);
1172 q
= static_cast<Quality
>(ql
);
1175 if ( _quality
!= q
)
1177 // Force a redraw if quality changes
1179 // redraw should only happen on next
1180 // frame advancement (tested)
1187 // We always tell the renderer, because it could
1188 // be the first time we do
1189 Renderer
* renderer
= _runResources
.renderer();
1190 if (renderer
) renderer
->setQuality(_quality
);
1194 /// Get actionscript width of stage, in pixels. The width
1195 /// returned depends on the scale mode.
1197 movie_root::getStageWidth() const
1199 if (_scaleMode
== SCALEMODE_NOSCALE
) {
1203 // If scaling is allowed, always return the original movie size.
1205 return static_cast<size_t>(_rootMovie
->widthPixels());
1210 /// Get actionscript height of stage, in pixels. The height
1211 /// returned depends on the scale mode.
1213 movie_root::getStageHeight() const
1215 if (_scaleMode
== SCALEMODE_NOSCALE
) {
1216 return _stageHeight
;
1219 // If scaling is allowed, always return the original movie size.
1221 return static_cast<size_t>(_rootMovie
->heightPixels());
1226 /// Takes a short int bitfield: the four bits correspond
1227 /// to the AlignMode enum
1229 movie_root::setStageAlignment(short s
)
1232 callInterface(HostMessage(HostMessage::UPDATE_STAGE
));
1235 /// The mode is one of never, always, with sameDomain the default
1237 movie_root::setAllowScriptAccess(AllowScriptAccessMode mode
)
1239 _allowScriptAccess
= mode
;
1242 movie_root::AllowScriptAccessMode
1243 movie_root::getAllowScriptAccess()
1245 return _allowScriptAccess
;
1248 /// Returns a pair of enum values giving the actual alignment
1249 /// of the stage after align mode flags are evaluated.
1250 movie_root::StageAlign
1251 movie_root::getStageAlignment() const
1253 /// L takes precedence over R. Default is centred.
1254 StageHorizontalAlign ha
= STAGE_H_ALIGN_C
;
1255 if (_alignMode
.test(STAGE_ALIGN_L
)) ha
= STAGE_H_ALIGN_L
;
1256 else if (_alignMode
.test(STAGE_ALIGN_R
)) ha
= STAGE_H_ALIGN_R
;
1258 /// T takes precedence over B. Default is centred.
1259 StageVerticalAlign va
= STAGE_V_ALIGN_C
;
1260 if (_alignMode
.test(STAGE_ALIGN_T
)) va
= STAGE_V_ALIGN_T
;
1261 else if (_alignMode
.test(STAGE_ALIGN_B
)) va
= STAGE_V_ALIGN_B
;
1263 return std::make_pair(ha
, va
);
1266 /// Returns a string that represents the boolean state of the _showMenu
1269 movie_root::getShowMenuState() const
1274 /// Sets the value of _showMenu and calls the gui handler to process the
1275 /// fscommand to change the display of the context menu
1277 movie_root::setShowMenuState(bool state
)
1280 //FIXME: The gui code for show menu is semantically different than what
1281 // ActionScript expects it to be. In gtk.cpp the showMenu function hides
1282 // or shows the menubar. Flash expects this option to disable some
1283 // context menu items.
1284 // callInterface is the proper handler for this
1285 callInterface(HostMessage(HostMessage::SHOW_MENU
, _showMenu
));
1288 /// Returns the string representation of the current align mode,
1289 /// which must always be in the order: LTRB
1291 movie_root::getStageAlignMode() const
1294 if (_alignMode
.test(STAGE_ALIGN_L
)) align
.push_back('L');
1295 if (_alignMode
.test(STAGE_ALIGN_T
)) align
.push_back('T');
1296 if (_alignMode
.test(STAGE_ALIGN_R
)) align
.push_back('R');
1297 if (_alignMode
.test(STAGE_ALIGN_B
)) align
.push_back('B');
1303 movie_root::setStageScaleMode(ScaleMode sm
)
1305 if (_scaleMode
== sm
) return; // nothing to do
1307 bool notifyResize
= false;
1309 // If we go from or to noScale, we notify a resize
1310 // if and only if display viewport is != then actual
1311 // movie size. If there is not yet a _rootMovie (when scaleMode
1312 // is passed as a parameter to the player), we also don't notify a
1315 (sm
== SCALEMODE_NOSCALE
|| _scaleMode
== SCALEMODE_NOSCALE
)) {
1317 const movie_definition
* md
= _rootMovie
->definition();
1318 log_debug("Going to or from scaleMode=noScale. Viewport:%dx%d Def:%dx%d",
1319 _stageWidth
, _stageHeight
,
1320 md
->get_width_pixels(), md
->get_height_pixels());
1322 if ( _stageWidth
!= md
->get_width_pixels()
1323 || _stageHeight
!= md
->get_height_pixels() )
1325 notifyResize
= true;
1330 callInterface(HostMessage(HostMessage::UPDATE_STAGE
));
1333 as_object
* stage
= getBuiltinObject(*this, NSV::CLASS_STAGE
);
1335 callMethod(stage
, NSV::PROP_BROADCAST_MESSAGE
, "onResize");
1341 movie_root::setStageDisplayState(const DisplayState ds
)
1345 as_object
* stage
= getBuiltinObject(*this, NSV::CLASS_STAGE
);
1347 const bool fs
= _displayState
== DISPLAYSTATE_FULLSCREEN
;
1348 callMethod(stage
, NSV::PROP_BROADCAST_MESSAGE
, "onFullScreen", fs
);
1351 if (!_interfaceHandler
) return; // No registered callback
1353 HostMessage
e(HostMessage::SET_DISPLAYSTATE
, _displayState
);
1358 movie_root::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
1360 if (isInvalidated()) {
1365 for (Levels::reverse_iterator i
=_movies
.rbegin(), e
=_movies
.rend(); i
!=e
;
1367 i
->second
->add_invalidated_bounds(ranges
, force
);
1372 movie_root::minPopulatedPriorityQueue() const
1374 for (size_t l
= 0; l
< PRIORITY_SIZE
; ++l
) {
1375 if (!_actionQueue
[l
].empty()) return l
;
1377 return PRIORITY_SIZE
;
1381 movie_root::processActionQueue(size_t lvl
)
1383 ActionQueue::value_type
& q
= _actionQueue
[lvl
];
1385 assert(minPopulatedPriorityQueue() == lvl
);
1388 static unsigned calls
=0;
1390 bool actionsToProcess
= !q
.empty();
1391 if (actionsToProcess
) {
1392 log_debug("Processing %d actions in priority queue %d (call %u)",
1393 q
.size(), lvl
, calls
);
1397 // _actionQueue may be changed due to actions (appended-to)
1398 // this loop might be optimized by using an iterator
1399 // and a final call to .clear()
1400 while (!q
.empty()) {
1402 const std::unique_ptr
<ExecutableCode
> code(q
.pop_front().release());
1405 size_t minLevel
= minPopulatedPriorityQueue();
1406 if (minLevel
< lvl
) {
1408 log_debug("Actions pushed in priority %d (< %d), restarting the scan (call %u)",
1409 minLevel
, lvl
, calls
);
1418 if (actionsToProcess
) {
1419 log_debug("Done processing actions in priority queue %d (call %u)",
1424 return minPopulatedPriorityQueue();
1428 movie_root::flushHigherPriorityActionQueues()
1430 if (!processingActions()) {
1431 // only flush the actions queue when we are
1432 // processing the queue.
1433 // ie. we don't want to flush the queue
1434 // during executing user event handlers,
1435 // which are not pushed at the moment.
1439 if (_disableScripts
) {
1440 /// cleanup anything pushed later..
1441 clear(_actionQueue
);
1445 int lvl
= minPopulatedPriorityQueue();
1446 while (lvl
< _processingActionLevel
) {
1447 lvl
= processActionQueue(lvl
);
1452 movie_root::addLoadableObject(as_object
* obj
, std::unique_ptr
<IOChannel
> str
)
1454 _loadCallbacks
.emplace_back(std::move(str
), obj
);
1458 movie_root::addAdvanceCallback(ActiveRelay
* obj
)
1460 _objectCallbacks
.insert(obj
);
1464 movie_root::removeAdvanceCallback(ActiveRelay
* obj
)
1466 _objectCallbacks
.erase(obj
);
1470 movie_root::processActionQueue()
1472 if (_disableScripts
) {
1473 /// cleanup anything pushed later..
1474 clear(_actionQueue
);
1478 _processingActionLevel
= minPopulatedPriorityQueue();
1480 while (_processingActionLevel
< PRIORITY_SIZE
) {
1481 _processingActionLevel
= processActionQueue(_processingActionLevel
);
1484 // Cleanup the stack.
1485 _vm
.getStack().clear();
1489 movie_root::removeQueuedConstructor(MovieClip
* target
)
1491 ActionQueue::value_type
& pr
= _actionQueue
[PRIORITY_CONSTRUCT
];
1492 pr
.erase_if(RemoveTargetCode(target
));
1496 movie_root::pushAction(std::unique_ptr
<ExecutableCode
> code
, size_t lvl
)
1498 assert(lvl
< PRIORITY_SIZE
);
1499 _actionQueue
[lvl
].push_back(code
.release());
1503 movie_root::pushAction(const action_buffer
& buf
, DisplayObject
* target
)
1506 log_debug("Pushed action buffer for target %s",
1507 target
->getTargetPath());
1510 std::unique_ptr
<ExecutableCode
> code(new GlobalCode(buf
, target
));
1512 _actionQueue
[PRIORITY_DOACTION
].push_back(code
.release());
1516 movie_root::executeAdvanceCallbacks()
1518 if (!_objectCallbacks
.empty()) {
1520 // We have two considerations:
1521 // 1. any update can change the active callbacks by removing or
1522 // adding to the original callbacks list.
1523 // 2. Additionally, an as_object may destroy its own Relay. This can
1524 // happen if the callback itself calls a native constructor on
1525 // an object that already has a Relay. If this is an ActiveRelay
1526 // registered with movie_root, a pointer to the destroyed object
1527 // will still be held, resulting in memory corruption. This is an
1528 // *extremely* unlikely case, but we are very careful!
1530 // By copying to a new container we avoid errors caused by changes to
1531 // the original set (such as infinite recursions or invalidated
1532 // iterators). We also know that no as_object will be destroyed
1533 // during processing, even though its Relay may be.
1534 std::vector
<as_object
*> currentCallbacks
;
1536 std::transform(_objectCallbacks
.begin(), _objectCallbacks
.end(),
1537 std::back_inserter(currentCallbacks
),
1538 std::bind(CreatePointer
<as_object
>(),
1539 std::bind(std::mem_fun(&ActiveRelay::owner
),
1540 std::placeholders::_1
)));
1542 std::for_each(currentCallbacks
.begin(), currentCallbacks
.end(),
1546 if (!_loadCallbacks
.empty()) {
1547 _loadCallbacks
.remove_if(
1548 std::mem_fun_ref(&movie_root::LoadCallback::processLoad
));
1551 // _controlfd is set when running as a child process of a hosting
1552 // application. If it is set, we have to check the socket connection
1553 // for XML messages.
1554 if (_controlfd
> 0) {
1555 std::unique_ptr
<ExternalInterface::invoke_t
> invoke
=
1556 ExternalInterface::ExternalEventCheck(_controlfd
);
1558 if (processInvoke(invoke
.get()) == false) {
1559 if (!invoke
->name
.empty()) {
1560 log_error(_("Couldn't process ExternalInterface Call %s"),
1567 processActionQueue();
1571 movie_root::processInvoke(ExternalInterface::invoke_t
*invoke
)
1573 GNASH_REPORT_FUNCTION
;
1575 if (!invoke
|| invoke
->name
.empty()) return false;
1577 log_debug("Processing %s call from the Browser.", invoke
->name
);
1579 std::stringstream ss
; // ss is the response string
1581 // These are the default methods used by ExternalInterface
1582 if (invoke
->name
== "Quit") {
1583 // Leave to the hosting application. If there isn't one or it
1584 // chooses not to exit, that's fine.
1585 if (_interfaceHandler
) _interfaceHandler
->exit();
1587 } else if (invoke
->name
== "SetVariable") {
1588 MovieClip
*mc
= getLevel(0);
1589 as_object
*obj
= getObject(mc
);
1591 std::string var
= invoke
->args
[0].to_string();
1592 as_value
&val
= invoke
->args
[1] ;
1593 obj
->set_member(getURI(vm
, var
), val
);
1594 // SetVariable doesn't send a response
1595 } else if (invoke
->name
== "GetVariable") {
1596 MovieClip
*mc
= getLevel(0);
1597 as_object
*obj
= getObject(mc
);
1599 as_environment timeline
= mc
->get_environment();
1600 as_environment::ScopeStack scope
;
1601 as_object
*container
= NULL
;
1602 std::string var
= invoke
->args
[0].to_string();
1603 scope
.push_back(obj
);
1604 as_value val
= getVariable(timeline
, var
, scope
, &container
);
1605 if (container
!= NULL
) {
1606 // If the variable exists, GetVariable returns a string
1607 // representation of its value. Variable with undefined
1608 // or null value counts as exist too.
1609 ss
<< ExternalInterface::toXML(val
.to_string(vm
.getSWFVersion()));
1612 // If the variable does not exist, GetVariable sends null value
1613 ss
<< ExternalInterface::toXML(as_value((as_object
*)NULL
));
1616 } else if (invoke
->name
== "GotoFrame") {
1617 log_unimpl(_("ExternalInterface::GotoFrame()"));
1618 // GotoFrame doesn't send a response
1619 } else if (invoke
->name
== "IsPlaying") {
1621 callInterface
<bool>(HostMessage(HostMessage::EXTERNALINTERFACE_ISPLAYING
));
1622 as_value
val(result
);
1623 ss
<< ExternalInterface::toXML(val
);
1625 } else if (invoke
->name
== "LoadMovie") {
1626 log_unimpl(_("ExternalInterface::LoadMovie()"));
1627 // LoadMovie doesn't send a response
1628 } else if (invoke
->name
== "Pan") {
1629 std::string arg
= invoke
->args
[0].to_string();
1631 arg
+= invoke
->args
[0].to_string();
1633 arg
+= invoke
->args
[1].to_string();
1635 arg
+= invoke
->args
[2].to_string();
1636 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_PAN
, arg
));
1637 // Pan doesn't send a response
1638 } else if (invoke
->name
== "PercentLoaded") {
1639 MovieClip
*mc
= getLevel(0);
1640 int loaded
= mc
->get_bytes_loaded();
1641 int total
= mc
->get_bytes_total();
1643 if (total
> 0) { /* avoid division by zero */
1644 percent
= 100 * loaded
/ total
;
1646 as_value
val(percent
);
1647 // PercentLoaded sends the percentage
1648 ss
<< ExternalInterface::toXML(val
);
1650 } else if (invoke
->name
== "Play") {
1651 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_PLAY
));
1652 // Play doesn't send a response
1653 } else if (invoke
->name
== "Rewind") {
1654 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_REWIND
));
1655 // Rewind doesn't send a response
1656 } else if (invoke
->name
== "SetZoomRect") {
1657 std::string arg
= invoke
->args
[0].to_string();
1659 arg
+= invoke
->args
[0].to_string();
1661 arg
+= invoke
->args
[1].to_string();
1663 arg
+= invoke
->args
[2].to_string();
1665 arg
+= invoke
->args
[3].to_string();
1666 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_SETZOOMRECT
, arg
));
1667 // SetZoomRect doesn't send a response
1668 } else if (invoke
->name
== "StopPlay") {
1669 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_STOPPLAY
));
1670 // StopPlay doesn't send a response
1671 } else if (invoke
->name
== "Zoom") {
1672 std::string var
= invoke
->args
[0].to_string();
1673 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_ZOOM
, var
));
1674 // Zoom doesn't send a response
1675 } else if (invoke
->name
== "TotalFrames") {
1676 MovieClip
*mc
= getLevel(0);
1677 as_value
val(mc
->get_loaded_frames());
1678 // TotalFrames sends the number of frames in the movie
1679 ss
<< ExternalInterface::toXML(val
);
1682 callExternalCallback(invoke
->name
, invoke
->args
);
1686 if (!ss
.str().empty()) {
1688 log_debug("Attempt to write response to ExternalInterface "
1689 "requests fd %d", _hostfd
);
1690 int ret
= write(_hostfd
, ss
.str().c_str(), ss
.str().size());
1692 log_error(_("Could not write to user-provided host requests "
1693 "fd %d: %s"), _hostfd
, std::strerror(errno
));
1697 log_debug("No response needed for %s request", invoke
->name
);
1704 movie_root::executeTimers()
1706 #ifdef GNASH_DEBUG_TIMERS_EXPIRATION
1707 log_debug("Checking %d timers for expiry", _intervalTimers
.size());
1710 // Don't do anything if we have no timers, just return so we don't
1711 // waste cpu cycles.
1712 if (_intervalTimers
.empty()) {
1716 unsigned long now
= _vm
.getTime();
1718 typedef std::multimap
<unsigned long, Timer
*>
1721 ExpiredTimers expiredTimers
;
1723 for (TimerMap::iterator it
= _intervalTimers
.begin(),
1724 itEnd
= _intervalTimers
.end(); it
!= itEnd
; ) {
1726 TimerMap::iterator nextIterator
= it
;
1729 Timer
* timer
= it
->second
.get();
1731 if (timer
->cleared()) {
1732 // this timer was cleared, erase it
1733 _intervalTimers
.erase(it
);
1736 unsigned long elapsed
;
1737 if (timer
->expired(now
, elapsed
)) {
1738 expiredTimers
.insert(std::make_pair(elapsed
, timer
));
1745 foreachSecond(expiredTimers
.begin(), expiredTimers
.end(),
1746 &Timer::executeAndReset
);
1748 if (!expiredTimers
.empty())
1749 processActionQueue();
1753 movie_root::markReachableResources() const
1755 _vm
.markReachableResources();
1757 foreachSecond(_movies
.rbegin(), _movies
.rend(), &MovieClip::setReachable
);
1759 // Mark original top-level movie
1760 // This should always be in _movies, but better make sure
1762 _rootMovie
->setReachable();
1764 // Mark mouse entities
1765 _mouseButtonState
.markReachableResources();
1767 // Mark timer targets
1768 foreachSecond(_intervalTimers
.begin(), _intervalTimers
.end(),
1769 &Timer::markReachableResources
);
1771 std::for_each(_objectCallbacks
.begin(), _objectCallbacks
.end(),
1772 std::mem_fun(&ActiveRelay::setReachable
));
1773 std::for_each(_loadCallbacks
.begin(), _loadCallbacks
.end(),
1774 std::mem_fun_ref(&movie_root::LoadCallback::setReachable
));
1776 // Mark LoadMovieRequest handlers as reachable
1777 _movieLoader
.setReachable();
1779 // Mark ExternalInterface callbacks and instances as reachable
1780 for (const auto& method
: _externalCallbackMethods
) {
1781 if (method
.second
) {
1782 method
.second
->setReachable();
1785 for (const auto& instance
: _externalCallbackInstances
) {
1786 if (instance
.second
) {
1787 instance
.second
->setReachable();
1791 // Mark resources reachable by queued action code
1792 for (size_t lvl
= 0; lvl
< PRIORITY_SIZE
; ++lvl
)
1794 const ActionQueue::value_type
& q
= _actionQueue
[lvl
];
1795 std::for_each(q
.begin(), q
.end(),
1796 std::mem_fun_ref(&ExecutableCode::markReachableResources
));
1799 if (_currentFocus
) _currentFocus
->setReachable();
1801 // Mark DisplayObject being dragged, if any
1802 if (_dragState
) _dragState
->markReachableResources();
1804 // NOTE: cleanupDisplayList() should have cleaned up all
1805 // unloaded live characters. The remaining ones should be marked
1806 // by their parents.
1807 #if ( GNASH_PARANOIA_LEVEL > 1 ) || defined(ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION)
1808 for (LiveChars::const_iterator i
=_liveChars
.begin(), e
=_liveChars
.end();
1810 #ifdef ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION
1811 (*i
)->setReachable();
1813 assert((*i
)->isReachable());
1818 foreachSecond(_registeredClasses
.begin(), _registeredClasses
.end(), &as_function::setReachable
);
1822 movie_root::getTopmostMouseEntity(std::int32_t x
, std::int32_t y
) const
1824 for (Levels::const_reverse_iterator i
=_movies
.rbegin(), e
=_movies
.rend();
1827 InteractiveObject
* ret
= i
->second
->topmostMouseEntity(x
, y
);
1828 if (ret
) return ret
;
1834 const DisplayObject
*
1835 movie_root::findDropTarget(std::int32_t x
, std::int32_t y
,
1836 DisplayObject
* dragging
) const
1838 for (Levels::const_reverse_iterator i
=_movies
.rbegin(), e
=_movies
.rend();
1841 const DisplayObject
* ret
= i
->second
->findDropTarget(x
, y
, dragging
);
1842 if (ret
) return ret
;
1847 /// This should store a callback object in movie_root.
1849 movie_root::addExternalCallback(const std::string
& name
, as_object
* callback
,
1850 as_object
* instance
)
1852 // Store registered callback and instance reference for later use
1853 // by callExternalCallback()
1854 if(_externalCallbackMethods
.count(name
)>0) {
1855 _externalCallbackMethods
.erase(name
);
1856 _externalCallbackInstances
.erase(name
);
1858 _externalCallbackMethods
.insert(
1859 std::pair
<std::string
, as_object
*>(name
,callback
)
1861 _externalCallbackInstances
.insert(
1862 std::pair
<std::string
, as_object
*>(name
,instance
)
1865 // When an external callback is added, we have to notify the plugin
1866 // that this method is available.
1868 std::vector
<as_value
> fnargs
;
1869 fnargs
.push_back(name
);
1870 std::string msg
= ExternalInterface::makeInvoke("addMethod", fnargs
);
1872 const size_t ret
= ExternalInterface::writeBrowser(_hostfd
, msg
);
1873 if (ret
!= msg
.size()) {
1874 log_error(_("Could not write to browser fd #%d: %s"),
1875 _hostfd
, std::strerror(errno
));
1880 /// This calls a JavaScript method in the web page
1882 /// @example "ExternalInterace::call message"
1885 /// <invoke name="methodname" returntype="xml">
1886 /// <arguments></arguments>
1888 /// <arguments></arguments>
1891 /// May return any supported type like Number or String in XML format.
1895 movie_root::callExternalJavascript(const std::string
&name
,
1896 const std::vector
<as_value
> &fnargs
)
1899 // If the browser is connected, we send an Invoke message to the
1901 if (_controlfd
>= 0 && _hostfd
>= 0) {
1902 std::string msg
= ExternalInterface::makeInvoke(name
, fnargs
);
1904 const size_t ret
= ExternalInterface::writeBrowser(_hostfd
, msg
);
1905 if (ret
!= msg
.size()) {
1906 log_error(_("Could not write to browser fd #%d: %s"),
1907 _hostfd
, std::strerror(errno
));
1909 // Now read the response from the browser after it's exectuted
1910 // the JavaScript function.
1911 result
= ExternalInterface::readBrowser(_controlfd
);
1918 // Call one of the registered callbacks, and return the result to
1919 // Javascript in the browser.
1921 movie_root::callExternalCallback(const std::string
&name
,
1922 const std::vector
<as_value
> &fnargs
)
1924 ExternalCallbackMethods::iterator method_iterator
;
1925 ExternalCallbackInstances::iterator instance_iterator
;
1927 as_object
*instance
;
1931 // Look up for ActionScript function registered as callback
1932 method_iterator
= _externalCallbackMethods
.find(name
);
1933 if (method_iterator
== _externalCallbackMethods
.end()) {
1934 val
.set_undefined();
1936 method
= method_iterator
->second
;
1938 // Look up for Object instance to use as "this" in the callback
1939 instance_iterator
= _externalCallbackInstances
.find(name
);
1940 if (instance_iterator
== _externalCallbackInstances
.end()) {
1941 instance
= as_value((as_object
*)NULL
).to_object(getVM());
1943 else instance
= instance_iterator
->second
;
1945 // Use _global object as "this" instance if the callback is originally
1946 // registered with null or undefined one.
1947 if (instance
== NULL
) {
1948 instance
= &getGlobal(*method
);
1951 // Populate function call arguments
1952 for (std::vector
<as_value
>::const_iterator args_iterator
1954 args_iterator
!= fnargs
.end();
1957 args
+= *args_iterator
;
1960 // Call the registered callback
1961 val
=invoke(as_value(method
), as_environment(getVM()), instance
, args
);
1965 result
= ExternalInterface::toXML(val
);
1967 // If the browser is connected, we send an Invoke message to the
1970 std::stringstream ss
;
1975 ret
= ExternalInterface::writeBrowser(_hostfd
, ss
.str());
1976 if (ret
!= ss
.str().size()) {
1977 log_error(_("Could not write to browser fd #%d: %s"),
1978 _hostfd
, std::strerror(errno
));
1986 movie_root::removeButton(Button
* listener
)
1988 _buttonListeners
.remove(listener
);
1992 movie_root::registerButton(Button
* listener
)
1994 if (std::find(_buttonListeners
.begin(), _buttonListeners
.end(), listener
)
1995 != _buttonListeners
.end()) {
1998 _buttonListeners
.push_front(listener
);
2002 movie_root::cleanupDisplayList()
2004 //#define GNASH_DEBUG_INSTANCE_LIST 1
2006 #ifdef GNASH_DEBUG_INSTANCE_LIST
2007 static size_t maxLiveChars
= 0;
2010 // Let every sprite cleanup the local DisplayList
2012 // TODO: we might skip this additional scan by delegating
2013 // cleanup of the local DisplayLists in the ::display
2014 // method of each sprite, but that will introduce
2015 // problems when we implement skipping ::display()
2016 // when late on FPS. Alternatively we may have the
2017 // MovieClip::markReachableResources take care
2018 // of cleaning up unloaded... but that will likely
2019 // introduce problems when allowing the GC to run
2020 // at arbitrary times.
2021 // The invariant to keep is that cleanup of unloaded DisplayObjects
2022 // in local display lists must happen at the *end* of global action
2023 // queue processing.
2024 foreachSecond(_movies
.rbegin(), _movies
.rend(),
2025 &MovieClip::cleanupDisplayList
);
2027 // Now remove from the instance list any unloaded DisplayObject
2028 // Note that some DisplayObjects may be unloaded but not yet destroyed,
2029 // in this case we'll also destroy them, which in turn might unload
2030 // further DisplayObjects, maybe already scanned, so we keep scanning
2031 // the list until no more unloaded-but-non-destroyed DisplayObjects
2033 // Keeping unloaded-but-non-destroyed DisplayObjects wouldn't really hurt
2034 // in that ::advanceLiveChars would skip any unloaded DisplayObjects.
2035 // Still, the more we remove the less work GC has to do...
2039 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2043 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2049 // Remove unloaded MovieClips from the _liveChars list
2050 _liveChars
.remove_if([&](MovieClip
* ch
) {
2051 if (ch
->unloaded()) {
2052 // the sprite might have been destroyed already
2053 // by effect of an unload() call with no onUnload
2054 // handlers available either in self or child
2056 if (!ch
->isDestroyed()) {
2058 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2059 cout
<< ch
->getTarget() << "(" << typeName(*ch
) <<
2060 ") was unloaded but not destroyed, destroying now" <<
2064 // destroy() might mark already-scanned chars as unloaded
2067 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2069 cout
<< ch
->getTarget() << "(" << typeName(*ch
) <<
2070 ") was unloaded and destroyed" << endl
;
2074 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2082 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2083 cout
<< " Scan " << scansCount
<< " cleaned " << cleaned
<<
2084 " instances" << endl
;
2088 #ifdef GNASH_DEBUG_INSTANCE_LIST
2089 size_t count
= std::distance(begin(_liveChars
), end(_liveChars
));
2090 if (count
> maxLiveChars
) {
2091 maxLiveChars
= count
;
2092 log_debug("Global instance list grew to %d entries", maxLiveChars
);
2098 movie_root::advanceLiveChars()
2101 log_debug("---- movie_root::advance: %d live DisplayObjects in the global list",
2105 // Advance all characters, then notify them.
2106 LiveChars::iterator it
;
2107 for (MovieClip
* liveChar
: _liveChars
) {
2108 advanceLiveChar(liveChar
);
2110 for (MovieClip
* liveChar
: _liveChars
) {
2111 notifyLoad(liveChar
);
2116 movie_root::set_background_color(const rgba
& color
)
2118 if (m_background_color_set
) return;
2119 m_background_color_set
= true;
2121 rgba newcolor
= color
;
2122 newcolor
.m_a
= m_background_color
.m_a
;
2124 if (m_background_color
!= newcolor
) {
2126 m_background_color
= newcolor
;
2131 movie_root::set_background_alpha(float alpha
)
2133 const std::uint8_t newAlpha
= clamp
<int>(frnd(alpha
* 255.0f
), 0, 255);
2135 if (m_background_color
.m_a
!= newAlpha
) {
2137 m_background_color
.m_a
= newAlpha
;
2142 movie_root::findCharacterByTarget(const std::string
& tgtstr
) const
2144 if (tgtstr
.empty()) return nullptr;
2146 // NOTE: getRootMovie() would be problematic in case the original
2147 // root movie is replaced by a load to _level0...
2148 // (but I guess we'd also drop loadMovie requests in that
2149 // case... just not tested)
2150 as_object
* o
= getObject(_movies
.begin()->second
);
2153 std::string::size_type from
= 0;
2154 while (std::string::size_type to
= tgtstr
.find('.', from
)) {
2155 std::string
part(tgtstr
, from
, to
- from
);
2157 // TODO: there is surely a cleaner way to implement path finding.
2158 const ObjectURI
& uri
= getURI(_vm
, part
);
2159 o
= o
->displayObject() ?
2160 o
->displayObject()->pathElement(uri
) :
2161 getPathElement(*o
, uri
);
2164 #ifdef GNASH_DEBUG_TARGET_RESOLUTION
2165 log_debug("Evaluating DisplayObject target path: element"
2166 "'%s' of path '%s' not found", part
, tgtstr
);
2170 if (to
== std::string::npos
) break;
2173 return get
<DisplayObject
>(o
);
2177 movie_root::getURL(const std::string
& urlstr
, const std::string
& target
,
2178 const std::string
& data
, MovieClip::VariablesMethod method
)
2180 log_network("%s: HOSTFD is %d", __FUNCTION__
, _hostfd
);
2183 /// If there is no hosting application, call the URL launcher. For
2184 /// safety, we resolve the URL against the base URL for this run.
2185 /// The data is not sent at all.
2186 URL
url(urlstr
, _runResources
.streamProvider().baseURL());
2188 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
2189 std::string command
= rcfile
.getURLOpenerFormat();
2191 /// Try to avoid letting flash movies execute
2192 /// arbitrary commands (sic).
2194 /// NOTE: it is assumed that the user-provided command
2195 /// puts the url place-holder within single quotes.
2196 /// Failing that, there will be the possibility
2197 /// for malicious SWF files to run arbitrary commands.
2200 /// Check safety of user provided command
2202 /// TODO: improve this check
2203 /// - quote nested in double quote
2204 /// - %u after second quote
2206 /// TODO: check only once
2208 bool command_is_safe
= false;
2210 std::string::size_type loc
= command
.find('\'');
2211 if ( loc
== std::string::npos
) break;
2212 loc
= command
.find("%u", loc
);
2213 if ( loc
== std::string::npos
) break;
2214 loc
= command
.find('\'', loc
);
2215 if ( loc
== std::string::npos
) break;
2216 command_is_safe
= true;
2219 if ( ! command_is_safe
) {
2220 log_error("The '%%u' token in urlOpenerFormat rc directive should be within single quotes");
2224 std::string safeurl
= urlstr
;
2225 boost::replace_all(safeurl
, "'", "'\\''");
2227 boost::replace_all(command
, "%u", safeurl
);
2229 log_debug("Launching URL: %s", command
);
2230 const int ret
= std::system(command
.c_str());
2232 log_error(_("Fork failed launching URL opener '%s'"), command
);
2237 /// This is when there is a hosting application.
2238 std::vector
<as_value
> fnargs
;
2239 // The first argument we push on the stack is the URL
2240 fnargs
.emplace_back(urlstr
);
2242 // The second argument we push is the method
2244 case MovieClip::METHOD_POST
:
2245 fnargs
.emplace_back("POST");
2247 case MovieClip::METHOD_GET
:
2248 fnargs
.emplace_back("GET");
2250 case MovieClip::METHOD_NONE
:
2252 fnargs
.emplace_back("GET");
2256 // The third argument is the target, which is something like _blank
2258 if (!target
.empty()) {
2259 fnargs
.emplace_back(target
);
2261 // Add any data as the optional 4th argument
2262 if (!data
.empty()) {
2263 // We have to write a value here so the data field is the fourth
2264 if (target
.empty()) {
2265 fnargs
.emplace_back("none");
2267 fnargs
.emplace_back(data
);
2270 // TODO: should mutex-protect this ?
2271 // NOTE: we are assuming the hostfd is set in blocking mode here..
2273 log_debug("Attempt to write geturl requests fd #%d", _hostfd
);
2275 std::string msg
= ExternalInterface::makeInvoke("getURL", fnargs
);
2277 const size_t ret
= ExternalInterface::writeBrowser(_hostfd
, msg
);
2278 if (ret
< msg
.size()) {
2279 log_error(_("Could only write %d bytes to fd #%d"),
2285 movie_root::setScriptLimits(std::uint16_t recursion
, std::uint16_t timeout
)
2287 if ( recursion
== _recursionLimit
&& _timeoutLimit
== timeout
) {
2288 // avoid the debug log...
2292 if (RcInitFile::getDefaultInstance().lockScriptLimits()) {
2293 LOG_ONCE(log_debug("SWF ScriptLimits tag attempting to set "
2294 "recursionLimit=%1% and scriptsTimeout=%2% ignored "
2295 "as per rcfile directive", recursion
, timeout
) );
2299 // This tag reported in some sources to be ignored for movies
2300 // below SWF7. However, on Linux with PP version 9, the tag
2301 // takes effect on SWFs of any version.
2302 log_debug("Setting script limits: max recursion %d, "
2303 "timeout %d seconds", recursion
, timeout
);
2305 _recursionLimit
= recursion
;
2306 _timeoutLimit
= timeout
;
2313 movie_root::getMovieInfo(InfoTree
& tr
, InfoTree::iterator it
)
2316 const movie_definition
* def
= _rootMovie
->definition();
2319 it
= tr
.insert(it
, std::make_pair("Stage Properties", ""));
2321 InfoTree::iterator localIter
= tr
.append_child(it
,
2322 std::make_pair("Root VM version",
2323 def
->isAS3() ? "AVM2 (unsupported)" : "AVM1"));
2325 std::ostringstream os
;
2326 os
<< "SWF " << def
->get_version();
2327 localIter
= tr
.append_child(it
, std::make_pair("Root SWF version",
2329 localIter
= tr
.append_child(it
, std::make_pair("URL", def
->get_url()));
2331 // Is there a sychronizating sound or not?
2332 localIter
= tr
.append_child(it
, std::make_pair("Streaming sound",
2333 _timelineSound
? "yes" : "no"));
2335 // TODO: format this better?
2336 localIter
= tr
.append_child(it
, std::make_pair("Descriptive metadata",
2337 def
->getDescriptiveMetadata()));
2339 /// Stage: real dimensions.
2341 os
<< def
->get_width_pixels() <<
2342 "x" << def
->get_height_pixels();
2343 localIter
= tr
.append_child(it
, std::make_pair("Real dimensions",
2346 /// Stage: rendered dimensions.
2348 os
<< _stageWidth
<< "x" << _stageHeight
;
2349 localIter
= tr
.append_child(it
, std::make_pair("Rendered dimensions",
2352 // Stage: scripts state (enabled/disabled)
2353 localIter
= tr
.append_child(it
, std::make_pair("Scripts",
2354 _disableScripts
? " disabled" : "enabled"));
2356 getCharacterTree(tr
, it
);
2360 movie_root::getCharacterTree(InfoTree
& tr
, InfoTree::iterator it
)
2362 InfoTree::iterator localIter
;
2364 /// Stage: number of live MovieClips.
2365 std::ostringstream os
;
2366 os
<< std::distance(begin(_liveChars
), end(_liveChars
));
2367 localIter
= tr
.append_child(it
, std::make_pair(_("Live MovieClips"),
2370 /// DisplayObject tree
2371 for (Levels::const_iterator i
= _movies
.begin(), e
= _movies
.end();
2373 i
->second
->getMovieInfo(tr
, localIter
);
2380 movie_root::handleFsCommand(const std::string
& cmd
, const std::string
& arg
)
2383 if (_fsCommandHandler
) _fsCommandHandler
->notify(cmd
, arg
);
2387 isLevelTarget(int version
, const std::string
& name
, unsigned int& levelno
)
2390 if (name
.compare(0, 6, "_level")) return false;
2393 StringNoCaseEqual noCaseCmp
;
2394 if (!noCaseCmp(name
.substr(0, 6), "_level")) return false;
2397 if (name
.find_first_not_of("0123456789", 7) != std::string::npos
) {
2400 // getting 0 here for "_level" is intentional
2401 levelno
= std::strtoul(name
.c_str() + 6, nullptr, 0);
2406 stringToStageAlign(const std::string
& str
)
2410 // Easy enough to do bitwise - std::bitset is not
2411 // really necessary!
2412 if (str
.find_first_of("lL") != std::string::npos
) {
2413 am
|= 1 << movie_root::STAGE_ALIGN_L
;
2416 if (str
.find_first_of("tT") != std::string::npos
) {
2417 am
|= 1 << movie_root::STAGE_ALIGN_T
;
2420 if (str
.find_first_of("rR") != std::string::npos
) {
2421 am
|= 1 << movie_root::STAGE_ALIGN_R
;
2424 if (str
.find_first_of("bB") != std::string::npos
) {
2425 am
|= 1 << movie_root::STAGE_ALIGN_B
;
2433 movie_root::LoadCallback::setReachable() const
2435 _obj
->setReachable();
2439 movie_root::LoadCallback::processLoad()
2442 callMethod(_obj
, NSV::PROP_ON_DATA
, as_value());
2446 const size_t chunksize
= 65535;
2447 std::uint8_t chunk
[chunksize
];
2449 size_t actuallyRead
= _stream
->readNonBlocking(chunk
, chunksize
);
2451 // We must still call onData if the stream is in error condition, e.g.
2452 // when an HTTP 404 error is returned.
2453 if (_stream
->bad()) {
2454 callMethod(_obj
, NSV::PROP_ON_DATA
, as_value());
2460 // set total size only on first read
2462 _obj
->set_member(NSV::PROP_uBYTES_TOTAL
, _stream
->size());
2465 _buf
.append(chunk
, actuallyRead
);
2467 _obj
->set_member(NSV::PROP_uBYTES_LOADED
, _buf
.size());
2469 log_debug("LoadableObject Loaded %d bytes, reaching %d/%d",
2470 actuallyRead
, _buf
.size(), _stream
->size());
2473 // We haven't finished till EOF
2474 if (!_stream
->eof()) return false;
2476 log_debug("LoadableObject reached EOF (%d/%d loaded)",
2477 _buf
.size(), _stream
->size());
2479 // got nothing, won't bother BOFs of nulls
2481 callMethod(_obj
, NSV::PROP_ON_DATA
, as_value());
2485 // Terminate the string
2486 _buf
.appendByte('\0');
2488 // Strip BOM, if any.
2489 // See http://savannah.gnu.org/bugs/?19915
2490 utf8::TextEncoding encoding
;
2491 size_t size
= _buf
.size();
2493 // NOTE: the call below will possibly change 'size' parameter
2494 const char* bufptr
= utf8::stripBOM(
2495 reinterpret_cast<const char*>(_buf
.data()), size
, encoding
);
2496 if (encoding
!= utf8::encUTF8
&& encoding
!= utf8::encUNSPECIFIED
) {
2497 log_unimpl(_("%s to UTF8 conversion in LoadableObject input parsing"),
2498 utf8::textEncodingName(encoding
));
2501 // NOTE: Data copy here !!
2502 as_value
dataVal(bufptr
);
2504 // NOTE: we could release memory associated
2505 // with the buffer here, before invoking a new method,
2506 // but at the time of writing there's no method of SimpleBuffer
2507 // providing memory release except destruction. Will be
2508 // destroyed as soon as we return though...
2510 callMethod(_obj
, NSV::PROP_ON_DATA
, std::move(dataVal
));
2516 movie_root::callInterface(const HostInterface::Message
& e
) const
2518 if (!_interfaceHandler
) {
2519 log_error(_("Hosting application registered no callback for events/queries, can't call %s(%s)"));
2522 _interfaceHandler
->call(e
);
2527 movie_root::testInvariant() const
2529 // TODO: fill this function !
2530 // The _movies map can not invariantably
2531 // be non-empty as the stage is autonomous
2533 //assert( ! _movies.empty() );
2540 // Return whether any action triggered by this event requires display redraw.
2542 /// TODO: make this code more readable !
2544 generate_mouse_button_events(movie_root
& mr
, MouseButtonState
& ms
)
2546 // Did this event trigger any action that needs redisplay ?
2547 bool need_redisplay
= false;
2549 // TODO: have mouseEvent return
2550 // whether the action must trigger
2554 // TODO: Handle trackAsMenu dragOver
2555 // Handle onDragOut, onDragOver
2556 if (!ms
.wasInsideActiveEntity
) {
2558 if (ms
.topmostEntity
== ms
.activeEntity
) {
2561 if (ms
.activeEntity
) {
2562 ms
.activeEntity
->mouseEvent(event_id(event_id::DRAG_OVER
));
2563 need_redisplay
=true;
2565 ms
.wasInsideActiveEntity
= true;
2568 else if (ms
.topmostEntity
!= ms
.activeEntity
) {
2570 if (ms
.activeEntity
) {
2571 ms
.activeEntity
->mouseEvent(event_id(event_id::DRAG_OUT
));
2572 need_redisplay
=true;
2574 ms
.wasInsideActiveEntity
= false;
2577 // Handle onRelease, onReleaseOutside
2579 // Mouse button just went up.
2582 if (ms
.activeEntity
) {
2583 if (ms
.wasInsideActiveEntity
) {
2585 ms
.activeEntity
->mouseEvent(event_id(event_id::RELEASE
));
2586 need_redisplay
= true;
2589 // TODO: Handle trackAsMenu
2591 ms
.activeEntity
->mouseEvent(
2592 event_id(event_id::RELEASE_OUTSIDE
));
2593 // We got out of active entity
2594 ms
.activeEntity
= nullptr; // so we don't get RollOut next...
2595 need_redisplay
= true;
2599 return need_redisplay
;
2603 // New active entity is whatever is below the mouse right now.
2604 if (ms
.topmostEntity
!= ms
.activeEntity
) {
2606 if (ms
.activeEntity
) {
2607 ms
.activeEntity
->mouseEvent(event_id(event_id::ROLL_OUT
));
2608 need_redisplay
=true;
2611 ms
.activeEntity
= ms
.topmostEntity
;
2614 if (ms
.activeEntity
) {
2615 ms
.activeEntity
->mouseEvent(event_id(event_id::ROLL_OVER
));
2616 need_redisplay
=true;
2619 ms
.wasInsideActiveEntity
= true;
2622 // mouse button press
2626 // Try setting focus on the new DisplayObject. This will handle
2627 // all necessary events and removal of current focus.
2628 // Do not set focus to NULL.
2629 if (ms
.activeEntity
) {
2630 mr
.setFocus(ms
.activeEntity
);
2632 ms
.activeEntity
->mouseEvent(event_id(event_id::PRESS
));
2633 need_redisplay
= true;
2636 ms
.wasInsideActiveEntity
= true;
2641 return need_redisplay
;
2645 const DisplayObject
*
2646 getNearestObject(const DisplayObject
* o
)
2650 if (isReferenceable(*o
)) return o
;
2656 getBuiltinObject(movie_root
& mr
, const ObjectURI
& cl
)
2658 Global_as
& gl
= *mr
.getVM().getGlobal();
2661 if (!gl
.get_member(cl
, &val
)) return nullptr;
2662 return toObject(val
, mr
.getVM());
2666 advanceLiveChar(MovieClip
* mo
)
2668 if (!mo
->unloaded()) {
2670 log_debug(" advancing DisplayObject %s", mo
->getTarget());
2676 log_debug(" DisplayObject %s is unloaded, not advancing it",
2683 notifyLoad(MovieClip
* mo
)
2685 if ( mo
->parent() ) {
2690 } // anonymous namespace
2691 } // namespace gnash
2695 // indent-tabs-mode: nil