avm1: Implement Object.watch & Object.unwatch (#268)
This commit is contained in:
parent
ecbab536b5
commit
8a0430d744
|
@ -229,6 +229,21 @@ impl<'gc> TObject<'gc> for ColorTransformObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.base()
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.base().remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_property(
|
fn has_property(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
|
|
@ -624,6 +624,20 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.base.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.base.remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_property(
|
fn has_property(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
|
|
@ -162,6 +162,55 @@ pub fn register_class<'gc>(
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `Object.prototype.watch`
|
||||||
|
fn watch<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
|
let name = if let Some(name) = args.get(0) {
|
||||||
|
name.coerce_to_string(activation, context)?
|
||||||
|
} else {
|
||||||
|
return Ok(false.into());
|
||||||
|
};
|
||||||
|
let callback = if let Some(callback) = args.get(1) {
|
||||||
|
if let Some(callback) = callback
|
||||||
|
.coerce_to_object(activation, context)
|
||||||
|
.as_executable()
|
||||||
|
{
|
||||||
|
callback
|
||||||
|
} else {
|
||||||
|
return Ok(false.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(false.into());
|
||||||
|
};
|
||||||
|
let user_data = args.get(2).cloned().unwrap_or(Value::Undefined);
|
||||||
|
|
||||||
|
this.set_watcher(context.gc_context, name, callback, user_data);
|
||||||
|
|
||||||
|
Ok(true.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements `Object.prototype.unmwatch`
|
||||||
|
fn unwatch<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
|
let name = if let Some(name) = args.get(0) {
|
||||||
|
name.coerce_to_string(activation, context)?
|
||||||
|
} else {
|
||||||
|
return Ok(false.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = this.remove_watcher(context.gc_context, name);
|
||||||
|
|
||||||
|
Ok(result.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Partially construct `Object.prototype`.
|
/// Partially construct `Object.prototype`.
|
||||||
///
|
///
|
||||||
/// `__proto__` and other cross-linked properties of this object will *not*
|
/// `__proto__` and other cross-linked properties of this object will *not*
|
||||||
|
@ -218,6 +267,20 @@ pub fn fill_proto<'gc>(
|
||||||
DontDelete | DontEnum,
|
DontDelete | DontEnum,
|
||||||
Some(fn_proto),
|
Some(fn_proto),
|
||||||
);
|
);
|
||||||
|
object_proto.as_script_object().unwrap().force_set_function(
|
||||||
|
"watch",
|
||||||
|
watch,
|
||||||
|
gc_context,
|
||||||
|
DontDelete | DontEnum,
|
||||||
|
Some(fn_proto),
|
||||||
|
);
|
||||||
|
object_proto.as_script_object().unwrap().force_set_function(
|
||||||
|
"unwatch",
|
||||||
|
unwatch,
|
||||||
|
gc_context,
|
||||||
|
DontDelete | DontEnum,
|
||||||
|
Some(fn_proto),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements `ASSetPropFlags`.
|
/// Implements `ASSetPropFlags`.
|
||||||
|
|
|
@ -259,6 +259,23 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
attributes: EnumSet<Attribute>,
|
attributes: EnumSet<Attribute>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Set the 'watcher' of a given property.
|
||||||
|
///
|
||||||
|
/// The property does not need to exist at the time of this being called.
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Removed any assigned 'watcher' from the given property.
|
||||||
|
///
|
||||||
|
/// The return value will indicate if there was a watcher present before this method was
|
||||||
|
/// called.
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool;
|
||||||
|
|
||||||
/// Checks if the object has a given named property.
|
/// Checks if the object has a given named property.
|
||||||
fn has_property(
|
fn has_property(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -8,6 +8,7 @@ use core::fmt;
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use gc_arena::{Collect, GcCell, MutationContext};
|
use gc_arena::{Collect, GcCell, MutationContext};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub const TYPE_OF_OBJECT: &str = "object";
|
pub const TYPE_OF_OBJECT: &str = "object";
|
||||||
|
|
||||||
|
@ -18,6 +19,50 @@ pub enum ArrayStorage<'gc> {
|
||||||
Properties { length: usize },
|
Properties { length: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Collect)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct Watcher<'gc> {
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Watcher<'gc> {
|
||||||
|
pub fn new(callback: Executable<'gc>, user_data: Value<'gc>) -> Self {
|
||||||
|
Self {
|
||||||
|
callback,
|
||||||
|
user_data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn call(
|
||||||
|
&self,
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
name: &str,
|
||||||
|
old_value: Value<'gc>,
|
||||||
|
new_value: Value<'gc>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
|
) -> Result<Value<'gc>, crate::avm1::error::Error<'gc>> {
|
||||||
|
let args = [
|
||||||
|
Value::String(name.to_string()),
|
||||||
|
old_value,
|
||||||
|
new_value,
|
||||||
|
self.user_data.clone(),
|
||||||
|
];
|
||||||
|
self.callback.exec(
|
||||||
|
name,
|
||||||
|
activation,
|
||||||
|
context,
|
||||||
|
this,
|
||||||
|
base_proto,
|
||||||
|
&args,
|
||||||
|
ExecutionReason::Special,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Collect)]
|
#[derive(Debug, Copy, Clone, Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
||||||
|
@ -28,6 +73,7 @@ pub struct ScriptObjectData<'gc> {
|
||||||
interfaces: Vec<Object<'gc>>,
|
interfaces: Vec<Object<'gc>>,
|
||||||
type_of: &'static str,
|
type_of: &'static str,
|
||||||
array: ArrayStorage<'gc>,
|
array: ArrayStorage<'gc>,
|
||||||
|
watchers: HashMap<String, Watcher<'gc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
||||||
|
@ -36,6 +82,7 @@ unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
||||||
self.values.trace(cc);
|
self.values.trace(cc);
|
||||||
self.array.trace(cc);
|
self.array.trace(cc);
|
||||||
self.interfaces.trace(cc);
|
self.interfaces.trace(cc);
|
||||||
|
self.watchers.trace(cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +92,7 @@ impl fmt::Debug for ScriptObjectData<'_> {
|
||||||
.field("prototype", &self.prototype)
|
.field("prototype", &self.prototype)
|
||||||
.field("values", &self.values)
|
.field("values", &self.values)
|
||||||
.field("array", &self.array)
|
.field("array", &self.array)
|
||||||
|
.field("watchers", &self.watchers)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +110,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: PropertyMap::new(),
|
values: PropertyMap::new(),
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
interfaces: vec![],
|
interfaces: vec![],
|
||||||
|
watchers: HashMap::new(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -78,6 +127,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: PropertyMap::new(),
|
values: PropertyMap::new(),
|
||||||
array: ArrayStorage::Vector(Vec::new()),
|
array: ArrayStorage::Vector(Vec::new()),
|
||||||
interfaces: vec![],
|
interfaces: vec![],
|
||||||
|
watchers: HashMap::new(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
object.sync_native_property("length", gc_context, Some(0.into()), false);
|
object.sync_native_property("length", gc_context, Some(0.into()), false);
|
||||||
|
@ -97,6 +147,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: PropertyMap::new(),
|
values: PropertyMap::new(),
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
interfaces: vec![],
|
interfaces: vec![],
|
||||||
|
watchers: HashMap::new(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.into()
|
.into()
|
||||||
|
@ -116,6 +167,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: PropertyMap::new(),
|
values: PropertyMap::new(),
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
interfaces: vec![],
|
interfaces: vec![],
|
||||||
|
watchers: HashMap::new(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -191,7 +243,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
pub(crate) fn internal_set(
|
pub(crate) fn internal_set(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: Value<'gc>,
|
mut value: Value<'gc>,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
|
@ -257,6 +309,28 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
//we'd resolve and return up there, but we have borrows that need
|
//we'd resolve and return up there, but we have borrows that need
|
||||||
//to end before we can do so.
|
//to end before we can do so.
|
||||||
if !worked {
|
if !worked {
|
||||||
|
let watcher = self.0.read().watchers.get(name).cloned();
|
||||||
|
let mut return_value = Ok(());
|
||||||
|
if let Some(watcher) = watcher {
|
||||||
|
let old_value = self.get(name, activation, context)?;
|
||||||
|
value = match watcher.call(
|
||||||
|
activation,
|
||||||
|
context,
|
||||||
|
name,
|
||||||
|
old_value,
|
||||||
|
value.clone(),
|
||||||
|
this,
|
||||||
|
base_proto,
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(Error::ThrownValue(error)) => {
|
||||||
|
return_value = Err(Error::ThrownValue(error));
|
||||||
|
Value::Undefined
|
||||||
|
}
|
||||||
|
Err(_) => Value::Undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let rval = match self
|
let rval = match self
|
||||||
.0
|
.0
|
||||||
.write(context.gc_context)
|
.write(context.gc_context)
|
||||||
|
@ -285,6 +359,8 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
ExecutionReason::Special,
|
ExecutionReason::Special,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return return_value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,6 +555,24 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.write(gc_context)
|
||||||
|
.watchers
|
||||||
|
.insert(name.to_string(), Watcher::new(callback, user_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
let old = self.0.write(gc_context).watchers.remove(name.as_ref());
|
||||||
|
old.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn define_value(
|
fn define_value(
|
||||||
&self,
|
&self,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
|
|
@ -191,6 +191,21 @@ impl<'gc> TObject<'gc> for SharedObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.base()
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.base().remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_property(
|
fn has_property(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
|
|
@ -251,6 +251,21 @@ impl<'gc> TObject<'gc> for SoundObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.base()
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.base().remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_property(
|
fn has_property(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
|
|
@ -332,6 +332,23 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.base
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.0.read().base.remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_property(
|
fn has_property(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
|
|
@ -237,6 +237,21 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
||||||
//`super` cannot have properties defined on it
|
//`super` cannot have properties defined on it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
_gc_context: MutationContext<'gc, '_>,
|
||||||
|
_name: Cow<str>,
|
||||||
|
_callback: Executable<'gc>,
|
||||||
|
_user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
//`super` cannot have properties defined on it
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, _gc_context: MutationContext<'gc, '_>, _name: Cow<str>) -> bool {
|
||||||
|
//`super` cannot have properties defined on it
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn has_property(
|
fn has_property(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
|
|
@ -231,6 +231,26 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.base
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.0
|
||||||
|
.write(gc_context)
|
||||||
|
.base
|
||||||
|
.remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn define_value(
|
fn define_value(
|
||||||
&self,
|
&self,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
|
|
@ -157,6 +157,21 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.base()
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.base().remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn define_value(
|
fn define_value(
|
||||||
&self,
|
&self,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
|
|
@ -155,6 +155,21 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.base()
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.base().remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn define_value(
|
fn define_value(
|
||||||
&self,
|
&self,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
|
|
@ -145,6 +145,21 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> {
|
||||||
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
.add_property_with_case(activation, gc_context, name, get, set, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Executable<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.base()
|
||||||
|
.set_watcher(gc_context, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, gc_context: MutationContext<'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.base().remove_watcher(gc_context, name)
|
||||||
|
}
|
||||||
|
|
||||||
fn define_value(
|
fn define_value(
|
||||||
&self,
|
&self,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
|
|
@ -193,6 +193,8 @@ swf_tests! {
|
||||||
(loadvariables_method, "avm1/loadvariables_method", 3),
|
(loadvariables_method, "avm1/loadvariables_method", 3),
|
||||||
(xml_load, "avm1/xml_load", 1),
|
(xml_load, "avm1/xml_load", 1),
|
||||||
(with_return, "avm1/with_return", 1),
|
(with_return, "avm1/with_return", 1),
|
||||||
|
(watch, "avm1/watch", 1),
|
||||||
|
#[ignore] (watch_virtual_property, "avm1/watch_virtual_property", 1),
|
||||||
(cross_movie_root, "avm1/cross_movie_root", 5),
|
(cross_movie_root, "avm1/cross_movie_root", 5),
|
||||||
(roots_and_levels, "avm1/roots_and_levels", 1),
|
(roots_and_levels, "avm1/roots_and_levels", 1),
|
||||||
(swf6_case_insensitive, "avm1/swf6_case_insensitive", 1),
|
(swf6_case_insensitive, "avm1/swf6_case_insensitive", 1),
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
class LoggingWatcher {
|
||||||
|
var count = 0;
|
||||||
|
var value = true;
|
||||||
|
|
||||||
|
function LoggingWatcher() {
|
||||||
|
trace("// this.watch(\"value\", this.log)");
|
||||||
|
this.watch("value", this.log);
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(property, oldValue, newValue, bounds) {
|
||||||
|
var userdata;
|
||||||
|
if (typeof bounds === "object") {
|
||||||
|
userdata = "{ ";
|
||||||
|
for (var key in bounds) {
|
||||||
|
userdata += key + "=" + bounds[key] + " ";
|
||||||
|
}
|
||||||
|
userdata += "}";
|
||||||
|
} else {
|
||||||
|
userdata = bounds;
|
||||||
|
}
|
||||||
|
this.count++;
|
||||||
|
trace("LoggingWatcher count " + this.count + ": " + property + " changed from " + oldValue + " to " + newValue + " with userdata " + userdata);
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
// watch()
|
||||||
|
false
|
||||||
|
|
||||||
|
// watch("variable")
|
||||||
|
false
|
||||||
|
|
||||||
|
// watch("variable", true)
|
||||||
|
false
|
||||||
|
|
||||||
|
// watch("variable", clamper, {min: 5, max: 10})
|
||||||
|
true
|
||||||
|
|
||||||
|
// variable = 5
|
||||||
|
Clamper: variable changed from undefined to 5 with userdata { min=5 max=10 }
|
||||||
|
// variable
|
||||||
|
5
|
||||||
|
|
||||||
|
// variable = 10
|
||||||
|
Clamper: variable changed from 5 to 10 with userdata { min=5 max=10 }
|
||||||
|
// variable
|
||||||
|
10
|
||||||
|
|
||||||
|
// variable = 4
|
||||||
|
Clamper: variable changed from 10 to 4 with userdata { min=5 max=10 }
|
||||||
|
// variable
|
||||||
|
5
|
||||||
|
|
||||||
|
// variable = 11
|
||||||
|
Clamper: variable changed from 5 to 11 with userdata { min=5 max=10 }
|
||||||
|
// variable
|
||||||
|
10
|
||||||
|
|
||||||
|
// variable = 6
|
||||||
|
Clamper: variable changed from 10 to 6 with userdata { min=5 max=10 }
|
||||||
|
// variable
|
||||||
|
6
|
||||||
|
|
||||||
|
// delete(variable)
|
||||||
|
|
||||||
|
// variable = 3
|
||||||
|
Clamper: variable changed from undefined to 3 with userdata { min=5 max=10 }
|
||||||
|
// variable
|
||||||
|
5
|
||||||
|
|
||||||
|
// watch("variable", clamper, {min: 15, max: 20})
|
||||||
|
true
|
||||||
|
|
||||||
|
// variable = 14
|
||||||
|
Clamper: variable changed from 5 to 14 with userdata { min=15 max=20 }
|
||||||
|
// variable
|
||||||
|
15
|
||||||
|
|
||||||
|
// variable = 21
|
||||||
|
Clamper: variable changed from 15 to 21 with userdata { min=15 max=20 }
|
||||||
|
// variable
|
||||||
|
20
|
||||||
|
|
||||||
|
// variable = 18
|
||||||
|
Clamper: variable changed from 20 to 18 with userdata { min=15 max=20 }
|
||||||
|
// variable
|
||||||
|
18
|
||||||
|
|
||||||
|
// watch("variable", exceptionalClamper, {min: 15, max: 20})
|
||||||
|
true
|
||||||
|
|
||||||
|
// variable = 14
|
||||||
|
exceptionalClamper: variable changed from 18 to 14 with userdata { min=15 max=20 }
|
||||||
|
ERROR: too low!
|
||||||
|
// variable
|
||||||
|
undefined
|
||||||
|
|
||||||
|
// variable = 21
|
||||||
|
exceptionalClamper: variable changed from undefined to 21 with userdata { min=15 max=20 }
|
||||||
|
ERROR: too high!
|
||||||
|
// variable
|
||||||
|
undefined
|
||||||
|
|
||||||
|
// variable = 18
|
||||||
|
exceptionalClamper: variable changed from undefined to 18 with userdata { min=15 max=20 }
|
||||||
|
// variable
|
||||||
|
18
|
||||||
|
|
||||||
|
// unwatch("variable")
|
||||||
|
true
|
||||||
|
|
||||||
|
// variable = 4
|
||||||
|
// variable
|
||||||
|
4
|
||||||
|
|
||||||
|
// variable = 11
|
||||||
|
// variable
|
||||||
|
11
|
||||||
|
|
||||||
|
// unwatch("variable")
|
||||||
|
false
|
||||||
|
|
||||||
|
// unwatch()
|
||||||
|
false
|
||||||
|
|
||||||
|
// variable = 6
|
||||||
|
// variable
|
||||||
|
6
|
||||||
|
|
||||||
|
// delete(variable)
|
||||||
|
|
||||||
|
|
||||||
|
// var loggingWatcher = new LoggingWatcher()
|
||||||
|
// this.watch("value", this.log)
|
||||||
|
|
||||||
|
// loggingWatcher.value = true
|
||||||
|
LoggingWatcher count 1: value changed from true to true with userdata undefined
|
||||||
|
|
||||||
|
// loggingWatcher.value = false
|
||||||
|
LoggingWatcher count 2: value changed from true to false with userdata undefined
|
||||||
|
|
||||||
|
// loggingWatcher.count
|
||||||
|
2
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,61 @@
|
||||||
|
// watch("variable", plusOne)
|
||||||
|
true
|
||||||
|
|
||||||
|
// addProperty("variable", getter, setter)
|
||||||
|
plusOne: variable changed from undefined to undefined with userdata undefined
|
||||||
|
true
|
||||||
|
|
||||||
|
// variable = 10
|
||||||
|
plusOne: variable changed from NaN to 10 with userdata undefined
|
||||||
|
setter: ignoring new value of 11
|
||||||
|
// variable
|
||||||
|
getter: returning 5
|
||||||
|
5
|
||||||
|
|
||||||
|
// variable = 4
|
||||||
|
plusOne: variable changed from 11 to 4 with userdata undefined
|
||||||
|
setter: ignoring new value of 5
|
||||||
|
// variable
|
||||||
|
getter: returning 5
|
||||||
|
5
|
||||||
|
|
||||||
|
// unwatch("variable")
|
||||||
|
false
|
||||||
|
|
||||||
|
// variable = 10
|
||||||
|
plusOne: variable changed from 5 to 10 with userdata undefined
|
||||||
|
setter: ignoring new value of 11
|
||||||
|
// variable
|
||||||
|
getter: returning 5
|
||||||
|
5
|
||||||
|
|
||||||
|
// variable = 4
|
||||||
|
plusOne: variable changed from 11 to 4 with userdata undefined
|
||||||
|
setter: ignoring new value of 5
|
||||||
|
// variable
|
||||||
|
getter: returning 5
|
||||||
|
5
|
||||||
|
|
||||||
|
// watch("variable", plusOne)
|
||||||
|
true
|
||||||
|
|
||||||
|
// variable = 10
|
||||||
|
plusOne: variable changed from 5 to 10 with userdata undefined
|
||||||
|
setter: ignoring new value of 11
|
||||||
|
// variable
|
||||||
|
getter: returning 5
|
||||||
|
5
|
||||||
|
|
||||||
|
// variable = 4
|
||||||
|
plusOne: variable changed from 11 to 4 with userdata undefined
|
||||||
|
setter: ignoring new value of 5
|
||||||
|
// variable
|
||||||
|
getter: returning 5
|
||||||
|
5
|
||||||
|
|
||||||
|
// delete(variable)
|
||||||
|
|
||||||
|
// addProperty("variable", getter, null)
|
||||||
|
plusOne: variable changed from undefined to undefined with userdata undefined
|
||||||
|
true
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue