Allow levels to be read as scope variables, and add a test for this.

This commit is contained in:
David Wendt 2020-02-01 14:44:25 -05:00
parent 5a7e530c91
commit 9adf0f43d7
19 changed files with 101 additions and 59 deletions

View File

@ -1450,13 +1450,17 @@ impl<'gc> Avm1<'gc> {
//Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns //Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns
//a boolean based on if the delete actually deleted something. //a boolean based on if the delete actually deleted something.
let did_exist = self.current_stack_frame().unwrap().read().is_defined(name); let did_exist = self
.current_stack_frame()
self.current_stack_frame()
.unwrap() .unwrap()
.read() .read()
.scope() .is_defined(context, name);
.delete(name, context.gc_context);
self.current_stack_frame().unwrap().read().scope().delete(
context,
name,
context.gc_context,
);
self.push(did_exist); self.push(did_exist);
Ok(()) Ok(())

View File

@ -331,7 +331,7 @@ impl<'gc> Activation<'gc> {
} }
/// Check if a particular property in the scope chain is defined. /// 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" { if name == "this" {
return true; return true;
} }
@ -340,7 +340,7 @@ impl<'gc> Activation<'gc> {
return true; return true;
} }
self.scope().is_defined(name) self.scope().is_defined(context, name)
} }
/// Define a named local variable within this activation. /// Define a named local variable within this activation.

View File

@ -544,12 +544,12 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
.add_property(gc_context, name, get, set, attributes) .add_property(gc_context, name, get, set, attributes)
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
self.base.has_property(name) 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.base.has_own_property(name) self.base.has_own_property(context, name)
} }
fn is_property_overwritable(&self, name: &str) -> bool { fn is_property_overwritable(&self, name: &str) -> bool {

View File

@ -66,12 +66,12 @@ pub fn add_property<'gc>(
/// Implements `Object.prototype.hasOwnProperty` /// Implements `Object.prototype.hasOwnProperty`
pub fn has_own_property<'gc>( pub fn has_own_property<'gc>(
_avm: &mut Avm1<'gc>, _avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
match args.get(0) { 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()), _ => Ok(Value::Bool(false).into()),
} }
} }

View File

@ -58,7 +58,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> { ) -> 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()) self.get_local(name, avm, context, (*self).into())
} else { } else {
search_prototype(self.proto(), name, avm, context, (*self).into()) 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. /// 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, /// Checks if the object has a given named property on itself (and not,
/// say, the object's prototype or superclass) /// 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. /// Checks if a named property can be overwritten.
fn is_property_overwritable(&self, name: &str) -> bool; 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()); 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); return proto.unwrap().get_local(name, avm, context, this);
} }

View File

@ -232,7 +232,7 @@ impl<'gc> Scope<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>, this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
if self.locals().has_property(name) { if self.locals().has_property(context, name) {
return self.locals().get(name, avm, context); return self.locals().get(name, avm, context);
} }
if let Some(scope) = self.parent() { 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. /// 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 self.locals().has_property(name) { if self.locals().has_property(context, name) {
return true; return true;
} }
if let Some(scope) = self.parent() { if let Some(scope) = self.parent() {
return scope.is_defined(name); return scope.is_defined(context, name);
} }
false false
@ -271,7 +271,8 @@ impl<'gc> Scope<'gc> {
this: Object<'gc>, this: Object<'gc>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if self.class == ScopeClass::Target 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. // Value found on this object, so overwrite it.
// Or we've hit the executing movie clip, so create it here. // 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 /// Delete a value from scope
pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) -> bool { pub fn delete(
if self.locals().has_property(name) { &self,
context: &mut UpdateContext<'_, 'gc, '_>,
name: &str,
mc: MutationContext<'gc, '_>,
) -> bool {
if self.locals().has_property(context, name) {
return self.locals().delete(mc, name); return self.locals().delete(mc, name);
} }
if let Some(scope) = self.parent() { if let Some(scope) = self.parent() {
return scope.delete(name, mc); return scope.delete(context, name, mc);
} }
false false

View File

@ -379,17 +379,17 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
} }
/// Checks if the object has a given named property. /// 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 {
self.has_own_property(name) self.has_own_property(context, name)
|| self || self
.proto() .proto()
.as_ref() .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, /// Checks if the object has a given named property on itself (and not,
/// say, the object's prototype or superclass) /// 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__" { if name == "__proto__" {
return true; return true;
} }

View File

