2020-02-06 04:15:03 +00:00
|
|
|
//! AVM2 executables.
|
|
|
|
|
2020-02-08 03:42:04 +00:00
|
|
|
use crate::avm2::activation::Activation;
|
2020-02-19 03:26:08 +00:00
|
|
|
use crate::avm2::names::{Namespace, QName};
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::object::{Object, ObjectPtr, TObject};
|
|
|
|
use crate::avm2::return_value::ReturnValue;
|
2020-02-12 23:52:00 +00:00
|
|
|
use crate::avm2::scope::Scope;
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData};
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::value::Value;
|
|
|
|
use crate::avm2::{Avm2, Error};
|
|
|
|
use crate::context::UpdateContext;
|
2020-02-14 04:33:24 +00:00
|
|
|
use gc_arena::{Collect, CollectionContext, GcCell, MutationContext};
|
2020-02-06 04:15:03 +00:00
|
|
|
use std::fmt;
|
|
|
|
use std::rc::Rc;
|
2020-02-14 04:33:24 +00:00
|
|
|
use swf::avm2::types::{
|
|
|
|
AbcFile, Class as AbcClass, Index, Instance as AbcInstance, Method as AbcMethod,
|
2020-03-02 19:29:31 +00:00
|
|
|
MethodBody as AbcMethodBody, Trait as AbcTrait,
|
2020-02-14 04:33:24 +00:00
|
|
|
};
|
2020-02-06 04:15:03 +00:00
|
|
|
|
|
|
|
/// Represents a function defined in Ruffle's code.
|
|
|
|
///
|
|
|
|
/// Parameters are as follows:
|
|
|
|
///
|
|
|
|
/// * The AVM2 runtime
|
|
|
|
/// * The action context
|
|
|
|
/// * The current `this` object
|
|
|
|
/// * The arguments this function was called with
|
|
|
|
///
|
|
|
|
/// Native functions are allowed to return a value or `None`. `None` indicates
|
|
|
|
/// that the given value will not be returned on the stack and instead will
|
|
|
|
/// resolve on the AVM stack, as if you had called a non-native function. If
|
|
|
|
/// your function yields `None`, you must ensure that the top-most activation
|
|
|
|
/// in the AVM1 runtime will return with the value of this function.
|
|
|
|
pub type NativeFunction<'gc> = fn(
|
|
|
|
&mut Avm2<'gc>,
|
|
|
|
&mut UpdateContext<'_, 'gc, '_>,
|
2020-02-24 03:11:02 +00:00
|
|
|
Option<Object<'gc>>,
|
2020-02-06 04:15:03 +00:00
|
|
|
&[Value<'gc>],
|
|
|
|
) -> Result<ReturnValue<'gc>, Error>;
|
|
|
|
|
2020-02-12 23:52:00 +00:00
|
|
|
/// Represents a reference to an AVM2 method and body.
|
2020-02-06 04:15:03 +00:00
|
|
|
#[derive(Collect, Clone, Debug)]
|
|
|
|
#[collect(require_static)]
|
2020-02-12 23:52:00 +00:00
|
|
|
pub struct Avm2MethodEntry {
|
2020-02-06 04:15:03 +00:00
|
|
|
/// The ABC file this function was defined in.
|
|
|
|
pub abc: Rc<AbcFile>,
|
|
|
|
|
|
|
|
/// The ABC method this function uses.
|
|
|
|
pub abc_method: u32,
|
|
|
|
|
|
|
|
/// The ABC method body this function uses.
|
|
|
|
pub abc_method_body: u32,
|
|
|
|
}
|
|
|
|
|
2020-02-12 23:52:00 +00:00
|
|
|
impl Avm2MethodEntry {
|
2020-02-14 04:33:24 +00:00
|
|
|
/// Construct an `Avm2MethodEntry` from an `AbcFile` and method index.
|
|
|
|
///
|
|
|
|
/// The method body index will be determined by searching through the ABC
|
|
|
|
/// for a matching method. If none exists, this function returns `None`.
|
|
|
|
pub fn from_method_index(abc: Rc<AbcFile>, abc_method: Index<AbcMethod>) -> Option<Self> {
|
|
|
|
if abc.methods.get(abc_method.0 as usize).is_some() {
|
|
|
|
for (index, method_body) in abc.method_bodies.iter().enumerate() {
|
|
|
|
if method_body.method.0 == abc_method.0 {
|
|
|
|
return Some(Self {
|
|
|
|
abc,
|
|
|
|
abc_method: abc_method.0,
|
|
|
|
abc_method_body: index as u32,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the underlying ABC file.
|
2020-02-20 04:10:21 +00:00
|
|
|
#[allow(dead_code)]
|
2020-02-14 04:33:24 +00:00
|
|
|
pub fn abc(&self) -> Rc<AbcFile> {
|
|
|
|
self.abc.clone()
|
|
|
|
}
|
|
|
|
|
2020-02-12 23:52:00 +00:00
|
|
|
/// Get a reference to the ABC method entry this refers to.
|
|
|
|
pub fn method(&self) -> &AbcMethod {
|
|
|
|
self.abc.methods.get(self.abc_method as usize).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC method body entry this refers to.
|
|
|
|
pub fn body(&self) -> &AbcMethodBody {
|
|
|
|
self.abc
|
|
|
|
.method_bodies
|
|
|
|
.get(self.abc_method_body as usize)
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents an AVM2 function.
|
|
|
|
#[derive(Collect, Clone, Debug)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Avm2Function<'gc> {
|
|
|
|
/// The AVM method entry used to create this function.
|
|
|
|
pub method: Avm2MethodEntry,
|
|
|
|
|
|
|
|
/// Closure scope stack at time of creation
|
|
|
|
pub scope: Option<GcCell<'gc, Scope<'gc>>>,
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
|
|
|
|
/// The reciever this method is attached to.
|
|
|
|
///
|
|
|
|
/// Objects without a reciever are free functions that can be invoked with
|
|
|
|
/// any desired parameter for `this`.
|
|
|
|
pub reciever: Option<Object<'gc>>,
|
2020-02-12 23:52:00 +00:00
|
|
|
}
|
|
|
|
|
2020-02-14 04:33:24 +00:00
|
|
|
impl<'gc> Avm2Function<'gc> {
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
pub fn from_method(
|
|
|
|
method: Avm2MethodEntry,
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
reciever: Option<Object<'gc>>,
|
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
method,
|
|
|
|
scope,
|
|
|
|
reciever,
|
|
|
|
}
|
2020-02-14 04:33:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
/// Represents code that can be executed by some means.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub enum Executable<'gc> {
|
|
|
|
Native(NativeFunction<'gc>),
|
2020-02-12 23:52:00 +00:00
|
|
|
Action(Avm2Function<'gc>),
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> Collect for Executable<'gc> {
|
|
|
|
fn trace(&self, cc: CollectionContext) {
|
|
|
|
match self {
|
|
|
|
Self::Action(a2f) => a2f.trace(cc),
|
|
|
|
Self::Native(_nf) => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
impl<'gc> Executable<'gc> {
|
2020-02-25 23:07:53 +00:00
|
|
|
/// Execute a method.
|
|
|
|
///
|
|
|
|
/// The function will either be called directly if it is a Rust builtin, or
|
|
|
|
/// placed on the stack of the passed-in AVM2 otherwise. As a result, we
|
|
|
|
/// return a `ReturnValue` which can be used to force execution of the
|
|
|
|
/// given stack frame and obtain it's return value or to push said value
|
|
|
|
/// onto the AVM operand stack.
|
2020-02-15 01:30:19 +00:00
|
|
|
pub fn exec(
|
|
|
|
&self,
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
unbound_reciever: Option<Object<'gc>>,
|
2020-02-15 01:30:19 +00:00
|
|
|
arguments: &[Value<'gc>],
|
2020-02-25 23:07:53 +00:00
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
base_proto: Option<Object<'gc>>,
|
2020-02-25 23:07:53 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
|
|
match self {
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
Executable::Native(nf) => nf(avm, context, unbound_reciever, arguments),
|
2020-02-25 23:07:53 +00:00
|
|
|
Executable::Action(a2f) => {
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
let reciever = a2f.reciever.or(unbound_reciever);
|
2020-02-15 01:30:19 +00:00
|
|
|
let activation = GcCell::allocate(
|
|
|
|
context.gc_context,
|
2020-02-25 23:07:53 +00:00
|
|
|
Activation::from_action(context, &a2f, reciever, arguments, base_proto)?,
|
2020-02-15 01:30:19 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
avm.insert_stack_frame(activation);
|
|
|
|
Ok(activation.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
impl<'gc> fmt::Debug for Executable<'gc> {
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::Action(a2f) => fmt.debug_tuple("Executable::Action").field(a2f).finish(),
|
|
|
|
Self::Native(nf) => fmt
|
|
|
|
.debug_tuple("Executable::Native")
|
|
|
|
.field(&format!("{:p}", nf))
|
|
|
|
.finish(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 04:33:24 +00:00
|
|
|
impl<'gc> From<NativeFunction<'gc>> for Executable<'gc> {
|
|
|
|
fn from(nf: NativeFunction<'gc>) -> Self {
|
|
|
|
Self::Native(nf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> From<Avm2Function<'gc>> for Executable<'gc> {
|
|
|
|
fn from(a2f: Avm2Function<'gc>) -> Self {
|
|
|
|
Self::Action(a2f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a reference to an AVM2 class.
|
|
|
|
///
|
|
|
|
/// For some reason, this comes in two parts, one for static properties (called
|
|
|
|
/// the "class") and one for dynamic properties (called the "instance", even
|
|
|
|
/// though it really defines what ES3/AS2 would call a prototype)
|
|
|
|
#[derive(Collect, Clone, Debug)]
|
|
|
|
#[collect(require_static)]
|
|
|
|
pub struct Avm2ClassEntry {
|
|
|
|
/// The ABC file this function was defined in.
|
|
|
|
pub abc: Rc<AbcFile>,
|
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
/// The ABC class (used to define static properties).
|
|
|
|
///
|
|
|
|
/// This is also the index of the ABC instance, which holds instance
|
|
|
|
/// properties.
|
2020-02-14 04:33:24 +00:00
|
|
|
pub abc_class: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Avm2ClassEntry {
|
2020-02-20 04:10:21 +00:00
|
|
|
/// Construct an `Avm2MethodEntry` from an `AbcFile` and method index.
|
|
|
|
///
|
|
|
|
/// This function returns `None` if the given class index does not resolve
|
|
|
|
/// to a valid ABC class, or a valid ABC instance. As mentioned in the type
|
|
|
|
/// documentation, ABC classes and instances are intended to be paired.
|
|
|
|
pub fn from_class_index(abc: Rc<AbcFile>, abc_class: Index<AbcClass>) -> Option<Self> {
|
|
|
|
if abc.classes.get(abc_class.0 as usize).is_some()
|
|
|
|
&& abc.instances.get(abc_class.0 as usize).is_some()
|
|
|
|
{
|
|
|
|
return Some(Self {
|
|
|
|
abc,
|
|
|
|
abc_class: abc_class.0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-02-14 04:33:24 +00:00
|
|
|
/// Get the underlying ABC file.
|
|
|
|
pub fn abc(&self) -> Rc<AbcFile> {
|
|
|
|
self.abc.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC class entry this refers to.
|
|
|
|
pub fn class(&self) -> &AbcClass {
|
|
|
|
self.abc.classes.get(self.abc_class as usize).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC class instance entry this refers to.
|
|
|
|
pub fn instance(&self) -> &AbcInstance {
|
2020-02-20 04:10:21 +00:00
|
|
|
self.abc.instances.get(self.abc_class as usize).unwrap()
|
2020-02-14 04:33:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
/// An Object which can be called to execute it's function code.
|
|
|
|
#[derive(Collect, Debug, Clone, Copy)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct FunctionObject<'gc>(GcCell<'gc, FunctionObjectData<'gc>>);
|
|
|
|
|
|
|
|
#[derive(Collect, Debug, Clone)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct FunctionObjectData<'gc> {
|
|
|
|
/// Base script object
|
|
|
|
base: ScriptObjectData<'gc>,
|
|
|
|
|
|
|
|
/// Executable code
|
2020-02-19 23:53:21 +00:00
|
|
|
exec: Option<Executable<'gc>>,
|
2020-02-14 04:33:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> FunctionObject<'gc> {
|
|
|
|
/// Construct a class from an ABC class/instance pair.
|
|
|
|
///
|
2020-02-26 01:49:37 +00:00
|
|
|
/// This function returns both the class itself, and the static class
|
|
|
|
/// initializer method that you should call before interacting with the
|
|
|
|
/// class. The latter should be called using the former as a reciever.
|
2020-02-14 04:33:24 +00:00
|
|
|
pub fn from_abc_class(
|
2020-02-20 04:10:21 +00:00
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-02-14 04:33:24 +00:00
|
|
|
class: Avm2ClassEntry,
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
mut base_class: Object<'gc>,
|
2020-02-20 04:10:21 +00:00
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
2020-02-26 01:49:37 +00:00
|
|
|
) -> Result<(Object<'gc>, Object<'gc>), Error> {
|
2020-02-22 22:54:38 +00:00
|
|
|
let super_proto: Result<Object<'gc>, Error> = base_class
|
|
|
|
.get_property(
|
2020-02-28 04:38:56 +00:00
|
|
|
base_class,
|
2020-02-22 22:54:38 +00:00
|
|
|
&QName::new(Namespace::public_namespace(), "prototype"),
|
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
)?
|
|
|
|
.resolve(avm, context)?
|
|
|
|
.as_object()
|
|
|
|
.map_err(|_| {
|
|
|
|
let super_name = QName::from_abc_multiname(
|
|
|
|
&class.abc(),
|
|
|
|
class.instance().super_name.clone(),
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Ok(super_name) = super_name {
|
|
|
|
format!(
|
|
|
|
"Could not resolve superclass prototype {:?}",
|
|
|
|
super_name.local_name()
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
"Could not resolve superclass prototype, and got this error when getting it's name: {:?}",
|
|
|
|
super_name.unwrap_err()
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
});
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
let mut class_proto = super_proto?.derive(avm, context, class.clone(), scope)?;
|
|
|
|
let fn_proto = avm.prototypes().function;
|
2020-03-09 01:53:33 +00:00
|
|
|
let class_constr_proto = avm.prototypes().class;
|
2020-02-22 22:54:38 +00:00
|
|
|
|
2020-02-24 19:41:09 +00:00
|
|
|
let initializer_index = class.instance().init_method.clone();
|
2020-02-20 04:10:21 +00:00
|
|
|
let initializer: Result<Avm2MethodEntry, Error> =
|
|
|
|
Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else(
|
|
|
|
|| {
|
|
|
|
format!(
|
2020-02-24 19:41:09 +00:00
|
|
|
"Instance initializer method index {} does not exist",
|
2020-02-20 04:10:21 +00:00
|
|
|
initializer_index.0
|
|
|
|
)
|
|
|
|
.into()
|
2020-02-14 04:33:24 +00:00
|
|
|
},
|
2020-02-20 04:10:21 +00:00
|
|
|
);
|
2020-02-22 22:54:38 +00:00
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
let mut constr: Object<'gc> = FunctionObject(GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
FunctionObjectData {
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
base: ScriptObjectData::base_new(
|
|
|
|
Some(fn_proto),
|
|
|
|
ScriptObjectClass::ClassConstructor(class.clone(), scope),
|
|
|
|
),
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
exec: Some(Avm2Function::from_method(initializer?, scope, None).into()),
|
2020-02-20 04:10:21 +00:00
|
|
|
},
|
|
|
|
))
|
|
|
|
.into();
|
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
constr.install_dynamic_property(
|
2020-02-20 04:10:21 +00:00
|
|
|
context.gc_context,
|
|
|
|
QName::new(Namespace::public_namespace(), "prototype"),
|
2020-02-24 03:11:02 +00:00
|
|
|
class_proto.into(),
|
|
|
|
)?;
|
2020-02-25 23:07:53 +00:00
|
|
|
class_proto.install_dynamic_property(
|
|
|
|
context.gc_context,
|
|
|
|
QName::new(Namespace::public_namespace(), "constructor"),
|
|
|
|
constr.into(),
|
|
|
|
)?;
|
2020-02-20 04:10:21 +00:00
|
|
|
|
2020-02-26 01:49:37 +00:00
|
|
|
let class_initializer_index = class.class().init_method.clone();
|
|
|
|
let class_initializer: Result<Avm2MethodEntry, Error> =
|
|
|
|
Avm2MethodEntry::from_method_index(class.abc(), class_initializer_index.clone())
|
|
|
|
.ok_or_else(|| {
|
|
|
|
format!(
|
|
|
|
"Class initializer method index {} does not exist",
|
|
|
|
class_initializer_index.0
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
});
|
2020-03-02 19:29:31 +00:00
|
|
|
let class_constr = FunctionObject::from_abc_method(
|
2020-02-26 01:49:37 +00:00
|
|
|
context.gc_context,
|
2020-03-02 19:29:31 +00:00
|
|
|
class_initializer?,
|
|
|
|
scope,
|
2020-03-09 01:53:33 +00:00
|
|
|
class_constr_proto,
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
None,
|
2020-03-02 19:29:31 +00:00
|
|
|
);
|
2020-02-26 01:49:37 +00:00
|
|
|
|
|
|
|
Ok((constr, class_constr))
|
2020-02-14 04:33:24 +00:00
|
|
|
}
|
2020-02-18 00:43:23 +00:00
|
|
|
|
|
|
|
/// Construct a function from an ABC method and the current closure scope.
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
///
|
|
|
|
/// The given `reciever`, if supplied, will override any user-specified
|
|
|
|
/// `this` parameter.
|
2020-02-18 00:43:23 +00:00
|
|
|
pub fn from_abc_method(
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
method: Avm2MethodEntry,
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
fn_proto: Object<'gc>,
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
reciever: Option<Object<'gc>>,
|
2020-02-18 00:43:23 +00:00
|
|
|
) -> Object<'gc> {
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
let exec = Some(Avm2Function::from_method(method, scope, reciever).into());
|
2020-02-18 00:43:23 +00:00
|
|
|
|
|
|
|
FunctionObject(GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
FunctionObjectData {
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass),
|
2020-02-18 00:43:23 +00:00
|
|
|
exec,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
.into()
|
|
|
|
}
|
2020-02-19 03:26:08 +00:00
|
|
|
|
|
|
|
/// Construct a builtin function object from a Rust function.
|
|
|
|
pub fn from_builtin(
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
nf: NativeFunction<'gc>,
|
|
|
|
fn_proto: Object<'gc>,
|
|
|
|
) -> Object<'gc> {
|
|
|
|
FunctionObject(GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
FunctionObjectData {
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass),
|
2020-02-19 23:53:21 +00:00
|
|
|
exec: Some(nf.into()),
|
2020-02-19 03:26:08 +00:00
|
|
|
},
|
|
|
|
))
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Construct a builtin type from a Rust constructor and prototype.
|
|
|
|
pub fn from_builtin_constr(
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
constr: NativeFunction<'gc>,
|
2020-02-25 23:07:53 +00:00
|
|
|
mut prototype: Object<'gc>,
|
2020-02-19 03:26:08 +00:00
|
|
|
fn_proto: Object<'gc>,
|
|
|
|
) -> Result<Object<'gc>, Error> {
|
2020-02-25 23:07:53 +00:00
|
|
|
let mut base: Object<'gc> = FunctionObject(GcCell::allocate(
|
2020-02-19 03:26:08 +00:00
|
|
|
mc,
|
|
|
|
FunctionObjectData {
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass),
|
2020-02-19 23:53:21 +00:00
|
|
|
exec: Some(constr.into()),
|
2020-02-19 03:26:08 +00:00
|
|
|
},
|
|
|
|
))
|
2020-02-25 23:07:53 +00:00
|
|
|
.into();
|
|
|
|
|
|
|
|
base.install_dynamic_property(
|
|
|
|
mc,
|
|
|
|
QName::new(Namespace::public_namespace(), "prototype"),
|
|
|
|
prototype.into(),
|
|
|
|
)?;
|
|
|
|
prototype.install_dynamic_property(
|
|
|
|
mc,
|
|
|
|
QName::new(Namespace::public_namespace(), "constructor"),
|
|
|
|
base.into(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(base)
|
2020-02-19 03:26:08 +00:00
|
|
|
}
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> TObject<'gc> for FunctionObject<'gc> {
|
2020-02-26 02:37:47 +00:00
|
|
|
fn get_property_local(
|
2020-02-12 00:28:42 +00:00
|
|
|
self,
|
2020-02-28 04:38:56 +00:00
|
|
|
reciever: Object<'gc>,
|
2020-02-12 00:28:42 +00:00
|
|
|
name: &QName,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-15 01:30:19 +00:00
|
|
|
self.0
|
|
|
|
.read()
|
|
|
|
.base
|
2020-02-28 04:38:56 +00:00
|
|
|
.get_property_local(reciever, name, avm, context)
|
2020-02-12 00:28:42 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 01:00:18 +00:00
|
|
|
fn set_property_local(
|
2020-02-12 00:28:42 +00:00
|
|
|
self,
|
2020-02-28 04:38:56 +00:00
|
|
|
reciever: Object<'gc>,
|
2020-02-12 00:28:42 +00:00
|
|
|
name: &QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-02-29 01:55:09 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-12 00:28:42 +00:00
|
|
|
self.0
|
|
|
|
.write(context.gc_context)
|
|
|
|
.base
|
2020-02-29 01:00:18 +00:00
|
|
|
.set_property_local(reciever, name, value, avm, context)
|
2020-02-12 00:28:42 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 01:00:18 +00:00
|
|
|
fn init_property_local(
|
2020-02-22 21:21:28 +00:00
|
|
|
self,
|
2020-02-29 01:00:18 +00:00
|
|
|
reciever: Object<'gc>,
|
2020-02-22 21:21:28 +00:00
|
|
|
name: &QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-02-29 01:55:09 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-22 21:21:28 +00:00
|
|
|
self.0
|
|
|
|
.write(context.gc_context)
|
|
|
|
.base
|
2020-02-29 01:00:18 +00:00
|
|
|
.init_property_local(reciever, name, value, avm, context)
|
2020-02-22 21:21:28 +00:00
|
|
|
}
|
|
|
|
|
2020-03-04 04:03:35 +00:00
|
|
|
fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool {
|
|
|
|
self.0.write(gc_context).base.is_property_overwritable(name)
|
|
|
|
}
|
|
|
|
|
2020-02-21 19:52:24 +00:00
|
|
|
fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool {
|
|
|
|
self.0.write(gc_context).base.delete_property(multiname)
|
|
|
|
}
|
|
|
|
|
2020-02-19 19:17:33 +00:00
|
|
|
fn get_slot(self, id: u32) -> Result<Value<'gc>, Error> {
|
|
|
|
self.0.read().base.get_slot(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_slot(
|
|
|
|
self,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0.write(mc).base.set_slot(id, value, mc)
|
|
|
|
}
|
|
|
|
|
2020-02-22 21:21:28 +00:00
|
|
|
fn init_slot(
|
|
|
|
self,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0.write(mc).base.init_slot(id, value, mc)
|
|
|
|
}
|
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
fn get_method(self, id: u32) -> Option<Object<'gc>> {
|
|
|
|
self.0.read().base.get_method(id)
|
|
|
|
}
|
|
|
|
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
fn get_trait(self, name: &QName) -> Result<Vec<AbcTrait>, Error> {
|
2020-03-02 19:29:31 +00:00
|
|
|
self.0.read().base.get_trait(name)
|
|
|
|
}
|
|
|
|
|
2020-03-07 21:01:03 +00:00
|
|
|
fn get_provided_trait(
|
|
|
|
&self,
|
|
|
|
name: &QName,
|
|
|
|
known_traits: &mut Vec<AbcTrait>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0.read().base.get_provided_trait(name, known_traits)
|
|
|
|
}
|
|
|
|
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> {
|
|
|
|
self.0.read().base.get_scope()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_abc(self) -> Option<Rc<AbcFile>> {
|
|
|
|
self.0.read().base.get_abc()
|
|
|
|
}
|
|
|
|
|
2020-03-07 04:15:36 +00:00
|
|
|
fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> {
|
2020-03-01 01:09:07 +00:00
|
|
|
self.0.read().base.resolve_any(local_name)
|
|
|
|
}
|
|
|
|
|
2020-03-07 04:15:36 +00:00
|
|
|
fn resolve_any_trait(self, local_name: &str) -> Result<Option<Namespace>, Error> {
|
|
|
|
self.0.read().base.resolve_any_trait(local_name)
|
|
|
|
}
|
|
|
|
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
fn has_own_property(self, name: &QName) -> Result<bool, Error> {
|
2020-02-26 02:37:47 +00:00
|
|
|
self.0.read().base.has_own_property(name)
|
2020-02-12 00:42:47 +00:00
|
|
|
}
|
|
|
|
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
fn has_trait(self, name: &QName) -> Result<bool, Error> {
|
|
|
|
self.0.read().base.has_trait(name)
|
|
|
|
}
|
|
|
|
|
2020-03-07 21:01:03 +00:00
|
|
|
fn provides_trait(self, name: &QName) -> Result<bool, Error> {
|
|
|
|
self.0.read().base.provides_trait(name)
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn has_instantiated_property(self, name: &QName) -> bool {
|
|
|
|
self.0.read().base.has_instantiated_property(name)
|
|
|
|
}
|
|
|
|
|
2020-02-29 02:37:25 +00:00
|
|
|
fn has_own_virtual_getter(self, name: &QName) -> bool {
|
|
|
|
self.0.read().base.has_own_virtual_getter(name)
|
|
|
|
}
|
|
|
|
|
2020-02-29 01:00:18 +00:00
|
|
|
fn has_own_virtual_setter(self, name: &QName) -> bool {
|
|
|
|
self.0.read().base.has_own_virtual_setter(name)
|
|
|
|
}
|
|
|
|
|
2020-02-13 03:47:56 +00:00
|
|
|
fn proto(&self) -> Option<Object<'gc>> {
|
|
|
|
self.0.read().base.proto()
|
|
|
|
}
|
|
|
|
|
Implement `hasnext`, `hasnext2`, `nextname`, `nextvalue`, and the underlying enumeration machinery that powers it.
I have... significant reservations with the way object enumeration happens in AVM2. For comparison, AVM1 enumeration works like this: You enumerate the entire object at once, producing a list of property names, which are then pushed onto the stack after a sentinel value. This is a properly abstract way to handle property enumeration.
In AVM2, they completely replaced this with index-based enumeration. What this means is that you hand the object an index and it gives you back a name or value. There's also an instruction that will give you the next index in the object.
The only advantage I can think of is that it results in less stack manipulation if you want to bail out of iteration early. You just jump out of your loop and kill the registers you don't care about. The disadvantage is that it locks the object representation down pretty hard. They also screwed up the definition of `hasnext`, and thus the VM is stuck enumerating properties from 1. This is because `hasnext` and `hasnext2` increment the index value before checking the object. Code generated by Animate 2020 (which I suspect to be the final version of that software that generates AVM2 code) initializes the index at hero, and then does `hasnext2`, hence we have to start from one.
I actually cheated a little and added a separate `Vec` for storing enumerant names. I strongly suspect that Adobe's implementation has objects be inherently slot-oriented, and named properties are just hashmap lookups to slots. This would allow enumerating the slots to get names out of the object.
2020-03-06 02:26:01 +00:00
|
|
|
fn get_enumerant_name(&self, index: u32) -> Option<QName> {
|
|
|
|
self.0.read().base.get_enumerant_name(index)
|
|
|
|
}
|
|
|
|
|
2020-03-08 01:16:48 +00:00
|
|
|
fn property_is_enumerable(&self, name: &QName) -> bool {
|
|
|
|
self.0.read().base.property_is_enumerable(name)
|
|
|
|
}
|
|
|
|
|
2020-03-08 23:12:17 +00:00
|
|
|
fn set_local_property_is_enumerable(
|
|
|
|
&self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: &QName,
|
|
|
|
is_enumerable: bool,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0
|
|
|
|
.write(mc)
|
|
|
|
.base
|
|
|
|
.set_local_property_is_enumerable(name, is_enumerable)
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
fn as_ptr(&self) -> *const ObjectPtr {
|
|
|
|
self.0.as_ptr() as *const ObjectPtr
|
|
|
|
}
|
2020-02-08 03:42:04 +00:00
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
fn as_executable(&self) -> Option<Executable<'gc>> {
|
|
|
|
self.0.read().exec.clone()
|
|
|
|
}
|
|
|
|
|
2020-02-08 03:42:04 +00:00
|
|
|
fn call(
|
|
|
|
self,
|
2020-02-24 03:11:02 +00:00
|
|
|
reciever: Option<Object<'gc>>,
|
2020-02-08 03:42:04 +00:00
|
|
|
arguments: &[Value<'gc>],
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
base_proto: Option<Object<'gc>>,
|
2020-02-08 03:42:04 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-19 23:53:21 +00:00
|
|
|
if let Some(exec) = &self.0.read().exec {
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
exec.exec(reciever, arguments, avm, context, base_proto)
|
2020-02-19 23:53:21 +00:00
|
|
|
} else {
|
|
|
|
Err("Not a callable function!".into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn construct(
|
|
|
|
&self,
|
|
|
|
_avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
_args: &[Value<'gc>],
|
|
|
|
) -> Result<Object<'gc>, Error> {
|
|
|
|
let this: Object<'gc> = Object::FunctionObject(*self);
|
Completely overhaul the way traits are defined on objects.
Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal.
This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property.
However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon.
The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests.
We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly.
`super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
|
|
|
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
|
|
|
|
|
|
|
|
Ok(FunctionObject(GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
FunctionObjectData { base, exec: None },
|
|
|
|
))
|
|
|
|
.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn derive(
|
|
|
|
&self,
|
|
|
|
_avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
class: Avm2ClassEntry,
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
) -> Result<Object<'gc>, Error> {
|
|
|
|
let this: Object<'gc> = Object::FunctionObject(*self);
|
|
|
|
let base = ScriptObjectData::base_new(
|
|
|
|
Some(this),
|
|
|
|
ScriptObjectClass::InstancePrototype(class, scope),
|
|
|
|
);
|
2020-02-19 23:53:21 +00:00
|
|
|
|
|
|
|
Ok(FunctionObject(GcCell::allocate(
|
|
|
|
context.gc_context,
|
2020-03-02 19:29:31 +00:00
|
|
|
FunctionObjectData { base, exec: None },
|
2020-02-19 23:53:21 +00:00
|
|
|
))
|
|
|
|
.into())
|
2020-02-08 03:42:04 +00:00
|
|
|
}
|
2020-02-19 01:08:13 +00:00
|
|
|
|
Make `toString` and `valueOf` methods of `TObject`, called `to_string` and `value_of` respectively.
The reason for this is that, in AVM2, `toString` and `valueOf` are not defined on the classes or prototypes of `Function` or `Class`. Instead, they use the `Object.prototype` versions of those functions. Ergo, string and primitive coercion are inherent object methods (the ones that get `[[DoubleSquareBrackets]]` in the ECMA standards). In Ruffle, our equivalent to `[[DoubleSquareBrackets]]` methods are methods on the `TObject` trait, so we're adding them there.
This mechanism will make implementing boxed value types (ala AVM1's `BoxedObject`) easier, too.
We also add some reasonable defaults for `ScriptObject` and `FunctionObject` which will appear on objects, functions, and classes.
2020-03-09 00:11:59 +00:00
|
|
|
fn to_string(&self) -> Result<Value<'gc>, Error> {
|
|
|
|
if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() {
|
|
|
|
let name = QName::from_abc_multiname(&class.abc(), class.instance().name.clone())?;
|
|
|
|
Ok(format!("[class {}]", name.local_name()).into())
|
|
|
|
} else {
|
|
|
|
Ok("function Function() {}".into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn value_of(&self) -> Result<Value<'gc>, Error> {
|
|
|
|
Ok(Value::Object(Object::from(*self)))
|
|
|
|
}
|
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
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)
|
2020-02-20 04:10:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn install_getter(
|
2020-02-19 01:08:13 +00:00
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
2020-02-20 04:10:21 +00:00
|
|
|
name: QName,
|
2020-02-24 03:11:02 +00:00
|
|
|
disp_id: u32,
|
|
|
|
function: Object<'gc>,
|
2020-02-19 01:08:13 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-02-24 03:11:02 +00:00
|
|
|
self.0
|
|
|
|
.write(mc)
|
|
|
|
.base
|
|
|
|
.install_getter(name, disp_id, function)
|
2020-02-19 01:08:13 +00:00
|
|
|
}
|
2020-02-19 03:26:08 +00:00
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
fn install_setter(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
2020-02-24 03:11:02 +00:00
|
|
|
disp_id: u32,
|
|
|
|
function: Object<'gc>,
|
2020-02-20 04:10:21 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-02-24 03:11:02 +00:00
|
|
|
self.0
|
|
|
|
.write(mc)
|
|
|
|
.base
|
|
|
|
.install_setter(name, disp_id, function)
|
2020-02-19 03:26:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn install_dynamic_property(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0.write(mc).base.install_dynamic_property(name, value)
|
|
|
|
}
|
2020-02-21 02:02:49 +00:00
|
|
|
|
|
|
|
fn install_slot(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
) {
|
|
|
|
self.0.write(mc).base.install_slot(name, id, value)
|
|
|
|
}
|
2020-02-21 04:20:46 +00:00
|
|
|
|
|
|
|
fn install_const(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
) {
|
|
|
|
self.0.write(mc).base.install_const(name, id, value)
|
|
|
|
}
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|