Skip to content

Commit

Permalink
avm1: _root, _parent, _global are magic properties (fix ruffle-…
Browse files Browse the repository at this point in the history
…rs#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 ruffle-rs#768.
  • Loading branch information
Herschel committed May 8, 2022
1 parent 43996db commit cd81b25
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 116 deletions.
90 changes: 0 additions & 90 deletions core/src/avm1/globals/display_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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.
Expand All @@ -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<Value<'gc>, Error<'gc>> {
Ok(activation.context.avm1.global_object())
}

pub fn get_root<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(activation.root_object())
}

pub fn get_parent<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, 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>,
Expand All @@ -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<Value<'gc>, 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<Value<'gc>, 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<Value<'gc>, 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, '_>,
Expand Down
58 changes: 32 additions & 26 deletions core/src/avm1/object/stage_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Value<'gc>> {
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())
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit cd81b25

Please sign in to comment.