Calls to `super` inherently bind to itself.
This requires some subclassing nonsense to be able to smuggle a self-reference into `SuperObject`s. When successfully smuggled, all calls to `call` will be invoked with the `super` object as `this`. This allows constructor chaining to work. Note that not all `Object` trait methods are implemented on `SuperObject`, so things like `delete this.x` in super constructors will randomly fail. This should be fixed.
This commit is contained in:
parent
548f19ffbb
commit
854526923e
|
@ -237,6 +237,13 @@ impl<'gc> Executable<'gc> {
|
|||
None
|
||||
};
|
||||
|
||||
if let Some(super_object) = super_object {
|
||||
super_object
|
||||
.as_super_object()
|
||||
.unwrap()
|
||||
.bind_this(ac.gc_context, super_object);
|
||||
}
|
||||
|
||||
let effective_ver = if avm.current_swf_version() > 5 {
|
||||
af.swf_version()
|
||||
} else {
|
||||
|
|
|
@ -227,6 +227,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
/// Get the underlying script object, if it exists.
|
||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
|
||||
|
||||
/// Get the underlying super object, if it exists.
|
||||
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the underlying display node for this object, if it exists.
|
||||
fn as_display_object(&self) -> Option<DisplayObject<'gc>>;
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ pub struct SuperObjectData<'gc> {
|
|||
child: Object<'gc>,
|
||||
proto: Option<Object<'gc>>,
|
||||
constr: Option<Object<'gc>>,
|
||||
this: Option<Object<'gc>>,
|
||||
}
|
||||
|
||||
impl<'gc> SuperObject<'gc> {
|
||||
|
@ -33,10 +34,11 @@ impl<'gc> SuperObject<'gc> {
|
|||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Result<Self, Error> {
|
||||
let parent_proto = child.proto().and_then(|pr| pr.proto());
|
||||
let parent_constr = if let Some(parent_proto) = parent_proto {
|
||||
let child_proto = child.proto();
|
||||
let parent_proto = child_proto.and_then(|pr| pr.proto());
|
||||
let parent_constr = if let Some(child_proto) = child_proto {
|
||||
Some(
|
||||
parent_proto
|
||||
child_proto
|
||||
.get("constructor", avm, context)?
|
||||
.resolve(avm, context)?
|
||||
.as_object()?,
|
||||
|
@ -51,9 +53,18 @@ impl<'gc> SuperObject<'gc> {
|
|||
child,
|
||||
proto: parent_proto,
|
||||
constr: parent_constr,
|
||||
this: None,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
/// Set `this` to a particular value.
|
||||
///
|
||||
/// This is intended to be called with a self-reference, so that future
|
||||
/// invocations of `super()` can get a `this` value one level up the chain.
|
||||
pub fn bind_this(&mut self, context: MutationContext<'gc, '_>, this: Object<'gc>) {
|
||||
self.0.write(context).this = Some(this);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
||||
|
@ -86,7 +97,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
|||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(constr) = self.0.read().constr {
|
||||
constr.call(avm, context, this, args)
|
||||
constr.call(avm, context, self.0.read().this.unwrap_or(this), args)
|
||||
} else {
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
@ -224,6 +235,10 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
|||
None
|
||||
}
|
||||
|
||||
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
|
||||
Some(*self)
|
||||
}
|
||||
|
||||
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
|
||||
self.0.read().child.as_display_object()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue