2019-11-30 18:31:11 +00:00
|
|
|
use crate::avm1::function::{Executable, FunctionObject, NativeFunction};
|
2019-10-25 03:21:35 +00:00
|
|
|
use crate::avm1::property::{Attribute, Property};
|
|
|
|
use crate::avm1::return_value::ReturnValue;
|
2019-12-06 18:24:36 +00:00
|
|
|
use crate::avm1::{Avm1, Error, Object, ObjectPtr, TObject, UpdateContext, Value};
|
2020-03-27 05:24:59 +00:00
|
|
|
use crate::property_map::{Entry, PropertyMap};
|
2019-10-25 03:21:35 +00:00
|
|
|
use core::fmt;
|
|
|
|
use enumset::EnumSet;
|
2019-12-06 18:24:36 +00:00
|
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
2019-10-25 03:21:35 +00:00
|
|
|
|
|
|
|
pub const TYPE_OF_OBJECT: &str = "object";
|
|
|
|
|
2019-12-13 14:50:58 +00:00
|
|
|
#[derive(Debug, Clone, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub enum ArrayStorage<'gc> {
|
|
|
|
Vector(Vec<Value<'gc>>),
|
|
|
|
Properties { length: usize },
|
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
#[derive(Debug, Copy, Clone, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
|
|
|
|
|
|
|
pub struct ScriptObjectData<'gc> {
|
|
|
|
prototype: Option<Object<'gc>>,
|
2020-03-27 05:24:59 +00:00
|
|
|
values: PropertyMap<Property<'gc>>,
|
2019-10-28 22:13:29 +00:00
|
|
|
interfaces: Vec<Object<'gc>>,
|
2019-10-25 03:21:35 +00:00
|
|
|
type_of: &'static str,
|
2019-12-13 14:50:58 +00:00
|
|
|
array: ArrayStorage<'gc>,
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
2019-10-25 03:21:35 +00:00
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
|
|
self.prototype.trace(cc);
|
|
|
|
self.values.trace(cc);
|
2019-12-13 14:50:58 +00:00
|
|
|
self.array.trace(cc);
|
2019-10-28 22:13:29 +00:00
|
|
|
self.interfaces.trace(cc);
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
impl fmt::Debug for ScriptObjectData<'_> {
|
2019-10-25 03:21:35 +00:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
f.debug_struct("Object")
|
|
|
|
.field("prototype", &self.prototype)
|
|
|
|
.field("values", &self.values)
|
2019-12-13 14:50:58 +00:00
|
|
|
.field("array", &self.array)
|
2019-10-25 03:21:35 +00:00
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> ScriptObject<'gc> {
|
|
|
|
pub fn object(
|
2019-12-06 18:24:36 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
proto: Option<Object<'gc>>,
|
2019-10-25 03:21:35 +00:00
|
|
|
) -> ScriptObject<'gc> {
|
2019-12-06 18:24:36 +00:00
|
|
|
ScriptObject(GcCell::allocate(
|
|
|
|
gc_context,
|
|
|
|
ScriptObjectData {
|
|
|
|
prototype: proto,
|
|
|
|
type_of: TYPE_OF_OBJECT,
|
2020-03-27 05:24:59 +00:00
|
|
|
values: PropertyMap::new(),
|
2019-12-13 14:50:58 +00:00
|
|
|
array: ArrayStorage::Properties { length: 0 },
|
2019-10-28 22:13:29 +00:00
|
|
|
interfaces: vec![],
|
2019-12-06 18:24:36 +00:00
|
|
|
},
|
|
|
|
))
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
2019-12-13 14:50:58 +00:00
|
|
|
pub fn array(
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
proto: Option<Object<'gc>>,
|
|
|
|
) -> ScriptObject<'gc> {
|
|
|
|
let object = ScriptObject(GcCell::allocate(
|
|
|
|
gc_context,
|
|
|
|
ScriptObjectData {
|
|
|
|
prototype: proto,
|
|
|
|
type_of: TYPE_OF_OBJECT,
|
2020-03-27 05:24:59 +00:00
|
|
|
values: PropertyMap::new(),
|
2019-12-13 14:50:58 +00:00
|
|
|
array: ArrayStorage::Vector(Vec::new()),
|
2019-10-28 22:13:29 +00:00
|
|
|
interfaces: vec![],
|
2019-12-13 14:50:58 +00:00
|
|
|
},
|
|
|
|
));
|
|
|
|
object.sync_native_property("length", gc_context, Some(0.into()));
|
|
|
|
object
|
|
|
|
}
|
|
|
|
|
2019-10-25 03:21:35 +00:00
|
|
|
/// Constructs and allocates an empty but normal object in one go.
|
|
|
|
pub fn object_cell(
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2019-12-06 18:24:36 +00:00
|
|
|
proto: Option<Object<'gc>>,
|
|
|
|
) -> Object<'gc> {
|
|
|
|
ScriptObject(GcCell::allocate(
|
2019-10-25 03:21:35 +00:00
|
|
|
gc_context,
|
2019-12-06 18:24:36 +00:00
|
|
|
ScriptObjectData {
|
2019-10-25 03:21:35 +00:00
|
|
|
prototype: proto,
|
|
|
|
type_of: TYPE_OF_OBJECT,
|
2020-03-27 05:24:59 +00:00
|
|
|
values: PropertyMap::new(),
|
2019-12-13 14:50:58 +00:00
|
|
|
array: ArrayStorage::Properties { length: 0 },
|
2019-10-28 22:13:29 +00:00
|
|
|
interfaces: vec![],
|
2019-12-06 18:24:36 +00:00
|
|
|
},
|
|
|
|
))
|
|
|
|
.into()
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Constructs an object with no values, not even builtins.
|
|
|
|
///
|
|
|
|
/// Intended for constructing scope chains, since they exclusively use the
|
|
|
|
/// object values, but can't just have a hashmap because of `with` and
|
|
|
|
/// friends.
|
2019-12-06 18:24:36 +00:00
|
|
|
pub fn bare_object(gc_context: MutationContext<'gc, '_>) -> Self {
|
|
|
|
ScriptObject(GcCell::allocate(
|
|
|
|
gc_context,
|
|
|
|
ScriptObjectData {
|
|
|
|
prototype: None,
|
|
|
|
type_of: TYPE_OF_OBJECT,
|
2020-03-27 05:24:59 +00:00
|
|
|
values: PropertyMap::new(),
|
2019-12-13 14:50:58 +00:00
|
|
|
array: ArrayStorage::Properties { length: 0 },
|
2019-10-28 22:13:29 +00:00
|
|
|
interfaces: vec![],
|
2019-12-06 18:24:36 +00:00
|
|
|
},
|
|
|
|
))
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Declare a native function on the current object.
|
|
|
|
///
|
|
|
|
/// This is intended for use with defining host object prototypes. Notably,
|
|
|
|
/// this creates a function object without an explicit `prototype`, which
|
|
|
|
/// is only possible when defining host functions. User-defined functions
|
|
|
|
/// always get a fresh explicit prototype, so you should never force set a
|
|
|
|
/// user-defined function.
|
|
|
|
pub fn force_set_function<A>(
|
|
|
|
&mut self,
|
|
|
|
name: &str,
|
|
|
|
function: NativeFunction<'gc>,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
attributes: A,
|
2019-12-06 18:24:36 +00:00
|
|
|
fn_proto: Option<Object<'gc>>,
|
2019-10-25 03:21:35 +00:00
|
|
|
) where
|
|
|
|
A: Into<EnumSet<Attribute>>,
|
|
|
|
{
|
2019-10-25 23:30:05 +00:00
|
|
|
self.define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
gc_context,
|
2019-10-25 03:21:35 +00:00
|
|
|
name,
|
2019-11-30 18:31:11 +00:00
|
|
|
Value::Object(FunctionObject::function(
|
|
|
|
gc_context, function, fn_proto, None,
|
|
|
|
)),
|
2019-10-25 23:30:05 +00:00
|
|
|
attributes.into(),
|
2019-10-25 03:21:35 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
pub fn set_type_of(&mut self, gc_context: MutationContext<'gc, '_>, type_of: &'static str) {
|
|
|
|
self.0.write(gc_context).type_of = type_of;
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
2019-12-13 14:50:58 +00:00
|
|
|
|
|
|
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
|
|
|
pub fn sync_native_property(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
native_value: Option<Value<'gc>>,
|
|
|
|
) {
|
2020-03-27 05:24:59 +00:00
|
|
|
match self
|
|
|
|
.0
|
|
|
|
.write(gc_context)
|
|
|
|
.values
|
|
|
|
.entry(name.to_string(), false)
|
|
|
|
{
|
2019-12-13 14:50:58 +00:00
|
|
|
Entry::Occupied(mut entry) => {
|
|
|
|
if let Property::Stored { value, .. } = entry.get_mut() {
|
|
|
|
match native_value {
|
|
|
|
None => {
|
|
|
|
entry.remove_entry();
|
|
|
|
}
|
|
|
|
Some(native_value) => {
|
|
|
|
*value = native_value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Entry::Vacant(entry) => {
|
|
|
|
if let Some(native_value) = native_value {
|
|
|
|
entry.insert(Property::Stored {
|
|
|
|
value: native_value,
|
|
|
|
attributes: Attribute::DontEnum.into(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-25 03:21:35 +00:00
|
|
|
|
2019-12-19 09:31:08 +00:00
|
|
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
|
|
|
pub(crate) fn internal_set(
|
2019-12-06 18:24:36 +00:00
|
|
|
&self,
|
2019-10-25 03:21:35 +00:00
|
|
|
name: &str,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-12-19 09:31:08 +00:00
|
|
|
this: Object<'gc>,
|
2019-10-25 03:21:35 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
if name == "__proto__" {
|
2019-12-06 18:24:36 +00:00
|
|
|
self.0.write(context.gc_context).prototype = value.as_object().ok();
|
2019-12-13 14:50:58 +00:00
|
|
|
} else if let Ok(index) = name.parse::<usize>() {
|
|
|
|
self.set_array_element(index, value.to_owned(), context.gc_context);
|
2019-10-25 03:21:35 +00:00
|
|
|
} else {
|
2019-12-13 14:50:58 +00:00
|
|
|
if name == "length" {
|
|
|
|
let length = value
|
|
|
|
.as_number(avm, context)
|
|
|
|
.map(|v| v.abs() as i32)
|
|
|
|
.unwrap_or(0);
|
|
|
|
if length > 0 {
|
|
|
|
self.set_length(context.gc_context, length as usize);
|
|
|
|
} else {
|
|
|
|
self.set_length(context.gc_context, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
match self
|
|
|
|
.0
|
|
|
|
.write(context.gc_context)
|
|
|
|
.values
|
2020-03-27 05:24:59 +00:00
|
|
|
.entry(name.to_owned(), avm.is_case_sensitive())
|
2019-12-06 18:24:36 +00:00
|
|
|
{
|
2019-10-25 03:21:35 +00:00
|
|
|
Entry::Occupied(mut entry) => {
|
2019-12-19 09:31:08 +00:00
|
|
|
entry.get_mut().set(avm, context, this, value)?;
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
Entry::Vacant(entry) => {
|
|
|
|
entry.insert(Property::Stored {
|
|
|
|
value,
|
|
|
|
attributes: Default::default(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-12-19 09:31:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|
|
|
/// Get the value of a particular property on this object.
|
|
|
|
///
|
|
|
|
/// The `avm`, `context`, and `this` parameters exist so that this object
|
|
|
|
/// can call virtual properties. Furthermore, since some virtual properties
|
|
|
|
/// may resolve on the AVM stack, this function may return `None` instead
|
|
|
|
/// of a `Value`. *This is not equivalent to `undefined`.* Instead, it is a
|
|
|
|
/// signal that your value will be returned on the ActionScript stack, and
|
|
|
|
/// that you should register a stack continuation in order to get it.
|
|
|
|
fn get_local(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
this: Object<'gc>,
|
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
|
|
if name == "__proto__" {
|
|
|
|
return Ok(self.proto().map_or(Value::Undefined, Value::Object).into());
|
|
|
|
}
|
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
if let Some(value) = self.0.read().values.get(name, avm.is_case_sensitive()) {
|
2019-12-19 09:31:08 +00:00
|
|
|
return value.get(avm, context, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::Undefined.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set a named property on the object.
|
|
|
|
///
|
|
|
|
/// This function takes a redundant `this` parameter which should be
|
|
|
|
/// the object's own `GcCell`, so that it can pass it to user-defined
|
|
|
|
/// overrides that may need to interact with the underlying object.
|
|
|
|
fn set(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.internal_set(name, value, avm, context, (*self).into())
|
|
|
|
}
|
2019-10-25 03:21:35 +00:00
|
|
|
|
|
|
|
/// Call the underlying object.
|
|
|
|
///
|
|
|
|
/// This function takes a redundant `this` parameter which should be
|
|
|
|
/// the object's own `GcCell`, so that it can pass it to user-defined
|
|
|
|
/// overrides that may need to interact with the underlying object.
|
|
|
|
fn call(
|
|
|
|
&self,
|
2019-11-30 18:31:11 +00:00
|
|
|
_avm: &mut Avm1<'gc>,
|
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
_this: Object<'gc>,
|
|
|
|
_args: &[Value<'gc>],
|
2019-10-25 03:21:35 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2019-11-30 18:31:11 +00:00
|
|
|
Ok(Value::Undefined.into())
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
2019-10-25 18:35:07 +00:00
|
|
|
#[allow(clippy::new_ret_no_self)]
|
|
|
|
fn new(
|
|
|
|
&self,
|
2019-10-28 03:54:35 +00:00
|
|
|
_avm: &mut Avm1<'gc>,
|
2019-10-25 18:35:07 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-12-06 18:24:36 +00:00
|
|
|
this: Object<'gc>,
|
2019-10-25 18:35:07 +00:00
|
|
|
_args: &[Value<'gc>],
|
2019-12-06 18:24:36 +00:00
|
|
|
) -> Result<Object<'gc>, Error> {
|
2019-12-13 14:50:58 +00:00
|
|
|
match self.0.read().array {
|
|
|
|
ArrayStorage::Vector(_) => {
|
|
|
|
Ok(ScriptObject::array(context.gc_context, Some(this)).into())
|
|
|
|
}
|
|
|
|
ArrayStorage::Properties { .. } => {
|
|
|
|
Ok(ScriptObject::object(context.gc_context, Some(this)).into())
|
|
|
|
}
|
|
|
|
}
|
2019-10-25 18:35:07 +00:00
|
|
|
}
|
|
|
|
|
2019-10-25 03:21:35 +00:00
|
|
|
/// Delete a named property from the object.
|
|
|
|
///
|
|
|
|
/// Returns false if the property cannot be deleted.
|
2020-03-27 05:24:59 +00:00
|
|
|
fn delete(
|
|
|
|
&self,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
name: &str,
|
|
|
|
) -> bool {
|
2019-12-06 18:24:36 +00:00
|
|
|
let mut object = self.0.write(gc_context);
|
2020-03-27 05:24:59 +00:00
|
|
|
if let Some(prop) = object.values.get(name, avm.is_case_sensitive()) {
|
2019-10-25 03:21:35 +00:00
|
|
|
if prop.can_delete() {
|
2020-03-27 05:24:59 +00:00
|
|
|
object.values.remove(name, avm.is_case_sensitive());
|
2019-10-25 03:21:35 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2019-10-25 23:30:05 +00:00
|
|
|
fn add_property(
|
2019-12-06 18:24:36 +00:00
|
|
|
&self,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2019-10-25 23:30:05 +00:00
|
|
|
name: &str,
|
|
|
|
get: Executable<'gc>,
|
|
|
|
set: Option<Executable<'gc>>,
|
|
|
|
attributes: EnumSet<Attribute>,
|
|
|
|
) {
|
2019-12-06 18:24:36 +00:00
|
|
|
self.0.write(gc_context).values.insert(
|
2019-10-25 23:30:05 +00:00
|
|
|
name.to_owned(),
|
|
|
|
Property::Virtual {
|
|
|
|
get,
|
|
|
|
set,
|
|
|
|
attributes,
|
|
|
|
},
|
2020-03-27 05:24:59 +00:00
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_property_with_case(
|
|
|
|
&self,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
name: &str,
|
|
|
|
get: Executable<'gc>,
|
|
|
|
set: Option<Executable<'gc>>,
|
|
|
|
attributes: EnumSet<Attribute>,
|
|
|
|
) {
|
|
|
|
self.0.write(gc_context).values.insert(
|
|
|
|
name.to_owned(),
|
|
|
|
Property::Virtual {
|
|
|
|
get,
|
|
|
|
set,
|
|
|
|
attributes,
|
|
|
|
},
|
|
|
|
avm.is_case_sensitive(),
|
2019-10-25 23:30:05 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
fn define_value(
|
|
|
|
&self,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
name: &str,
|
|
|
|
value: Value<'gc>,
|
|
|
|
attributes: EnumSet<Attribute>,
|
|
|
|
) {
|
2020-03-27 05:24:59 +00:00
|
|
|
self.0.write(gc_context).values.insert(
|
|
|
|
name.to_string(),
|
|
|
|
Property::Stored { value, attributes },
|
|
|
|
false,
|
|
|
|
);
|
2019-10-25 23:30:05 +00:00
|
|
|
}
|
|
|
|
|
2019-11-03 21:52:10 +00:00
|
|
|
fn set_attributes(
|
|
|
|
&mut self,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
name: Option<&str>,
|
|
|
|
set_attributes: EnumSet<Attribute>,
|
|
|
|
clear_attributes: EnumSet<Attribute>,
|
|
|
|
) {
|
|
|
|
match name {
|
|
|
|
None => {
|
|
|
|
// Change *all* attributes.
|
|
|
|
for (_name, prop) in self.0.write(gc_context).values.iter_mut() {
|
|
|
|
let new_atts = (prop.attributes() - clear_attributes) | set_attributes;
|
|
|
|
prop.set_attributes(new_atts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(name) => {
|
2020-03-27 05:24:59 +00:00
|
|
|
if let Some(prop) = self.0.write(gc_context).values.get_mut(name, false) {
|
2019-11-03 21:52:10 +00:00
|
|
|
let new_atts = (prop.attributes() - clear_attributes) | set_attributes;
|
|
|
|
prop.set_attributes(new_atts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
fn proto(&self) -> Option<Object<'gc>> {
|
|
|
|
self.0.read().prototype
|
2019-10-25 22:02:47 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 17:52:16 +00:00
|
|
|
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Option<Object<'gc>>) {
|
|
|
|
self.0.write(gc_context).prototype = prototype;
|
|
|
|
}
|
|
|
|
|
2019-10-25 03:21:35 +00:00
|
|
|
/// Checks if the object has a given named property.
|
2020-03-27 05:24:59 +00:00
|
|
|
fn has_property(
|
|
|
|
&self,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
name: &str,
|
|
|
|
) -> bool {
|
|
|
|
self.has_own_property(avm, context, name)
|
2019-10-25 03:21:35 +00:00
|
|
|
|| self
|
2019-12-06 18:24:36 +00:00
|
|
|
.proto()
|
2019-10-25 03:21:35 +00:00
|
|
|
.as_ref()
|
2020-03-27 05:24:59 +00:00
|
|
|
.map_or(false, |p| p.has_property(avm, context, name))
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks if the object has a given named property on itself (and not,
|
|
|
|
/// say, the object's prototype or superclass)
|
2020-03-27 05:24:59 +00:00
|
|
|
fn has_own_property(
|
|
|
|
&self,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
name: &str,
|
|
|
|
) -> bool {
|
2019-10-25 03:21:35 +00:00
|
|
|
if name == "__proto__" {
|
|
|
|
return true;
|
|
|
|
}
|
2020-03-27 05:24:59 +00:00
|
|
|
self.0
|
|
|
|
.read()
|
|
|
|
.values
|
|
|
|
.contains_key(name, avm.is_case_sensitive())
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool {
|
2019-12-06 18:24:36 +00:00
|
|
|
self.0
|
|
|
|
.read()
|
|
|
|
.values
|
2020-03-27 05:24:59 +00:00
|
|
|
.get(name, avm.is_case_sensitive())
|
2019-10-25 03:21:35 +00:00
|
|
|
.map(|p| p.is_overwritable())
|
|
|
|
.unwrap_or(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks if a named property appears when enumerating the object.
|
2020-03-27 05:24:59 +00:00
|
|
|
fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool {
|
|
|
|
if let Some(prop) = self.0.read().values.get(name, avm.is_case_sensitive()) {
|
2019-10-25 03:21:35 +00:00
|
|
|
prop.is_enumerable()
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Enumerate the object.
|
2020-03-27 05:24:59 +00:00
|
|
|
fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec<String> {
|
|
|
|
let proto_keys = self.proto().map_or_else(Vec::new, |p| p.get_keys(avm));
|
|
|
|
let mut out_keys = vec![];
|
|
|
|
let object = self.0.read();
|
2020-03-28 00:12:50 +00:00
|
|
|
|
|
|
|
// Prototype keys come first.
|
|
|
|
out_keys.extend(
|
|
|
|
proto_keys
|
|
|
|
.into_iter()
|
|
|
|
.filter(|k| !object.values.contains_key(k, avm.is_case_sensitive())),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Then our own keys.
|
|
|
|
out_keys.extend(self.0.read().values.iter().filter_map(move |(k, p)| {
|
2020-03-27 05:24:59 +00:00
|
|
|
if p.is_enumerable() {
|
|
|
|
Some(k.to_string())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2020-03-28 00:12:50 +00:00
|
|
|
}));
|
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
out_keys
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn as_string(&self) -> String {
|
2019-11-30 18:31:11 +00:00
|
|
|
"[object Object]".to_string()
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn type_of(&self) -> &'static str {
|
2019-12-06 18:24:36 +00:00
|
|
|
self.0.read().type_of
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
2019-10-28 22:13:29 +00:00
|
|
|
fn interfaces(&self) -> Vec<Object<'gc>> {
|
|
|
|
self.0.read().interfaces.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
|
|
|
self.0.write(context).interfaces = iface_list;
|
|
|
|
}
|
|
|
|
|
2019-12-09 22:19:35 +00:00
|
|
|
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
|
|
|
Some(*self)
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
2019-12-11 23:30:04 +00:00
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
fn as_ptr(&self) -> *const ObjectPtr {
|
|
|
|
self.0.as_ptr() as *const ObjectPtr
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
2019-12-13 14:50:58 +00:00
|
|
|
|
2019-12-15 20:25:41 +00:00
|
|
|
fn length(&self) -> usize {
|
2019-12-13 14:50:58 +00:00
|
|
|
match &self.0.read().array {
|
|
|
|
ArrayStorage::Vector(vector) => vector.len(),
|
|
|
|
ArrayStorage::Properties { length } => *length,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) {
|
|
|
|
let mut to_remove = None;
|
|
|
|
|
|
|
|
match &mut self.0.write(gc_context).array {
|
|
|
|
ArrayStorage::Vector(vector) => {
|
|
|
|
let old_length = vector.len();
|
|
|
|
vector.resize(new_length, Value::Undefined);
|
|
|
|
if new_length < old_length {
|
|
|
|
to_remove = Some(new_length..old_length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ArrayStorage::Properties { length } => {
|
|
|
|
*length = new_length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(to_remove) = to_remove {
|
|
|
|
for i in to_remove {
|
|
|
|
self.sync_native_property(&i.to_string(), gc_context, None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.sync_native_property("length", gc_context, Some(new_length.into()));
|
|
|
|
}
|
|
|
|
|
2019-12-15 20:25:41 +00:00
|
|
|
fn array(&self) -> Vec<Value<'gc>> {
|
2019-12-13 14:50:58 +00:00
|
|
|
match &self.0.read().array {
|
|
|
|
ArrayStorage::Vector(vector) => vector.to_owned(),
|
|
|
|
ArrayStorage::Properties { length } => {
|
|
|
|
let mut values = Vec::new();
|
|
|
|
for i in 0..*length {
|
2019-12-15 20:25:41 +00:00
|
|
|
values.push(self.array_element(i));
|
2019-12-13 14:50:58 +00:00
|
|
|
}
|
|
|
|
values
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-15 20:25:41 +00:00
|
|
|
fn array_element(&self, index: usize) -> Value<'gc> {
|
2019-12-13 14:50:58 +00:00
|
|
|
match &self.0.read().array {
|
|
|
|
ArrayStorage::Vector(vector) => {
|
|
|
|
if let Some(value) = vector.get(index) {
|
|
|
|
value.to_owned()
|
|
|
|
} else {
|
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ArrayStorage::Properties { length } => {
|
|
|
|
if index < *length {
|
|
|
|
if let Some(Property::Stored { value, .. }) =
|
2020-03-27 05:24:59 +00:00
|
|
|
self.0.read().values.get(&index.to_string(), false)
|
2019-12-13 14:50:58 +00:00
|
|
|
{
|
|
|
|
return value.to_owned();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_array_element(
|
|
|
|
&self,
|
|
|
|
index: usize,
|
|
|
|
value: Value<'gc>,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
) -> usize {
|
|
|
|
self.sync_native_property(&index.to_string(), gc_context, Some(value.clone()));
|
|
|
|
let mut adjust_length = false;
|
|
|
|
let length = match &mut self.0.write(gc_context).array {
|
|
|
|
ArrayStorage::Vector(vector) => {
|
|
|
|
if index >= vector.len() {
|
|
|
|
vector.resize(index + 1, Value::Undefined);
|
|
|
|
}
|
|
|
|
vector[index] = value.clone();
|
|
|
|
adjust_length = true;
|
|
|
|
vector.len()
|
|
|
|
}
|
|
|
|
ArrayStorage::Properties { length } => *length,
|
|
|
|
};
|
|
|
|
if adjust_length {
|
|
|
|
self.sync_native_property("length", gc_context, Some(length.into()));
|
|
|
|
}
|
|
|
|
length
|
|
|
|
}
|
|
|
|
|
|
|
|
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
|
|
|
|
if let ArrayStorage::Vector(vector) = &mut self.0.write(gc_context).array {
|
|
|
|
if index < vector.len() {
|
|
|
|
vector[index] = Value::Undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-25 03:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use crate::avm1::activation::Activation;
|
|
|
|
use crate::avm1::property::Attribute::*;
|
|
|
|
use crate::backend::audio::NullAudioBackend;
|
2019-12-18 21:25:54 +00:00
|
|
|
use crate::backend::input::NullInputBackend;
|
2019-10-25 03:21:35 +00:00
|
|
|
use crate::backend::navigator::NullNavigatorBackend;
|
|
|
|
use crate::backend::render::NullRenderer;
|
2019-12-06 18:24:36 +00:00
|
|
|
use crate::display_object::MovieClip;
|
2019-10-25 03:21:35 +00:00
|
|
|
use crate::library::Library;
|
2020-01-10 23:28:49 +00:00
|
|
|
use crate::loader::LoadManager;
|
2019-10-25 03:21:35 +00:00
|
|
|
use crate::prelude::*;
|
2020-04-20 09:38:01 +00:00
|
|
|
use crate::tag_utils::{SwfMovie, SwfSlice};
|
2019-10-25 03:21:35 +00:00
|
|
|
use gc_arena::rootless_arena;
|
|
|
|
use rand::{rngs::SmallRng, SeedableRng};
|
2019-12-02 02:00:01 +00:00
|
|
|
use std::collections::BTreeMap;
|
2019-10-25 03:21:35 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
fn with_object<F, R>(swf_version: u8, test: F) -> R
|
|
|
|
where
|
2019-12-06 18:24:36 +00:00
|
|
|
F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>, Object<'gc>) -> R,
|
2019-10-25 03:21:35 +00:00
|
|
|
{
|
|
|
|
rootless_arena(|gc_context| {
|
|
|
|
let mut avm = Avm1::new(gc_context, swf_version);
|
2019-11-12 20:05:18 +00:00
|
|
|
let swf = Arc::new(SwfMovie::empty(swf_version));
|
2020-04-20 09:38:01 +00:00
|
|
|
let mut root: DisplayObject<'_> =
|
|
|
|
MovieClip::new(SwfSlice::empty(swf.clone()), gc_context).into();
|
2020-02-01 23:29:21 +00:00
|
|
|
root.set_depth(gc_context, 0);
|
2020-02-24 07:41:55 +00:00
|
|
|
let mut levels = BTreeMap::new();
|
|
|
|
levels.insert(0, root);
|
2019-12-02 02:00:01 +00:00
|
|
|
|
2019-10-25 03:21:35 +00:00
|
|
|
let mut context = UpdateContext {
|
|
|
|
gc_context,
|
|
|
|
global_time: 0,
|
|
|
|
player_version: 32,
|
2019-11-12 20:05:18 +00:00
|
|
|
swf: &swf,
|
2020-02-24 07:41:55 +00:00
|
|
|
levels: &mut levels,
|
2019-10-25 03:21:35 +00:00
|
|
|
rng: &mut SmallRng::from_seed([0u8; 16]),
|
|
|
|
action_queue: &mut crate::context::ActionQueue::new(),
|
|
|
|
audio: &mut NullAudioBackend::new(),
|
2019-12-18 21:25:54 +00:00
|
|
|
input: &mut NullInputBackend::new(),
|
2019-10-25 03:21:35 +00:00
|
|
|
background_color: &mut Color {
|
|
|
|
r: 0,
|
|
|
|
g: 0,
|
|
|
|
b: 0,
|
|
|
|
a: 0,
|
|
|
|
},
|
2019-11-14 02:41:38 +00:00
|
|
|
library: &mut Library::default(),
|
2019-10-25 03:21:35 +00:00
|
|
|
navigator: &mut NullNavigatorBackend::new(),
|
|
|
|
renderer: &mut NullRenderer::new(),
|
|
|
|
system_prototypes: avm.prototypes().clone(),
|
2019-12-08 18:49:23 +00:00
|
|
|
mouse_hovered_object: None,
|
2019-12-16 09:51:19 +00:00
|
|
|
mouse_position: &(Twips::new(0), Twips::new(0)),
|
2019-12-21 23:37:27 +00:00
|
|
|
drag_object: &mut None,
|
2019-12-18 06:26:09 +00:00
|
|
|
stage_size: (Twips::from_pixels(550.0), Twips::from_pixels(400.0)),
|
2019-11-08 20:09:57 +00:00
|
|
|
player: None,
|
2020-01-10 23:28:49 +00:00
|
|
|
load_manager: &mut LoadManager::new(),
|
2019-10-25 03:21:35 +00:00
|
|
|
};
|
|
|
|
|
2020-01-20 21:38:23 +00:00
|
|
|
root.post_instantiation(&mut avm, &mut context, root);
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
let object = ScriptObject::object(gc_context, Some(avm.prototypes().object)).into();
|
2019-10-25 03:21:35 +00:00
|
|
|
|
|
|
|
let globals = avm.global_object_cell();
|
|
|
|
avm.insert_stack_frame(GcCell::allocate(
|
|
|
|
gc_context,
|
2019-12-19 23:42:26 +00:00
|
|
|
Activation::from_nothing(swf_version, globals, gc_context, root),
|
2019-10-25 03:21:35 +00:00
|
|
|
));
|
|
|
|
|
|
|
|
test(&mut avm, &mut context, object)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_undefined() {
|
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("not_defined", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate(Value::Undefined)
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_set_get() {
|
2019-12-09 22:19:35 +00:00
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"forced",
|
|
|
|
"forced".into(),
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-10-25 03:21:35 +00:00
|
|
|
object
|
2019-12-10 09:33:22 +00:00
|
|
|
.set("natural", "natural".into(), avm, context)
|
2019-10-25 03:21:35 +00:00
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("forced", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("forced".into())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("natural", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("natural".into())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_set_readonly() {
|
2019-12-09 22:19:35 +00:00
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"normal",
|
|
|
|
"initial".into(),
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"readonly",
|
|
|
|
"initial".into(),
|
|
|
|
ReadOnly.into(),
|
|
|
|
);
|
2019-10-25 03:21:35 +00:00
|
|
|
|
|
|
|
object
|
2019-12-10 09:33:22 +00:00
|
|
|
.set("normal", "replaced".into(), avm, context)
|
2019-10-25 03:21:35 +00:00
|
|
|
.unwrap();
|
|
|
|
object
|
2019-12-10 09:33:22 +00:00
|
|
|
.set("readonly", "replaced".into(), avm, context)
|
2019-10-25 03:21:35 +00:00
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("normal", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("replaced".into())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("readonly", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("initial".into())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_deletable_not_readonly() {
|
2019-12-09 22:19:35 +00:00
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"test",
|
|
|
|
"initial".into(),
|
|
|
|
DontDelete.into(),
|
|
|
|
);
|
2019-10-25 03:21:35 +00:00
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
assert_eq!(object.delete(avm, context.gc_context, "test"), false);
|
2019-10-25 03:21:35 +00:00
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("test", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("initial".into())
|
|
|
|
);
|
|
|
|
|
|
|
|
object
|
2019-12-09 22:19:35 +00:00
|
|
|
.as_script_object()
|
2019-10-25 03:21:35 +00:00
|
|
|
.unwrap()
|
2019-12-10 09:33:22 +00:00
|
|
|
.set("test", "replaced".into(), avm, context)
|
2019-10-25 03:21:35 +00:00
|
|
|
.unwrap();
|
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
assert_eq!(object.delete(avm, context.gc_context, "test"), false);
|
2019-10-25 03:21:35 +00:00
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("test", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("replaced".into())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_virtual_get() {
|
2019-12-09 22:19:35 +00:00
|
|
|
with_object(0, |avm, context, object| {
|
2019-10-25 03:21:35 +00:00
|
|
|
let getter = Executable::Native(|_avm, _context, _this, _args| {
|
|
|
|
Ok(ReturnValue::Immediate("Virtual!".into()))
|
|
|
|
});
|
|
|
|
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().add_property(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"test",
|
|
|
|
getter,
|
|
|
|
None,
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-10-25 03:21:35 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("test", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("Virtual!".into())
|
|
|
|
);
|
|
|
|
|
|
|
|
// This set should do nothing
|
2019-12-10 09:33:22 +00:00
|
|
|
object.set("test", "Ignored!".into(), avm, context).unwrap();
|
2019-10-25 03:21:35 +00:00
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("test", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("Virtual!".into())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_delete() {
|
2019-12-09 22:19:35 +00:00
|
|
|
with_object(0, |avm, context, object| {
|
2019-10-25 03:21:35 +00:00
|
|
|
let getter = Executable::Native(|_avm, _context, _this, _args| {
|
|
|
|
Ok(ReturnValue::Immediate("Virtual!".into()))
|
|
|
|
});
|
|
|
|
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().add_property(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"virtual",
|
|
|
|
getter.clone(),
|
|
|
|
None,
|
|
|
|
EnumSet::empty(),
|
2019-10-25 03:21:35 +00:00
|
|
|
);
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().add_property(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"virtual_un",
|
|
|
|
getter,
|
|
|
|
None,
|
|
|
|
DontDelete.into(),
|
|
|
|
);
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"stored",
|
|
|
|
"Stored!".into(),
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"stored_un",
|
|
|
|
"Stored!".into(),
|
|
|
|
DontDelete.into(),
|
|
|
|
);
|
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
assert_eq!(object.delete(avm, context.gc_context, "virtual"), true);
|
|
|
|
assert_eq!(object.delete(avm, context.gc_context, "virtual_un"), false);
|
|
|
|
assert_eq!(object.delete(avm, context.gc_context, "stored"), true);
|
|
|
|
assert_eq!(object.delete(avm, context.gc_context, "stored_un"), false);
|
|
|
|
assert_eq!(
|
|
|
|
object.delete(avm, context.gc_context, "non_existent"),
|
|
|
|
false
|
|
|
|
);
|
2019-10-25 03:21:35 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("virtual", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate(Value::Undefined)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("virtual_un", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("Virtual!".into())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("stored", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate(Value::Undefined)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-12-10 09:33:22 +00:00
|
|
|
object.get("stored_un", avm, context).unwrap(),
|
2019-10-25 03:21:35 +00:00
|
|
|
ReturnValue::Immediate("Stored!".into())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_iter_values() {
|
2020-03-27 05:24:59 +00:00
|
|
|
with_object(0, |avm, context, object| {
|
2019-10-25 03:21:35 +00:00
|
|
|
let getter = Executable::Native(|_avm, _context, _this, _args| {
|
|
|
|
Ok(ReturnValue::Immediate(Value::Null))
|
|
|
|
});
|
|
|
|
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"stored",
|
|
|
|
Value::Null,
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().define_value(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"stored_hidden",
|
|
|
|
Value::Null,
|
|
|
|
DontEnum.into(),
|
|
|
|
);
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().add_property(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"virtual",
|
|
|
|
getter.clone(),
|
|
|
|
None,
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-12-09 22:19:35 +00:00
|
|
|
object.as_script_object().unwrap().add_property(
|
2019-12-06 18:24:36 +00:00
|
|
|
context.gc_context,
|
|
|
|
"virtual_hidden",
|
|
|
|
getter,
|
|
|
|
None,
|
|
|
|
DontEnum.into(),
|
|
|
|
);
|
2019-10-25 03:21:35 +00:00
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
let keys: Vec<_> = object.get_keys(avm);
|
2019-10-25 03:21:35 +00:00
|
|
|
assert_eq!(keys.len(), 2);
|
|
|
|
assert_eq!(keys.contains(&"stored".to_string()), true);
|
|
|
|
assert_eq!(keys.contains(&"stored_hidden".to_string()), false);
|
|
|
|
assert_eq!(keys.contains(&"virtual".to_string()), true);
|
|
|
|
assert_eq!(keys.contains(&"virtual_hidden".to_string()), false);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|