big merge from master, fix rpm creation, drop fetching swfdec
[gnash.git] / libcore / movie_root.cpp
blob3253bf24adc47f54a1662e234bc95869883e1af4
1 // movie_root.cpp: The root movie, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software Foundation, Inc
5 //
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.
10 //
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
19 //
21 #include "movie_root.h"
23 #include <utility>
24 #include <string>
25 #include <sstream>
26 #include <map>
27 #include <bitset>
28 #include <cassert>
29 #include <functional>
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()
38 #include "log.h"
39 #include "MovieClip.h"
40 #include "Movie.h"
41 #include "VM.h"
42 #include "ExecutableCode.h"
43 #include "URL.h"
44 #include "namedStrings.h"
45 #include "GnashException.h"
46 #include "sound_handler.h"
47 #include "Timers.h"
48 #include "GnashKey.h"
49 #include "MovieFactory.h"
50 #include "GnashAlgorithm.h"
51 #include "GnashNumeric.h"
52 #include "Global_as.h"
53 #include "utf8.h"
54 #include "IOChannel.h"
55 #include "RunResources.h"
56 #include "Renderer.h"
57 #include "ExternalInterface.h"
58 #include "TextField.h"
59 #include "Button.h"
60 #include "Transform.h"
61 #include "StreamProvider.h"
63 #ifdef USE_SWFTREE
64 # include "tree.hh"
65 #endif
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
89 namespace gnash {
91 // Forward declarations
92 namespace {
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);
99 // Utility classes
100 namespace {
102 /// Execute an ActiveRelay if the object has that type.
103 struct ExecuteCallback
105 void operator()(const as_object* o) const {
106 ActiveRelay* a;
107 if (isNativeType(o, a)) {
108 a->update();
113 /// Identify and delete ExecutableCode that matches a particular target.
114 class RemoveTargetCode
116 public:
117 RemoveTargetCode(DisplayObject* target) : _target(target) {}
118 bool operator()(const ExecutableCode& c) const {
119 return _target == c.target();
121 private:
122 DisplayObject* _target;
125 void
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)
138 _gc(*this),
139 _runResources(runResources),
140 _vm(def.get_version(), *this, clock),
141 _interfaceHandler(0),
142 _fsCommandHandler(0),
143 _stageWidth(1),
144 _stageHeight(1),
145 m_background_color(255, 255, 255, 255),
146 m_background_color_set(false),
147 _mouseX(0),
148 _mouseY(0),
149 _lastTimerId(0),
150 _lastKeyEvent(key::INVALID),
151 _currentFocus(0),
152 _dragState(0),
153 _movies(),
154 _rootMovie(0),
155 _invalidated(true),
156 _disableScripts(false),
157 _processingActionLevel(PRIORITY_SIZE),
158 _hostfd(-1),
159 _controlfd(-1),
160 _quality(QUALITY_HIGH),
161 _alignMode(0),
162 _allowScriptAccess(SCRIPT_ACCESS_SAME_DOMAIN),
163 _showMenu(true),
164 _scaleMode(SCALEMODE_SHOWALL),
165 _displayState(DISPLAYSTATE_NORMAL),
166 _recursionLimit(256),
167 _timeoutLimit(15),
168 _movieAdvancementDelay(83), // ~12 fps by default
169 _lastMovieAdvancement(0),
170 _unnamedInstance(0),
171 _movieLoader(*this)
173 // This takes care of informing the renderer (if present) too.
174 setQuality(QUALITY_HIGH);
177 void
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
185 // of the queue.
188 movie_root::~movie_root()
190 clear(_actionQueue);
191 _intervalTimers.clear();
192 _movieLoader.clear();
194 assert(testInvariant());
197 Movie*
198 movie_root::init(movie_definition* def, const MovieClip::MovieVariables& vars)
200 Movie* mr = def->createMovie(*_vm.getGlobal());
201 mr->setVariables(vars);
202 setRootMovie(mr);
203 return mr;
206 void
207 movie_root::setRootMovie(Movie* movie)
209 _rootMovie = 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);
223 try {
224 setLevel(0, movie);
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",
238 e.what());
241 cleanupAndCollect();
244 void
245 movie_root::handleActionLimitHit(const std::string& msg)
247 bool disable = true;
248 if (_interfaceHandler) {
249 disable = callInterface<bool>(HostMessage(HostMessage::QUERY, msg));
251 else {
252 log_error("No user interface registered, assuming 'Yes' answer to "
253 "question: %s", msg);
255 if (disable) {
256 disableScripts();
257 clear(_actionQueue);
261 void
262 movie_root::cleanupAndCollect()
264 // Cleanup the stack.
265 _vm.getStack().clear();
267 cleanupDisplayList();
268 _gc.fuzzyCollect();
271 /* private */
272 void
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;
284 else {
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");
298 if (num == 0) {
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();
327 it->second = movie;
330 movie->set_invalidated();
332 /// Notify placement
333 movie->construct();
335 assert(testInvariant());
338 void
339 movie_root::swapLevels(MovieClip* movie, int depth)
341 assert(movie);
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());
355 #endif
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);
364 return;
367 if (oldDepth >= 0) {
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);
374 return;
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);
382 return;
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;
392 else {
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());
406 #endif
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());
418 void
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");
429 return;
432 MovieClip* mo = it->second;
433 if (mo == _rootMovie) {
434 IF_VERBOSE_ASCODING_ERRORS(
435 log_aserror(_("Original root movie can't be removed"));
437 return;
440 // TOCHECK: safe to erase here ?
441 mo->unload();
442 mo->destroy();
443 _movies.erase(it);
445 assert(testInvariant());
448 void
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");
456 return;
459 // TODO: rework this to avoid the double scan
460 setLevel(num, extern_movie);
463 MovieClip*
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;
471 return i->second;
474 void
475 movie_root::reset()
477 sound::sound_handler* sh = _runResources.soundHandler();
478 if (sh) sh->reset();
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
486 _liveChars.clear();
488 // wipe out queued actions
489 clear(_actionQueue);
491 // wipe out all levels
492 _movies.clear();
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
507 _gc.fuzzyCollect();
509 setInvalidated();
511 _disableScripts = false;
514 void
515 movie_root::setDimensions(size_t w, size_t h)
517 assert(testInvariant());
519 _stageWidth = w;
520 _stageHeight = h;
522 if (_scaleMode == SCALEMODE_NOSCALE) {
523 as_object* stage = getBuiltinObject(*this,
524 getURI(_vm, NSV::CLASS_STAGE));
525 if (stage) {
526 callMethod(stage, getURI(_vm, NSV::PROP_BROADCAST_MESSAGE),
527 "onResize");
532 assert(testInvariant());
535 bool
536 movie_root::mouseMoved(boost::int32_t x, boost::int32_t y)
538 assert(testInvariant());
540 _mouseX = x;
541 _mouseY = y;
542 return notify_mouse_listeners(event_id(event_id::MOUSE_MOVE));
546 bool
547 movie_root::keyEvent(key::code k, bool down)
549 _lastKeyEvent = k;
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;
563 if (down) {
564 ch->notifyEvent(event_id(event_id::KEY_DOWN, key::INVALID));
565 ch->notifyEvent(event_id(event_id::KEY_PRESS, k));
567 else {
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));
574 if (key) {
576 try {
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.
581 if (down) {
582 callMethod(key, getURI(_vm, NSV::PROP_BROADCAST_MESSAGE), "onKeyDown");
584 else {
585 callMethod(key, getURI(_vm,NSV::PROP_BROADCAST_MESSAGE), "onKeyUp");
588 catch (const ActionLimitException &e) {
589 log_error(_("ActionLimits hit notifying key listeners: %s."),
590 e.what());
591 clear(_actionQueue);
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;
604 if (down) {
605 ch->notifyEvent(event_id(event_id::KEY_DOWN, key::INVALID));
606 ch->notifyEvent(event_id(event_id::KEY_PRESS, k));
608 else {
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
615 if (down) {
616 TextField* tf = dynamic_cast<TextField*>(_currentFocus);
617 if (tf) tf->notifyEvent(event_id(event_id::KEY_PRESS, k));
620 processActionQueue();
622 return false;
625 bool
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());
641 return true;
644 bool
645 movie_root::mouseClick(bool mouse_pressed)
647 assert(testInvariant());
649 _mouseButtonState.isDown = mouse_pressed;
651 if (mouse_pressed) {
652 return notify_mouse_listeners(event_id(event_id::MOUSE_DOWN));
654 return notify_mouse_listeners(event_id(event_id::MOUSE_UP));
658 bool
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();
674 if (dragging) {
675 // TODO: optimize making findDropTarget and getTopmostMouseEntity
676 // use a single scan.
677 const DisplayObject* dropChar = findDropTarget(x, y, dragging);
678 if (dropChar) {
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).
692 try {
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());
702 return need_redraw;
706 std::pair<boost::int32_t, boost::int32_t>
707 movie_root::mousePosition() const
709 assert(testInvariant());
710 return std::make_pair(_mouseX, _mouseY);
713 void
714 movie_root::setDragState(const DragState& st)
716 _dragState = st;
717 DisplayObject* ch = st.getCharacter();
718 if (ch && !st.isLockCentered()) {
719 // Get coordinates of the DisplayObject's origin
720 point origin(0, 0);
721 SWFMatrix chmat = getWorldMatrix(*ch);
722 point world_origin;
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());
736 void
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
744 _dragState.reset();
745 return;
748 point world_mouse(pixelsToTwips(_mouseX), pixelsToTwips(_mouseY));
750 SWFMatrix parent_world_mat;
751 DisplayObject* p = dragChar->parent();
752 if (p) {
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()) {
762 SWFRect bounds;
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);
781 boost::uint32_t
782 movie_root::addIntervalTimer(std::auto_ptr<Timer> timer)
784 assert(timer.get());
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));
794 return id;
797 bool
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();
812 return true;
816 bool
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;
826 try {
828 const size_t elapsed = now - _lastMovieAdvancement;
829 if (elapsed >= _movieAdvancementDelay)
831 advanced = true;
832 advanceMovie();
834 // To catch-up lateness we pretend we advanced when
835 // was time for it.
836 // NOTE:
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;
845 if (catchup) {
846 _lastMovieAdvancement += _movieAdvancementDelay;
847 } else {
848 _lastMovieAdvancement = now;
853 //log_debug("Lateness: %d", now-_lastMovieAdvancement);
855 executeAdvanceCallbacks();
857 executeTimers();
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());
864 clear(_actionQueue);
866 catch (const ActionParserException& e) {
867 log_error(_("Buffer overread during advance: %s"), e.what());
868 clear(_actionQueue);
871 return advanced;
874 void
875 movie_root::advanceMovie()
878 // Do mouse drag, if needed
879 doMouseDrag();
881 // Advance all non-unloaded DisplayObjects in the LiveChars list
882 // in reverse order (last added, first advanced)
883 // NOTE: can throw ActionLimitException
884 advanceLiveChars();
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();
900 cleanupAndCollect();
902 assert(testInvariant());
906 movie_root::timeToNextFrame() const
908 unsigned int now = _vm.getTime();
909 const int elapsed = now - _lastMovieAdvancement;
910 return _movieAdvancementDelay - elapsed;
913 void
914 movie_root::display()
916 // GNASH_REPORT_FUNCTION;
918 assert(testInvariant());
920 clearInvalidated();
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
927 // have valid bounds
928 log_debug("original root movie had null bounds, not displaying");
929 return;
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);
952 continue;
955 movie->display(*renderer, Transform());
960 bool
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);
979 if (mouseObj) {
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.
984 try {
985 callMethod(mouseObj, propBroadcastMessage, event.functionName());
987 catch (ActionLimitException &e) {
988 log_error(_("ActionLimits hit notifying mouse events: %s."),
989 e.what());
990 clear(_actionQueue);
995 assert(testInvariant());
997 if (!copy.empty()) {
998 // process actions queued in the above step
999 processActionQueue();
1001 return fire_mouse_event();
1004 DisplayObject*
1005 movie_root::getFocus()
1007 assert(testInvariant());
1008 return _currentFocus;
1011 bool
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)) {
1020 return false;
1023 if (to && !to->handleFocus()) {
1024 // TODO: not clear whether to remove focus in this case.
1025 return false;
1028 // Undefined or NULL DisplayObject removes current focus. Otherwise, try
1029 // setting focus to the new DisplayObject. If it fails, remove current
1030 // focus anyway.
1032 // Store previous focus, as the focus needs to change before onSetFocus
1033 // is called and listeners are notified.
1034 DisplayObject* from = _currentFocus;
1036 if (from) {
1037 // Perform any actions required on killing focus (only TextField).
1038 from->killFocus();
1040 /// A valid focus must have an associated object.
1041 assert(getObject(from));
1042 callMethod(getObject(from), NSV::PROP_ON_KILL_FOCUS, getObject(to));
1045 _currentFocus = to;
1047 if (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.
1056 if (sel) {
1057 callMethod(sel, NSV::PROP_BROADCAST_MESSAGE, "onSetFocus",
1058 getObject(from), getObject(to));
1061 assert(testInvariant());
1063 return true;
1066 DisplayObject*
1067 movie_root::getActiveEntityUnderPointer() const
1069 return _mouseButtonState.activeEntity;
1072 DisplayObject*
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());
1087 void
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)
1106 setInvalidated();
1108 _quality = q;
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.
1120 size_t
1121 movie_root::getStageWidth() const
1123 if (_scaleMode == SCALEMODE_NOSCALE) {
1124 return _stageWidth;
1127 // If scaling is allowed, always return the original movie size.
1128 if (_rootMovie) {
1129 return static_cast<size_t>(_rootMovie->widthPixels());
1130 } else {
1131 return 0;
1135 /// Get actionscript height of stage, in pixels. The height
1136 /// returned depends on the scale mode.
1137 size_t
1138 movie_root::getStageHeight() const
1140 if (_scaleMode == SCALEMODE_NOSCALE) {
1141 return _stageHeight;
1144 // If scaling is allowed, always return the original movie size.
1145 if (_rootMovie) {
1146 return static_cast<size_t>(_rootMovie->heightPixels());
1147 } else {
1148 return 0;
1152 /// Takes a short int bitfield: the four bits correspond
1153 /// to the AlignMode enum
1154 void
1155 movie_root::setStageAlignment(short s)
1157 _alignMode = s;
1158 callInterface(HostMessage(HostMessage::UPDATE_STAGE));
1161 /// The mode is one of never, always, with sameDomain the default
1162 void
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
1193 /// variable
1194 bool
1195 movie_root::getShowMenuState() const
1197 return _showMenu;
1200 /// Sets the value of _showMenu and calls the gui handler to process the
1201 /// fscommand to change the display of the context menu
1202 void
1203 movie_root::setShowMenuState(bool state)
1205 _showMenu = 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
1216 std::string
1217 movie_root::getStageAlignMode() const
1219 std::string align;
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');
1225 return align;
1228 void
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
1239 // resize.
1240 if (_rootMovie &&
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;
1255 _scaleMode = sm;
1256 callInterface(HostMessage(HostMessage::UPDATE_STAGE));
1258 if (notifyResize) {
1259 as_object* stage = getBuiltinObject(*this, NSV::CLASS_STAGE);
1260 if (stage) {
1261 callMethod(stage, NSV::PROP_BROADCAST_MESSAGE, "onResize");
1266 void
1267 movie_root::setStageDisplayState(const DisplayState ds)
1269 _displayState = ds;
1271 as_object* stage = getBuiltinObject(*this, NSV::CLASS_STAGE);
1272 if (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);
1280 callInterface(e);
1283 void
1284 movie_root::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
1286 if (isInvalidated()) {
1287 ranges.setWorld();
1288 return;
1291 for (Levels::reverse_iterator i=_movies.rbegin(), e=_movies.rend(); i!=e;
1292 ++i) {
1293 i->second->add_invalidated_bounds(ranges, force);
1298 size_t
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;
1307 size_t
1308 movie_root::processActionQueue(size_t lvl)
1310 ActionQueue::value_type& q = _actionQueue[lvl];
1312 assert(minPopulatedPriorityQueue() == lvl);
1314 #ifdef GNASH_DEBUG
1315 static unsigned calls=0;
1316 ++calls;
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);
1322 #endif
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());
1330 code->execute();
1332 size_t minLevel = minPopulatedPriorityQueue();
1333 if (minLevel < lvl) {
1334 #ifdef GNASH_DEBUG
1335 log_debug(" Actions pushed in priority %d (< "
1336 "%d), restarting the scan (call"
1337 " %u)", minLevel, lvl, calls);
1338 #endif
1339 return minLevel;
1343 assert(q.empty());
1345 #ifdef GNASH_DEBUG
1346 if (actionsToProcess) {
1347 log_debug(" Done processing actions in priority queue "
1348 "%d (call %u)", lvl, calls);
1350 #endif
1352 return minPopulatedPriorityQueue();
1355 void
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.
1364 return;
1367 if (_disableScripts) {
1368 /// cleanup anything pushed later..
1369 clear(_actionQueue);
1370 return;
1373 int lvl=minPopulatedPriorityQueue();
1374 while (lvl < _processingActionLevel) {
1375 lvl = processActionQueue(lvl);
1380 void
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));
1387 void
1388 movie_root::addAdvanceCallback(ActiveRelay* obj)
1390 _objectCallbacks.insert(obj);
1393 void
1394 movie_root::removeAdvanceCallback(ActiveRelay* obj)
1396 _objectCallbacks.erase(obj);
1399 void
1400 movie_root::processActionQueue()
1402 if (_disableScripts) {
1403 /// cleanup anything pushed later..
1404 clear(_actionQueue);
1405 return;
1408 _processingActionLevel = minPopulatedPriorityQueue();
1410 while (_processingActionLevel < PRIORITY_SIZE) {
1411 _processingActionLevel = processActionQueue(_processingActionLevel);
1414 // Cleanup the stack.
1415 _vm.getStack().clear();
1419 void
1420 movie_root::removeQueuedConstructor(DisplayObject* target)
1422 ActionQueue::value_type& pr = _actionQueue[PRIORITY_CONSTRUCT];
1423 pr.erase_if(RemoveTargetCode(target));
1426 void
1427 movie_root::pushAction(std::auto_ptr<ExecutableCode> code, size_t lvl)
1429 assert(lvl < PRIORITY_SIZE);
1430 _actionQueue[lvl].push_back(code);
1433 void
1434 movie_root::pushAction(const action_buffer& buf, DisplayObject* target)
1436 #ifdef GNASH_DEBUG
1437 log_debug("Pushed action buffer for target %s",
1438 target->getTargetPath());
1439 #endif
1441 std::auto_ptr<ExecutableCode> code(new GlobalCode(buf, target));
1443 _actionQueue[PRIORITY_DOACTION].push_back(code);
1446 void
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(),
1474 ExecuteCallback());
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.
1485 if (_controlfd) {
1486 boost::shared_ptr<ExternalInterface::invoke_t> invoke =
1487 ExternalInterface::ExternalEventCheck(_controlfd);
1488 if (invoke) {
1489 if (processInvoke(invoke.get()) == false) {
1490 if (!invoke->name.empty()) {
1491 log_error("Couldn't process ExternalInterface Call %s",
1492 invoke->name);
1498 processActionQueue();
1501 bool
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);
1521 VM &vm = getVM();
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);
1529 VM &vm = getVM();
1530 std::string var = invoke->args[0].to_string();
1531 as_value val;
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") {
1539 const bool result =
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();
1548 arg += ":";
1549 arg += invoke->args[0].to_string();
1550 arg += ":";
1551 arg += invoke->args[1].to_string();
1552 arg += ":";
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();
1571 arg += ":";
1572 arg += invoke->args[0].to_string();
1573 arg += ":";
1574 arg += invoke->args[1].to_string();
1575 arg += ":";
1576 arg += invoke->args[2].to_string();
1577 arg += ":";
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);
1593 } else {
1594 std::string result = callExternalCallback(invoke->name, invoke->args);
1595 if (result == ExternalInterface::makeString("Error")) {
1596 return false;
1597 } else if (result == ExternalInterface::makeString("SecurityError")) {
1598 return false;
1600 return true;
1603 if (!ss.str().empty()) {
1604 if (_hostfd >= 0) {
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());
1608 if (ret == -1) {
1609 log_error(_("Could not write to user-provided host requests "
1610 "fd %d: %s"), _hostfd, std::strerror(errno));
1613 } else {
1614 log_debug("No response needed for %s request", invoke->name);
1617 return true;
1620 void
1621 movie_root::executeTimers()
1623 #ifdef GNASH_DEBUG_TIMERS_EXPIRATION
1624 log_debug("Checking %d timers for expiry", _intervalTimers.size());
1625 #endif
1627 unsigned long now = _vm.getTime();
1629 typedef std::multimap<unsigned int, boost::shared_ptr<Timer> >
1630 ExpiredTimers;
1632 ExpiredTimers expiredTimers;
1634 for (TimerMap::iterator it = _intervalTimers.begin(),
1635 itEnd = _intervalTimers.end(); it != itEnd; ) {
1637 TimerMap::iterator nextIterator = it;
1638 ++nextIterator;
1640 boost::shared_ptr<Timer> timer(it->second);
1642 if (timer->cleared()) {
1643 // this timer was cleared, erase it
1644 _intervalTimers.erase(it);
1646 else {
1647 unsigned long elapsed;
1648 if (timer->expired(now, elapsed)) {
1649 expiredTimers.insert(std::make_pair(elapsed, timer));
1653 it = nextIterator;
1656 foreachSecond(expiredTimers.begin(), expiredTimers.end(),
1657 &Timer::executeAndReset);
1659 if (!expiredTimers.empty()) processActionQueue();
1663 void
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();
1708 i!=e; ++i) {
1709 #ifdef ALLOW_GC_RUN_DURING_ACTIONS_EXECUTION
1710 (*i)->setReachable();
1711 #else
1712 assert((*i)->isReachable());
1713 #endif
1715 #endif
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();
1722 #else
1723 assert((*i)->isReachable());
1724 #endif
1726 #endif
1730 InteractiveObject*
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();
1735 i != e; ++i)
1737 InteractiveObject* ret = i->second->topmostMouseEntity(x, y);
1738 if (ret) return ret;
1741 return 0;
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();
1750 i!=e; ++i) {
1752 const DisplayObject* ret = i->second->findDropTarget(x, y, dragging);
1753 if (ret) return ret;
1755 return 0;
1758 /// This should store a callback object in movie_root.
1760 /// TODO: currently it doesn't.
1761 void
1762 movie_root::addExternalCallback(const std::string& name, as_object* callback)
1764 UNUSED(callback);
1766 // When an external callback is added, we have to notify the plugin
1767 // that this method is available.
1768 if (_hostfd >= 0) {
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"
1785 /// <pre>
1786 /// <invoke name="methodname" returntype="xml">
1787 /// <arguments></arguments>
1788 /// ...
1789 /// <arguments></arguments>
1790 /// </invoke>
1792 /// May return any supported type like Number or String in XML format.
1794 /// </pre>
1795 std::string
1796 movie_root::callExternalJavascript(const std::string &name,
1797 const std::vector<as_value> &fnargs)
1799 // GNASH_REPORT_FUNCTION;
1800 std::string result;
1801 // If the browser is connected, we send an Invoke message to the
1802 // browser.
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));
1810 } else {
1811 // Now read the response from the browser after it's exectuted
1812 // the JavaScript function.
1813 result = ExternalInterface::readBrowser(_controlfd);
1817 return result;
1820 // Call one of the registered callbacks, and return the result to
1821 // Javascript in the browser.
1822 std::string
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
1833 // length arg list
1834 as_value val;
1835 switch (fnargs.size()) {
1836 case 0:
1837 val = callMethod(obj, key);
1838 break;
1839 case 1:
1840 val = callMethod(obj, key, fnargs[0]);
1841 break;
1842 case 2:
1843 val = callMethod(obj, key, fnargs[0], fnargs[1]);
1844 break;
1845 case 3:
1846 val = callMethod(obj, key, fnargs[0], fnargs[1], fnargs[2]);
1847 break;
1848 default:
1849 val = callMethod(obj, key);
1850 break;
1853 std::string result;
1854 if (val.is_null()) {
1855 // Return an error
1856 result = ExternalInterface::makeString("Error");
1857 } else {
1858 result = ExternalInterface::toXML(val);
1861 // If the browser is connected, we send an Invoke message to the
1862 // browser.
1863 if (_hostfd >= 0) {
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));
1871 return result;
1874 void
1875 movie_root::remove_key_listener(Button* listener)
1877 _keyListeners.remove_if(std::bind2nd(std::equal_to<Button*>(), listener));
1880 void
1881 movie_root::add_key_listener(Button* listener)
1883 assert(listener);
1885 if (std::find(_keyListeners.begin(), _keyListeners.end(), listener)
1886 != _keyListeners.end()) return;
1888 _keyListeners.push_front(listener);
1891 void
1892 movie_root::cleanupDisplayList()
1895 #define GNASH_DEBUG_INSTANCE_LIST 1
1897 #ifdef GNASH_DEBUG_INSTANCE_LIST
1898 static size_t maxLiveChars = 0;
1899 #endif
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
1923 // are found.
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...
1929 bool needScan;
1930 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1931 int scansCount = 0;
1932 #endif
1933 do {
1934 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1935 scansCount++;
1936 int cleaned =0;
1937 #endif
1938 needScan=false;
1940 // Remove unloaded MovieClips from the _liveChars list
1941 for (LiveChars::iterator i = _liveChars.begin(), e = _liveChars.end();
1942 i != e;) {
1943 MovieClip* ch = *i;
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
1948 // DisplayObjects
1949 if (!ch->isDestroyed()) {
1951 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1952 cout << ch->getTarget() << "(" << typeName(*ch) <<
1953 ") was unloaded but not destroyed, destroying now" <<
1954 endl;
1955 #endif
1956 ch->destroy();
1957 // destroy() might mark already-scanned chars as unloaded
1958 needScan = true;
1960 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1961 else {
1962 cout << ch->getTarget() << "(" << typeName(*ch) <<
1963 ") was unloaded and destroyed" << endl;
1965 #endif
1967 i = _liveChars.erase(i);
1969 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1970 cleaned++;
1971 #endif
1973 else ++i;
1976 #ifdef GNASH_DEBUG_DLIST_CLEANUP
1977 cout << " Scan " << scansCount << " cleaned " << cleaned <<
1978 " instances" << endl;
1979 #endif
1980 } while (needScan);
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);
1987 #endif
1991 void
1992 movie_root::advanceLiveChars()
1995 #ifdef GNASH_DEBUG
1996 log_debug("---- movie_root::advance: %d live DisplayObjects in "
1997 "the global list", _liveChars.size());
1998 #endif
2000 std::for_each(_liveChars.begin(), _liveChars.end(),
2001 boost::bind(advanceLiveChar, _1));
2004 void
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) {
2014 setInvalidated();
2015 m_background_color = color;
2019 void
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) {
2026 setInvalidated();
2027 m_background_color.m_a = newAlpha;
2031 DisplayObject*
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);
2041 assert(o);
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);
2053 if (!o) {
2054 #ifdef GNASH_DEBUG_TARGET_RESOLUTION
2055 log_debug("Evaluating DisplayObject target path: element "
2056 "'%s' of path '%s' not found", part, tgtstr);
2057 #endif
2058 return NULL;
2060 if (to == std::string::npos) break;
2061 from = to + 1;
2063 return get<DisplayObject>(o);
2066 void
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);
2073 if (_hostfd < 0) {
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());
2094 if (ret == -1) {
2095 log_error(_("Fork failed launching url opener '%s'"), command);
2097 return;
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
2106 switch (method) {
2107 case MovieClip::METHOD_POST:
2108 fnargs.push_back(as_value("POST"));
2109 break;
2110 case MovieClip::METHOD_GET:
2111 fnargs.push_back(as_value("GET"));
2112 break;
2113 case MovieClip::METHOD_NONE:
2114 default:
2115 fnargs.push_back(as_value("GET"));
2116 break;
2119 // The third argument is the target, which is something like _blank
2120 // or _self.
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"),
2143 ret, _hostfd);
2147 void
2148 movie_root::setScriptLimits(boost::uint16_t recursion, boost::uint16_t timeout)
2151 if ( recursion == _recursionLimit && _timeoutLimit == timeout ) {
2152 // avoid the debug log...
2153 return;
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;
2168 #ifdef USE_SWFTREE
2169 void
2170 movie_root::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
2173 // Stage
2174 const movie_definition* def = _rootMovie->definition();
2175 assert(def);
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",
2186 os.str()));
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.
2194 os.str("");
2195 os << def->get_width_pixels() <<
2196 "x" << def->get_height_pixels();
2197 localIter = tr.append_child(it, std::make_pair("Real dimensions",
2198 os.str()));
2200 /// Stage: rendered dimensions.
2201 os.str("");
2202 os << _stageWidth << "x" << _stageHeight;
2203 localIter = tr.append_child(it, std::make_pair("Rendered dimensions",
2204 os.str()));
2206 // Stage: scripts state (enabled/disabled)
2207 localIter = tr.append_child(it, std::make_pair("Scripts",
2208 _disableScripts ? " disabled" : "enabled"));
2210 getCharacterTree(tr, it);
2213 void
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"),
2223 os.str()));
2225 /// DisplayObject tree
2226 for (Levels::const_iterator i = _movies.begin(), e = _movies.end();
2227 i != e; ++i) {
2228 i->second->getMovieInfo(tr, localIter);
2233 #endif
2235 void
2236 movie_root::handleFsCommand(const std::string& cmd, const std::string& arg)
2237 const
2239 if (_fsCommandHandler) _fsCommandHandler->notify(cmd, arg);
2242 bool
2243 isLevelTarget(int version, const std::string& name, unsigned int& levelno)
2245 if (version > 6) {
2246 if (name.compare(0, 6, "_level")) return false;
2248 else {
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) {
2254 return false;
2256 // getting 0 here for "_level" is intentional
2257 levelno = std::strtoul(name.c_str() + 6, NULL, 0);
2258 return true;
2262 short
2263 stringToStageAlign(const std::string& str)
2265 short am = 0;
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;
2285 return am;
2289 void
2290 movie_root::LoadCallback::setReachable() const
2292 _obj->setReachable();
2295 bool
2296 movie_root::LoadCallback::processLoad()
2299 if (!_stream) {
2300 callMethod(_obj, NSV::PROP_ON_DATA, as_value());
2301 return true;
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());
2313 return true;
2316 if (actuallyRead) {
2318 // set total size only on first read
2319 if (_buf.empty()) {
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
2338 if (_buf.empty()) {
2339 callMethod(_obj, NSV::PROP_ON_DATA, as_value());
2340 return true;
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);
2370 return true;
2373 void
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)");
2379 return;
2381 _interfaceHandler->call(e);
2385 inline bool
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
2391 // itself
2392 //assert( ! _movies.empty() );
2394 return true;
2397 namespace {
2399 // Return whether any action triggered by this event requires display redraw.
2401 /// TODO: make this code more readable !
2402 bool
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
2410 // a redraw.
2412 if (ms.wasDown) {
2413 // TODO: Handle trackAsMenu dragOver
2414 // Handle onDragOut, onDragOver
2415 if (!ms.wasInsideActiveEntity) {
2417 if (ms.topmostEntity == ms.activeEntity) {
2419 // onDragOver
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) {
2428 // onDragOut
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
2437 if (!ms.isDown) {
2438 // Mouse button just went up.
2439 ms.wasDown = false;
2441 if (ms.activeEntity) {
2442 if (ms.wasInsideActiveEntity) {
2443 // onRelease
2444 ms.activeEntity->mouseEvent(event_id(event_id::RELEASE));
2445 need_redisplay = true;
2447 else {
2448 // TODO: Handle trackAsMenu
2449 // onReleaseOutside
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;
2461 else {
2462 // New active entity is whatever is below the mouse right now.
2463 if (ms.topmostEntity != ms.activeEntity) {
2464 // onRollOut
2465 if (ms.activeEntity) {
2466 ms.activeEntity->mouseEvent(event_id(event_id::ROLL_OUT));
2467 need_redisplay=true;
2470 ms.activeEntity = ms.topmostEntity;
2472 // onRollOver
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
2482 if (ms.isDown) {
2483 // onPress
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;
2496 ms.wasDown = true;
2500 return need_redisplay;
2504 const DisplayObject*
2505 getNearestObject(const DisplayObject* o)
2507 while (1) {
2508 assert(o);
2509 if (isReferenceable(*o)) return o;
2510 o = o->parent();
2514 as_object*
2515 getBuiltinObject(movie_root& mr, const ObjectURI& cl)
2517 Global_as& gl = *mr.getVM().getGlobal();
2519 as_value val;
2520 if (!gl.get_member(cl, &val)) return 0;
2521 return toObject(val, mr.getVM());
2524 void
2525 advanceLiveChar(MovieClip* mo)
2527 if (!mo->unloaded()) {
2528 #ifdef GNASH_DEBUG
2529 log_debug(" advancing DisplayObject %s", ch->getTarget());
2530 #endif
2531 mo->advance();
2533 #ifdef GNASH_DEBUG
2534 else {
2535 log_debug(" DisplayObject %s is unloaded, not advancing it",
2536 mo->getTarget());
2538 #endif
2541 } // anonymous namespace
2542 } // namespace gnash
2544 // local Variables:
2545 // mode: C++
2546 // indent-tabs-mode: nil
2547 // End: