avm2: Move the class association from prototypes to instances.

This also incurred a large number of ancillary changes, as it turns out nearly every native object is currently pulling a prototype and sticking it into an object. Right now, I have it instead pulling the constructor out of the prototype, but a future PR will also remove `system_prototypes` as well.

Other ancillary changes include:

 * `Domain` now supports partial initialization to avoid an order-of-events issue. Accessing domain memory on a partially-initialized `Domain` will panic.
 * `Domain` construction requires a full `activation` now, except for `global_scope` which needs to be initialized later with valid domain memory before user code runs.
 * Pretty much every native object constructor now takes a proto/constr pair
 * Trait lookup was rewritten to handle this. It's still buggy - seven tests don't work
 * `TObject.construct` now actually does the full object construction dance. This allows `ClassObject` to implement the ES4 object construction pathway directly while `FunctionObject` maintains ES3 compatibility.

This is a tentative commit; there are still seven failing tests that I need to fix.
This commit is contained in:
David Wendt 2021-05-21 22:46:17 -04:00
parent f26f193508
commit 323da9ded3
49 changed files with 1289 additions and 1210 deletions

View File

@ -144,8 +144,20 @@ impl<'gc> Avm2<'gc> {
target: Object<'gc>, target: Object<'gc>,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
use crate::avm2::events::dispatch_event; use crate::avm2::events::dispatch_event;
let event_proto = context.avm2.system_prototypes.as_ref().unwrap().event;
let event_object = EventObject::from_event(context.gc_context, Some(event_proto), event); let mut activation = Activation::from_nothing(context.reborrow());
let mut event_proto = activation.avm2().prototypes().event;
let event_constr = event_proto
.get_property(
event_proto,
&QName::new(Namespace::public(), "constructor"),
&mut activation,
)?
.coerce_to_object(&mut activation)?;
drop(activation);
let event_object =
EventObject::from_event(context.gc_context, event_constr, Some(event_proto), event);
let mut activation = Activation::from_nothing(context.reborrow()); let mut activation = Activation::from_nothing(context.reborrow());
dispatch_event(&mut activation, target, event_object) dispatch_event(&mut activation, target, event_object)
@ -255,7 +267,7 @@ impl<'gc> Avm2<'gc> {
let tunit = TranslationUnit::from_abc(abc_file.clone(), domain, context.gc_context); let tunit = TranslationUnit::from_abc(abc_file.clone(), domain, context.gc_context);
for i in (0..abc_file.scripts.len()).rev() { for i in (0..abc_file.scripts.len()).rev() {
let mut script = tunit.load_script(i as u32, context.avm2, context.gc_context)?; let mut script = tunit.load_script(i as u32, context)?;
if !lazy_init { if !lazy_init {
script.globals(context)?; script.globals(context)?;

View File

@ -105,20 +105,20 @@ pub struct Activation<'a, 'gc: 'a, 'gc_context: 'a> {
/// A `scope` of `None` indicates that the scope stack is empty. /// A `scope` of `None` indicates that the scope stack is empty.
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
/// The base prototype of `this`. /// The base class constructor that yielded the currently executing method.
/// ///
/// This will not be available if this is not a method call. /// This will not be available if this is not a method call.
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
/// The proto of all objects returned from `newactivation`. /// The constructor of all objects returned from `newactivation`.
/// ///
/// In method calls that call for an activation object, this will be /// In method calls that call for an activation object, this will be
/// configured as the anonymous class whose traits match the method's /// configured as the anonymous class whose traits match the method's
/// declared traits. /// declared traits.
/// ///
/// If this is `None`, then the method did not ask for an activation object /// If this is `None`, then the method did not ask for an activation object
/// and we will not construct a prototype for one. /// and we will not allocate a constructor for one.
activation_proto: Option<Object<'gc>>, activation_constr: Option<Object<'gc>>,
pub context: UpdateContext<'a, 'gc, 'gc_context>, pub context: UpdateContext<'a, 'gc, 'gc_context>,
} }
@ -144,8 +144,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return_value: None, return_value: None,
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope: None, scope: None,
base_proto: None, base_constr: None,
activation_proto: None, activation_constr: None,
context, context,
} }
} }
@ -185,8 +185,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return_value: None, return_value: None,
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope, scope,
base_proto: None, base_constr: None,
activation_proto: None, activation_constr: None,
context, context,
}) })
} }
@ -194,12 +194,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
/// Construct an activation for the execution of a particular bytecode /// Construct an activation for the execution of a particular bytecode
/// method. /// method.
pub fn from_method( pub fn from_method(
context: UpdateContext<'a, 'gc, 'gc_context>, mut context: UpdateContext<'a, 'gc, 'gc_context>,
method: Gc<'gc, BytecodeMethod<'gc>>, method: Gc<'gc, BytecodeMethod<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
arguments: &[Value<'gc>], arguments: &[Value<'gc>],
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
callee: Object<'gc>, callee: Object<'gc>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let body: Result<_, Error> = method let body: Result<_, Error> = method
@ -227,22 +227,18 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} }
} }
let activation_proto = if method.method().needs_activation { let activation_constr = if method.method().needs_activation {
let translation_unit = method.translation_unit(); let translation_unit = method.translation_unit();
let abc_method = method.method(); let abc_method = method.method();
let activation_class = Class::from_method_body( let activation_class =
context.avm2, Class::from_method_body(&mut context, translation_unit, abc_method, body)?;
context.gc_context, let mut dummy_activation = Activation::from_nothing(context.reborrow());
translation_unit, let (constr, _cinit) =
abc_method, ClassObject::from_class(&mut dummy_activation, activation_class, None, scope)?;
body,
)?;
Some(ScriptObject::bare_prototype( drop(dummy_activation);
context.gc_context,
activation_class, Some(constr)
scope,
))
} else { } else {
None None
}; };
@ -256,8 +252,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return_value: None, return_value: None,
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope, scope,
base_proto, base_constr,
activation_proto, activation_constr,
context, context,
}; };
@ -274,15 +270,18 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
unreachable!(); unreachable!();
}; };
let mut array_proto = activation.avm2().prototypes().array;
let array_constr = array_proto
.get_property(
array_proto,
&QName::new(Namespace::public(), "constructor"),
&mut activation,
)?
.coerce_to_object(&mut activation)?;
let mut args_object = ArrayObject::from_array( let mut args_object = ArrayObject::from_array(
args_array, args_array,
activation array_constr,
.context array_proto,
.avm2
.system_prototypes
.as_ref()
.unwrap()
.array,
activation.context.gc_context, activation.context.gc_context,
); );
@ -318,7 +317,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
context: UpdateContext<'a, 'gc, 'gc_context>, context: UpdateContext<'a, 'gc, 'gc_context>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let local_registers = GcCell::allocate(context.gc_context, RegisterSet::new(0)); let local_registers = GcCell::allocate(context.gc_context, RegisterSet::new(0));
@ -331,8 +330,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return_value: None, return_value: None,
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope, scope,
base_proto, base_constr,
activation_proto: None, activation_constr: None,
context, context,
}) })
} }
@ -369,20 +368,17 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
receiver: Object<'gc>, receiver: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
let name = QName::new(Namespace::public(), "constructor"); let base_constr: Result<Object<'gc>, Error> = self
let base_proto: Result<Object<'gc>, Error> = .base_constr()
self.base_proto().and_then(|p| p.proto()).ok_or_else(|| { .and_then(|c| c.base_class_constr())
.ok_or_else(|| {
"Attempted to call super constructor without a superclass." "Attempted to call super constructor without a superclass."
.to_string() .to_string()
.into() .into()
}); });
let mut base_proto = base_proto?; let base_constr = base_constr?;
let function = base_proto base_constr.call(Some(receiver), args, self, Some(base_constr))
.get_property(receiver, &name, self)?
.coerce_to_object(self)?;
function.call(Some(receiver), args, self, Some(base_proto))
} }
/// Attempts to lock the activation frame for execution. /// Attempts to lock the activation frame for execution.
@ -452,8 +448,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
/// Get the base prototype of the object that the currently executing /// Get the base prototype of the object that the currently executing
/// method was retrieved from, if one exists. /// method was retrieved from, if one exists.
pub fn base_proto(&self) -> Option<Object<'gc>> { pub fn base_constr(&self) -> Option<Object<'gc>> {
self.base_proto self.base_constr
} }
/// Retrieve a int from the current constant pool. /// Retrieve a int from the current constant pool.
@ -542,7 +538,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
) -> Result<GcCell<'gc, Class<'gc>>, Error> { ) -> Result<GcCell<'gc, Class<'gc>>, Error> {
method method
.translation_unit() .translation_unit()
.load_class(index.0, self.context.avm2, self.context.gc_context) .load_class(index.0, &mut self.context)
} }
pub fn run_actions( pub fn run_actions(
@ -817,10 +813,19 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
value: Index<AbcNamespace>, value: Index<AbcNamespace>,
) -> Result<FrameControl<'gc>, Error> { ) -> Result<FrameControl<'gc>, Error> {
let ns = self.pool_namespace(method, value, self.context.gc_context)?; let ns = self.pool_namespace(method, value, self.context.gc_context)?;
let mut ns_proto = self.context.avm2.prototypes().namespace;
let ns_constr = ns_proto
.get_property(
ns_proto,
&QName::new(Namespace::public(), "constructor"),
self,
)?
.coerce_to_object(self)?;
self.context.avm2.push(NamespaceObject::from_namespace( self.context.avm2.push(NamespaceObject::from_namespace(
ns, ns,
self.context.avm2.prototypes().namespace, ns_constr,
ns_proto,
self.context.gc_context, self.context.gc_context,
)?); )?);
Ok(FrameControl::Continue) Ok(FrameControl::Continue)
@ -952,11 +957,15 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.resolve_multiname(&multiname)? .resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?; let name = name?;
let base_proto = receiver.get_base_proto(&name)?; let base_constr = if let Some(c) = receiver.as_constr() {
c.find_base_constr_for_trait(&name)?
} else {
None
};
let function = receiver let function = receiver
.get_property(receiver, &name, self)? .get_property(receiver, &name, self)?
.coerce_to_object(self)?; .coerce_to_object(self)?;
let value = function.call(Some(receiver), &args, self, base_proto)?; let value = function.call(Some(receiver), &args, self, base_constr)?;
self.context.avm2.push(value); self.context.avm2.push(value);
@ -998,12 +1007,16 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.resolve_multiname(&multiname)? .resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?; let name = name?;
let base_proto = receiver.get_base_proto(&name)?; let base_constr = if let Some(c) = receiver.as_constr() {
c.find_base_constr_for_trait(&name)?
} else {
None
};
let function = receiver let function = receiver
.get_property(receiver, &name, self)? .get_property(receiver, &name, self)?
.coerce_to_object(self)?; .coerce_to_object(self)?;
function.call(Some(receiver), &args, self, base_proto)?; function.call(Some(receiver), &args, self, base_constr)?;
Ok(FrameControl::Continue) Ok(FrameControl::Continue)
} }
@ -1025,7 +1038,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
self.context.avm2.prototypes().function, self.context.avm2.prototypes().function,
None, None,
); );
let value = function.call(Some(receiver), &args, self, receiver.proto())?; let value = function.call(Some(receiver), &args, self, receiver.as_constr())?;
self.context.avm2.push(value); self.context.avm2.push(value);
@ -1041,23 +1054,23 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let args = self.context.avm2.pop_args(arg_count); let args = self.context.avm2.pop_args(arg_count);
let multiname = self.pool_multiname(method, index)?; let multiname = self.pool_multiname(method, index)?;
let receiver = self.context.avm2.pop().coerce_to_object(self)?; let receiver = self.context.avm2.pop().coerce_to_object(self)?;
let name: Result<QName, Error> = receiver let name: Result<QName, Error> = receiver
.resolve_multiname(&multiname)? .resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let base_proto: Result<Object<'gc>, Error> = let name = name?;
self.base_proto().and_then(|bp| bp.proto()).ok_or_else(|| {
let base_constr: Result<Object<'gc>, Error> = self
.base_constr()
.and_then(|bc| bc.base_class_constr())
.ok_or_else(|| {
"Attempted to call super method without a superclass." "Attempted to call super method without a superclass."
.to_string() .to_string()
.into() .into()
}); });
let base_proto = base_proto?; let base_constr = base_constr?;
let mut base = base_proto.construct(self, &[])?; //TODO: very hacky workaround
let function = base let value = base_constr.call_instance_method(&name, Some(receiver), &args, self)?;
.get_property(receiver, &name?, self)?
.coerce_to_object(self)?;
let value = function.call(Some(receiver), &args, self, Some(base_proto))?;
self.context.avm2.push(value); self.context.avm2.push(value);
@ -1073,23 +1086,23 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let args = self.context.avm2.pop_args(arg_count); let args = self.context.avm2.pop_args(arg_count);
let multiname = self.pool_multiname(method, index)?; let multiname = self.pool_multiname(method, index)?;
let receiver = self.context.avm2.pop().coerce_to_object(self)?; let receiver = self.context.avm2.pop().coerce_to_object(self)?;
let name: Result<QName, Error> = receiver let name: Result<QName, Error> = receiver
.resolve_multiname(&multiname)? .resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let base_proto: Result<Object<'gc>, Error> = let name = name?;
self.base_proto().and_then(|bp| bp.proto()).ok_or_else(|| {
let base_constr: Result<Object<'gc>, Error> = self
.base_constr()
.and_then(|bc| bc.base_class_constr())
.ok_or_else(|| {
"Attempted to call super method without a superclass." "Attempted to call super method without a superclass."
.to_string() .to_string()
.into() .into()
}); });
let base_proto = base_proto?; let base_constr = base_constr?;
let mut base = base_proto.construct(self, &[])?; //TODO: very hacky workaround
let function = base base_constr.call_instance_method(&name, Some(receiver), &args, self)?;
.get_property(receiver, &name?, self)?
.coerce_to_object(self)?;
function.call(Some(receiver), &args, self, Some(base_proto))?;
Ok(FrameControl::Continue) Ok(FrameControl::Continue)
} }
@ -1120,7 +1133,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// dynamic properties not yet set // dynamic properties not yet set
if name.is_err() if name.is_err()
&& !object && !object
.as_proto_class() .as_class()
.map(|c| c.read().is_sealed()) .map(|c| c.read().is_sealed())
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1198,7 +1211,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Unknown properties on a dynamic class delete successfully. // Unknown properties on a dynamic class delete successfully.
self.context.avm2.push( self.context.avm2.push(
!object !object
.as_proto_class() .as_class()
.map(|c| c.read().is_sealed()) .map(|c| c.read().is_sealed())
.unwrap_or(false), .unwrap_or(false),
) )
@ -1214,22 +1227,23 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
) -> Result<FrameControl<'gc>, Error> { ) -> Result<FrameControl<'gc>, Error> {
let multiname = self.pool_multiname(method, index)?; let multiname = self.pool_multiname(method, index)?;
let object = self.context.avm2.pop().coerce_to_object(self)?; let object = self.context.avm2.pop().coerce_to_object(self)?;
let base_proto: Result<Object<'gc>, Error> = self
.base_proto()
.and_then(|p| p.proto())
.ok_or_else(|| "Attempted to get property on non-existent super object".into());
let base_proto = base_proto?;
let mut base = base_proto.construct(self, &[])?; //TODO: very hacky workaround
let name: Result<QName, Error> = base.resolve_multiname(&multiname)?.ok_or_else(|| { let name: Result<QName, Error> = object
format!( .resolve_multiname(&multiname)?
"Could not resolve {:?} as super property", .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
multiname.local_name() let name = name?;
)
let base_constr: Result<Object<'gc>, Error> = self
.base_constr()
.and_then(|bc| bc.base_class_constr())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
.to_string()
.into() .into()
}); });
let base_constr = base_constr?;
let value = base.get_property(object, &name?, self)?; let value = base_constr.call_instance_getter(&name, Some(object), self)?;
self.context.avm2.push(value); self.context.avm2.push(value);
@ -1244,22 +1258,23 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let value = self.context.avm2.pop(); let value = self.context.avm2.pop();
let multiname = self.pool_multiname(method, index)?; let multiname = self.pool_multiname(method, index)?;
let object = self.context.avm2.pop().coerce_to_object(self)?; let object = self.context.avm2.pop().coerce_to_object(self)?;
let base_proto: Result<Object<'gc>, Error> = self
.base_proto()
.and_then(|p| p.proto())
.ok_or_else(|| "Attempted to get property on non-existent super object".into());
let base_proto = base_proto?;
let mut base = base_proto.construct(self, &[])?; //TODO: very hacky workaround
let name: Result<QName, Error> = base.resolve_multiname(&multiname)?.ok_or_else(|| { let name: Result<QName, Error> = object
format!( .resolve_multiname(&multiname)?
"Could not resolve {:?} as super property", .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
multiname.local_name() let name = name?;
)
let base_constr: Result<Object<'gc>, Error> = self
.base_constr()
.and_then(|bc| bc.base_class_constr())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
.to_string()
.into() .into()
}); });
let base_constr = base_constr?;
base.set_property(object, &name?, value, self)?; base_constr.call_instance_setter(&name, value, Some(object), self)?;
Ok(FrameControl::Continue) Ok(FrameControl::Continue)
} }
@ -1433,14 +1448,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn op_construct(&mut self, arg_count: u32) -> Result<FrameControl<'gc>, Error> { fn op_construct(&mut self, arg_count: u32) -> Result<FrameControl<'gc>, Error> {
let args = self.context.avm2.pop_args(arg_count); let args = self.context.avm2.pop_args(arg_count);
let mut ctor = self.context.avm2.pop().coerce_to_object(self)?; let ctor = self.context.avm2.pop().coerce_to_object(self)?;
let proto = ctor let object = ctor.construct(self, &args)?;
.get_property(ctor, &QName::new(Namespace::public(), "prototype"), self)?
.coerce_to_object(self)?;
let object = proto.construct(self, &args)?;
ctor.call(Some(object), &args, self, object.proto())?;
self.context.avm2.push(object); self.context.avm2.push(object);
@ -1461,15 +1471,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
source.resolve_multiname(&multiname)?.ok_or_else(|| { source.resolve_multiname(&multiname)?.ok_or_else(|| {
format!("Could not resolve property {:?}", multiname.local_name()).into() format!("Could not resolve property {:?}", multiname.local_name()).into()
}); });
let mut ctor = source let ctor_name = ctor_name?;
.get_property(source, &ctor_name?, self)? let ctor = source
.coerce_to_object(self)?; .get_property(source, &ctor_name, self)?
let proto = ctor
.get_property(ctor, &QName::new(Namespace::public(), "prototype"), self)?
.coerce_to_object(self)?; .coerce_to_object(self)?;
let object = proto.construct(self, &args)?; let object = ctor.construct(self, &args)?;
ctor.call(Some(object), &args, self, Some(proto))?;
self.context.avm2.push(object); self.context.avm2.push(object);
@ -1486,16 +1493,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} }
fn op_new_activation(&mut self) -> Result<FrameControl<'gc>, Error> { fn op_new_activation(&mut self) -> Result<FrameControl<'gc>, Error> {
if let Some(activation_proto) = self.activation_proto { let instance = if let Some(activation_constr) = self.activation_constr {
self.context.avm2.push(ScriptObject::object( activation_constr.construct(self, &[])?
self.context.gc_context,
activation_proto,
));
} else { } else {
self.context ScriptObject::bare_object(self.context.gc_context)
.avm2 };
.push(ScriptObject::bare_object(self.context.gc_context));
} self.context.avm2.push(instance);
Ok(FrameControl::Continue) Ok(FrameControl::Continue)
} }
@ -1583,11 +1587,17 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn op_new_array(&mut self, num_args: u32) -> Result<FrameControl<'gc>, Error> { fn op_new_array(&mut self, num_args: u32) -> Result<FrameControl<'gc>, Error> {
let args = self.context.avm2.pop_args(num_args); let args = self.context.avm2.pop_args(num_args);
let array = ArrayStorage::from_args(&args[..]); let array = ArrayStorage::from_args(&args[..]);
let array_obj = ArrayObject::from_array( let mut array_proto = self.context.avm2.prototypes().array;
array, let array_constr = array_proto
self.context.avm2.system_prototypes.clone().unwrap().array, .get_property(
self.context.gc_context, array_proto,
); &QName::new(Namespace::public(), "constructor"),
self,
)?
.coerce_to_object(self)?;
let array_obj =
ArrayObject::from_array(array, array_constr, array_proto, self.context.gc_context);
self.context.avm2.push(array_obj); self.context.avm2.push(array_obj);

View File

@ -4,11 +4,11 @@ use crate::avm2::activation::Activation;
use crate::avm2::method::{Method, NativeMethod}; use crate::avm2::method::{Method, NativeMethod};
use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::object::{Object, ScriptObject, TObject}; use crate::avm2::object::{Object, ScriptObject, TObject};
use crate::avm2::scope::Scope;
use crate::avm2::script::TranslationUnit; use crate::avm2::script::TranslationUnit;
use crate::avm2::string::AvmString; use crate::avm2::string::AvmString;
use crate::avm2::traits::{Trait, TraitKind}; use crate::avm2::traits::{Trait, TraitKind};
use crate::avm2::{Avm2, Error}; use crate::avm2::Error;
use crate::context::UpdateContext;
use bitflags::bitflags; use bitflags::bitflags;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell, MutationContext};
use std::fmt; use std::fmt;
@ -42,17 +42,14 @@ bitflags! {
/// Parameters for the deriver are: /// Parameters for the deriver are:
/// ///
/// * `constr` - The class constructor that was called (or will be called) to /// * `constr` - The class constructor that was called (or will be called) to
/// construct this object. This may either be a base class (if we're creating /// construct this object. This must be the current class (using a base class
/// a derived class prototype object) or the current class (if we're creating /// will cause the wrong class to be read for traits).
/// an instance of the new class) /// * `proto` - The prototype attached to the class constructor.
/// * `activation` - This is the current AVM2 activation. /// * `activation` - This is the current AVM2 activation.
/// * `class` - The class we are attempting to derive.
/// * `scope` - The scope the class was declared in.
pub type DeriverFn = for<'gc> fn( pub type DeriverFn = for<'gc> fn(
Object<'gc>,
Object<'gc>, Object<'gc>,
&mut Activation<'_, 'gc, '_>, &mut Activation<'_, 'gc, '_>,
GcCell<'gc, Class<'gc>>,
Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error>; ) -> Result<Object<'gc>, Error>;
#[derive(Clone, Collect)] #[derive(Clone, Collect)]
@ -73,9 +70,8 @@ impl fmt::Debug for Deriver {
/// not exist, we default to `ScriptObject`. /// not exist, we default to `ScriptObject`.
pub fn implicit_deriver<'gc>( pub fn implicit_deriver<'gc>(
mut constr: Object<'gc>, mut constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let mut base_constr = Some(constr); let mut base_constr = Some(constr);
let mut base_class = constr.as_class(); let mut base_class = constr.as_class();
@ -94,7 +90,7 @@ pub fn implicit_deriver<'gc>(
} }
if let Some(base_deriver) = instance_deriver { if let Some(base_deriver) = instance_deriver {
base_deriver(constr, activation, class, scope) base_deriver(constr, proto, activation)
} else { } else {
let base_proto = constr let base_proto = constr
.get_property( .get_property(
@ -104,11 +100,10 @@ pub fn implicit_deriver<'gc>(
)? )?
.coerce_to_object(activation)?; .coerce_to_object(activation)?;
Ok(ScriptObject::prototype( Ok(ScriptObject::instance(
activation.context.gc_context, activation.context.gc_context,
constr,
base_proto, base_proto,
class,
scope,
)) ))
} }
} }
@ -349,8 +344,7 @@ impl<'gc> Class<'gc> {
&mut self, &mut self,
unit: TranslationUnit<'gc>, unit: TranslationUnit<'gc>,
class_index: u32, class_index: u32,
avm2: &mut Avm2<'gc>, uc: &mut UpdateContext<'_, 'gc, '_>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if self.traits_loaded { if self.traits_loaded {
return Ok(()); return Ok(());
@ -373,38 +367,32 @@ impl<'gc> Class<'gc> {
for abc_trait in abc_instance.traits.iter() { for abc_trait in abc_instance.traits.iter() {
self.instance_traits self.instance_traits
.push(Trait::from_abc_trait(unit, abc_trait, avm2, mc)?); .push(Trait::from_abc_trait(unit, abc_trait, uc)?);
} }
for abc_trait in abc_class.traits.iter() { for abc_trait in abc_class.traits.iter() {
self.class_traits self.class_traits
.push(Trait::from_abc_trait(unit, abc_trait, avm2, mc)?); .push(Trait::from_abc_trait(unit, abc_trait, uc)?);
} }
Ok(()) Ok(())
} }
pub fn from_method_body( pub fn from_method_body(
avm2: &mut Avm2<'gc>, uc: &mut UpdateContext<'_, 'gc, '_>,
mc: MutationContext<'gc, '_>,
translation_unit: TranslationUnit<'gc>, translation_unit: TranslationUnit<'gc>,
method: &AbcMethod, method: &AbcMethod,
body: &AbcMethodBody, body: &AbcMethodBody,
) -> Result<GcCell<'gc, Self>, Error> { ) -> Result<GcCell<'gc, Self>, Error> {
let name = translation_unit.pool_string(method.name.as_u30(), mc)?; let name = translation_unit.pool_string(method.name.as_u30(), uc.gc_context)?;
let mut traits = Vec::with_capacity(body.traits.len()); let mut traits = Vec::with_capacity(body.traits.len());
for trait_entry in &body.traits {
traits.push(Trait::from_abc_trait( for trait_entry in body.traits.iter() {
translation_unit, traits.push(Trait::from_abc_trait(translation_unit, trait_entry, uc)?);
trait_entry,
avm2,
mc,
)?);
} }
Ok(GcCell::allocate( Ok(GcCell::allocate(
mc, uc.gc_context,
Self { Self {
name: QName::dynamic_name(name), name: QName::dynamic_name(name),
super_class: None, super_class: None,

View File

@ -1,7 +1,7 @@
//! Application Domains //! Application Domains
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::names::{Multiname, QName}; use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::object::{ByteArrayObject, TObject}; use crate::avm2::object::{ByteArrayObject, TObject};
use crate::avm2::script::Script; use crate::avm2::script::Script;
use crate::avm2::value::Value; use crate::avm2::value::Value;
@ -25,7 +25,12 @@ struct DomainData<'gc> {
parent: Option<Domain<'gc>>, parent: Option<Domain<'gc>>,
/// The bytearray used for storing domain memory /// The bytearray used for storing domain memory
pub domain_memory: ByteArrayObject<'gc>, ///
/// Note: While this property is optional, it is not recommended to set it
/// to `None`. It is only optional to avoid an order-of-events problem in
/// player globals setup (we need a global domain to put globals into, but
/// that domain needs the bytearray global)
pub domain_memory: Option<ByteArrayObject<'gc>>,
} }
impl<'gc> Domain<'gc> { impl<'gc> Domain<'gc> {
@ -33,33 +38,41 @@ impl<'gc> Domain<'gc> {
/// ///
/// This is intended exclusively for creating the player globals domain, /// This is intended exclusively for creating the player globals domain,
/// hence the name. /// hence the name.
///
/// Note: the global domain will be created without valid domain memory.
/// You must initialize domain memory later on after the ByteArray class is
/// instantiated but before user code runs.
pub fn global_domain(mc: MutationContext<'gc, '_>) -> Domain<'gc> { pub fn global_domain(mc: MutationContext<'gc, '_>) -> Domain<'gc> {
let domain_memory = ByteArrayObject::new(mc, None);
domain_memory.as_bytearray_mut(mc).unwrap().set_length(1024);
Self(GcCell::allocate( Self(GcCell::allocate(
mc, mc,
DomainData { DomainData {
defs: HashMap::new(), defs: HashMap::new(),
parent: None, parent: None,
domain_memory, domain_memory: None,
}, },
)) ))
} }
/// Create a new domain with a given parent. /// Create a new domain with a given parent.
pub fn movie_domain(mc: MutationContext<'gc, '_>, parent: Domain<'gc>) -> Domain<'gc> { ///
let domain_memory = ByteArrayObject::new(mc, None); /// This function must not be called before the player globals have been
domain_memory.as_bytearray_mut(mc).unwrap().set_length(1024); /// fully allocated.
pub fn movie_domain(
Self(GcCell::allocate( activation: &mut Activation<'_, 'gc, '_>,
mc, parent: Domain<'gc>,
) -> Domain<'gc> {
let this = Self(GcCell::allocate(
activation.context.gc_context,
DomainData { DomainData {
defs: HashMap::new(), defs: HashMap::new(),
parent: Some(parent), parent: Some(parent),
domain_memory, domain_memory: None,
}, },
)) ));
this.init_default_domain_memory(activation).unwrap();
this
} }
/// Get the parent of this domain /// Get the parent of this domain
@ -159,7 +172,10 @@ impl<'gc> Domain<'gc> {
} }
pub fn domain_memory(&self) -> ByteArrayObject<'gc> { pub fn domain_memory(&self) -> ByteArrayObject<'gc> {
self.0.read().domain_memory self.0
.read()
.domain_memory
.expect("Domain must have valid memory at all times")
} }
pub fn set_domain_memory( pub fn set_domain_memory(
@ -167,6 +183,41 @@ impl<'gc> Domain<'gc> {
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
domain_memory: ByteArrayObject<'gc>, domain_memory: ByteArrayObject<'gc>,
) { ) {
self.0.write(mc).domain_memory = domain_memory self.0.write(mc).domain_memory = Some(domain_memory)
}
/// Allocate the default domain memory for this domain, if it does not
/// already exist.
///
/// This function is only necessary to be called for domains created via
/// `global_domain`. It will do nothing on already fully-initialized
/// domains.
pub fn init_default_domain_memory(
self,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<(), Error> {
let mut bytearray_proto = activation.avm2().prototypes().bytearray;
let bytearray_constr = bytearray_proto
.get_property(
bytearray_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
let domain_memory = ByteArrayObject::new(
activation.context.gc_context,
bytearray_constr,
Some(bytearray_proto),
);
domain_memory
.as_bytearray_mut(activation.context.gc_context)
.unwrap()
.set_length(1024);
let mut write = self.0.write(activation.context.gc_context);
write.domain_memory.get_or_insert(domain_memory);
Ok(())
} }
} }

View File

@ -82,7 +82,7 @@ impl<'gc> Executable<'gc> {
unbound_reciever: Option<Object<'gc>>, unbound_reciever: Option<Object<'gc>>,
arguments: &[Value<'gc>], arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
callee: Object<'gc>, callee: Object<'gc>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
match self { match self {
@ -93,7 +93,7 @@ impl<'gc> Executable<'gc> {
activation.context.reborrow(), activation.context.reborrow(),
scope, scope,
receiver, receiver,
base_proto, base_constr,
)?; )?;
nf(&mut activation, receiver, arguments) nf(&mut activation, receiver, arguments)
@ -106,7 +106,7 @@ impl<'gc> Executable<'gc> {
bm.scope, bm.scope,
receiver, receiver,
arguments, arguments,
base_proto, base_constr,
callee, callee,
)?; )?;

View File

@ -192,10 +192,12 @@ fn function<'gc>(
fn dynamic_class<'gc>( fn dynamic_class<'gc>(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
constr: Object<'gc>, constr: Object<'gc>,
class: GcCell<'gc, Class<'gc>>,
mut domain: Domain<'gc>, mut domain: Domain<'gc>,
script: Script<'gc>, script: Script<'gc>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let class = constr
.as_class()
.ok_or("Attempted to create builtin dynamic class without class on it's constructor!")?;
let name = class.read().name().clone(); let name = class.read().name().clone();
script script
@ -288,7 +290,7 @@ pub fn load_player_globals<'gc>(
domain: Domain<'gc>, domain: Domain<'gc>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let gs = DomainObject::from_domain(mc, None, domain); let gs = DomainObject::from_early_domain(mc, domain);
let script = Script::empty_script(mc, gs); let script = Script::empty_script(mc, gs);
// public / root package // public / root package
@ -296,18 +298,18 @@ pub fn load_player_globals<'gc>(
// We have to do this particular dance so that we have Object methods whose // We have to do this particular dance so that we have Object methods whose
// functions have call/apply in their prototypes, and that Function is also // functions have call/apply in their prototypes, and that Function is also
// a subclass of Object. // a subclass of Object.
let (object_proto, object_class) = object::create_proto(activation, gs); let object_proto = object::create_proto(activation);
let (fn_proto, fn_class) = function::create_proto(activation, gs, object_proto); let fn_proto = function::create_proto(activation, object_proto);
let object_constr = object::fill_proto(activation, object_proto, fn_proto)?; let object_constr = object::fill_proto(activation, gs, object_proto, fn_proto)?;
let function_constr = function::fill_proto(activation, fn_proto, object_constr); let function_constr = function::fill_proto(activation, gs, fn_proto, object_constr);
let (class_constr, class_proto, class_class) = let (class_constr, class_proto) =
class::create_class(activation, gs, object_constr, object_proto, fn_proto); class::create_class(activation, gs, object_constr, object_proto, fn_proto);
dynamic_class(mc, object_constr, object_class, domain, script)?; dynamic_class(mc, object_constr, domain, script)?;
dynamic_class(mc, function_constr, fn_class, domain, script)?; dynamic_class(mc, function_constr, domain, script)?;
dynamic_class(mc, class_constr, class_class, domain, script)?; dynamic_class(mc, class_constr, domain, script)?;
// At this point, we need at least a partial set of system prototypes in // At this point, we need at least a partial set of system prototypes in
// order to continue initializing the player. The rest of the prototypes // order to continue initializing the player. The rest of the prototypes
@ -425,6 +427,9 @@ pub fn load_player_globals<'gc>(
domain, domain,
script, script,
)?; )?;
gs.as_application_domain()
.unwrap()
.init_default_domain_memory(activation)?;
class( class(
activation, activation,
@ -436,7 +441,6 @@ pub fn load_player_globals<'gc>(
class( class(
activation, activation,
flash::utils::compression_algorithm::create_class(mc), flash::utils::compression_algorithm::create_class(mc),
implicit_deriver,
domain, domain,
script, script,
)?; )?;

View File

@ -96,15 +96,19 @@ pub fn build_array<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
array: ArrayStorage<'gc>, array: ArrayStorage<'gc>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
let mut array_proto = activation.avm2().prototypes().array;
let array_constr = array_proto
.get_property(
array_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
Ok(ArrayObject::from_array( Ok(ArrayObject::from_array(
array, array,
activation array_constr,
.context array_proto,
.avm2
.system_prototypes
.as_ref()
.map(|sp| sp.array)
.unwrap(),
activation.context.gc_context, activation.context.gc_context,
) )
.into()) .into())

View File

@ -8,7 +8,6 @@ use crate::avm2::object::{ClassObject, Object, ScriptObject, TObject};
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::GcCell;
/// Implements `Class`'s instance initializer. /// Implements `Class`'s instance initializer.
/// ///
@ -38,7 +37,7 @@ pub fn create_class<'gc>(
super_constr: Object<'gc>, super_constr: Object<'gc>,
super_proto: Object<'gc>, super_proto: Object<'gc>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) -> (Object<'gc>, Object<'gc>, GcCell<'gc, Class<'gc>>) { ) -> (Object<'gc>, Object<'gc>) {
let class_class = Class::new( let class_class = Class::new(
QName::new(Namespace::public(), "Class"), QName::new(Namespace::public(), "Class"),
Some(QName::new(Namespace::public(), "Object").into()), Some(QName::new(Namespace::public(), "Object").into()),
@ -48,20 +47,17 @@ pub fn create_class<'gc>(
); );
let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context);
let proto = ScriptObject::prototype( let proto = ScriptObject::object(activation.context.gc_context, super_proto);
activation.context.gc_context,
super_proto,
class_class,
Some(scope),
);
let constr = ClassObject::from_builtin_constr( let constr = ClassObject::from_builtin_constr(
activation.context.gc_context, activation.context.gc_context,
Some(super_constr), Some(super_constr),
class_class,
Some(scope),
proto, proto,
fn_proto, fn_proto,
) )
.unwrap(); .unwrap();
(constr, proto, class_class) (constr, proto)
} }

View File

@ -560,6 +560,15 @@ pub fn loader_info<'gc>(
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(dobj) = this.and_then(|this| this.as_display_object()) { if let Some(dobj) = this.and_then(|this| this.as_display_object()) {
let mut loaderinfo_proto = activation.avm2().prototypes().loaderinfo;
let loaderinfo_constr = loaderinfo_proto
.get_property(
loaderinfo_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
if let Some(root) = dobj.avm2_root(&mut activation.context) { if let Some(root) = dobj.avm2_root(&mut activation.context) {
if DisplayObject::ptr_eq(root, dobj) { if DisplayObject::ptr_eq(root, dobj) {
let movie = dobj.movie(); let movie = dobj.movie();
@ -568,7 +577,8 @@ pub fn loader_info<'gc>(
let obj = LoaderInfoObject::from_movie( let obj = LoaderInfoObject::from_movie(
movie, movie,
root, root,
activation.context.avm2.prototypes().loaderinfo, loaderinfo_constr,
loaderinfo_proto,
activation.context.gc_context, activation.context.gc_context,
)?; )?;
@ -581,7 +591,8 @@ pub fn loader_info<'gc>(
if DisplayObject::ptr_eq(dobj, activation.context.stage.into()) { if DisplayObject::ptr_eq(dobj, activation.context.stage.into()) {
return Ok(LoaderInfoObject::from_stage( return Ok(LoaderInfoObject::from_stage(
activation.context.avm2.prototypes().loaderinfo, loaderinfo_constr,
loaderinfo_proto,
activation.context.gc_context, activation.context.gc_context,
) )
.into()); .into());

View File

@ -66,11 +66,21 @@ pub fn application_domain<'gc>(
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(this) = this { if let Some(this) = this {
if let Some(loader_stream) = this.as_loader_stream() { if let Some(loader_stream) = this.as_loader_stream() {
let mut appdomain_proto = activation.avm2().prototypes().application_domain;
let appdomain_constr = appdomain_proto
.get_property(
appdomain_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
match &*loader_stream { match &*loader_stream {
LoaderStream::Stage => { LoaderStream::Stage => {
return Ok(DomainObject::from_domain( return Ok(DomainObject::from_domain(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm2.prototypes().application_domain), appdomain_constr,
Some(appdomain_proto),
activation.context.avm2.global_domain(), activation.context.avm2.global_domain(),
) )
.into()); .into());
@ -82,7 +92,8 @@ pub fn application_domain<'gc>(
.library_for_movie_mut(movie.clone()); .library_for_movie_mut(movie.clone());
return Ok(DomainObject::from_domain( return Ok(DomainObject::from_domain(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm2.prototypes().application_domain), appdomain_constr,
Some(appdomain_proto),
library.avm2_domain(), library.avm2_domain(),
) )
.into()); .into());
@ -290,9 +301,20 @@ pub fn bytes<'gc>(
return Err("Error: The stage's loader info does not have a bytestream".into()) return Err("Error: The stage's loader info does not have a bytestream".into())
} }
LoaderStream::Swf(root, _) => { LoaderStream::Swf(root, _) => {
let ba_proto = activation.context.avm2.prototypes().bytearray; let mut ba_proto = activation.context.avm2.prototypes().bytearray;
let ba = let ba_constr = ba_proto
ByteArrayObject::construct(activation.context.gc_context, Some(ba_proto)); .get_property(
ba_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
let ba = ByteArrayObject::construct(
activation.context.gc_context,
ba_constr,
Some(ba_proto),
);
let mut ba_write = ba.as_bytearray_mut(activation.context.gc_context).unwrap(); let mut ba_write = ba.as_bytearray_mut(activation.context.gc_context).unwrap();
// First, write a fake header corresponding to an // First, write a fake header corresponding to an

View File

@ -3,7 +3,6 @@
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::array::ArrayStorage; use crate::avm2::array::ArrayStorage;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::globals::flash::display::{framelabel, scene};
use crate::avm2::method::{Method, NativeMethod}; use crate::avm2::method::{Method, NativeMethod};
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{ArrayObject, Object, TObject}; use crate::avm2::object::{ArrayObject, Object, TObject};
@ -163,26 +162,42 @@ fn labels_for_scene<'gc>(
start: scene_start, start: scene_start,
length: scene_length, length: scene_length,
} = scene; } = scene;
let frame_label_proto = activation.context.avm2.prototypes().framelabel; let mut frame_label_proto = activation.context.avm2.prototypes().framelabel;
let labels = mc.labels_in_range(*scene_start, scene_start + scene_length); let labels = mc.labels_in_range(*scene_start, scene_start + scene_length);
let mut frame_labels = Vec::with_capacity(labels.len()); let mut frame_labels = Vec::with_capacity(labels.len());
let frame_label_constr = frame_label_proto
.get_property(
frame_label_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
for (name, frame) in labels { for (name, frame) in labels {
let name: Value<'gc> = AvmString::new(activation.context.gc_context, name).into(); let name: Value<'gc> = AvmString::new(activation.context.gc_context, name).into();
let local_frame = frame - scene_start + 1; let local_frame = frame - scene_start + 1;
let args = [name, local_frame.into()]; let args = [name, local_frame.into()];
let frame_label = frame_label_proto.construct(activation, &args)?; let frame_label = frame_label_constr.construct(activation, &args)?;
framelabel::instance_init(activation, Some(frame_label), &args)?;
frame_labels.push(Some(frame_label.into())); frame_labels.push(Some(frame_label.into()));
} }
let mut array_proto = activation.avm2().prototypes().array;
let array_constr = array_proto
.get_property(
array_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
Ok(( Ok((
scene_name.to_string(), scene_name.to_string(),
*scene_length, *scene_length,
ArrayObject::from_array( ArrayObject::from_array(
ArrayStorage::from_storage(frame_labels), ArrayStorage::from_storage(frame_labels),
activation.context.avm2.prototypes().array, array_constr,
array_proto,
activation.context.gc_context, activation.context.gc_context,
), ),
)) ))
@ -225,16 +240,21 @@ pub fn current_scene<'gc>(
length: mc.total_frames(), length: mc.total_frames(),
}); });
let (scene_name, scene_length, scene_labels) = labels_for_scene(activation, mc, &scene)?; let (scene_name, scene_length, scene_labels) = labels_for_scene(activation, mc, &scene)?;
let scene_proto = activation.context.avm2.prototypes().scene; let mut scene_proto = activation.context.avm2.prototypes().scene;
let scene_constr = scene_proto
.get_property(
scene_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
let args = [ let args = [
AvmString::new(activation.context.gc_context, scene_name).into(), AvmString::new(activation.context.gc_context, scene_name).into(),
scene_labels.into(), scene_labels.into(),
scene_length.into(), scene_length.into(),
]; ];
let scene = scene_proto.construct(activation, &args)?; let scene = scene_constr.construct(activation, &args)?;
scene::instance_init(activation, Some(scene), &args)?;
return Ok(scene.into()); return Ok(scene.into());
} }
@ -265,23 +285,38 @@ pub fn scenes<'gc>(
for scene in mc_scenes { for scene in mc_scenes {
let (scene_name, scene_length, scene_labels) = let (scene_name, scene_length, scene_labels) =
labels_for_scene(activation, mc, &scene)?; labels_for_scene(activation, mc, &scene)?;
let scene_proto = activation.context.avm2.prototypes().scene; let mut scene_proto = activation.context.avm2.prototypes().scene;
let scene_constr = scene_proto
.get_property(
scene_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
let args = [ let args = [
AvmString::new(activation.context.gc_context, scene_name).into(), AvmString::new(activation.context.gc_context, scene_name).into(),
scene_labels.into(), scene_labels.into(),
scene_length.into(), scene_length.into(),
]; ];
let scene = scene_proto.construct(activation, &args)?; let scene = scene_constr.construct(activation, &args)?;
scene::instance_init(activation, Some(scene), &args)?;
scene_objects.push(Some(scene.into())); scene_objects.push(Some(scene.into()));
} }
let mut array_proto = activation.avm2().prototypes().array;
let array_constr = array_proto
.get_property(
array_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
return Ok(ArrayObject::from_array( return Ok(ArrayObject::from_array(
ArrayStorage::from_storage(scene_objects), ArrayStorage::from_storage(scene_objects),
activation.context.avm2.prototypes().array, array_constr,
array_proto,
activation.context.gc_context, activation.context.gc_context,
) )
.into()); .into());

View File

@ -62,10 +62,20 @@ pub fn graphics<'gc>(
activation, activation,
)? { )? {
Value::Undefined | Value::Null => { Value::Undefined | Value::Null => {
let graphics_proto = activation.context.avm2.prototypes().graphics; let mut graphics_proto = activation.context.avm2.prototypes().graphics;
let graphics_constr = graphics_proto
.get_property(
graphics_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)
.and_then(|v| v.coerce_to_object(activation))
.expect("Video proto needs constr");
let graphics = Value::from(StageObject::for_display_object( let graphics = Value::from(StageObject::for_display_object(
activation.context.gc_context, activation.context.gc_context,
dobj, dobj,
graphics_constr,
graphics_proto, graphics_proto,
)); ));
this.set_property( this.set_property(

View File

@ -48,10 +48,20 @@ pub fn graphics<'gc>(
activation, activation,
)? { )? {
Value::Undefined | Value::Null => { Value::Undefined | Value::Null => {
let graphics_proto = activation.context.avm2.prototypes().graphics; let mut graphics_proto = activation.context.avm2.prototypes().graphics;
let graphics_constr = graphics_proto
.get_property(
graphics_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)
.and_then(|v| v.coerce_to_object(activation))
.expect("Video proto needs constr");
let graphics = Value::from(StageObject::for_display_object( let graphics = Value::from(StageObject::for_display_object(
activation.context.gc_context, activation.context.gc_context,
dobj, dobj,
graphics_constr,
graphics_proto, graphics_proto,
)); ));
this.set_property( this.set_property(

View File

@ -142,10 +142,18 @@ pub fn clone<'gc>(
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(evt) = this.unwrap().as_event() { if let Some(evt) = this.unwrap().as_event() {
let evt_proto = activation.avm2().system_prototypes.as_ref().unwrap().event; let mut evt_proto = activation.avm2().prototypes().event;
let evt_constr = evt_proto
.get_property(
evt_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
return Ok(EventObject::from_event( return Ok(EventObject::from_event(
activation.context.gc_context, activation.context.gc_context,
evt_constr,
Some(evt_proto), Some(evt_proto),
evt.clone(), evt.clone(),
) )

View File

@ -10,9 +10,17 @@ fn create_point<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
coords: (f64, f64), coords: (f64, f64),
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
let proto = activation.context.avm2.prototypes().point; let mut point_proto = activation.context.avm2.prototypes().point;
let point_constr = point_proto
.get_property(
point_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
let args = [Value::Number(coords.0), Value::Number(coords.1)]; let args = [Value::Number(coords.0), Value::Number(coords.1)];
let new_point = proto.construct(activation, &args)?; let new_point = point_constr.construct(activation, &args)?;
instance_init(activation, Some(new_point), &args)?; instance_init(activation, Some(new_point), &args)?;
Ok(new_point.into()) Ok(new_point.into())

View File

@ -39,10 +39,20 @@ pub fn current_domain<'gc>(
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
let globals = activation.scope().map(|s| s.read().globals()); let globals = activation.scope().map(|s| s.read().globals());
let appdomain = globals.and_then(|g| g.as_application_domain()); let appdomain = globals.and_then(|g| g.as_application_domain());
let mut appdomain_proto = activation.avm2().prototypes().application_domain;
let appdomain_constr = appdomain_proto
.get_property(
appdomain_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
if let Some(appdomain) = appdomain { if let Some(appdomain) = appdomain {
return Ok(DomainObject::from_domain( return Ok(DomainObject::from_domain(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm2.prototypes().application_domain), appdomain_constr,
Some(appdomain_proto),
appdomain, appdomain,
) )
.into()); .into());
@ -59,9 +69,19 @@ pub fn parent_domain<'gc>(
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(appdomain) = this.and_then(|this| this.as_application_domain()) { if let Some(appdomain) = this.and_then(|this| this.as_application_domain()) {
if let Some(parent_domain) = appdomain.parent_domain() { if let Some(parent_domain) = appdomain.parent_domain() {
let mut appdomain_proto = activation.avm2().prototypes().application_domain;
let appdomain_constr = appdomain_proto
.get_property(
appdomain_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
return Ok(DomainObject::from_domain( return Ok(DomainObject::from_domain(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm2.prototypes().application_domain), appdomain_constr,
Some(appdomain_proto),
parent_domain, parent_domain,
) )
.into()); .into());

View File

@ -9,7 +9,6 @@ use crate::avm2::object::{ClassObject, FunctionObject, Object, ScriptObject, TOb
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::GcCell;
/// Implements `Function`'s instance initializer. /// Implements `Function`'s instance initializer.
pub fn instance_init<'gc>( pub fn instance_init<'gc>(
@ -90,12 +89,21 @@ fn apply<'gc>(
/// Create Function prototype. /// Create Function prototype.
/// ///
/// This function creates a suitable class and object prototype attached to it. /// This function creates a suitable prototype and returns it.
pub fn create_proto<'gc>( pub fn create_proto<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
globals: Object<'gc>,
super_proto: Object<'gc>, super_proto: Object<'gc>,
) -> (Object<'gc>, GcCell<'gc, Class<'gc>>) { ) -> Object<'gc> {
ScriptObject::object(activation.context.gc_context, super_proto)
}
/// Fill `Function.prototype` and allocate it's constructor.
pub fn fill_proto<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
globals: Object<'gc>,
mut function_proto: Object<'gc>,
super_constr: Object<'gc>,
) -> Object<'gc> {
let function_class = Class::new( let function_class = Class::new(
QName::new(Namespace::public(), "Function"), QName::new(Namespace::public(), "Function"),
Some(QName::new(Namespace::public(), "Object").into()), Some(QName::new(Namespace::public(), "Object").into()),
@ -104,22 +112,7 @@ pub fn create_proto<'gc>(
activation.context.gc_context, activation.context.gc_context,
); );
let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context);
let function_proto = ScriptObject::prototype(
activation.context.gc_context,
super_proto,
function_class,
Some(scope),
);
(function_proto, function_class)
}
/// Fill `Function.prototype` and allocate it's constructor.
pub fn fill_proto<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
mut function_proto: Object<'gc>,
super_class: Object<'gc>,
) -> Object<'gc> {
function_proto.install_method( function_proto.install_method(
activation.context.gc_context, activation.context.gc_context,
QName::new(Namespace::as3_namespace(), "call"), QName::new(Namespace::as3_namespace(), "call"),
@ -135,7 +128,9 @@ pub fn fill_proto<'gc>(
ClassObject::from_builtin_constr( ClassObject::from_builtin_constr(
activation.context.gc_context, activation.context.gc_context,
Some(super_class), Some(super_constr),
function_class,
Some(scope),
function_proto, function_proto,
function_proto, function_proto,
) )

View File

@ -9,7 +9,6 @@ use crate::avm2::scope::Scope;
use crate::avm2::traits::Trait; use crate::avm2::traits::Trait;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::GcCell;
/// Implements `Object`'s instance initializer. /// Implements `Object`'s instance initializer.
pub fn instance_init<'gc>( pub fn instance_init<'gc>(
@ -155,38 +154,8 @@ pub fn set_property_is_enumerable<'gc>(
/// This function creates a suitable class and object prototype attached to it, /// This function creates a suitable class and object prototype attached to it,
/// but does not actually fill it with methods. That requires a valid function /// but does not actually fill it with methods. That requires a valid function
/// prototype, and is thus done by `fill_proto` below. /// prototype, and is thus done by `fill_proto` below.
pub fn create_proto<'gc>( pub fn create_proto<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Object<'gc> {
activation: &mut Activation<'_, 'gc, '_>, ScriptObject::bare_object(activation.context.gc_context)
globals: Object<'gc>,
) -> (Object<'gc>, GcCell<'gc, Class<'gc>>) {
let object_class = Class::new(
QName::new(Namespace::public(), "Object"),
None,
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
activation.context.gc_context,
);
let mut write = object_class.write(activation.context.gc_context);
write.define_class_trait(Trait::from_const(
QName::new(Namespace::public(), "length"),
QName::new(Namespace::public(), "int").into(),
None,
));
// Fixed traits (in AS3 namespace)
const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethod)] = &[
("hasOwnProperty", has_own_property),
("isPrototypeOf", is_prototype_of),
("propertyIsEnumerable", property_is_enumerable),
];
write.define_as3_builtin_instance_methods(PUBLIC_INSTANCE_METHODS);
let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context);
let proto =
ScriptObject::bare_prototype(activation.context.gc_context, object_class, Some(scope));
(proto, object_class)
} }
/// Finish constructing `Object.prototype`, and also construct `Object`. /// Finish constructing `Object.prototype`, and also construct `Object`.
@ -200,10 +169,12 @@ pub fn create_proto<'gc>(
/// bare objects for both and let this function fill Object for you. /// bare objects for both and let this function fill Object for you.
pub fn fill_proto<'gc>( pub fn fill_proto<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
globals: Object<'gc>,
mut object_proto: Object<'gc>, mut object_proto: Object<'gc>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let gc_context = activation.context.gc_context; let gc_context = activation.context.gc_context;
object_proto.install_dynamic_property( object_proto.install_dynamic_property(
gc_context, gc_context,
QName::new(Namespace::public(), "hasOwnProperty"), QName::new(Namespace::public(), "hasOwnProperty"),
@ -240,6 +211,39 @@ pub fn fill_proto<'gc>(
FunctionObject::from_builtin(gc_context, value_of, fn_proto).into(), FunctionObject::from_builtin(gc_context, value_of, fn_proto).into(),
)?; )?;
let object_constr = ClassObject::from_builtin_constr(gc_context, None, object_proto, fn_proto)?; let object_class = Class::new(
Ok(object_constr) QName::new(Namespace::public(), "Object"),
None,
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
gc_context,
);
let mut write = object_class.write(gc_context);
write.define_class_trait(Trait::from_const(
QName::new(Namespace::public(), "length"),
QName::new(Namespace::public(), "int").into(),
None,
));
// Fixed traits (in AS3 namespace)
const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethod)] = &[
("hasOwnProperty", has_own_property),
("isPrototypeOf", is_prototype_of),
("propertyIsEnumerable", property_is_enumerable),
];
write.define_as3_builtin_instance_methods(PUBLIC_INSTANCE_METHODS);
drop(write);
let scope = Scope::push_scope(globals.get_scope(), globals, gc_context);
ClassObject::from_builtin_constr(
gc_context,
None,
object_class,
Some(scope),
object_proto,
fn_proto,
)
} }

View File

@ -209,15 +209,19 @@ pub fn exec<'gc>(
None => return Ok(Value::Null), None => return Ok(Value::Null),
}; };
let mut regexp_proto = activation.avm2().prototypes().array;
let regexp_constr = regexp_proto
.get_property(
regexp_proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
let object = ArrayObject::from_array( let object = ArrayObject::from_array(
storage, storage,
activation regexp_constr,
.context regexp_proto,
.avm2
.system_prototypes
.as_ref()
.map(|sp| sp.array)
.unwrap(),
activation.context.gc_context, activation.context.gc_context,
); );

View File

@ -113,17 +113,25 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Retrieve the base prototype that a particular QName trait is defined in. /// Retrieve the base class constructor that a particular QName trait is
/// defined in.
///
/// Must be called on a class constructor; will error out if called on
/// anything else.
/// ///
/// This function returns `None` for non-trait properties, such as actually /// This function returns `None` for non-trait properties, such as actually
/// defined prototype methods for ES3-style classes. /// defined prototype methods for ES3-style classes.
fn get_base_proto(self, name: &QName<'gc>) -> Result<Option<Object<'gc>>, Error> { fn find_base_constr_for_trait(self, name: &QName<'gc>) -> Result<Option<Object<'gc>>, Error> {
if self.provides_trait(name)? { let class = self
.as_class()
.ok_or("Cannot get base traits on non-class object")?;
if class.read().has_instance_trait(name) {
return Ok(Some(self.into())); return Ok(Some(self.into()));
} }
if let Some(proto) = self.proto() { if let Some(base) = self.base_class_constr() {
return proto.get_base_proto(name); return base.find_base_constr_for_trait(name);
} }
Ok(None) Ok(None)
@ -305,26 +313,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// prototype chain bearing this name is malformed in some way. /// prototype chain bearing this name is malformed in some way.
fn get_trait_slot(self, id: u32) -> Result<Option<Trait<'gc>>, Error>; fn get_trait_slot(self, id: u32) -> Result<Option<Trait<'gc>>, Error>;
/// Populate a list of traits that this object provides for a given name.
///
/// This function yields traits for class constructors and prototypes, but
/// not instances. For resolving traits for normal `TObject` methods, use
/// `get_trait` and `has_trait` as it will tell you if the current object
/// has a given trait.
fn get_provided_trait(
&self,
name: &QName<'gc>,
known_traits: &mut Vec<Trait<'gc>>,
) -> Result<(), Error>;
/// Populate a list of traits that this object provides for a given slot.
///
/// This function yields traits for class constructors and prototypes, but
/// not instances. For resolving traits for normal `TObject` methods, use
/// `get_trait` and `has_trait` as it will tell you if the current object
/// has a given trait.
fn get_provided_trait_slot(&self, id: u32) -> Result<Option<Trait<'gc>>, Error>;
/// Retrieves the scope chain of the object at time of its creation. /// Retrieves the scope chain of the object at time of its creation.
/// ///
/// The scope chain is used to determine the starting scope stack when an /// The scope chain is used to determine the starting scope stack when an
@ -397,11 +385,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Returns true if an object has one or more traits of a given name. /// Returns true if an object has one or more traits of a given name.
fn has_trait(self, name: &QName<'gc>) -> Result<bool, Error>; fn has_trait(self, name: &QName<'gc>) -> Result<bool, Error>;
/// Returns true if an object is part of a class that defines a trait of a
/// given name on itself (as opposed to merely inheriting a superclass
/// trait.)
fn provides_trait(self, name: &QName<'gc>) -> Result<bool, Error>;
/// Indicates whether or not a property or *instantiated* trait exists on /// Indicates whether or not a property or *instantiated* trait exists on
/// an object and is not part of the prototype chain. /// an object and is not part of the prototype chain.
/// ///
@ -732,49 +715,200 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
_reciever: Option<Object<'gc>>, _reciever: Option<Object<'gc>>,
_arguments: &[Value<'gc>], _arguments: &[Value<'gc>],
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
_base_proto: Option<Object<'gc>>, _base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
Err("Object is not callable".into()) Err("Object is not callable".into())
} }
/// Construct a host object of some kind and return its cell. /// Call an instance method by name.
///
/// Intended to be called on the current base constructor used for
/// searching for traits. That constructor's base classes will be searched
/// for an appropriately named method trait, and if found, the method will
/// be called with the given reciever, arguments and new ancestor
/// constructor.
fn call_instance_method(
self,
name: &QName<'gc>,
reciever: Option<Object<'gc>>,
arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Value<'gc>, Error> {
let constr_with_trait = self.find_base_constr_for_trait(&name)?.ok_or_else(|| {
format!(
"Attempted to supercall method {:?}, which does not exist",
name
)
})?;
let mut class_traits = Vec::new();
constr_with_trait
.as_class()
.unwrap()
.read()
.lookup_instance_traits(name, &mut class_traits)?;
let base_trait = class_traits.first().ok_or_else(|| {
format!(
"Attempted to supercall method {:?}, which does not exist",
name
)
})?;
let scope = constr_with_trait.get_scope();
if let TraitKind::Method { method, .. } = base_trait.kind() {
let callee = FunctionObject::from_method(
activation.context.gc_context,
method.clone(),
scope,
activation.avm2().prototypes().function,
reciever,
);
callee.call(reciever, arguments, activation, Some(constr_with_trait))
} else {
Err(format!(
"Attempted to supercall method {:?}, which does not exist",
name
)
.into())
}
}
/// Call an instance getter by name.
///
/// Intended to be called on the current base constructor used for
/// searching for traits. That constructor's base classes will be searched
/// for an appropriately named getter trait, and if found, the getter will
/// be called with the given reciever, arguments and new ancestor
/// constructor.
fn call_instance_getter(
self,
name: &QName<'gc>,
reciever: Option<Object<'gc>>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Value<'gc>, Error> {
let constr_with_trait = self.find_base_constr_for_trait(&name)?.ok_or_else(|| {
format!(
"Attempted to supercall method {:?}, which does not exist",
name
)
})?;
let mut class_traits = Vec::new();
constr_with_trait
.as_class()
.unwrap()
.read()
.lookup_instance_traits(name, &mut class_traits)?;
let base_trait = class_traits.first().ok_or_else(|| {
format!(
"Attempted to supercall method {:?}, which does not exist",
name
)
})?;
let scope = constr_with_trait.get_scope();
if let TraitKind::Getter { method, .. } = base_trait.kind() {
let callee = FunctionObject::from_method(
activation.context.gc_context,
method.clone(),
scope,
activation.avm2().prototypes().function,
reciever,
);
callee.call(reciever, &[], activation, Some(constr_with_trait))
} else {
Err(format!(
"Attempted to supercall getter for {:?}, which does not exist",
name
)
.into())
}
}
/// Call an instance setter by name.
///
/// Intended to be called on the current base constructor used for
/// searching for traits. That constructor's base classes will be searched
/// for an appropriately named setter trait, and if found, the setter will
/// be called with the given reciever, arguments and new ancestor
/// constructor.
fn call_instance_setter(
self,
name: &QName<'gc>,
value: Value<'gc>,
reciever: Option<Object<'gc>>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<(), Error> {
let constr_with_trait = self.find_base_constr_for_trait(&name)?.ok_or_else(|| {
format!(
"Attempted to supercall method {:?}, which does not exist",
name
)
})?;
let mut class_traits = Vec::new();
constr_with_trait
.as_class()
.unwrap()
.read()
.lookup_instance_traits(name, &mut class_traits)?;
let base_trait = class_traits.first().ok_or_else(|| {
format!(
"Attempted to supercall method {:?}, which does not exist",
name
)
})?;
let scope = constr_with_trait.get_scope();
if let TraitKind::Setter { method, .. } = base_trait.kind() {
let callee = FunctionObject::from_method(
activation.context.gc_context,
method.clone(),
scope,
activation.avm2().prototypes().function,
reciever,
);
callee.call(reciever, &[value], activation, Some(constr_with_trait))?;
Ok(())
} else {
Err(format!(
"Attempted to supercall setter for {:?}, which does not exist",
name
)
.into())
}
}
/// Construct a Class or Function and return an instance of it.
/// ///
/// As the first step in object construction, the `construct` method is /// As the first step in object construction, the `construct` method is
/// called on the prototype to create a new object. The prototype may /// called on the constructor to create a new object. The constructor is
/// construct any object implementation it wants, however, it's expected /// then expected to perform the following steps, in order:
/// to produce a like `TObject` implementor with itself as the new object's
/// proto.
/// ///
/// After construction, the constructor function is `call`ed with the new /// 1. Allocate the instance object. If the constructor is a `Class`, then
/// object as `this` to initialize the object. /// use the class's instance deriver to allocate the object. Otherwise,
/// /// allocate it as the same type as the constructor's explicit `prototype`.
/// `construct`ed objects should instantiate instance traits of the class /// 2. Associate the instance object with the constructor's explicit
/// that this prototype represents. /// `prototype`.
/// /// 3. If the instance has traits, install them at this time.
/// The arguments passed to the constructor are provided here; however, all /// 4. Call the constructor method with the newly-allocated object as
/// object construction should happen in `call`, not `new`. `new` exists /// reciever. For `Function`s, this is just the function's method.
/// purely so that host objects can be constructed by the VM. /// 5. Yield the allocated object. (The return values of constructors are
/// ignored.)
fn construct( fn construct(
&self, self,
activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Object<'gc>, Error>; ) -> Result<Object<'gc>, Error> {
Err("Object is not constructable".into())
}
/// Construct a host object prototype of some kind and return it. /// Construct a host object prototype of some kind and return it.
/// ///
/// This is called specifically to construct prototypes. The primary /// This is called specifically to allocate old-style ES3 instances. The
/// difference is that a new class and scope closure are defined here. /// returned object should have no properties upon it.
/// Objects constructed from the new prototype should use that new class fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error>;
/// and scope closure when instantiating non-prototype traits.
///
/// Unlike `construct`, `derive`d objects should *not* instantiate instance
/// traits.
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error>;
/// Determine the type of primitive coercion this object would prefer, in /// Determine the type of primitive coercion this object would prefer, in
/// the case that there is no obvious reason to prefer one type over the /// the case that there is no obvious reason to prefer one type over the
@ -796,7 +930,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// coercions. /// coercions.
fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
let class_name = self let class_name = self
.as_proto_class() .as_class()
.map(|c| c.read().name().local_name()) .map(|c| c.read().name().local_name())
.unwrap_or_else(|| "Object".into()); .unwrap_or_else(|| "Object".into());
@ -813,7 +947,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// of the class that created this object). /// of the class that created this object).
fn to_locale_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn to_locale_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
let class_name = self let class_name = self
.as_proto_class() .as_class()
.map(|c| c.read().name().local_name()) .map(|c| c.read().name().local_name())
.unwrap_or_else(|| "Object".into()); .unwrap_or_else(|| "Object".into());
@ -892,28 +1026,21 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Get this object's `Class`, if it has one. /// Get this object's `Class`, if it has one.
fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>>; fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>>;
/// Get this object's constructor, if it has one.
fn as_constr(&self) -> Option<Object<'gc>>;
/// Associate the object with a particular constructor.
///
/// This turns the object into an instance of that class. It should only be
/// used in situations where the object cannot be made an instance of the
/// class at allocation time, such as during early runtime setup.
fn set_constr(self, mc: MutationContext<'gc, '_>, constr: Object<'gc>);
/// Get the base class constructor of this object. /// Get the base class constructor of this object.
fn base_class_constr(self) -> Option<Object<'gc>> { fn base_class_constr(self) -> Option<Object<'gc>> {
None None
} }
/// Get this object's `Class`, or any `Class` on its prototype chain.
///
/// This only yields `None` for bare objects.
fn as_proto_class(&self) -> Option<GcCell<'gc, Class<'gc>>> {
let mut class = self.as_class();
while class.is_none() {
if let Some(proto) = self.proto() {
class = proto.as_class();
} else {
return None;
}
}
class
}
/// Get this object's `Executable`, if it has one. /// Get this object's `Executable`, if it has one.
fn as_executable(&self) -> Option<Executable<'gc>> { fn as_executable(&self) -> Option<Executable<'gc>> {
None None

View File

@ -17,20 +17,11 @@ use std::cell::{Ref, RefMut};
/// A class instance deriver that constructs array objects. /// A class instance deriver that constructs array objects.
pub fn array_deriver<'gc>( pub fn array_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr ArrayObject::derive(constr, proto, activation.context.gc_context)
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
ArrayObject::derive(base_proto, activation.context.gc_context, class, scope)
} }
/// An Object which stores numerical properties in an array. /// An Object which stores numerical properties in an array.
@ -50,8 +41,13 @@ pub struct ArrayObjectData<'gc> {
impl<'gc> ArrayObject<'gc> { impl<'gc> ArrayObject<'gc> {
/// Construct a fresh array. /// Construct a fresh array.
pub fn construct(base_proto: Object<'gc>, mc: MutationContext<'gc, '_>) -> Object<'gc> { pub fn construct(
let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass); constr: Object<'gc>,
proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> Object<'gc> {
let base =
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
ArrayObject(GcCell::allocate( ArrayObject(GcCell::allocate(
mc, mc,
@ -65,15 +61,12 @@ impl<'gc> ArrayObject<'gc> {
/// Construct a primitive subclass. /// Construct a primitive subclass.
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(ArrayObject(GcCell::allocate( Ok(ArrayObject(GcCell::allocate(
mc, mc,
@ -88,10 +81,12 @@ impl<'gc> ArrayObject<'gc> {
/// Wrap an existing array in an object. /// Wrap an existing array in an object.
pub fn from_array( pub fn from_array(
array: ArrayStorage<'gc>, array: ArrayStorage<'gc>,
base_proto: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
) -> Object<'gc> { ) -> Object<'gc> {
let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass); let base =
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
ArrayObject(GcCell::allocate(mc, ArrayObjectData { base, array })).into() ArrayObject(GcCell::allocate(mc, ArrayObjectData { base, array })).into()
} }
@ -242,11 +237,7 @@ impl<'gc> TObject<'gc> for ArrayObject<'gc> {
Some(RefMut::map(self.0.write(mc), |aod| &mut aod.array)) Some(RefMut::map(self.0.write(mc), |aod| &mut aod.array))
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ArrayObject(*self); let this: Object<'gc> = Object::ArrayObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -259,26 +250,4 @@ impl<'gc> TObject<'gc> for ArrayObject<'gc> {
)) ))
.into()) .into())
} }
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ArrayObject(*self);
let base = ScriptObjectData::base_new(
Some(this),
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(ArrayObject(GcCell::allocate(
activation.context.gc_context,
ArrayObjectData {
base,
array: ArrayStorage::new(0),
},
))
.into())
}
} }

View File

@ -15,20 +15,11 @@ use std::cell::{Ref, RefMut};
/// A class instance deriver that constructs ByteArray objects. /// A class instance deriver that constructs ByteArray objects.
pub fn bytearray_deriver<'gc>( pub fn bytearray_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr ByteArrayObject::derive(constr, proto, activation.context.gc_context)
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
ByteArrayObject::derive(base_proto, activation.context.gc_context, class, scope)
} }
#[derive(Clone, Collect, Debug, Copy)] #[derive(Clone, Collect, Debug, Copy)]
@ -47,9 +38,10 @@ pub struct ByteArrayObjectData<'gc> {
impl<'gc> ByteArrayObject<'gc> { impl<'gc> ByteArrayObject<'gc> {
pub fn new( pub fn new(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
base_proto: Option<Object<'gc>>, constr: Object<'gc>,
proto: Option<Object<'gc>>,
) -> ByteArrayObject<'gc> { ) -> ByteArrayObject<'gc> {
let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(proto, ScriptObjectClass::ClassInstance(constr));
ByteArrayObject(GcCell::allocate( ByteArrayObject(GcCell::allocate(
mc, mc,
@ -60,20 +52,21 @@ impl<'gc> ByteArrayObject<'gc> {
)) ))
} }
pub fn construct(mc: MutationContext<'gc, '_>, base_proto: Option<Object<'gc>>) -> Object<'gc> { pub fn construct(
Self::new(mc, base_proto).into() mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Option<Object<'gc>>,
) -> Object<'gc> {
Self::new(mc, constr, base_proto).into()
} }
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(ByteArrayObject(GcCell::allocate( Ok(ByteArrayObject(GcCell::allocate(
mc, mc,
@ -219,29 +212,9 @@ impl<'gc> TObject<'gc> for ByteArrayObject<'gc> {
self.0.read().base.resolve_any_trait(local_name) self.0.read().base.resolve_any_trait(local_name)
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ByteArrayObject(*self); let this: Object<'gc> = Object::ByteArrayObject(*self);
Ok(ByteArrayObject::construct( let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
activation.context.gc_context,
Some(this),
))
}
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ByteArrayObject(*self);
let base = ScriptObjectData::base_new(
Some(this),
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(ByteArrayObject(GcCell::allocate( Ok(ByteArrayObject(GcCell::allocate(
activation.context.gc_context, activation.context.gc_context,

View File

@ -58,11 +58,20 @@ impl<'gc> ClassObject<'gc> {
base_class_constr: Option<Object<'gc>>, base_class_constr: Option<Object<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<(Object<'gc>, Object<'gc>), Error> { ) -> Result<(Object<'gc>, Object<'gc>), Error> {
let class_proto = if let Some(base_class_constr) = base_class_constr { //TODO: Class prototypes are *not* instances of their class and should
//not be allocated by a deriver, but instead should be regular objects
let class_proto = if let Some(mut base_class_constr) = base_class_constr {
let base_proto = base_class_constr
.get_property(
base_class_constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
let derive = class.read().instance_deriver(); let derive = class.read().instance_deriver();
derive(base_class_constr, activation, class, scope)? derive(base_class_constr, base_proto, activation)?
} else { } else {
ScriptObject::bare_prototype(activation.context.gc_context, class, scope) ScriptObject::bare_object(activation.context.gc_context)
}; };
ClassObject::from_class_and_proto(activation, class, base_class_constr, class_proto, scope) ClassObject::from_class_and_proto(activation, class, base_class_constr, class_proto, scope)
@ -160,14 +169,11 @@ impl<'gc> ClassObject<'gc> {
pub fn from_builtin_constr( pub fn from_builtin_constr(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
base_class_constr: Option<Object<'gc>>, base_class_constr: Option<Object<'gc>>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
mut prototype: Object<'gc>, mut prototype: Object<'gc>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let scope = prototype.get_scope();
let class: Result<_, Error> = prototype
.as_class()
.ok_or_else(|| "Cannot construct builtin type without a class".into());
let class = class?;
let instance_constr = let instance_constr =
Executable::from_method(class.read().instance_init(), scope, None, mc); Executable::from_method(class.read().instance_init(), scope, None, mc);
let mut base: Object<'gc> = ClassObject(GcCell::allocate( let mut base: Object<'gc> = ClassObject(GcCell::allocate(
@ -225,31 +231,38 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
receiver: Option<Object<'gc>>, receiver: Option<Object<'gc>>,
arguments: &[Value<'gc>], arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
let instance_constr = self.0.read().instance_constr; let instance_constr = self.0.read().instance_constr;
instance_constr.exec(receiver, arguments, activation, base_proto, self.into()) instance_constr.exec(receiver, arguments, activation, base_constr, self.into())
} }
fn construct( fn construct(
&self, mut self,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>], arguments: &[Value<'gc>],
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
Ok(ClassObject(GcCell::allocate( let class = self.as_class().ok_or("Cannot construct classless class!")?;
activation.context.gc_context, let deriver = class.read().instance_deriver();
self.0.read().clone(), let constr: Object<'gc> = self.into();
)) let prototype = self
.into()) .get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
let instance = deriver(constr, prototype, activation)?;
let instance_constr = self.0.read().instance_constr;
instance_constr.exec(Some(instance), arguments, activation, Some(constr), constr)?;
Ok(instance)
} }
fn derive( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_class: GcCell<'gc, Class<'gc>>,
_scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
Ok(ClassObject(GcCell::allocate( Ok(ClassObject(GcCell::allocate(
activation.context.gc_context, activation.context.gc_context,
self.0.read().clone(), self.0.read().clone(),

View File

@ -132,18 +132,6 @@ macro_rules! impl_avm2_custom_object {
self.0.read().$field.get_trait_slot(id) self.0.read().$field.get_trait_slot(id)
} }
fn get_provided_trait(
&self,
name: &QName<'gc>,
known_traits: &mut Vec<Trait<'gc>>,
) -> Result<(), Error> {
self.0.read().$field.get_provided_trait(name, known_traits)
}
fn get_provided_trait_slot(&self, id: u32) -> Result<Option<Trait<'gc>>, Error> {
self.0.read().$field.get_provided_trait_slot(id)
}
fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> { fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> {
self.0.read().$field.get_scope() self.0.read().$field.get_scope()
} }
@ -152,10 +140,6 @@ macro_rules! impl_avm2_custom_object {
self.0.read().$field.has_trait(name) self.0.read().$field.has_trait(name)
} }
fn provides_trait(self, name: &QName<'gc>) -> Result<bool, Error> {
self.0.read().$field.provides_trait(name)
}
fn has_instantiated_property(self, name: &QName<'gc>) -> bool { fn has_instantiated_property(self, name: &QName<'gc>) -> bool {
self.0.read().$field.has_instantiated_property(name) self.0.read().$field.has_instantiated_property(name)
} }
@ -204,6 +188,14 @@ macro_rules! impl_avm2_custom_object {
self.0.read().base.as_class() self.0.read().base.as_class()
} }
fn as_constr(&self) -> Option<Object<'gc>> {
self.0.read().base.as_constr()
}
fn set_constr(self, mc: MutationContext<'gc, '_>, constr: Object<'gc>) {
self.0.write(mc).base.set_constr(constr);
}
fn install_method( fn install_method(
&mut self, &mut self,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,

View File

@ -75,19 +75,14 @@ impl<'gc> TObject<'gc> for DispatchObject<'gc> {
impl_avm2_custom_object_properties!(base); impl_avm2_custom_object_properties!(base);
fn construct( fn construct(
&self, self,
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
Err("Cannot construct internal event dispatcher structures.".into()) Err("Cannot construct internal event dispatcher structures.".into())
} }
fn derive( fn derive(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
_activation: &mut Activation<'_, 'gc, '_>,
_class: GcCell<'gc, Class<'gc>>,
_scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
Err("Cannot subclass internal event dispatcher structures.".into()) Err("Cannot subclass internal event dispatcher structures.".into())
} }

View File

@ -16,32 +16,20 @@ use gc_arena::{Collect, GcCell, MutationContext};
/// A class instance deriver that constructs AppDomain objects. /// A class instance deriver that constructs AppDomain objects.
pub fn appdomain_deriver<'gc>( pub fn appdomain_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let scope = constr
.get_scope()
.ok_or("Constructor has an empty scope stack")?;
let domain = scope let domain = scope
.unwrap()
.read() .read()
.globals() .globals()
.as_application_domain() .as_application_domain()
.unwrap(); .ok_or("Constructor scope must have an appdomain at the bottom of it's scope stack")?;
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
DomainObject::derive( DomainObject::derive(constr, proto, domain, activation.context.gc_context)
activation.context.gc_context,
base_proto,
domain,
class,
scope,
)
} }
#[derive(Clone, Collect, Debug, Copy)] #[derive(Clone, Collect, Debug, Copy)]
@ -59,28 +47,37 @@ pub struct DomainObjectData<'gc> {
} }
impl<'gc> DomainObject<'gc> { impl<'gc> DomainObject<'gc> {
/// Create a new domain without association with any class or prototype.
///
/// This should only be called during early player runtime initialization.
/// It will return a `Domain` with no proto or instance constructor link,
/// meaning that you will have to set those yourself.
pub fn from_early_domain(mc: MutationContext<'gc, '_>, domain: Domain<'gc>) -> Object<'gc> {
let base = ScriptObjectData::base_new(None, ScriptObjectClass::NoClass);
DomainObject(GcCell::allocate(mc, DomainObjectData { base, domain })).into()
}
pub fn from_domain( pub fn from_domain(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Option<Object<'gc>>, base_proto: Option<Object<'gc>>,
domain: Domain<'gc>, domain: Domain<'gc>,
) -> Object<'gc> { ) -> Object<'gc> {
let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::ClassInstance(constr));
DomainObject(GcCell::allocate(mc, DomainObjectData { base, domain })).into() DomainObject(GcCell::allocate(mc, DomainObjectData { base, domain })).into()
} }
/// Construct a primitive subclass. /// Construct a primitive subclass.
pub fn derive( pub fn derive(
mc: MutationContext<'gc, '_>, constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
domain: Domain<'gc>, domain: Domain<'gc>,
class: GcCell<'gc, Class<'gc>>, mc: MutationContext<'gc, '_>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(DomainObject(GcCell::allocate(mc, DomainObjectData { base, domain })).into()) Ok(DomainObject(GcCell::allocate(mc, DomainObjectData { base, domain })).into())
} }
@ -100,40 +97,19 @@ impl<'gc> TObject<'gc> for DomainObject<'gc> {
Ok(this.into()) Ok(this.into())
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self, let mut this: Object<'gc> = Object::DomainObject(*self);
activation: &mut Activation<'_, 'gc, '_>, let constr = this
args: &[Value<'gc>], .get_property(
) -> Result<Object<'gc>, Error> { this,
let this: Object<'gc> = Object::DomainObject(*self); &QName::new(Namespace::public(), "constructor"),
let parent_domain = if let Some(parent_domain) = args activation,
.get(0) )?
.cloned() .coerce_to_object(activation)?;
.unwrap_or(Value::Undefined)
.coerce_to_object(activation)?
.as_application_domain()
{
parent_domain
} else {
activation.context.avm2.global_domain()
};
Ok(DomainObject::from_domain( Ok(DomainObject::from_domain(
activation.context.gc_context, activation.context.gc_context,
Some(this), constr,
Domain::movie_domain(activation.context.gc_context, parent_domain),
))
}
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_class: GcCell<'gc, Class<'gc>>,
_scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::DomainObject(*self);
Ok(DomainObject::from_domain(
activation.context.gc_context,
Some(this), Some(this),
activation.context.avm2.global_domain(), activation.context.avm2.global_domain(),
)) ))

View File

@ -17,24 +17,14 @@ use std::cell::{Ref, RefMut};
/// A class instance deriver that constructs Event objects. /// A class instance deriver that constructs Event objects.
pub fn event_deriver<'gc>( pub fn event_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
Ok(EventObject::derive( Ok(EventObject::derive(
base_proto, constr,
proto,
activation.context.gc_context, activation.context.gc_context,
class,
scope,
)) ))
} }
@ -56,25 +46,23 @@ impl<'gc> EventObject<'gc> {
/// Convert a bare event into it's object representation. /// Convert a bare event into it's object representation.
pub fn from_event( pub fn from_event(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Option<Object<'gc>>, base_proto: Option<Object<'gc>>,
event: Event<'gc>, event: Event<'gc>,
) -> Object<'gc> { ) -> Object<'gc> {
let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::ClassInstance(constr));
EventObject(GcCell::allocate(mc, EventObjectData { base, event })).into() EventObject(GcCell::allocate(mc, EventObjectData { base, event })).into()
} }
/// Instantiate an event subclass. /// Instantiate an event subclass.
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> { ) -> Object<'gc> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
EventObject(GcCell::allocate( EventObject(GcCell::allocate(
mc, mc,
@ -91,33 +79,17 @@ impl<'gc> TObject<'gc> for EventObject<'gc> {
impl_avm2_custom_object!(base); impl_avm2_custom_object!(base);
impl_avm2_custom_object_properties!(base); impl_avm2_custom_object_properties!(base);
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self, let base = ScriptObjectData::base_new(Some((*self).into()), ScriptObjectClass::NoClass);
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::EventObject(*self);
Ok(EventObject::from_event(
activation.context.gc_context,
Some(this),
Event::new(""),
))
}
fn derive( Ok(EventObject(GcCell::allocate(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::EventObject(*self);
Ok(Self::derive(
this,
activation.context.gc_context, activation.context.gc_context,
class, EventObjectData {
scope, base,
event: Event::new(""),
},
)) ))
.into())
} }
fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {

View File

@ -100,20 +100,37 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
receiver: Option<Object<'gc>>, receiver: Option<Object<'gc>>,
arguments: &[Value<'gc>], arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(exec) = &self.0.read().exec { if let Some(exec) = &self.0.read().exec {
exec.exec(receiver, arguments, activation, base_proto, self.into()) exec.exec(receiver, arguments, activation, base_constr, self.into())
} else { } else {
Err("Not a callable function!".into()) Err("Not a callable function!".into())
} }
} }
fn construct( fn construct(
&self, mut self,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>], arguments: &[Value<'gc>],
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let constr: Object<'gc> = self.into();
let prototype = self
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
let instance = prototype.derive(activation)?;
self.call(Some(instance), arguments, activation, None)?;
Ok(instance)
}
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::FunctionObject(*self); let this: Object<'gc> = Object::FunctionObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -123,23 +140,4 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
)) ))
.into()) .into())
} }
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
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),
);
Ok(FunctionObject(GcCell::allocate(
activation.context.gc_context,
FunctionObjectData { base, exec: None },
))
.into())
}
} }

View File

@ -19,20 +19,11 @@ use std::sync::Arc;
/// A class instance deriver that constructs LoaderInfo objects. /// A class instance deriver that constructs LoaderInfo objects.
pub fn loaderinfo_deriver<'gc>( pub fn loaderinfo_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr LoaderInfoObject::derive(constr, proto, activation.context.gc_context)
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
LoaderInfoObject::derive(base_proto, activation.context.gc_context, class, scope)
} }
/// Represents a thing which can be loaded by a loader. /// Represents a thing which can be loaded by a loader.
@ -73,10 +64,12 @@ impl<'gc> LoaderInfoObject<'gc> {
pub fn from_movie( pub fn from_movie(
movie: Arc<SwfMovie>, movie: Arc<SwfMovie>,
root: DisplayObject<'gc>, root: DisplayObject<'gc>,
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass); let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
let loaded_stream = Some(LoaderStream::Swf(movie, root)); let loaded_stream = Some(LoaderStream::Swf(movie, root));
Ok(LoaderInfoObject(GcCell::allocate( Ok(LoaderInfoObject(GcCell::allocate(
@ -90,8 +83,13 @@ impl<'gc> LoaderInfoObject<'gc> {
} }
/// Create a loader info object for the stage. /// Create a loader info object for the stage.
pub fn from_stage(base_proto: Object<'gc>, mc: MutationContext<'gc, '_>) -> Object<'gc> { pub fn from_stage(
let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass); constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> Object<'gc> {
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
LoaderInfoObject(GcCell::allocate( LoaderInfoObject(GcCell::allocate(
mc, mc,
@ -105,15 +103,12 @@ impl<'gc> LoaderInfoObject<'gc> {
/// Construct a loader-info subclass. /// Construct a loader-info subclass.
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(LoaderInfoObject(GcCell::allocate( Ok(LoaderInfoObject(GcCell::allocate(
mc, mc,
@ -131,18 +126,14 @@ impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
impl_avm2_custom_object_properties!(base); impl_avm2_custom_object_properties!(base);
fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
if let Some(class) = self.as_proto_class() { if let Some(class) = self.as_class() {
Ok(AvmString::new(mc, format!("[object {}]", class.read().name().local_name())).into()) Ok(AvmString::new(mc, format!("[object {}]", class.read().name().local_name())).into())
} else { } else {
Ok("[object Object]".into()) Ok("[object Object]".into())
} }
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::LoaderInfoObject(*self); let this: Object<'gc> = Object::LoaderInfoObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -156,28 +147,6 @@ impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
.into()) .into())
} }
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::LoaderInfoObject(*self);
let base = ScriptObjectData::base_new(
Some(this),
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(LoaderInfoObject(GcCell::allocate(
activation.context.gc_context,
LoaderInfoObjectData {
base,
loaded_stream: None,
},
))
.into())
}
/// Unwrap this object's loader stream /// Unwrap this object's loader stream
fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> { fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> {
if self.0.read().loaded_stream.is_some() { if self.0.read().loaded_stream.is_some() {

View File

@ -16,20 +16,11 @@ use std::cell::Ref;
/// A class instance deriver that constructs namespace objects. /// A class instance deriver that constructs namespace objects.
pub fn namespace_deriver<'gc>( pub fn namespace_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr NamespaceObject::derive(constr, proto, activation.context.gc_context)
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
NamespaceObject::derive(base_proto, activation.context.gc_context, class, scope)
} }
/// An Object which represents a boxed namespace name. /// An Object which represents a boxed namespace name.
@ -51,10 +42,12 @@ impl<'gc> NamespaceObject<'gc> {
/// Box a namespace into an object. /// Box a namespace into an object.
pub fn from_namespace( pub fn from_namespace(
namespace: Namespace<'gc>, namespace: Namespace<'gc>,
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass); let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(NamespaceObject(GcCell::allocate( Ok(NamespaceObject(GcCell::allocate(
mc, mc,
@ -65,15 +58,12 @@ impl<'gc> NamespaceObject<'gc> {
/// Construct a namespace subclass. /// Construct a namespace subclass.
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(NamespaceObject(GcCell::allocate( Ok(NamespaceObject(GcCell::allocate(
mc, mc,
@ -102,11 +92,7 @@ impl<'gc> TObject<'gc> for NamespaceObject<'gc> {
Some(Ref::map(self.0.read(), |s| &s.namespace)) Some(Ref::map(self.0.read(), |s| &s.namespace))
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::NamespaceObject(*self); let this: Object<'gc> = Object::NamespaceObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -119,26 +105,4 @@ impl<'gc> TObject<'gc> for NamespaceObject<'gc> {
)) ))
.into()) .into())
} }
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::NamespaceObject(*self);
let base = ScriptObjectData::base_new(
Some(this),
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(NamespaceObject(GcCell::allocate(
activation.context.gc_context,
NamespaceObjectData {
base,
namespace: Namespace::public(),
},
))
.into())
}
} }

View File

@ -17,20 +17,11 @@ use gc_arena::{Collect, GcCell, MutationContext};
/// A class instance deriver that constructs primitive objects. /// A class instance deriver that constructs primitive objects.
pub fn primitive_deriver<'gc>( pub fn primitive_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr PrimitiveObject::derive(constr, proto, activation.context.gc_context)
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
PrimitiveObject::derive(base_proto, activation.context.gc_context, class, scope)
} }
/// An Object which represents a primitive value of some other kind. /// An Object which represents a primitive value of some other kind.
@ -52,6 +43,7 @@ impl<'gc> PrimitiveObject<'gc> {
/// Box a primitive into an object. /// Box a primitive into an object.
pub fn from_primitive( pub fn from_primitive(
primitive: Value<'gc>, primitive: Value<'gc>,
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
@ -59,7 +51,8 @@ impl<'gc> PrimitiveObject<'gc> {
return Err("Attempted to box an object as a primitive".into()); return Err("Attempted to box an object as a primitive".into());
} }
let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass); let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(PrimitiveObject(GcCell::allocate( Ok(PrimitiveObject(GcCell::allocate(
mc, mc,
@ -70,15 +63,12 @@ impl<'gc> PrimitiveObject<'gc> {
/// Construct a primitive subclass. /// Construct a primitive subclass.
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(PrimitiveObject(GcCell::allocate( Ok(PrimitiveObject(GcCell::allocate(
mc, mc,
@ -104,7 +94,7 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> {
val @ Value::Integer(_) | val @ Value::Unsigned(_) => Ok(val), val @ Value::Integer(_) | val @ Value::Unsigned(_) => Ok(val),
_ => { _ => {
let class_name = self let class_name = self
.as_proto_class() .as_class()
.map(|c| c.read().name().local_name()) .map(|c| c.read().name().local_name())
.unwrap_or_else(|| "Object".into()); .unwrap_or_else(|| "Object".into());
@ -117,11 +107,7 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> {
Ok(self.0.read().primitive.clone()) Ok(self.0.read().primitive.clone())
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::PrimitiveObject(*self); let this: Object<'gc> = Object::PrimitiveObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -135,28 +121,6 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> {
.into()) .into())
} }
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::PrimitiveObject(*self);
let base = ScriptObjectData::base_new(
Some(this),
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(PrimitiveObject(GcCell::allocate(
activation.context.gc_context,
PrimitiveObjectData {
base,
primitive: Value::Undefined,
},
))
.into())
}
fn as_primitive_mut(&self, mc: MutationContext<'gc, '_>) -> Option<RefMut<Value<'gc>>> { fn as_primitive_mut(&self, mc: MutationContext<'gc, '_>) -> Option<RefMut<Value<'gc>>> {
Some(RefMut::map(self.0.write(mc), |pod| &mut pod.primitive)) Some(RefMut::map(self.0.write(mc), |pod| &mut pod.primitive))
} }

View File

@ -17,24 +17,14 @@ use std::cell::{Ref, RefMut};
/// A class instance deriver that constructs RegExp objects. /// A class instance deriver that constructs RegExp objects.
pub fn regexp_deriver<'gc>( pub fn regexp_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
Ok(RegExpObject::derive( Ok(RegExpObject::derive(
base_proto, constr,
proto,
activation.context.gc_context, activation.context.gc_context,
class,
scope,
)) ))
} }
@ -54,25 +44,23 @@ pub struct RegExpObjectData<'gc> {
impl<'gc> RegExpObject<'gc> { impl<'gc> RegExpObject<'gc> {
pub fn from_regexp( pub fn from_regexp(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Option<Object<'gc>>, base_proto: Option<Object<'gc>>,
regexp: RegExp<'gc>, regexp: RegExp<'gc>,
) -> Object<'gc> { ) -> Object<'gc> {
let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::ClassInstance(constr));
RegExpObject(GcCell::allocate(mc, RegExpObjectData { base, regexp })).into() RegExpObject(GcCell::allocate(mc, RegExpObjectData { base, regexp })).into()
} }
/// Instantiate a regexp subclass. /// Instantiate a regexp subclass.
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> { ) -> Object<'gc> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
RegExpObject(GcCell::allocate( RegExpObject(GcCell::allocate(
mc, mc,
@ -89,32 +77,17 @@ impl<'gc> TObject<'gc> for RegExpObject<'gc> {
impl_avm2_custom_object!(base); impl_avm2_custom_object!(base);
impl_avm2_custom_object_properties!(base); impl_avm2_custom_object_properties!(base);
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self, let base = ScriptObjectData::base_new(Some((*self).into()), ScriptObjectClass::NoClass);
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::RegExpObject(*self);
Ok(RegExpObject::from_regexp(
activation.context.gc_context,
Some(this),
RegExp::new(""),
))
}
fn derive( Ok(RegExpObject(GcCell::allocate(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::RegExpObject(*self);
Ok(Self::derive(
this,
activation.context.gc_context, activation.context.gc_context,
class, RegExpObjectData {
scope, base,
regexp: RegExp::new(""),
},
)) ))
.into())
} }
fn to_string(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn to_string(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {

View File

@ -33,12 +33,12 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
#[derive(Clone, Collect, Debug)] #[derive(Clone, Collect, Debug)]
#[collect(no_drop)] #[collect(no_drop)]
pub enum ScriptObjectClass<'gc> { pub enum ScriptObjectClass<'gc> {
/// Instantiate instance traits, for prototypes.
InstancePrototype(GcCell<'gc, Class<'gc>>, Option<GcCell<'gc, Scope<'gc>>>),
/// Instantiate class traits, for class constructors. /// Instantiate class traits, for class constructors.
ClassConstructor(GcCell<'gc, Class<'gc>>, Option<GcCell<'gc, Scope<'gc>>>), ClassConstructor(GcCell<'gc, Class<'gc>>, Option<GcCell<'gc, Scope<'gc>>>),
/// Instantiate instance traits, for class instances.
ClassInstance(Object<'gc>),
/// Do not instantiate any class or instance traits. /// Do not instantiate any class or instance traits.
NoClass, NoClass,
} }
@ -172,18 +172,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.read().get_trait_slot(id) self.0.read().get_trait_slot(id)
} }
fn get_provided_trait(
&self,
name: &QName<'gc>,
known_traits: &mut Vec<Trait<'gc>>,
) -> Result<(), Error> {
self.0.read().get_provided_trait(name, known_traits)
}
fn get_provided_trait_slot(&self, id: u32) -> Result<Option<Trait<'gc>>, Error> {
self.0.read().get_provided_trait_slot(id)
}
fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> { fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> {
self.0.read().get_scope() self.0.read().get_scope()
} }
@ -207,10 +195,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.read().has_trait(name) self.0.read().has_trait(name)
} }
fn provides_trait(self, name: &QName<'gc>) -> Result<bool, Error> {
self.0.read().provides_trait(name)
}
fn has_instantiated_property(self, name: &QName<'gc>) -> bool { fn has_instantiated_property(self, name: &QName<'gc>) -> bool {
self.0.read().has_instantiated_property(name) self.0.read().has_instantiated_property(name)
} }
@ -254,32 +238,13 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.as_ptr() as *const ObjectPtr self.0.as_ptr() as *const ObjectPtr
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ScriptObject(*self); let this: Object<'gc> = Object::ScriptObject(*self);
Ok(ScriptObject::object(activation.context.gc_context, this)) Ok(ScriptObject::object(activation.context.gc_context, this))
} }
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ScriptObject(*self);
Ok(ScriptObject::prototype(
activation.context.gc_context,
this,
class,
scope,
))
}
fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
if let Some(class) = self.as_proto_class() { if let Some(class) = self.as_class() {
Ok(AvmString::new(mc, format!("[object {}]", class.read().name().local_name())).into()) Ok(AvmString::new(mc, format!("[object {}]", class.read().name().local_name())).into())
} else { } else {
Ok("[object Object]".into()) Ok("[object Object]".into())
@ -360,6 +325,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>> { fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>> {
self.0.read().as_class() self.0.read().as_class()
} }
fn as_constr(&self) -> Option<Object<'gc>> {
self.0.read().as_constr()
}
fn set_constr(self, mc: MutationContext<'gc, '_>, constr: Object<'gc>) {
self.0.write(mc).set_constr(constr);
}
} }
impl<'gc> ScriptObject<'gc> { impl<'gc> ScriptObject<'gc> {
@ -375,26 +348,6 @@ impl<'gc> ScriptObject<'gc> {
.into() .into()
} }
/// Construct a bare class prototype with no base class.
///
/// This is used in cases where a prototype needs to exist, but it does not
/// need to extend `Object`. This is the case for interfaces and activation
/// objects, both of which need to participate in the class mechanism but
/// are not `Object`s.
pub fn bare_prototype(
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> {
let script_class = ScriptObjectClass::InstancePrototype(class, scope);
ScriptObject(GcCell::allocate(
mc,
ScriptObjectData::base_new(None, script_class),
))
.into()
}
/// Construct an object with a prototype. /// Construct an object with a prototype.
pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> {
ScriptObject(GcCell::allocate( ScriptObject(GcCell::allocate(
@ -404,18 +357,15 @@ impl<'gc> ScriptObject<'gc> {
.into() .into()
} }
/// Construct a prototype for an ES4 class. /// Construct an instance with a class and scope stack.
pub fn prototype( pub fn instance(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
proto: Object<'gc>, proto: Object<'gc>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> { ) -> Object<'gc> {
let script_class = ScriptObjectClass::InstancePrototype(class, scope);
ScriptObject(GcCell::allocate( ScriptObject(GcCell::allocate(
mc, mc,
ScriptObjectData::base_new(Some(proto), script_class), ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr)),
)) ))
.into() .into()
} }
@ -443,7 +393,15 @@ impl<'gc> ScriptObjectData<'gc> {
let prop = self.values.get(name); let prop = self.values.get(name);
if let Some(prop) = prop { if let Some(prop) = prop {
prop.get(receiver, activation.base_proto().or(self.proto)) prop.get(
receiver,
Some(
activation
.base_constr()
.or_else(|| self.as_constr())
.unwrap_or(receiver),
),
)
} else { } else {
Ok(Value::Undefined.into()) Ok(Value::Undefined.into())
} }
@ -456,6 +414,7 @@ impl<'gc> ScriptObjectData<'gc> {
value: Value<'gc>, value: Value<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
let constr = self.as_constr();
let slot_id = if let Some(prop) = self.values.get(name) { let slot_id = if let Some(prop) = self.values.get(name) {
if let Some(slot_id) = prop.slot_id() { if let Some(slot_id) = prop.slot_id() {
Some(slot_id) Some(slot_id)
@ -473,8 +432,11 @@ impl<'gc> ScriptObjectData<'gc> {
Ok(Value::Undefined.into()) Ok(Value::Undefined.into())
} else if self.values.contains_key(name) { } else if self.values.contains_key(name) {
let prop = self.values.get_mut(name).unwrap(); let prop = self.values.get_mut(name).unwrap();
let proto = self.proto; prop.set(
prop.set(receiver, activation.base_proto().or(proto), value) receiver,
Some(activation.base_constr().or(constr).unwrap_or(receiver)),
value,
)
} else { } else {
//TODO: Not all classes are dynamic like this //TODO: Not all classes are dynamic like this
self.enumerants.push(name.clone()); self.enumerants.push(name.clone());
@ -492,6 +454,7 @@ impl<'gc> ScriptObjectData<'gc> {
value: Value<'gc>, value: Value<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
let constr = self.as_constr();
if let Some(prop) = self.values.get_mut(name) { if let Some(prop) = self.values.get_mut(name) {
if let Some(slot_id) = prop.slot_id() { if let Some(slot_id) = prop.slot_id() {
// This doesn't need the non-local version of this property // This doesn't need the non-local version of this property
@ -500,8 +463,11 @@ impl<'gc> ScriptObjectData<'gc> {
self.init_slot_local(slot_id, value, activation.context.gc_context)?; self.init_slot_local(slot_id, value, activation.context.gc_context)?;
Ok(Value::Undefined.into()) Ok(Value::Undefined.into())
} else { } else {
let proto = self.proto; prop.init(
prop.init(receiver, activation.base_proto().or(proto), value) receiver,
Some(activation.base_constr().or(constr).unwrap_or(receiver)),
value,
)
} }
} else { } else {
//TODO: Not all classes are dynamic like this //TODO: Not all classes are dynamic like this
@ -584,89 +550,67 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn get_trait(&self, name: &QName<'gc>) -> Result<Vec<Trait<'gc>>, Error> { pub fn get_trait(&self, name: &QName<'gc>) -> Result<Vec<Trait<'gc>>, Error> {
match &self.class { match &self.class {
//Class constructors have local traits only. //Class constructors have local traits only.
ScriptObjectClass::ClassConstructor(..) => { ScriptObjectClass::ClassConstructor(class, ..) => {
let mut known_traits = Vec::new(); let mut known_traits = Vec::new();
self.get_provided_trait(name, &mut known_traits)?; class.read().lookup_class_traits(name, &mut known_traits)?;
Ok(known_traits) Ok(known_traits)
} }
//Prototypes do not have traits available locally, but they provide //Class instances have all instance traits from all superclasses.
//traits instead. ScriptObjectClass::ClassInstance(constr) => {
ScriptObjectClass::InstancePrototype(..) => Ok(Vec::new()), let mut constr_list = Vec::new();
let mut cur_constr = Some(*constr);
while let Some(constr) = cur_constr {
constr_list.push(constr);
//Instances walk the prototype chain to build a list of known cur_constr = constr.base_class_constr();
//traits provided by the classes attached to those prototypes.
ScriptObjectClass::NoClass => {
let mut known_traits = Vec::new();
let mut chain = Vec::new();
let mut proto = self.proto();
while let Some(p) = proto {
chain.push(p);
proto = p.proto();
} }
for proto in chain.iter().rev() { let mut known_traits = Vec::new();
proto.get_provided_trait(name, &mut known_traits)?; for constr in constr_list.iter().rev() {
let cur_class = constr
.as_class()
.ok_or("Object is not a class constructor")?;
cur_class
.read()
.lookup_instance_traits(name, &mut known_traits)?;
} }
Ok(known_traits) Ok(known_traits)
} }
// Bare objects, ES3 objects, and prototypes do not have traits.
ScriptObjectClass::NoClass => Ok(Vec::new()),
} }
} }
pub fn get_trait_slot(&self, id: u32) -> Result<Option<Trait<'gc>>, Error> { pub fn get_trait_slot(&self, id: u32) -> Result<Option<Trait<'gc>>, Error> {
match &self.class { match &self.class {
//Class constructors have local slot traits only. //Class constructors have local slot traits only.
ScriptObjectClass::ClassConstructor(..) => self.get_provided_trait_slot(id), ScriptObjectClass::ClassConstructor(class, ..) => {
class.read().lookup_class_traits_by_slot(id)
//Prototypes do not have traits available locally, but they provide
//traits instead.
ScriptObjectClass::InstancePrototype(..) => Ok(None),
//Instances walk the prototype chain to build a list of known
//traits provided by the classes attached to those prototypes.
ScriptObjectClass::NoClass => {
let mut proto = self.proto();
while let Some(p) = proto {
if let Some(trait_val) = p.get_provided_trait_slot(id)? {
return Ok(Some(trait_val));
} }
proto = p.proto(); //Class instances have all instance slot traits from all superclasses.
ScriptObjectClass::ClassInstance(constr) => {
let mut cur_constr = Some(*constr);
while let Some(constr) = cur_constr {
let cur_class = constr
.as_class()
.ok_or("Object is not a class constructor")?;
if let Some(inst_trait) = cur_class.read().lookup_instance_traits_by_slot(id)? {
return Ok(Some(inst_trait));
}
cur_constr = constr.base_class_constr();
} }
Ok(None) Ok(None)
} }
}
}
pub fn get_provided_trait( // Bare objects, ES3 objects, and prototypes do not have traits.
&self,
name: &QName<'gc>,
known_traits: &mut Vec<Trait<'gc>>,
) -> Result<(), Error> {
match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => {
class.read().lookup_class_traits(name, known_traits)
}
ScriptObjectClass::InstancePrototype(class, ..) => {
class.read().lookup_instance_traits(name, known_traits)
}
ScriptObjectClass::NoClass => Ok(()),
}
}
pub fn get_provided_trait_slot(&self, id: u32) -> Result<Option<Trait<'gc>>, Error> {
match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => {
class.read().lookup_class_traits_by_slot(id)
}
ScriptObjectClass::InstancePrototype(class, ..) => {
class.read().lookup_instance_traits_by_slot(id)
}
ScriptObjectClass::NoClass => Ok(None), ScriptObjectClass::NoClass => Ok(None),
} }
} }
@ -674,38 +618,30 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn has_trait(&self, name: &QName<'gc>) -> Result<bool, Error> { pub fn has_trait(&self, name: &QName<'gc>) -> Result<bool, Error> {
match &self.class { match &self.class {
//Class constructors have local traits only. //Class constructors have local traits only.
ScriptObjectClass::ClassConstructor(..) => self.provides_trait(name), ScriptObjectClass::ClassConstructor(class, ..) => {
Ok(class.read().has_class_trait(name))
}
//Prototypes do not have traits available locally, but we walk //Class instances have instance traits from any class in the base
//through them to find traits (see `provides_trait`) //class chain.
ScriptObjectClass::InstancePrototype(..) => Ok(false), ScriptObjectClass::ClassInstance(constr) => {
let mut cur_constr = Some(*constr);
//Instances walk the prototype chain to build a list of known while let Some(constr) = cur_constr {
//traits provided by the classes attached to those prototypes. let cur_class = constr
ScriptObjectClass::NoClass => { .as_class()
let mut proto = self.proto(); .ok_or("Object is not a class constructor")?;
if cur_class.read().has_instance_trait(name) {
while let Some(p) = proto {
if p.provides_trait(name)? {
return Ok(true); return Ok(true);
} }
proto = p.proto(); cur_constr = constr.base_class_constr();
} }
Ok(false) Ok(false)
} }
}
}
pub fn provides_trait(&self, name: &QName<'gc>) -> Result<bool, Error> { // Bare objects, ES3 objects, and prototypes do not have traits.
match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => {
Ok(class.read().has_class_trait(name))
}
ScriptObjectClass::InstancePrototype(class, ..) => {
Ok(class.read().has_instance_trait(name))
}
ScriptObjectClass::NoClass => Ok(false), ScriptObjectClass::NoClass => Ok(false),
} }
} }
@ -713,8 +649,8 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn get_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> { pub fn get_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
match &self.class { match &self.class {
ScriptObjectClass::ClassConstructor(_class, scope) => *scope, ScriptObjectClass::ClassConstructor(_class, scope) => *scope,
ScriptObjectClass::InstancePrototype(_class, scope) => *scope, ScriptObjectClass::ClassInstance(constr) => constr.get_scope(),
ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_scope()), ScriptObjectClass::NoClass => None,
} }
} }
@ -725,11 +661,7 @@ impl<'gc> ScriptObjectData<'gc> {
} }
} }
let trait_ns = match self.class { let trait_ns = self.resolve_any_trait(local_name)?;
ScriptObjectClass::ClassConstructor(..) => self.resolve_any_trait(local_name)?,
ScriptObjectClass::NoClass => self.resolve_any_trait(local_name)?,
_ => None,
};
if trait_ns.is_none() { if trait_ns.is_none() {
if let Some(proto) = self.proto() { if let Some(proto) = self.proto() {
@ -757,8 +689,21 @@ impl<'gc> ScriptObjectData<'gc> {
ScriptObjectClass::ClassConstructor(class, ..) => { ScriptObjectClass::ClassConstructor(class, ..) => {
Ok(class.read().resolve_any_class_trait(local_name)) Ok(class.read().resolve_any_class_trait(local_name))
} }
ScriptObjectClass::InstancePrototype(class, ..) => { ScriptObjectClass::ClassInstance(constr) => {
Ok(class.read().resolve_any_instance_trait(local_name)) let mut cur_constr = Some(*constr);
while let Some(constr) = cur_constr {
let cur_class = constr
.as_class()
.ok_or("Object is not a class constructor")?;
if let Some(ns) = cur_class.read().resolve_any_instance_trait(local_name) {
return Ok(Some(ns));
}
cur_constr = constr.base_class_constr();
}
Ok(None)
} }
ScriptObjectClass::NoClass => Ok(None), ScriptObjectClass::NoClass => Ok(None),
} }
@ -989,8 +934,26 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>> { pub fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>> {
match self.class { match self.class {
ScriptObjectClass::ClassConstructor(class, _) => Some(class), ScriptObjectClass::ClassConstructor(class, _) => Some(class),
ScriptObjectClass::InstancePrototype(class, _) => Some(class), ScriptObjectClass::ClassInstance(constr) => constr.as_class(),
ScriptObjectClass::NoClass => None, ScriptObjectClass::NoClass => None,
} }
} }
/// Get the class constructor for this object, if it has one.
pub fn as_constr(&self) -> Option<Object<'gc>> {
match self.class {
ScriptObjectClass::ClassConstructor(..) => None,
ScriptObjectClass::ClassInstance(constr) => Some(constr),
ScriptObjectClass::NoClass => None,
}
}
/// Associate the object with a particular constructor.
///
/// This turns the object into an instance of that class. It should only be
/// used in situations where the object cannot be made an instance of the
/// class at allocation time, such as during early runtime setup.
pub fn set_constr(&mut self, constr: Object<'gc>) {
self.class = ScriptObjectClass::ClassInstance(constr);
}
} }

View File

@ -16,20 +16,11 @@ use gc_arena::{Collect, GcCell, MutationContext};
/// A class instance deriver that constructs Stage objects. /// A class instance deriver that constructs Stage objects.
pub fn stage_deriver<'gc>( pub fn stage_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr StageObject::derive(constr, proto, activation.context.gc_context)
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
StageObject::derive(base_proto, activation.context.gc_context, class, scope)
} }
#[derive(Clone, Collect, Debug, Copy)] #[derive(Clone, Collect, Debug, Copy)]
@ -50,12 +41,16 @@ impl<'gc> StageObject<'gc> {
pub fn for_display_object( pub fn for_display_object(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
display_object: DisplayObject<'gc>, display_object: DisplayObject<'gc>,
constr: Object<'gc>,
proto: Object<'gc>, proto: Object<'gc>,
) -> Self { ) -> Self {
Self(GcCell::allocate( Self(GcCell::allocate(
mc, mc,
StageObjectData { StageObjectData {
base: ScriptObjectData::base_new(Some(proto), ScriptObjectClass::NoClass), base: ScriptObjectData::base_new(
Some(proto),
ScriptObjectClass::ClassInstance(constr),
),
display_object: Some(display_object), display_object: Some(display_object),
}, },
)) ))
@ -63,15 +58,12 @@ impl<'gc> StageObject<'gc> {
/// Construct a stage object subclass. /// Construct a stage object subclass.
pub fn derive( pub fn derive(
base_proto: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(StageObject(GcCell::allocate( Ok(StageObject(GcCell::allocate(
mc, mc,
@ -191,18 +183,6 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.0.read().base.get_trait_slot(id) self.0.read().base.get_trait_slot(id)
} }
fn get_provided_trait(
&self,
name: &QName<'gc>,
known_traits: &mut Vec<Trait<'gc>>,
) -> Result<(), Error> {
self.0.read().base.get_provided_trait(name, known_traits)
}
fn get_provided_trait_slot(&self, id: u32) -> Result<Option<Trait<'gc>>, Error> {
self.0.read().base.get_provided_trait_slot(id)
}
fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> { fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> {
self.0.read().base.get_scope() self.0.read().base.get_scope()
} }
@ -226,10 +206,6 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.0.read().base.has_trait(name) self.0.read().base.has_trait(name)
} }
fn provides_trait(self, name: &QName<'gc>) -> Result<bool, Error> {
self.0.read().base.provides_trait(name)
}
fn has_instantiated_property(self, name: &QName<'gc>) -> bool { fn has_instantiated_property(self, name: &QName<'gc>) -> bool {
self.0.read().base.has_instantiated_property(name) self.0.read().base.has_instantiated_property(name)
} }
@ -282,6 +258,14 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.0.read().base.as_class() self.0.read().base.as_class()
} }
fn as_constr(&self) -> Option<Object<'gc>> {
self.0.read().base.as_constr()
}
fn set_constr(self, mc: MutationContext<'gc, '_>, constr: Object<'gc>) {
self.0.write(mc).base.set_constr(constr);
}
fn as_display_object(&self) -> Option<DisplayObject<'gc>> { fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
self.0.read().display_object self.0.read().display_object
} }
@ -300,11 +284,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
Err("Not a callable function!".into()) Err("Not a callable function!".into())
} }
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::StageObject(*self); let this: Object<'gc> = Object::StageObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -318,28 +298,6 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
.into()) .into())
} }
fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::StageObject(*self);
let base = ScriptObjectData::base_new(
Some(this),
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(StageObject(GcCell::allocate(
activation.context.gc_context,
StageObjectData {
base,
display_object: None,
},
))
.into())
}
fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
Ok(Value::Object(Object::from(*self))) Ok(Value::Object(Object::from(*self)))
} }

View File

@ -15,20 +15,11 @@ use gc_arena::{Collect, GcCell, MutationContext};
/// A class instance deriver that constructs XML objects. /// A class instance deriver that constructs XML objects.
pub fn xml_deriver<'gc>( pub fn xml_deriver<'gc>(
mut constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base_proto = constr XmlObject::derive(constr, proto, activation.context.gc_context)
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
XmlObject::derive(base_proto, activation.context.gc_context, class, scope)
} }
#[derive(Clone, Collect, Debug, Copy)] #[derive(Clone, Collect, Debug, Copy)]
@ -45,15 +36,12 @@ pub struct XmlObjectData<'gc> {
impl<'gc> XmlObject<'gc> { impl<'gc> XmlObject<'gc> {
/// Instantiate an xml subclass. /// Instantiate an xml subclass.
pub fn derive( pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>, base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new( let base =
Some(base_proto), ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
ScriptObjectClass::InstancePrototype(class, scope),
);
Ok(XmlObject(GcCell::allocate(mc, XmlObjectData { base })).into()) Ok(XmlObject(GcCell::allocate(mc, XmlObjectData { base })).into())
} }
@ -72,26 +60,15 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> {
impl_avm2_custom_object!(base); impl_avm2_custom_object!(base);
impl_avm2_custom_object_properties!(base); impl_avm2_custom_object_properties!(base);
fn construct( fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::XmlObject(*self); let this: Object<'gc> = Object::XmlObject(*self);
Ok(Self::empty_object( let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
activation.context.gc_context,
Some(this),
))
}
fn derive( Ok(XmlObject(GcCell::allocate(
&self, activation.context.gc_context,
activation: &mut Activation<'_, 'gc, '_>, XmlObjectData { base },
class: GcCell<'gc, Class<'gc>>, ))
scope: Option<GcCell<'gc, Scope<'gc>>>, .into())
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::XmlObject(*self);
Self::derive(this, activation.context.gc_context, class, scope)
} }
fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {

View File

@ -131,14 +131,14 @@ impl<'gc> Property<'gc> {
pub fn get( pub fn get(
&self, &self,
this: Object<'gc>, this: Object<'gc>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
match self { match self {
Property::Virtual { get: Some(get), .. } => Ok(ReturnValue::defer_execution( Property::Virtual { get: Some(get), .. } => Ok(ReturnValue::defer_execution(
*get, *get,
Some(this), Some(this),
vec![], vec![],
base_proto, base_constr,
)), )),
Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()), Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()),
Property::Stored { value, .. } => Ok(value.to_owned().into()), Property::Stored { value, .. } => Ok(value.to_owned().into()),
@ -159,7 +159,7 @@ impl<'gc> Property<'gc> {
pub fn set( pub fn set(
&mut self, &mut self,
this: Object<'gc>, this: Object<'gc>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
new_value: impl Into<Value<'gc>>, new_value: impl Into<Value<'gc>>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
match self { match self {
@ -169,7 +169,7 @@ impl<'gc> Property<'gc> {
*function, *function,
Some(this), Some(this),
vec![new_value.into()], vec![new_value.into()],
base_proto, base_constr,
)); ));
} }
@ -203,7 +203,7 @@ impl<'gc> Property<'gc> {
pub fn init( pub fn init(
&mut self, &mut self,
this: Object<'gc>, this: Object<'gc>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
new_value: impl Into<Value<'gc>>, new_value: impl Into<Value<'gc>>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
match self { match self {
@ -213,7 +213,7 @@ impl<'gc> Property<'gc> {
*function, *function,
Some(this), Some(this),
vec![new_value.into()], vec![new_value.into()],
base_proto, base_constr,
)); ));
} }

View File

@ -37,7 +37,7 @@ pub enum ReturnValue<'gc> {
callee: Object<'gc>, callee: Object<'gc>,
unbound_reciever: Option<Object<'gc>>, unbound_reciever: Option<Object<'gc>>,
arguments: Vec<Value<'gc>>, arguments: Vec<Value<'gc>>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
}, },
} }
@ -49,13 +49,13 @@ impl fmt::Debug for ReturnValue<'_> {
callee, callee,
unbound_reciever, unbound_reciever,
arguments, arguments,
base_proto, base_constr,
} => f } => f
.debug_struct("ReturnValue") .debug_struct("ReturnValue")
.field("callee", callee) .field("callee", callee)
.field("unbound_reciever", unbound_reciever) .field("unbound_reciever", unbound_reciever)
.field("arguments", arguments) .field("arguments", arguments)
.field("base_proto", base_proto) .field("base_constr", base_constr)
.finish(), .finish(),
} }
} }
@ -67,13 +67,13 @@ impl<'gc> ReturnValue<'gc> {
callee: Object<'gc>, callee: Object<'gc>,
unbound_reciever: Option<Object<'gc>>, unbound_reciever: Option<Object<'gc>>,
arguments: Vec<Value<'gc>>, arguments: Vec<Value<'gc>>,
base_proto: Option<Object<'gc>>, base_constr: Option<Object<'gc>>,
) -> Self { ) -> Self {
Self::ResultOf { Self::ResultOf {
callee, callee,
unbound_reciever, unbound_reciever,
arguments, arguments,
base_proto, base_constr,
} }
} }
@ -88,12 +88,12 @@ impl<'gc> ReturnValue<'gc> {
callee, callee,
unbound_reciever, unbound_reciever,
arguments, arguments,
base_proto, base_constr,
} => callee.as_executable().unwrap().exec( } => callee.as_executable().unwrap().exec(
unbound_reciever, unbound_reciever,
&arguments, &arguments,
activation, activation,
base_proto, base_constr,
callee, callee,
), ),
} }

View File

@ -4,6 +4,7 @@ use crate::avm2::activation::Activation;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::domain::Domain; use crate::avm2::domain::Domain;
use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::method::{BytecodeMethod, Method};
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{DomainObject, Object, TObject}; use crate::avm2::object::{DomainObject, Object, TObject};
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::string::AvmString; use crate::avm2::string::AvmString;
@ -109,8 +110,7 @@ impl<'gc> TranslationUnit<'gc> {
pub fn load_class( pub fn load_class(
self, self,
class_index: u32, class_index: u32,
avm2: &mut Avm2<'gc>, uc: &mut UpdateContext<'_, 'gc, '_>,
mc: MutationContext<'gc, '_>,
) -> Result<GcCell<'gc, Class<'gc>>, Error> { ) -> Result<GcCell<'gc, Class<'gc>>, Error> {
let read = self.0.read(); let read = self.0.read();
if let Some(class) = read.classes.get(&class_index) { if let Some(class) = read.classes.get(&class_index) {
@ -119,10 +119,15 @@ impl<'gc> TranslationUnit<'gc> {
drop(read); drop(read);
let class = Class::from_abc_index(self, class_index, mc)?; let class = Class::from_abc_index(self, class_index, uc.gc_context)?;
self.0.write(mc).classes.insert(class_index, class); self.0
.write(uc.gc_context)
.classes
.insert(class_index, class);
class.write(mc).load_traits(self, class_index, avm2, mc)?; class
.write(uc.gc_context)
.load_traits(self, class_index, uc)?;
Ok(class) Ok(class)
} }
@ -131,8 +136,7 @@ impl<'gc> TranslationUnit<'gc> {
pub fn load_script( pub fn load_script(
self, self,
script_index: u32, script_index: u32,
avm2: &mut Avm2<'gc>, uc: &mut UpdateContext<'_, 'gc, '_>,
mc: MutationContext<'gc, '_>,
) -> Result<Script<'gc>, Error> { ) -> Result<Script<'gc>, Error> {
let read = self.0.read(); let read = self.0.read();
if let Some(scripts) = read.scripts.get(&script_index) { if let Some(scripts) = read.scripts.get(&script_index) {
@ -143,15 +147,32 @@ impl<'gc> TranslationUnit<'gc> {
drop(read); drop(read);
let global = DomainObject::from_domain(mc, Some(avm2.prototypes().global), domain); let mut activation = Activation::from_nothing(uc.reborrow());
let mut script = Script::from_abc_index(self, script_index, global, mc)?; let mut global_proto = activation.context.avm2.prototypes().global;
self.0.write(mc).scripts.insert(script_index, script); let global_constr = global_proto
.get_property(
global_proto,
&QName::new(Namespace::public(), "constructor"),
&mut activation,
)?
.coerce_to_object(&mut activation)?;
script.load_traits(self, script_index, avm2, mc)?; drop(activation);
let global =
DomainObject::from_domain(uc.gc_context, global_constr, Some(global_proto), domain);
let mut script = Script::from_abc_index(self, script_index, global, uc.gc_context)?;
self.0
.write(uc.gc_context)
.scripts
.insert(script_index, script);
script.load_traits(self, script_index, uc)?;
for traitdef in script.traits()?.iter() { for traitdef in script.traits()?.iter() {
domain.export_definition(traitdef.name().clone(), script, mc)?; domain.export_definition(traitdef.name().clone(), script, uc.gc_context)?;
} }
Ok(script) Ok(script)
@ -302,10 +323,9 @@ impl<'gc> Script<'gc> {
&mut self, &mut self,
unit: TranslationUnit<'gc>, unit: TranslationUnit<'gc>,
script_index: u32, script_index: u32,
avm2: &mut Avm2<'gc>, uc: &mut UpdateContext<'_, 'gc, '_>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut write = self.0.write(mc); let mut write = self.0.write(uc.gc_context);
if write.traits_loaded { if write.traits_loaded {
return Ok(()); return Ok(());
@ -323,9 +343,9 @@ impl<'gc> Script<'gc> {
for abc_trait in script.traits.iter() { for abc_trait in script.traits.iter() {
drop(write); drop(write);
let newtrait = Trait::from_abc_trait(unit, abc_trait, avm2, mc)?; let newtrait = Trait::from_abc_trait(unit, abc_trait, uc)?;
write = self.0.write(mc); write = self.0.write(uc.gc_context);
write.traits.push(newtrait); write.traits.push(newtrait);
} }

View File

@ -5,9 +5,10 @@ use crate::avm2::method::Method;
use crate::avm2::names::{Multiname, QName}; use crate::avm2::names::{Multiname, QName};
use crate::avm2::script::TranslationUnit; use crate::avm2::script::TranslationUnit;
use crate::avm2::value::{abc_default_value, Value}; use crate::avm2::value::{abc_default_value, Value};
use crate::avm2::{Avm2, Error}; use crate::avm2::Error;
use crate::context::UpdateContext;
use bitflags::bitflags; use bitflags::bitflags;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell};
use swf::avm2::types::{Trait as AbcTrait, TraitKind as AbcTraitKind}; use swf::avm2::types::{Trait as AbcTrait, TraitKind as AbcTraitKind};
bitflags! { bitflags! {
@ -179,10 +180,9 @@ impl<'gc> Trait<'gc> {
pub fn from_abc_trait( pub fn from_abc_trait(
unit: TranslationUnit<'gc>, unit: TranslationUnit<'gc>,
abc_trait: &AbcTrait, abc_trait: &AbcTrait,
avm2: &mut Avm2<'gc>, uc: &mut UpdateContext<'_, 'gc, '_>,
mc: MutationContext<'gc, '_>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let name = QName::from_abc_multiname(unit, abc_trait.name.clone(), mc)?; let name = QName::from_abc_multiname(unit, abc_trait.name.clone(), uc.gc_context)?;
Ok(match &abc_trait.kind { Ok(match &abc_trait.kind {
AbcTraitKind::Slot { AbcTraitKind::Slot {
@ -197,10 +197,14 @@ impl<'gc> Trait<'gc> {
type_name: if type_name.0 == 0 { type_name: if type_name.0 == 0 {
Multiname::any() Multiname::any()
} else { } else {
Multiname::from_abc_multiname_static(unit, type_name.clone(), mc)? Multiname::from_abc_multiname_static(
unit,
type_name.clone(),
uc.gc_context,
)?
}, },
default_value: if let Some(dv) = value { default_value: if let Some(dv) = value {
Some(abc_default_value(unit, dv, avm2, mc)?) Some(abc_default_value(unit, dv, uc)?)
} else { } else {
None None
}, },
@ -211,7 +215,7 @@ impl<'gc> Trait<'gc> {
attributes: trait_attribs_from_abc_traits(abc_trait), attributes: trait_attribs_from_abc_traits(abc_trait),
kind: TraitKind::Method { kind: TraitKind::Method {
disp_id: *disp_id, disp_id: *disp_id,
method: unit.load_method(method.0, mc)?, method: unit.load_method(method.0, uc.gc_context)?,
}, },
}, },
AbcTraitKind::Getter { disp_id, method } => Trait { AbcTraitKind::Getter { disp_id, method } => Trait {
@ -219,7 +223,7 @@ impl<'gc> Trait<'gc> {
attributes: trait_attribs_from_abc_traits(abc_trait), attributes: trait_attribs_from_abc_traits(abc_trait),
kind: TraitKind::Getter { kind: TraitKind::Getter {
disp_id: *disp_id, disp_id: *disp_id,
method: unit.load_method(method.0, mc)?, method: unit.load_method(method.0, uc.gc_context)?,
}, },
}, },
AbcTraitKind::Setter { disp_id, method } => Trait { AbcTraitKind::Setter { disp_id, method } => Trait {
@ -227,7 +231,7 @@ impl<'gc> Trait<'gc> {
attributes: trait_attribs_from_abc_traits(abc_trait), attributes: trait_attribs_from_abc_traits(abc_trait),
kind: TraitKind::Setter { kind: TraitKind::Setter {
disp_id: *disp_id, disp_id: *disp_id,
method: unit.load_method(method.0, mc)?, method: unit.load_method(method.0, uc.gc_context)?,
}, },
}, },
AbcTraitKind::Class { slot_id, class } => Trait { AbcTraitKind::Class { slot_id, class } => Trait {
@ -235,7 +239,7 @@ impl<'gc> Trait<'gc> {
attributes: trait_attribs_from_abc_traits(abc_trait), attributes: trait_attribs_from_abc_traits(abc_trait),
kind: TraitKind::Class { kind: TraitKind::Class {
slot_id: *slot_id, slot_id: *slot_id,
class: unit.load_class(class.0, avm2, mc)?, class: unit.load_class(class.0, uc)?,
}, },
}, },
AbcTraitKind::Function { slot_id, function } => Trait { AbcTraitKind::Function { slot_id, function } => Trait {
@ -243,7 +247,7 @@ impl<'gc> Trait<'gc> {
attributes: trait_attribs_from_abc_traits(abc_trait), attributes: trait_attribs_from_abc_traits(abc_trait),
kind: TraitKind::Function { kind: TraitKind::Function {
slot_id: *slot_id, slot_id: *slot_id,
function: unit.load_method(function.0, mc)?, function: unit.load_method(function.0, uc.gc_context)?,
}, },
}, },
AbcTraitKind::Const { AbcTraitKind::Const {
@ -258,10 +262,14 @@ impl<'gc> Trait<'gc> {
type_name: if type_name.0 == 0 { type_name: if type_name.0 == 0 {
Multiname::any() Multiname::any()
} else { } else {
Multiname::from_abc_multiname_static(unit, type_name.clone(), mc)? Multiname::from_abc_multiname_static(
unit,
type_name.clone(),
uc.gc_context,
)?
}, },
default_value: if let Some(dv) = value { default_value: if let Some(dv) = value {
Some(abc_default_value(unit, dv, avm2, mc)?) Some(abc_default_value(unit, dv, uc)?)
} else { } else {
None None
}, },

View File

@ -6,7 +6,8 @@ use crate::avm2::names::QName;
use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject}; use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject};
use crate::avm2::script::TranslationUnit; use crate::avm2::script::TranslationUnit;
use crate::avm2::string::AvmString; use crate::avm2::string::AvmString;
use crate::avm2::{Avm2, Error}; use crate::avm2::Error;
use crate::context::UpdateContext;
use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32}; use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32};
use gc_arena::{Collect, MutationContext}; use gc_arena::{Collect, MutationContext};
use std::cell::Ref; use std::cell::Ref;
@ -184,14 +185,15 @@ pub fn abc_double(translation_unit: TranslationUnit<'_>, index: Index<f64>) -> R
pub fn abc_default_value<'gc>( pub fn abc_default_value<'gc>(
translation_unit: TranslationUnit<'gc>, translation_unit: TranslationUnit<'gc>,
default: &AbcDefaultValue, default: &AbcDefaultValue,
avm2: &mut Avm2<'gc>, uc: &mut UpdateContext<'_, 'gc, '_>,
mc: MutationContext<'gc, '_>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
match default { match default {
AbcDefaultValue::Int(i) => abc_int(translation_unit, *i).map(|v| v.into()), AbcDefaultValue::Int(i) => abc_int(translation_unit, *i).map(|v| v.into()),
AbcDefaultValue::Uint(u) => abc_uint(translation_unit, *u).map(|v| v.into()), AbcDefaultValue::Uint(u) => abc_uint(translation_unit, *u).map(|v| v.into()),
AbcDefaultValue::Double(d) => abc_double(translation_unit, *d).map(|v| v.into()), AbcDefaultValue::Double(d) => abc_double(translation_unit, *d).map(|v| v.into()),
AbcDefaultValue::String(s) => translation_unit.pool_string(s.0, mc).map(|v| v.into()), AbcDefaultValue::String(s) => translation_unit
.pool_string(s.0, uc.gc_context)
.map(|v| v.into()),
AbcDefaultValue::True => Ok(true.into()), AbcDefaultValue::True => Ok(true.into()),
AbcDefaultValue::False => Ok(false.into()), AbcDefaultValue::False => Ok(false.into()),
AbcDefaultValue::Null => Ok(Value::Null), AbcDefaultValue::Null => Ok(Value::Null),
@ -202,12 +204,28 @@ pub fn abc_default_value<'gc>(
| AbcDefaultValue::Protected(ns) | AbcDefaultValue::Protected(ns)
| AbcDefaultValue::Explicit(ns) | AbcDefaultValue::Explicit(ns)
| AbcDefaultValue::StaticProtected(ns) | AbcDefaultValue::StaticProtected(ns)
| AbcDefaultValue::Private(ns) => Ok(NamespaceObject::from_namespace( | AbcDefaultValue::Private(ns) => {
Namespace::from_abc_namespace(translation_unit, ns.clone(), mc)?, let mut activation = Activation::from_nothing(uc.reborrow());
avm2.prototypes().namespace,
mc, let mut ns_proto = activation.avm2().prototypes().namespace;
let ns_constr = ns_proto
.get_property(
ns_proto,
&QName::new(Namespace::public(), "constructor"),
&mut activation,
)? )?
.into()), .coerce_to_object(&mut activation)?;
drop(activation);
Ok(NamespaceObject::from_namespace(
Namespace::from_abc_namespace(translation_unit, ns.clone(), uc.gc_context)?,
ns_constr,
ns_proto,
uc.gc_context,
)?
.into())
}
} }
} }
@ -561,7 +579,7 @@ impl<'gc> Value<'gc> {
_ => {} _ => {}
}; };
let proto = match self { let mut proto = match self {
Value::Bool(_) => activation.avm2().prototypes().boolean, Value::Bool(_) => activation.avm2().prototypes().boolean,
Value::Number(_) => activation.avm2().prototypes().number, Value::Number(_) => activation.avm2().prototypes().number,
Value::Unsigned(_) => activation.avm2().prototypes().uint, Value::Unsigned(_) => activation.avm2().prototypes().uint,
@ -569,8 +587,15 @@ impl<'gc> Value<'gc> {
Value::String(_) => activation.avm2().prototypes().string, Value::String(_) => activation.avm2().prototypes().string,
_ => unreachable!(), _ => unreachable!(),
}; };
let constr = proto
.get_property(
proto,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
PrimitiveObject::from_primitive(self.clone(), proto, activation.context.gc_context) PrimitiveObject::from_primitive(self.clone(), constr, proto, activation.context.gc_context)
} }
/// Determine if two values are abstractly equal to each other. /// Determine if two values are abstractly equal to each other.

View File

@ -441,10 +441,22 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
} }
if self.0.read().object.is_none() { if self.0.read().object.is_none() {
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mut simplebutton_proto = activation.avm2().prototypes().simplebutton;
let simplebutton_constr = simplebutton_proto
.get_property(
simplebutton_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation,
)
.unwrap()
.coerce_to_object(&mut activation)
.unwrap();
let object = Avm2StageObject::for_display_object( let object = Avm2StageObject::for_display_object(
context.gc_context, context.gc_context,
(*self).into(), (*self).into(),
context.avm2.prototypes().simplebutton, simplebutton_constr,
simplebutton_proto,
); );
self.0.write(context.gc_context).object = Some(object.into()); self.0.write(context.gc_context).object = Some(object.into());

View File

@ -1509,22 +1509,28 @@ impl<'gc> EditText<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>, display_object: DisplayObject<'gc>,
) { ) {
let mut proto = context.avm2.prototypes().textfield; let mut textfield_proto = context.avm2.prototypes().textfield;
let object: Avm2Object<'gc> =
Avm2StageObject::for_display_object(context.gc_context, display_object, proto).into();
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let constr = proto let textfield_constr = textfield_proto
.get_property( .get_property(
proto, textfield_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"), &Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation, &mut activation,
) )
.unwrap() .and_then(|v| v.coerce_to_object(&mut activation))
.coerce_to_object(&mut activation) .expect("Textfield proto needs constr");
.unwrap();
if let Err(e) = constr.call(Some(object), &[], &mut activation, Some(proto)) { let object: Avm2Object<'gc> = Avm2StageObject::for_display_object(
activation.context.gc_context,
display_object,
textfield_constr,
textfield_proto,
)
.into();
if let Err(e) =
textfield_constr.call(Some(object), &[], &mut activation, Some(textfield_constr))
{
log::error!( log::error!(
"Got {} when constructing AVM2 side of dynamic text field", "Got {} when constructing AVM2 side of dynamic text field",
e e

View File

@ -118,10 +118,10 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
if self.avm_type() == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) { if self.avm_type() == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) {
let mut allocator = || { let mut allocator = || {
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mut proto = activation.context.avm2.prototypes().shape; let mut shape_proto = activation.context.avm2.prototypes().shape;
let constr = proto let shape_constr = shape_proto
.get_property( .get_property(
proto, shape_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"), &Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation, &mut activation,
)? )?
@ -130,10 +130,11 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
let object = Avm2StageObject::for_display_object( let object = Avm2StageObject::for_display_object(
activation.context.gc_context, activation.context.gc_context,
(*self).into(), (*self).into(),
proto, shape_constr,
shape_proto,
) )
.into(); .into();
constr.call(Some(object), &[], &mut activation, Some(proto))?; shape_constr.call(Some(object), &[], &mut activation, Some(shape_proto))?;
Ok(object) Ok(object)
}; };

View File

@ -1529,7 +1529,7 @@ impl<'gc> MovieClip<'gc> {
let mut constr_thing = || { let mut constr_thing = || {
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let proto = constructor let mc_proto = constructor
.get_property( .get_property(
constructor, constructor,
&Avm2QName::new(Avm2Namespace::public(), "prototype"), &Avm2QName::new(Avm2Namespace::public(), "prototype"),
@ -1539,7 +1539,8 @@ impl<'gc> MovieClip<'gc> {
let object = Avm2StageObject::for_display_object( let object = Avm2StageObject::for_display_object(
activation.context.gc_context, activation.context.gc_context,
display_object, display_object,
proto, constructor,
mc_proto,
) )
.into(); .into();
@ -1560,7 +1561,7 @@ impl<'gc> MovieClip<'gc> {
/// will allocate the object first before doing so. This function is /// will allocate the object first before doing so. This function is
/// intended to be called from `post_instantiate`. /// intended to be called from `post_instantiate`.
fn construct_as_avm2_object(self, context: &mut UpdateContext<'_, 'gc, '_>) { fn construct_as_avm2_object(self, context: &mut UpdateContext<'_, 'gc, '_>) {
let mut constructor = self.0.read().avm2_constructor.unwrap_or_else(|| { let constructor = self.0.read().avm2_constructor.unwrap_or_else(|| {
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mut mc_proto = activation.context.avm2.prototypes().movieclip; let mut mc_proto = activation.context.avm2.prototypes().movieclip;
mc_proto mc_proto
@ -1577,14 +1578,7 @@ impl<'gc> MovieClip<'gc> {
if let Avm2Value::Object(object) = self.object2() { if let Avm2Value::Object(object) = self.object2() {
let mut constr_thing = || { let mut constr_thing = || {
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let proto = constructor constructor.call(Some(object), &[], &mut activation, Some(constructor))?;
.get_property(
constructor,
&Avm2QName::new(Avm2Namespace::public(), "prototype"),
&mut activation,
)?
.coerce_to_object(&mut activation)?;
constructor.call(Some(object), &[], &mut activation, Some(proto))?;
Ok(()) Ok(())
}; };

View File

@ -4,7 +4,7 @@ use crate::avm1::Object as Avm1Object;
use crate::avm2::{ use crate::avm2::{
Activation as Avm2Activation, Event as Avm2Event, Namespace as Avm2Namespace, Activation as Avm2Activation, Event as Avm2Event, Namespace as Avm2Namespace,
Object as Avm2Object, QName as Avm2QName, ScriptObject as Avm2ScriptObject, Object as Avm2Object, QName as Avm2QName, ScriptObject as Avm2ScriptObject,
StageObject as Avm2StageObject, Value as Avm2Value, StageObject as Avm2StageObject, TObject as Avm2TObject, Value as Avm2Value,
}; };
use crate::backend::ui::UiBackend; use crate::backend::ui::UiBackend;
use crate::config::Letterbox; use crate::config::Letterbox;
@ -518,28 +518,32 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
_instantiated_by: Instantiator, _instantiated_by: Instantiator,
_run_frame: bool, _run_frame: bool,
) { ) {
let stage_proto = context.avm2.prototypes().stage; let mut stage_proto = context.avm2.prototypes().stage;
let avm2_stage =
Avm2StageObject::for_display_object(context.gc_context, (*self).into(), stage_proto);
// TODO: Replace this when we have a convenience method for constructing AVM2 native objects.
// TODO: We should only do this if the movie is actually an AVM2 movie.
// This is necessary for EventDispatcher super-constructor to run.
use crate::avm2::TObject;
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mut proto = activation.context.avm2.prototypes().stage; let stage_constr = stage_proto
if let Err(e) = proto
.get_property( .get_property(
proto, stage_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"), &Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation, &mut activation,
) )
.and_then(|v| v.coerce_to_object(&mut activation)) .and_then(|v| v.coerce_to_object(&mut activation))
.and_then(|constr| { .expect("Stage proto needs constr");
// TODO: Stage's AS-visible constructor actually throws. Have to call non-throwing native constructor here. let avm2_stage = Avm2StageObject::for_display_object(
constr.call(Some(avm2_stage.into()), &[], &mut activation, Some(proto)) activation.context.gc_context,
}) (*self).into(),
{ stage_constr,
stage_proto,
);
// TODO: Replace this when we have a convenience method for constructing AVM2 native objects.
// TODO: We should only do this if the movie is actually an AVM2 movie.
// This is necessary for EventDispatcher super-constructor to run.
if let Err(e) = stage_constr.call(
Some(avm2_stage.into()),
&[],
&mut activation,
Some(stage_constr),
) {
log::error!("Unable to construct AVM2 Stage: {}", e); log::error!("Unable to construct AVM2 Stage: {}", e);
} }

View File

@ -1,7 +1,10 @@
//! Video player display object //! Video player display object
use crate::avm1::{Object as Avm1Object, StageObject as Avm1StageObject}; use crate::avm1::{Object as Avm1Object, StageObject as Avm1StageObject};
use crate::avm2::{Object as Avm2Object, StageObject as Avm2StageObject}; use crate::avm2::{
Activation as Avm2Activation, Namespace as Avm2Namespace, Object as Avm2Object,
QName as Avm2QName, StageObject as Avm2StageObject, TObject as Avm2TObject,
};
use crate::backend::render::BitmapInfo; use crate::backend::render::BitmapInfo;
use crate::backend::video::{EncodedFrame, VideoStreamHandle}; use crate::backend::video::{EncodedFrame, VideoStreamHandle};
use crate::bounding_box::BoundingBox; use crate::bounding_box::BoundingBox;
@ -381,13 +384,25 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> {
fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) { fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
let vm_type = self.avm_type(); let vm_type = self.avm_type();
if vm_type == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) { if vm_type == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) {
let mut video_proto = context.avm2.prototypes().video;
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let video_constr = video_proto
.get_property(
video_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation,
)
.and_then(|v| v.coerce_to_object(&mut activation))
.expect("Video proto needs constr");
let object: Avm2Object<'_> = Avm2StageObject::for_display_object( let object: Avm2Object<'_> = Avm2StageObject::for_display_object(
context.gc_context, activation.context.gc_context,
(*self).into(), (*self).into(),
context.avm2.prototypes().video, video_constr,
video_proto,
) )
.into(); .into();
self.0.write(context.gc_context).object = Some(object.into()); self.0.write(activation.context.gc_context).object = Some(object.into());
} }
} }

View File

@ -708,7 +708,7 @@ impl TextFormat {
.coerce_to_object(activation)?; .coerce_to_object(activation)?;
let mut object = Avm2ScriptObject::object(activation.context.gc_context, proto); let mut object = Avm2ScriptObject::object(activation.context.gc_context, proto);
constr.call(Some(object), &[], activation, Some(proto))?; constr.call(Some(object), &[], activation, Some(constr))?;
object.set_property( object.set_property(
object, object,
@ -849,9 +849,20 @@ impl TextFormat {
if let Some(ts) = &self.tab_stops { if let Some(ts) = &self.tab_stops {
let tab_stop_storage = ts.iter().copied().collect(); let tab_stop_storage = ts.iter().copied().collect();
let mut array_proto = activation.avm2().prototypes().array;
let array_constr = array_proto
.get_property(
array_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
let tab_stops = Avm2ArrayObject::from_array( let tab_stops = Avm2ArrayObject::from_array(
tab_stop_storage, tab_stop_storage,
activation.context.avm2.prototypes().array, array_constr,
array_proto,
activation.context.gc_context, activation.context.gc_context,
); );

View File

@ -2,7 +2,7 @@
use crate::avm1::activation::{Activation, ActivationIdentifier}; use crate::avm1::activation::{Activation, ActivationIdentifier};
use crate::avm1::{Avm1, AvmString, Object, TObject, Value}; use crate::avm1::{Avm1, AvmString, Object, TObject, Value};
use crate::avm2::Domain as Avm2Domain; use crate::avm2::{Activation as Avm2Activation, Domain as Avm2Domain};
use crate::backend::navigator::OwnedFuture; use crate::backend::navigator::OwnedFuture;
use crate::context::{ActionQueue, ActionType}; use crate::context::{ActionQueue, ActionType};
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject}; use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
@ -498,11 +498,12 @@ impl<'gc> Loader<'gc> {
_ => unreachable!(), _ => unreachable!(),
}; };
let domain = let mut activation = Avm2Activation::from_nothing(uc.reborrow());
Avm2Domain::movie_domain(uc.gc_context, uc.avm2.global_domain()); let parent_domain = activation.avm2().global_domain();
let library = uc.library.library_for_movie_mut(movie.clone()); let domain = Avm2Domain::movie_domain(&mut activation, parent_domain);
uc.library
library.set_avm2_domain(domain); .library_for_movie_mut(movie.clone())
.set_avm2_domain(domain);
if let Some(broadcaster) = broadcaster { if let Some(broadcaster) = broadcaster {
Avm1::run_stack_frame_for_method( Avm1::run_stack_frame_for_method(

View File

@ -4,7 +4,7 @@ use crate::avm1::globals::system::SystemProperties;
use crate::avm1::object::Object; use crate::avm1::object::Object;
use crate::avm1::property::Attribute; use crate::avm1::property::Attribute;
use crate::avm1::{Avm1, AvmString, ScriptObject, TObject, Timers, Value}; use crate::avm1::{Avm1, AvmString, ScriptObject, TObject, Timers, Value};
use crate::avm2::{Avm2, Domain as Avm2Domain}; use crate::avm2::{Activation as Avm2Activation, Avm2, Domain as Avm2Domain};
use crate::backend::{ use crate::backend::{
audio::{AudioBackend, AudioManager}, audio::{AudioBackend, AudioManager},
locale::LocaleBackend, locale::LocaleBackend,
@ -389,14 +389,21 @@ impl Player {
context.swf.width().to_pixels() as u32, context.swf.width().to_pixels() as u32,
context.swf.height().to_pixels() as u32, context.swf.height().to_pixels() as u32,
); );
let domain = Avm2Domain::movie_domain(context.gc_context, context.avm2.global_domain());
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let global_domain = activation.avm2().global_domain();
let domain = Avm2Domain::movie_domain(&mut activation, global_domain);
drop(activation);
context
.library
.library_for_movie_mut(context.swf.clone())
.set_avm2_domain(domain);
let root: DisplayObject = let root: DisplayObject =
MovieClip::from_movie(context.gc_context, context.swf.clone()).into(); MovieClip::from_movie(context.gc_context, context.swf.clone()).into();
let library = context.library.library_for_movie_mut(context.swf.clone());
library.set_avm2_domain(domain);
root.set_depth(context.gc_context, 0); root.set_depth(context.gc_context, 0);
let flashvars = if !context.swf.parameters().is_empty() { let flashvars = if !context.swf.parameters().is_empty() {
let object = ScriptObject::object(context.gc_context, None); let object = ScriptObject::object(context.gc_context, None);