diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 098cee533..6e8b9f01b 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -571,6 +571,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_enumerant_name(index) } + fn property_is_enumerable(&self, name: &QName) -> bool { + self.0.read().base.property_is_enumerable(name) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 7d0c944a3..74677f7eb 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -64,6 +64,28 @@ pub fn is_prototype_of<'gc>( Ok(false.into()) } +/// `Object.prototype.propertyIsEnumerable` +pub fn property_is_enumerable<'gc>( + _avm: &mut Avm2<'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let this: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); + let this = this?; + let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); + let name = name?.as_string()?; + + if let Some(ns) = this.resolve_any(&name)? { + if !ns.is_private() { + let qname = QName::new(ns, &name); + return Ok(this.property_is_enumerable(&qname).into()); + } + } + + Ok(false.into()) +} + /// Partially construct `Object.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -90,4 +112,10 @@ pub fn fill_proto<'gc>( 0, FunctionObject::from_builtin(gc_context, is_prototype_of, fn_proto), ); + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "propertyIsEnumerable"), + 0, + FunctionObject::from_builtin(gc_context, property_is_enumerable, fn_proto), + ); } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 37cd6bf23..99a820a58 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -352,6 +352,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// mechanism. fn get_enumerant_name(&self, index: u32) -> Option; + /// Determine if a property is currently enumerable. + /// + /// Properties that do not exist are also not enumerable. + fn property_is_enumerable(&self, name: &QName) -> bool; + /// Install a method (or any other non-slot value) on an object. fn install_method( &mut self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index b0700fa5c..eb2e14580 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -202,6 +202,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_enumerant_name(index) } + fn property_is_enumerable(&self, name: &QName) -> bool { + self.0.read().property_is_enumerable(name) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } @@ -750,6 +754,10 @@ impl<'gc> ScriptObjectData<'gc> { self.enumerants.get(true_index).cloned() } + pub fn property_is_enumerable(&self, name: &QName) -> bool { + self.enumerants.contains(name) + } + /// Install a method into the object. pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { if disp_id > 0 {