2020-02-15 01:30:19 +00:00
|
|
|
//! Property data structures
|
|
|
|
|
|
|
|
use self::Attribute::*;
|
|
|
|
use crate::avm2::function::Executable;
|
2020-02-19 19:17:33 +00:00
|
|
|
use crate::avm2::object::{Object, TObject};
|
2020-02-15 01:30:19 +00:00
|
|
|
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 {
|
2020-02-18 00:43:23 +00:00
|
|
|
get: Option<Executable<'gc>>,
|
2020-02-15 01:30:19 +00:00
|
|
|
set: Option<Executable<'gc>>,
|
|
|
|
attributes: EnumSet<Attribute>,
|
|
|
|
},
|
|
|
|
Stored {
|
|
|
|
value: Value<'gc>,
|
|
|
|
attributes: EnumSet<Attribute>,
|
|
|
|
},
|
2020-02-19 19:17:33 +00:00
|
|
|
Slot {
|
|
|
|
slot_id: u32,
|
|
|
|
attributes: EnumSet<Attribute>,
|
|
|
|
},
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { .. } => {}
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Property<'gc> {
|
2020-02-21 02:02:49 +00:00
|
|
|
/// Create a new stored property.
|
|
|
|
pub fn new_stored(value: impl Into<Value<'gc>>) -> Self {
|
|
|
|
Property::Stored {
|
|
|
|
value: value.into(),
|
|
|
|
attributes: EnumSet::from(Attribute::DontDelete),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-21 04:20:46 +00:00
|
|
|
/// Create a new stored property.
|
|
|
|
pub fn new_const(value: impl Into<Value<'gc>>) -> Self {
|
|
|
|
Property::Stored {
|
|
|
|
value: value.into(),
|
|
|
|
attributes: Attribute::ReadOnly | Attribute::DontDelete,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
/// Convert a value into a dynamic property.
|
|
|
|
pub fn new_dynamic_property(value: impl Into<Value<'gc>>) -> Self {
|
|
|
|
Property::Stored {
|
|
|
|
value: value.into(),
|
|
|
|
attributes: EnumSet::empty(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-18 00:43:23 +00:00
|
|
|
/// Convert a function into a method.
|
|
|
|
///
|
|
|
|
/// This applies ReadOnly/DontDelete to the property.
|
|
|
|
pub fn new_method(fn_obj: Object<'gc>) -> Self {
|
|
|
|
Property::Stored {
|
|
|
|
value: fn_obj.into(),
|
|
|
|
attributes: Attribute::ReadOnly | Attribute::DontDelete,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new, unconfigured virtual property item.
|
|
|
|
pub fn new_virtual() -> Self {
|
|
|
|
Property::Virtual {
|
|
|
|
get: None,
|
|
|
|
set: None,
|
|
|
|
attributes: Attribute::ReadOnly | Attribute::DontDelete,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-21 02:02:49 +00:00
|
|
|
/// Create a new slot property.
|
|
|
|
pub fn new_slot(slot_id: u32) -> Self {
|
|
|
|
Property::Slot {
|
|
|
|
slot_id,
|
|
|
|
attributes: EnumSet::from(Attribute::DontDelete),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-18 00:43:23 +00:00
|
|
|
/// Install a getter into this property.
|
|
|
|
///
|
|
|
|
/// This function errors if attempting to install executables into a
|
|
|
|
/// non-virtual property.
|
|
|
|
pub fn install_virtual_getter(&mut self, getter_impl: Executable<'gc>) -> Result<(), Error> {
|
|
|
|
match self {
|
|
|
|
Property::Virtual { get, .. } => *get = Some(getter_impl),
|
|
|
|
Property::Stored { .. } => return Err("Not a virtual property".into()),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { .. } => return Err("Not a virtual property".into()),
|
2020-02-18 00:43:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Install a setter into this property.
|
|
|
|
///
|
|
|
|
/// This function errors if attempting to install executables into a
|
|
|
|
/// non-virtual property.
|
|
|
|
pub fn install_virtual_setter(&mut self, setter_impl: Executable<'gc>) -> Result<(), Error> {
|
|
|
|
match self {
|
|
|
|
Property::Virtual { set, .. } => *set = Some(setter_impl),
|
|
|
|
Property::Stored { .. } => return Err("Not a virtual property".into()),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { .. } => return Err("Not a virtual property".into()),
|
2020-02-18 00:43:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
/// 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<ReturnValue<'gc>, Error> {
|
|
|
|
match self {
|
2020-02-18 00:43:23 +00:00
|
|
|
Property::Virtual { get: Some(get), .. } => get.exec(avm, context, this, &[]),
|
|
|
|
Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()),
|
2020-02-15 01:30:19 +00:00
|
|
|
Property::Stored { value, .. } => Ok(value.to_owned().into()),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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<Value<'gc>>,
|
|
|
|
) -> Result<bool, Error> {
|
|
|
|
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<'gc>>(value, new_value.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(true)
|
|
|
|
}
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { slot_id, .. } => this
|
|
|
|
.set_slot(*slot_id, new_value.into(), context.gc_context)
|
|
|
|
.map(|v| true),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// List this property's attributes.
|
|
|
|
pub fn attributes(&self) -> EnumSet<Attribute> {
|
|
|
|
match self {
|
|
|
|
Property::Virtual { attributes, .. } => *attributes,
|
|
|
|
Property::Stored { attributes, .. } => *attributes,
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { attributes, .. } => *attributes,
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Re-define this property's attributes.
|
|
|
|
pub fn set_attributes(&mut self, new_attributes: EnumSet<Attribute>) {
|
|
|
|
match self {
|
|
|
|
Property::Virtual {
|
|
|
|
ref mut attributes, ..
|
|
|
|
} => *attributes = new_attributes,
|
|
|
|
Property::Stored {
|
|
|
|
ref mut attributes, ..
|
|
|
|
} => *attributes = new_attributes,
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot {
|
|
|
|
ref mut attributes, ..
|
|
|
|
} => *attributes = new_attributes,
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn can_delete(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Property::Virtual { attributes, .. } => !attributes.contains(DontDelete),
|
|
|
|
Property::Stored { attributes, .. } => !attributes.contains(DontDelete),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { attributes, .. } => !attributes.contains(DontDelete),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_enumerable(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Property::Virtual { attributes, .. } => !attributes.contains(DontEnum),
|
|
|
|
Property::Stored { attributes, .. } => !attributes.contains(DontEnum),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { attributes, .. } => !attributes.contains(DontEnum),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_overwritable(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Property::Virtual {
|
|
|
|
attributes, set, ..
|
|
|
|
} => !attributes.contains(ReadOnly) && !set.is_none(),
|
|
|
|
Property::Stored { attributes, .. } => !attributes.contains(ReadOnly),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { attributes, .. } => !attributes.contains(ReadOnly),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|