avm1: Add TObject::set_local

This commit is contained in:
relrelb 2021-06-05 11:48:48 +03:00 committed by Mike Welsh
parent c9d015d7c5
commit 15fa92a9fb
8 changed files with 152 additions and 170 deletions

View File

@ -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(

View File

@ -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.
///

View File

@ -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))
}
};

View File

@ -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,
value,
activation,
(*self).into(),
Some((*self).into()),
)
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,
this,
base_proto,
&[value],
ExecutionReason::Special,
setter,
) {
return Err(Error::ThrownValue(e));
}
}
}
result
}
/// Call the underlying object.

View File

@ -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(

View File

@ -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(())

View File

@ -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,

View File

@ -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,