@ -213,12 +213,12 @@ impl<'gc> TObject<'gc> for SoundObject<'gc> {
.add_property(gc_context, name, get, set, attributes) .add_property(gc_context, name, get, set, attributes)
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
self.base().has_property(name) 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.base().has_own_property(name) self.base().has_own_property(context, name)
} }
fn is_property_overwritable(&self, name: &str) -> bool { fn is_property_overwritable(&self, name: &str) -> bool {

View File

@ -65,7 +65,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
let props = avm.display_properties; let props = avm.display_properties;
// Property search order for DisplayObjects: // 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 // 1) Actual properties on the underlying object
self.get_local(name, avm, context, (*self).into()) self.get_local(name, avm, context, (*self).into())
} else if let Some(property) = props.read().get_by_name(&name) { } 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) { } else if let Some(child) = self.display_object.get_child_by_name(name) {
// 3) Child display objects with the given instance name // 3) Child display objects with the given instance name
Ok(child.object().into()) 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 { } else {
// 4) Prototype // 5) Prototype
crate::avm1::object::search_prototype(self.proto(), name, avm, context, (*self).into()) crate::avm1::object::search_prototype(self.proto(), name, avm, context, (*self).into())
} }
// 4) TODO: __resolve? // 4) TODO: __resolve?
@ -100,7 +103,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let props = avm.display_properties; 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 // 1) Actual proeprties on the underlying object
self.base self.base
.internal_set(name, value, avm, context, (*self).into()) .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) .add_property(gc_context, name, get, set, attributes)
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
if self.base.has_property(name) { if self.base.has_property(context, name) {
return true; return true;
} }
@ -184,12 +187,20 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
return true; return true;
} }
if self
.display_object
.get_layer_by_name(name, context)
.is_some()
{
return true;
}
false 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. // 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 { fn is_property_enumerable(&self, name: &str) -> bool {

View File

@ -161,12 +161,12 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
//`super` cannot have properties defined on it //`super` cannot have properties defined on it
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
self.0.read().child.has_property(name) self.0.read().child.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.0.read().child.has_own_property(name) self.0.read().child.has_own_property(context, name)
} }
fn is_property_enumerable(&self, name: &str) -> bool { fn is_property_enumerable(&self, name: &str) -> bool {

View File

@ -210,12 +210,12 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> {
self.0.read().base.proto() self.0.read().base.proto()
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
self.0.read().base.has_property(name) self.0.read().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.0.read().base.has_own_property(name) self.0.read().base.has_own_property(context, name)
} }
fn is_property_overwritable(&self, name: &str) -> bool { fn is_property_overwritable(&self, name: &str) -> bool {

View File

@ -152,11 +152,11 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> {
self.base().proto() self.base().proto()
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
self.base().has_property(name) 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() self.node()
.attribute_value(&XMLName::from_str(name)) .attribute_value(&XMLName::from_str(name))
.is_some() .is_some()

View File

@ -146,12 +146,13 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> {
self.base().proto() self.base().proto()
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
self.base().has_property(name) 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.document().get_node_by_id(name).is_some() || self.base().has_own_property(name) self.document().get_node_by_id(name).is_some()
|| self.base().has_own_property(context, name)
} }
fn is_property_overwritable(&self, name: &str) -> bool { fn is_property_overwritable(&self, name: &str) -> bool {

View File

@ -140,12 +140,12 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> {
self.base().proto() self.base().proto()
} }
fn has_property(&self, name: &str) -> bool { fn has_property(&self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool {
self.base().has_property(name) 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.base().has_own_property(name) self.base().has_own_property(context, name)
} }
fn is_property_overwritable(&self, name: &str) -> bool { fn is_property_overwritable(&self, name: &str) -> bool {

View File

@ -634,6 +634,18 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into<DisplayObject<'gc>>
// TODO: Make a HashMap from name -> child? // TODO: Make a HashMap from name -> child?
self.children().find(|child| &*child.name() == name) 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 removed(&self) -> bool;
fn set_removed(&mut self, context: MutationContext<'gc, '_>, value: bool); fn set_removed(&mut self, context: MutationContext<'gc, '_>, value: bool);

View File

@ -174,6 +174,7 @@ swf_tests! {
(loadvariables_method, "avm1/loadvariables_method", 3), (loadvariables_method, "avm1/loadvariables_method", 3),
(xml_load, "avm1/xml_load", 1), (xml_load, "avm1/xml_load", 1),
(cross_movie_root, "avm1/cross_movie_root", 5), (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. // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.

View File

@ -0,0 +1,7 @@
_level0
_level0
_level0
_level0
true
true
true

Binary file not shown.

Binary file not shown.