Allow levels to be read as scope variables, and add a test for this.
This commit is contained in:
parent
5a7e530c91
commit
9adf0f43d7
|
@ -1450,13 +1450,17 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
//Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns
|
||||
//a boolean based on if the delete actually deleted something.
|
||||
let did_exist = self.current_stack_frame().unwrap().read().is_defined(name);
|
||||
|
||||
self.current_stack_frame()
|
||||
let did_exist = self
|
||||
.current_stack_frame()
|
||||
.unwrap()
|
||||
.read()
|
||||
.scope()
|
||||
.delete(name, context.gc_context);
|
||||
.is_defined(context, name);
|
||||
|
||||
self.current_stack_frame().unwrap().read().scope().delete(
|
||||
context,
|
||||
name,
|
||||
context.gc_context,
|
||||
);
|
||||
self.push(did_exist);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -331,7 +331,7 @@ impl<'gc> Activation<'gc> {
|
|||
}
|
||||
|
||||
/// Check if a particular property in the scope chain is defined.
|
||||
pub fn is_defined(&self, name: &str) -> bool {
|
||||
pub fn is_defined(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
if name == "this" {
|
||||
return true;
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ impl<'gc> Activation<'gc> {
|
|||
return true;
|
||||
}
|
||||
|
||||
self.scope().is_defined(name)
|
||||
self.scope().is_defined(context, name)
|
||||
}
|
||||
|
||||
/// Define a named local variable within this activation.
|
||||
|
|
|
@ -544,12 +544,12 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
|
|||
.add_property(gc_context, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base.has_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base.has_property(context, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.base.has_own_property(name)
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base.has_own_property(context, name)
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
|
|
|
@ -66,12 +66,12 @@ pub fn add_property<'gc>(
|
|||
/// Implements `Object.prototype.hasOwnProperty`
|
||||
pub fn has_own_property<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
match args.get(0) {
|
||||
Some(Value::String(name)) => Ok(Value::Bool(this.has_own_property(name)).into()),
|
||||
Some(Value::String(name)) => Ok(Value::Bool(this.has_own_property(context, name)).into()),
|
||||
_ => Ok(Value::Bool(false).into()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if self.has_own_property(name) {
|
||||
if self.has_own_property(context, name) {
|
||||
self.get_local(name, avm, context, (*self).into())
|
||||
} else {
|
||||
search_prototype(self.proto(), name, avm, context, (*self).into())
|
||||
|
@ -172,11 +172,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
);
|
||||
|
||||
/// Checks if the object has a given named property.
|
||||
fn has_property(&self, name: &str) -> bool;
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool;
|
||||
|
||||
/// Checks if the object has a given named property on itself (and not,
|
||||
/// say, the object's prototype or superclass)
|
||||
fn has_own_property(&self, name: &str) -> bool;
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool;
|
||||
|
||||
/// Checks if a named property can be overwritten.
|
||||
fn is_property_overwritable(&self, name: &str) -> bool;
|
||||
|
@ -362,7 +362,7 @@ pub fn search_prototype<'gc>(
|
|||
return Err("Encountered an excessively deep prototype chain.".into());
|
||||
}
|
||||
|
||||
if proto.unwrap().has_own_property(name) {
|
||||
if proto.unwrap().has_own_property(context, name) {
|
||||
return proto.unwrap().get_local(name, avm, context, this);
|
||||
}
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ impl<'gc> Scope<'gc> {
|
|||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if self.locals().has_property(name) {
|
||||
if self.locals().has_property(context, name) {
|
||||
return self.locals().get(name, avm, context);
|
||||
}
|
||||
if let Some(scope) = self.parent() {
|
||||
|
@ -244,13 +244,13 @@ impl<'gc> Scope<'gc> {
|
|||
}
|
||||
|
||||
/// Check if a particular property in the scope chain is defined.
|
||||
pub fn is_defined(&self, name: &str) -> bool {
|
||||
if self.locals().has_property(name) {
|
||||
pub fn is_defined(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
if self.locals().has_property(context, name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(scope) = self.parent() {
|
||||
return scope.is_defined(name);
|
||||
return scope.is_defined(context, name);
|
||||
}
|
||||
|
||||
false
|
||||
|
@ -271,7 +271,8 @@ impl<'gc> Scope<'gc> {
|
|||
this: Object<'gc>,
|
||||
) -> Result<(), Error> {
|
||||
if self.class == ScopeClass::Target
|
||||
|| (self.locals().has_property(name) && self.locals().is_property_overwritable(name))
|
||||
|| (self.locals().has_property(context, name)
|
||||
&& self.locals().is_property_overwritable(name))
|
||||
{
|
||||
// Value found on this object, so overwrite it.
|
||||
// Or we've hit the executing movie clip, so create it here.
|
||||
|
@ -300,13 +301,18 @@ impl<'gc> Scope<'gc> {
|
|||
}
|
||||
|
||||
/// Delete a value from scope
|
||||
pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) -> bool {
|
||||
if self.locals().has_property(name) {
|
||||
pub fn delete(
|
||||
&self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
name: &str,
|
||||
mc: MutationContext<'gc, '_>,
|
||||
) -> bool {
|
||||
if self.locals().has_property(context, name) {
|
||||
return self.locals().delete(mc, name);
|
||||
}
|
||||
|
||||
if let Some(scope) = self.parent() {
|
||||
return scope.delete(name, mc);
|
||||
return scope.delete(context, name, mc);
|
||||
}
|
||||
|
||||
false
|
||||
|
|
|
@ -379,17 +379,17 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|||
}
|
||||
|
||||
/// Checks if the object has a given named property.
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.has_own_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.has_own_property(context, name)
|
||||
|| self
|
||||
.proto()
|
||||
.as_ref()
|
||||
.map_or(false, |p| p.has_property(name))
|
||||
.map_or(false, |p| p.has_property(context, name))
|
||||
}
|
||||
|
||||
/// Checks if the object has a given named property on itself (and not,
|
||||
/// say, the object's prototype or superclass)
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
fn has_own_property(&self, _context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
if name == "__proto__" {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -213,12 +213,12 @@ impl<'gc> TObject<'gc> for SoundObject<'gc> {
|
|||
.add_property(gc_context, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base().has_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base().has_property(context, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.base().has_own_property(name)
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base().has_own_property(context, name)
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
|
|
|
@ -65,7 +65,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
let props = avm.display_properties;
|
||||
// Property search order for DisplayObjects:
|
||||
if self.has_own_property(name) {
|
||||
if self.has_own_property(context, name) {
|
||||
// 1) Actual properties on the underlying object
|
||||
self.get_local(name, avm, context, (*self).into())
|
||||
} else if let Some(property) = props.read().get_by_name(&name) {
|
||||
|
@ -75,8 +75,11 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
} else if let Some(child) = self.display_object.get_child_by_name(name) {
|
||||
// 3) Child display objects with the given instance name
|
||||
Ok(child.object().into())
|
||||
} else if let Some(layer) = self.display_object.get_layer_by_name(name, context) {
|
||||
// 4) Top-level layers
|
||||
Ok(layer.object().into())
|
||||
} else {
|
||||
// 4) Prototype
|
||||
// 5) Prototype
|
||||
crate::avm1::object::search_prototype(self.proto(), name, avm, context, (*self).into())
|
||||
}
|
||||
// 4) TODO: __resolve?
|
||||
|
@ -100,7 +103,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Result<(), Error> {
|
||||
let props = avm.display_properties;
|
||||
if self.base.has_own_property(name) {
|
||||
if self.base.has_own_property(context, name) {
|
||||
// 1) Actual proeprties on the underlying object
|
||||
self.base
|
||||
.internal_set(name, value, avm, context, (*self).into())
|
||||
|
@ -175,8 +178,8 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
.add_property(gc_context, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
if self.base.has_property(name) {
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
if self.base.has_property(context, name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -184,12 +187,20 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
return true;
|
||||
}
|
||||
|
||||
if self
|
||||
.display_object
|
||||
.get_layer_by_name(name, context)
|
||||
.is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
// Note that `hasOwnProperty` does NOT return true for child display objects.
|
||||
self.base.has_own_property(name)
|
||||
self.base.has_own_property(context, name)
|
||||
}
|
||||
|
||||
fn is_property_enumerable(&self, name: &str) -> bool {
|
||||
|
|
|
@ -161,12 +161,12 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
|||
//`super` cannot have properties defined on it
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.0.read().child.has_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().child.has_property(context, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.0.read().child.has_own_property(name)
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().child.has_own_property(context, name)
|
||||
}
|
||||
|
||||
fn is_property_enumerable(&self, name: &str) -> bool {
|
||||
|
|
|
@ -210,12 +210,12 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> {
|
|||
self.0.read().base.proto()
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.0.read().base.has_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().base.has_property(context, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.0.read().base.has_own_property(name)
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().base.has_own_property(context, name)
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
|
|
|
@ -152,11 +152,11 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> {
|
|||
self.base().proto()
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base().has_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base().has_property(context, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
fn has_own_property(&self, _context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.node()
|
||||
.attribute_value(&XMLName::from_str(name))
|
||||
.is_some()
|
||||
|
|
|
@ -146,12 +146,13 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> {
|
|||
self.base().proto()
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base().has_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base().has_property(context, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.document().get_node_by_id(name).is_some() || self.base().has_own_property(name)
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.document().get_node_by_id(name).is_some()
|
||||
|| self.base().has_own_property(context, name)
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
|
|
|
@ -140,12 +140,12 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> {
|
|||
self.base().proto()
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base().has_property(name)
|
||||
fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base().has_property(context, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.base().has_own_property(name)
|
||||
fn has_own_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.base().has_own_property(context, name)
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
|
|
|
@ -634,6 +634,18 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into<DisplayObject<'gc>>
|
|||
// TODO: Make a HashMap from name -> child?
|
||||
self.children().find(|child| &*child.name() == name)
|
||||
}
|
||||
/// Get another layer by layer name.
|
||||
fn get_layer_by_name(
|
||||
&self,
|
||||
name: &str,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Option<DisplayObject<'gc>> {
|
||||
context
|
||||
.layers
|
||||
.values()
|
||||
.find(|layer| &*layer.name() == name)
|
||||
.copied()
|
||||
}
|
||||
fn removed(&self) -> bool;
|
||||
fn set_removed(&mut self, context: MutationContext<'gc, '_>, value: bool);
|
||||
|
||||
|
|
|
@ -174,6 +174,7 @@ swf_tests! {
|
|||
(loadvariables_method, "avm1/loadvariables_method", 3),
|
||||
(xml_load, "avm1/xml_load", 1),
|
||||
(cross_movie_root, "avm1/cross_movie_root", 5),
|
||||
(roots_and_levels, "avm1/roots_and_levels", 1),
|
||||
}
|
||||
|
||||
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
_level0
|
||||
_level0
|
||||
_level0
|
||||
_level0
|
||||
true
|
||||
true
|
||||
true
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue