core: Trying to access a named shape should resolve to the parent as they have no object representation
This commit is contained in:
parent
4c1c3cc105
commit
771f568509
|
@ -1127,7 +1127,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
let object_val = self.context.avm1.pop();
|
||||
let object = object_val.coerce_to_object(self);
|
||||
|
||||
let result = object.get(name, self)?;
|
||||
let result = object.get_non_slash_path(name, self)?;
|
||||
self.stack_push(result);
|
||||
|
||||
Ok(FrameControl::Continue)
|
||||
|
@ -2563,7 +2563,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
let root = start.avm1_root();
|
||||
let start = start.object().coerce_to_object(self);
|
||||
Ok(self
|
||||
.resolve_target_path(root, start, &path, false)?
|
||||
.resolve_target_path(root, start, &path, false, true)?
|
||||
.and_then(|o| o.as_display_object()))
|
||||
}
|
||||
|
||||
|
@ -2582,6 +2582,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
start: Object<'gc>,
|
||||
mut path: &WStr,
|
||||
mut first_element: bool,
|
||||
path_has_slash: bool,
|
||||
) -> Result<Option<Object<'gc>>, Error<'gc>> {
|
||||
// Empty path resolves immediately to start clip.
|
||||
if path.is_empty() {
|
||||
|
@ -2656,10 +2657,25 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
.and_then(|o| o.as_container())
|
||||
.and_then(|o| o.child_by_name(name, case_sensitive))
|
||||
{
|
||||
child.object()
|
||||
// If an object doesn't have an object representation, e.g. Graphic, then trying to access it
|
||||
// Returns the parent instead
|
||||
if path_has_slash {
|
||||
child.object()
|
||||
} else if let crate::display_object::DisplayObject::Graphic(_) = child {
|
||||
child
|
||||
.parent()
|
||||
.map(|p| p.object())
|
||||
.unwrap_or(Value::Undefined)
|
||||
} else {
|
||||
child.object()
|
||||
}
|
||||
} else {
|
||||
let name = AvmString::new(self.context.gc_context, name);
|
||||
object.get(name, self).unwrap()
|
||||
if path_has_slash {
|
||||
object.get(name, self).unwrap()
|
||||
} else {
|
||||
object.get_non_slash_path(name, self).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -2689,6 +2705,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
start: DisplayObject<'gc>,
|
||||
path: &'s WStr,
|
||||
) -> Result<Option<(Object<'gc>, &'s WStr)>, Error<'gc>> {
|
||||
let path_has_slash = path.contains(b'/');
|
||||
|
||||
// Find the right-most : or . in the path.
|
||||
// If we have one, we must resolve as a target path.
|
||||
if let Some(separator) = path.rfind(b":.".as_ref()) {
|
||||
|
@ -2699,9 +2717,13 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
let mut current_scope = Some(self.scope());
|
||||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.locals(), path, true)?
|
||||
{
|
||||
if let Some(object) = self.resolve_target_path(
|
||||
avm1_root,
|
||||
*scope.locals(),
|
||||
path,
|
||||
true,
|
||||
path_has_slash,
|
||||
)? {
|
||||
return Ok(Some((object, var_name)));
|
||||
}
|
||||
current_scope = scope.parent();
|
||||
|
@ -2744,6 +2766,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
// Resolve a variable path for a GetVariable action.
|
||||
let start = self.target_clip_or_root();
|
||||
|
||||
let path_has_slash = path.contains(b'/');
|
||||
|
||||
// Find the right-most : or . in the path.
|
||||
// If we have one, we must resolve as a target path.
|
||||
if let Some(separator) = path.rfind(b":.".as_ref()) {
|
||||
|
@ -2754,9 +2778,13 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
let mut current_scope = Some(self.scope());
|
||||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.locals(), path, true)?
|
||||
{
|
||||
if let Some(object) = self.resolve_target_path(
|
||||
avm1_root,
|
||||
*scope.locals(),
|
||||
path,
|
||||
true,
|
||||
path_has_slash,
|
||||
)? {
|
||||
let var_name = AvmString::new(self.context.gc_context, var_name);
|
||||
if object.has_property(self, var_name) {
|
||||
return Ok(CallableValue::Callable(object, object.get(var_name, self)?));
|
||||
|
@ -2774,7 +2802,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.locals(), &path, false)?
|
||||
self.resolve_target_path(avm1_root, *scope.locals(), &path, false, true)?
|
||||
{
|
||||
return Ok(CallableValue::UnCallable(object.into()));
|
||||
}
|
||||
|
@ -2841,7 +2869,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.locals(), path, true)?
|
||||
self.resolve_target_path(avm1_root, *scope.locals(), path, true, true)?
|
||||
{
|
||||
let var_name = AvmString::new(self.context.gc_context, var_name);
|
||||
object.set(var_name, value, self)?;
|
||||
|
@ -3075,7 +3103,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
if target.is_empty() {
|
||||
new_target_clip = Some(base_clip);
|
||||
} else if let Some(clip) = self
|
||||
.resolve_target_path(root, start, target, false)?
|
||||
.resolve_target_path(root, start, target, false, true)?
|
||||
.and_then(|o| o.as_display_object())
|
||||
.filter(|_| !self.base_clip.avm1_removed())
|
||||
// All properties invalid if base clip is removed.
|
||||
|
|
|
@ -582,10 +582,10 @@ fn sort_on_compare<'a, 'gc>(fields: &'a [(AvmString<'gc>, SortOptions)]) -> Comp
|
|||
if let [Value::Object(a), Value::Object(b)] = [a, b] {
|
||||
for (field_name, options) in fields {
|
||||
let a_prop = a
|
||||
.get_local_stored(*field_name, activation)
|
||||
.get_local_stored(*field_name, activation, false)
|
||||
.unwrap_or(Value::Undefined);
|
||||
let b_prop = b
|
||||
.get_local_stored(*field_name, activation)
|
||||
.get_local_stored(*field_name, activation, false)
|
||||
.unwrap_or(Value::Undefined);
|
||||
|
||||
let result = sort_compare(activation, &a_prop, &b_prop, *options)?;
|
||||
|
|
|
@ -391,7 +391,7 @@ fn clone<'gc>(
|
|||
if !bitmap_data.disposed() {
|
||||
return Ok(new_bitmap_data(
|
||||
activation.context.gc_context,
|
||||
this.get_local_stored("__proto__", activation),
|
||||
this.get_local_stored("__proto__", activation, false),
|
||||
operations::clone(bitmap_data),
|
||||
)
|
||||
.into());
|
||||
|
@ -756,8 +756,8 @@ fn hit_test<'gc>(
|
|||
.unwrap_or(&Value::Undefined)
|
||||
.coerce_to_object(activation);
|
||||
let top_left = if let (Some(x), Some(y)) = (
|
||||
first_point.get_local_stored("x", activation),
|
||||
first_point.get_local_stored("y", activation),
|
||||
first_point.get_local_stored("x", activation, false),
|
||||
first_point.get_local_stored("y", activation, false),
|
||||
) {
|
||||
(x.coerce_to_i32(activation)?, y.coerce_to_i32(activation)?)
|
||||
} else {
|
||||
|
@ -787,8 +787,8 @@ fn hit_test<'gc>(
|
|||
.unwrap_or(&Value::Undefined)
|
||||
.coerce_to_object(activation);
|
||||
let second_point = if let (Some(x), Some(y)) = (
|
||||
second_point.get_local_stored("x", activation),
|
||||
second_point.get_local_stored("y", activation),
|
||||
second_point.get_local_stored("x", activation, false),
|
||||
second_point.get_local_stored("y", activation, false),
|
||||
) {
|
||||
(x.coerce_to_i32(activation)?, y.coerce_to_i32(activation)?)
|
||||
} else {
|
||||
|
@ -814,10 +814,10 @@ fn hit_test<'gc>(
|
|||
// Determine what kind of Object we have, point or rectangle.
|
||||
// Duck-typed dumb objects are allowed.
|
||||
let compare_fields = (
|
||||
compare_object.get_local_stored("x", activation),
|
||||
compare_object.get_local_stored("y", activation),
|
||||
compare_object.get_local_stored("width", activation),
|
||||
compare_object.get_local_stored("height", activation),
|
||||
compare_object.get_local_stored("x", activation, false),
|
||||
compare_object.get_local_stored("y", activation, false),
|
||||
compare_object.get_local_stored("width", activation, false),
|
||||
compare_object.get_local_stored("height", activation, false),
|
||||
);
|
||||
match compare_fields {
|
||||
// BitmapData vs. point
|
||||
|
@ -1144,10 +1144,10 @@ fn pixel_dissolve<'gc>(
|
|||
.coerce_to_object(activation);
|
||||
let (src_min_x, src_min_y, src_width, src_height) =
|
||||
if let (Some(x), Some(y), Some(width), Some(height)) = (
|
||||
source_rect.get_local_stored("x", activation),
|
||||
source_rect.get_local_stored("y", activation),
|
||||
source_rect.get_local_stored("width", activation),
|
||||
source_rect.get_local_stored("height", activation),
|
||||
source_rect.get_local_stored("x", activation, false),
|
||||
source_rect.get_local_stored("y", activation, false),
|
||||
source_rect.get_local_stored("width", activation, false),
|
||||
source_rect.get_local_stored("height", activation, false),
|
||||
) {
|
||||
(
|
||||
x.coerce_to_f64(activation)? as i32,
|
||||
|
@ -1365,7 +1365,7 @@ fn compare<'gc>(
|
|||
match operations::compare(this_bitmap_data, other_bitmap_data) {
|
||||
Some(bitmap_data) => Ok(new_bitmap_data(
|
||||
activation.context.gc_context,
|
||||
this.get_local_stored("__proto__", activation),
|
||||
this.get_local_stored("__proto__", activation, false),
|
||||
bitmap_data,
|
||||
)
|
||||
.into()),
|
||||
|
@ -1406,7 +1406,7 @@ fn load_bitmap<'gc>(
|
|||
);
|
||||
Ok(new_bitmap_data(
|
||||
activation.context.gc_context,
|
||||
this.get_local_stored("prototype", activation),
|
||||
this.get_local_stored("prototype", activation, false),
|
||||
bitmap_data,
|
||||
)
|
||||
.into())
|
||||
|
|
|
@ -67,7 +67,7 @@ pub fn clone<'gc>(
|
|||
),
|
||||
_ => return Ok(Value::Undefined),
|
||||
};
|
||||
let proto = this.get_local_stored("__proto__", activation);
|
||||
let proto = this.get_local_stored("__proto__", activation, false);
|
||||
Ok(create_instance(activation, native, proto).into())
|
||||
}
|
||||
|
||||
|
|
|
@ -177,9 +177,9 @@ impl<'gc> DisplacementMapFilter<'gc> {
|
|||
value: Option<&Value<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
if let Some(Value::Object(object)) = value {
|
||||
if let Some(x) = object.get_local_stored("x", activation) {
|
||||
if let Some(x) = object.get_local_stored("x", activation, false) {
|
||||
let x = x.coerce_to_f64(activation)?.clamp_to_i32();
|
||||
if let Some(y) = object.get_local_stored("y", activation) {
|
||||
if let Some(y) = object.get_local_stored("y", activation, false) {
|
||||
let y = y.coerce_to_f64(activation)?.clamp_to_i32();
|
||||
self.0.write(activation.context.gc_context).map_point = Point::new(x, y);
|
||||
}
|
||||
|
|
|
@ -114,12 +114,12 @@ pub fn object_to_matrix_or_default<'gc>(
|
|||
) -> Result<Matrix, Error<'gc>> {
|
||||
if let (Some(a), Some(b), Some(c), Some(d), Some(tx), Some(ty)) = (
|
||||
// These lookups do not search the prototype chain and ignore virtual properties.
|
||||
object.get_local_stored("a", activation),
|
||||
object.get_local_stored("b", activation),
|
||||
object.get_local_stored("c", activation),
|
||||
object.get_local_stored("d", activation),
|
||||
object.get_local_stored("tx", activation),
|
||||
object.get_local_stored("ty", activation),
|
||||
object.get_local_stored("a", activation, false),
|
||||
object.get_local_stored("b", activation, false),
|
||||
object.get_local_stored("c", activation, false),
|
||||
object.get_local_stored("d", activation, false),
|
||||
object.get_local_stored("tx", activation, false),
|
||||
object.get_local_stored("ty", activation, false),
|
||||
) {
|
||||
let a = a.coerce_to_f64(activation)? as f32;
|
||||
let b = b.coerce_to_f64(activation)? as f32;
|
||||
|
|
|
@ -148,7 +148,7 @@ fn object_to_rectangle<'gc>(
|
|||
const NAMES: &[&str] = &["x", "y", "width", "height"];
|
||||
let mut values = [0; 4];
|
||||
for (&name, value) in NAMES.iter().zip(&mut values) {
|
||||
*value = match object.get_local_stored(name, activation) {
|
||||
*value = match object.get_local_stored(name, activation, false) {
|
||||
Some(value) => value.coerce_to_i32(activation)?,
|
||||
None => return Ok(None),
|
||||
}
|
||||
|
@ -1371,10 +1371,10 @@ fn local_to_global<'gc>(
|
|||
// It does not search the prototype chain and ignores virtual properties.
|
||||
if let (Value::Number(x), Value::Number(y)) = (
|
||||
point
|
||||
.get_local_stored("x", activation)
|
||||
.get_local_stored("x", activation, false)
|
||||
.unwrap_or(Value::Undefined),
|
||||
point
|
||||
.get_local_stored("y", activation)
|
||||
.get_local_stored("y", activation, false)
|
||||
.unwrap_or(Value::Undefined),
|
||||
) {
|
||||
let local = Point::from_pixels(x, y);
|
||||
|
@ -1537,10 +1537,10 @@ fn global_to_local<'gc>(
|
|||
// It does not search the prototype chain and ignores virtual properties.
|
||||
if let (Value::Number(x), Value::Number(y)) = (
|
||||
point
|
||||
.get_local_stored("x", activation)
|
||||
.get_local_stored("x", activation, false)
|
||||
.unwrap_or(Value::Undefined),
|
||||
point
|
||||
.get_local_stored("y", activation)
|
||||
.get_local_stored("y", activation, false)
|
||||
.unwrap_or(Value::Undefined),
|
||||
) {
|
||||
let global = Point::from_pixels(x, y);
|
||||
|
|
|
@ -89,8 +89,28 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
|
|||
&self,
|
||||
name: impl Into<AvmString<'gc>>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
is_slash_path: bool,
|
||||
) -> Option<Value<'gc>> {
|
||||
self.raw_script_object().get_local_stored(name, activation)
|
||||
self.raw_script_object()
|
||||
.get_local_stored(name, activation, is_slash_path)
|
||||
}
|
||||
|
||||
/// Retrieve a named property from the object, or its prototype.
|
||||
fn get_non_slash_path(
|
||||
&self,
|
||||
name: impl Into<AvmString<'gc>>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// TODO: Extract logic to a `lookup` function.
|
||||
let (this, proto) = if let Some(super_object) = self.as_super_object() {
|
||||
(super_object.this(), super_object.proto(activation))
|
||||
} else {
|
||||
((*self).into(), Value::Object((*self).into()))
|
||||
};
|
||||
match search_prototype(proto, name.into(), activation, this, false)? {
|
||||
Some((value, _depth)) => Ok(value),
|
||||
None => Ok(Value::Undefined),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve a named property from the object, or its prototype.
|
||||
|
@ -105,7 +125,7 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
|
|||
} else {
|
||||
((*self).into(), Value::Object((*self).into()))
|
||||
};
|
||||
match search_prototype(proto, name.into(), activation, this)? {
|
||||
match search_prototype(proto, name.into(), activation, this, true)? {
|
||||
Some((value, _depth)) => Ok(value),
|
||||
None => Ok(Value::Undefined),
|
||||
}
|
||||
|
@ -127,7 +147,7 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
|
|||
return Err(Error::PrototypeRecursionLimit);
|
||||
}
|
||||
|
||||
if let Some(value) = p.get_local_stored(name, activation) {
|
||||
if let Some(value) = p.get_local_stored(name, activation, true) {
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
|
@ -257,10 +277,11 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
|
|||
}
|
||||
}
|
||||
|
||||
let (method, depth) = match search_prototype(Value::Object(this), name, activation, this)? {
|
||||
Some((Value::Object(method), depth)) => (method, depth),
|
||||
_ => return Ok(Value::Undefined),
|
||||
};
|
||||
let (method, depth) =
|
||||
match search_prototype(Value::Object(this), name, activation, this, false)? {
|
||||
Some((Value::Object(method), depth)) => (method, depth),
|
||||
_ => return Ok(Value::Undefined),
|
||||
};
|
||||
|
||||
// If the method was found on the object itself, change `depth` as-if
|
||||
// the method was found on the object's prototype.
|
||||
|
@ -669,6 +690,7 @@ pub fn search_prototype<'gc>(
|
|||
name: AvmString<'gc>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
is_slash_path: bool,
|
||||
) -> Result<Option<(Value<'gc>, u8)>, Error<'gc>> {
|
||||
let mut depth = 0;
|
||||
|
||||
|
@ -697,7 +719,7 @@ pub fn search_prototype<'gc>(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(value) = p.get_local_stored(name, activation) {
|
||||
if let Some(value) = p.get_local_stored(name, activation, is_slash_path) {
|
||||
return Ok(Some((value, depth)));
|
||||
}
|
||||
|
||||
|
|
|
@ -155,6 +155,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|||
&self,
|
||||
name: impl Into<AvmString<'gc>>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
_is_slash_path: bool,
|
||||
) -> Option<Value<'gc>> {
|
||||
self.0
|
||||
.read()
|
||||
|
|
|
@ -193,13 +193,14 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
&self,
|
||||
name: impl Into<AvmString<'gc>>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
is_slash_path: bool,
|
||||
) -> Option<Value<'gc>> {
|
||||
let name = name.into();
|
||||
let obj = self.0.read();
|
||||
|
||||
// Property search order for DisplayObjects:
|
||||
// 1) Actual properties on the underlying object
|
||||
if let Some(value) = obj.base.get_local_stored(name, activation) {
|
||||
if let Some(value) = obj.base.get_local_stored(name, activation, is_slash_path) {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
|
@ -217,7 +218,15 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
.as_container()
|
||||
.and_then(|o| o.child_by_name(&name, activation.is_case_sensitive()))
|
||||
{
|
||||
return Some(child.object());
|
||||
return if is_slash_path {
|
||||
Some(child.object())
|
||||
// If an object doesn't have an object representation, e.g. Graphic, then trying to access it
|
||||
// Returns the parent instead
|
||||
} else if let crate::display_object::DisplayObject::Graphic(_) = child {
|
||||
child.parent().map(|p| p.object())
|
||||
} else {
|
||||
Some(child.object())
|
||||
};
|
||||
}
|
||||
|
||||
// 4) Display object properties such as `_x`, `_y` (never case sensitive)
|
||||
|
|
|
@ -75,6 +75,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
|||
&self,
|
||||
_name: impl Into<AvmString<'gc>>,
|
||||
_activation: &mut Activation<'_, 'gc>,
|
||||
_is_slash_path: bool,
|
||||
) -> Option<Value<'gc>> {
|
||||
None
|
||||
}
|
||||
|
@ -124,7 +125,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
|||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let this = self.0.this;
|
||||
let (method, depth) =
|
||||
match search_prototype(self.proto(activation), name, activation, this)? {
|
||||
match search_prototype(self.proto(activation), name, activation, this, false)? {
|
||||
Some((Value::Object(method), depth)) => (method, depth),
|
||||
_ => return Ok(Value::Undefined),
|
||||
};
|
||||
|
|
|
@ -140,7 +140,7 @@ impl<'gc> Scope<'gc> {
|
|||
if self.locals().has_property(activation, name) {
|
||||
return self
|
||||
.locals()
|
||||
.get(name, activation)
|
||||
.get_non_slash_path(name, activation)
|
||||
.map(|v| CallableValue::Callable(self.locals_cell(), v));
|
||||
}
|
||||
if let Some(scope) = self.parent() {
|
||||
|
@ -155,7 +155,7 @@ impl<'gc> Scope<'gc> {
|
|||
return activation
|
||||
.root_object()
|
||||
.coerce_to_object(activation)
|
||||
.get(name, activation)
|
||||
.get_non_slash_path(name, activation)
|
||||
.map(|v| CallableValue::Callable(self.locals_cell(), v));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
root: _level0
|
||||
_root.foo: foo1
|
||||
_root.real: _level0.real
|
||||
_root.real.foo: foo2
|
||||
get(root.real.foo): foo2
|
||||
get(root.real/foo): undefined
|
||||
get(root.real:foo): foo2
|
||||
_root.empty: _level0
|
||||
_root.empty.foo: foo1
|
||||
get(root.empty.foo): foo1
|
||||
get(root.empty/foo): undefined
|
||||
get(root.empty:foo): foo1
|
||||
_root.nothere: undefined
|
||||
_root.nothere.foo: undefined
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
num_frames = 1
|
Loading…
Reference in New Issue