Impl `callmethod`, `callproperty`, `callproplex`, `callpropvoid`, and `callstatic`.

Also, implement a method table that method traits can optionally add themselves to.

Also also, add the ability to invoke a method without a `this` object. This required a non-trivial refactoring of the activation machinery, and changes to the signature of `NativeFunction`, and all native AVM2 functions.
This commit is contained in:
David Wendt 2020-02-23 22:11:02 -05:00
parent 68cf9e8869
commit a0ab978bed
15 changed files with 316 additions and 59 deletions

View File

@ -426,6 +426,17 @@ impl<'gc> Avm2<'gc> {
Op::GetLocal { index } => self.op_get_local(index),
Op::SetLocal { index } => self.op_set_local(context, index),
Op::Call { num_args } => self.op_call(context, num_args),
Op::CallMethod { index, num_args } => self.op_call_method(context, index, num_args),
Op::CallProperty { index, num_args } => {
self.op_call_property(context, index, num_args)
}
Op::CallPropLex { index, num_args } => {
self.op_call_prop_lex(context, index, num_args)
}
Op::CallPropVoid { index, num_args } => {
self.op_call_prop_void(context, index, num_args)
}
Op::CallStatic { index, num_args } => self.op_call_static(context, index, num_args),
Op::ReturnValue => self.op_return_value(context),
Op::ReturnVoid => self.op_return_void(context),
Op::GetProperty { index } => self.op_get_property(context, index),
@ -562,18 +573,146 @@ impl<'gc> Avm2<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
arg_count: u32,
) -> Result<(), Error> {
let function = self.pop().as_object()?;
let receiver = self.pop().as_object()?;
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.pop());
}
let receiver = self.pop().as_object().ok();
let function = self.pop().as_object()?;
function.call(receiver, &args, self, context)?.push(self);
Ok(())
}
fn op_call_method(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMethod>,
arg_count: u32,
) -> Result<(), Error> {
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.pop());
}
let receiver = self.pop().as_object()?;
let function: Result<Object<'gc>, Error> = receiver
.get_method(index.0)
.ok_or_else(|| format!("Object method {} does not exist", index.0).into());
function?
.call(Some(receiver), &args, self, context)?
.push(self);
Ok(())
}
fn op_call_property(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.pop());
}
let multiname = self.pool_multiname(index)?;
let receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
.resolve_multiname(&multiname)
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let function = receiver
.get_property(&name?, self, context)?
.resolve(self, context)?
.as_object()?;
function
.call(Some(receiver), &args, self, context)?
.push(self);
Ok(())
}
fn op_call_prop_lex(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.pop());
}
let multiname = self.pool_multiname(index)?;
let receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
.resolve_multiname(&multiname)
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let function = receiver
.get_property(&name?, self, context)?
.resolve(self, context)?
.as_object()?;
function.call(None, &args, self, context)?.push(self);
Ok(())
}
fn op_call_prop_void(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.pop());
}
let multiname = self.pool_multiname(index)?;
let receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
.resolve_multiname(&multiname)
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let function = receiver
.get_property(&name?, self, context)?
.resolve(self, context)?
.as_object()?;
function
.call(Some(receiver), &args, self, context)?
.resolve(self, context)?;
Ok(())
}
fn op_call_static(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMethod>,
arg_count: u32,
) -> Result<(), Error> {
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.pop());
}
let receiver = self.pop().as_object()?;
let method = self.table_method(index)?;
let scope = self.current_stack_frame().unwrap().read().scope(); //TODO: Is this correct?
let function = FunctionObject::from_abc_method(
context.gc_context,
method,
scope,
self.system_prototypes.function,
);
function
.call(Some(receiver), &args, self, context)?
.push(self);
Ok(())
}
fn op_return_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let return_value = self.pop();
@ -861,7 +1000,7 @@ impl<'gc> Avm2<'gc> {
.as_object()?;
let object = proto.construct(self, context, &args)?;
ctor.call(object, &args, self, context)?
ctor.call(Some(object), &args, self, context)?
.resolve(self, context)?;
Ok(())
@ -899,7 +1038,7 @@ impl<'gc> Avm2<'gc> {
.as_object()?;
let object = proto.construct(self, context, &args)?;
ctor.call(object, &args, self, context)?
ctor.call(Some(object), &args, self, context)?
.resolve(self, context)?;
Ok(())

View File

@ -95,7 +95,7 @@ pub struct Activation<'gc> {
pc: usize,
/// The immutable value of `this`.
this: Object<'gc>,
this: Option<Object<'gc>>,
/// The arguments this function was called by.
arguments: Option<Object<'gc>>,
@ -151,7 +151,7 @@ impl<'gc> Activation<'gc> {
Ok(Self {
method,
pc: 0,
this: global,
this: Some(global),
arguments: None,
is_executing: false,
local_registers,
@ -164,7 +164,7 @@ impl<'gc> Activation<'gc> {
pub fn from_action(
context: &mut UpdateContext<'_, 'gc, '_>,
action: &Avm2Function<'gc>,
this: Object<'gc>,
this: Option<Object<'gc>>,
arguments: &[Value<'gc>],
) -> Result<Self, Error> {
let method = action.method.clone();
@ -178,7 +178,7 @@ impl<'gc> Activation<'gc> {
{
let mut write = local_registers.write(context.gc_context);
*write.get_mut(0).unwrap() = this.into();
*write.get_mut(0).unwrap() = this.map(|t| t.into()).unwrap_or(Value::Null);
for i in 0..num_declared_arguments {
*write.get_mut(1 + i).unwrap() = arguments

View File

@ -34,7 +34,7 @@ use swf::avm2::types::{
pub type NativeFunction<'gc> = fn(
&mut Avm2<'gc>,
&mut UpdateContext<'_, 'gc, '_>,
Object<'gc>,
Option<Object<'gc>>,
&[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error>;
@ -131,7 +131,7 @@ impl<'gc> Executable<'gc> {
&self,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
reciever: Object<'gc>,
reciever: Option<Object<'gc>>,
arguments: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
match self {
@ -316,11 +316,11 @@ impl<'gc> FunctionObject<'gc> {
constr.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?;
}
constr.install_method(
constr.install_dynamic_property(
context.gc_context,
QName::new(Namespace::public_namespace(), "prototype"),
class_proto,
);
class_proto.into(),
)?;
Ok(constr)
}
@ -453,6 +453,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.0.write(mc).base.init_slot(id, value, mc)
}
fn get_method(self, id: u32) -> Option<Object<'gc>> {
self.0.read().base.get_method(id)
}
fn has_property(self, name: &QName) -> bool {
self.0.read().base.has_property(name)
}
@ -465,9 +469,13 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.0.as_ptr() as *const ObjectPtr
}
fn as_executable(&self) -> Option<Executable<'gc>> {
self.0.read().exec.clone()
}
fn call(
self,
reciever: Object<'gc>,
reciever: Option<Object<'gc>>,
arguments: &[Value<'gc>],
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
@ -499,26 +507,43 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
.into())
}
fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) {
self.0.write(mc).base.install_method(name, function)
fn install_method(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
disp_id: u32,
function: Object<'gc>,
) {
self.0
.write(mc)
.base
.install_method(name, disp_id, function)
}
fn install_getter(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
function: Executable<'gc>,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error> {
self.0.write(mc).base.install_getter(name, function)
self.0
.write(mc)
.base
.install_getter(name, disp_id, function)
}
fn install_setter(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
function: Executable<'gc>,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error> {
self.0.write(mc).base.install_setter(name, function)
self.0
.write(mc)
.base
.install_setter(name, disp_id, function)
}
fn install_dynamic_property(

View File

@ -17,7 +17,7 @@ mod object;
fn trace<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(s) = args.get(0) {

View File

@ -12,7 +12,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())

View File

@ -12,7 +12,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())

View File

@ -12,7 +12,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())

View File

@ -12,7 +12,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())

View File

@ -12,7 +12,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())

View File

@ -12,7 +12,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())

View File

@ -14,7 +14,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
@ -24,7 +24,7 @@ pub fn constructor<'gc>(
fn to_string<'gc>(
_: &mut Avm2<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
_: Object<'gc>,
_: Option<Object<'gc>>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(ReturnValue::Immediate("[type Function]".into()))
@ -43,6 +43,7 @@ pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc
function_proto.install_method(
gc_context,
QName::new(Namespace::public_namespace(), "toString"),
0,
FunctionObject::from_builtin(gc_context, to_string, function_proto),
);

View File

@ -11,7 +11,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())

View File

@ -1,8 +1,6 @@
//! AVM2 objects.
use crate::avm2::function::{
Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject,
};
use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject};
use crate::avm2::names::{Multiname, QName};
use crate::avm2::return_value::ReturnValue;
use crate::avm2::scope::Scope;
@ -72,6 +70,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
mc: MutationContext<'gc, '_>,
) -> Result<(), Error>;
/// Retrieve a method by it's index.
fn get_method(self, id: u32) -> Option<Object<'gc>>;
/// Resolve a multiname into a single QName, if any of the namespaces
/// match.
fn resolve_multiname(self, multiname: &Multiname) -> Option<QName> {
@ -113,14 +114,21 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
fn proto(&self) -> Option<Object<'gc>>;
/// Install a method (or any other non-slot value) on an object.
fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>);
fn install_method(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
disp_id: u32,
function: Object<'gc>,
);
/// Install a getter method on an object property.
fn install_getter(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
function: Executable<'gc>,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error>;
/// Install a setter method on an object property.
@ -128,7 +136,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
function: Executable<'gc>,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error>;
/// Install a dynamic or built-in value property on an object.
@ -183,21 +192,29 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
};
self.install_slot(context.gc_context, trait_name, *slot_id, value);
}
AbcTraitKind::Method { method, .. } => {
AbcTraitKind::Method {
disp_id, method, ..
} => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
let function =
FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto);
self.install_method(context.gc_context, trait_name, function);
self.install_method(context.gc_context, trait_name, *disp_id, function);
}
AbcTraitKind::Getter { method, .. } => {
AbcTraitKind::Getter {
disp_id, method, ..
} => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
let exec = Avm2Function::from_method(method, scope).into();
self.install_getter(context.gc_context, trait_name, exec)?;
let function =
FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto);
self.install_getter(context.gc_context, trait_name, *disp_id, function)?;
}
AbcTraitKind::Setter { method, .. } => {
AbcTraitKind::Setter {
disp_id, method, ..
} => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
let exec = Avm2Function::from_method(method, scope).into();
self.install_setter(context.gc_context, trait_name, exec)?;
let function =
FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto);
self.install_setter(context.gc_context, trait_name, *disp_id, function)?;
}
AbcTraitKind::Class { slot_id, class } => {
let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap();
@ -251,7 +268,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Call the object.
fn call(
self,
_reciever: Object<'gc>,
_reciever: Option<Object<'gc>>,
_arguments: &[Value<'gc>],
_avm: &mut Avm2<'gc>,
_context: &mut UpdateContext<'_, 'gc, '_>,
@ -282,6 +299,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Get a raw pointer value for this object.
fn as_ptr(&self) -> *const ObjectPtr;
/// Get this object's `Executable`, if it has one.
fn as_executable(&self) -> Option<Executable<'gc>> {
None
}
}
pub enum ObjectPtr {}

View File

@ -142,7 +142,7 @@ impl<'gc> Property<'gc> {
this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> {
match self {
Property::Virtual { get: Some(get), .. } => get.exec(avm, context, this, &[]),
Property::Virtual { get: Some(get), .. } => get.exec(avm, context, Some(this), &[]),
Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()),
Property::Stored { value, .. } => Ok(value.to_owned().into()),
Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()),
@ -168,7 +168,8 @@ impl<'gc> Property<'gc> {
match self {
Property::Virtual { set, .. } => {
if let Some(function) = set {
let return_value = function.exec(avm, context, this, &[new_value.into()])?;
let return_value =
function.exec(avm, context, Some(this), &[new_value.into()])?;
Ok(return_value.is_immediate())
} else {
Ok(true)
@ -211,7 +212,8 @@ impl<'gc> Property<'gc> {
match self {
Property::Virtual { set, .. } => {
if let Some(function) = set {
let return_value = function.exec(avm, context, this, &[new_value.into()])?;
let return_value =
function.exec(avm, context, Some(this), &[new_value.into()])?;
Ok(return_value.is_immediate())
} else {
Ok(true)

View File

@ -27,6 +27,9 @@ pub struct ScriptObjectData<'gc> {
/// Slots stored on this object.
slots: Vec<Slot<'gc>>,
/// Methods stored on this object.
methods: Vec<Option<Object<'gc>>>,
/// Implicit prototype (or declared base class) of this script object.
proto: Option<Object<'gc>>,
}
@ -91,6 +94,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.write(mc).init_slot(id, value, mc)
}
fn get_method(self, id: u32) -> Option<Object<'gc>> {
self.0.read().get_method(id)
}
fn has_property(self, name: &QName) -> bool {
self.0.read().has_property(name)
}
@ -113,26 +120,34 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
Ok(ScriptObject::object(context.gc_context, this))
}
fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) {
self.0.write(mc).install_method(name, function)
fn install_method(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
disp_id: u32,
function: Object<'gc>,
) {
self.0.write(mc).install_method(name, disp_id, function)
}
fn install_getter(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
function: Executable<'gc>,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error> {
self.0.write(mc).install_getter(name, function)
self.0.write(mc).install_getter(name, disp_id, function)
}
fn install_setter(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
function: Executable<'gc>,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error> {
self.0.write(mc).install_setter(name, function)
self.0.write(mc).install_setter(name, disp_id, function)
}
fn install_dynamic_property(
@ -189,6 +204,7 @@ impl<'gc> ScriptObjectData<'gc> {
ScriptObjectData {
values: HashMap::new(),
slots: Vec::new(),
methods: Vec::new(),
proto,
}
}
@ -306,6 +322,11 @@ impl<'gc> ScriptObjectData<'gc> {
}
}
/// Retrieve a method from the method table.
pub fn get_method(&self, id: u32) -> Option<Object<'gc>> {
self.methods.get(id as usize).and_then(|v| *v)
}
pub fn has_property(&self, name: &QName) -> bool {
self.values.get(name).is_some()
}
@ -315,7 +336,16 @@ impl<'gc> ScriptObjectData<'gc> {
}
/// Install a method into the object.
pub fn install_method(&mut self, name: QName, function: Object<'gc>) {
pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) {
if disp_id > 0 {
if self.methods.len() <= disp_id as usize {
self.methods
.resize_with(disp_id as usize + 1, Default::default);
}
*self.methods.get_mut(disp_id as usize).unwrap() = Some(function);
}
self.values.insert(name, Property::new_method(function));
}
@ -324,7 +354,26 @@ impl<'gc> ScriptObjectData<'gc> {
/// This is a little more complicated than methods, since virtual property
/// slots can be installed in two parts. Thus, we need to support
/// installing them in either order.
pub fn install_getter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> {
pub fn install_getter(
&mut self,
name: QName,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error> {
let executable: Result<Executable<'gc>, Error> = function
.as_executable()
.ok_or_else(|| "Attempted to install getter without a valid method".into());
let executable = executable?;
if disp_id > 0 {
if self.methods.len() <= disp_id as usize {
self.methods
.resize_with(disp_id as usize + 1, Default::default);
}
*self.methods.get_mut(disp_id as usize).unwrap() = Some(function);
}
if !self.values.contains_key(&name) {
self.values.insert(name.clone(), Property::new_virtual());
}
@ -332,7 +381,7 @@ impl<'gc> ScriptObjectData<'gc> {
self.values
.get_mut(&name)
.unwrap()
.install_virtual_getter(function)
.install_virtual_getter(executable)
}
/// Install a setter into the object.
@ -340,7 +389,26 @@ impl<'gc> ScriptObjectData<'gc> {
/// This is a little more complicated than methods, since virtual property
/// slots can be installed in two parts. Thus, we need to support
/// installing them in either order.
pub fn install_setter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> {
pub fn install_setter(
&mut self,
name: QName,
disp_id: u32,
function: Object<'gc>,
) -> Result<(), Error> {
let executable: Result<Executable<'gc>, Error> = function
.as_executable()
.ok_or_else(|| "Attempted to install setter without a valid method".into());
let executable = executable?;
if disp_id > 0 {
if self.methods.len() <= disp_id as usize {
self.methods
.resize_with(disp_id as usize + 1, Default::default);
}
*self.methods.get_mut(disp_id as usize).unwrap() = Some(function);
}
if !self.values.contains_key(&name) {
self.values.insert(name.clone(), Property::new_virtual());
}
@ -348,7 +416,7 @@ impl<'gc> ScriptObjectData<'gc> {
self.values
.get_mut(&name)
.unwrap()
.install_virtual_setter(function)
.install_virtual_setter(executable)
}
pub fn install_dynamic_property(