diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 254d05e7d..bb885f27f 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -654,7 +654,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let depth = self.context.avm1.pop(); let target = self.context.avm1.pop(); let source = self.context.avm1.pop(); - let start_clip = self.target_clip_or_root()?; + let start_clip = self.target_clip_or_root(); let source_clip = self.resolve_target_display_object(start_clip, source, true)?; if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) { @@ -722,7 +722,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn action_call(&mut self) -> Result, Error<'gc>> { // Runs any actions on the given frame. let arg = self.context.avm1.pop(); - let target = self.target_clip_or_root()?; + let target = self.target_clip_or_root(); // The parameter can be a frame # or a path to a movie clip with a frame number. let mut call_frame = None; @@ -778,7 +778,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let variable = self.get_variable(fn_name)?; let result = variable.call_with_default_this( - self.target_clip_or_root()?.object().coerce_to_object(self), + self.target_clip_or_root().object().coerce_to_object(self), fn_name, self, &args, @@ -1196,11 +1196,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { if let Value::Object(target) = target { target.as_display_object() } else { - let start = self.target_clip_or_root()?; + let start = self.target_clip_or_root(); self.resolve_target_display_object(start, target, true)? } } else { - Some(self.target_clip_or_root()?) + Some(self.target_clip_or_root()) }; if action.is_load_vars() { @@ -1313,7 +1313,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn action_goto_frame_2(&mut self, action: GotoFrame2) -> Result, Error<'gc>> { // Version 4+ gotoAndPlay/gotoAndStop // Param can either be a frame number or a frame label. - if let Some(clip) = self.target_clip_or_root()?.as_movie_clip() { + if let Some(clip) = self.target_clip_or_root().as_movie_clip() { let frame = self.context.avm1.pop(); let _ = globals::movie_clip::goto_frame( clip, @@ -1764,7 +1764,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn action_remove_sprite(&mut self) -> Result, Error<'gc>> { let target = self.context.avm1.pop(); - let start_clip = self.target_clip_or_root()?; + let start_clip = self.target_clip_or_root(); let target_clip = self.resolve_target_display_object(start_clip, target, true)?; if let Some(target_clip) = target_clip { @@ -1871,7 +1871,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { }; let scope = self.scope_cell(); - let clip_obj = self.target_clip_or_root()?.object().coerce_to_object(self); + let clip_obj = self.target_clip_or_root().object().coerce_to_object(self); self.set_scope(Scope::new_target_scope( scope, @@ -1891,7 +1891,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn action_start_drag(&mut self) -> Result, Error<'gc>> { let target = self.context.avm1.pop(); - let start_clip = self.target_clip_or_root()?; + let start_clip = self.target_clip_or_root(); let display_object = self.resolve_target_display_object(start_clip, target, true)?; if let Some(display_object) = display_object { let lock_center = self.context.avm1.pop(); @@ -2389,7 +2389,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { return Ok(None); } - let root = start.avm1_root(&self.context)?; + let root = start.avm1_root(); let start = start.object().coerce_to_object(self); Ok(self .resolve_target_path(root, start, &path, false)? @@ -2475,7 +2475,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { if first_element && name == b"this" { self.this_cell() } else if first_element && name == b"_root" { - self.root_object()? + self.root_object() } else { // Get the value from the object. // Resolves display object instances first, then local variables. @@ -2525,7 +2525,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let mut current_scope = Some(self.scope_cell()); while let Some(scope) = current_scope { - let avm1_root = start.avm1_root(&self.context)?; + let avm1_root = start.avm1_root(); if let Some(object) = self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)? { @@ -2569,7 +2569,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { /// scope chain. pub fn get_variable(&mut self, path: AvmString<'gc>) -> Result, Error<'gc>> { // Resolve a variable path for a GetVariable action. - let start = self.target_clip_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. @@ -2580,7 +2580,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let mut current_scope = Some(self.scope_cell()); while let Some(scope) = current_scope { - let avm1_root = start.avm1_root(&self.context)?; + let avm1_root = start.avm1_root(); if let Some(object) = self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)? { @@ -2599,7 +2599,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { if path.contains(b'/') { let mut current_scope = Some(self.scope_cell()); while let Some(scope) = current_scope { - let avm1_root = start.avm1_root(&self.context)?; + let avm1_root = start.avm1_root(); if let Some(object) = self.resolve_target_path(avm1_root, *scope.read().locals(), &path, false)? { @@ -2642,7 +2642,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { value: Value<'gc>, ) -> Result<(), Error<'gc>> { // Resolve a variable path for a GetVariable action. - let start = self.target_clip_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() { @@ -2660,7 +2660,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let mut current_scope = Some(self.scope_cell()); while let Some(scope) = current_scope { - let avm1_root = start.avm1_root(&self.context)?; + let avm1_root = start.avm1_root(); if let Some(object) = self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)? { @@ -2709,17 +2709,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { /// Actions that affect `root` after an invalid `tellTarget` will use this. /// /// The `root` is determined relative to the base clip that defined the - pub fn target_clip_or_root(&self) -> Result, Error<'gc>> { - if let Some(target) = self.target_clip() { - return Ok(target); - } - - self.base_clip().avm1_root(&self.context) + pub fn target_clip_or_root(&self) -> DisplayObject<'gc> { + self.target_clip() + .unwrap_or_else(|| self.base_clip().avm1_root()) } /// Obtain the value of `_root`. - pub fn root_object(&self) -> Result, Error<'gc>> { - Ok(self.base_clip().avm1_root(&self.context)?.object()) + pub fn root_object(&self) -> Value<'gc> { + self.base_clip().avm1_root().object() } /// Returns whether property keys should be case sensitive based on the current SWF version. @@ -2887,7 +2884,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn set_target(&mut self, target: &WStr) -> Result, Error<'gc>> { let base_clip = self.base_clip(); let new_target_clip; - let root = base_clip.avm1_root(&self.context)?; + let root = base_clip.avm1_root(); let start = base_clip.object().coerce_to_object(self); if target.is_empty() { new_target_clip = Some(base_clip); @@ -2925,7 +2922,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { self.set_target_clip(new_target_clip); let scope = self.scope_cell(); - let clip_obj = self.target_clip_or_root()?.object().coerce_to_object(self); + let clip_obj = self.target_clip_or_root().object().coerce_to_object(self); self.set_scope(Scope::new_target_scope( scope, diff --git a/core/src/avm1/error.rs b/core/src/avm1/error.rs index 82d7af076..a7f3fc03a 100644 --- a/core/src/avm1/error.rs +++ b/core/src/avm1/error.rs @@ -18,9 +18,6 @@ pub enum Error<'gc> { #[error("Couldn't parse SWF")] InvalidSwf(#[from] swf::error::Error), - #[error("Attempted to interact with a rootless display object in AVM1. Such objects can only be created in AS3, this is a runtime bug in Ruffle.")] - InvalidDisplayObjectHierarchy, - #[error("A script has thrown a custom error.")] ThrownValue(Value<'gc>), } diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index d273ea2db..b06fa3469 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -350,10 +350,7 @@ impl<'gc> Executable<'gc> { } if af.flags.contains(FunctionFlags::PRELOAD_ROOT) { - frame.set_local_register( - preload_r, - af.base_clip.avm1_root(&frame.context)?.object(), - ); + frame.set_local_register(preload_r, af.base_clip.avm1_root().object()); preload_r += 1; } diff --git a/core/src/avm1/globals/bitmap_data.rs b/core/src/avm1/globals/bitmap_data.rs index 95f75c7ee..198bdd157 100644 --- a/core/src/avm1/globals/bitmap_data.rs +++ b/core/src/avm1/globals/bitmap_data.rs @@ -1149,7 +1149,7 @@ pub fn load_bitmap<'gc>( let library = &*activation.context.library; - let movie = activation.target_clip_or_root()?.movie(); + let movie = activation.target_clip_or_root().movie(); let renderer = &mut activation.context.renderer; diff --git a/core/src/avm1/globals/color.rs b/core/src/avm1/globals/color.rs index 188254e64..28335a569 100644 --- a/core/src/avm1/globals/color.rs +++ b/core/src/avm1/globals/color.rs @@ -57,7 +57,7 @@ fn target<'gc>( let target = this.get("target", activation)?; // Undefined or empty target is no-op. if target != Value::Undefined { - let start_clip = activation.target_clip_or_root()?; + let start_clip = activation.target_clip_or_root(); activation.resolve_target_display_object(start_clip, target, false) } else { Ok(None) diff --git a/core/src/avm1/globals/display_object.rs b/core/src/avm1/globals/display_object.rs index f5cc5df7f..dfa23b70c 100644 --- a/core/src/avm1/globals/display_object.rs +++ b/core/src/avm1/globals/display_object.rs @@ -53,7 +53,7 @@ pub fn get_root<'gc>( _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - activation.root_object() + Ok(activation.root_object()) } pub fn get_parent<'gc>( diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 27bb4e1f6..e25e34ac4 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -134,7 +134,7 @@ pub fn hit_test<'gc>( // 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 = movie_clip - .avm1_root(&activation.context)? + .avm1_root() .local_to_global((Twips::from_pixels(x), Twips::from_pixels(y))); let ret = if shape { movie_clip.hit_test_shape( diff --git a/core/src/avm1/globals/movie_clip_loader.rs b/core/src/avm1/globals/movie_clip_loader.rs index 4b37c7c08..6513a4aa1 100644 --- a/core/src/avm1/globals/movie_clip_loader.rs +++ b/core/src/avm1/globals/movie_clip_loader.rs @@ -46,7 +46,7 @@ fn load_clip<'gc>( if let Value::String(url) = url { let target = match target { Value::String(_) => { - let start_clip = activation.target_clip_or_root()?; + let start_clip = activation.target_clip_or_root(); activation.resolve_target_display_object(start_clip, *target, true)? } Value::Number(level_id) => { @@ -90,7 +90,7 @@ fn unload_clip<'gc>( if let [target, ..] = args { let target = match target { Value::String(_) => { - let start_clip = activation.target_clip_or_root()?; + let start_clip = activation.target_clip_or_root(); activation.resolve_target_display_object(start_clip, *target, true)? } Value::Number(level_id) => { @@ -123,7 +123,7 @@ fn get_progress<'gc>( if let [target, ..] = args { let target = match target { Value::String(_) => { - let start_clip = activation.target_clip_or_root()?; + let start_clip = activation.target_clip_or_root(); activation.resolve_target_display_object(start_clip, *target, true)? } Value::Number(level_id) => { diff --git a/core/src/avm1/globals/sound.rs b/core/src/avm1/globals/sound.rs index d76afa58c..c62ee7521 100644 --- a/core/src/avm1/globals/sound.rs +++ b/core/src/avm1/globals/sound.rs @@ -39,7 +39,7 @@ pub fn constructor<'gc>( // 1st parameter is the movie clip that "owns" all sounds started by this object. // `Sound.setTransform`, `Sound.stop`, etc. will affect all sounds owned by this clip. let owner = if let Some(owner) = args.get(0) { - let start_clip = activation.target_clip_or_root()?; + let start_clip = activation.target_clip_or_root(); activation.resolve_target_display_object(start_clip, *owner, false)? } else { None diff --git a/core/src/display_object.rs b/core/src/display_object.rs index e42bca187..a78bc5939 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -1,6 +1,4 @@ -use crate::avm1::{ - Error as Avm1Error, Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value, -}; +use crate::avm1::{Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value}; use crate::avm2::{ Activation as Avm2Activation, Avm2, Error as Avm2Error, Event as Avm2Event, EventData as Avm2EventData, Namespace as Avm2Namespace, Object as Avm2Object, @@ -1314,55 +1312,22 @@ pub trait TDisplayObject<'gc>: true } - /// Obtain the top-most non-Stage parent of the display tree hierarchy, if - /// a suitable object exists. If none such object exists, this function - /// yields an AVM1 error (which shouldn't happen in normal usage). + /// Obtain the top-most non-Stage parent of the display tree hierarchy. /// /// This function implements the AVM1 concept of root clips. For the AVM2 /// version, see `avm2_root`. - fn avm1_root( - &self, - context: &UpdateContext<'_, 'gc, '_>, - ) -> Result, Avm1Error<'gc>> { - let mut parent = if self.lock_root() { - None - } else { - self.avm1_parent() - }; - - while let Some(p) = parent { - if p.lock_root() { + fn avm1_root(&self) -> DisplayObject<'gc> { + let mut root = (*self).into(); + loop { + if root.lock_root() { break; } - - let grandparent = p.avm1_parent(); - - if grandparent.is_none() { - break; - } - - parent = grandparent; + root = match root.avm1_parent() { + Some(parent) => parent, + None => break, + }; } - - parent - .ok_or(Avm1Error::InvalidDisplayObjectHierarchy) - .or_else(|_| { - if let Avm1Value::Object(object) = self.object() { - object - .as_display_object() - .ok_or(Avm1Error::InvalidDisplayObjectHierarchy) - } else if let Avm2Value::Object(object) = self.object2() { - if self.is_on_stage(context) { - object - .as_display_object() - .ok_or(Avm1Error::InvalidDisplayObjectHierarchy) - } else { - Err(Avm1Error::InvalidDisplayObjectHierarchy) - } - } else { - Err(Avm1Error::InvalidDisplayObjectHierarchy) - } - }) + root } /// Obtain the top-most non-Stage parent of the display tree hierarchy, if