2019-10-08 18:35:23 +00:00
|
|
|
use self::Attribute::*;
|
2019-10-08 22:54:58 +00:00
|
|
|
use crate::avm1::function::{Avm1Function, Executable, NativeFunction};
|
2019-10-06 21:46:49 +00:00
|
|
|
use crate::avm1::{ActionContext, Avm1, Value};
|
2019-08-28 23:29:43 +00:00
|
|
|
use crate::display_object::DisplayNode;
|
|
|
|
use core::fmt;
|
2019-10-08 12:21:07 +00:00
|
|
|
use enumset::{EnumSet, EnumSetType};
|
2019-08-28 23:29:43 +00:00
|
|
|
use gc_arena::{GcCell, MutationContext};
|
2019-09-27 13:46:53 +00:00
|
|
|
use std::collections::hash_map::Entry;
|
2019-08-26 22:53:50 +00:00
|
|
|
use std::collections::HashMap;
|
2019-09-27 13:46:53 +00:00
|
|
|
use std::mem::replace;
|
2019-08-26 22:53:50 +00:00
|
|
|
|
2019-08-31 12:09:37 +00:00
|
|
|
pub const TYPE_OF_OBJECT: &str = "object";
|
|
|
|
pub const TYPE_OF_FUNCTION: &str = "function";
|
|
|
|
pub const TYPE_OF_MOVIE_CLIP: &str = "movieclip";
|
|
|
|
|
2019-08-31 16:28:28 +00:00
|
|
|
fn default_to_string<'gc>(
|
2019-09-03 00:44:24 +00:00
|
|
|
_: &mut Avm1<'gc>,
|
2019-09-02 17:28:38 +00:00
|
|
|
_: &mut ActionContext<'_, 'gc, '_>,
|
2019-08-31 16:28:28 +00:00
|
|
|
_: GcCell<'gc, Object<'gc>>,
|
|
|
|
_: &[Value<'gc>],
|
|
|
|
) -> Value<'gc> {
|
2019-08-31 15:54:15 +00:00
|
|
|
Value::String("[Object object]".to_string())
|
|
|
|
}
|
|
|
|
|
2019-10-08 12:21:07 +00:00
|
|
|
#[derive(EnumSetType, Debug)]
|
|
|
|
pub enum Attribute {
|
|
|
|
DontDelete,
|
2019-10-08 14:34:08 +00:00
|
|
|
DontEnum,
|
2019-10-08 13:24:57 +00:00
|
|
|
ReadOnly,
|
2019-10-08 12:21:07 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 13:46:53 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub enum Property<'gc> {
|
|
|
|
Virtual {
|
|
|
|
get: NativeFunction<'gc>,
|
|
|
|
set: Option<NativeFunction<'gc>>,
|
2019-10-08 12:21:07 +00:00
|
|
|
attributes: EnumSet<Attribute>,
|
2019-09-27 13:46:53 +00:00
|
|
|
},
|
|
|
|
Stored {
|
|
|
|
value: Value<'gc>,
|
2019-10-08 12:21:07 +00:00
|
|
|
attributes: EnumSet<Attribute>,
|
2019-09-27 13:46:53 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Property<'gc> {
|
|
|
|
pub fn get(
|
|
|
|
&self,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut ActionContext<'_, 'gc, '_>,
|
|
|
|
this: GcCell<'gc, Object<'gc>>,
|
|
|
|
) -> Value<'gc> {
|
|
|
|
match self {
|
|
|
|
Property::Virtual { get, .. } => get(avm, context, this, &[]),
|
|
|
|
Property::Stored { value, .. } => value.to_owned(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set(
|
|
|
|
&mut self,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut ActionContext<'_, 'gc, '_>,
|
|
|
|
this: GcCell<'gc, Object<'gc>>,
|
|
|
|
new_value: Value<'gc>,
|
|
|
|
) {
|
|
|
|
match self {
|
|
|
|
Property::Virtual { set, .. } => {
|
|
|
|
if let Some(function) = set {
|
|
|
|
function(avm, context, this, &[new_value]);
|
|
|
|
}
|
|
|
|
}
|
2019-10-08 14:34:08 +00:00
|
|
|
Property::Stored {
|
|
|
|
value, attributes, ..
|
|
|
|
} => {
|
2019-10-08 18:35:23 +00:00
|
|
|
if !attributes.contains(ReadOnly) {
|
2019-10-08 13:24:57 +00:00
|
|
|
replace::<Value<'gc>>(value, new_value);
|
|
|
|
}
|
2019-09-27 13:46:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-08 12:21:07 +00:00
|
|
|
|
|
|
|
pub fn can_delete(&self) -> bool {
|
|
|
|
match self {
|
2019-10-08 18:35:23 +00:00
|
|
|
Property::Virtual { attributes, .. } => !attributes.contains(DontDelete),
|
|
|
|
Property::Stored { attributes, .. } => !attributes.contains(DontDelete),
|
2019-10-08 12:21:07 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-08 14:34:08 +00:00
|
|
|
|
|
|
|
pub fn is_enumerable(&self) -> bool {
|
|
|
|
match self {
|
2019-10-08 18:35:23 +00:00
|
|
|
Property::Virtual { attributes, .. } => !attributes.contains(DontEnum),
|
|
|
|
Property::Stored { attributes, .. } => !attributes.contains(DontEnum),
|
2019-10-08 14:34:08 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-27 13:46:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for Property<'gc> {
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
|
|
match self {
|
2019-10-08 12:21:07 +00:00
|
|
|
Property::Virtual { get, set, .. } => {
|
2019-09-27 13:46:53 +00:00
|
|
|
get.trace(cc);
|
|
|
|
set.trace(cc);
|
|
|
|
}
|
|
|
|
Property::Stored { value, .. } => value.trace(cc),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for Property<'_> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
2019-10-08 12:21:07 +00:00
|
|
|
Property::Virtual {
|
|
|
|
get: _,
|
|
|
|
set,
|
|
|
|
attributes,
|
|
|
|
} => f
|
2019-09-27 13:46:53 +00:00
|
|
|
.debug_struct("Property::Virtual")
|
|
|
|
.field("get", &true)
|
|
|
|
.field("set", &set.is_some())
|
2019-10-08 12:21:07 +00:00
|
|
|
.field("attributes", &attributes)
|
2019-09-27 13:46:53 +00:00
|
|
|
.finish(),
|
2019-10-08 12:21:07 +00:00
|
|
|
Property::Stored { value, attributes } => f
|
2019-09-27 13:46:53 +00:00
|
|
|
.debug_struct("Property::Stored")
|
|
|
|
.field("value", &value)
|
2019-10-08 12:21:07 +00:00
|
|
|
.field("attributes", &attributes)
|
2019-09-27 13:46:53 +00:00
|
|
|
.finish(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-31 12:09:37 +00:00
|
|
|
#[derive(Clone)]
|
2019-08-26 22:53:50 +00:00
|
|
|
pub struct Object<'gc> {
|
2019-08-28 23:29:43 +00:00
|
|
|
display_node: Option<DisplayNode<'gc>>,
|
2019-09-27 13:46:53 +00:00
|
|
|
values: HashMap<String, Property<'gc>>,
|
2019-09-16 01:21:57 +00:00
|
|
|
function: Option<Executable<'gc>>,
|
2019-08-31 12:09:37 +00:00
|
|
|
type_of: &'static str,
|
2019-08-28 23:29:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for Object<'gc> {
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
|
|
self.display_node.trace(cc);
|
|
|
|
self.values.trace(cc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for Object<'_> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
f.debug_struct("Object")
|
|
|
|
.field("display_node", &self.display_node)
|
|
|
|
.field("values", &self.values)
|
|
|
|
.field("function", &self.function.is_some())
|
|
|
|
.finish()
|
|
|
|
}
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Object<'gc> {
|
2019-08-31 15:54:15 +00:00
|
|
|
pub fn object(gc_context: MutationContext<'gc, '_>) -> Self {
|
|
|
|
let mut result = Self {
|
2019-08-31 12:09:37 +00:00
|
|
|
type_of: TYPE_OF_OBJECT,
|
|
|
|
display_node: None,
|
|
|
|
values: HashMap::new(),
|
|
|
|
function: None,
|
2019-08-31 15:54:15 +00:00
|
|
|
};
|
|
|
|
|
2019-10-08 12:21:07 +00:00
|
|
|
result.force_set_function(
|
|
|
|
"toString",
|
|
|
|
default_to_string,
|
|
|
|
gc_context,
|
2019-10-08 18:35:23 +00:00
|
|
|
DontDelete | DontEnum,
|
2019-10-08 12:21:07 +00:00
|
|
|
);
|
2019-08-31 15:54:15 +00:00
|
|
|
|
|
|
|
result
|
2019-08-28 23:29:43 +00:00
|
|
|
}
|
|
|
|
|
2019-09-17 04:04:38 +00:00
|
|
|
/// Constructs an object with no values, not even builtins.
|
2019-10-06 21:46:49 +00:00
|
|
|
///
|
2019-09-17 04:04:38 +00:00
|
|
|
/// Intended for constructing scope chains, since they exclusively use the
|
|
|
|
/// object values, but can't just have a hashmap because of `with` and
|
|
|
|
/// friends.
|
|
|
|
pub fn bare_object() -> Self {
|
|
|
|
Self {
|
|
|
|
type_of: TYPE_OF_OBJECT,
|
|
|
|
display_node: None,
|
|
|
|
values: HashMap::new(),
|
|
|
|
function: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-16 01:21:57 +00:00
|
|
|
pub fn native_function(function: NativeFunction<'gc>) -> Self {
|
2019-08-26 22:53:50 +00:00
|
|
|
Self {
|
2019-08-31 12:09:37 +00:00
|
|
|
type_of: TYPE_OF_FUNCTION,
|
2019-09-16 01:21:57 +00:00
|
|
|
function: Some(Executable::Native(function)),
|
2019-08-31 12:09:37 +00:00
|
|
|
display_node: None,
|
|
|
|
values: HashMap::new(),
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-08 22:54:58 +00:00
|
|
|
pub fn action_function(func: Avm1Function<'gc>) -> Self {
|
2019-09-16 05:37:15 +00:00
|
|
|
Self {
|
|
|
|
type_of: TYPE_OF_FUNCTION,
|
2019-10-08 22:54:58 +00:00
|
|
|
function: Some(Executable::Action(func)),
|
2019-09-29 03:11:03 +00:00
|
|
|
display_node: None,
|
2019-10-06 21:46:49 +00:00
|
|
|
values: HashMap::new(),
|
2019-09-29 03:11:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-28 23:29:43 +00:00
|
|
|
pub fn set_display_node(&mut self, display_node: DisplayNode<'gc>) {
|
|
|
|
self.display_node = Some(display_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn display_node(&self) -> Option<DisplayNode<'gc>> {
|
|
|
|
self.display_node
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 13:46:53 +00:00
|
|
|
pub fn set(
|
|
|
|
&mut self,
|
|
|
|
name: &str,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut ActionContext<'_, 'gc, '_>,
|
|
|
|
this: GcCell<'gc, Object<'gc>>,
|
|
|
|
) {
|
|
|
|
match self.values.entry(name.to_owned()) {
|
|
|
|
Entry::Occupied(mut entry) => {
|
|
|
|
entry.get_mut().set(avm, context, this, value);
|
|
|
|
}
|
|
|
|
Entry::Vacant(entry) => {
|
2019-10-08 12:21:07 +00:00
|
|
|
entry.insert(Property::Stored {
|
|
|
|
value,
|
|
|
|
attributes: Default::default(),
|
|
|
|
});
|
2019-09-27 13:46:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-08 12:21:07 +00:00
|
|
|
pub fn force_set_virtual<A>(
|
2019-09-27 13:46:53 +00:00
|
|
|
&mut self,
|
|
|
|
name: &str,
|
|
|
|
get: NativeFunction<'gc>,
|
|
|
|
set: Option<NativeFunction<'gc>>,
|
2019-10-08 12:21:07 +00:00
|
|
|
attributes: A,
|
|
|
|
) where
|
|
|
|
A: Into<EnumSet<Attribute>>,
|
|
|
|
{
|
|
|
|
self.values.insert(
|
|
|
|
name.to_owned(),
|
|
|
|
Property::Virtual {
|
|
|
|
get,
|
|
|
|
set,
|
|
|
|
attributes: attributes.into(),
|
|
|
|
},
|
|
|
|
);
|
2019-09-02 20:19:09 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 12:21:07 +00:00
|
|
|
pub fn force_set<A>(&mut self, name: &str, value: Value<'gc>, attributes: A)
|
|
|
|
where
|
|
|
|
A: Into<EnumSet<Attribute>>,
|
|
|
|
{
|
|
|
|
self.values.insert(
|
|
|
|
name.to_string(),
|
|
|
|
Property::Stored {
|
|
|
|
value,
|
|
|
|
attributes: attributes.into(),
|
|
|
|
},
|
|
|
|
);
|
2019-09-27 13:46:53 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 12:21:07 +00:00
|
|
|
pub fn force_set_function<A>(
|
2019-09-27 13:46:53 +00:00
|
|
|
&mut self,
|
|
|
|
name: &str,
|
|
|
|
function: NativeFunction<'gc>,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2019-10-08 12:21:07 +00:00
|
|
|
attributes: A,
|
|
|
|
) where
|
|
|
|
A: Into<EnumSet<Attribute>>,
|
|
|
|
{
|
2019-09-27 13:46:53 +00:00
|
|
|
self.force_set(
|
2019-08-30 18:37:30 +00:00
|
|
|
name,
|
2019-10-06 21:46:49 +00:00
|
|
|
Value::Object(GcCell::allocate(
|
|
|
|
gc_context,
|
|
|
|
Object::native_function(function),
|
|
|
|
)),
|
2019-10-08 12:21:07 +00:00
|
|
|
attributes,
|
2019-08-30 18:37:30 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-09-27 13:46:53 +00:00
|
|
|
pub fn get(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut ActionContext<'_, 'gc, '_>,
|
|
|
|
this: GcCell<'gc, Object<'gc>>,
|
|
|
|
) -> Value<'gc> {
|
2019-08-28 23:29:43 +00:00
|
|
|
if let Some(value) = self.values.get(name) {
|
2019-09-27 13:46:53 +00:00
|
|
|
return value.get(avm, context, this);
|
2019-08-28 23:29:43 +00:00
|
|
|
}
|
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
|
2019-09-22 01:41:30 +00:00
|
|
|
/// Retrieve a value from an object if and only if the value in the object
|
|
|
|
/// property is non-virtual.
|
|
|
|
pub fn force_get(&self, name: &str) -> Value<'gc> {
|
|
|
|
if let Some(Property::Stored { value, .. }) = self.values.get(name) {
|
|
|
|
return value.to_owned();
|
|
|
|
}
|
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
|
2019-09-23 18:33:44 +00:00
|
|
|
/// Delete a given value off the object.
|
2019-10-08 12:21:07 +00:00
|
|
|
pub fn delete(&mut self, name: &str) -> bool {
|
|
|
|
if let Some(prop) = self.values.get(name) {
|
|
|
|
if prop.can_delete() {
|
|
|
|
self.values.remove(name);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
2019-09-23 18:33:44 +00:00
|
|
|
}
|
|
|
|
|
2019-08-28 23:29:43 +00:00
|
|
|
pub fn has_property(&self, name: &str) -> bool {
|
|
|
|
self.values.contains_key(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn has_own_property(&self, name: &str) -> bool {
|
|
|
|
self.values.contains_key(name)
|
|
|
|
}
|
|
|
|
|
2019-10-08 14:34:08 +00:00
|
|
|
pub fn get_keys(&self) -> Vec<String> {
|
|
|
|
self.values
|
|
|
|
.iter()
|
|
|
|
.filter_map(|(k, p)| {
|
|
|
|
if p.is_enumerable() {
|
|
|
|
Some(k.to_string())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
2019-09-17 04:04:38 +00:00
|
|
|
}
|
|
|
|
|
2019-08-28 23:29:43 +00:00
|
|
|
pub fn call(
|
|
|
|
&self,
|
2019-09-03 00:44:24 +00:00
|
|
|
avm: &mut Avm1<'gc>,
|
2019-09-02 17:28:38 +00:00
|
|
|
context: &mut ActionContext<'_, 'gc, '_>,
|
2019-08-28 23:29:43 +00:00
|
|
|
this: GcCell<'gc, Object<'gc>>,
|
|
|
|
args: &[Value<'gc>],
|
2019-09-16 01:21:57 +00:00
|
|
|
) -> Option<Value<'gc>> {
|
|
|
|
if let Some(function) = &self.function {
|
|
|
|
function.exec(avm, context, this, args)
|
2019-08-28 23:29:43 +00:00
|
|
|
} else {
|
2019-09-16 01:21:57 +00:00
|
|
|
Some(Value::Undefined)
|
2019-08-28 23:29:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-31 12:09:37 +00:00
|
|
|
|
2019-08-31 16:28:28 +00:00
|
|
|
pub fn as_string(&self) -> String {
|
|
|
|
if self.function.is_some() {
|
|
|
|
"[type Function]".to_string()
|
|
|
|
} else {
|
|
|
|
"[object Object]".to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-31 12:09:37 +00:00
|
|
|
pub fn set_type_of(&mut self, type_of: &'static str) {
|
|
|
|
self.type_of = type_of;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn type_of(&self) -> &'static str {
|
|
|
|
self.type_of
|
|
|
|
}
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
2019-09-27 13:46:53 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
2019-10-06 21:46:49 +00:00
|
|
|
use crate::avm1::activation::Activation;
|
2019-09-27 13:46:53 +00:00
|
|
|
use crate::backend::audio::NullAudioBackend;
|
|
|
|
use crate::backend::navigator::NullNavigatorBackend;
|
|
|
|
use crate::display_object::DisplayObject;
|
|
|
|
use crate::movie_clip::MovieClip;
|
|
|
|
use gc_arena::rootless_arena;
|
|
|
|
use rand::{rngs::SmallRng, SeedableRng};
|
|
|
|
|
|
|
|
fn with_object<F, R>(swf_version: u8, test: F) -> R
|
|
|
|
where
|
|
|
|
F: for<'a, 'gc> FnOnce(
|
|
|
|
&mut Avm1<'gc>,
|
|
|
|
&mut ActionContext<'a, 'gc, '_>,
|
|
|
|
GcCell<'gc, Object<'gc>>,
|
|
|
|
) -> R,
|
|
|
|
{
|
|
|
|
rootless_arena(|gc_context| {
|
2019-10-04 02:42:32 +00:00
|
|
|
let mut avm = Avm1::new(gc_context);
|
2019-09-27 13:46:53 +00:00
|
|
|
let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(gc_context));
|
|
|
|
let root = GcCell::allocate(gc_context, movie_clip);
|
|
|
|
let mut context = ActionContext {
|
|
|
|
gc_context,
|
|
|
|
global_time: 0,
|
|
|
|
root,
|
|
|
|
start_clip: root,
|
|
|
|
active_clip: root,
|
|
|
|
target_clip: Some(root),
|
|
|
|
target_path: Value::Undefined,
|
|
|
|
rng: &mut SmallRng::from_seed([0u8; 16]),
|
|
|
|
audio: &mut NullAudioBackend::new(),
|
|
|
|
navigator: &mut NullNavigatorBackend::new(),
|
|
|
|
};
|
|
|
|
let object = GcCell::allocate(gc_context, Object::object(gc_context));
|
|
|
|
|
2019-10-04 02:42:32 +00:00
|
|
|
let globals = avm.global_object_cell();
|
|
|
|
avm.insert_stack_frame(Activation::from_nothing(swf_version, globals, gc_context));
|
|
|
|
|
2019-09-27 13:46:53 +00:00
|
|
|
test(&mut avm, &mut context, object)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_undefined() {
|
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("not_defined", avm, context, object),
|
|
|
|
Value::Undefined
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_set_get() {
|
|
|
|
with_object(0, |avm, context, object| {
|
2019-10-08 12:21:07 +00:00
|
|
|
object.write(context.gc_context).force_set(
|
|
|
|
"forced",
|
|
|
|
Value::String("forced".to_string()),
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-09-27 13:46:53 +00:00
|
|
|
object.write(context.gc_context).set(
|
|
|
|
"natural",
|
|
|
|
Value::String("natural".to_string()),
|
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
object,
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("forced", avm, context, object),
|
|
|
|
Value::String("forced".to_string())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("natural", avm, context, object),
|
|
|
|
Value::String("natural".to_string())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-08 13:24:57 +00:00
|
|
|
#[test]
|
|
|
|
fn test_set_readonly() {
|
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
object.write(context.gc_context).force_set(
|
|
|
|
"normal",
|
|
|
|
Value::String("initial".to_string()),
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
|
|
|
object.write(context.gc_context).force_set(
|
|
|
|
"readonly",
|
|
|
|
Value::String("initial".to_string()),
|
2019-10-08 18:35:23 +00:00
|
|
|
ReadOnly,
|
2019-10-08 13:24:57 +00:00
|
|
|
);
|
|
|
|
|
2019-10-08 14:34:08 +00:00
|
|
|
object.write(context.gc_context).set(
|
|
|
|
"normal",
|
|
|
|
Value::String("replaced".to_string()),
|
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
object,
|
|
|
|
);
|
|
|
|
object.write(context.gc_context).set(
|
|
|
|
"readonly",
|
|
|
|
Value::String("replaced".to_string()),
|
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
object,
|
|
|
|
);
|
2019-10-08 13:24:57 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("normal", avm, context, object),
|
|
|
|
Value::String("replaced".to_string())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("readonly", avm, context, object),
|
|
|
|
Value::String("initial".to_string())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_deletable_not_readonly() {
|
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
object.write(context.gc_context).force_set(
|
|
|
|
"test",
|
|
|
|
Value::String("initial".to_string()),
|
2019-10-08 18:35:23 +00:00
|
|
|
DontDelete,
|
2019-10-08 13:24:57 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(object.write(context.gc_context).delete("test"), false);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("test", avm, context, object),
|
|
|
|
Value::String("initial".to_string())
|
|
|
|
);
|
|
|
|
|
2019-10-08 14:34:08 +00:00
|
|
|
object.write(context.gc_context).set(
|
|
|
|
"test",
|
|
|
|
Value::String("replaced".to_string()),
|
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
object,
|
|
|
|
);
|
2019-10-08 13:24:57 +00:00
|
|
|
|
|
|
|
assert_eq!(object.write(context.gc_context).delete("test"), false);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("test", avm, context, object),
|
|
|
|
Value::String("replaced".to_string())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-27 13:46:53 +00:00
|
|
|
#[test]
|
|
|
|
fn test_virtual_get() {
|
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
let getter: NativeFunction =
|
|
|
|
|_avm, _context, _this, _args| Value::String("Virtual!".to_string());
|
2019-10-08 12:21:07 +00:00
|
|
|
object.write(context.gc_context).force_set_virtual(
|
|
|
|
"test",
|
|
|
|
getter,
|
|
|
|
None,
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
2019-09-27 13:46:53 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("test", avm, context, object),
|
|
|
|
Value::String("Virtual!".to_string())
|
|
|
|
);
|
|
|
|
|
|
|
|
// This set should do nothing
|
|
|
|
object.write(context.gc_context).set(
|
|
|
|
"test",
|
|
|
|
Value::String("Ignored!".to_string()),
|
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
object,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("test", avm, context, object),
|
|
|
|
Value::String("Virtual!".to_string())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
2019-10-08 12:21:07 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_delete() {
|
|
|
|
with_object(0, |avm, context, object| {
|
|
|
|
let getter: NativeFunction =
|
|
|
|
|_avm, _context, _this, _args| Value::String("Virtual!".to_string());
|
|
|
|
|
|
|
|
object.write(context.gc_context).force_set_virtual(
|
|
|
|
"virtual",
|
|
|
|
getter,
|
|
|
|
None,
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
|
|
|
object.write(context.gc_context).force_set_virtual(
|
|
|
|
"virtual_un",
|
|
|
|
getter,
|
|
|
|
None,
|
2019-10-08 18:35:23 +00:00
|
|
|
DontDelete,
|
2019-10-08 12:21:07 +00:00
|
|
|
);
|
|
|
|
object.write(context.gc_context).force_set(
|
|
|
|
"stored",
|
|
|
|
Value::String("Stored!".to_string()),
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
|
|
|
object.write(context.gc_context).force_set(
|
|
|
|
"stored_un",
|
|
|
|
Value::String("Stored!".to_string()),
|
2019-10-08 18:35:23 +00:00
|
|
|
DontDelete,
|
2019-10-08 12:21:07 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(object.write(context.gc_context).delete("virtual"), true);
|
|
|
|
assert_eq!(object.write(context.gc_context).delete("virtual_un"), false);
|
|
|
|
assert_eq!(object.write(context.gc_context).delete("stored"), true);
|
|
|
|
assert_eq!(object.write(context.gc_context).delete("stored_un"), false);
|
2019-10-08 14:34:08 +00:00
|
|
|
assert_eq!(
|
|
|
|
object.write(context.gc_context).delete("non_existent"),
|
|
|
|
false
|
|
|
|
);
|
2019-10-08 12:21:07 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("virtual", avm, context, object),
|
|
|
|
Value::Undefined
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("virtual_un", avm, context, object),
|
|
|
|
Value::String("Virtual!".to_string())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("stored", avm, context, object),
|
|
|
|
Value::Undefined
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
object.read().get("stored_un", avm, context, object),
|
|
|
|
Value::String("Stored!".to_string())
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
2019-10-08 14:34:08 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_iter_values() {
|
|
|
|
with_object(0, |_avm, context, object| {
|
|
|
|
let getter: NativeFunction = |_avm, _context, _this, _args| Value::Null;
|
|
|
|
|
|
|
|
object
|
|
|
|
.write(context.gc_context)
|
|
|
|
.force_set("stored", Value::Null, EnumSet::empty());
|
2019-10-08 18:35:23 +00:00
|
|
|
object
|
|
|
|
.write(context.gc_context)
|
|
|
|
.force_set("stored_hidden", Value::Null, DontEnum);
|
2019-10-08 14:34:08 +00:00
|
|
|
object.write(context.gc_context).force_set_virtual(
|
|
|
|
"virtual",
|
|
|
|
getter,
|
|
|
|
None,
|
|
|
|
EnumSet::empty(),
|
|
|
|
);
|
|
|
|
object.write(context.gc_context).force_set_virtual(
|
|
|
|
"virtual_hidden",
|
|
|
|
getter,
|
|
|
|
None,
|
2019-10-08 18:35:23 +00:00
|
|
|
DontEnum,
|
2019-10-08 14:34:08 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let keys = object.read().get_keys();
|
|
|
|
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);
|
|
|
|
})
|
|
|
|
}
|
2019-09-27 13:46:53 +00:00
|
|
|
}
|