Added object property attributes (initially just DontDelete)
This commit is contained in:
parent
4856efe7d8
commit
f2a4000ee2
|
@ -16,6 +16,7 @@ minimp3 = { version = "0.3.3", optional = true }
|
|||
puremp3 = { version = "0.1", optional = true }
|
||||
rand = "0.6.5"
|
||||
swf = { path = "../swf" }
|
||||
enumset = "0.4.2"
|
||||
|
||||
[dependencies.jpeg-decoder]
|
||||
version = "0.1.16"
|
||||
|
|
|
@ -822,11 +822,8 @@ impl<'gc> Avm1<'gc> {
|
|||
let name = name_val.as_string()?;
|
||||
let object = self.pop()?.as_object()?;
|
||||
|
||||
//Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns
|
||||
//a boolean based on if the delete actually deleted something.
|
||||
let did_exist = Value::Bool(object.read().has_property(name));
|
||||
object.write(context.gc_context).delete(name);
|
||||
self.push(did_exist);
|
||||
let success = object.write(context.gc_context).delete(name);
|
||||
self.push(Value::Bool(success));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Code relating to executable functions + calling conventions.
|
||||
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::object::Object;
|
||||
use crate::avm1::object::{Attribute, Object};
|
||||
use crate::avm1::scope::Scope;
|
||||
use crate::avm1::value::Value;
|
||||
use crate::avm1::{ActionContext, Avm1};
|
||||
|
@ -201,10 +201,18 @@ impl<'gc> Executable<'gc> {
|
|||
let mut arguments = Object::object(ac.gc_context);
|
||||
|
||||
for i in 0..args.len() {
|
||||
arguments.force_set(&format!("{}", i), args.get(i).unwrap().clone());
|
||||
arguments.force_set(
|
||||
&format!("{}", i),
|
||||
args.get(i).unwrap().clone(),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
}
|
||||
|
||||
arguments.force_set("length", Value::Number(args.len() as f64));
|
||||
arguments.force_set(
|
||||
"length",
|
||||
Value::Number(args.len() as f64),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
let argcell = GcCell::allocate(ac.gc_context, arguments);
|
||||
let child_scope = GcCell::allocate(
|
||||
ac.gc_context,
|
||||
|
@ -240,10 +248,18 @@ impl<'gc> Executable<'gc> {
|
|||
let mut arguments = Object::object(ac.gc_context);
|
||||
if !af.suppress_arguments {
|
||||
for i in 0..args.len() {
|
||||
arguments.force_set(&format!("{}", i), args.get(i).unwrap().clone())
|
||||
arguments.force_set(
|
||||
&format!("{}", i),
|
||||
args.get(i).unwrap().clone(),
|
||||
Attribute::DontDelete,
|
||||
)
|
||||
}
|
||||
|
||||
arguments.force_set("length", Value::Number(args.len() as f64));
|
||||
arguments.force_set(
|
||||
"length",
|
||||
Value::Number(args.len() as f64),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
}
|
||||
|
||||
let argcell = GcCell::allocate(ac.gc_context, arguments);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::avm1::fscommand;
|
||||
use crate::avm1::{ActionContext, Avm1, Object, Value};
|
||||
use crate::backend::navigator::NavigationMethod;
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use rand::Rng;
|
||||
|
||||
|
@ -52,11 +53,19 @@ pub fn random<'gc>(
|
|||
pub fn create_globals<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> {
|
||||
let mut globals = Object::object(gc_context);
|
||||
|
||||
globals.force_set("Math", Value::Object(math::create(gc_context)));
|
||||
globals.force_set_function("getURL", getURL, gc_context);
|
||||
globals.force_set_function("random", random, gc_context);
|
||||
globals.force_set("NaN", Value::Number(std::f64::NAN));
|
||||
globals.force_set("Infinity", Value::Number(std::f64::INFINITY));
|
||||
globals.force_set(
|
||||
"Math",
|
||||
Value::Object(math::create(gc_context)),
|
||||
EnumSet::empty(),
|
||||
);
|
||||
globals.force_set_function("getURL", getURL, gc_context, EnumSet::empty());
|
||||
globals.force_set_function("random", random, gc_context, EnumSet::empty());
|
||||
globals.force_set("NaN", Value::Number(std::f64::NAN), EnumSet::empty());
|
||||
globals.force_set(
|
||||
"Infinity",
|
||||
Value::Number(std::f64::INFINITY),
|
||||
EnumSet::empty(),
|
||||
);
|
||||
|
||||
globals
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::avm1::object::Attribute;
|
||||
use crate::avm1::{ActionContext, Avm1, Object, Value};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use rand::Rng;
|
||||
|
@ -16,6 +17,7 @@ macro_rules! wrap_std {
|
|||
}
|
||||
},
|
||||
$gc_context,
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
)*
|
||||
}};
|
||||
|
@ -49,14 +51,46 @@ pub fn random<'gc>(
|
|||
pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> {
|
||||
let mut math = Object::object(gc_context);
|
||||
|
||||
math.force_set("E", Value::Number(std::f64::consts::E));
|
||||
math.force_set("LN10", Value::Number(std::f64::consts::LN_10));
|
||||
math.force_set("LN2", Value::Number(std::f64::consts::LN_2));
|
||||
math.force_set("LOG10E", Value::Number(std::f64::consts::LOG10_E));
|
||||
math.force_set("LOG2E", Value::Number(std::f64::consts::LOG2_E));
|
||||
math.force_set("PI", Value::Number(std::f64::consts::PI));
|
||||
math.force_set("SQRT1_2", Value::Number(std::f64::consts::FRAC_1_SQRT_2));
|
||||
math.force_set("SQRT2", Value::Number(std::f64::consts::SQRT_2));
|
||||
math.force_set(
|
||||
"E",
|
||||
Value::Number(std::f64::consts::E),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
math.force_set(
|
||||
"LN10",
|
||||
Value::Number(std::f64::consts::LN_10),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
math.force_set(
|
||||
"LN2",
|
||||
Value::Number(std::f64::consts::LN_2),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
math.force_set(
|
||||
"LOG10E",
|
||||
Value::Number(std::f64::consts::LOG10_E),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
math.force_set(
|
||||
"LOG2E",
|
||||
Value::Number(std::f64::consts::LOG2_E),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
math.force_set(
|
||||
"PI",
|
||||
Value::Number(std::f64::consts::PI),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
math.force_set(
|
||||
"SQRT1_2",
|
||||
Value::Number(std::f64::consts::FRAC_1_SQRT_2),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
math.force_set(
|
||||
"SQRT2",
|
||||
Value::Number(std::f64::consts::SQRT_2),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
|
||||
wrap_std!(math, gc_context,
|
||||
"abs" => f64::abs,
|
||||
|
@ -73,8 +107,8 @@ pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'
|
|||
"tan" => f64::tan
|
||||
);
|
||||
|
||||
math.force_set_function("atan2", atan2, gc_context);
|
||||
math.force_set_function("random", random, gc_context);
|
||||
math.force_set_function("atan2", atan2, gc_context, Attribute::DontDelete);
|
||||
math.force_set_function("random", random, gc_context, Attribute::DontDelete);
|
||||
|
||||
GcCell::allocate(gc_context, math)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::avm1::object::Object;
|
||||
use crate::avm1::object::{Attribute, Object};
|
||||
use crate::avm1::Value;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use gc_arena::MutationContext;
|
||||
|
@ -17,6 +17,7 @@ macro_rules! with_movie_clip {
|
|||
Value::Undefined
|
||||
},
|
||||
$gc_context,
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
)*
|
||||
}};
|
||||
|
@ -36,6 +37,7 @@ macro_rules! with_movie_clip_mut {
|
|||
Value::Undefined
|
||||
},
|
||||
$gc_context,
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
)*
|
||||
}};
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::avm1::{ActionContext, Avm1, Value};
|
|||
use crate::display_object::DisplayNode;
|
||||
use crate::tag_utils::SwfSlice;
|
||||
use core::fmt;
|
||||
use enumset::{EnumSet, EnumSetType};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
|
@ -22,15 +23,23 @@ fn default_to_string<'gc>(
|
|||
Value::String("[Object object]".to_string())
|
||||
}
|
||||
|
||||
#[derive(EnumSetType, Debug)]
|
||||
pub enum Attribute {
|
||||
DontDelete,
|
||||
// DontEnum,
|
||||
// ReadOnly,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Property<'gc> {
|
||||
Virtual {
|
||||
get: NativeFunction<'gc>,
|
||||
set: Option<NativeFunction<'gc>>,
|
||||
attributes: EnumSet<Attribute>,
|
||||
},
|
||||
Stored {
|
||||
value: Value<'gc>,
|
||||
// TODO: attributes
|
||||
attributes: EnumSet<Attribute>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -65,12 +74,19 @@ impl<'gc> Property<'gc> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_delete(&self) -> bool {
|
||||
match self {
|
||||
Property::Virtual { attributes, .. } => !attributes.contains(Attribute::DontDelete),
|
||||
Property::Stored { attributes, .. } => !attributes.contains(Attribute::DontDelete),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for Property<'gc> {
|
||||
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||
match self {
|
||||
Property::Virtual { get, set } => {
|
||||
Property::Virtual { get, set, .. } => {
|
||||
get.trace(cc);
|
||||
set.trace(cc);
|
||||
}
|
||||
|
@ -82,14 +98,20 @@ unsafe impl<'gc> gc_arena::Collect for Property<'gc> {
|
|||
impl fmt::Debug for Property<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Property::Virtual { get: _, set } => f
|
||||
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 } => f
|
||||
Property::Stored { value, attributes } => f
|
||||
.debug_struct("Property::Stored")
|
||||
.field("value", &value)
|
||||
.field("attributes", &attributes)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +151,12 @@ impl<'gc> Object<'gc> {
|
|||
function: None,
|
||||
};
|
||||
|
||||
result.force_set_function("toString", default_to_string, gc_context);
|
||||
result.force_set_function(
|
||||
"toString",
|
||||
default_to_string,
|
||||
gc_context,
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -208,58 +235,62 @@ impl<'gc> Object<'gc> {
|
|||
entry.get_mut().set(avm, context, this, value);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(Property::Stored { value });
|
||||
entry.insert(Property::Stored {
|
||||
value,
|
||||
attributes: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force_set_virtual(
|
||||
pub fn force_set_virtual<A>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
get: NativeFunction<'gc>,
|
||||
set: Option<NativeFunction<'gc>>,
|
||||
) {
|
||||
self.values
|
||||
.insert(name.to_owned(), Property::Virtual { get, set });
|
||||
attributes: A,
|
||||
) where
|
||||
A: Into<EnumSet<Attribute>>,
|
||||
{
|
||||
self.values.insert(
|
||||
name.to_owned(),
|
||||
Property::Virtual {
|
||||
get,
|
||||
set,
|
||||
attributes: attributes.into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn force_set(&mut self, name: &str, value: Value<'gc>) {
|
||||
self.values
|
||||
.insert(name.to_string(), Property::Stored { value });
|
||||
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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_native_function(
|
||||
&mut self,
|
||||
name: &str,
|
||||
function: NativeFunction<'gc>,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut ActionContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
) {
|
||||
self.set(
|
||||
name,
|
||||
Value::Object(GcCell::allocate(
|
||||
context.gc_context,
|
||||
Object::native_function(function),
|
||||
)),
|
||||
avm,
|
||||
context,
|
||||
this,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn force_set_function(
|
||||
pub fn force_set_function<A>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
function: NativeFunction<'gc>,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
) {
|
||||
attributes: A,
|
||||
) where
|
||||
A: Into<EnumSet<Attribute>>,
|
||||
{
|
||||
self.force_set(
|
||||
name,
|
||||
Value::Object(GcCell::allocate(
|
||||
gc_context,
|
||||
Object::native_function(function),
|
||||
)),
|
||||
attributes,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -286,8 +317,15 @@ impl<'gc> Object<'gc> {
|
|||
}
|
||||
|
||||
/// Delete a given value off the object.
|
||||
pub fn delete(&mut self, name: &str) {
|
||||
self.values.remove(name);
|
||||
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
|
||||
}
|
||||
|
||||
pub fn has_property(&self, name: &str) -> bool {
|
||||
|
@ -394,9 +432,11 @@ mod tests {
|
|||
#[test]
|
||||
fn test_set_get() {
|
||||
with_object(0, |avm, context, object| {
|
||||
object
|
||||
.write(context.gc_context)
|
||||
.force_set("forced", Value::String("forced".to_string()));
|
||||
object.write(context.gc_context).force_set(
|
||||
"forced",
|
||||
Value::String("forced".to_string()),
|
||||
EnumSet::empty(),
|
||||
);
|
||||
object.write(context.gc_context).set(
|
||||
"natural",
|
||||
Value::String("natural".to_string()),
|
||||
|
@ -421,9 +461,12 @@ mod tests {
|
|||
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("test", getter, None);
|
||||
object.write(context.gc_context).force_set_virtual(
|
||||
"test",
|
||||
getter,
|
||||
None,
|
||||
EnumSet::empty(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
object.read().get("test", avm, context, object),
|
||||
|
@ -444,4 +487,57 @@ mod tests {
|
|||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[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,
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
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()),
|
||||
Attribute::DontDelete,
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
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())
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Represents AVM1 scope chain resolution.
|
||||
|
||||
use crate::avm1::{ActionContext, Avm1, Object, Value};
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use std::cell::{Ref, RefMut};
|
||||
|
||||
|
@ -283,11 +284,11 @@ impl<'gc> Scope<'gc> {
|
|||
/// chain. As a result, this function always force sets a property on the
|
||||
/// local object and does not traverse the scope chain.
|
||||
pub fn define(&self, name: &str, value: Value<'gc>, mc: MutationContext<'gc, '_>) {
|
||||
self.locals_mut(mc).force_set(name, value);
|
||||
self.locals_mut(mc).force_set(name, value, EnumSet::empty());
|
||||
}
|
||||
|
||||
/// Delete a value from scope
|
||||
pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) {
|
||||
pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) -> bool {
|
||||
if self.locals().has_property(name) {
|
||||
return self.locals_mut(mc).delete(name);
|
||||
}
|
||||
|
@ -295,5 +296,7 @@ impl<'gc> Scope<'gc> {
|
|||
if let Some(scope) = self.parent() {
|
||||
return scope.delete(name, mc);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue