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>,
) -> Result<bool, Error> {
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());
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);
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 {
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.
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.
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
/// configured as the anonymous class whose traits match the method's
/// declared traits.
///
/// If this is `None`, then the method did not ask for an activation object
/// and we will not construct a prototype for one.
activation_proto: Option<Object<'gc>>,
/// and we will not allocate a constructor for one.
activation_constr: Option<Object<'gc>>,
pub context: UpdateContext<'a, 'gc, 'gc_context>,
}
@ -144,8 +144,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return_value: None,
local_scope: ScriptObject::bare_object(context.gc_context),
scope: None,
base_proto: None,
activation_proto: None,
base_constr: None,
activation_constr: None,
context,
}
}
@ -185,8 +185,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return_value: None,
local_scope: ScriptObject::bare_object(context.gc_context),
scope,
base_proto: None,
activation_proto: None,
base_constr: None,
activation_constr: None,
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
/// method.
pub fn from_method(
context: UpdateContext<'a, 'gc, 'gc_context>,
mut context: UpdateContext<'a, 'gc, 'gc_context>,
method: Gc<'gc, BytecodeMethod<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
this: Option<Object<'gc>>,
arguments: &[Value<'gc>],
base_proto: Option<Object<'gc>>,
base_constr: Option<Object<'gc>>,
callee: Object<'gc>,
) -> Result<Self, Error> {
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 abc_method = method.method();
let activation_class = Class::from_method_body(
context.avm2,
context.gc_context,
translation_unit,
abc_method,
body,
)?;
let activation_class =
Class::from_method_body(&mut context, translation_unit, abc_method, body)?;
let mut dummy_activation = Activation::from_nothing(context.reborrow());
let (constr, _cinit) =
ClassObject::from_class(&mut dummy_activation, activation_class, None, scope)?;
Some(ScriptObject::bare_prototype(
context.gc_context,
activation_class,
scope,
))
drop(dummy_activation);
Some(constr)
} else {
None
};
@ -256,8 +252,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return_value: None,
local_scope: ScriptObject::bare_object(context.gc_context),
scope,
base_proto,
activation_proto,
base_constr,
activation_constr,
context,
};
@ -274,15 +270,18 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
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(
args_array,
activation
.context
.avm2
.system_prototypes
.as_ref()
.unwrap()
.array,
array_constr,
array_proto,
activation.context.gc_context,
);
@ -318,7 +317,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
context: UpdateContext<'a, 'gc, 'gc_context>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
this: Option<Object<'gc>>,
base_proto: Option<Object<'gc>>,
base_constr: Option<Object<'gc>>,
) -> Result<Self, Error> {
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,
local_scope: ScriptObject::bare_object(context.gc_context),
scope,
base_proto,
activation_proto: None,
base_constr,
activation_constr: None,
context,
})
}
@ -369,20 +368,17 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
receiver: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
let name = QName::new(Namespace::public(), "constructor");
let base_proto: Result<Object<'gc>, Error> =
self.base_proto().and_then(|p| p.proto()).ok_or_else(|| {
let base_constr: Result<Object<'gc>, Error> = self
.base_constr()
.and_then(|c| c.base_class_constr())
.ok_or_else(|| {
"Attempted to call super constructor without a superclass."
.to_string()
.into()
});
let mut base_proto = base_proto?;
let base_constr = base_constr?;
let function = base_proto
.get_property(receiver, &name, self)?
.coerce_to_object(self)?;
function.call(Some(receiver), args, self, Some(base_proto))
base_constr.call(Some(receiver), args, self, Some(base_constr))
}
/// 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
/// method was retrieved from, if one exists.
pub fn base_proto(&self) -> Option<Object<'gc>> {
self.base_proto
pub fn base_constr(&self) -> Option<Object<'gc>> {
self.base_constr
}
/// 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> {
method
.translation_unit()
.load_class(index.0, self.context.avm2, self.context.gc_context)
.load_class(index.0, &mut self.context)
}
pub fn run_actions(
@ -817,10 +813,19 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
value: Index<AbcNamespace>,
) -> Result<FrameControl<'gc>, Error> {
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(
ns,
self.context.avm2.prototypes().namespace,
ns_constr,
ns_proto,
self.context.gc_context,
)?);
Ok(FrameControl::Continue)
@ -952,11 +957,15 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
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
.get_property(receiver, &name, 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);
@ -998,12 +1007,16 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
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
.get_property(receiver, &name, self)?
.coerce_to_object(self)?;
function.call(Some(receiver), &args, self, base_proto)?;
function.call(Some(receiver), &args, self, base_constr)?;
Ok(FrameControl::Continue)
}
@ -1025,7 +1038,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
self.context.avm2.prototypes().function,
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);
@ -1041,23 +1054,23 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let args = self.context.avm2.pop_args(arg_count);
let multiname = self.pool_multiname(method, index)?;
let receiver = self.context.avm2.pop().coerce_to_object(self)?;
let name: Result<QName, Error> = receiver
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let base_proto: Result<Object<'gc>, Error> =
self.base_proto().and_then(|bp| bp.proto()).ok_or_else(|| {
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()
});
let base_proto = base_proto?;
let mut base = base_proto.construct(self, &[])?; //TODO: very hacky workaround
let base_constr = base_constr?;
let function = base
.get_property(receiver, &name?, self)?
.coerce_to_object(self)?;
let value = function.call(Some(receiver), &args, self, Some(base_proto))?;
let value = base_constr.call_instance_method(&name, Some(receiver), &args, self)?;
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 multiname = self.pool_multiname(method, index)?;
let receiver = self.context.avm2.pop().coerce_to_object(self)?;
let name: Result<QName, Error> = receiver
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let base_proto: Result<Object<'gc>, Error> =
self.base_proto().and_then(|bp| bp.proto()).ok_or_else(|| {
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()
});
let base_proto = base_proto?;
let mut base = base_proto.construct(self, &[])?; //TODO: very hacky workaround
let base_constr = base_constr?;
let function = base
.get_property(receiver, &name?, self)?
.coerce_to_object(self)?;
function.call(Some(receiver), &args, self, Some(base_proto))?;
base_constr.call_instance_method(&name, Some(receiver), &args, self)?;
Ok(FrameControl::Continue)
}
@ -1120,7 +1133,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// dynamic properties not yet set
if name.is_err()
&& !object
.as_proto_class()
.as_class()
.map(|c| c.read().is_sealed())
.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.
self.context.avm2.push(
!object
.as_proto_class()
.as_class()
.map(|c| c.read().is_sealed())
.unwrap_or(false),
)
@ -1214,22 +1227,23 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
) -> Result<FrameControl<'gc>, Error> {
let multiname = self.pool_multiname(method, index)?;
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(|| {
format!(
"Could not resolve {:?} as super property",
multiname.local_name()
)
.into()
});
let name: Result<QName, Error> = object
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?;
let value = base.get_property(object, &name?, self)?;
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()
});
let base_constr = base_constr?;
let value = base_constr.call_instance_getter(&name, Some(object), self)?;
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 multiname = self.pool_multiname(method, index)?;
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(|| {
format!(
"Could not resolve {:?} as super property",
multiname.local_name()
)
.into()
});
let name: Result<QName, Error> = object
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?;
base.set_property(object, &name?, value, self)?;
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()
});
let base_constr = base_constr?;
base_constr.call_instance_setter(&name, value, Some(object), self)?;
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> {
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
.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())?;
let object = ctor.construct(self, &args)?;
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(|| {
format!("Could not resolve property {:?}", multiname.local_name()).into()
});
let mut ctor = source
.get_property(source, &ctor_name?, self)?
.coerce_to_object(self)?;
let proto = ctor
.get_property(ctor, &QName::new(Namespace::public(), "prototype"), self)?
let ctor_name = ctor_name?;
let ctor = source
.get_property(source, &ctor_name, self)?
.coerce_to_object(self)?;
let object = proto.construct(self, &args)?;
ctor.call(Some(object), &args, self, Some(proto))?;
let object = ctor.construct(self, &args)?;
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> {
if let Some(activation_proto) = self.activation_proto {
self.context.avm2.push(ScriptObject::object(
self.context.gc_context,
activation_proto,
));
let instance = if let Some(activation_constr) = self.activation_constr {
activation_constr.construct(self, &[])?
} else {
self.context
.avm2
.push(ScriptObject::bare_object(self.context.gc_context));
}
ScriptObject::bare_object(self.context.gc_context)
};
self.context.avm2.push(instance);
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> {
let args = self.context.avm2.pop_args(num_args);
let array = ArrayStorage::from_args(&args[..]);
let array_obj = ArrayObject::from_array(
array,
self.context.avm2.system_prototypes.clone().unwrap().array,
self.context.gc_context,
);
let mut array_proto = self.context.avm2.prototypes().array;
let array_constr = array_proto
.get_property(
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);

View File

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

View File

@ -1,7 +1,7 @@
//! Application Domains
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::script::Script;
use crate::avm2::value::Value;
@ -25,7 +25,12 @@ struct DomainData<'gc> {
parent: Option<Domain<'gc>>,
/// 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> {
@ -33,33 +38,41 @@ impl<'gc> Domain<'gc> {
///
/// This is intended exclusively for creating the player globals domain,
/// 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> {
let domain_memory = ByteArrayObject::new(mc, None);
domain_memory.as_bytearray_mut(mc).unwrap().set_length(1024);
Self(GcCell::allocate(
mc,
DomainData {
defs: HashMap::new(),
parent: None,
domain_memory,
domain_memory: None,
},
))
}
/// 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);
domain_memory.as_bytearray_mut(mc).unwrap().set_length(1024);
Self(GcCell::allocate(
mc,
///
/// This function must not be called before the player globals have been
/// fully allocated.
pub fn movie_domain(
activation: &mut Activation<'_, 'gc, '_>,
parent: Domain<'gc>,
) -> Domain<'gc> {
let this = Self(GcCell::allocate(
activation.context.gc_context,
DomainData {
defs: HashMap::new(),
parent: Some(parent),
domain_memory,
domain_memory: None,
},
))
));
this.init_default_domain_memory(activation).unwrap();
this
}
/// Get the parent of this domain
@ -159,7 +172,10 @@ impl<'gc> Domain<'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(
@ -167,6 +183,41 @@ impl<'gc> Domain<'gc> {
mc: MutationContext<'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>>,
arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>,
base_proto: Option<Object<'gc>>,
base_constr: Option<Object<'gc>>,
callee: Object<'gc>,
) -> Result<Value<'gc>, Error> {
match self {
@ -93,7 +93,7 @@ impl<'gc> Executable<'gc> {
activation.context.reborrow(),
scope,
receiver,
base_proto,
base_constr,
)?;
nf(&mut activation, receiver, arguments)
@ -106,7 +106,7 @@ impl<'gc> Executable<'gc> {
bm.scope,
receiver,
arguments,
base_proto,
base_constr,
callee,
)?;

View File

@ -192,10 +192,12 @@ fn function<'gc>(
fn dynamic_class<'gc>(
mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
class: GcCell<'gc, Class<'gc>>,
mut domain: Domain<'gc>,
script: Script<'gc>,
) -> 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();
script
@ -288,7 +290,7 @@ pub fn load_player_globals<'gc>(
domain: Domain<'gc>,
) -> Result<(), Error> {
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);
// 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
// functions have call/apply in their prototypes, and that Function is also
// a subclass of Object.
let (object_proto, object_class) = object::create_proto(activation, gs);
let (fn_proto, fn_class) = function::create_proto(activation, gs, object_proto);
let object_proto = object::create_proto(activation);
let fn_proto = function::create_proto(activation, object_proto);
let object_constr = object::fill_proto(activation, object_proto, fn_proto)?;
let function_constr = function::fill_proto(activation, fn_proto, object_constr);
let object_constr = object::fill_proto(activation, gs, object_proto, fn_proto)?;
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);
dynamic_class(mc, object_constr, object_class, domain, script)?;
dynamic_class(mc, function_constr, fn_class, domain, script)?;
dynamic_class(mc, class_constr, class_class, domain, script)?;
dynamic_class(mc, object_constr, domain, script)?;
dynamic_class(mc, function_constr, domain, script)?;
dynamic_class(mc, class_constr, domain, script)?;
// 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
@ -425,6 +427,9 @@ pub fn load_player_globals<'gc>(
domain,
script,
)?;
gs.as_application_domain()
.unwrap()
.init_default_domain_memory(activation)?;
class(
activation,
@ -436,7 +441,6 @@ pub fn load_player_globals<'gc>(
class(
activation,
flash::utils::compression_algorithm::create_class(mc),
implicit_deriver,
domain,
script,
)?;

View File

@ -96,15 +96,19 @@ pub fn build_array<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
array: ArrayStorage<'gc>,
) -> 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(
array,
activation
.context
.avm2
.system_prototypes
.as_ref()
.map(|sp| sp.array)
.unwrap(),
array_constr,
array_proto,
activation.context.gc_context,
)
.into())

