diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 9ece32cfa..bcbdeec5a 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -149,15 +149,14 @@ impl<'gc> Avm1<'gc> { /// The current target clip of the executing code, or `root` if there is none. /// Actions that affect `root` after an invalid `tellTarget` will use this. - pub fn target_clip_or_root( - &self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> DisplayObject<'gc> { + /// + /// The `root` is determined relative to the base clip that defined the + pub fn target_clip_or_root(&self) -> DisplayObject<'gc> { self.current_stack_frame() .unwrap() .read() .target_clip() - .unwrap_or(context.root) + .unwrap_or_else(|| self.base_clip().root()) } /// Convert the current locals pool into a set of form values. @@ -695,7 +694,7 @@ impl<'gc> Avm1<'gc> { start: DisplayObject<'gc>, path: &str, ) -> Result>, Error> { - let root = context.root; + let root = start.root(); // Empty path resolves immediately to start clip. if path.is_empty() { @@ -809,8 +808,7 @@ impl<'gc> Avm1<'gc> { path: &'s str, ) -> Result, Error> { // Resolve a variable path for a GetVariable action. - let root = context.root; - let start = self.target_clip().unwrap_or(root); + let start = self.target_clip_or_root(); // Find the right-most : or . in the path. // If we have one, we must resolve as a target path. @@ -878,8 +876,7 @@ impl<'gc> Avm1<'gc> { value: Value<'gc>, ) -> Result<(), Error> { // Resolve a variable path for a GetVariable action. - let root = context.root; - let start = self.target_clip().unwrap_or(root); + let start = self.target_clip_or_root(); // If the target clip is invalid, we default to root for the variable path. if path.is_empty() { @@ -1092,7 +1089,7 @@ impl<'gc> Avm1<'gc> { let depth = self.pop(); let target = self.pop(); let source = self.pop(); - let start_clip = self.target_clip_or_root(context); + let start_clip = self.target_clip_or_root(); let source_clip = self.resolve_target_display_object(context, start_clip, source)?; if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) { @@ -1161,7 +1158,7 @@ impl<'gc> Avm1<'gc> { fn action_call(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { // Runs any actions on the given frame. let frame = self.pop(); - let clip = self.target_clip_or_root(context); + let clip = self.target_clip_or_root(); if let Some(clip) = clip.as_movie_clip() { // Use frame # if parameter is a number, otherwise cast to string and check for frame labels. let frame = if let Ok(frame) = frame.as_u32() { @@ -1180,7 +1177,7 @@ impl<'gc> Avm1<'gc> { // so we want to push the stack frames in reverse order. for action in clip.actions_on_frame(context, frame).rev() { self.insert_stack_frame_for_action( - self.target_clip_or_root(context), + self.target_clip_or_root(), self.current_swf_version(), action, context, @@ -1214,7 +1211,7 @@ impl<'gc> Avm1<'gc> { .read() .resolve(fn_name.as_string()?, self, context)? .resolve(self, context)?; - let this = self.target_clip_or_root(context).object().as_object()?; + let this = self.target_clip_or_root().object().as_object()?; target_fn.call(self, context, this, &args)?.push(self); Ok(()) @@ -1235,7 +1232,7 @@ impl<'gc> Avm1<'gc> { match method_name { Value::Undefined | Value::Null => { - let this = self.target_clip_or_root(context).object(); + let this = self.target_clip_or_root().object(); if let Ok(this) = this.as_object() { object.call(self, context, this, &args)?.push(self); } else { @@ -1340,7 +1337,7 @@ impl<'gc> Avm1<'gc> { params, scope, constant_pool, - self.target_clip_or_root(context), + self.target_clip_or_root(), ); let prototype = ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into(); @@ -1606,8 +1603,8 @@ impl<'gc> Avm1<'gc> { } /// Obtain the value of `_root`. - pub fn root_object(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> { - context.root.object() + pub fn root_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> { + self.base_clip().root().object() } /// Obtain the value of `_global`. @@ -1692,11 +1689,11 @@ impl<'gc> Avm1<'gc> { if let Value::Object(target) = target { target.as_display_object() } else { - let start = self.target_clip_or_root(context); + let start = self.target_clip_or_root(); self.resolve_target_display_object(context, start, target.clone())? } } else { - Some(self.target_clip_or_root(context)) + Some(self.target_clip_or_root()) }; if is_load_vars { @@ -2209,7 +2206,7 @@ impl<'gc> Avm1<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { let target = self.pop(); - let start_clip = self.target_clip_or_root(context); + let start_clip = self.target_clip_or_root(); let target_clip = self.resolve_target_display_object(context, start_clip, target)?; if let Some(target_clip) = target_clip.and_then(|o| o.as_movie_clip()) { @@ -2295,16 +2292,15 @@ impl<'gc> Avm1<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, target: &str, ) -> Result<(), Error> { - let stack_frame = self.current_stack_frame().unwrap(); - let mut sf = stack_frame.write(context.gc_context); - let base_clip = sf.base_clip(); + let base_clip = self.base_clip(); + let new_target_clip; if target.is_empty() { - sf.set_target_clip(Some(base_clip)); + new_target_clip = Some(base_clip); } else if let Some(clip) = self .resolve_target_path(context, base_clip, target)? .and_then(|o| o.as_display_object()) { - sf.set_target_clip(Some(clip)); + new_target_clip = Some(clip); } else { log::warn!("SetTarget failed: {} not found", target); // TODO: Emulate AVM1 trace error message. @@ -2313,13 +2309,17 @@ impl<'gc> Avm1<'gc> { // When SetTarget has an invalid target, subsequent GetVariables act // as if they are targeting root, but subsequent Play/Stop/etc. // fail silenty. - sf.set_target_clip(None); + new_target_clip = None; } + let stack_frame = self.current_stack_frame().unwrap(); + let mut sf = stack_frame.write(context.gc_context); + sf.set_target_clip(new_target_clip); + let scope = sf.scope_cell(); let clip_obj = sf .target_clip() - .unwrap_or(context.root) + .unwrap_or_else(|| sf.base_clip().root()) .object() .as_object() .unwrap(); @@ -2367,7 +2367,7 @@ impl<'gc> Avm1<'gc> { let scope = sf.scope_cell(); let clip_obj = sf .target_clip() - .unwrap_or(context.root) + .unwrap_or_else(|| sf.base_clip().root()) .object() .as_object() .unwrap(); @@ -2385,7 +2385,7 @@ impl<'gc> Avm1<'gc> { fn action_start_drag(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { let target = self.pop(); - let start_clip = self.target_clip_or_root(context); + let start_clip = self.target_clip_or_root(); let display_object = self.resolve_target_display_object(context, start_clip, target)?; if let Some(display_object) = display_object { let lock_center = self.pop(); diff --git a/core/src/avm1/globals/color.rs b/core/src/avm1/globals/color.rs index 8693f1f7b..1685b5fb1 100644 --- a/core/src/avm1/globals/color.rs +++ b/core/src/avm1/globals/color.rs @@ -82,7 +82,7 @@ fn target<'gc>( // This means calls on the same `Color` object could set the color of different clips // depending on which timeline its called from! let target = this.get("target", avm, context)?.resolve(avm, context)?; - let start_clip = avm.target_clip_or_root(context); + let start_clip = avm.target_clip_or_root(); avm.resolve_target_display_object(context, start_clip, target) } diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 73db6ad7f..e52ebcf37 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -102,8 +102,8 @@ pub fn hit_test<'gc>( if x.is_finite() && y.is_finite() { // The docs say the point is in "Stage coordinates", but actually they are in root coordinates. // root can be moved via _root._x etc., so we actually have to transform from root to world space. - let point = context - .root + let point = movie_clip + .root() .local_to_global((Twips::from_pixels(x), Twips::from_pixels(y))); return Ok(movie_clip.hit_test(point).into()); } diff --git a/core/src/avm1/globals/xml.rs b/core/src/avm1/globals/xml.rs index 5213aa790..2c6643f51 100644 --- a/core/src/avm1/globals/xml.rs +++ b/core/src/avm1/globals/xml.rs @@ -800,7 +800,7 @@ pub fn xml_load<'gc>( this.set("loaded", false.into(), avm, ac)?; let fetch = ac.navigator.fetch(url, RequestOptions::get()); - let target_clip = avm.target_clip_or_root(ac); + let target_clip = avm.target_clip_or_root(); let process = ac.load_manager.load_xml_into_node( ac.player.clone().unwrap(), node, diff --git a/core/src/avm1/script_object.rs b/core/src/avm1/script_object.rs index 0b7c1f760..418f7a327 100644 --- a/core/src/avm1/script_object.rs +++ b/core/src/avm1/script_object.rs @@ -597,7 +597,6 @@ mod tests { player_version: 32, swf: &swf, layers: &mut layers, - root, rng: &mut SmallRng::from_seed([0u8; 16]), action_queue: &mut crate::context::ActionQueue::new(), audio: &mut NullAudioBackend::new(), diff --git a/core/src/avm1/test_utils.rs b/core/src/avm1/test_utils.rs index a5b48fcc4..fde4f0570 100644 --- a/core/src/avm1/test_utils.rs +++ b/core/src/avm1/test_utils.rs @@ -36,7 +36,6 @@ where player_version: 32, swf: &swf, layers: &mut layers, - root, rng: &mut SmallRng::from_seed([0u8; 16]), audio: &mut NullAudioBackend::new(), input: &mut NullInputBackend::new(), diff --git a/core/src/avm1/tests.rs b/core/src/avm1/tests.rs index 58953cdfa..9cc019a04 100644 --- a/core/src/avm1/tests.rs +++ b/core/src/avm1/tests.rs @@ -10,7 +10,7 @@ fn locals_into_form_values() { 19, avm.global_object_cell(), context.gc_context, - context.root, + *context.layers.get(&0).expect("root layer in test"), ); let my_locals = my_activation.scope().locals().to_owned(); diff --git a/core/src/context.rs b/core/src/context.rs index 8f71f0d6f..b167e3938 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -68,10 +68,6 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> { /// All loaded layers of the current player. pub layers: &'a mut BTreeMap>, - /// The root of the current timeline being updated. - /// This will always be one of the layers in `layers`. - pub root: DisplayObject<'gc>, - /// The current set of system-specified prototypes to use when constructing /// new built-in objects. pub system_prototypes: avm1::SystemPrototypes<'gc>, @@ -106,9 +102,6 @@ pub struct QueuedActions<'gc> { /// The movie clip this ActionScript is running on. pub clip: DisplayObject<'gc>, - /// The root timeline this action was queued in. - pub root: DisplayObject<'gc>, - /// The type of action this is, along with the corresponding bytecode/method data. pub action_type: ActionType<'gc>, @@ -145,13 +138,11 @@ impl<'gc> ActionQueue<'gc> { pub fn queue_actions( &mut self, clip: DisplayObject<'gc>, - root: DisplayObject<'gc>, action_type: ActionType<'gc>, is_unload: bool, ) { self.queue.push_back(QueuedActions { clip, - root, action_type, is_unload, }) diff --git a/core/src/display_object.rs b/core/src/display_object.rs index d8677b159..88402824a 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -1,4 +1,4 @@ -use crate::avm1::{Object, Value}; +use crate::avm1::{Object, TObject, Value}; use crate::context::{RenderContext, UpdateContext}; use crate::player::NEWEST_PLAYER_VERSION; use crate::prelude::*; @@ -366,7 +366,7 @@ impl<'gc> DisplayObjectBase<'gc> { Text(Text<'gc>), } )] -pub trait TDisplayObject<'gc>: 'gc + Collect + Debug { +pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into> { fn id(&self) -> CharacterId; fn depth(&self) -> Depth; fn set_depth(&self, gc_context: MutationContext<'gc, '_>, depth: Depth); @@ -804,6 +804,34 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug { fn allow_as_mask(&self) -> bool { true } + + /// Obtain the top-most parent of the display tree hierarchy. + /// + /// This function can panic in the rare case that a top-level display + /// object has not been post-instantiated, or that a top-level display + /// object does not implement `object`. + fn root(&self) -> DisplayObject<'gc> { + let mut parent = self.parent(); + + while let Some(p) = parent { + let grandparent = p.parent(); + + if grandparent.is_none() { + break; + } + + parent = grandparent; + } + + parent + .or_else(|| { + self.object() + .as_object() + .ok() + .and_then(|o| o.as_display_object()) + }) + .expect("All objects must have root") + } } pub enum DisplayObjectPtr {} diff --git a/core/src/display_object/button.rs b/core/src/display_object/button.rs index 2e51e1979..094f713a6 100644 --- a/core/src/display_object/button.rs +++ b/core/src/display_object/button.rs @@ -370,7 +370,6 @@ impl<'gc> ButtonData<'gc> { handled = ButtonEventResult::Handled; context.action_queue.queue_actions( parent, - context.root, ActionType::Normal { bytecode: action.action_data.clone(), }, diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index a39044a01..37398de1b 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -923,7 +923,6 @@ impl<'gc> MovieClipData<'gc> { { context.action_queue.queue_actions( self_display_object, - context.root, ActionType::Normal { bytecode: clip_action.action_data.clone(), }, @@ -958,7 +957,6 @@ impl<'gc> MovieClipData<'gc> { if let Some(name) = name { context.action_queue.queue_actions( self_display_object, - context.root, ActionType::Method { object: self.object.unwrap(), name, @@ -991,7 +989,6 @@ impl<'gc> MovieClipData<'gc> { context.load_manager.movie_clip_on_load( self_display_object, self.object, - context.root, context.action_queue, ); } @@ -1772,7 +1769,6 @@ impl<'gc, 'a> MovieClipData<'gc> { })?; context.action_queue.queue_actions( self_display_object, - context.root, ActionType::Normal { bytecode: slice }, false, ); @@ -1806,7 +1802,6 @@ impl<'gc, 'a> MovieClipData<'gc> { })?; context.action_queue.queue_actions( self_display_object, - context.root, ActionType::Init { bytecode: slice }, true, ); diff --git a/core/src/loader.rs b/core/src/loader.rs index 6ea0e40e0..75b50796e 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -89,13 +89,12 @@ impl<'gc> LoadManager<'gc> { &mut self, loaded_clip: DisplayObject<'gc>, clip_object: Option>, - root: DisplayObject<'gc>, queue: &mut ActionQueue<'gc>, ) { let mut invalidated_loaders = vec![]; for (index, loader) in self.0.iter_mut() { - if loader.movie_clip_loaded(loaded_clip, clip_object, root, queue) { + if loader.movie_clip_loaded(loaded_clip, clip_object, queue) { invalidated_loaders.push(index); } } @@ -433,7 +432,6 @@ impl<'gc> Loader<'gc> { &mut self, loaded_clip: DisplayObject<'gc>, clip_object: Option>, - root: DisplayObject<'gc>, queue: &mut ActionQueue<'gc>, ) -> bool { let (clip, broadcaster) = match self { @@ -449,7 +447,6 @@ impl<'gc> Loader<'gc> { if let Some(broadcaster) = broadcaster { queue.queue_actions( clip, - root, ActionType::Method { object: broadcaster, name: "broadcastMessage", diff --git a/core/src/player.rs b/core/src/player.rs index c3f22816a..538dc0b58 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -6,7 +6,7 @@ use crate::backend::{ }; use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext}; use crate::display_object::{MorphShape, MovieClip}; -use crate::events::{ButtonEvent, ButtonKeyCode, ClipEvent, PlayerEvent}; +use crate::events::{ButtonEvent, ButtonEventResult, ButtonKeyCode, ClipEvent, PlayerEvent}; use crate::library::Library; use crate::loader::LoadManager; use crate::prelude::*; @@ -369,9 +369,14 @@ impl Player { if button_event.is_some() { self.mutate_with_update_context(|_avm, context| { - let root = context.root; - if let Some(button_event) = button_event { - root.propagate_button_event(context, button_event); + let layers: Vec> = context.layers.values().copied().collect(); + for layer in layers { + if let Some(button_event) = button_event { + let state = layer.propagate_button_event(context, button_event); + if state == ButtonEventResult::Handled { + return; + } + } } }); } @@ -387,16 +392,17 @@ impl Player { if clip_event.is_some() || mouse_event_name.is_some() { self.mutate_with_update_context(|_avm, context| { - let root = context.root; + let layers: Vec> = context.layers.values().copied().collect(); - if let Some(clip_event) = clip_event { - root.propagate_clip_event(context, clip_event); + for layer in layers { + if let Some(clip_event) = clip_event { + layer.propagate_clip_event(context, clip_event); + } } if let Some(mouse_event_name) = mouse_event_name { context.action_queue.queue_actions( - root, - root, + *context.layers.get(&0).expect("root layer"), ActionType::NotifyListeners { listener: SystemListener::Mouse, method: mouse_event_name, @@ -521,7 +527,7 @@ impl Player { fn preload(&mut self) { self.mutate_with_update_context(|_avm, context| { let mut morph_shapes = fnv::FnvHashMap::default(); - let root = context.root; + let root = *context.layers.get(&0).expect("root layer"); root.as_movie_clip() .unwrap() .preload(context, &mut morph_shapes); @@ -550,8 +556,6 @@ impl Player { } for mut layer in layers { - update_context.root = layer; - layer.run_frame(update_context); } }) @@ -636,8 +640,6 @@ impl Player { continue; } - context.root = actions.root; - match actions.action_type { // DoAction/clip event code ActionType::Normal { bytecode } => { @@ -772,7 +774,6 @@ impl Player { let mouse_hovered_object = root_data.mouse_hovered_object; let (layers, library, action_queue, avm, drag_object, load_manager) = root_data.update_context_params(); - let layer0 = layers.get(&0).expect("Layer 0 should always exist"); let mut update_context = UpdateContext { player_version, @@ -787,7 +788,6 @@ impl Player { input, action_queue, gc_context, - root: *layer0, layers, mouse_hovered_object, mouse_position,