Implement `isPropertyEnumerable` and `isPrototypeOf`.

This commit is contained in:
David Wendt 2019-10-19 11:06:33 -04:00
parent aa7997b658
commit b4e9b8442e
3 changed files with 86 additions and 14 deletions

View File

@ -1433,11 +1433,13 @@ impl<'gc> Avm1<'gc> {
let constructor = object let constructor = object
.read() .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()?; .as_object()?;
let proto = constructor let proto = constructor
.read() .read()
.get("prototype", self, context, constructor.to_owned()); .get("prototype", self, context, constructor.to_owned())?
.resolve(self, context)?;
let this = GcCell::allocate( let this = GcCell::allocate(
context.gc_context, context.gc_context,
Object::object( Object::object(
@ -1447,7 +1449,10 @@ impl<'gc> Avm1<'gc> {
); );
//TODO: What happens if you `ActionNewMethod` without a method name? //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 //TODO: The SWF docs are really cagey about what the hell happens with
//user defined constructors... do we need stack continuations?! //user defined constructors... do we need stack continuations?!
@ -1470,11 +1475,13 @@ impl<'gc> Avm1<'gc> {
.unwrap() .unwrap()
.clone() .clone()
.read() .read()
.resolve(fn_name.as_string()?, self, context) .resolve(fn_name.as_string()?, self, context)?
.resolve(self, context)?
.as_object()?; .as_object()?;
let proto = constructor let proto = constructor
.read() .read()
.get("prototype", self, context, constructor.to_owned()); .get("prototype", self, context, constructor.to_owned())?
.resolve(self, context)?;
let this = GcCell::allocate( let this = GcCell::allocate(
context.gc_context, context.gc_context,
Object::object( 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); self.push(Value::Undefined);

View File

@ -12,7 +12,7 @@ pub fn add_property<'gc>(
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Value<'gc> { ) -> Result<ReturnValue<'gc>, Error> {
let name = args.get(0).unwrap_or(&Value::Undefined); let name = args.get(0).unwrap_or(&Value::Undefined);
let getter = args.get(1).unwrap_or(&Value::Undefined); let getter = args.get(1).unwrap_or(&Value::Undefined);
let setter = args.get(2).unwrap_or(&Value::Undefined); let setter = args.get(2).unwrap_or(&Value::Undefined);
@ -29,19 +29,19 @@ pub fn add_property<'gc>(
EnumSet::empty(), EnumSet::empty(),
); );
} else { } else {
return Value::Bool(false); return Ok(false.into());
} }
} else if let Value::Null = setter { } else if let Value::Null = setter {
this.write(context.gc_context) this.write(context.gc_context)
.force_set_virtual(name, get_func, None, ReadOnly); .force_set_virtual(name, get_func, None, ReadOnly);
} else { } 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())) 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<ReturnValue<'gc>, 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<ReturnValue<'gc>, 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`. /// Partially construct `Object.prototype`.
/// ///
/// `__proto__` and other cross-linked properties of this object will *not* /// `__proto__` and other cross-linked properties of this object will *not*
@ -88,14 +128,28 @@ pub fn fill_proto<'gc>(
"addProperty", "addProperty",
add_property, add_property,
gc_context, gc_context,
EnumSet::empty(), DontDelete | DontEnum,
Some(fn_proto), Some(fn_proto),
); );
ob_proto_write.force_set_function( ob_proto_write.force_set_function(
"hasOwnProperty", "hasOwnProperty",
has_own_property, has_own_property,
gc_context, 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), Some(fn_proto),
); );
ob_proto_write.force_set_function( ob_proto_write.force_set_function(

View File

@ -389,6 +389,14 @@ impl<'gc> Object<'gc> {
.unwrap_or(false) .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<String> { pub fn get_keys(&self) -> Vec<String> {
self.values self.values
.iter() .iter()
@ -435,7 +443,7 @@ impl<'gc> Object<'gc> {
/// Returns a copy of a given function. /// Returns a copy of a given function.
/// ///
/// TODO: We have to clone here because of how executables are stored on /// 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<Executable<'gc>> { pub fn as_executable(&self) -> Option<Executable<'gc>> {
self.function.clone() self.function.clone()
} }