View File

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

View File

@ -560,6 +560,15 @@ pub fn loader_info<'gc>(
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
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 DisplayObject::ptr_eq(root, dobj) {
let movie = dobj.movie();
@ -568,7 +577,8 @@ pub fn loader_info<'gc>(
let obj = LoaderInfoObject::from_movie(
movie,
root,
activation.context.avm2.prototypes().loaderinfo,
loaderinfo_constr,
loaderinfo_proto,
activation.context.gc_context,
)?;
@ -581,7 +591,8 @@ pub fn loader_info<'gc>(
if DisplayObject::ptr_eq(dobj, activation.context.stage.into()) {
return Ok(LoaderInfoObject::from_stage(
activation.context.avm2.prototypes().loaderinfo,
loaderinfo_constr,
loaderinfo_proto,
activation.context.gc_context,
)
.into());

View File

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

View File

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

View File

@ -62,10 +62,20 @@ pub fn graphics<'gc>(
activation,
)? {
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(
activation.context.gc_context,
dobj,
graphics_constr,
graphics_proto,
));
this.set_property(

View File

@ -48,10 +48,20 @@ pub fn graphics<'gc>(
activation,
)? {
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(
activation.context.gc_context,
dobj,
graphics_constr,
graphics_proto,
));
this.set_property(

View File

@ -142,10 +142,18 @@ pub fn clone<'gc>(
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
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(
activation.context.gc_context,
evt_constr,
Some(evt_proto),
evt.clone(),
)

View File

@ -10,9 +10,17 @@ fn create_point<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
coords: (f64, f64),
) -> 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 new_point = proto.construct(activation, &args)?;
let new_point = point_constr.construct(activation, &args)?;
instance_init(activation, Some(new_point), &args)?;
Ok(new_point.into())

View File

@ -39,10 +39,20 @@ pub fn current_domain<'gc>(
) -> Result<Value<'gc>, Error> {
let globals = activation.scope().map(|s| s.read().globals());
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 {
return Ok(DomainObject::from_domain(
activation.context.gc_context,
Some(activation.context.avm2.prototypes().application_domain),
appdomain_constr,
Some(appdomain_proto),
appdomain,
)
.into());
@ -59,9 +69,19 @@ pub fn parent_domain<'gc>(
) -> Result<Value<'gc>, Error> {
if let Some(appdomain) = this.and_then(|this| this.as_application_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(
activation.context.gc_context,
Some(activation.context.avm2.prototypes().application_domain),
appdomain_constr,
Some(appdomain_proto),
parent_domain,
)
.into());

