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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
|
|
|
@ -162,6 +162,55 @@ pub fn register_class<'gc>(
|
|||
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`.
|
||||
///
|
||||
/// `__proto__` and other cross-linked properties of this object will *not*
|
||||
|
@ -218,6 +267,20 @@ pub fn fill_proto<'gc>(
|
|||
DontDelete | DontEnum,
|
||||
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`.
|
||||
|
|
|
@ -259,6 +259,23 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
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.
|
||||
fn has_property(
|
||||
&self,
|
||||
|
|
|
@ -8,6 +8,7 @@ use core::fmt;
|
|||
use enumset::EnumSet;
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const TYPE_OF_OBJECT: &str = "object";
|
||||
|
||||
|
@ -18,6 +19,50 @@ pub enum ArrayStorage<'gc> {
|
|||
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)]
|
||||
#[collect(no_drop)]
|
||||
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
||||
|
@ -28,6 +73,7 @@ pub struct ScriptObjectData<'gc> {
|
|||
interfaces: Vec<Object<'gc>>,
|
||||
type_of: &'static str,
|
||||
array: ArrayStorage<'gc>,
|
||||
watchers: HashMap<String, Watcher<'gc>>,
|
||||
}
|
||||
|
||||
unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
||||
|
@ -36,6 +82,7 @@ unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
|||
self.values.trace(cc);
|
||||
self.array.trace(cc);
|
||||
self.interfaces.trace(cc);
|
||||
self.watchers.trace(cc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +92,7 @@ impl fmt::Debug for ScriptObjectData<'_> {
|
|||
.field("prototype", &self.prototype)
|
||||
.field("values", &self.values)
|
||||
.field("array", &self.array)
|
||||
.field("watchers", &self.watchers)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +110,7 @@ impl<'gc> ScriptObject<'gc> {
|
|||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Properties { length: 0 },
|
||||
interfaces: vec![],
|
||||
watchers: HashMap::new(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -78,6 +127,7 @@ impl<'gc> ScriptObject<'gc> {
|
|||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Vector(Vec::new()),
|
||||
interfaces: vec![],
|
||||
watchers: HashMap::new(),
|
||||
},
|
||||
));
|
||||
object.sync_native_property("length", gc_context, Some(0.into()), false);
|
||||
|
@ -97,6 +147,7 @@ impl<'gc> ScriptObject<'gc> {
|
|||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Properties { length: 0 },
|
||||
interfaces: vec![],
|
||||
watchers: HashMap::new(),
|
||||
},
|
||||
))
|
||||
.into()
|
||||
|
@ -116,6 +167,7 @@ impl<'gc> ScriptObject<'gc> {
|
|||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Properties { length: 0 },
|
||||
interfaces: vec![],
|
||||
watchers: HashMap::new(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -191,7 +243,7 @@ impl<'gc> ScriptObject<'gc> {
|
|||
pub(crate) fn internal_set(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
mut value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, '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
|
||||
//to end before we can do so.
|
||||
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
|
||||
.0
|
||||
.write(context.gc_context)
|
||||
|
@ -285,6 +359,8 @@ impl<'gc> ScriptObject<'gc> {
|
|||
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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
|
|
|
@ -237,6 +237,21 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
|||
//`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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
|
|
|
@ -193,6 +193,8 @@ swf_tests! {
|
|||
(loadvariables_method, "avm1/loadvariables_method", 3),
|
||||
(xml_load, "avm1/xml_load", 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),
|
||||
(roots_and_levels, "avm1/roots_and_levels", 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