diff --git a/core/src/avm1.rs b/core/src/avm1.rs index fa5ae180b..7be79f764 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -1433,11 +1433,13 @@ impl<'gc> Avm1<'gc> { let constructor = object .read() - .get(&method_name.as_string()?, self, context, object.to_owned()) + .get(&method_name.as_string()?, self, context, object.to_owned())? + .resolve(self, context)? .as_object()?; let proto = constructor .read() - .get("prototype", self, context, constructor.to_owned()); + .get("prototype", self, context, constructor.to_owned())? + .resolve(self, context)?; let this = GcCell::allocate( context.gc_context, Object::object( @@ -1447,7 +1449,10 @@ impl<'gc> Avm1<'gc> { ); //TODO: What happens if you `ActionNewMethod` without a method name? - constructor.read().call(self, context, this, &args); + constructor + .read() + .call(self, context, this, &args)? + .ignore(); //TODO: The SWF docs are really cagey about what the hell happens with //user defined constructors... do we need stack continuations?! @@ -1470,11 +1475,13 @@ impl<'gc> Avm1<'gc> { .unwrap() .clone() .read() - .resolve(fn_name.as_string()?, self, context) + .resolve(fn_name.as_string()?, self, context)? + .resolve(self, context)? .as_object()?; let proto = constructor .read() - .get("prototype", self, context, constructor.to_owned()); + .get("prototype", self, context, constructor.to_owned())? + .resolve(self, context)?; let this = GcCell::allocate( context.gc_context, Object::object( @@ -1483,7 +1490,10 @@ impl<'gc> Avm1<'gc> { ), ); - constructor.read().call(self, context, this, &args); + constructor + .read() + .call(self, context, this, &args)? + .ignore(); self.push(Value::Undefined); diff --git a/core/src/avm1/globals/object.rs b/core/src/avm1/globals/object.rs index 9d6ac1d06..bc4558dad 100644 --- a/core/src/avm1/globals/object.rs +++ b/core/src/avm1/globals/object.rs @@ -12,7 +12,7 @@ pub fn add_property<'gc>( context: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Value<'gc> { +) -> Result, Error> { let name = args.get(0).unwrap_or(&Value::Undefined); let getter = args.get(1).unwrap_or(&Value::Undefined); let setter = args.get(2).unwrap_or(&Value::Undefined); @@ -29,19 +29,19 @@ pub fn add_property<'gc>( EnumSet::empty(), ); } else { - return Value::Bool(false); + return Ok(false.into()); } } else if let Value::Null = setter { this.write(context.gc_context) .force_set_virtual(name, get_func, None, ReadOnly); } else { - return Value::Bool(false); + return Ok(false.into()); } } - Value::Bool(false) + Ok(false.into()) } - _ => Value::Bool(false), + _ => Ok(false.into()), } } @@ -68,6 +68,46 @@ fn to_string<'gc>( Ok(ReturnValue::Immediate("[Object object]".into())) } +/// Implements `Object.prototype.isPropertyEnumerable` +fn is_property_enumerable<'gc>( + _: &mut Avm1<'gc>, + _: &mut UpdateContext<'_, 'gc, '_>, + this: GcCell<'gc, Object<'gc>>, + args: &[Value<'gc>], +) -> Result, Error> { + match args.get(0) { + Some(Value::String(name)) => { + Ok(Value::Bool(this.read().is_property_enumerable(name)).into()) + } + _ => Ok(Value::Bool(false).into()), + } +} + +/// Implements `Object.prototype.isPrototypeOf` +fn is_prototype_of<'gc>( + _: &mut Avm1<'gc>, + _: &mut UpdateContext<'_, 'gc, '_>, + this: GcCell<'gc, Object<'gc>>, + args: &[Value<'gc>], +) -> Result, Error> { + match args.get(0) { + Some(Value::Object(ob)) => { + let mut proto = ob.read().prototype().cloned(); + + while let Some(proto_ob) = proto { + if GcCell::ptr_eq(this, proto_ob) { + return Ok(Value::Bool(true).into()); + } + + proto = proto_ob.read().prototype().cloned(); + } + + Ok(Value::Bool(false).into()) + } + _ => Ok(Value::Bool(false).into()), + } +} + /// Partially construct `Object.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -88,14 +128,28 @@ pub fn fill_proto<'gc>( "addProperty", add_property, gc_context, - EnumSet::empty(), + DontDelete | DontEnum, Some(fn_proto), ); ob_proto_write.force_set_function( "hasOwnProperty", has_own_property, gc_context, - EnumSet::empty(), + DontDelete | DontEnum, + Some(fn_proto), + ); + ob_proto_write.force_set_function( + "isPropertyEnumerable", + is_property_enumerable, + gc_context, + DontDelete | DontEnum, + Some(fn_proto), + ); + ob_proto_write.force_set_function( + "isPrototypeOf", + is_prototype_of, + gc_context, + DontDelete | DontEnum, Some(fn_proto), ); ob_proto_write.force_set_function( diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index a1f75fc5a..07b20e164 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -389,6 +389,14 @@ impl<'gc> Object<'gc> { .unwrap_or(false) } + pub fn is_property_enumerable(&self, name: &str) -> bool { + if let Some(prop) = self.values.get(name) { + prop.is_enumerable() + } else { + false + } + } + pub fn get_keys(&self) -> Vec { self.values .iter() @@ -435,7 +443,7 @@ impl<'gc> Object<'gc> { /// Returns a copy of a given function. /// /// TODO: We have to clone here because of how executables are stored on - /// objects. This might not be a good idea for performance. + /// objects directly. This might not be a good idea for performance. pub fn as_executable(&self) -> Option> { self.function.clone() }