View File

@ -9,7 +9,6 @@ use crate::avm2::object::{ClassObject, FunctionObject, Object, ScriptObject, TOb
use crate::avm2::scope::Scope;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::GcCell;
/// Implements `Function`'s instance initializer.
pub fn instance_init<'gc>(
@ -90,12 +89,21 @@ fn apply<'gc>(
/// 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>(
activation: &mut Activation<'_, 'gc, '_>,
globals: 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(
QName::new(Namespace::public(), "Function"),
Some(QName::new(Namespace::public(), "Object").into()),
@ -104,22 +112,7 @@ pub fn create_proto<'gc>(
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(
activation.context.gc_context,
QName::new(Namespace::as3_namespace(), "call"),
@ -135,7 +128,9 @@ pub fn fill_proto<'gc>(
ClassObject::from_builtin_constr(
activation.context.gc_context,
Some(super_class),
Some(super_constr),
function_class,
Some(scope),
function_proto,
function_proto,
)

View File

@ -9,7 +9,6 @@ use crate::avm2::scope::Scope;
use crate::avm2::traits::Trait;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::GcCell;
/// Implements `Object`'s instance initializer.
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,
/// but does not actually fill it with methods. That requires a valid function
/// prototype, and is thus done by `fill_proto` below.
pub fn create_proto<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
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)
pub fn create_proto<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Object<'gc> {
ScriptObject::bare_object(activation.context.gc_context)
}
/// 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.
pub fn fill_proto<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
globals: Object<'gc>,
mut object_proto: Object<'gc>,
fn_proto: Object<'gc>,
) -> Result<Object<'gc>, Error> {
let gc_context = activation.context.gc_context;
object_proto.install_dynamic_property(
gc_context,
QName::new(Namespace::public(), "hasOwnProperty"),
@ -240,6 +211,39 @@ pub fn fill_proto<'gc>(
FunctionObject::from_builtin(gc_context, value_of, fn_proto).into(),
)?;
let object_constr = ClassObject::from_builtin_constr(gc_context, None, object_proto, fn_proto)?;
Ok(object_constr)
let object_class = Class::new(
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),
};
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(
storage,
activation
.context
.avm2
.system_prototypes
.as_ref()
.map(|sp| sp.array)
.unwrap(),
regexp_constr,
regexp_proto,
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)
}
/// 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
/// defined prototype methods for ES3-style classes.
fn get_base_proto(self, name: &QName<'gc>) -> Result<Option<Object<'gc>>, Error> {
if self.provides_trait(name)? {
fn find_base_constr_for_trait(self, name: &QName<'gc>) -> Result<Option<Object<'gc>>, Error> {
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()));
}
if let Some(proto) = self.proto() {
return proto.get_base_proto(name);
if let Some(base) = self.base_class_constr() {
return base.find_base_constr_for_trait(name);
}
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.
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.
///
/// 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.
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
/// 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>>,
_arguments: &[Value<'gc>],
_activation: &mut Activation<'_, 'gc, '_>,
_base_proto: Option<Object<'gc>>,
_base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, Error> {
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
/// called on the prototype to create a new object. The prototype may
/// construct any object implementation it wants, however, it's expected
/// to produce a like `TObject` implementor with itself as the new object's
/// proto.
/// called on the constructor to create a new object. The constructor is
/// then expected to perform the following steps, in order:
///
/// After construction, the constructor function is `call`ed with the new
/// object as `this` to initialize the object.
///
/// `construct`ed objects should instantiate instance traits of the class
/// that this prototype represents.
///
/// The arguments passed to the constructor are provided here; however, all
/// object construction should happen in `call`, not `new`. `new` exists
/// purely so that host objects can be constructed by the VM.
/// 1. Allocate the instance object. If the constructor is a `Class`, then
/// use the class's instance deriver to allocate the object. Otherwise,
/// allocate it as the same type as the constructor's explicit `prototype`.
/// 2. Associate the instance object with the constructor's explicit
/// `prototype`.
/// 3. If the instance has traits, install them at this time.
/// 4. Call the constructor method with the newly-allocated object as
/// reciever. For `Function`s, this is just the function's method.
/// 5. Yield the allocated object. (The return values of constructors are
/// ignored.)
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<Object<'gc>, Error>;
self,
_activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
Err("Object is not constructable".into())
}
/// Construct a host object prototype of some kind and return it.
///
/// This is called specifically to construct prototypes. The primary
/// difference is that a new class and scope closure are defined here.
/// Objects constructed from the new prototype should use that new class
/// 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>;
/// This is called specifically to allocate old-style ES3 instances. The
/// returned object should have no properties upon it.
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error>;
/// 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
@ -796,7 +930,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// coercions.
fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
let class_name = self
.as_proto_class()
.as_class()
.map(|c| c.read().name().local_name())
.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).
fn to_locale_string(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
let class_name = self
.as_proto_class()
.as_class()
.map(|c| c.read().name().local_name())
.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.
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.
fn base_class_constr(self) -> Option<Object<'gc>> {
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.
fn as_executable(&self) -> Option<Executable<'gc>> {
None

View File

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

View File

@ -58,11 +58,20 @@ impl<'gc> ClassObject<'gc> {
base_class_constr: Option<Object<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> 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();
derive(base_class_constr, activation, class, scope)?
derive(base_class_constr, base_proto, activation)?
} 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)
@ -160,14 +169,11 @@ impl<'gc> ClassObject<'gc> {
pub fn from_builtin_constr(
mc: MutationContext<'gc, '_>,
base_class_constr: Option<Object<'gc>>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
mut prototype: Object<'gc>,
fn_proto: Object<'gc>,
) -> 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 =
Executable::from_method(class.read().instance_init(), scope, None, mc);
let mut base: Object<'gc> = ClassObject(GcCell::allocate(
@ -225,31 +231,38 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
receiver: Option<Object<'gc>>,
arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>,
base_proto: Option<Object<'gc>>,
base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, Error> {
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(
&self,
mut self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
arguments: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
Ok(ClassObject(GcCell::allocate(
activation.context.gc_context,
self.0.read().clone(),
))
.into())
let class = self.as_class().ok_or("Cannot construct classless class!")?;
let deriver = class.read().instance_deriver();
let constr: Object<'gc> = self.into();
let prototype = self
.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(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_class: GcCell<'gc, Class<'gc>>,
_scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
Ok(ClassObject(GcCell::allocate(
activation.context.gc_context,
self.0.read().clone(),

View File

@ -132,18 +132,6 @@ macro_rules! impl_avm2_custom_object {
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>>> {
self.0.read().$field.get_scope()
}
@ -152,10 +140,6 @@ macro_rules! impl_avm2_custom_object {
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 {
self.0.read().$field.has_instantiated_property(name)
}
@ -204,6 +188,14 @@ macro_rules! impl_avm2_custom_object {
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(
&mut self,
mc: MutationContext<'gc, '_>,

View File

@ -75,19 +75,14 @@ impl<'gc> TObject<'gc> for DispatchObject<'gc> {
impl_avm2_custom_object_properties!(base);
fn construct(
&self,
self,
_activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
Err("Cannot construct internal event dispatcher structures.".into())
}
fn derive(
&self,
_activation: &mut Activation<'_, 'gc, '_>,
_class: GcCell<'gc, Class<'gc>>,
_scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
fn derive(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
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.
pub fn appdomain_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let scope = constr
.get_scope()
.ok_or("Constructor has an empty scope stack")?;
let domain = scope
.unwrap()
.read()
.globals()
.as_application_domain()
.unwrap();
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
.ok_or("Constructor scope must have an appdomain at the bottom of it's scope stack")?;
DomainObject::derive(
activation.context.gc_context,
base_proto,
domain,
class,
scope,
)
DomainObject::derive(constr, proto, domain, activation.context.gc_context)
}
#[derive(Clone, Collect, Debug, Copy)]
@ -59,28 +47,37 @@ pub struct DomainObjectData<'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(
mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Option<Object<'gc>>,
domain: Domain<'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()
}
/// Construct a primitive subclass.
pub fn derive(
mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Object<'gc>,
domain: Domain<'gc>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(DomainObject(GcCell::allocate(mc, DomainObjectData { base, domain })).into())
}
@ -100,40 +97,19 @@ impl<'gc> TObject<'gc> for DomainObject<'gc> {
Ok(this.into())
}
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::DomainObject(*self);
let parent_domain = if let Some(parent_domain) = args
.get(0)
.cloned()
.unwrap_or(Value::Undefined)
.coerce_to_object(activation)?
.as_application_domain()
{
parent_domain
} else {
activation.context.avm2.global_domain()
};
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let mut this: Object<'gc> = Object::DomainObject(*self);
let constr = this
.get_property(
this,
&QName::new(Namespace::public(), "constructor"),
activation,
)?
.coerce_to_object(activation)?;
Ok(DomainObject::from_domain(
activation.context.gc_context,
Some(this),
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,
constr,
Some(this),
activation.context.avm2.global_domain(),
))

View File

@ -17,24 +17,14 @@ use std::cell::{Ref, RefMut};
/// A class instance deriver that constructs Event objects.
pub fn event_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
Ok(EventObject::derive(
base_proto,
constr,
proto,
activation.context.gc_context,
class,
scope,
))
}
@ -56,25 +46,23 @@ impl<'gc> EventObject<'gc> {
/// Convert a bare event into it's object representation.
pub fn from_event(
mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Option<Object<'gc>>,
event: Event<'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()
}
/// Instantiate an event subclass.
pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
EventObject(GcCell::allocate(
mc,
@ -91,33 +79,17 @@ impl<'gc> TObject<'gc> for EventObject<'gc> {
impl_avm2_custom_object!(base);
impl_avm2_custom_object_properties!(base);
fn construct(
&self,
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(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(Some((*self).into()), ScriptObjectClass::NoClass);
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::EventObject(*self);
Ok(Self::derive(
this,
Ok(EventObject(GcCell::allocate(
activation.context.gc_context,
class,
scope,
EventObjectData {
base,
event: Event::new(""),
},
))
.into())
}
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>>,
arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>,
base_proto: Option<Object<'gc>>,
base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, Error> {
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 {
Err("Not a callable function!".into())
}
}
fn construct(
&self,
mut self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
arguments: &[Value<'gc>],
) -> 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 base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -123,23 +140,4 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
))
.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.
pub fn loaderinfo_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
LoaderInfoObject::derive(base_proto, activation.context.gc_context, class, scope)
LoaderInfoObject::derive(constr, proto, activation.context.gc_context)
}
/// Represents a thing which can be loaded by a loader.
@ -73,10 +64,12 @@ impl<'gc> LoaderInfoObject<'gc> {
pub fn from_movie(
movie: Arc<SwfMovie>,
root: DisplayObject<'gc>,
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> 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));
Ok(LoaderInfoObject(GcCell::allocate(
@ -90,8 +83,13 @@ impl<'gc> LoaderInfoObject<'gc> {
}
/// Create a loader info object for the stage.
pub fn from_stage(base_proto: Object<'gc>, mc: MutationContext<'gc, '_>) -> Object<'gc> {
let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass);
pub fn from_stage(
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(
mc,
@ -105,15 +103,12 @@ impl<'gc> LoaderInfoObject<'gc> {
/// Construct a loader-info subclass.
pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(LoaderInfoObject(GcCell::allocate(
mc,
@ -131,18 +126,14 @@ impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
impl_avm2_custom_object_properties!(base);
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())
} else {
Ok("[object Object]".into())
}
}
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::LoaderInfoObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -156,28 +147,6 @@ impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
.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
fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> {
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.
pub fn namespace_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
NamespaceObject::derive(base_proto, activation.context.gc_context, class, scope)
NamespaceObject::derive(constr, proto, activation.context.gc_context)
}
/// An Object which represents a boxed namespace name.
@ -51,10 +42,12 @@ impl<'gc> NamespaceObject<'gc> {
/// Box a namespace into an object.
pub fn from_namespace(
namespace: Namespace<'gc>,
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> 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(
mc,
@ -65,15 +58,12 @@ impl<'gc> NamespaceObject<'gc> {
/// Construct a namespace subclass.
pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(NamespaceObject(GcCell::allocate(
mc,
@ -102,11 +92,7 @@ impl<'gc> TObject<'gc> for NamespaceObject<'gc> {
Some(Ref::map(self.0.read(), |s| &s.namespace))
}
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::NamespaceObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -119,26 +105,4 @@ impl<'gc> TObject<'gc> for NamespaceObject<'gc> {
))
.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.
pub fn primitive_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
PrimitiveObject::derive(base_proto, activation.context.gc_context, class, scope)
PrimitiveObject::derive(constr, proto, activation.context.gc_context)
}
/// 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.
pub fn from_primitive(
primitive: Value<'gc>,
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> {
@ -59,7 +51,8 @@ impl<'gc> PrimitiveObject<'gc> {
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(
mc,
@ -70,15 +63,12 @@ impl<'gc> PrimitiveObject<'gc> {
/// Construct a primitive subclass.
pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(PrimitiveObject(GcCell::allocate(
mc,
@ -104,7 +94,7 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> {
val @ Value::Integer(_) | val @ Value::Unsigned(_) => Ok(val),
_ => {
let class_name = self
.as_proto_class()
.as_class()
.map(|c| c.read().name().local_name())
.unwrap_or_else(|| "Object".into());
@ -117,11 +107,7 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> {
Ok(self.0.read().primitive.clone())
}
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::PrimitiveObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -135,28 +121,6 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> {
.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>>> {
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.
pub fn regexp_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
Ok(RegExpObject::derive(
base_proto,
constr,
proto,
activation.context.gc_context,
class,
scope,
))
}
@ -54,25 +44,23 @@ pub struct RegExpObjectData<'gc> {
impl<'gc> RegExpObject<'gc> {
pub fn from_regexp(
mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
base_proto: Option<Object<'gc>>,
regexp: RegExp<'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()
}
/// Instantiate a regexp subclass.
pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
RegExpObject(GcCell::allocate(
mc,
@ -89,32 +77,17 @@ impl<'gc> TObject<'gc> for RegExpObject<'gc> {
impl_avm2_custom_object!(base);
impl_avm2_custom_object_properties!(base);
fn construct(
&self,
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(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(Some((*self).into()), ScriptObjectClass::NoClass);
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::RegExpObject(*self);
Ok(Self::derive(
this,
Ok(RegExpObject(GcCell::allocate(
activation.context.gc_context,
class,
scope,
RegExpObjectData {
base,
regexp: RegExp::new(""),
},
))
.into())
}
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)]
#[collect(no_drop)]
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.
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.
NoClass,
}
@ -172,18 +172,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
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>>> {
self.0.read().get_scope()
}
@ -207,10 +195,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
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 {
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
}
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ScriptObject(*self);
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> {
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())
} else {
Ok("[object Object]".into())
@ -360,6 +325,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>> {
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> {
@ -375,26 +348,6 @@ impl<'gc> ScriptObject<'gc> {
.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.
pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> {
ScriptObject(GcCell::allocate(
@ -404,18 +357,15 @@ impl<'gc> ScriptObject<'gc> {
.into()
}
/// Construct a prototype for an ES4 class.
pub fn prototype(
/// Construct an instance with a class and scope stack.
pub fn instance(
mc: MutationContext<'gc, '_>,
constr: Object<'gc>,
proto: Object<'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(Some(proto), script_class),
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr)),
))
.into()
}
@ -443,7 +393,15 @@ impl<'gc> ScriptObjectData<'gc> {
let prop = self.values.get(name);
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 {
Ok(Value::Undefined.into())
}
@ -456,6 +414,7 @@ impl<'gc> ScriptObjectData<'gc> {
value: Value<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> {
let constr = self.as_constr();
let slot_id = if let Some(prop) = self.values.get(name) {
if let Some(slot_id) = prop.slot_id() {
Some(slot_id)
@ -473,8 +432,11 @@ impl<'gc> ScriptObjectData<'gc> {
Ok(Value::Undefined.into())
} else if self.values.contains_key(name) {
let prop = self.values.get_mut(name).unwrap();
let proto = self.proto;
prop.set(receiver, activation.base_proto().or(proto), value)
prop.set(
receiver,
Some(activation.base_constr().or(constr).unwrap_or(receiver)),
value,
)
} else {
//TODO: Not all classes are dynamic like this
self.enumerants.push(name.clone());
@ -492,6 +454,7 @@ impl<'gc> ScriptObjectData<'gc> {
value: Value<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> {
let constr = self.as_constr();
if let Some(prop) = self.values.get_mut(name) {
if let Some(slot_id) = prop.slot_id() {
// 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)?;
Ok(Value::Undefined.into())
} else {
let proto = self.proto;
prop.init(receiver, activation.base_proto().or(proto), value)
prop.init(
receiver,
Some(activation.base_constr().or(constr).unwrap_or(receiver)),
value,
)
}
} else {
//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> {
match &self.class {
//Class constructors have local traits only.
ScriptObjectClass::ClassConstructor(..) => {
ScriptObjectClass::ClassConstructor(class, ..) => {
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)
}
//Prototypes do not have traits available locally, but they provide
//traits instead.
ScriptObjectClass::InstancePrototype(..) => Ok(Vec::new()),
//Class instances have all instance traits from all superclasses.
ScriptObjectClass::ClassInstance(constr) => {
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
//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();
cur_constr = constr.base_class_constr();
}
for proto in chain.iter().rev() {
proto.get_provided_trait(name, &mut known_traits)?;
let mut known_traits = Vec::new();
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)
}
// 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> {
match &self.class {
//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),
//Class instances have all instance slot traits from all superclasses.
ScriptObjectClass::ClassInstance(constr) => {
let mut cur_constr = Some(*constr);
//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));
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));
}
proto = p.proto();
cur_constr = constr.base_class_constr();
}
Ok(None)
}
}
}
pub fn get_provided_trait(
&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)
}
// Bare objects, ES3 objects, and prototypes do not have traits.
ScriptObjectClass::NoClass => Ok(None),
}
}
@ -674,38 +618,30 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn has_trait(&self, name: &QName<'gc>) -> Result<bool, Error> {
match &self.class {
//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
//through them to find traits (see `provides_trait`)
ScriptObjectClass::InstancePrototype(..) => Ok(false),
//Class instances have instance traits from any class in the base
//class chain.
ScriptObjectClass::ClassInstance(constr) => {
let mut cur_constr = Some(*constr);
//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 p.provides_trait(name)? {
while let Some(constr) = cur_constr {
let cur_class = constr
.as_class()
.ok_or("Object is not a class constructor")?;
if cur_class.read().has_instance_trait(name) {
return Ok(true);
}
proto = p.proto();
cur_constr = constr.base_class_constr();
}
Ok(false)
}
}
}
pub fn provides_trait(&self, name: &QName<'gc>) -> Result<bool, Error> {
match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => {
Ok(class.read().has_class_trait(name))
}
ScriptObjectClass::InstancePrototype(class, ..) => {
Ok(class.read().has_instance_trait(name))
}
// Bare objects, ES3 objects, and prototypes do not have traits.
ScriptObjectClass::NoClass => Ok(false),
}
}
@ -713,8 +649,8 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn get_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
match &self.class {
ScriptObjectClass::ClassConstructor(_class, scope) => *scope,
ScriptObjectClass::InstancePrototype(_class, scope) => *scope,
ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_scope()),
ScriptObjectClass::ClassInstance(constr) => constr.get_scope(),
ScriptObjectClass::NoClass => None,
}
}
@ -725,11 +661,7 @@ impl<'gc> ScriptObjectData<'gc> {
}
}
let trait_ns = match self.class {
ScriptObjectClass::ClassConstructor(..) => self.resolve_any_trait(local_name)?,
ScriptObjectClass::NoClass => self.resolve_any_trait(local_name)?,
_ => None,
};
let trait_ns = self.resolve_any_trait(local_name)?;
if trait_ns.is_none() {
if let Some(proto) = self.proto() {
@ -757,8 +689,21 @@ impl<'gc> ScriptObjectData<'gc> {
ScriptObjectClass::ClassConstructor(class, ..) => {
Ok(class.read().resolve_any_class_trait(local_name))
}
ScriptObjectClass::InstancePrototype(class, ..) => {
Ok(class.read().resolve_any_instance_trait(local_name))
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(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),
}
@ -989,8 +934,26 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn as_class(&self) -> Option<GcCell<'gc, Class<'gc>>> {
match self.class {
ScriptObjectClass::ClassConstructor(class, _) => Some(class),
ScriptObjectClass::InstancePrototype(class, _) => Some(class),
ScriptObjectClass::ClassInstance(constr) => constr.as_class(),
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.
pub fn stage_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
StageObject::derive(base_proto, activation.context.gc_context, class, scope)
StageObject::derive(constr, proto, activation.context.gc_context)
}
#[derive(Clone, Collect, Debug, Copy)]
@ -50,12 +41,16 @@ impl<'gc> StageObject<'gc> {
pub fn for_display_object(
mc: MutationContext<'gc, '_>,
display_object: DisplayObject<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
) -> Self {
Self(GcCell::allocate(
mc,
StageObjectData {
base: ScriptObjectData::base_new(Some(proto), ScriptObjectClass::NoClass),
base: ScriptObjectData::base_new(
Some(proto),
ScriptObjectClass::ClassInstance(constr),
),
display_object: Some(display_object),
},
))
@ -63,15 +58,12 @@ impl<'gc> StageObject<'gc> {
/// Construct a stage object subclass.
pub fn derive(
base_proto: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
Ok(StageObject(GcCell::allocate(
mc,
@ -191,18 +183,6 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
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>>> {
self.0.read().base.get_scope()
}
@ -226,10 +206,6 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
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 {
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()
}
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>> {
self.0.read().display_object
}
@ -300,11 +284,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
Err("Not a callable function!".into())
}
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::StageObject(*self);
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
@ -318,28 +298,6 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
.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> {
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.
pub fn xml_deriver<'gc>(
mut constr: Object<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base_proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
XmlObject::derive(base_proto, activation.context.gc_context, class, scope)
XmlObject::derive(constr, proto, activation.context.gc_context)
}
#[derive(Clone, Collect, Debug, Copy)]
@ -45,15 +36,12 @@ pub struct XmlObjectData<'gc> {
impl<'gc> XmlObject<'gc> {
/// Instantiate an xml subclass.
pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
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_properties!(base);
fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::XmlObject(*self);
Ok(Self::empty_object(
activation.context.gc_context,
Some(this),
))
}
let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass);
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::XmlObject(*self);
Self::derive(this, activation.context.gc_context, class, scope)
Ok(XmlObject(GcCell::allocate(
activation.context.gc_context,
XmlObjectData { base },
))
.into())
}
fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,8 @@ use crate::avm2::names::QName;
use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject};
use crate::avm2::script::TranslationUnit;
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 gc_arena::{Collect, MutationContext};
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>(
translation_unit: TranslationUnit<'gc>,
default: &AbcDefaultValue,
avm2: &mut Avm2<'gc>,
mc: MutationContext<'gc, '_>,
uc: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Value<'gc>, Error> {
match default {
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::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::False => Ok(false.into()),
AbcDefaultValue::Null => Ok(Value::Null),
@ -202,12 +204,28 @@ pub fn abc_default_value<'gc>(
| AbcDefaultValue::Protected(ns)
| AbcDefaultValue::Explicit(ns)
| AbcDefaultValue::StaticProtected(ns)
| AbcDefaultValue::Private(ns) => Ok(NamespaceObject::from_namespace(
Namespace::from_abc_namespace(translation_unit, ns.clone(), mc)?,
avm2.prototypes().namespace,
mc,
)?
.into()),
| AbcDefaultValue::Private(ns) => {
let mut activation = Activation::from_nothing(uc.reborrow());
let mut ns_proto = activation.avm2().prototypes().namespace;
let ns_constr = ns_proto
.get_property(
ns_proto,
&QName::new(Namespace::public(), "constructor"),
&mut activation,
)?
.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::Number(_) => activation.avm2().prototypes().number,
Value::Unsigned(_) => activation.avm2().prototypes().uint,
@ -569,8 +587,15 @@ impl<'gc> Value<'gc> {
Value::String(_) => activation.avm2().prototypes().string,
_ => 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.

View File

@ -441,10 +441,22 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
}
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(
context.gc_context,
(*self).into(),
context.avm2.prototypes().simplebutton,
simplebutton_constr,
simplebutton_proto,
);
self.0.write(context.gc_context).object = Some(object.into());

View File

@ -1509,22 +1509,28 @@ impl<'gc> EditText<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
) {
let mut proto = context.avm2.prototypes().textfield;
let object: Avm2Object<'gc> =
Avm2StageObject::for_display_object(context.gc_context, display_object, proto).into();
let mut textfield_proto = context.avm2.prototypes().textfield;
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let constr = proto
let textfield_constr = textfield_proto
.get_property(
proto,
textfield_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation,
)
.unwrap()
.coerce_to_object(&mut activation)
.unwrap();
.and_then(|v| v.coerce_to_object(&mut activation))
.expect("Textfield proto needs constr");
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!(
"Got {} when constructing AVM2 side of dynamic text field",
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) {
let mut allocator = || {
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mut proto = activation.context.avm2.prototypes().shape;
let constr = proto
let mut shape_proto = activation.context.avm2.prototypes().shape;
let shape_constr = shape_proto
.get_property(
proto,
shape_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation,
)?
@ -130,10 +130,11 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
let object = Avm2StageObject::for_display_object(
activation.context.gc_context,
(*self).into(),
proto,
shape_constr,
shape_proto,
)
.into();
constr.call(Some(object), &[], &mut activation, Some(proto))?;
shape_constr.call(Some(object), &[], &mut activation, Some(shape_proto))?;
Ok(object)
};

View File

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

View File

@ -4,7 +4,7 @@ use crate::avm1::Object as Avm1Object;
use crate::avm2::{
Activation as Avm2Activation, Event as Avm2Event, Namespace as Avm2Namespace,
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::config::Letterbox;
@ -518,28 +518,32 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
_instantiated_by: Instantiator,
_run_frame: bool,
) {
let 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 stage_proto = context.avm2.prototypes().stage;
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mut proto = activation.context.avm2.prototypes().stage;
if let Err(e) = proto
let stage_constr = stage_proto
.get_property(
proto,
stage_proto,
&Avm2QName::new(Avm2Namespace::public(), "constructor"),
&mut activation,
)
.and_then(|v| v.coerce_to_object(&mut activation))
.and_then(|constr| {
// TODO: Stage's AS-visible constructor actually throws. Have to call non-throwing native constructor here.
constr.call(Some(avm2_stage.into()), &[], &mut activation, Some(proto))
})
{
.expect("Stage proto needs constr");
let avm2_stage = Avm2StageObject::for_display_object(
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);
}

View File

@ -1,7 +1,10 @@
//! Video player display object
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::video::{EncodedFrame, VideoStreamHandle};
use crate::bounding_box::BoundingBox;
@ -381,13 +384,25 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> {
fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
let vm_type = self.avm_type();
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(
context.gc_context,
activation.context.gc_context,
(*self).into(),
context.avm2.prototypes().video,
video_constr,
video_proto,
)
.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)?;
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,
@ -849,9 +849,20 @@ impl TextFormat {
if let Some(ts) = &self.tab_stops {
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(
tab_stop_storage,
activation.context.avm2.prototypes().array,
array_constr,
array_proto,
activation.context.gc_context,
);

View File

@ -2,7 +2,7 @@
use crate::avm1::activation::{Activation, ActivationIdentifier};
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::context::{ActionQueue, ActionType};
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
@ -498,11 +498,12 @@ impl<'gc> Loader<'gc> {
_ => unreachable!(),
};
let domain =
Avm2Domain::movie_domain(uc.gc_context, uc.avm2.global_domain());
let library = uc.library.library_for_movie_mut(movie.clone());
library.set_avm2_domain(domain);
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
let parent_domain = activation.avm2().global_domain();
let domain = Avm2Domain::movie_domain(&mut activation, parent_domain);
uc.library
.library_for_movie_mut(movie.clone())
.set_avm2_domain(domain);
if let Some(broadcaster) = broadcaster {
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::property::Attribute;
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::{
audio::{AudioBackend, AudioManager},
locale::LocaleBackend,
@ -389,14 +389,21 @@ impl Player {
context.swf.width().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 =
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);
let flashvars = if !context.swf.parameters().is_empty() {
let object = ScriptObject::object(context.gc_context, None);