1 // movie_root.cpp: The root movie, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 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/erase.hpp>
31 #include <boost/algorithm/string/replace.hpp>
32 #include <boost/ptr_container/ptr_map.hpp>
33 #include <boost/ptr_container/ptr_deque.hpp>
34 #include <boost/algorithm/string/case_conv.hpp>
35 #include <boost/bind.hpp>
37 #include "GnashSystemIOHeaders.h" // write()
39 #include "MovieClip.h"
42 #include "ExecutableCode.h"
44 #include "namedStrings.h"
45 #include "GnashException.h"
46 #include "sound_handler.h"
49 #include "MovieFactory.h"
50 #include "GnashAlgorithm.h"
51 #include "GnashNumeric.h"
52 #include "Global_as.h"
54 #include "IOChannel.h"
55 #include "RunResources.h"
57 #include "ExternalInterface.h"
58 #include "TextField.h"
60 #include "Transform.h"
61 #include "StreamProvider.h"
67 //#define GNASH_DEBUG 1
68 //#define GNASH_DEBUG_LOADMOVIE_REQUESTS_PROCESSING 1
69 //#define GNASH_DEBUG_TIMERS_EXPIRATION 1
71 // Defining the macro below prints info about
72 // cleanup of live chars (advanceable + key/mouse listeners)
73 // Is useful in particular to check for cost of multiple scans
74 // when a movie destruction destrois more elements.
76 // NOTE: I think the whole confusion here was introduced
77 // by zou making it "optional" to ::unload() childs
78 // when being unloaded. Zou was trying to avoid
79 // queuing an onUnload event, which I suggested we'd
80 // do by having unload() take an additional argument
81 // or similar. Failing to tag childs as unloaded
82 // will result in tagging them later (in ::destroy)
83 // which will require scanning the lists again
84 // (key/mouse + advanceable).
85 // See https://savannah.gnu.org/bugs/index.php?21804
87 //#define GNASH_DEBUG_DLIST_CLEANUP 1
91 // Forward declarations
93 bool generate_mouse_button_events(movie_root
& mr
, MouseButtonState
& ms
);
94 const DisplayObject
* getNearestObject(const DisplayObject
* o
);
95 as_object
* getBuiltinObject(movie_root
& mr
, const ObjectURI
& cl
);
96 void advanceLiveChar(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 boost::mem_fn(&movie_root::ActionQueue::value_type::clear
));
132 } // anonymous namespace
135 movie_root::movie_root(const movie_definition
& def
,
136 VirtualClock
& clock
, const RunResources
& runResources
)
139 _runResources(runResources
),
140 _vm(def
.get_version(), *this, clock
),
141 _interfaceHandler(0),
142 _fsCommandHandler(0),
145 m_background_color(255, 255, 255, 255),
146 m_background_color_set(false),
150 _lastKeyEvent(key::INVALID
),
156 _disableScripts(false),
157 _processingActionLevel(PRIORITY_SIZE
),
160 _quality(QUALITY_HIGH
),
162 _allowScriptAccess(SCRIPT_ACCESS_SAME_DOMAIN
),
164 _scaleMode(SCALEMODE_SHOWALL
),
165 _displayState(DISPLAYSTATE_NORMAL
),
166 _recursionLimit(256),
168 _movieAdvancementDelay(83), // ~12 fps by default
169 _lastMovieAdvancement(0),
173 // This takes care of informing the renderer (if present) too.
174 setQuality(QUALITY_HIGH
);
178 movie_root::disableScripts()
180 _disableScripts
= true;
182 // NOTE: we won't clear the action queue now
183 // to avoid invalidating iterators as we've
184 // been probably called during processing
188 movie_root::~movie_root()
191 _intervalTimers
.clear();
192 _movieLoader
.clear();
194 assert(testInvariant());
198 movie_root::init(movie_definition
* def
, const MovieClip::MovieVariables
& vars
)
200 Movie
* mr
= def
->createMovie(*_vm
.getGlobal());
201 mr
->setVariables(vars
);
207 movie_root::setRootMovie(Movie
* movie
)
211 const movie_definition
* md
= movie
->definition();
212 float fps
= md
->get_frame_rate();
213 _movieAdvancementDelay
= static_cast<int>(1000/fps
);
215 _lastMovieAdvancement
= _vm
.getTime();
217 _stageWidth
= static_cast<int>(md
->get_width_pixels());
218 _stageHeight
= static_cast<int>(md
->get_height_pixels());
220 // assert(movie->get_depth() == 0); ?
221 movie
->set_depth(DisplayObject::staticDepthOffset
);
226 // actions in first frame of _level0 must execute now,
227 // before next advance,
228 // or they'll be executed with _currentframe being set to 2
229 processActionQueue();
231 catch (const ActionLimitException
& al
) {
232 boost::format fmt
= boost::format(_("ActionLimits hit during "
233 "setRootMovie: %s. Disable scripts?")) % al
.what();
234 handleActionLimitHit(fmt
.str());
236 catch (const ActionParserException
& e
) {
237 log_error("ActionParserException thrown during setRootMovie: %s",
245 movie_root::handleActionLimitHit(const std::string
& msg
)
248 if (_interfaceHandler
) {
249 disable
= callInterface
<bool>(HostMessage(HostMessage::QUERY
, msg
));
252 log_error("No user interface registered, assuming 'Yes' answer to "
253 "question: %s", msg
);
262 movie_root::cleanupAndCollect()
264 // Cleanup the stack.
265 _vm
.getStack().clear();
267 cleanupDisplayList();
273 movie_root::setLevel(unsigned int num
, Movie
* movie
)
275 assert(movie
!= NULL
);
276 assert(static_cast<unsigned int>(movie
->get_depth()) ==
277 num
+ DisplayObject::staticDepthOffset
);
280 Levels::iterator it
= _movies
.find(movie
->get_depth());
281 if (it
== _movies
.end()) {
282 _movies
[movie
->get_depth()] = movie
;
285 // don't leak overloaded levels
287 MovieClip
* lm
= it
->second
;
288 if (lm
== _rootMovie
) {
289 // NOTE: this is not enough to trigger
290 // an application reset. Was tested
291 // but not automated. If curious
292 // use swapDepths against _level0
293 // and load into the new target while
294 // a timeout/interval is active.
295 log_debug("Replacing starting movie");
300 log_debug("Loading into _level0");
302 // NOTE: this was tested but not automated, the
303 // test sets an interval and then loads something
304 // in _level0. The result is the interval is disabled.
305 _intervalTimers
.clear();
307 // TODO: check what else we should do in these cases
308 // (like, unregistering all childs etc...)
309 // Tested, but not automated, is that other
310 // levels should be maintained alive.
311 // Sat Nov 14 10:31:19 CET 2009
312 // ^^^ not confirmed in this date, I think other levels
313 // are dropped too! (strk)
315 _stageWidth
= movie
->widthPixels();
316 _stageHeight
= movie
->heightPixels();
318 // notify stage replacement
319 if (_interfaceHandler
) {
320 const HostMessage
e(HostMessage::RESIZE_STAGE
,
321 std::make_pair(_stageWidth
, _stageHeight
));
322 _interfaceHandler
->call(e
);
326 it
->second
->destroy();
330 movie
->set_invalidated();
335 assert(testInvariant());
339 movie_root::swapLevels(MovieClip
* movie
, int depth
)
343 //#define GNASH_DEBUG_LEVELS_SWAPPING 1
345 const int oldDepth
= movie
->get_depth();
347 #ifdef GNASH_DEBUG_LEVELS_SWAPPING
348 log_debug("Before swapLevels (source depth %d, target depth %d) "
349 "levels are: ", oldDepth
, depth
);
350 for (Levels::const_iterator i
=_movies
.begin(), e
=_movies
.end(); i
!=e
; ++i
) {
351 log_debug(" %d: %p (%s @ depth %d)", i
->first
,
352 (void*)(i
->second
), i
->second
->getTarget(),
353 i
->second
->get_depth());
356 // should include _level0 !
357 if (oldDepth
< DisplayObject::staticDepthOffset
) {
358 IF_VERBOSE_ASCODING_ERRORS(
359 log_aserror(_("%s.swapDepth(%d): movie has a depth (%d) below "
360 "static depth zone (%d), won't swap its depth"),
361 movie
->getTarget(), depth
, oldDepth
,
362 DisplayObject::staticDepthOffset
);
368 IF_VERBOSE_ASCODING_ERRORS(
369 log_aserror(_("%s.swapDepth(%d): movie has a depth (%d) below "
370 "static depth zone (%d), won't swap its depth"),
371 movie
->getTarget(), depth
, oldDepth
,
372 DisplayObject::staticDepthOffset
);
377 const int oldNum
= oldDepth
;
378 Levels::iterator oldIt
= _movies
.find(oldNum
);
379 if (oldIt
== _movies
.end()) {
380 log_debug("%s.swapDepth(%d): target depth (%d) contains no movie",
381 movie
->getTarget(), depth
, oldNum
);
385 const int newNum
= depth
;
386 movie
->set_depth(depth
);
387 Levels::iterator targetIt
= _movies
.find(newNum
);
388 if (targetIt
== _movies
.end()) {
389 _movies
.erase(oldIt
);
390 _movies
[newNum
] = movie
;
393 MovieClip
* otherMovie
= targetIt
->second
;
394 otherMovie
->set_depth(oldDepth
);
395 oldIt
->second
= otherMovie
;
396 targetIt
->second
= movie
;
399 #ifdef GNASH_DEBUG_LEVELS_SWAPPING
400 log_debug("After swapLevels levels are: ");
401 for (Levels::const_iterator i
=_movies
.begin(), e
=_movies
.end(); i
!=e
; ++i
) {
402 log_debug(" %d: %p (%s @ depth %d)", i
->first
,
403 (void*)(i
->second
), i
->second
->getTarget(),
404 i
->second
->get_depth());
408 // TODO: invalidate self, not the movie
409 // movie_root::setInvalidated() seems
410 // to do just that, if anyone feels
411 // like more closely research on this
412 // (does level swapping require full redraw always?)
413 movie
->set_invalidated();
415 assert(testInvariant());
419 movie_root::dropLevel(int depth
)
421 // should be checked by caller
422 // TODO: don't use a magic number! See MovieClip::removeMovieClip().
423 assert(depth
>= 0 && depth
<= 1048575);
425 Levels::iterator it
= _movies
.find(depth
);
426 if (it
== _movies
.end()) {
427 log_error("movie_root::dropLevel called against a movie not "
428 "found in the levels container");
432 MovieClip
* mo
= it
->second
;
433 if (mo
== _rootMovie
) {
434 IF_VERBOSE_ASCODING_ERRORS(
435 log_aserror(_("Original root movie can't be removed"));
440 // TOCHECK: safe to erase here ?
445 assert(testInvariant());
449 movie_root::replaceLevel(unsigned int num
, Movie
* extern_movie
)
451 extern_movie
->set_depth(num
+ DisplayObject::staticDepthOffset
);
452 Levels::iterator it
= _movies
.find(extern_movie
->get_depth());
453 if (it
== _movies
.end()) {
454 log_error("TESTME: loadMovie called on level %d which is not "
455 "available at load time, skipped placement for now");
459 // TODO: rework this to avoid the double scan
460 setLevel(num
, extern_movie
);
464 movie_root::getLevel(unsigned int num
) const
466 Levels::const_iterator i
=
467 _movies
.find(num
+ DisplayObject::staticDepthOffset
);
469 if (i
== _movies
.end()) return 0;
477 sound::sound_handler
* sh
= _runResources
.soundHandler();
480 // reset background color, to allow
481 // next load to set it again.
482 m_background_color
.set(255, 255, 255, 255);
483 m_background_color_set
= false;
485 // wipe out live chars
488 // wipe out queued actions
491 // wipe out all levels
494 // remove all intervals
495 _intervalTimers
.clear();
497 // remove all loadMovie requests
498 _movieLoader
.clear();
500 // remove key listeners
501 _keyListeners
.clear();
503 // Cleanup the stack.
504 _vm
.getStack().clear();
506 // Run the garbage collector again
511 _disableScripts
= false;
515 movie_root::setDimensions(size_t w
, size_t h
)
517 assert(testInvariant());
522 if (_scaleMode
== SCALEMODE_NOSCALE
) {
523 as_object
* stage
= getBuiltinObject(*this,
524 getURI(_vm
, NSV::CLASS_STAGE
));
526 callMethod(stage
, getURI(_vm
, NSV::PROP_BROADCAST_MESSAGE
),
532 assert(testInvariant());
536 movie_root::mouseMoved(boost::int32_t x
, boost::int32_t y
)
538 assert(testInvariant());
542 return notify_mouse_listeners(event_id(event_id::MOUSE_MOVE
));
547 movie_root::keyEvent(key::code k
, bool down
)
550 const size_t keycode
= key::codeMap
[k
][key::KEY
];
551 if (keycode
< key::KEYCOUNT
) {
552 _unreleasedKeys
.set(keycode
, down
);
555 LiveChars copy
= _liveChars
;
556 for (LiveChars::iterator iter
= copy
.begin(), itEnd
=copy
.end();
557 iter
!= itEnd
; ++iter
) {
559 // sprite, button & input_edit_text DisplayObjects
560 InteractiveObject
* const ch
= *iter
;
561 if (ch
->unloaded()) continue;
564 ch
->notifyEvent(event_id(event_id::KEY_DOWN
, key::INVALID
));
565 ch
->notifyEvent(event_id(event_id::KEY_PRESS
, k
));
568 ch
->notifyEvent(event_id(event_id::KEY_UP
, key::INVALID
));
572 // Broadcast event to Key._listeners.
573 as_object
* key
= getBuiltinObject(*this, getURI(_vm
, NSV::CLASS_KEY
));
577 // Can throw an action limit exception if the stack limit is 0 or 1,
578 // i.e. if the stack is at the limit before it contains anything.
579 // A stack limit like that is hardly of any use, but could be used
580 // maliciously to crash Gnash.
582 callMethod(key
, getURI(_vm
, NSV::PROP_BROADCAST_MESSAGE
), "onKeyDown");
585 callMethod(key
, getURI(_vm
,NSV::PROP_BROADCAST_MESSAGE
), "onKeyUp");
588 catch (const ActionLimitException
&e
) {
589 log_error(_("ActionLimits hit notifying key listeners: %s."),
595 // Then any button keys are notified.
596 Listeners lcopy
= _keyListeners
;
597 for (Listeners::iterator iter
= lcopy
.begin(), itEnd
= lcopy
.end();
598 iter
!= itEnd
; ++iter
) {
600 // sprite, button & input_edit_text DisplayObjects
601 Button
* const ch
= *iter
;
602 if (ch
->unloaded()) continue;
605 ch
->notifyEvent(event_id(event_id::KEY_DOWN
, key::INVALID
));
606 ch
->notifyEvent(event_id(event_id::KEY_PRESS
, k
));
609 ch
->notifyEvent(event_id(event_id::KEY_UP
, key::INVALID
));
614 // If we're focused on an editable text field, finally the text is updated
616 TextField
* tf
= dynamic_cast<TextField
*>(_currentFocus
);
617 if (tf
) tf
->notifyEvent(event_id(event_id::KEY_PRESS
, k
));
620 processActionQueue();
626 movie_root::mouseWheel(int delta
)
628 as_object
* mouseObj
=
629 getBuiltinObject(*this, getURI(_vm
, NSV::CLASS_MOUSE
));
630 if (!mouseObj
) return false;
632 const boost::int32_t x
= pixelsToTwips(_mouseX
);
633 const boost::int32_t y
= pixelsToTwips(_mouseY
);
635 DisplayObject
* i
= getTopmostMouseEntity(x
, y
);
637 // Always called with two arguments.
638 callMethod(mouseObj
, getURI(_vm
,NSV::PROP_BROADCAST_MESSAGE
), "onMouseWheel",
639 delta
, i
? getObject(i
) : as_value());
645 movie_root::mouseClick(bool mouse_pressed
)
647 assert(testInvariant());
649 _mouseButtonState
.isDown
= mouse_pressed
;
652 return notify_mouse_listeners(event_id(event_id::MOUSE_DOWN
));
654 return notify_mouse_listeners(event_id(event_id::MOUSE_UP
));
659 movie_root::fire_mouse_event()
662 assert(testInvariant());
664 boost::int32_t x
= pixelsToTwips(_mouseX
);
665 boost::int32_t y
= pixelsToTwips(_mouseY
);
667 // Generate a mouse event
668 _mouseButtonState
.topmostEntity
= getTopmostMouseEntity(x
, y
);
670 // Set _droptarget if dragging a sprite
671 MovieClip
* dragging
= 0;
672 DisplayObject
* draggingChar
= getDraggingCharacter();
673 if (draggingChar
) dragging
= draggingChar
->to_movie();
675 // TODO: optimize making findDropTarget and getTopmostMouseEntity
676 // use a single scan.
677 const DisplayObject
* dropChar
= findDropTarget(x
, y
, dragging
);
679 // Use target of closest script DisplayObject containing this
680 dropChar
= getNearestObject(dropChar
);
681 dragging
->setDropTarget(dropChar
->getTargetPath());
683 else dragging
->setDropTarget("");
687 bool need_redraw
= false;
689 // FIXME: need_redraw might also depend on actual
690 // actions execution (consider updateAfterEvent).
693 need_redraw
= generate_mouse_button_events(*this, _mouseButtonState
);
694 processActionQueue();
696 catch (const ActionLimitException
& al
) {
697 boost::format fmt
= boost::format(_("ActionLimits hit during mouse "
698 "event processing: %s. Disable scripts ?")) % al
.what();
699 handleActionLimitHit(fmt
.str());
706 std::pair
<boost::int32_t, boost::int32_t>
707 movie_root::mousePosition() const
709 assert(testInvariant());
710 return std::make_pair(_mouseX
, _mouseY
);
714 movie_root::setDragState(const DragState
& st
)
717 DisplayObject
* ch
= st
.getCharacter();
718 if (ch
&& !st
.isLockCentered()) {
719 // Get coordinates of the DisplayObject's origin
721 SWFMatrix chmat
= getWorldMatrix(*ch
);
723 chmat
.transform(&world_origin
, origin
);
725 // Get current mouse coordinates
726 point
world_mouse(pixelsToTwips(_mouseX
), pixelsToTwips(_mouseY
));
728 boost::int32_t xoffset
= world_mouse
.x
- world_origin
.x
;
729 boost::int32_t yoffset
= world_mouse
.y
- world_origin
.y
;
731 _dragState
.setOffset(xoffset
, yoffset
);
733 assert(testInvariant());
737 movie_root::doMouseDrag()
739 DisplayObject
* dragChar
= getDraggingCharacter();
740 if (!dragChar
) return; // nothing to do
742 if (dragChar
->unloaded()) {
743 // Reset drag state if dragging char was unloaded
748 point
world_mouse(pixelsToTwips(_mouseX
), pixelsToTwips(_mouseY
));
750 SWFMatrix parent_world_mat
;
751 DisplayObject
* p
= dragChar
->parent();
753 parent_world_mat
= getWorldMatrix(*p
);
756 if (!_dragState
.isLockCentered()) {
757 world_mouse
.x
-= _dragState
.xOffset();
758 world_mouse
.y
-= _dragState
.yOffset();
761 if (_dragState
.hasBounds()) {
763 // bounds are in local coordinate space
764 bounds
.enclose_transformed_rect(parent_world_mat
,
765 _dragState
.getBounds());
766 // Clamp mouse coords within a defined SWFRect.
767 bounds
.clamp(world_mouse
);
770 parent_world_mat
.invert().transform(world_mouse
);
771 // Place our origin so that it coincides with the mouse coords
772 // in our parent frame.
773 // TODO: add a DisplayObject::set_translation ?
774 SWFMatrix local
= getMatrix(*dragChar
);
775 local
.set_translation(world_mouse
.x
, world_mouse
.y
);
777 // no need to update caches when only changing translation
778 dragChar
->setMatrix(local
);
782 movie_root::addIntervalTimer(std::auto_ptr
<Timer
> timer
)
785 assert(testInvariant());
787 const size_t id
= ++_lastTimerId
;
789 assert(_intervalTimers
.find(id
) == _intervalTimers
.end());
791 boost::shared_ptr
<Timer
> t(timer
);
792 _intervalTimers
.insert(std::make_pair(id
, t
));
798 movie_root::clearIntervalTimer(boost::uint32_t x
)
800 TimerMap::iterator it
= _intervalTimers
.find(x
);
801 if (it
== _intervalTimers
.end()) return false;
803 // We do not remove the element here because
804 // we might have been called during execution
805 // of another timer, thus during a scan of the _intervalTimers
806 // container. If we use erase() here, the iterators in executeTimers
807 // would be invalidated. Rather, executeTimers() would check container
808 // elements for being still active and remove the cleared one in a safe way
809 // at each iteration.
810 it
->second
->clearInterval();
817 movie_root::advance()
819 // We can't actually rely on now being later than _lastMovieAdvancement,
820 // so we will have to check. Otherwise we risk elapsed being
821 // contructed from a negative value.
822 const size_t now
= std::max
<size_t>(_vm
.getTime(), _lastMovieAdvancement
);
824 bool advanced
= false;
828 const size_t elapsed
= now
- _lastMovieAdvancement
;
829 if (elapsed
>= _movieAdvancementDelay
)
834 // To catch-up lateness we pretend we advanced when
837 // now - _lastMovieAdvancement
838 // gives you actual lateness in milliseconds
840 // TODO: make 'catchup' setting user-settable
841 // as it helps A/V sync but sacrifices
842 // smoothness of animation which is very
843 // important for games.
844 static const bool catchup
= true;
846 _lastMovieAdvancement
+= _movieAdvancementDelay
;
848 _lastMovieAdvancement
= now
;
853 //log_debug("Lateness: %d", now-_lastMovieAdvancement);
855 executeAdvanceCallbacks();
860 catch (const ActionLimitException
& al
) {
861 // The PP does not disable scripts when the stack limit is reached,
862 // but rather struggles on.
863 log_error(_("Action limit hit during advance: %s"), al
.what());
866 catch (const ActionParserException
& e
) {
867 log_error(_("Buffer overread during advance: %s"), e
.what());
875 movie_root::advanceMovie()
878 // Do mouse drag, if needed
881 // Advance all non-unloaded DisplayObjects in the LiveChars list
882 // in reverse order (last added, first advanced)
883 // NOTE: can throw ActionLimitException
886 // Process loadMovie requests
888 // NOTE: should be done before executing timers,
889 // see swfdec's test/trace/loadmovie-case-{5,6}.swf
890 // NOTE: processing loadMovie requests after advanceLiveChars
891 // is known to fix more tests in misc-mtasc.all/levels.swf
892 // to be checked if it keeps the swfdec testsuite safe
894 _movieLoader
.processCompletedRequests();
896 // Process queued actions
897 // NOTE: can throw ActionLimitException
898 processActionQueue();
902 assert(testInvariant());
906 movie_root::timeToNextFrame() const
908 unsigned int now
= _vm
.getTime();
909 const int elapsed
= now
- _lastMovieAdvancement
;
910 return _movieAdvancementDelay
- elapsed
;
914 movie_root::display()
916 // GNASH_REPORT_FUNCTION;
918 assert(testInvariant());
922 // TODO: should we consider the union of all levels bounds ?
923 const SWFRect
& frame_size
= _rootMovie
->get_frame_size();
924 if ( frame_size
.is_null() )
926 // TODO: check what we should do if other levels
928 log_debug("original root movie had null bounds, not displaying");
932 Renderer
* renderer
= _runResources
.renderer();
933 if (!renderer
) return;
935 Renderer::External
ex(*renderer
, m_background_color
,
936 _stageWidth
, _stageHeight
,
937 frame_size
.get_x_min(), frame_size
.get_x_max(),
938 frame_size
.get_y_min(), frame_size
.get_y_max());
940 for (Levels::iterator i
=_movies
.begin(), e
=_movies
.end(); i
!=e
; ++i
) {
941 MovieClip
* movie
= i
->second
;
943 movie
->clear_invalidated();
945 if (movie
->visible() == false) continue;
947 // null frame size ? don't display !
948 const SWFRect
& sub_frame_size
= movie
->get_frame_size();
950 if (sub_frame_size
.is_null()) {
951 log_debug("_level%u has null frame size, skipping", i
->first
);
955 movie
->display(*renderer
, Transform());
961 movie_root::notify_mouse_listeners(const event_id
& event
)
964 LiveChars copy
= _liveChars
;
965 for (LiveChars::iterator iter
= copy
.begin(), itEnd
=copy
.end();
966 iter
!= itEnd
; ++iter
)
968 MovieClip
* const ch
= *iter
;
969 if (!ch
->unloaded()) {
970 ch
->mouseEvent(event
);
974 const ObjectURI
& propMouse
= getURI(_vm
, NSV::CLASS_MOUSE
);
975 const ObjectURI
& propBroadcastMessage
=
976 getURI(_vm
, NSV::PROP_BROADCAST_MESSAGE
);
978 as_object
* mouseObj
= getBuiltinObject(*this, propMouse
);
981 // Can throw an action limit exception if the stack limit is 0 or 1.
982 // A stack limit like that is hardly of any use, but could be used
983 // maliciously to crash Gnash.
985 callMethod(mouseObj
, propBroadcastMessage
, event
.functionName());
987 catch (ActionLimitException
&e
) {
988 log_error(_("ActionLimits hit notifying mouse events: %s."),
995 assert(testInvariant());
998 // process actions queued in the above step
999 processActionQueue();
1001 return fire_mouse_event();
1005 movie_root::getFocus()
1007 assert(testInvariant());
1008 return _currentFocus
;
1012 movie_root::setFocus(DisplayObject
* to
)
1015 // Nothing to do if current focus is the same as the new focus.
1016 // _level0 also seems unable to receive focus under any circumstances
1017 // TODO: what about _level1 etc ?
1018 if (to
== _currentFocus
||
1019 to
== static_cast<DisplayObject
*>(_rootMovie
)) {
1023 if (to
&& !to
->handleFocus()) {
1024 // TODO: not clear whether to remove focus in this case.
1028 // Undefined or NULL DisplayObject removes current focus. Otherwise, try
1029 // setting focus to the new DisplayObject. If it fails, remove current
1032 // Store previous focus, as the focus needs to change before onSetFocus
1033 // is called and listeners are notified.
1034 DisplayObject
* from
= _currentFocus
;
1037 // Perform any actions required on killing focus (only TextField).
1040 /// A valid focus must have an associated object.
1041 assert(getObject(from
));
1042 callMethod(getObject(from
), NSV::PROP_ON_KILL_FOCUS
, getObject(to
));
1048 assert(getObject(to
));
1049 callMethod(getObject(to
), NSV::PROP_ON_SET_FOCUS
, getObject(from
));
1052 as_object
* sel
= getBuiltinObject(*this, NSV::CLASS_SELECTION
);
1054 // Notify Selection listeners with previous and new focus as arguments.
1055 // Either argument may be null.
1057 callMethod(sel
, NSV::PROP_BROADCAST_MESSAGE
, "onSetFocus",
1058 getObject(from
), getObject(to
));
1061 assert(testInvariant());
1067 movie_root::getActiveEntityUnderPointer() const
1069 return _mouseButtonState
.activeEntity
;
1073 movie_root::getDraggingCharacter() const
1075 return _dragState
.getCharacter();
1078 const DisplayObject
*
1079 movie_root::getEntityUnderPointer() const
1081 const boost::int32_t x
= pixelsToTwips(_mouseX
);
1082 const boost::int32_t y
= pixelsToTwips(_mouseY
);
1083 return findDropTarget(x
, y
, getDraggingCharacter());
1088 movie_root::setQuality(Quality q
)
1090 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
1092 /// Overridden quality if not negative.
1093 if (rcfile
.qualityLevel() >= 0) {
1094 int ql
= rcfile
.qualityLevel();
1095 ql
= std::min
<int>(ql
, QUALITY_BEST
);
1096 q
= static_cast<Quality
>(ql
);
1099 if ( _quality
!= q
)
1101 // Force a redraw if quality changes
1103 // redraw should only happen on next
1104 // frame advancement (tested)
1111 // We always tell the renderer, because it could
1112 // be the first time we do
1113 Renderer
* renderer
= _runResources
.renderer();
1114 if (renderer
) renderer
->setQuality(_quality
);
1118 /// Get actionscript width of stage, in pixels. The width
1119 /// returned depends on the scale mode.
1121 movie_root::getStageWidth() const
1123 if (_scaleMode
== SCALEMODE_NOSCALE
) {
1127 // If scaling is allowed, always return the original movie size.
1129 return static_cast<size_t>(_rootMovie
->widthPixels());
1135 /// Get actionscript height of stage, in pixels. The height
1136 /// returned depends on the scale mode.
1138 movie_root::getStageHeight() const
1140 if (_scaleMode
== SCALEMODE_NOSCALE
) {
1141 return _stageHeight
;
1144 // If scaling is allowed, always return the original movie size.
1146 return static_cast<size_t>(_rootMovie
->heightPixels());
1152 /// Takes a short int bitfield: the four bits correspond
1153 /// to the AlignMode enum
1155 movie_root::setStageAlignment(short s
)
1158 callInterface(HostMessage(HostMessage::UPDATE_STAGE
));
1161 /// The mode is one of never, always, with sameDomain the default
1163 movie_root::setAllowScriptAccess(AllowScriptAccessMode mode
)
1165 _allowScriptAccess
= mode
;
1168 movie_root::AllowScriptAccessMode
1169 movie_root::getAllowScriptAccess()
1171 return _allowScriptAccess
;
1174 /// Returns a pair of enum values giving the actual alignment
1175 /// of the stage after align mode flags are evaluated.
1176 movie_root::StageAlign
1177 movie_root::getStageAlignment() const
1179 /// L takes precedence over R. Default is centred.
1180 StageHorizontalAlign ha
= STAGE_H_ALIGN_C
;
1181 if (_alignMode
.test(STAGE_ALIGN_L
)) ha
= STAGE_H_ALIGN_L
;
1182 else if (_alignMode
.test(STAGE_ALIGN_R
)) ha
= STAGE_H_ALIGN_R
;
1184 /// T takes precedence over B. Default is centred.
1185 StageVerticalAlign va
= STAGE_V_ALIGN_C
;
1186 if (_alignMode
.test(STAGE_ALIGN_T
)) va
= STAGE_V_ALIGN_T
;
1187 else if (_alignMode
.test(STAGE_ALIGN_B
)) va
= STAGE_V_ALIGN_B
;
1189 return std::make_pair(ha
, va
);
1192 /// Returns a string that represents the boolean state of the _showMenu
1195 movie_root::getShowMenuState() const
1200 /// Sets the value of _showMenu and calls the gui handler to process the
1201 /// fscommand to change the display of the context menu
1203 movie_root::setShowMenuState(bool state
)
1206 //FIXME: The gui code for show menu is semantically different than what
1207 // ActionScript expects it to be. In gtk.cpp the showMenu function hides
1208 // or shows the menubar. Flash expects this option to disable some
1209 // context menu items.
1210 // callInterface is the proper handler for this
1211 callInterface(HostMessage(HostMessage::SHOW_MENU
, _showMenu
));
1214 /// Returns the string representation of the current align mode,
1215 /// which must always be in the order: LTRB
1217 movie_root::getStageAlignMode() const
1220 if (_alignMode
.test(STAGE_ALIGN_L
)) align
.push_back('L');
1221 if (_alignMode
.test(STAGE_ALIGN_T
)) align
.push_back('T');
1222 if (_alignMode
.test(STAGE_ALIGN_R
)) align
.push_back('R');
1223 if (_alignMode
.test(STAGE_ALIGN_B
)) align
.push_back('B');
1229 movie_root::setStageScaleMode(ScaleMode sm
)
1231 if (_scaleMode
== sm
) return; // nothing to do
1233 bool notifyResize
= false;
1235 // If we go from or to noScale, we notify a resize
1236 // if and only if display viewport is != then actual
1237 // movie size. If there is not yet a _rootMovie (when scaleMode
1238 // is passed as a parameter to the player), we also don't notify a
1241 (sm
== SCALEMODE_NOSCALE
|| _scaleMode
== SCALEMODE_NOSCALE
)) {
1243 const movie_definition
* md
= _rootMovie
->definition();
1244 log_debug("Going to or from scaleMode=noScale. Viewport:%dx%d "
1245 "Def:%dx%d", _stageWidth
, _stageHeight
,
1246 md
->get_width_pixels(), md
->get_height_pixels());
1248 if ( _stageWidth
!= md
->get_width_pixels()
1249 || _stageHeight
!= md
->get_height_pixels() )
1251 notifyResize
= true;
1256 callInterface(HostMessage(HostMessage::UPDATE_STAGE
));
1259 as_object
* stage
= getBuiltinObject(*this, NSV::CLASS_STAGE
);
1261 callMethod(stage
, NSV::PROP_BROADCAST_MESSAGE
, "onResize");
1267 movie_root::setStageDisplayState(const DisplayState ds
)
1271 as_object
* stage
= getBuiltinObject(*this, NSV::CLASS_STAGE
);
1273 const bool fs
= _displayState
== DISPLAYSTATE_FULLSCREEN
;
1274 callMethod(stage
, NSV::PROP_BROADCAST_MESSAGE
, "onFullScreen", fs
);
1277 if (!_interfaceHandler
) return; // No registered callback
1279 HostMessage
e(HostMessage::SET_DISPLAYSTATE
, _displayState
);
1284 movie_root::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
1286 if (isInvalidated()) {
1291 for (Levels::reverse_iterator i
=_movies
.rbegin(), e
=_movies
.rend(); i
!=e
;
1293 i
->second
->add_invalidated_bounds(ranges
, force
);
1299 movie_root::minPopulatedPriorityQueue() const
1301 for (size_t l
= 0; l
< PRIORITY_SIZE
; ++l
) {
1302 if (!_actionQueue
[l
].empty()) return l
;
1304 return PRIORITY_SIZE
;
1308 movie_root::processActionQueue(size_t lvl
)
1310 ActionQueue::value_type
& q
= _actionQueue
[lvl
];
1312 assert(minPopulatedPriorityQueue() == lvl
);
1315 static unsigned calls
=0;
1317 bool actionsToProcess
= !q
.empty();
1318 if (actionsToProcess
) {
1319 log_debug(" Processing %d actions in priority queue %d (call %u)",
1320 q
.size(), lvl
, calls
);
1324 // _actionQueue may be changed due to actions (appended-to)
1325 // this loop might be optimized by using an iterator
1326 // and a final call to .clear()
1327 while (!q
.empty()) {
1329 std::auto_ptr
<ExecutableCode
> code(q
.pop_front().release());
1332 size_t minLevel
= minPopulatedPriorityQueue();
1333 if (minLevel
< lvl
) {
1335 log_debug(" Actions pushed in priority %d (< "
1336 "%d), restarting the scan (call"
1337 " %u)", minLevel
, lvl
, calls
);
1346 if (actionsToProcess
) {
1347 log_debug(" Done processing actions in priority queue "
1348 "%d (call %u)", lvl
, calls
);
1352 return minPopulatedPriorityQueue();
1356 movie_root::flushHigherPriorityActionQueues()
1358 if (!processingActions()) {
1359 // only flush the actions queue when we are
1360 // processing the queue.
1361 // ie. we don't want to flush the queue
1362 // during executing user event handlers,
1363 // which are not pushed at the moment.
1367 if (_disableScripts
) {
1368 /// cleanup anything pushed later..
1369 clear(_actionQueue
);
1373 int lvl
=minPopulatedPriorityQueue();
1374 while (lvl
< _processingActionLevel
) {
1375 lvl
= processActionQueue(lvl
);
1381 movie_root::addLoadableObject(as_object
* obj
, std::auto_ptr
<IOChannel
> str
)
1383 boost::shared_ptr
<IOChannel
> io(str
.release());
1384 _loadCallbacks
.push_back(LoadCallback(io
, obj
));
1388 movie_root::addAdvanceCallback(ActiveRelay
* obj
)
1390 _objectCallbacks
.insert(obj
);
1394 movie_root::removeAdvanceCallback(ActiveRelay
* obj
)
1396 _objectCallbacks
.erase(obj
);
1400 movie_root::processActionQueue()
1402 if (_disableScripts
) {
1403 /// cleanup anything pushed later..
1404 clear(_actionQueue
);
1408 _processingActionLevel
= minPopulatedPriorityQueue();
1410 while (_processingActionLevel
< PRIORITY_SIZE
) {
1411 _processingActionLevel
= processActionQueue(_processingActionLevel
);
1414 // Cleanup the stack.
1415 _vm
.getStack().clear();
1420 movie_root::removeQueuedConstructor(DisplayObject
* target
)
1422 ActionQueue::value_type
& pr
= _actionQueue
[PRIORITY_CONSTRUCT
];
1423 pr
.erase_if(RemoveTargetCode(target
));
1427 movie_root::pushAction(std::auto_ptr
<ExecutableCode
> code
, size_t lvl
)
1429 assert(lvl
< PRIORITY_SIZE
);
1430 _actionQueue
[lvl
].push_back(code
);
1434 movie_root::pushAction(const action_buffer
& buf
, DisplayObject
* target
)
1437 log_debug("Pushed action buffer for target %s",
1438 target
->getTargetPath());
1441 std::auto_ptr
<ExecutableCode
> code(new GlobalCode(buf
, target
));
1443 _actionQueue
[PRIORITY_DOACTION
].push_back(code
);
1447 movie_root::executeAdvanceCallbacks()
1450 if (!_objectCallbacks
.empty()) {
1452 // We have two considerations:
1453 // 1. any update can change the active callbacks by removing or
1454 // adding to the original callbacks list.
1455 // 2. Additionally, an as_object may destroy its own Relay. This can
1456 // happen if the callback itself calls a native constructor on
1457 // an object that already has a Relay. If this is an ActiveRelay
1458 // registered with movie_root, a pointer to the destroyed object
1459 // will still be held, resulting in memory corruption. This is an
1460 // *extremely* unlikely case, but we are very careful!
1462 // By copying to a new container we avoid errors caused by changes to
1463 // the original set (such as infinite recursions or invalidated
1464 // iterators). We also know that no as_object will be destroyed
1465 // during processing, even though its Relay may be.
1466 std::vector
<as_object
*> currentCallbacks
;
1468 std::transform(_objectCallbacks
.begin(), _objectCallbacks
.end(),
1469 std::back_inserter(currentCallbacks
),
1470 boost::bind(CreatePointer
<as_object
>(),
1471 boost::bind(std::mem_fun(&ActiveRelay::owner
), _1
)));
1473 std::for_each(currentCallbacks
.begin(), currentCallbacks
.end(),
1477 if (!_loadCallbacks
.empty()) {
1478 _loadCallbacks
.remove_if(
1479 std::mem_fun_ref(&movie_root::LoadCallback::processLoad
));
1482 // _controlfd is set when running as a child process of a hosting
1483 // application. If it is set, we have to check the socket connection
1484 // for XML messages.
1486 boost::shared_ptr
<ExternalInterface::invoke_t
> invoke
=
1487 ExternalInterface::ExternalEventCheck(_controlfd
);
1489 if (processInvoke(invoke
.get()) == false) {
1490 if (!invoke
->name
.empty()) {
1491 log_error("Couldn't process ExternalInterface Call %s",
1498 processActionQueue();
1502 movie_root::processInvoke(ExternalInterface::invoke_t
*invoke
)
1504 GNASH_REPORT_FUNCTION
;
1506 if (!invoke
|| invoke
->name
.empty()) return false;
1508 log_debug("Processing %s call from the Browser.", invoke
->name
);
1510 std::stringstream ss
; // ss is the response string
1512 // These are the default methods used by ExternalInterface
1513 if (invoke
->name
== "Quit") {
1514 // Leave to the hosting application. If there isn't one or it
1515 // chooses not to exit, that's fine.
1516 if (_interfaceHandler
) _interfaceHandler
->exit();
1518 } else if (invoke
->name
== "SetVariable") {
1519 MovieClip
*mc
= getLevel(0);
1520 as_object
*obj
= getObject(mc
);
1522 std::string var
= invoke
->args
[0].to_string();
1523 as_value
&val
= invoke
->args
[1] ;
1524 obj
->set_member(getURI(vm
, var
), val
);
1525 // SetVariable doesn't send a response
1526 } else if (invoke
->name
== "GetVariable") {
1527 MovieClip
*mc
= getLevel(0);
1528 as_object
*obj
= getObject(mc
);
1530 std::string var
= invoke
->args
[0].to_string();
1532 obj
->get_member(getURI(vm
, var
), &val
);
1533 // GetVariable sends the value of the variable
1534 ss
<< ExternalInterface::toXML(val
);
1535 } else if (invoke
->name
== "GotoFrame") {
1536 log_unimpl("ExternalInterface::GotoFrame()");
1537 // GotoFrame doesn't send a response
1538 } else if (invoke
->name
== "IsPlaying") {
1540 callInterface
<bool>(HostMessage(HostMessage::EXTERNALINTERFACE_ISPLAYING
));
1541 as_value
val(result
);
1542 ss
<< ExternalInterface::toXML(val
);
1543 } else if (invoke
->name
== "LoadMovie") {
1544 log_unimpl("ExternalInterface::LoadMovie()");
1545 // LoadMovie doesn't send a response
1546 } else if (invoke
->name
== "Pan") {
1547 std::string arg
= invoke
->args
[0].to_string();
1549 arg
+= invoke
->args
[0].to_string();
1551 arg
+= invoke
->args
[1].to_string();
1553 arg
+= invoke
->args
[2].to_string();
1554 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_PAN
, arg
));
1555 // Pan doesn't send a response
1556 } else if (invoke
->name
== "PercentLoaded") {
1557 MovieClip
*mc
= getLevel(0);
1558 int loaded
= mc
->get_bytes_loaded();
1559 int total
= mc
->get_bytes_total();
1560 as_value
val((loaded
/total
) * 100);
1561 // PercentLoaded sends the percentage
1562 ss
<< ExternalInterface::toXML(val
);
1563 } else if (invoke
->name
== "Play") {
1564 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_PLAY
));
1565 // Play doesn't send a response
1566 } else if (invoke
->name
== "Rewind") {
1567 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_REWIND
));
1568 // Rewind doesn't send a response
1569 } else if (invoke
->name
== "SetZoomRect") {
1570 std::string arg
= invoke
->args
[0].to_string();
1572 arg
+= invoke
->args
[0].to_string();
1574 arg
+= invoke
->args
[1].to_string();
1576 arg
+= invoke
->args
[2].to_string();
1578 arg
+= invoke
->args
[3].to_string();
1579 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_SETZOOMRECT
, arg
));
1580 // SetZoomRect doesn't send a response
1581 } else if (invoke
->name
== "StopPlay") {
1582 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_STOPPLAY
));
1583 // StopPlay doesn't send a response
1584 } else if (invoke
->name
== "Zoom") {
1585 std::string var
= invoke
->args
[0].to_string();
1586 callInterface(HostMessage(HostMessage::EXTERNALINTERFACE_ZOOM
, var
));
1587 // Zoom doesn't send a response
1588 } else if (invoke
->name
== "TotalFrames") {
1589 MovieClip
*mc
= getLevel(0);
1590 as_value
val(mc
->get_loaded_frames());
1591 // TotalFrames sends the number of frames in the movie
1592 ss
<< ExternalInterface::toXML(val
);
1594 std::string result
= callExternalCallback(invoke
->name
, invoke
->args
);
1595 if (result
== ExternalInterface::makeString("Error")) {
1597 } else if (result
== ExternalInterface::makeString("SecurityError")) {
1603 if (!ss
.str().empty()) {
1605 log_debug(_("Attempt to write response to ExternalInterface "
1606 "requests fd %d"), _hostfd
);
1607 int ret
= write(_hostfd
, ss
.str().c_str(), ss
.str().size());
1609 log_error(_("Could not write to user-provided host requests "
1610 "fd %d: %s"), _hostfd
, std::strerror(errno
));
1614 log_debug("No response needed for %s request", invoke
->name
);
1621 movie_root::executeTimers()
1623 #ifdef GNASH_DEBUG_TIMERS_EXPIRATION
1624 log_debug("Checking %d timers for expiry", _intervalTimers
.size());
1627 unsigned long now
= _vm
.getTime();
1629 typedef std::multimap
<unsigned int, boost::shared_ptr
<Timer
> >
1632 ExpiredTimers expiredTimers
;
1634 for (TimerMap::iterator it
= _intervalTimers
.begin(),
1635 itEnd
= _intervalTimers
.end(); it
!= itEnd
; ) {
1637 TimerMap::iterator nextIterator
= it
;
1640 boost::shared_ptr
<Timer
> timer(it
->second
);
1642 if (timer
->cleared()) {
1643 // this timer was cleared, erase it
1644 _intervalTimers
.erase(it
);
1647 unsigned long elapsed
;
1648 if (timer
->expired(now
, elapsed
)) {
1649 expiredTimers
.insert(std::make_pair(elapsed
, timer
));
1656 foreachSecond(expiredTimers
.begin(), expiredTimers
.end(),
1657 &Timer::executeAndReset
);
1659 if (!expiredTimers
.empty()) processActionQueue();
1664 movie_root::markReachableResources() const
1666 _vm
.markReachableResources();
1668 foreachSecond(_movies
.rbegin(), _movies
.rend(), &MovieClip::setReachable
);
1670 // Mark original top-level movie
1671 // This should always be in _movies, but better make sure
1672 if ( _rootMovie
) _rootMovie
->setReachable();
1674 // Mark mouse entities
1675 _mouseButtonState
.markReachableResources();
1677 // Mark timer targets
1678 foreachSecond(_intervalTimers
.begin(), _intervalTimers
.end(),
1679 &Timer::markReachableResources
);
1681 std::for_each(_objectCallbacks
.begin(), _objectCallbacks
.end(),
1682 std::mem_fun(&ActiveRelay::setReachable
));
1684 std::for_each(_loadCallbacks
.begin(), _loadCallbacks
.end(),
1685 std::mem_fun_ref(&movie_root::LoadCallback::setReachable
));
1687 // Mark LoadMovieRequest handlers as reachable
1688 _movieLoader
.setReachable();
1690 // Mark resources reachable by queued action code
1691 for (size_t lvl
= 0; lvl
< PRIORITY_SIZE
; ++lvl
)
1693 const ActionQueue::value_type
& q
= _actionQueue
[lvl
];
1694 std::for_each(q
.begin(), q
.end(),
1695 std::mem_fun_ref(&ExecutableCode::markReachableResources
));
1698 if (_currentFocus
) _currentFocus
->setReachable();
1700 // Mark DisplayObject being dragged, if any
1701 _dragState
.markReachableResources();
1703 // NOTE: cleanupDisplayList() should have cleaned up all
1704 // unloaded live characters. The remaining ones should be marked
1705 // by their parents.
1706 #if ( GNASH_PARANOIA_LEVEL > 1 ) || defined(ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION)
1707 for (LiveChars::const_iterator i
=_liveChars
.begin(), e
=_liveChars
.end();
1709 #ifdef ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION
1710 (*i
)->setReachable();
1712 assert((*i
)->isReachable());
1717 #if ( GNASH_PARANOIA_LEVEL > 1 ) || defined(ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION)
1718 for (Listeners::const_iterator i
=_keyListeners
.begin(),
1719 e
=_keyListeners
.end(); i
!=e
; ++i
) {
1720 #ifdef ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION
1721 (*i
)->setReachable();
1723 assert((*i
)->isReachable());
1731 movie_root::getTopmostMouseEntity(boost::int32_t x
, boost::int32_t y
) const
1734 for (Levels::const_reverse_iterator i
=_movies
.rbegin(), e
=_movies
.rend();
1737 InteractiveObject
* ret
= i
->second
->topmostMouseEntity(x
, y
);
1738 if (ret
) return ret
;
1744 const DisplayObject
*
1745 movie_root::findDropTarget(boost::int32_t x
, boost::int32_t y
,
1746 DisplayObject
* dragging
) const
1749 for (Levels::const_reverse_iterator i
=_movies
.rbegin(), e
=_movies
.rend();
1752 const DisplayObject
* ret
= i
->second
->findDropTarget(x
, y
, dragging
);
1753 if (ret
) return ret
;
1758 /// This should store a callback object in movie_root.
1760 /// TODO: currently it doesn't.
1762 movie_root::addExternalCallback(const std::string
& name
, as_object
* callback
)
1766 // When an external callback is added, we have to notify the plugin
1767 // that this method is available.
1769 std::vector
<as_value
> fnargs
;
1770 fnargs
.push_back(name
);
1771 std::string msg
= ExternalInterface::makeInvoke("addMethod", fnargs
);
1773 const size_t ret
= ExternalInterface::writeBrowser(_hostfd
, msg
);
1774 if (ret
!= msg
.size()) {
1775 log_error(_("Could not write to browser fd #%d: %s"),
1776 _hostfd
, std::strerror(errno
));
1781 /// This calls a JavaScript method in the web page
1783 /// @example "ExternalInterace::call message"
1786 /// <invoke name="methodname" returntype="xml">
1787 /// <arguments></arguments>
1789 /// <arguments></arguments>
1792 /// May return any supported type like Number or String in XML format.
1796 movie_root::callExternalJavascript(const std::string
&name
,
1797 const std::vector
<as_value
> &fnargs
)
1799 // GNASH_REPORT_FUNCTION;
1801 // If the browser is connected, we send an Invoke message to the
1803 if (_controlfd
>= 0 && _hostfd
>= 0) {
1804 std::string msg
= ExternalInterface::makeInvoke(name
, fnargs
);
1806 const size_t ret
= ExternalInterface::writeBrowser(_hostfd
, msg
);
1807 if (ret
!= msg
.size()) {
1808 log_error(_("Could not write to browser fd #%d: %s"),
1809 _hostfd
, std::strerror(errno
));
1811 // Now read the response from the browser after it's exectuted
1812 // the JavaScript function.
1813 result
= ExternalInterface::readBrowser(_controlfd
);
1820 // Call one of the registered callbacks, and return the result to
1821 // Javascript in the browser.
1823 movie_root::callExternalCallback(const std::string
&name
,
1824 const std::vector
<as_value
> &fnargs
)
1826 // GNASH_REPORT_FUNCTION;
1828 MovieClip
*mc
= getLevel(0);
1829 as_object
*obj
= getObject(mc
);
1831 const ObjectURI
& key
= getURI(getVM(), name
);
1832 // FIXME: there has got to be a better way of handling the variable
1835 switch (fnargs
.size()) {
1837 val
= callMethod(obj
, key
);
1840 val
= callMethod(obj
, key
, fnargs
[0]);
1843 val
= callMethod(obj
, key
, fnargs
[0], fnargs
[1]);
1846 val
= callMethod(obj
, key
, fnargs
[0], fnargs
[1], fnargs
[2]);
1849 val
= callMethod(obj
, key
);
1854 if (val
.is_null()) {
1856 result
= ExternalInterface::makeString("Error");
1858 result
= ExternalInterface::toXML(val
);
1861 // If the browser is connected, we send an Invoke message to the
1864 const size_t ret
= ExternalInterface::writeBrowser(_hostfd
, result
);
1865 if (ret
!= result
.size()) {
1866 log_error(_("Could not write to browser fd #%d: %s"),
1867 _hostfd
, std::strerror(errno
));
1875 movie_root::remove_key_listener(Button
* listener
)
1877 _keyListeners
.remove_if(std::bind2nd(std::equal_to
<Button
*>(), listener
));
1881 movie_root::add_key_listener(Button
* listener
)
1885 if (std::find(_keyListeners
.begin(), _keyListeners
.end(), listener
)
1886 != _keyListeners
.end()) return;
1888 _keyListeners
.push_front(listener
);
1892 movie_root::cleanupDisplayList()
1895 #define GNASH_DEBUG_INSTANCE_LIST 1
1897 #ifdef GNASH_DEBUG_INSTANCE_LIST
1898 static size_t maxLiveChars
= 0;
1901 // Let every sprite cleanup the local DisplayList
1903 // TODO: we might skip this additional scan by delegating
1904 // cleanup of the local DisplayLists in the ::display
1905 // method of each sprite, but that will introduce
1906 // problems when we implement skipping ::display()
1907 // when late on FPS. Alternatively we may have the
1908 // MovieClip::markReachableResources take care
1909 // of cleaning up unloaded... but that will likely
1910 // introduce problems when allowing the GC to run
1911 // at arbitrary times.
1912 // The invariant to keep is that cleanup of unloaded DisplayObjects
1913 // in local display lists must happen at the *end* of global action
1914 // queue processing.
1915 foreachSecond(_movies
.rbegin(), _movies
.rend(),
1916 &MovieClip::cleanupDisplayList
);
1918 // Now remove from the instance list any unloaded DisplayObject
1919 // Note that some DisplayObjects may be unloaded but not yet destroyed,
1920 // in this case we'll also destroy them, which in turn might unload
1921 // further DisplayObjects, maybe already scanned, so we keep scanning
1922 // the list until no more unloaded-but-non-destroyed DisplayObjects
1924 // Keeping unloaded-but-non-destroyed DisplayObjects wouldn't really hurt
1925 // in that ::advanceLiveChars would skip any unloaded DisplayObjects.
1926 // Still, the more we remove the less work GC has to do...
1930 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1934 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1940 // Remove unloaded MovieClips from the _liveChars list
1941 for (LiveChars::iterator i
= _liveChars
.begin(), e
= _liveChars
.end();
1944 if (ch
->unloaded()) {
1945 // the sprite might have been destroyed already
1946 // by effect of an unload() call with no onUnload
1947 // handlers available either in self or child
1949 if (!ch
->isDestroyed()) {
1951 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1952 cout
<< ch
->getTarget() << "(" << typeName(*ch
) <<
1953 ") was unloaded but not destroyed, destroying now" <<
1957 // destroy() might mark already-scanned chars as unloaded
1960 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1962 cout
<< ch
->getTarget() << "(" << typeName(*ch
) <<
1963 ") was unloaded and destroyed" << endl
;
1967 i
= _liveChars
.erase(i
);
1969 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1976 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1977 cout
<< " Scan " << scansCount
<< " cleaned " << cleaned
<<
1978 " instances" << endl
;
1982 #ifdef GNASH_DEBUG_INSTANCE_LIST
1983 if (_liveChars
.size() > maxLiveChars
) {
1984 maxLiveChars
= _liveChars
.size();
1985 log_debug("Global instance list grew to %d entries", maxLiveChars
);
1992 movie_root::advanceLiveChars()
1996 log_debug("---- movie_root::advance: %d live DisplayObjects in "
1997 "the global list", _liveChars
.size());
2000 std::for_each(_liveChars
.begin(), _liveChars
.end(),
2001 boost::bind(advanceLiveChar
, _1
));
2005 movie_root::set_background_color(const rgba
& color
)
2007 if (m_background_color_set
) return;
2008 m_background_color_set
= true;
2010 rgba newcolor
= color
;
2011 newcolor
.m_a
= m_background_color
.m_a
;
2013 if (m_background_color
!= color
) {
2015 m_background_color
= color
;
2020 movie_root::set_background_alpha(float alpha
)
2023 boost::uint8_t newAlpha
= clamp
<int>(frnd(alpha
* 255.0f
), 0, 255);
2025 if (m_background_color
.m_a
!= newAlpha
) {
2027 m_background_color
.m_a
= newAlpha
;
2032 movie_root::findCharacterByTarget(const std::string
& tgtstr
) const
2034 if (tgtstr
.empty()) return 0;
2036 // NOTE: getRootMovie() would be problematic in case the original
2037 // root movie is replaced by a load to _level0...
2038 // (but I guess we'd also drop loadMovie requests in that
2039 // case... just not tested)
2040 as_object
* o
= getObject(_movies
.begin()->second
);
2043 std::string::size_type from
= 0;
2044 while (std::string::size_type to
= tgtstr
.find('.', from
)) {
2045 std::string
part(tgtstr
, from
, to
- from
);
2047 // TODO: there is surely a cleaner way to implement path finding.
2048 const ObjectURI
& uri
= getURI(_vm
, part
);
2049 o
= o
->displayObject() ?
2050 o
->displayObject()->pathElement(uri
) :
2051 getPathElement(*o
, uri
);
2054 #ifdef GNASH_DEBUG_TARGET_RESOLUTION
2055 log_debug("Evaluating DisplayObject target path: element "
2056 "'%s' of path '%s' not found", part
, tgtstr
);
2060 if (to
== std::string::npos
) break;
2063 return get
<DisplayObject
>(o
);
2067 movie_root::getURL(const std::string
& urlstr
, const std::string
& target
,
2068 const std::string
& data
, MovieClip::VariablesMethod method
)
2071 log_network("%s: HOSTFD is %d", __FUNCTION__
, _hostfd
);
2074 /// If there is no hosting application, call the URL launcher. For
2075 /// safety, we resolve the URL against the base URL for this run.
2076 /// The data is not sent at all.
2077 URL
url(urlstr
, _runResources
.streamProvider().baseURL());
2079 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
2080 std::string command
= rcfile
.getURLOpenerFormat();
2082 /// Try to avoid letting flash movies execute
2083 /// arbitrary commands (sic)
2085 /// Maybe we should exec here, but if we do we might have problems
2086 /// with complex urlOpenerFormats like:
2087 /// firefox -remote 'openurl(%u)'
2089 std::string safeurl
= url
.encode(urlstr
);
2090 boost::replace_all(command
, "%u", safeurl
);
2092 log_debug (_("Launching URL: %s"), command
);
2093 const int ret
= std::system(command
.c_str());
2095 log_error(_("Fork failed launching url opener '%s'"), command
);
2100 /// This is when there is a hosting application.
2101 std::vector
<as_value
> fnargs
;
2102 // The first argument we push on the stack is the URL
2103 fnargs
.push_back(as_value(urlstr
));
2105 // The second argument we push is the method
2107 case MovieClip::METHOD_POST
:
2108 fnargs
.push_back(as_value("POST"));
2110 case MovieClip::METHOD_GET
:
2111 fnargs
.push_back(as_value("GET"));
2113 case MovieClip::METHOD_NONE
:
2115 fnargs
.push_back(as_value("GET"));
2119 // The third argument is the target, which is something like _blank
2121 if (!target
.empty()) {
2122 fnargs
.push_back(as_value(target
));
2124 // Add any data as the optional 4th argument
2125 if (!data
.empty()) {
2126 // We have to write a value here so the data field is the fourth
2127 if (target
.empty()) {
2128 fnargs
.push_back(as_value("none"));
2130 fnargs
.push_back(as_value(data
));
2133 // TODO: should mutex-protect this ?
2134 // NOTE: we are assuming the hostfd is set in blocking mode here..
2136 log_debug(_("Attempt to write geturl requests fd #%d"), _hostfd
);
2138 std::string msg
= ExternalInterface::makeInvoke("getURL", fnargs
);
2140 const size_t ret
= ExternalInterface::writeBrowser(_hostfd
, msg
);
2141 if (ret
< msg
.size()) {
2142 log_error(_("Could only write %d bytes to fd #%d"),
2148 movie_root::setScriptLimits(boost::uint16_t recursion
, boost::uint16_t timeout
)
2151 if ( recursion
== _recursionLimit
&& _timeoutLimit
== timeout
) {
2152 // avoid the debug log...
2156 // This tag reported in some sources to be ignored for movies
2157 // below SWF7. However, on Linux with PP version 9, the tag
2158 // takes effect on SWFs of any version.
2159 log_debug(_("Setting script limits: max recursion %d, "
2160 "timeout %d seconds"), recursion
, timeout
);
2162 _recursionLimit
= recursion
;
2163 _timeoutLimit
= timeout
;
2170 movie_root::getMovieInfo(InfoTree
& tr
, InfoTree::iterator it
)
2174 const movie_definition
* def
= _rootMovie
->definition();
2177 it
= tr
.insert(it
, std::make_pair("Stage Properties", ""));
2179 InfoTree::iterator localIter
= tr
.append_child(it
,
2180 std::make_pair("Root VM version",
2181 def
->isAS3() ? "AVM2 (unsupported)" : "AVM1"));
2183 std::ostringstream os
;
2184 os
<< "SWF " << def
->get_version();
2185 localIter
= tr
.append_child(it
, std::make_pair("Root SWF version",
2187 localIter
= tr
.append_child(it
, std::make_pair("URL", def
->get_url()));
2189 // TODO: format this better?
2190 localIter
= tr
.append_child(it
, std::make_pair("Descriptive metadata",
2191 def
->getDescriptiveMetadata()));
2193 /// Stage: real dimensions.
2195 os
<< def
->get_width_pixels() <<
2196 "x" << def
->get_height_pixels();
2197 localIter
= tr
.append_child(it
, std::make_pair("Real dimensions",
2200 /// Stage: rendered dimensions.
2202 os
<< _stageWidth
<< "x" << _stageHeight
;
2203 localIter
= tr
.append_child(it
, std::make_pair("Rendered dimensions",
2206 // Stage: scripts state (enabled/disabled)
2207 localIter
= tr
.append_child(it
, std::make_pair("Scripts",
2208 _disableScripts
? " disabled" : "enabled"));
2210 getCharacterTree(tr
, it
);
2214 movie_root::getCharacterTree(InfoTree
& tr
, InfoTree::iterator it
)
2217 InfoTree::iterator localIter
;
2219 /// Stage: number of live MovieClips.
2220 std::ostringstream os
;
2221 os
<< _liveChars
.size();
2222 localIter
= tr
.append_child(it
, std::make_pair(_("Live MovieClips"),
2225 /// DisplayObject tree
2226 for (Levels::const_iterator i
= _movies
.begin(), e
= _movies
.end();
2228 i
->second
->getMovieInfo(tr
, localIter
);
2236 movie_root::handleFsCommand(const std::string
& cmd
, const std::string
& arg
)
2239 if (_fsCommandHandler
) _fsCommandHandler
->notify(cmd
, arg
);
2243 isLevelTarget(int version
, const std::string
& name
, unsigned int& levelno
)
2246 if (name
.compare(0, 6, "_level")) return false;
2249 StringNoCaseEqual noCaseCmp
;
2250 if (!noCaseCmp(name
.substr(0, 6), "_level")) return false;
2253 if (name
.find_first_not_of("0123456789", 7) != std::string::npos
) {
2256 // getting 0 here for "_level" is intentional
2257 levelno
= std::strtoul(name
.c_str() + 6, NULL
, 0);
2263 stringToStageAlign(const std::string
& str
)
2267 // Easy enough to do bitwise - std::bitset is not
2268 // really necessary!
2269 if (str
.find_first_of("lL") != std::string::npos
) {
2270 am
|= 1 << movie_root::STAGE_ALIGN_L
;
2273 if (str
.find_first_of("tT") != std::string::npos
) {
2274 am
|= 1 << movie_root::STAGE_ALIGN_T
;
2277 if (str
.find_first_of("rR") != std::string::npos
) {
2278 am
|= 1 << movie_root::STAGE_ALIGN_R
;
2281 if (str
.find_first_of("bB") != std::string::npos
) {
2282 am
|= 1 << movie_root::STAGE_ALIGN_B
;
2290 movie_root::LoadCallback::setReachable() const
2292 _obj
->setReachable();
2296 movie_root::LoadCallback::processLoad()
2300 callMethod(_obj
, NSV::PROP_ON_DATA
, as_value());
2304 const size_t chunksize
= 65535;
2305 boost::uint8_t chunk
[chunksize
];
2307 size_t actuallyRead
= _stream
->readNonBlocking(chunk
, chunksize
);
2309 // We must still call onData if the stream is in error condition, e.g.
2310 // when an HTTP 404 error is returned.
2311 if (_stream
->bad()) {
2312 callMethod(_obj
, NSV::PROP_ON_DATA
, as_value());
2318 // set total size only on first read
2320 _obj
->set_member(NSV::PROP_uBYTES_TOTAL
, _stream
->size());
2323 _buf
.append(chunk
, actuallyRead
);
2325 _obj
->set_member(NSV::PROP_uBYTES_LOADED
, _buf
.size());
2327 log_debug("LoadableObject Loaded %d bytes, reaching %d/%d",
2328 actuallyRead
, _buf
.size(), _stream
->size());
2331 // We haven't finished till EOF
2332 if (!_stream
->eof()) return false;
2334 log_debug("LoadableObject reached EOF (%d/%d loaded)",
2335 _buf
.size(), _stream
->size());
2337 // got nothing, won't bother BOFs of nulls
2339 callMethod(_obj
, NSV::PROP_ON_DATA
, as_value());
2343 // Terminate the string
2344 _buf
.appendByte('\0');
2346 // Strip BOM, if any.
2347 // See http://savannah.gnu.org/bugs/?19915
2348 utf8::TextEncoding encoding
;
2349 size_t size
= _buf
.size();
2351 // NOTE: the call below will possibly change 'size' parameter
2352 char* bufptr
= utf8::stripBOM((char*)_buf
.data(), size
, encoding
);
2353 if (encoding
!= utf8::encUTF8
&& encoding
!= utf8::encUNSPECIFIED
) {
2354 log_unimpl("%s to utf8 conversion in LoadableObject input parsing",
2355 utf8::textEncodingName(encoding
));
2358 // NOTE: Data copy here !!
2359 as_value
dataVal(bufptr
);
2361 // NOTE: we could release memory associated
2362 // with the buffer here, before invoking a new method,
2363 // but at the time of writing there's no method of SimpleBuffer
2364 // providing memory release except destruction. Will be
2365 // destroyed as soon as we return though...
2367 // NOTE: Another data copy here !
2368 callMethod(_obj
, NSV::PROP_ON_DATA
, dataVal
);
2374 movie_root::callInterface(const HostInterface::Message
& e
) const
2376 if (!_interfaceHandler
) {
2377 log_error("Hosting application registered no callback for events/queries"
2378 ", can't call %s(%s)");
2381 _interfaceHandler
->call(e
);
2386 movie_root::testInvariant() const
2388 // TODO: fill this function !
2389 // The _movies map can not invariantably
2390 // be non-empty as the stage is autonomous
2392 //assert( ! _movies.empty() );
2399 // Return whether any action triggered by this event requires display redraw.
2401 /// TODO: make this code more readable !
2403 generate_mouse_button_events(movie_root
& mr
, MouseButtonState
& ms
)
2405 // Did this event trigger any action that needs redisplay ?
2406 bool need_redisplay
= false;
2408 // TODO: have mouseEvent return
2409 // whether the action must trigger
2413 // TODO: Handle trackAsMenu dragOver
2414 // Handle onDragOut, onDragOver
2415 if (!ms
.wasInsideActiveEntity
) {
2417 if (ms
.topmostEntity
== ms
.activeEntity
) {
2420 if (ms
.activeEntity
) {
2421 ms
.activeEntity
->mouseEvent(event_id(event_id::DRAG_OVER
));
2422 need_redisplay
=true;
2424 ms
.wasInsideActiveEntity
= true;
2427 else if (ms
.topmostEntity
!= ms
.activeEntity
) {
2429 if (ms
.activeEntity
) {
2430 ms
.activeEntity
->mouseEvent(event_id(event_id::DRAG_OUT
));
2431 need_redisplay
=true;
2433 ms
.wasInsideActiveEntity
= false;
2436 // Handle onRelease, onReleaseOutside
2438 // Mouse button just went up.
2441 if (ms
.activeEntity
) {
2442 if (ms
.wasInsideActiveEntity
) {
2444 ms
.activeEntity
->mouseEvent(event_id(event_id::RELEASE
));
2445 need_redisplay
= true;
2448 // TODO: Handle trackAsMenu
2450 ms
.activeEntity
->mouseEvent(
2451 event_id(event_id::RELEASE_OUTSIDE
));
2452 // We got out of active entity
2453 ms
.activeEntity
= 0; // so we don't get RollOut next...
2454 need_redisplay
= true;
2458 return need_redisplay
;
2462 // New active entity is whatever is below the mouse right now.
2463 if (ms
.topmostEntity
!= ms
.activeEntity
) {
2465 if (ms
.activeEntity
) {
2466 ms
.activeEntity
->mouseEvent(event_id(event_id::ROLL_OUT
));
2467 need_redisplay
=true;
2470 ms
.activeEntity
= ms
.topmostEntity
;
2473 if (ms
.activeEntity
) {
2474 ms
.activeEntity
->mouseEvent(event_id(event_id::ROLL_OVER
));
2475 need_redisplay
=true;
2478 ms
.wasInsideActiveEntity
= true;
2481 // mouse button press
2485 // Try setting focus on the new DisplayObject. This will handle
2486 // all necessary events and removal of current focus.
2487 // Do not set focus to NULL.
2488 if (ms
.activeEntity
) {
2489 mr
.setFocus(ms
.activeEntity
);
2491 ms
.activeEntity
->mouseEvent(event_id(event_id::PRESS
));
2492 need_redisplay
= true;
2495 ms
.wasInsideActiveEntity
= true;
2500 return need_redisplay
;
2504 const DisplayObject
*
2505 getNearestObject(const DisplayObject
* o
)
2509 if (isReferenceable(*o
)) return o
;
2515 getBuiltinObject(movie_root
& mr
, const ObjectURI
& cl
)
2517 Global_as
& gl
= *mr
.getVM().getGlobal();
2520 if (!gl
.get_member(cl
, &val
)) return 0;
2521 return toObject(val
, mr
.getVM());
2525 advanceLiveChar(MovieClip
* mo
)
2527 if (!mo
->unloaded()) {
2529 log_debug(" advancing DisplayObject %s", ch
->getTarget());
2535 log_debug(" DisplayObject %s is unloaded, not advancing it",
2541 } // anonymous namespace
2542 } // namespace gnash
2546 // indent-tabs-mode: nil