From cd81b256268ccf1da438a63842659800ba992402 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Sun, 8 May 2022 11:40:09 -0700 Subject: [PATCH] avm1: `_root`, `_parent`, `_global` are magic properties (fix #768) Remove `_root`, `_parent` and `_global` from `MovieClip.prototype`. Instead, these are "magic" properties similar to `_x` and `_y`. Add `StageObject::resolve_path_property` to handle these, alongside the `_levelN` property. Fixes #768. --- core/src/avm1/globals/display_object.rs | 90 ------------------------- core/src/avm1/object/stage_object.rs | 58 +++++++++------- 2 files changed, 32 insertions(+), 116 deletions(-) diff --git a/core/src/avm1/globals/display_object.rs b/core/src/avm1/globals/display_object.rs index 00ff33be9416..8531608e12a0 100644 --- a/core/src/avm1/globals/display_object.rs +++ b/core/src/avm1/globals/display_object.rs @@ -2,7 +2,6 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; -use crate::avm1::property::Attribute; use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::display_object::{DisplayObject, Lists, TDisplayObject, TDisplayObjectContainer}; @@ -26,9 +25,6 @@ pub const AVM_MAX_REMOVE_DEPTH: i32 = 2_130_706_416; const OBJECT_DECLS: &[Declaration] = declare_properties! { "getDepth" => method(get_depth; DONT_ENUM | DONT_DELETE | READ_ONLY; version(6)); "toString" => method(to_string; DONT_ENUM | DONT_DELETE | READ_ONLY); - "_global" => property(get_global, overwrite_global; DONT_ENUM | DONT_DELETE | READ_ONLY); - "_root" => property(get_root, overwrite_root; DONT_ENUM | DONT_DELETE | READ_ONLY); - "_parent" => property(get_parent, overwrite_parent; DONT_ENUM | DONT_DELETE | READ_ONLY); }; /// Add common display object prototype methods to the given prototype. @@ -40,35 +36,6 @@ pub fn define_display_object_proto<'gc>( define_properties_on(OBJECT_DECLS, gc_context, object, fn_proto); } -pub fn get_global<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - _this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(activation.context.avm1.global_object()) -} - -pub fn get_root<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - _this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(activation.root_object()) -} - -pub fn get_parent<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(this - .as_display_object() - .and_then(|mc| mc.avm1_parent()) - .map(|dn| dn.object().coerce_to_object(activation)) - .map(Value::Object) - .unwrap_or(Value::Undefined)) -} - pub fn get_depth<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Object<'gc>, @@ -95,63 +62,6 @@ pub fn to_string<'gc>( } } -pub fn overwrite_root<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let new_val = args - .get(0) - .map(|v| v.to_owned()) - .unwrap_or(Value::Undefined); - this.define_value( - activation.context.gc_context, - "_root", - new_val, - Attribute::empty(), - ); - - Ok(Value::Undefined) -} - -pub fn overwrite_global<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let new_val = args - .get(0) - .map(|v| v.to_owned()) - .unwrap_or(Value::Undefined); - this.define_value( - activation.context.gc_context, - "_global", - new_val, - Attribute::empty(), - ); - - Ok(Value::Undefined) -} - -pub fn overwrite_parent<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let new_val = args - .get(0) - .map(|v| v.to_owned()) - .unwrap_or(Value::Undefined); - this.define_value( - activation.context.gc_context, - "_parent", - new_val, - Attribute::empty(), - ); - - Ok(Value::Undefined) -} - pub fn remove_display_object<'gc>( this: DisplayObject<'gc>, activation: &mut Activation<'_, 'gc, '_>, diff --git a/core/src/avm1/object/stage_object.rs b/core/src/avm1/object/stage_object.rs index 7ed684382f62..4a101cfc78c3 100644 --- a/core/src/avm1/object/stage_object.rs +++ b/core/src/avm1/object/stage_object.rs @@ -95,29 +95,37 @@ impl<'gc> StageObject<'gc> { } } - /// Get another level by level name. - /// - /// Since levels don't have instance names, this function instead parses - /// their ID and uses that to retrieve the level. - /// - /// If the name is a valid level path, it will return the level object - /// or `Some(Value::Undefined)` if the level is not occupied. - /// Returns `None` if `name` is not a valid level path. - fn get_level_by_path( + fn resolve_path_property( + self, name: AvmString<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - case_sensitive: bool, + activation: &mut Activation<'_, 'gc, '_>, ) -> Option> { + let case_sensitive = activation.is_case_sensitive(); + if name.eq_with_case(b"_root", case_sensitive) { + return Some(activation.root_object()); + } else if name.eq_with_case(b"_parent", case_sensitive) { + return Some( + self.0 + .read() + .display_object + .avm1_parent() + .map(|dn| dn.object().coerce_to_object(activation)) + .map(Value::Object) + .unwrap_or(Value::Undefined), + ); + } else if name.eq_with_case(b"_global", case_sensitive) { + return Some(activation.context.avm1.global_object()); + } + + // Resolve level names `_levelN`. if let Some(prefix) = name.slice(..6) { - let is_level = if case_sensitive { - prefix == b"_level" || prefix == b"_flash" - } else { - prefix.eq_ignore_case(WStr::from_units(b"_level")) - || prefix.eq_ignore_case(WStr::from_units(b"_flash")) - }; - if is_level { + // `_flash` is a synonym of `_level`, a relic from the earliest Flash versions. + if prefix.eq_with_case(b"_level", case_sensitive) + || prefix.eq_with_case(b"_flash", case_sensitive) + { let level_id = Self::parse_level_id(&name[6..]); - let level = context + let level = activation + .context .stage .child_by_depth(level_id) .map(|o| o.object()) @@ -182,11 +190,9 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { if self.has_own_property(activation, name) { // 1) Actual properties on the underlying object obj.base.get_local_stored(name, activation) - } else if let Some(level) = - Self::get_level_by_path(name, &mut activation.context, case_sensitive) - { - // 2) _levelN - Some(level) + } else if let Some(object) = self.resolve_path_property(name, activation) { + // 2) Path properties such as `_root`, `_parent`, `_levelN` (obeys case sensitivity) + Some(object) } else if let Some(child) = obj .display_object .as_container() @@ -195,7 +201,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { // 3) Child display objects with the given instance name Some(child.object()) } else if let Some(property) = props.read().get_by_name(name) { - // 4) Display object properties such as _x, _y + // 4) Display object properties such as `_x`, `_y` (never case sensitive) Some(property.get(activation, obj.display_object)) } else { None @@ -400,7 +406,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { return true; } - if Self::get_level_by_path(name, &mut activation.context, case_sensitive).is_some() { + if self.resolve_path_property(name, activation).is_some() { return true; }