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.
15 //
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 //
20 
21 #include "movie_root.h"
22 
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>
34 
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"
61 
62 #ifdef USE_SWFTREE
63 # include "tree.hh"
64 #endif
65 
66 //#define GNASH_DEBUG 1
67 //#define GNASH_DEBUG_LOADMOVIE_REQUESTS_PROCESSING 1
68 //#define GNASH_DEBUG_TIMERS_EXPIRATION 1
69 
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.
74 //
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
85 //
86 //#define GNASH_DEBUG_DLIST_CLEANUP 1
87 
88 namespace gnash {
89 
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);
97 }
98 
99 // Utility classes
100 namespace {
101 
102 /// Execute an ActiveRelay if the object has that type.
103 struct ExecuteCallback
104 {
operator ()gnash::__anon959a94ef0211::ExecuteCallback105     void operator()(const as_object* o) const {
106         ActiveRelay* a;
107         if (isNativeType(o, a)) {
108             a->update();
109         }
110     }
111 };
112 
113 /// Identify and delete ExecutableCode that matches a particular target.
114 class RemoveTargetCode
115 {
116 public:
RemoveTargetCode(DisplayObject * target)117     RemoveTargetCode(DisplayObject* target) : _target(target) {}
operator ()(const ExecutableCode & c) const118     bool operator()(const ExecutableCode& c) const {
119         return _target == c.target();
120     }
121 private:
122     DisplayObject* _target;
123 };
124 
125 void
clear(movie_root::ActionQueue & aq)126 clear(movie_root::ActionQueue& aq)
127 {
128     std::for_each(aq.begin(), aq.end(),
129             std::mem_fn(&movie_root::ActionQueue::value_type::clear));
130 }
131 
132 } // anonymous namespace
133 
134 
movie_root(VirtualClock & clock,const RunResources & runResources)135 movie_root::movie_root(VirtualClock& clock, const RunResources& runResources)
136     :
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(0ULL),
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)
170 {
171     // This takes care of informing the renderer (if present) too.
172     setQuality(QUALITY_HIGH);
173 
174     gnash::RcInitFile& rcfile = gnash::RcInitFile::getDefaultInstance();
175     _recursionLimit = rcfile.getScriptsRecursionLimit();
176     _timeoutLimit = rcfile.getScriptsTimeout();
177 }
178 
179 void
disableScripts()180 movie_root::disableScripts()
181 {
182     _disableScripts = true;
183 
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.
188 }
189 
~movie_root()190 movie_root::~movie_root()
191 {
192     clear(_actionQueue);
193     _intervalTimers.clear();
194     _movieLoader.clear();
195 
196     assert(testInvariant());
197 }
198 
199 Movie*
init(movie_definition * def,const MovieClip::MovieVariables & vars)200 movie_root::init(movie_definition* def, const MovieClip::MovieVariables& vars)
201 {
202     _vm.setSWFVersion(def->get_version());
203 
204     Movie* mr = def->createMovie(*_vm.getGlobal());
205     mr->setVariables(vars);
206     setRootMovie(mr);
207     return mr;
208 }
209 
210 void
setRootMovie(Movie * movie)211 movie_root::setRootMovie(Movie* movie)
212 {
213     _rootMovie = movie;
214 
215     const movie_definition* md = movie->definition();
216     float fps = md->get_frame_rate();
217     _movieAdvancementDelay = static_cast<int>(1000/fps);
218 
219     _lastMovieAdvancement = _vm.getTime();
220 
221     _stageWidth = static_cast<int>(md->get_width_pixels());
222     _stageHeight = static_cast<int>(md->get_height_pixels());
223 
224     movie->set_depth(DisplayObject::staticDepthOffset);
225 
226     try {
227         setLevel(0, movie);
228 
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();
233     }
234     catch (const ActionLimitException& al) {
235         handleActionLimitHit(al.what());
236     }
237     catch (const ActionParserException& e) {
238         log_error(_("ActionParserException thrown during setRootMovie: %s"),
239                 e.what());
240     }
241 
242     cleanupAndCollect();
243 }
244 
245 bool
queryInterface(const std::string & what) const246 movie_root::queryInterface(const std::string& what) const
247 {
248     bool disable = true;
249     if (_interfaceHandler) {
250         disable = callInterface<bool>(HostMessage(HostMessage::QUERY, what));
251     }
252     else {
253         log_error(_("No user interface registered, assuming 'Yes' answer to question: %s"), what);
254     }
255     return disable;
256 }
257 
258 void
setStreamBlock(int id,int block)259 movie_root::setStreamBlock(int id, int block)
260 {
261     if (!_timelineSound) {
262         _timelineSound = SoundStream(id, block);
263         return;
264     }
265 
266     // Don't replace timeline stream.
267     if (_timelineSound->id != id) return;
268 
269     _timelineSound->block = block;
270 }
271 
272 void
stopStream(int id)273 movie_root::stopStream(int id)
274 {
275     if (!_timelineSound) return;
276     if (_timelineSound->id == id) _timelineSound.reset();
277 }
278 
279 void
registerClass(const SWF::DefinitionTag * sprite,as_function * cls)280 movie_root::registerClass(const SWF::DefinitionTag* sprite, as_function* cls)
281 {
282     _registeredClasses[sprite] = cls;
283 }
284 
285 as_function*
getRegisteredClass(const SWF::DefinitionTag * sprite) const286 movie_root::getRegisteredClass(const SWF::DefinitionTag* sprite) const
287 {
288     RegisteredClasses::const_iterator it = _registeredClasses.find(sprite);
289     if (it == _registeredClasses.end()) return nullptr;
290     return it->second;
291 }
292 
293 void
handleActionLimitHit(const std::string & msg)294 movie_root::handleActionLimitHit(const std::string& msg)
295 {
296     log_debug("Disabling scripts: %1%", msg);
297     disableScripts();
298     clear(_actionQueue);
299 }
300 
301 void
cleanupAndCollect()302 movie_root::cleanupAndCollect()
303 {
304     // Cleanup the stack.
305     _vm.getStack().clear();
306 
307     // Reset the constant pool
308     _vm.setConstantPool(nullptr);
309 
310     cleanupDisplayList();
311     _gc.fuzzyCollect();
312 }
313 
314 /* private */
315 void
setLevel(unsigned int num,Movie * movie)316 movie_root::setLevel(unsigned int num, Movie* movie)
317 {
318     assert(movie != nullptr);
319     assert(static_cast<unsigned int>(movie->get_depth()) ==
320                             num + DisplayObject::staticDepthOffset);
321 
322 
323     Levels::iterator it = _movies.find(movie->get_depth());
324     if (it == _movies.end()) {
325         _movies[movie->get_depth()] = movie;
326     }
327     else {
328         // don't leak overloaded levels
329 
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");
339         }
340 
341         if (num == 0) {
342 
343             log_debug("Loading into _level0");
344 
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();
349 
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)
357 
358             _stageWidth = movie->widthPixels();
359             _stageHeight = movie->heightPixels();
360 
361             // notify  stage replacement
362             if (_interfaceHandler) {
363                 const HostMessage e(HostMessage::RESIZE_STAGE,
364                         std::make_pair(_stageWidth, _stageHeight));
365                 _interfaceHandler->call(e);
366             }
367         }
368 
369         it->second->destroy();
370         it->second = movie;
371     }
372 
373     movie->set_invalidated();
374 
375     /// Notify placement
376     movie->construct();
377 
378     assert(testInvariant());
379 }
380 
381 void
swapLevels(MovieClip * movie,int depth)382 movie_root::swapLevels(MovieClip* movie, int depth)
383 {
384     assert(movie);
385 
386 //#define GNASH_DEBUG_LEVELS_SWAPPING 1
387 
388     const int oldDepth = movie->get_depth();
389 
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());
397     }
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);
406         );
407         return;
408     }
409 
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);
416         );
417         return;
418     }
419 
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;
426     }
427 
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;
434     }
435     else {
436         MovieClip* otherMovie = targetIt->second;
437         otherMovie->set_depth(oldDepth);
438         oldIt->second = otherMovie;
439         targetIt->second = movie;
440     }
441 
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());
448     }
449 #endif
450 
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();
457 
458     assert(testInvariant());
459 }
460 
461 void
dropLevel(int depth)462 movie_root::dropLevel(int depth)
463 {
464     // should be checked by caller
465     // TODO: don't use a magic number! See MovieClip::removeMovieClip().
466     assert(depth >= 0 && depth <= 1048575);
467 
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;
472     }
473 
474     MovieClip* mo = it->second;
475     if (mo == _rootMovie) {
476         IF_VERBOSE_ASCODING_ERRORS(
477             log_aserror(_("Original root movie can't be removed"));
478         );
479         return;
480     }
481 
482     // TOCHECK: safe to erase here ?
483 
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);
489 
490     assert(testInvariant());
491 }
492 
493 void
replaceLevel(unsigned int num,Movie * extern_movie)494 movie_root::replaceLevel(unsigned int num, Movie* extern_movie)
495 {
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;
501     }
502 
503     // TODO: rework this to avoid the double scan
504     setLevel(num, extern_movie);
505 }
506 
507 MovieClip*
getLevel(unsigned int num) const508 movie_root::getLevel(unsigned int num) const
509 {
510     Levels::const_iterator i =
511         _movies.find(num + DisplayObject::staticDepthOffset);
512 
513     if (i == _movies.end()) return nullptr;
514 
515     return i->second;
516 }
517 
518 void
reset()519 movie_root::reset()
520 {
521     sound::sound_handler* sh = _runResources.soundHandler();
522     if (sh) sh->reset();
523 
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;
528 
529     // wipe out live chars
530     _liveChars.clear();
531 
532     // wipe out queued actions
533     clear(_actionQueue);
534 
535     // wipe out all levels
536     _movies.clear();
537 
538     // remove all intervals
539     _intervalTimers.clear();
540 
541     // remove all loadMovie requests
542     _movieLoader.clear();
543 
544     // Remove button key events.
545     _buttonListeners.clear();
546 
547     // Cleanup the stack.
548     _vm.getStack().clear();
549 
550     // Run the garbage collector again
551     _gc.fuzzyCollect();
552 
553     setInvalidated();
554 
555     _disableScripts = false;
556 
557     _timelineSound.reset();
558 }
559 
560 void
setDimensions(size_t w,size_t h)561 movie_root::setDimensions(size_t w, size_t h)
562 {
563     assert(testInvariant());
564 
565     _stageWidth = w;
566     _stageHeight = h;
567 
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");
574         }
575 
576     }
577 
578     assert(testInvariant());
579 }
580 
581 bool
mouseMoved(std::int32_t x,std::int32_t y)582 movie_root::mouseMoved(std::int32_t x, std::int32_t y)
583 {
584     assert(testInvariant());
585 
586     _mouseX = x;
587     _mouseY = y;
588     return notify_mouse_listeners(event_id(event_id::MOUSE_MOVE));
589 }
590 
591 
592 bool
keyEvent(key::code k,bool down)593 movie_root::keyEvent(key::code k, bool down)
594 {
595     _lastKeyEvent = k;
596     const size_t keycode = key::codeMap[k][key::KEY];
597     if (keycode < key::KEYCOUNT) {
598         _unreleasedKeys.set(keycode, down);
599     }
600 
601     LiveChars copy = _liveChars;
602     for (MovieClip* const ch : copy) {
603 
604         if (ch->unloaded()) continue;
605 
606         if (down) {
607             ch->notifyEvent(event_id(event_id::KEY_DOWN, key::INVALID));
608             ch->notifyEvent(event_id(event_id::KEY_PRESS, k));
609         }
610         else {
611             ch->notifyEvent(event_id(event_id::KEY_UP, key::INVALID));
612         }
613     }
614 
615     // Broadcast event to Key._listeners.
616     as_object* key = getBuiltinObject(*this, getURI(_vm, NSV::CLASS_KEY));
617     if (key) {
618 
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");
626         }
627         catch (const ActionLimitException &e) {
628             log_error(_("ActionLimits hit notifying key listeners: %s."),
629                     e.what());
630             clear(_actionQueue);
631         }
632     }
633 
634     if (down) {
635 
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);
644             }
645         }
646 
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);
651     }
652 
653     processActionQueue();
654 
655     return false;
656 }
657 
658 bool
mouseWheel(int delta)659 movie_root::mouseWheel(int delta)
660 {
661     as_object* mouseObj =
662         getBuiltinObject(*this, getURI(_vm, NSV::CLASS_MOUSE));
663     if (!mouseObj) return false;
664 
665     const std::int32_t x = pixelsToTwips(_mouseX);
666     const std::int32_t y = pixelsToTwips(_mouseY);
667 
668     DisplayObject* i = getTopmostMouseEntity(x, y);
669 
670     // Always called with two arguments.
671     callMethod(mouseObj, getURI(_vm,NSV::PROP_BROADCAST_MESSAGE), "onMouseWheel",
672             delta, i ? getObject(i) : as_value());
673 
674     return true;
675 }
676 
677 bool
mouseClick(bool mouse_pressed)678 movie_root::mouseClick(bool mouse_pressed)
679 {
680     assert(testInvariant());
681 
682     _mouseButtonState.isDown = mouse_pressed;
683 
684     if (mouse_pressed) {
685         return notify_mouse_listeners(event_id(event_id::MOUSE_DOWN));
686     }
687     return notify_mouse_listeners(event_id(event_id::MOUSE_UP));
688 }
689 
690 
691 bool
fire_mouse_event()692 movie_root::fire_mouse_event()
693 {
694     assert(testInvariant());
695 
696     std::int32_t x = pixelsToTwips(_mouseX);
697     std::int32_t y = pixelsToTwips(_mouseY);
698 
699     // Generate a mouse event
700     _mouseButtonState.topmostEntity = getTopmostMouseEntity(x, y);
701 
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());
714             }
715             else dragging->setDropTarget("");
716         }
717     }
718 
719     bool need_redraw = false;
720 
721     // FIXME: need_redraw might also depend on actual
722     //        actions execution (consider updateAfterEvent).
723 
724     try {
725         need_redraw = generate_mouse_button_events(*this, _mouseButtonState);
726         processActionQueue();
727     }
728     catch (const ActionLimitException& al) {
729         handleActionLimitHit(al.what());
730     }
731 
732     return need_redraw;
733 }
734 
735 std::pair<std::int32_t, std::int32_t>
mousePosition() const736 movie_root::mousePosition() const
737 {
738     assert(testInvariant());
739     return std::make_pair(_mouseX, _mouseY);
740 }
741 
742 void
setDragState(const DragState & st)743 movie_root::setDragState(const DragState& st)
744 {
745     _dragState = st;
746 
747     DisplayObject* ch = _dragState->getCharacter();
748 
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);
755 
756         // Get current mouse coordinates
757         const point world_mouse(pixelsToTwips(_mouseX), pixelsToTwips(_mouseY));
758 
759         std::int32_t xoffset = world_mouse.x - world_origin.x;
760         std::int32_t yoffset = world_mouse.y - world_origin.y;
761 
762         _dragState->setOffset(xoffset, yoffset);
763     }
764     assert(testInvariant());
765 }
766 
767 void
doMouseDrag()768 movie_root::doMouseDrag()
769 {
770     DisplayObject* dragChar = getDraggingCharacter();
771     if (!dragChar) return; // nothing to do
772 
773     if (dragChar->unloaded()) {
774         // Reset drag state if dragging char was unloaded
775         _dragState.reset();
776         return;
777     }
778 
779     point world_mouse(pixelsToTwips(_mouseX), pixelsToTwips(_mouseY));
780 
781     SWFMatrix parent_world_mat;
782     DisplayObject* p = dragChar->parent();
783     if (p) {
784         parent_world_mat = getWorldMatrix(*p);
785     }
786 
787     if (!_dragState->isLockCentered()) {
788         world_mouse.x -= _dragState->xOffset();
789         world_mouse.y -= _dragState->yOffset();
790     }
791 
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);
799     }
800 
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);
807 
808     // no need to update caches when only changing translation
809     dragChar->setMatrix(local);
810 }
811 
812 std::uint32_t
addIntervalTimer(std::unique_ptr<Timer> timer)813 movie_root::addIntervalTimer(std::unique_ptr<Timer> timer)
814 {
815     assert(timer.get());
816     assert(testInvariant());
817 
818     const size_t id = ++_lastTimerId;
819 
820     assert(_intervalTimers.find(id) == _intervalTimers.end());
821 
822     _intervalTimers.insert(std::make_pair(id, std::move(timer)));
823 
824     return id;
825 }
826 
827 bool
clearIntervalTimer(std::uint32_t x)828 movie_root::clearIntervalTimer(std::uint32_t x)
829 {
830     TimerMap::iterator it = _intervalTimers.find(x);
831     if (it == _intervalTimers.end()) {
832         return false;
833     }
834 
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();
843 
844     return true;
845 }
846 
847 bool
advance()848 movie_root::advance()
849 {
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);
854 
855     bool advanced = false;
856 
857     try {
858 
859 #ifdef USE_SOUND
860         sound::sound_handler* s = _runResources.soundHandler();
861 
862         if (s && _timelineSound) {
863 
864             if (!s->streamingSound()) {
865                 log_error(_("movie_root tracking a streaming sound, but the sound handler is not streaming!"));
866 
867                 // Give up; we've probably failed to catch up.
868                 _timelineSound.reset();
869             } else {
870 
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);
874 
875                 const int startBlock = _timelineSound->block;
876 
877                 const size_t maxTime = getTimeoutLimit() * 1000;
878                 SystemClock clock;
879 
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();
886 
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;
893                     }
894 
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;
901 
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;
908                         }
909                     }
910 
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);
920 
921                 }
922                 if (advanced) {
923                     _lastMovieAdvancement = now;
924                 }
925             }
926         }
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;
935             }
936 #ifdef USE_SOUND
937         }
938 #endif  // USE_SOUND
939 
940         // Always do this.
941         executeAdvanceCallbacks();
942         executeTimers();
943 
944     }
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());
950     }
951     catch (const ActionParserException& e) {
952         log_error(_("Buffer overread during advance: %s"), e.what());
953         clear(_actionQueue);
954     }
955 
956     return advanced;
957 }
958 
959 void
advanceMovie()960 movie_root::advanceMovie()
961 {
962     // Do mouse drag, if needed
963     doMouseDrag();
964 
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();
969 
970     // Process loadMovie requests
971     //
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
977     //
978     _movieLoader.processCompletedRequests();
979 
980     // Process queued actions
981     // NOTE: can throw ActionLimitException
982     processActionQueue();
983 
984     cleanupAndCollect();
985 
986     assert(testInvariant());
987 }
988 
989 int
timeToNextFrame() const990 movie_root::timeToNextFrame() const
991 {
992     unsigned int now = _vm.getTime();
993     const int elapsed = now - _lastMovieAdvancement;
994     return _movieAdvancementDelay - elapsed;
995 }
996 
997 void
display()998 movie_root::display()
999 {
1000     // GNASH_REPORT_FUNCTION;
1001 
1002     assert(testInvariant());
1003 
1004     clearInvalidated();
1005 
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() )
1009     {
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;
1014     }
1015 
1016     Renderer* renderer = _runResources.renderer();
1017     if (!renderer) return;
1018 
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());
1023 
1024     for (auto& elem : _movies) {
1025         MovieClip* movie = elem.second;
1026 
1027         movie->clear_invalidated();
1028 
1029         if (movie->visible() == false) continue;
1030 
1031         // null frame size ? don't display !
1032         const SWFRect& sub_frame_size = movie->get_frame_size();
1033 
1034         if (sub_frame_size.is_null()) {
1035             log_debug("_level%u has null frame size, skipping", elem.first);
1036             continue;
1037         }
1038 
1039         movie->display(*renderer, Transform());
1040     }
1041 }
1042 
1043 bool
notify_mouse_listeners(const event_id & event)1044 movie_root::notify_mouse_listeners(const event_id& event)
1045 {
1046     LiveChars copy = _liveChars;
1047     for (MovieClip* const ch : copy)
1048     {
1049         if (!ch->unloaded()) {
1050             ch->mouseEvent(event);
1051         }
1052     }
1053 
1054     const ObjectURI& propMouse = getURI(_vm, NSV::CLASS_MOUSE);
1055     const ObjectURI& propBroadcastMessage =
1056         getURI(_vm, NSV::PROP_BROADCAST_MESSAGE);
1057 
1058     as_object* mouseObj = getBuiltinObject(*this, propMouse);
1059     if (mouseObj) {
1060 
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());
1066         }
1067         catch (const ActionLimitException& e) {
1068             log_error(_("ActionLimits hit notifying mouse events: %s."),
1069                     e.what());
1070             clear(_actionQueue);
1071         }
1072     }
1073 
1074     assert(testInvariant());
1075 
1076     if (!copy.empty()) {
1077         // process actions queued in the above step
1078         processActionQueue();
1079     }
1080     return fire_mouse_event();
1081 }
1082 
1083 DisplayObject*
getFocus()1084 movie_root::getFocus()
1085 {
1086     return _currentFocus;
1087 }
1088 
1089 bool
setFocus(DisplayObject * to)1090 movie_root::setFocus(DisplayObject* to)
1091 {
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;
1097     }
1098 
1099     if (to && !to->handleFocus()) {
1100         // TODO: not clear whether to remove focus in this case.
1101         return false;
1102     }
1103 
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.
1107 
1108     // Store previous focus, as the focus needs to change before onSetFocus
1109     // is called and listeners are notified.
1110     DisplayObject* from = _currentFocus;
1111 
1112     if (from) {
1113         // Perform any actions required on killing focus (only TextField).
1114         from->killFocus();
1115 
1116         /// A valid focus must have an associated object.
1117         assert(getObject(from));
1118         callMethod(getObject(from), NSV::PROP_ON_KILL_FOCUS, getObject(to));
1119     }
1120 
1121     _currentFocus = to;
1122 
1123     if (to) {
1124         assert(getObject(to));
1125         callMethod(getObject(to), NSV::PROP_ON_SET_FOCUS, getObject(from));
1126     }
1127 
1128     as_object* sel = getBuiltinObject(*this, NSV::CLASS_SELECTION);
1129 
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));
1135     }
1136 
1137     assert(testInvariant());
1138 
1139     return true;
1140 }
1141 
1142 DisplayObject*
getActiveEntityUnderPointer() const1143 movie_root::getActiveEntityUnderPointer() const
1144 {
1145     return _mouseButtonState.activeEntity;
1146 }
1147 
1148 DisplayObject*
getDraggingCharacter() const1149 movie_root::getDraggingCharacter() const
1150 {
1151     return _dragState ? _dragState->getCharacter() : nullptr;
1152 }
1153 
1154 const DisplayObject*
getEntityUnderPointer() const1155 movie_root::getEntityUnderPointer() const
1156 {
1157     const std::int32_t x = pixelsToTwips(_mouseX);
1158     const std::int32_t y = pixelsToTwips(_mouseY);
1159     return findDropTarget(x, y, getDraggingCharacter());
1160 }
1161 
1162 
1163 void
setQuality(Quality q)1164 movie_root::setQuality(Quality q)
1165 {
1166     gnash::RcInitFile& rcfile = gnash::RcInitFile::getDefaultInstance();
1167 
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);
1173     }
1174 
1175     if ( _quality != q )
1176     {
1177         // Force a redraw if quality changes
1178         //
1179         // redraw should only happen on next
1180         // frame advancement (tested)
1181         //
1182         setInvalidated();
1183 
1184         _quality = q;
1185     }
1186 
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);
1191 
1192 }
1193 
1194 /// Get actionscript width of stage, in pixels. The width
1195 /// returned depends on the scale mode.
1196 size_t
getStageWidth() const1197 movie_root::getStageWidth() const
1198 {
1199     if (_scaleMode == SCALEMODE_NOSCALE) {
1200         return _stageWidth;
1201     }
1202 
1203     // If scaling is allowed, always return the original movie size.
1204     if (_rootMovie) {
1205         return static_cast<size_t>(_rootMovie->widthPixels());
1206     }
1207     return 0;
1208 }
1209 
1210 /// Get actionscript height of stage, in pixels. The height
1211 /// returned depends on the scale mode.
1212 size_t
getStageHeight() const1213 movie_root::getStageHeight() const
1214 {
1215     if (_scaleMode == SCALEMODE_NOSCALE) {
1216         return _stageHeight;
1217     }
1218 
1219     // If scaling is allowed, always return the original movie size.
1220     if (_rootMovie) {
1221         return static_cast<size_t>(_rootMovie->heightPixels());
1222     }
1223     return 0;
1224 }
1225 
1226 /// Takes a short int bitfield: the four bits correspond
1227 /// to the AlignMode enum
1228 void
setStageAlignment(short s)1229 movie_root::setStageAlignment(short s)
1230 {
1231     _alignMode = s;
1232     callInterface(HostMessage(HostMessage::UPDATE_STAGE));
1233 }
1234 
1235 /// The mode is one of never, always, with sameDomain the default
1236 void
setAllowScriptAccess(AllowScriptAccessMode mode)1237 movie_root::setAllowScriptAccess(AllowScriptAccessMode mode)
1238 {
1239     _allowScriptAccess = mode;
1240 }
1241 
1242 movie_root::AllowScriptAccessMode
getAllowScriptAccess()1243 movie_root::getAllowScriptAccess()
1244 {
1245     return _allowScriptAccess;
1246 }
1247 
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
getStageAlignment() const1251 movie_root::getStageAlignment() const
1252 {
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;
1257 
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;
1262 
1263     return std::make_pair(ha, va);
1264 }
1265 
1266 /// Returns a string that represents the boolean state of the _showMenu
1267 /// variable
1268 bool
getShowMenuState() const1269 movie_root::getShowMenuState() const
1270 {
1271     return _showMenu;
1272 }
1273 
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
setShowMenuState(bool state)1277 movie_root::setShowMenuState(bool state)
1278 {
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));
1286 }
1287 
1288 /// Returns the string representation of the current align mode,
1289 /// which must always be in the order: LTRB
1290 std::string
getStageAlignMode() const1291 movie_root::getStageAlignMode() const
1292 {
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');
1298 
1299     return align;
1300 }
1301 
1302 void
setStageScaleMode(ScaleMode sm)1303 movie_root::setStageScaleMode(ScaleMode sm)
1304 {
1305     if (_scaleMode == sm) return; // nothing to do
1306 
1307     bool notifyResize = false;
1308 
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)) {
1316 
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());
1321 
1322         if ( _stageWidth != md->get_width_pixels()
1323              || _stageHeight != md->get_height_pixels() )
1324         {
1325             notifyResize = true;
1326         }
1327     }
1328 
1329     _scaleMode = sm;
1330     callInterface(HostMessage(HostMessage::UPDATE_STAGE));
1331 
1332     if (notifyResize) {
1333         as_object* stage = getBuiltinObject(*this, NSV::CLASS_STAGE);
1334         if (stage) {
1335             callMethod(stage, NSV::PROP_BROADCAST_MESSAGE, "onResize");
1336         }
1337     }
1338 }
1339 
1340 void
setStageDisplayState(const DisplayState ds)1341 movie_root::setStageDisplayState(const DisplayState ds)
1342 {
1343     _displayState = ds;
1344 
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);
1349     }
1350 
1351     if (!_interfaceHandler) return; // No registered callback
1352 
1353     HostMessage e(HostMessage::SET_DISPLAYSTATE, _displayState);
1354     callInterface(e);
1355 }
1356 
1357 void
add_invalidated_bounds(InvalidatedRanges & ranges,bool force)1358 movie_root::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
1359 {
1360     if (isInvalidated()) {
1361         ranges.setWorld();
1362         return;
1363     }
1364 
1365     for (Levels::reverse_iterator i=_movies.rbegin(), e=_movies.rend(); i!=e;
1366                         ++i) {
1367         i->second->add_invalidated_bounds(ranges, force);
1368     }
1369 }
1370 
1371 size_t
minPopulatedPriorityQueue() const1372 movie_root::minPopulatedPriorityQueue() const
1373 {
1374     for (size_t l = 0; l < PRIORITY_SIZE; ++l) {
1375         if (!_actionQueue[l].empty()) return l;
1376     }
1377     return PRIORITY_SIZE;
1378 }
1379 
1380 size_t
processActionQueue(size_t lvl)1381 movie_root::processActionQueue(size_t lvl)
1382 {
1383     ActionQueue::value_type& q = _actionQueue[lvl];
1384 
1385     assert(minPopulatedPriorityQueue() == lvl);
1386 
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);
1394     }
1395 #endif
1396 
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()) {
1401 
1402         const std::unique_ptr<ExecutableCode> code(q.pop_front().release());
1403         code->execute();
1404 
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;
1412         }
1413     }
1414 
1415     assert(q.empty());
1416 
1417 #ifdef GNASH_DEBUG
1418     if (actionsToProcess) {
1419         log_debug("Done processing actions in priority queue %d (call %u)",
1420                     lvl, calls);
1421     }
1422 #endif
1423 
1424     return minPopulatedPriorityQueue();
1425 }
1426 
1427 void
flushHigherPriorityActionQueues()1428 movie_root::flushHigherPriorityActionQueues()
1429 {
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;
1437     }
1438 
1439     if (_disableScripts) {
1440         /// cleanup anything pushed later..
1441         clear(_actionQueue);
1442         return;
1443     }
1444 
1445     int lvl = minPopulatedPriorityQueue();
1446     while (lvl < _processingActionLevel) {
1447         lvl = processActionQueue(lvl);
1448     }
1449 }
1450 
1451 void
addLoadableObject(as_object * obj,std::unique_ptr<IOChannel> str)1452 movie_root::addLoadableObject(as_object* obj, std::unique_ptr<IOChannel> str)
1453 {
1454     _loadCallbacks.emplace_back(std::move(str), obj);
1455 }
1456 
1457 void
addAdvanceCallback(ActiveRelay * obj)1458 movie_root::addAdvanceCallback(ActiveRelay* obj)
1459 {
1460     _objectCallbacks.insert(obj);
1461 }
1462 
1463 void
removeAdvanceCallback(ActiveRelay * obj)1464 movie_root::removeAdvanceCallback(ActiveRelay* obj)
1465 {
1466     _objectCallbacks.erase(obj);
1467 }
1468 
1469 void
processActionQueue()1470 movie_root::processActionQueue()
1471 {
1472     if (_disableScripts) {
1473         /// cleanup anything pushed later..
1474         clear(_actionQueue);
1475         return;
1476     }
1477 
1478     _processingActionLevel = minPopulatedPriorityQueue();
1479 
1480     while (_processingActionLevel < PRIORITY_SIZE) {
1481         _processingActionLevel = processActionQueue(_processingActionLevel);
1482     }
1483 
1484     // Cleanup the stack.
1485     _vm.getStack().clear();
1486 }
1487 
1488 void
removeQueuedConstructor(MovieClip * target)1489 movie_root::removeQueuedConstructor(MovieClip* target)
1490 {
1491     ActionQueue::value_type& pr = _actionQueue[PRIORITY_CONSTRUCT];
1492     pr.erase_if(RemoveTargetCode(target));
1493 }
1494 
1495 void
pushAction(std::unique_ptr<ExecutableCode> code,size_t lvl)1496 movie_root::pushAction(std::unique_ptr<ExecutableCode> code, size_t lvl)
1497 {
1498     assert(lvl < PRIORITY_SIZE);
1499     _actionQueue[lvl].push_back(code.release());
1500 }
1501 
1502 void
pushAction(const action_buffer & buf,DisplayObject * target)1503 movie_root::pushAction(const action_buffer& buf, DisplayObject* target)
1504 {
1505 #ifdef GNASH_DEBUG
1506     log_debug("Pushed action buffer for target %s",
1507             target->getTargetPath());
1508 #endif
1509 
1510     std::unique_ptr<ExecutableCode> code(new GlobalCode(buf, target));
1511 
1512     _actionQueue[PRIORITY_DOACTION].push_back(code.release());
1513 }
1514 
1515 void
executeAdvanceCallbacks()1516 movie_root::executeAdvanceCallbacks()
1517 {
1518     if (!_objectCallbacks.empty()) {
1519 
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!
1529         //
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;
1535 
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)));
1541 
1542         std::for_each(currentCallbacks.begin(), currentCallbacks.end(),
1543                 ExecuteCallback());
1544     }
1545 
1546     if (!_loadCallbacks.empty()) {
1547         _loadCallbacks.remove_if(
1548                 std::mem_fun_ref(&movie_root::LoadCallback::processLoad));
1549     }
1550 
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);
1562                 }
1563             }
1564         }
1565     }
1566 
1567     processActionQueue();
1568 }
1569 
1570 bool
processInvoke(ExternalInterface::invoke_t * invoke)1571 movie_root::processInvoke(ExternalInterface::invoke_t *invoke)
1572 {
1573     GNASH_REPORT_FUNCTION;
1574 
1575     if (!invoke || invoke->name.empty()) return false;
1576 
1577     log_debug("Processing %s call from the Browser.", invoke->name);
1578 
1579     std::stringstream ss;       // ss is the response string
1580 
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();
1586 
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;
1615         }
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;
1645         }
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;
1684     }
1685 
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));
1694             }
1695         }
1696     } else {
1697         log_debug("No response needed for %s request", invoke->name);
1698     }
1699 
1700     return true;
1701 }
1702 
1703 void
executeTimers()1704 movie_root::executeTimers()
1705 {
1706 #ifdef GNASH_DEBUG_TIMERS_EXPIRATION
1707     log_debug("Checking %d timers for expiry", _intervalTimers.size());
1708 #endif
1709 
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;
1714     }
1715 
1716     unsigned long now = _vm.getTime();
1717 
1718     typedef std::multimap<unsigned long, Timer*>
1719         ExpiredTimers;
1720 
1721     ExpiredTimers expiredTimers;
1722 
1723     for (TimerMap::iterator it = _intervalTimers.begin(),
1724             itEnd = _intervalTimers.end(); it != itEnd; ) {
1725 
1726         TimerMap::iterator nextIterator = it;
1727         ++nextIterator;
1728 
1729         Timer* timer = it->second.get();
1730 
1731         if (timer->cleared()) {
1732             // this timer was cleared, erase it
1733             _intervalTimers.erase(it);
1734         }
1735         else {
1736             unsigned long elapsed;
1737             if (timer->expired(now, elapsed)) {
1738                 expiredTimers.insert(std::make_pair(elapsed, timer));
1739             }
1740         }
1741 
1742         it = nextIterator;
1743     }
1744 
1745     foreachSecond(expiredTimers.begin(), expiredTimers.end(),
1746                   &Timer::executeAndReset);
1747 
1748     if (!expiredTimers.empty())
1749         processActionQueue();
1750 }
1751 
1752 void
markReachableResources() const1753 movie_root::markReachableResources() const
1754 {
1755     _vm.markReachableResources();
1756 
1757     foreachSecond(_movies.rbegin(), _movies.rend(), &MovieClip::setReachable);
1758 
1759     // Mark original top-level movie
1760     // This should always be in _movies, but better make sure
1761     assert(_rootMovie);
1762     _rootMovie->setReachable();
1763 
1764     // Mark mouse entities
1765     _mouseButtonState.markReachableResources();
1766 
1767     // Mark timer targets
1768     foreachSecond(_intervalTimers.begin(), _intervalTimers.end(),
1769                   &Timer::markReachableResources);
1770 
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));
1775 
1776     // Mark LoadMovieRequest handlers as reachable
1777     _movieLoader.setReachable();
1778 
1779     // Mark ExternalInterface callbacks and instances as reachable
1780     for (const auto& method : _externalCallbackMethods) {
1781         if (method.second) {
1782             method.second->setReachable();
1783         }
1784     }
1785     for (const auto& instance : _externalCallbackInstances) {
1786         if (instance.second) {
1787             instance.second->setReachable();
1788         }
1789     }
1790 
1791     // Mark resources reachable by queued action code
1792     for (size_t lvl = 0; lvl < PRIORITY_SIZE; ++lvl)
1793     {
1794         const ActionQueue::value_type& q = _actionQueue[lvl];
1795         std::for_each(q.begin(), q.end(),
1796                 std::mem_fun_ref(&ExecutableCode::markReachableResources));
1797     }
1798 
1799     if (_currentFocus) _currentFocus->setReachable();
1800 
1801     // Mark DisplayObject being dragged, if any
1802     if (_dragState) _dragState->markReachableResources();
1803 
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
1815     }
1816 #endif
1817 
1818     foreachSecond(_registeredClasses.begin(), _registeredClasses.end(), &as_function::setReachable);
1819 }
1820 
1821 InteractiveObject*
getTopmostMouseEntity(std::int32_t x,std::int32_t y) const1822 movie_root::getTopmostMouseEntity(std::int32_t x, std::int32_t y) const
1823 {
1824     for (Levels::const_reverse_iterator i=_movies.rbegin(), e=_movies.rend();
1825             i != e; ++i)
1826     {
1827         InteractiveObject* ret = i->second->topmostMouseEntity(x, y);
1828         if (ret) return ret;
1829     }
1830 
1831     return nullptr;
1832 }
1833 
1834 const DisplayObject *
findDropTarget(std::int32_t x,std::int32_t y,DisplayObject * dragging) const1835 movie_root::findDropTarget(std::int32_t x, std::int32_t y,
1836         DisplayObject* dragging) const
1837 {
1838     for (Levels::const_reverse_iterator i=_movies.rbegin(), e=_movies.rend();
1839             i!=e; ++i) {
1840 
1841         const DisplayObject* ret = i->second->findDropTarget(x, y, dragging);
1842         if (ret) return ret;
1843     }
1844     return nullptr;
1845 }
1846 
1847 /// This should store a callback object in movie_root.
1848 void
addExternalCallback(const std::string & name,as_object * callback,as_object * instance)1849 movie_root::addExternalCallback(const std::string& name, as_object* callback,
1850                                 as_object* instance)
1851 {
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);
1857     }
1858     _externalCallbackMethods.insert(
1859         std::pair<std::string, as_object*>(name,callback)
1860     );
1861     _externalCallbackInstances.insert(
1862         std::pair<std::string, as_object*>(name,instance)
1863     );
1864 
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);
1871 
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));
1876         }
1877     }
1878 }
1879 
1880 /// This calls a JavaScript method in the web page
1881 ///
1882 /// @example "ExternalInterace::call message"
1883 ///
1884 /// <pre>
1885 /// <invoke name="methodname" returntype="xml">
1886 ///      <arguments></arguments>
1887 ///             ...
1888 ///      <arguments></arguments>
1889 /// </invoke>
1890 ///
1891 /// May return any supported type like Number or String in XML format.
1892 ///
1893 /// </pre>
1894 std::string
callExternalJavascript(const std::string & name,const std::vector<as_value> & fnargs)1895 movie_root::callExternalJavascript(const std::string &name,
1896                                    const std::vector<as_value> &fnargs)
1897 {
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);
1903 
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);
1912         }
1913     }
1914 
1915     return result;
1916 }
1917 
1918 // Call one of the registered callbacks, and return the result to
1919 // Javascript in the browser.
1920 std::string
callExternalCallback(const std::string & name,const std::vector<as_value> & fnargs)1921 movie_root::callExternalCallback(const std::string &name,
1922                  const std::vector<as_value> &fnargs)
1923 {
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;
1930 
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;
1937 
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());
1942         }
1943         else instance = instance_iterator->second;
1944 
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);
1949         }
1950 
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 ++)
1956         {
1957             args += *args_iterator;
1958         }
1959 
1960         // Call the registered callback
1961         val=invoke(as_value(method), as_environment(getVM()), instance, args);
1962     }
1963 
1964     std::string result;
1965     result = ExternalInterface::toXML(val);
1966 
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;
1972 
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));
1979         }
1980     }
1981 
1982     return result;
1983 }
1984 
1985 void
removeButton(Button * listener)1986 movie_root::removeButton(Button* listener)
1987 {
1988     _buttonListeners.remove(listener);
1989 }
1990 
1991 void
registerButton(Button * listener)1992 movie_root::registerButton(Button* listener)
1993 {
1994     if (std::find(_buttonListeners.begin(), _buttonListeners.end(), listener)
1995             != _buttonListeners.end()) {
1996         return;
1997     }
1998     _buttonListeners.push_front(listener);
1999 }
2000 
2001 void
cleanupDisplayList()2002 movie_root::cleanupDisplayList()
2003 {
2004 //#define GNASH_DEBUG_INSTANCE_LIST 1
2005 
2006 #ifdef GNASH_DEBUG_INSTANCE_LIST
2007     static size_t maxLiveChars = 0;
2008 #endif
2009 
2010     // Let every sprite cleanup the local DisplayList
2011     //
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);
2026 
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...
2036     //
2037 
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;
2048 
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()) {
2057 
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;
2066                 }
2067 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2068                 else {
2069                     cout << ch->getTarget() << "(" << typeName(*ch) <<
2070                         ") was unloaded and destroyed" << endl;
2071                 }
2072 #endif
2073 
2074 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2075                 cleaned++;
2076 #endif
2077                 return true;
2078             }
2079             return false;
2080         });
2081 
2082 #ifdef GNASH_DEBUG_DLIST_CLEANUP
2083         cout << " Scan " << scansCount << " cleaned " << cleaned <<
2084             " instances" << endl;
2085 #endif
2086     } while (needScan);
2087 
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);
2093     }
2094 #endif
2095 }
2096 
2097 void
advanceLiveChars()2098 movie_root::advanceLiveChars()
2099 {
2100 #ifdef GNASH_DEBUG
2101     log_debug("---- movie_root::advance: %d live DisplayObjects in the global list",
2102                 _liveChars.size());
2103 #endif
2104 
2105     // Advance all characters, then notify them.
2106     LiveChars::iterator it;
2107     for (MovieClip* liveChar : _liveChars) {
2108         advanceLiveChar(liveChar);
2109     }
2110     for (MovieClip* liveChar : _liveChars) {
2111         notifyLoad(liveChar);
2112     }
2113 }
2114 
2115 void
set_background_color(const rgba & color)2116 movie_root::set_background_color(const rgba& color)
2117 {
2118     if (m_background_color_set) return;
2119     m_background_color_set = true;
2120 
2121     rgba newcolor = color;
2122     newcolor.m_a = m_background_color.m_a;
2123 
2124     if (m_background_color != newcolor) {
2125         setInvalidated();
2126         m_background_color = newcolor;
2127     }
2128 }
2129 
2130 void
set_background_alpha(float alpha)2131 movie_root::set_background_alpha(float alpha)
2132 {
2133     const std::uint8_t newAlpha = clamp<int>(frnd(alpha * 255.0f), 0, 255);
2134 
2135     if (m_background_color.m_a != newAlpha) {
2136         setInvalidated();
2137         m_background_color.m_a = newAlpha;
2138     }
2139 }
2140 
2141 DisplayObject*
findCharacterByTarget(const std::string & tgtstr) const2142 movie_root::findCharacterByTarget(const std::string& tgtstr) const
2143 {
2144     if (tgtstr.empty()) return nullptr;
2145 
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);
2152 
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);
2156 
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);
2162 
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;
2169         }
2170         if (to == std::string::npos) break;
2171         from = to + 1;
2172     }
2173     return get<DisplayObject>(o);
2174 }
2175 
2176 void
getURL(const std::string & urlstr,const std::string & target,const std::string & data,MovieClip::VariablesMethod method)2177 movie_root::getURL(const std::string& urlstr, const std::string& target,
2178         const std::string& data, MovieClip::VariablesMethod method)
2179 {
2180     log_network("%s: HOSTFD is %d",  __FUNCTION__, _hostfd);
2181 
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());
2187 
2188         gnash::RcInitFile& rcfile = gnash::RcInitFile::getDefaultInstance();
2189         std::string command = rcfile.getURLOpenerFormat();
2190 
2191         /// Try to avoid letting flash movies execute
2192         /// arbitrary commands (sic).
2193         ///
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         ///
2199         ///
2200         /// Check safety of user provided command
2201         ///
2202         /// TODO: improve this check
2203         ///       - quote nested in double quote
2204         ///       - %u after second quote
2205         ///       - use regexp ?
2206         /// TODO: check only once
2207         ///
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);
2218 
2219         if ( ! command_is_safe ) {
2220             log_error("The '%%u' token in urlOpenerFormat rc directive should be within single quotes");
2221             return;
2222         }
2223 
2224         std::string safeurl = urlstr;
2225         boost::replace_all(safeurl, "'", "'\\''");
2226 
2227         boost::replace_all(command, "%u", safeurl);
2228 
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);
2233         }
2234         return;
2235     }
2236 
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);
2241 
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;
2254     }
2255 
2256     // The third argument is the target, which is something like _blank
2257     // or _self.
2258     if (!target.empty()) {
2259         fnargs.emplace_back(target);
2260     }
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");
2266         }
2267         fnargs.emplace_back(data);
2268     }
2269 
2270     // TODO: should mutex-protect this ?
2271     // NOTE: we are assuming the hostfd is set in blocking mode here..
2272 
2273     log_debug("Attempt to write geturl requests fd #%d", _hostfd);
2274 
2275     std::string msg = ExternalInterface::makeInvoke("getURL", fnargs);
2276 
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);
2281     }
2282 }
2283 
2284 void
setScriptLimits(std::uint16_t recursion,std::uint16_t timeout)2285 movie_root::setScriptLimits(std::uint16_t recursion, std::uint16_t timeout)
2286 {
2287     if ( recursion == _recursionLimit && _timeoutLimit == timeout ) {
2288         // avoid the debug log...
2289         return;
2290     }
2291 
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;
2297     }
2298 
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);
2304 
2305     _recursionLimit = recursion;
2306     _timeoutLimit = timeout;
2307 
2308 }
2309 
2310 
2311 #ifdef USE_SWFTREE
2312 void
getMovieInfo(InfoTree & tr,InfoTree::iterator it)2313 movie_root::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
2314 {
2315     // Stage
2316     const movie_definition* def = _rootMovie->definition();
2317     assert(def);
2318 
2319     it = tr.insert(it, std::make_pair("Stage Properties", ""));
2320 
2321     InfoTree::iterator localIter =  tr.append_child(it,
2322             std::make_pair("Root VM version",
2323                 def->isAS3() ? "AVM2 (unsupported)" : "AVM1"));
2324 
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()));
2330 
2331     // Is there a sychronizating sound or not?
2332     localIter = tr.append_child(it, std::make_pair("Streaming sound",
2333                 _timelineSound ? "yes" : "no"));
2334 
2335     // TODO: format this better?
2336     localIter = tr.append_child(it, std::make_pair("Descriptive metadata",
2337                                         def->getDescriptiveMetadata()));
2338 
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()));
2345 
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()));
2351 
2352     // Stage: scripts state (enabled/disabled)
2353     localIter = tr.append_child(it, std::make_pair("Scripts",
2354                 _disableScripts ? " disabled" : "enabled"));
2355 
2356     getCharacterTree(tr, it);
2357 }
2358 
2359 void
getCharacterTree(InfoTree & tr,InfoTree::iterator it)2360 movie_root::getCharacterTree(InfoTree& tr, InfoTree::iterator it)
2361 {
2362     InfoTree::iterator localIter;
2363 
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()));
2369 
2370     /// DisplayObject tree
2371     for (Levels::const_iterator i = _movies.begin(), e = _movies.end();
2372             i != e; ++i) {
2373         i->second->getMovieInfo(tr, localIter);
2374     }
2375 }
2376 
2377 #endif
2378 
2379 void
handleFsCommand(const std::string & cmd,const std::string & arg) const2380 movie_root::handleFsCommand(const std::string& cmd, const std::string& arg)
2381     const
2382 {
2383     if (_fsCommandHandler) _fsCommandHandler->notify(cmd, arg);
2384 }
2385 
2386 bool
isLevelTarget(int version,const std::string & name,unsigned int & levelno)2387 isLevelTarget(int version, const std::string& name, unsigned int& levelno)
2388 {
2389     if (version > 6) {
2390         if (name.compare(0, 6, "_level")) return false;
2391     }
2392     else {
2393         StringNoCaseEqual noCaseCmp;
2394         if (!noCaseCmp(name.substr(0, 6), "_level")) return false;
2395     }
2396 
2397     if (name.find_first_not_of("0123456789", 7) != std::string::npos) {
2398         return false;
2399     }
2400     // getting 0 here for "_level" is intentional
2401     levelno = std::strtoul(name.c_str() + 6, nullptr, 0);
2402     return true;
2403 }
2404 
2405 short
stringToStageAlign(const std::string & str)2406 stringToStageAlign(const std::string& str)
2407 {
2408     short am = 0;
2409 
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;
2414     }
2415 
2416     if (str.find_first_of("tT") != std::string::npos) {
2417         am |= 1 << movie_root::STAGE_ALIGN_T;
2418     }
2419 
2420     if (str.find_first_of("rR") != std::string::npos) {
2421         am |= 1 << movie_root::STAGE_ALIGN_R;
2422     }
2423 
2424     if (str.find_first_of("bB") != std::string::npos) {
2425         am |= 1 << movie_root::STAGE_ALIGN_B;
2426     }
2427 
2428     return am;
2429 
2430 }
2431 
2432 void
setReachable() const2433 movie_root::LoadCallback::setReachable() const
2434 {
2435     _obj->setReachable();
2436 }
2437 
2438 bool
processLoad()2439 movie_root::LoadCallback::processLoad()
2440 {
2441     if (!_stream) {
2442         callMethod(_obj, NSV::PROP_ON_DATA, as_value());
2443         return true;
2444     }
2445 
2446     const size_t chunksize = 65535;
2447     std::uint8_t chunk[chunksize];
2448 
2449     size_t actuallyRead = _stream->readNonBlocking(chunk, chunksize);
2450 
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;
2456     }
2457 
2458     if (actuallyRead) {
2459 
2460         // set total size only on first read
2461         if (_buf.empty()) {
2462             _obj->set_member(NSV::PROP_uBYTES_TOTAL, _stream->size());
2463         }
2464 
2465         _buf.append(chunk, actuallyRead);
2466 
2467         _obj->set_member(NSV::PROP_uBYTES_LOADED, _buf.size());
2468 
2469         log_debug("LoadableObject Loaded %d bytes, reaching %d/%d",
2470             actuallyRead, _buf.size(), _stream->size());
2471     }
2472 
2473     // We haven't finished till EOF
2474     if (!_stream->eof()) return false;
2475 
2476     log_debug("LoadableObject reached EOF (%d/%d loaded)",
2477                 _buf.size(), _stream->size());
2478 
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;
2483     }
2484 
2485     // Terminate the string
2486     _buf.appendByte('\0');
2487 
2488     // Strip BOM, if any.
2489     // See http://savannah.gnu.org/bugs/?19915
2490     utf8::TextEncoding encoding;
2491     size_t size = _buf.size();
2492 
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));
2499     }
2500 
2501     // NOTE: Data copy here !!
2502     as_value dataVal(bufptr);
2503 
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...
2509 
2510     callMethod(_obj, NSV::PROP_ON_DATA, std::move(dataVal));
2511 
2512     return true;
2513 }
2514 
2515 void
callInterface(const HostInterface::Message & e) const2516 movie_root::callInterface(const HostInterface::Message& e) const
2517 {
2518     if (!_interfaceHandler) {
2519         log_error(_("Hosting application registered no callback for events/queries, can't call %s(%s)"));
2520         return;
2521     }
2522     _interfaceHandler->call(e);
2523 }
2524 
2525 
2526 inline bool
testInvariant() const2527 movie_root::testInvariant() const
2528 {
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() );
2534 
2535     return true;
2536 }
2537 
2538 namespace {
2539 
2540 // Return whether any action triggered by this event requires display redraw.
2541 //
2542 /// TODO: make this code more readable !
2543 bool
generate_mouse_button_events(movie_root & mr,MouseButtonState & ms)2544 generate_mouse_button_events(movie_root& mr, MouseButtonState& ms)
2545 {
2546     // Did this event trigger any action that needs redisplay ?
2547     bool need_redisplay = false;
2548 
2549     // TODO: have mouseEvent return
2550     // whether the action must trigger
2551     // a redraw.
2552 
2553     if (ms.wasDown) {
2554         // TODO: Handle trackAsMenu dragOver
2555         // Handle onDragOut, onDragOver
2556         if (!ms.wasInsideActiveEntity) {
2557 
2558             if (ms.topmostEntity == ms.activeEntity) {
2559 
2560                 // onDragOver
2561                 if (ms.activeEntity) {
2562                     ms.activeEntity->mouseEvent(event_id(event_id::DRAG_OVER));
2563                     need_redisplay=true;
2564                 }
2565                 ms.wasInsideActiveEntity = true;
2566             }
2567         }
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;
2573             }
2574             ms.wasInsideActiveEntity = false;
2575         }
2576 
2577         // Handle onRelease, onReleaseOutside
2578         if (!ms.isDown) {
2579             // Mouse button just went up.
2580             ms.wasDown = false;
2581 
2582             if (ms.activeEntity) {
2583                 if (ms.wasInsideActiveEntity) {
2584                     // onRelease
2585                     ms.activeEntity->mouseEvent(event_id(event_id::RELEASE));
2586                     need_redisplay = true;
2587                 }
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;
2596                 }
2597             }
2598         }
2599         return need_redisplay;
2600     }
2601 
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;
2609             }
2610 
2611             ms.activeEntity = ms.topmostEntity;
2612 
2613             // onRollOver
2614             if (ms.activeEntity) {
2615                 ms.activeEntity->mouseEvent(event_id(event_id::ROLL_OVER));
2616                 need_redisplay=true;
2617             }
2618 
2619             ms.wasInsideActiveEntity = true;
2620         }
2621 
2622         // mouse button press
2623         if (ms.isDown) {
2624             // onPress
2625 
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);
2631 
2632                 ms.activeEntity->mouseEvent(event_id(event_id::PRESS));
2633                 need_redisplay = true;
2634             }
2635 
2636             ms.wasInsideActiveEntity = true;
2637             ms.wasDown = true;
2638         }
2639 
2640     }
2641     return need_redisplay;
2642 
2643 }
2644 
2645 const DisplayObject*
getNearestObject(const DisplayObject * o)2646 getNearestObject(const DisplayObject* o)
2647 {
2648     while (1) {
2649         assert(o);
2650         if (isReferenceable(*o)) return o;
2651         o = o->parent();
2652     }
2653 }
2654 
2655 as_object*
getBuiltinObject(movie_root & mr,const ObjectURI & cl)2656 getBuiltinObject(movie_root& mr, const ObjectURI& cl)
2657 {
2658     Global_as& gl = *mr.getVM().getGlobal();
2659 
2660     as_value val;
2661     if (!gl.get_member(cl, &val)) return nullptr;
2662     return toObject(val, mr.getVM());
2663 }
2664 
2665 void
advanceLiveChar(MovieClip * mo)2666 advanceLiveChar(MovieClip* mo)
2667 {
2668     if (!mo->unloaded()) {
2669 #ifdef GNASH_DEBUG
2670         log_debug("    advancing DisplayObject %s", mo->getTarget());
2671 #endif
2672         mo->advance();
2673     }
2674 #ifdef GNASH_DEBUG
2675     else {
2676         log_debug("    DisplayObject %s is unloaded, not advancing it",
2677                 mo->getTarget());
2678     }
2679 #endif
2680 }
2681 
2682 void
notifyLoad(MovieClip * mo)2683 notifyLoad(MovieClip* mo)
2684 {
2685     if ( mo->parent() ) {
2686         mo->queueLoad();
2687     }
2688 }
2689 
2690 } // anonymous namespace
2691 } // namespace gnash
2692 
2693 // local Variables:
2694 // mode: C++
2695 // indent-tabs-mode: nil
2696 // End:
2697