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