diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 415d06f16..7c171b3ed 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -144,8 +144,20 @@ impl<'gc> Avm2<'gc> { target: Object<'gc>, ) -> Result { 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)?; diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index e07a9277a..bea835983 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -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>>, - /// 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>, + base_constr: Option>, - /// 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>, + /// and we will not allocate a constructor for one. + activation_constr: Option>, 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>>, this: Option>, arguments: &[Value<'gc>], - base_proto: Option>, + base_constr: Option>, callee: Object<'gc>, ) -> Result { 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>>, this: Option>, - base_proto: Option>, + base_constr: Option>, ) -> Result { 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, Error> { - let name = QName::new(Namespace::public(), "constructor"); - let base_proto: Result, Error> = - self.base_proto().and_then(|p| p.proto()).ok_or_else(|| { + let base_constr: Result, 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> { - self.base_proto + pub fn base_constr(&self) -> Option> { + 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>, 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, ) -> Result, 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 = receiver .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let base_proto: Result, Error> = - self.base_proto().and_then(|bp| bp.proto()).ok_or_else(|| { + let name = name?; + + let base_constr: Result, 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 = receiver .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let base_proto: Result, Error> = - self.base_proto().and_then(|bp| bp.proto()).ok_or_else(|| { + let name = name?; + + let base_constr: Result, 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, Error> { let multiname = self.pool_multiname(method, index)?; let object = self.context.avm2.pop().coerce_to_object(self)?; - let base_proto: Result, 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 = base.resolve_multiname(&multiname)?.ok_or_else(|| { - format!( - "Could not resolve {:?} as super property", - multiname.local_name() - ) - .into() - }); + let name: Result = 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, 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, 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 = base.resolve_multiname(&multiname)?.ok_or_else(|| { - format!( - "Could not resolve {:?} as super property", - multiname.local_name() - ) - .into() - }); + let name: Result = 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, 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, 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, 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, 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); diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 697bab503..dd5108c88 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -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>>, ) -> Result, 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>>, ) -> Result, 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, 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, diff --git a/core/src/avm2/domain.rs b/core/src/avm2/domain.rs index 7015cde55..142141a32 100644 --- a/core/src/avm2/domain.rs +++ b/core/src/avm2/domain.rs @@ -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>, /// 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>, } 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(()) } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index aeae1758a..8205b8f2d 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -82,7 +82,7 @@ impl<'gc> Executable<'gc> { unbound_reciever: Option>, arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc, '_>, - base_proto: Option>, + base_constr: Option>, callee: Object<'gc>, ) -> Result, 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, )?; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 9623c664f..0179c9ce5 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -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, )?; diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index dbb4f0026..6b2697cfa 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -96,15 +96,19 @@ pub fn build_array<'gc>( activation: &mut Activation<'_, 'gc, '_>, array: ArrayStorage<'gc>, ) -> Result, 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()) diff --git a/core/src/avm2/globals/class.rs b/core/src/avm2/globals/class.rs index 6f874b882..c882ef69d 100644 --- a/core/src/avm2/globals/class.rs +++ b/core/src/avm2/globals/class.rs @@ -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) } diff --git a/core/src/avm2/globals/flash/display/displayobject.rs b/core/src/avm2/globals/flash/display/displayobject.rs index 64a64d10d..5ce0a9490 100644 --- a/core/src/avm2/globals/flash/display/displayobject.rs +++ b/core/src/avm2/globals/flash/display/displayobject.rs @@ -560,6 +560,15 @@ pub fn loader_info<'gc>( _args: &[Value<'gc>], ) -> Result, 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()); diff --git a/core/src/avm2/globals/flash/display/loaderinfo.rs b/core/src/avm2/globals/flash/display/loaderinfo.rs index a6be3d327..0ae1e11a8 100644 --- a/core/src/avm2/globals/flash/display/loaderinfo.rs +++ b/core/src/avm2/globals/flash/display/loaderinfo.rs @@ -66,11 +66,21 @@ pub fn application_domain<'gc>( ) -> Result, 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 diff --git a/core/src/avm2/globals/flash/display/movieclip.rs b/core/src/avm2/globals/flash/display/movieclip.rs index 635952227..c054f1386 100644 --- a/core/src/avm2/globals/flash/display/movieclip.rs +++ b/core/src/avm2/globals/flash/display/movieclip.rs @@ -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()); diff --git a/core/src/avm2/globals/flash/display/shape.rs b/core/src/avm2/globals/flash/display/shape.rs index 380b3f149..2169eecdf 100644 --- a/core/src/avm2/globals/flash/display/shape.rs +++ b/core/src/avm2/globals/flash/display/shape.rs @@ -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( diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index e93981d65..e4b06dc36 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -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( diff --git a/core/src/avm2/globals/flash/events/event.rs b/core/src/avm2/globals/flash/events/event.rs index 46d4f0603..ed81f49bf 100644 --- a/core/src/avm2/globals/flash/events/event.rs +++ b/core/src/avm2/globals/flash/events/event.rs @@ -142,10 +142,18 @@ pub fn clone<'gc>( _args: &[Value<'gc>], ) -> Result, 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(), ) diff --git a/core/src/avm2/globals/flash/geom/point.rs b/core/src/avm2/globals/flash/geom/point.rs index 73d0f5c56..8dd3d58d7 100644 --- a/core/src/avm2/globals/flash/geom/point.rs +++ b/core/src/avm2/globals/flash/geom/point.rs @@ -10,9 +10,17 @@ fn create_point<'gc>( activation: &mut Activation<'_, 'gc, '_>, coords: (f64, f64), ) -> Result, 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()) diff --git a/core/src/avm2/globals/flash/system/application_domain.rs b/core/src/avm2/globals/flash/system/application_domain.rs index 2bc9e93ad..479efe7fd 100644 --- a/core/src/avm2/globals/flash/system/application_domain.rs +++ b/core/src/avm2/globals/flash/system/application_domain.rs @@ -39,10 +39,20 @@ pub fn current_domain<'gc>( ) -> Result, 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, 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()); diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index 39bb301ef..f4f577f10 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -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, ) diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 6e9057083..b43230ee9 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -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, 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, + ) } diff --git a/core/src/avm2/globals/regexp.rs b/core/src/avm2/globals/regexp.rs index 09d5fa0cd..8a21ef0fd 100644 --- a/core/src/avm2/globals/regexp.rs +++ b/core/src/avm2/globals/regexp.rs @@ -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, ); diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index ee090dad8..7bdbf48d5 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -113,17 +113,25 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + 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>, Error> { - if self.provides_trait(name)? { + fn find_base_constr_for_trait(self, name: &QName<'gc>) -> Result>, 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> + Clone + Copy /// prototype chain bearing this name is malformed in some way. fn get_trait_slot(self, id: u32) -> Result>, 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>, - ) -> 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>, 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> + Clone + Copy /// Returns true if an object has one or more traits of a given name. fn has_trait(self, name: &QName<'gc>) -> Result; - /// 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; - /// 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> + Clone + Copy _reciever: Option>, _arguments: &[Value<'gc>], _activation: &mut Activation<'_, 'gc, '_>, - _base_proto: Option>, + _base_constr: Option>, ) -> Result, 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>, + arguments: &[Value<'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::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>, + 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::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>, + 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, Error>; + self, + _activation: &mut Activation<'_, 'gc, '_>, + _args: &[Value<'gc>], + ) -> Result, 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>>, - ) -> Result, 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, 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> + Clone + Copy /// coercions. fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result, 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> + Clone + Copy /// of the class that created this object). fn to_locale_string(&self, mc: MutationContext<'gc, '_>) -> Result, 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> + Clone + Copy /// Get this object's `Class`, if it has one. fn as_class(&self) -> Option>>; + /// Get this object's constructor, if it has one. + fn as_constr(&self) -> Option>; + + /// 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> { 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>> { - 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> { None diff --git a/core/src/avm2/object/array_object.rs b/core/src/avm2/object/array_object.rs index 080cd08a6..a3d2b8fcf 100644 --- a/core/src/avm2/object/array_object.rs +++ b/core/src/avm2/object/array_object.rs @@ -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>>, ) -> Result, 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>>, ) -> Result, 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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()) - } } diff --git a/core/src/avm2/object/bytearray_object.rs b/core/src/avm2/object/bytearray_object.rs index 8d94d8b7c..05f0d781f 100644 --- a/core/src/avm2/object/bytearray_object.rs +++ b/core/src/avm2/object/bytearray_object.rs @@ -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>>, ) -> Result, 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>, + constr: Object<'gc>, + proto: Option>, ) -> 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> { - Self::new(mc, base_proto).into() + pub fn construct( + mc: MutationContext<'gc, '_>, + constr: Object<'gc>, + base_proto: Option>, + ) -> 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>>, ) -> Result, 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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, diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index 98f902575..04da202cc 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -58,11 +58,20 @@ impl<'gc> ClassObject<'gc> { base_class_constr: Option>, scope: Option>>, ) -> 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>, + class: GcCell<'gc, Class<'gc>>, + scope: Option>>, mut prototype: Object<'gc>, fn_proto: Object<'gc>, ) -> Result, 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>, arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc, '_>, - base_proto: Option>, + base_constr: Option>, ) -> Result, 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, 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>>, - ) -> Result, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, Error> { Ok(ClassObject(GcCell::allocate( activation.context.gc_context, self.0.read().clone(), diff --git a/core/src/avm2/object/custom_object.rs b/core/src/avm2/object/custom_object.rs index 5e20482af..6309516a4 100644 --- a/core/src/avm2/object/custom_object.rs +++ b/core/src/avm2/object/custom_object.rs @@ -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>, - ) -> Result<(), Error> { - self.0.read().$field.get_provided_trait(name, known_traits) - } - - fn get_provided_trait_slot(&self, id: u32) -> Result>, Error> { - self.0.read().$field.get_provided_trait_slot(id) - } - fn get_scope(self) -> Option>> { 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 { - 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> { + 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, '_>, diff --git a/core/src/avm2/object/dispatch_object.rs b/core/src/avm2/object/dispatch_object.rs index 32896c062..69e25daba 100644 --- a/core/src/avm2/object/dispatch_object.rs +++ b/core/src/avm2/object/dispatch_object.rs @@ -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, Error> { Err("Cannot construct internal event dispatcher structures.".into()) } - fn derive( - &self, - _activation: &mut Activation<'_, 'gc, '_>, - _class: GcCell<'gc, Class<'gc>>, - _scope: Option>>, - ) -> Result, Error> { + fn derive(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result, Error> { Err("Cannot subclass internal event dispatcher structures.".into()) } diff --git a/core/src/avm2/object/domain_object.rs b/core/src/avm2/object/domain_object.rs index d86c36b18..8620e6739 100644 --- a/core/src/avm2/object/domain_object.rs +++ b/core/src/avm2/object/domain_object.rs @@ -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>>, ) -> Result, 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>, 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>>, + mc: MutationContext<'gc, '_>, ) -> Result, 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, 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, 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>>, - ) -> Result, Error> { - let this: Object<'gc> = Object::DomainObject(*self); - Ok(DomainObject::from_domain( - activation.context.gc_context, + constr, Some(this), activation.context.avm2.global_domain(), )) diff --git a/core/src/avm2/object/event_object.rs b/core/src/avm2/object/event_object.rs index fd5d2a35f..e4234be07 100644 --- a/core/src/avm2/object/event_object.rs +++ b/core/src/avm2/object/event_object.rs @@ -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>>, ) -> Result, 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>, 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>>, ) -> 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, 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, 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>>, - ) -> Result, 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, Error> { diff --git a/core/src/avm2/object/function_object.rs b/core/src/avm2/object/function_object.rs index 2cd92fc11..f50e47b2c 100644 --- a/core/src/avm2/object/function_object.rs +++ b/core/src/avm2/object/function_object.rs @@ -100,20 +100,37 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { receiver: Option>, arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc, '_>, - base_proto: Option>, + base_constr: Option>, ) -> Result, 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, 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, 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>>, - ) -> Result, 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()) - } } diff --git a/core/src/avm2/object/loaderinfo_object.rs b/core/src/avm2/object/loaderinfo_object.rs index 85353375e..5d69c7844 100644 --- a/core/src/avm2/object/loaderinfo_object.rs +++ b/core/src/avm2/object/loaderinfo_object.rs @@ -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>>, ) -> Result, 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, root: DisplayObject<'gc>, + constr: Object<'gc>, base_proto: Object<'gc>, mc: MutationContext<'gc, '_>, ) -> Result, 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>>, ) -> Result, 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, 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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>> { if self.0.read().loaded_stream.is_some() { diff --git a/core/src/avm2/object/namespace_object.rs b/core/src/avm2/object/namespace_object.rs index c39ce0f9a..d70118673 100644 --- a/core/src/avm2/object/namespace_object.rs +++ b/core/src/avm2/object/namespace_object.rs @@ -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>>, ) -> Result, 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, 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>>, ) -> Result, 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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()) - } } diff --git a/core/src/avm2/object/primitive_object.rs b/core/src/avm2/object/primitive_object.rs index 9f86d29fe..2288bc432 100644 --- a/core/src/avm2/object/primitive_object.rs +++ b/core/src/avm2/object/primitive_object.rs @@ -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>>, ) -> Result, 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, 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>>, ) -> Result, 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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>> { Some(RefMut::map(self.0.write(mc), |pod| &mut pod.primitive)) } diff --git a/core/src/avm2/object/regexp_object.rs b/core/src/avm2/object/regexp_object.rs index c46dcdfb6..779c59d46 100644 --- a/core/src/avm2/object/regexp_object.rs +++ b/core/src/avm2/object/regexp_object.rs @@ -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>>, ) -> Result, 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>, 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>>, ) -> 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, 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, 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>>, - ) -> Result, 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, Error> { diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 6f602446f..2a66c8812 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -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>>), - /// Instantiate class traits, for class constructors. ClassConstructor(GcCell<'gc, Class<'gc>>, Option>>), + /// 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>, - ) -> Result<(), Error> { - self.0.read().get_provided_trait(name, known_traits) - } - - fn get_provided_trait_slot(&self, id: u32) -> Result>, Error> { - self.0.read().get_provided_trait_slot(id) - } - fn get_scope(self) -> Option>> { 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 { - 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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, 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>> { self.0.read().as_class() } + + fn as_constr(&self) -> Option> { + 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>>, - ) -> 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>>, ) -> 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, 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, 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>, 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>, 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>, - ) -> 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>, 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 { 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 { - 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>> { 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>> { 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> { + 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); + } } diff --git a/core/src/avm2/object/stage_object.rs b/core/src/avm2/object/stage_object.rs index 791f4ff23..22f7ab31a 100644 --- a/core/src/avm2/object/stage_object.rs +++ b/core/src/avm2/object/stage_object.rs @@ -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>>, ) -> Result, 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>>, ) -> Result, 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>, - ) -> Result<(), Error> { - self.0.read().base.get_provided_trait(name, known_traits) - } - - fn get_provided_trait_slot(&self, id: u32) -> Result>, Error> { - self.0.read().base.get_provided_trait_slot(id) - } - fn get_scope(self) -> Option>> { 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 { - 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> { + 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> { 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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, Error> { Ok(Value::Object(Object::from(*self))) } diff --git a/core/src/avm2/object/xml_object.rs b/core/src/avm2/object/xml_object.rs index 2021061f4..7a809facf 100644 --- a/core/src/avm2/object/xml_object.rs +++ b/core/src/avm2/object/xml_object.rs @@ -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>>, ) -> Result, 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>>, ) -> Result, 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, Error> { + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, 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>>, - ) -> Result, 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, Error> { diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 3321aab54..7c59210b2 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -131,14 +131,14 @@ impl<'gc> Property<'gc> { pub fn get( &self, this: Object<'gc>, - base_proto: Option>, + base_constr: Option>, ) -> Result, 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>, + base_constr: Option>, new_value: impl Into>, ) -> Result, 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>, + base_constr: Option>, new_value: impl Into>, ) -> Result, Error> { match self { @@ -213,7 +213,7 @@ impl<'gc> Property<'gc> { *function, Some(this), vec![new_value.into()], - base_proto, + base_constr, )); } diff --git a/core/src/avm2/return_value.rs b/core/src/avm2/return_value.rs index f1a0a154f..c8b587b9a 100644 --- a/core/src/avm2/return_value.rs +++ b/core/src/avm2/return_value.rs @@ -37,7 +37,7 @@ pub enum ReturnValue<'gc> { callee: Object<'gc>, unbound_reciever: Option>, arguments: Vec>, - base_proto: Option>, + base_constr: Option>, }, } @@ -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>, arguments: Vec>, - base_proto: Option>, + base_constr: Option>, ) -> 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, ), } diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index d39a809ed..0d96caaf5 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -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>, 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, 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); } diff --git a/core/src/avm2/traits.rs b/core/src/avm2/traits.rs index 81d43c750..6b5f3f33d 100644 --- a/core/src/avm2/traits.rs +++ b/core/src/avm2/traits.rs @@ -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 { - 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 }, diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index bf7999fb5..478186fc6 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -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) -> 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, 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. diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs index 4827779dd..467f6dd09 100644 --- a/core/src/display_object/avm2_button.rs +++ b/core/src/display_object/avm2_button.rs @@ -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()); diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 844da9517..7cfba74aa 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -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 diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index 4da64a513..ba16ef449 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -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) }; diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 449585034..3f9043844 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -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(()) }; diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index 40900e31d..dc5aa9b83 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -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); } diff --git a/core/src/display_object/video.rs b/core/src/display_object/video.rs index eb7a8fa2c..b57269a01 100644 --- a/core/src/display_object/video.rs +++ b/core/src/display_object/video.rs @@ -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()); } } diff --git a/core/src/html/text_format.rs b/core/src/html/text_format.rs index ddfc4124c..94b2d219b 100644 --- a/core/src/html/text_format.rs +++ b/core/src/html/text_format.rs @@ -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, ); diff --git a/core/src/loader.rs b/core/src/loader.rs index 6298732d8..f9d37afd4 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -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( diff --git a/core/src/player.rs b/core/src/player.rs index 7a2d749db..7c68977d0 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -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);