diff --git a/core/src/avm1/object/script_object.rs b/core/src/avm1/object/script_object.rs index 4b6b6bcfa..1c8f8f39a 100644 --- a/core/src/avm1/object/script_object.rs +++ b/core/src/avm1/object/script_object.rs @@ -129,16 +129,11 @@ impl<'gc> ScriptObject<'gc> { /// Doesn't look up the prototype chain and ignores virtual properties, thus cannot cause /// any side-effects. pub fn get_data(&self, name: &str, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> { - if let Some(Property::Stored { value, .. }) = self - .0 + self.0 .read() .values .get(name, activation.is_case_sensitive()) - { - value.to_owned() - } else { - Value::Undefined - } + .map_or(Value::Undefined, |property| property.data()) } /// Sets a data property on this object. @@ -158,15 +153,8 @@ impl<'gc> ScriptObject<'gc> { .values .entry(name, activation.is_case_sensitive()) { - Entry::Occupied(mut entry) => { - entry.get_mut().set(value); - } - Entry::Vacant(entry) => { - entry.insert(Property::Stored { - value, - attributes: Attribute::empty(), - }); - } + Entry::Occupied(mut entry) => entry.get_mut().set_data(value), + Entry::Vacant(entry) => entry.insert(Property::new_stored(value, Attribute::empty())), } Ok(()) } @@ -186,8 +174,13 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .values .get(name, activation.is_case_sensitive()) { - Some(Property::Virtual { get, .. }) => get.to_owned(), - Some(Property::Stored { value, .. }) => return Some(Ok(value.to_owned())), + Some(property) => { + if let Some(getter) = property.getter() { + getter + } else { + return Some(Ok(property.data())); + } + } None => return None, }; @@ -249,12 +242,13 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .values .entry(name, activation.is_case_sensitive()) { - Entry::Occupied(mut entry) => entry.get_mut().set(value), + Entry::Occupied(mut entry) => { + let entry = entry.get_mut(); + entry.set_data(value); + entry.setter() + } Entry::Vacant(entry) => { - entry.insert(Property::Stored { - value, - attributes: Attribute::empty(), - }); + entry.insert(Property::new_stored(value, Attribute::empty())); None } }; @@ -297,18 +291,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn call_setter( &self, name: &str, - value: Value<'gc>, + _value: Value<'gc>, activation: &mut Activation<'_, 'gc, '_>, ) -> Option> { - match self - .0 - .write(activation.context.gc_context) + self.0 + .read() .values - .get_mut(name, activation.is_case_sensitive()) - { - Some(propref) if propref.is_virtual() => propref.set(value), - _ => None, - } + .get(name, activation.is_case_sensitive()) + .and_then(|property| property.setter()) } fn create_bare_object( @@ -341,38 +331,33 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { &self, gc_context: MutationContext<'gc, '_>, name: &str, - get: Object<'gc>, - set: Option>, + getter: Object<'gc>, + setter: Option>, attributes: Attribute, ) { - self.0.write(gc_context).values.insert( - name, - Property::Virtual { - get, - set, - attributes, - }, - false, - ); + match self.0.write(gc_context).values.entry(name, false) { + Entry::Occupied(mut entry) => entry.get_mut().set_virtual(getter, setter), + Entry::Vacant(entry) => entry.insert(Property::new_virtual(getter, setter, attributes)), + } } fn add_property_with_case( &self, activation: &mut Activation<'_, 'gc, '_>, name: &str, - get: Object<'gc>, - set: Option>, + getter: Object<'gc>, + setter: Option>, attributes: Attribute, ) { - self.0.write(activation.context.gc_context).values.insert( - name, - Property::Virtual { - get, - set, - attributes, - }, - activation.is_case_sensitive(), - ); + match self + .0 + .write(activation.context.gc_context) + .values + .entry(name, activation.is_case_sensitive()) + { + Entry::Occupied(mut entry) => entry.get_mut().set_virtual(getter, setter), + Entry::Vacant(entry) => entry.insert(Property::new_virtual(getter, setter, attributes)), + } } fn watch( @@ -390,12 +375,11 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { } fn unwatch(&self, activation: &mut Activation<'_, 'gc, '_>, name: Cow) -> bool { - let old = self - .0 + self.0 .write(activation.context.gc_context) .watchers - .remove(name.as_ref(), activation.is_case_sensitive()); - old.is_some() + .remove(name.as_ref(), activation.is_case_sensitive()) + .is_some() } fn define_value( @@ -408,7 +392,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0 .write(gc_context) .values - .insert(name, Property::Stored { value, attributes }, true); + .insert(name, Property::new_stored(value, attributes), true); } fn set_attributes( @@ -459,30 +443,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { } fn has_own_virtual(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool { - if let Some(slot) = self - .0 + self.0 .read() .values .get(name, activation.is_case_sensitive()) - { - slot.is_virtual() - } else { - false - } + .map_or(false, |property| property.is_virtual()) } /// Checks if a named property appears when enumerating the object. fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool { - if let Some(prop) = self - .0 + self.0 .read() .values .get(name, activation.is_case_sensitive()) - { - prop.is_enumerable() - } else { - false - } + .map_or(false, |property| property.is_enumerable()) } /// Enumerate the object. @@ -493,14 +467,13 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { Vec::new() }; let mut out_keys = vec![]; - let object = self.0.read(); // Prototype keys come first. - out_keys.extend(proto_keys.into_iter().filter(|k| { - !object - .values - .contains_key(k, activation.is_case_sensitive()) - })); + out_keys.extend( + proto_keys + .into_iter() + .filter(|k| !self.has_own_property(activation, k)), + ); // Then our own keys. out_keys.extend(self.0.read().values.iter().filter_map(move |(k, p)| { diff --git a/core/src/avm1/property.rs b/core/src/avm1/property.rs index 07c925395..82b246e19 100644 --- a/core/src/avm1/property.rs +++ b/core/src/avm1/property.rs @@ -17,113 +17,99 @@ bitflags! { } } -#[allow(clippy::large_enum_variant)] #[derive(Clone, Collect)] #[collect(no_drop)] -pub enum Property<'gc> { - Virtual { - get: Object<'gc>, - set: Option>, - attributes: Attribute, - }, - Stored { - value: Value<'gc>, - attributes: Attribute, - }, +pub struct Property<'gc> { + data: Value<'gc>, + getter: Option>, + setter: Option>, + attributes: Attribute, } impl<'gc> Property<'gc> { - /// Set a property slot. - /// - /// This function may return an `Executable` of the property's virtual - /// function, if any happen to exist. It should be resolved, and its value - /// discarded. - pub fn set(&mut self, new_value: impl Into>) -> Option> { - match self { - Property::Virtual { - set: Some(function), - .. - } => Some(function.to_owned()), - Property::Stored { - value, attributes, .. - } => { - if !attributes.contains(Attribute::READ_ONLY) { - *value = new_value.into(); - } - - None - } - _ => None, + pub fn new_stored(data: Value<'gc>, attributes: Attribute) -> Self { + Self { + data, + getter: None, + setter: None, + attributes, } } + pub fn new_virtual( + getter: Object<'gc>, + setter: Option>, + attributes: Attribute, + ) -> Self { + Self { + data: Value::Undefined, + getter: Some(getter), + setter, + attributes, + } + } + + pub fn data(&self) -> Value<'gc> { + self.data + } + + pub fn getter(&self) -> Option> { + self.getter + } + + pub fn setter(&self) -> Option> { + self.setter + } + + /// Store data on this property, ignoring virtual setters. + /// + /// Read-only properties are not affected. + pub fn set_data(&mut self, data: Value<'gc>) { + if self.is_overwritable() { + self.data = data; + } + } + + /// Make this property virtual by attaching a getter/setter to it. + pub fn set_virtual(&mut self, getter: Object<'gc>, setter: Option>) { + self.getter = Some(getter); + self.setter = setter; + } + /// List this property's attributes. pub fn attributes(&self) -> Attribute { - match self { - Property::Virtual { attributes, .. } => *attributes, - Property::Stored { attributes, .. } => *attributes, - } + self.attributes } /// Re-define this property's attributes. - pub fn set_attributes(&mut self, new_attributes: Attribute) { - match self { - Property::Virtual { - ref mut attributes, .. - } => *attributes = new_attributes, - Property::Stored { - ref mut attributes, .. - } => *attributes = new_attributes, - } - } - - pub fn can_delete(&self) -> bool { - match self { - Property::Virtual { attributes, .. } => !attributes.contains(Attribute::DONT_DELETE), - Property::Stored { attributes, .. } => !attributes.contains(Attribute::DONT_DELETE), - } + pub fn set_attributes(&mut self, attributes: Attribute) { + self.attributes = attributes; } pub fn is_enumerable(&self) -> bool { - match self { - Property::Virtual { attributes, .. } => !attributes.contains(Attribute::DONT_ENUM), - Property::Stored { attributes, .. } => !attributes.contains(Attribute::DONT_ENUM), - } + !self.attributes.contains(Attribute::DONT_ENUM) + } + + pub fn can_delete(&self) -> bool { + !self.attributes.contains(Attribute::DONT_DELETE) } - #[allow(dead_code)] pub fn is_overwritable(&self) -> bool { - match self { - Property::Virtual { - attributes, set, .. - } => !attributes.contains(Attribute::READ_ONLY) && !set.is_none(), - Property::Stored { attributes, .. } => !attributes.contains(Attribute::READ_ONLY), - } + !self.attributes.contains(Attribute::READ_ONLY) } pub fn is_virtual(&self) -> bool { - matches!(self, Property::Virtual { .. }) + self.getter.is_some() } } impl fmt::Debug for Property<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Property::Virtual { - get: _, - set, - attributes, - } => f - .debug_struct("Property::Virtual") - .field("get", &true) - .field("set", &set.is_some()) - .field("attributes", &attributes) - .finish(), - Property::Stored { value, attributes } => f - .debug_struct("Property::Stored") - .field("value", &value) - .field("attributes", &attributes) - .finish(), - } + f.debug_struct("Property") + .field("data", &self.data) + .field("getter", &self.getter) + .field("setter", &self.setter) + .field("attributes", &self.attributes) + .finish() } }