avm1: Add TObject::set_local
This commit is contained in:
parent
c9d015d7c5
commit
15fa92a9fb
|
@ -539,13 +539,16 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
|
|||
self.base.get_local(name, activation, this)
|
||||
}
|
||||
|
||||
fn set(
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
self.base.set(name, value, activation)
|
||||
self.base
|
||||
.set_local(name, value, activation, this, base_proto)
|
||||
}
|
||||
|
||||
fn call(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Object trait to expose objects to AVM
|
||||
|
||||
use crate::avm1::error::Error;
|
||||
use crate::avm1::function::{Executable, FunctionObject};
|
||||
use crate::avm1::function::{Executable, ExecutionReason, FunctionObject};
|
||||
use crate::avm1::object::shared_object::SharedObject;
|
||||
use crate::avm1::object::super_object::SuperObject;
|
||||
use crate::avm1::object::value_object::ValueObject;
|
||||
|
@ -115,13 +115,77 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
Ok(search_prototype(Value::Object(this), name, activation, this)?.0)
|
||||
}
|
||||
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>>;
|
||||
|
||||
/// Set a named property on this object, or its prototype.
|
||||
fn set(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
) -> Result<(), Error<'gc>>;
|
||||
) -> Result<(), Error<'gc>> {
|
||||
if name.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if name == "__proto__" {
|
||||
self.set_proto(activation.context.gc_context, value);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Ok(index) = name.parse::<usize>() {
|
||||
self.set_array_element(index, value.to_owned(), activation.context.gc_context);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if name == "length" {
|
||||
let length = value
|
||||
.coerce_to_f64(activation)
|
||||
.map(|v| v.abs() as i32)
|
||||
.unwrap_or(0);
|
||||
if length > 0 {
|
||||
self.set_length(activation.context.gc_context, length as usize);
|
||||
} else {
|
||||
self.set_length(activation.context.gc_context, 0);
|
||||
}
|
||||
}
|
||||
|
||||
let this = (*self).into();
|
||||
if !self.has_own_property(activation, name) {
|
||||
// Before actually inserting a new property, we need to crawl the
|
||||
// prototype chain for virtual setters.
|
||||
let mut proto = Value::Object(this);
|
||||
while let Value::Object(this_proto) = proto {
|
||||
if this_proto.has_own_virtual(activation, name) {
|
||||
if let Some(setter) = this_proto.call_setter(name, value, activation) {
|
||||
if let Some(exec) = setter.as_executable() {
|
||||
let _ = exec.exec(
|
||||
"[Setter]",
|
||||
activation,
|
||||
this,
|
||||
Some(this_proto),
|
||||
&[value],
|
||||
ExecutionReason::Special,
|
||||
setter,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
proto = this_proto.proto();
|
||||
}
|
||||
}
|
||||
|
||||
self.set_local(name, value, activation, this, Some(this))
|
||||
}
|
||||
|
||||
/// Call the underlying object.
|
||||
///
|
||||
|
|
|
@ -5,31 +5,28 @@ macro_rules! impl_custom_object {
|
|||
};
|
||||
|
||||
(@extra $field:ident set(proto: self)) => {
|
||||
fn set(
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: crate::avm1::Value<'gc>,
|
||||
activation: &mut crate::avm1::Activation<'_, 'gc, '_>,
|
||||
this: crate::avm1::Object<'gc>,
|
||||
base_proto: Option<crate::avm1::Object<'gc>>,
|
||||
) -> Result<(), crate::avm1::Error<'gc>> {
|
||||
self.0.read().$field.set(name, value, activation)
|
||||
self.0.read().$field.set_local(name, value, activation, this, base_proto)
|
||||
}
|
||||
};
|
||||
|
||||
(@extra $field:ident set(proto: $proto:ident)) => {
|
||||
fn set(
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: crate::avm1::Value<'gc>,
|
||||
activation: &mut crate::avm1::Activation<'_, 'gc, '_>,
|
||||
this: crate::avm1::Object<'gc>,
|
||||
_base_proto: Option<crate::avm1::Object<'gc>>,
|
||||
) -> Result<(), crate::avm1::Error<'gc>> {
|
||||
let base = self.0.read().$field;
|
||||
base.internal_set(
|
||||
name,
|
||||
value,
|
||||
activation,
|
||||
(*self).into(),
|
||||
Some(activation.context.avm1.prototypes.$proto),
|
||||
)
|
||||
self.0.read().$field.set_local(name, value, activation, this, Some(activation.context.avm1.prototypes.$proto))
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -207,132 +207,6 @@ impl<'gc> ScriptObject<'gc> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub(crate) fn internal_set(
|
||||
&self,
|
||||
name: &str,
|
||||
mut value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
if name == "__proto__" {
|
||||
self.0.write(activation.context.gc_context).prototype = value;
|
||||
} else if let Ok(index) = name.parse::<usize>() {
|
||||
self.set_array_element(index, value.to_owned(), activation.context.gc_context);
|
||||
} else if !name.is_empty() {
|
||||
if name == "length" {
|
||||
let length = value
|
||||
.coerce_to_f64(activation)
|
||||
.map(|v| v.abs() as i32)
|
||||
.unwrap_or(0);
|
||||
if length > 0 {
|
||||
self.set_length(activation.context.gc_context, length as usize);
|
||||
} else {
|
||||
self.set_length(activation.context.gc_context, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//Before actually inserting a new property, we need to crawl the
|
||||
//prototype chain for virtual setters, which kind of break how
|
||||
//ECMAScript `[[Set]]` is supposed to work...
|
||||
let is_vacant = !self
|
||||
.0
|
||||
.read()
|
||||
.values
|
||||
.contains_key(name, activation.is_case_sensitive());
|
||||
let mut worked = false;
|
||||
|
||||
if is_vacant {
|
||||
let mut proto: Value<'gc> = (*self).into();
|
||||
while let Value::Object(this_proto) = proto {
|
||||
if this_proto.has_own_virtual(activation, name) {
|
||||
break;
|
||||
}
|
||||
|
||||
proto = this_proto.proto();
|
||||
}
|
||||
|
||||
if let Value::Object(this_proto) = proto {
|
||||
worked = true;
|
||||
if let Some(rval) = this_proto.call_setter(name, value, activation) {
|
||||
if let Some(exec) = rval.as_executable() {
|
||||
let _ = exec.exec(
|
||||
"[Setter]",
|
||||
activation,
|
||||
this,
|
||||
Some(this_proto),
|
||||
&[value],
|
||||
ExecutionReason::Special,
|
||||
rval,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//This signals we didn't call a virtual setter above. Normally,
|
||||
//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, activation.is_case_sensitive())
|
||||
.cloned();
|
||||
let mut return_value = Ok(());
|
||||
if let Some(watcher) = watcher {
|
||||
let old_value = self.get(name, activation)?;
|
||||
value = match watcher.call(activation, name, old_value, value, 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(activation.context.gc_context)
|
||||
.values
|
||||
.entry(name, activation.is_case_sensitive())
|
||||
{
|
||||
Entry::Occupied(mut entry) => entry.get_mut().set(value),
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(Property::Stored {
|
||||
value,
|
||||
attributes: Attribute::empty(),
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(rval) = rval {
|
||||
if let Some(exec) = rval.as_executable() {
|
||||
let _ = exec.exec(
|
||||
"[Setter]",
|
||||
activation,
|
||||
this,
|
||||
base_proto,
|
||||
&[value],
|
||||
ExecutionReason::Special,
|
||||
rval,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return return_value;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||
|
@ -396,19 +270,66 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|||
/// 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(
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
mut value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
self.internal_set(
|
||||
name,
|
||||
let watcher = self
|
||||
.0
|
||||
.read()
|
||||
.watchers
|
||||
.get(name, activation.is_case_sensitive())
|
||||
.cloned();
|
||||
let mut result = Ok(());
|
||||
if let Some(watcher) = watcher {
|
||||
let old_value = self.get(name, activation)?;
|
||||
match watcher.call(activation, name, old_value, value, this, base_proto) {
|
||||
Ok(v) => value = v,
|
||||
Err(Error::ThrownValue(e)) => {
|
||||
value = Value::Undefined;
|
||||
result = Err(Error::ThrownValue(e));
|
||||
}
|
||||
Err(_) => value = Value::Undefined,
|
||||
};
|
||||
}
|
||||
|
||||
let setter = match self
|
||||
.0
|
||||
.write(activation.context.gc_context)
|
||||
.values
|
||||
.entry(name, activation.is_case_sensitive())
|
||||
{
|
||||
Entry::Occupied(mut entry) => entry.get_mut().set(value),
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(Property::Stored {
|
||||
value,
|
||||
attributes: Attribute::empty(),
|
||||
});
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(setter) = setter {
|
||||
if let Some(exec) = setter.as_executable() {
|
||||
if let Err(Error::ThrownValue(e)) = exec.exec(
|
||||
"[Setter]",
|
||||
activation,
|
||||
(*self).into(),
|
||||
Some((*self).into()),
|
||||
)
|
||||
this,
|
||||
base_proto,
|
||||
&[value],
|
||||
ExecutionReason::Special,
|
||||
setter,
|
||||
) {
|
||||
return Err(Error::ThrownValue(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Call the underlying object.
|
||||
|
|
|
@ -206,11 +206,13 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
self.0.read().base.get_local(name, activation, this)
|
||||
}
|
||||
|
||||
fn set(
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
let obj = self.0.read();
|
||||
let props = activation.context.avm1.display_properties;
|
||||
|
@ -234,26 +236,14 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
|||
|
||||
if base.has_own_property(activation, name) {
|
||||
// 1) Actual properties on the underlying object
|
||||
base.internal_set(
|
||||
name,
|
||||
value,
|
||||
activation,
|
||||
(*self).into(),
|
||||
Some((*self).into()),
|
||||
)
|
||||
base.set_local(name, value, activation, this, base_proto)
|
||||
} else if let Some(property) = props.read().get_by_name(name) {
|
||||
// 2) Display object properties such as _x, _y
|
||||
property.set(activation, display_object, value)?;
|
||||
Ok(())
|
||||
} else {
|
||||
// 3) TODO: Prototype
|
||||
base.internal_set(
|
||||
name,
|
||||
value,
|
||||
activation,
|
||||
(*self).into(),
|
||||
Some((*self).into()),
|
||||
)
|
||||
base.set_local(name, value, activation, this, base_proto)
|
||||
}
|
||||
}
|
||||
fn call(
|
||||
|
|
|
@ -87,11 +87,13 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
|||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
fn set(
|
||||
fn set_local(
|
||||
&self,
|
||||
_name: &str,
|
||||
_value: Value<'gc>,
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
//TODO: What happens if you set `super.__proto__`?
|
||||
Ok(())
|
||||
|
|
|
@ -69,18 +69,20 @@ impl<'gc> TObject<'gc> for XmlAttributesObject<'gc> {
|
|||
.unwrap_or_else(|| Value::Undefined))
|
||||
}
|
||||
|
||||
fn set(
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
self.node().set_attribute_value(
|
||||
activation.context.gc_context,
|
||||
&XmlName::from_str(name),
|
||||
&value.coerce_to_string(activation)?,
|
||||
);
|
||||
self.base().set(name, value, activation)
|
||||
Ok(())
|
||||
}
|
||||
fn call(
|
||||
&self,
|
||||
|
|
|
@ -74,13 +74,16 @@ impl<'gc> TObject<'gc> for XmlIdMapObject<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
fn set(
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
self.base().set(name, value, activation)
|
||||
self.base()
|
||||
.set_local(name, value, activation, this, base_proto)
|
||||
}
|
||||
fn call(
|
||||
&self,
|
||||
|
|
Loading…
Reference in New Issue