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)
|
self.base.get_local(name, activation, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: Value<'gc>,
|
value: Value<'gc>,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
self.base.set(name, value, activation)
|
self.base
|
||||||
|
.set_local(name, value, activation, this, base_proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(
|
fn call(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Object trait to expose objects to AVM
|
//! Object trait to expose objects to AVM
|
||||||
|
|
||||||
use crate::avm1::error::Error;
|
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::shared_object::SharedObject;
|
||||||
use crate::avm1::object::super_object::SuperObject;
|
use crate::avm1::object::super_object::SuperObject;
|
||||||
use crate::avm1::object::value_object::ValueObject;
|
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)
|
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.
|
/// Set a named property on this object, or its prototype.
|
||||||
fn set(
|
fn set(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: Value<'gc>,
|
value: Value<'gc>,
|
||||||
activation: &mut Activation<'_, '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.
|
/// Call the underlying object.
|
||||||
///
|
///
|
||||||
|
|
|
@ -5,31 +5,28 @@ macro_rules! impl_custom_object {
|
||||||
};
|
};
|
||||||
|
|
||||||
(@extra $field:ident set(proto: self)) => {
|
(@extra $field:ident set(proto: self)) => {
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: crate::avm1::Value<'gc>,
|
value: crate::avm1::Value<'gc>,
|
||||||
activation: &mut crate::avm1::Activation<'_, 'gc, '_>,
|
activation: &mut crate::avm1::Activation<'_, 'gc, '_>,
|
||||||
|
this: crate::avm1::Object<'gc>,
|
||||||
|
base_proto: Option<crate::avm1::Object<'gc>>,
|
||||||
) -> Result<(), crate::avm1::Error<'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)) => {
|
(@extra $field:ident set(proto: $proto:ident)) => {
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: crate::avm1::Value<'gc>,
|
value: crate::avm1::Value<'gc>,
|
||||||
activation: &mut crate::avm1::Activation<'_, 'gc, '_>,
|
activation: &mut crate::avm1::Activation<'_, 'gc, '_>,
|
||||||
|
this: crate::avm1::Object<'gc>,
|
||||||
|
_base_proto: Option<crate::avm1::Object<'gc>>,
|
||||||
) -> Result<(), crate::avm1::Error<'gc>> {
|
) -> Result<(), crate::avm1::Error<'gc>> {
|
||||||
let base = self.0.read().$field;
|
self.0.read().$field.set_local(name, value, activation, this, Some(activation.context.avm1.prototypes.$proto))
|
||||||
base.internal_set(
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
activation,
|
|
||||||
(*self).into(),
|
|
||||||
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> {
|
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
|
/// This function takes a redundant `this` parameter which should be
|
||||||
/// the object's own `GcCell`, so that it can pass it to user-defined
|
/// the object's own `GcCell`, so that it can pass it to user-defined
|
||||||
/// overrides that may need to interact with the underlying object.
|
/// overrides that may need to interact with the underlying object.
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: Value<'gc>,
|
mut value: Value<'gc>,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
self.internal_set(
|
let watcher = self
|
||||||
name,
|
.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,
|
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,
|
activation,
|
||||||
(*self).into(),
|
this,
|
||||||
Some((*self).into()),
|
base_proto,
|
||||||
)
|
&[value],
|
||||||
|
ExecutionReason::Special,
|
||||||
|
setter,
|
||||||
|
) {
|
||||||
|
return Err(Error::ThrownValue(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the underlying object.
|
/// Call the underlying object.
|
||||||
|
|
|
@ -206,11 +206,13 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
||||||
self.0.read().base.get_local(name, activation, this)
|
self.0.read().base.get_local(name, activation, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: Value<'gc>,
|
value: Value<'gc>,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
let obj = self.0.read();
|
let obj = self.0.read();
|
||||||
let props = activation.context.avm1.display_properties;
|
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) {
|
if base.has_own_property(activation, name) {
|
||||||
// 1) Actual properties on the underlying object
|
// 1) Actual properties on the underlying object
|
||||||
base.internal_set(
|
base.set_local(name, value, activation, this, base_proto)
|
||||||
name,
|
|
||||||
value,
|
|
||||||
activation,
|
|
||||||
(*self).into(),
|
|
||||||
Some((*self).into()),
|
|
||||||
)
|
|
||||||
} else if let Some(property) = props.read().get_by_name(name) {
|
} else if let Some(property) = props.read().get_by_name(name) {
|
||||||
// 2) Display object properties such as _x, _y
|
// 2) Display object properties such as _x, _y
|
||||||
property.set(activation, display_object, value)?;
|
property.set(activation, display_object, value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
// 3) TODO: Prototype
|
// 3) TODO: Prototype
|
||||||
base.internal_set(
|
base.set_local(name, value, activation, this, base_proto)
|
||||||
name,
|
|
||||||
value,
|
|
||||||
activation,
|
|
||||||
(*self).into(),
|
|
||||||
Some((*self).into()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn call(
|
fn call(
|
||||||
|
|
|
@ -87,11 +87,13 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
_name: &str,
|
_name: &str,
|
||||||
_value: Value<'gc>,
|
_value: Value<'gc>,
|
||||||
_activation: &mut Activation<'_, 'gc, '_>,
|
_activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
_this: Object<'gc>,
|
||||||
|
_base_proto: Option<Object<'gc>>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
//TODO: What happens if you set `super.__proto__`?
|
//TODO: What happens if you set `super.__proto__`?
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -69,18 +69,20 @@ impl<'gc> TObject<'gc> for XmlAttributesObject<'gc> {
|
||||||
.unwrap_or_else(|| Value::Undefined))
|
.unwrap_or_else(|| Value::Undefined))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: Value<'gc>,
|
value: Value<'gc>,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
_this: Object<'gc>,
|
||||||
|
_base_proto: Option<Object<'gc>>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
self.node().set_attribute_value(
|
self.node().set_attribute_value(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
&XmlName::from_str(name),
|
&XmlName::from_str(name),
|
||||||
&value.coerce_to_string(activation)?,
|
&value.coerce_to_string(activation)?,
|
||||||
);
|
);
|
||||||
self.base().set(name, value, activation)
|
Ok(())
|
||||||
}
|
}
|
||||||
fn call(
|
fn call(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -74,13 +74,16 @@ impl<'gc> TObject<'gc> for XmlIdMapObject<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(
|
fn set_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
value: Value<'gc>,
|
value: Value<'gc>,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
self.base().set(name, value, activation)
|
self.base()
|
||||||
|
.set_local(name, value, activation, this, base_proto)
|
||||||
}
|
}
|
||||||
fn call(
|
fn call(
|
||||||
&self,
|
&self,
|
||||||
|
|
Loading…
Reference in New Issue