//! Property data structures use self::Attribute::*; use crate::avm2::function::Executable; use crate::avm2::object::Object; use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use enumset::{EnumSet, EnumSetType}; use gc_arena::{Collect, CollectionContext}; use std::mem::replace; /// Attributes of properties in the AVM runtime. /// /// TODO: Replace with AVM2 properties for traits #[derive(EnumSetType, Debug)] pub enum Attribute { DontEnum, DontDelete, ReadOnly, } #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum Property<'gc> { Virtual { get: Executable<'gc>, set: Option>, attributes: EnumSet, }, Stored { value: Value<'gc>, attributes: EnumSet, }, } unsafe impl<'gc> Collect for Property<'gc> { fn trace(&self, cc: CollectionContext) { match self { Property::Virtual { get, set, .. } => { get.trace(cc); set.trace(cc); } Property::Stored { value, .. } => value.trace(cc), } } } impl<'gc> Property<'gc> { /// Convert a value into a dynamic property. pub fn new_dynamic_property(value: impl Into>) -> Self { Property::Stored { value: value.into(), attributes: EnumSet::empty(), } } /// Get the value of a property slot. /// /// This function yields `ReturnValue` because some properties may be /// user-defined. pub fn get( &self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error> { match self { Property::Virtual { get, .. } => get.exec(avm, context, this, &[]), Property::Stored { value, .. } => Ok(value.to_owned().into()), } } /// Set a property slot. /// /// This function returns `true` if the set has completed, or `false` if /// it has not yet occured. If `false`, and you need to run code after the /// set has occured, you must recursively execute the top-most frame via /// `run_current_frame`. pub fn set( &mut self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, new_value: impl Into>, ) -> Result { match self { Property::Virtual { set, .. } => { if let Some(function) = set { let return_value = function.exec(avm, context, this, &[new_value.into()])?; Ok(return_value.is_immediate()) } else { Ok(true) } } Property::Stored { value, attributes, .. } => { if !attributes.contains(ReadOnly) { replace::>(value, new_value.into()); } Ok(true) } } } /// List this property's attributes. pub fn attributes(&self) -> EnumSet { match self { Property::Virtual { attributes, .. } => *attributes, Property::Stored { attributes, .. } => *attributes, } } /// Re-define this property's attributes. pub fn set_attributes(&mut self, new_attributes: EnumSet) { 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(DontDelete), Property::Stored { attributes, .. } => !attributes.contains(DontDelete), } } pub fn is_enumerable(&self) -> bool { match self { Property::Virtual { attributes, .. } => !attributes.contains(DontEnum), Property::Stored { attributes, .. } => !attributes.contains(DontEnum), } } pub fn is_overwritable(&self) -> bool { match self { Property::Virtual { attributes, set, .. } => !attributes.contains(ReadOnly) && !set.is_none(), Property::Stored { attributes, .. } => !attributes.contains(ReadOnly), } } }