diff --git a/core/src/avm2/globals/flash/display/shape.rs b/core/src/avm2/globals/flash/display/shape.rs index 70b2cac7e..8a0063404 100644 --- a/core/src/avm2/globals/flash/display/shape.rs +++ b/core/src/avm2/globals/flash/display/shape.rs @@ -62,15 +62,7 @@ pub fn graphics<'gc>( activation, )? { Value::Undefined | Value::Null => { - let graphics_proto = activation.context.avm2.prototypes().graphics; - let graphics_constr = activation.context.avm2.constructors().graphics; - - let graphics = Value::from(StageObject::for_display_object( - activation.context.gc_context, - dobj, - graphics_constr, - graphics_proto, - )); + let graphics = Value::from(StageObject::graphics_of(activation, dobj)?); this.set_property( this, &QName::new(Namespace::private(NS_RUFFLE_INTERNAL), "graphics"), diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index eb0bdef1e..66b097bcd 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -48,15 +48,7 @@ pub fn graphics<'gc>( activation, )? { Value::Undefined | Value::Null => { - let graphics_proto = activation.context.avm2.prototypes().graphics; - let graphics_constr = activation.context.avm2.constructors().graphics; - - let graphics = Value::from(StageObject::for_display_object( - activation.context.gc_context, - dobj, - graphics_constr, - graphics_proto, - )); + let graphics = Value::from(StageObject::graphics_of(activation, dobj)?); this.set_property( this, &QName::new(Namespace::private(NS_RUFFLE_INTERNAL), "graphics"), diff --git a/core/src/avm2/object/stage_object.rs b/core/src/avm2/object/stage_object.rs index 22f7ab31a..78d5e41bd 100644 --- a/core/src/avm2/object/stage_object.rs +++ b/core/src/avm2/object/stage_object.rs @@ -20,7 +20,16 @@ pub fn stage_deriver<'gc>( proto: Object<'gc>, activation: &mut Activation<'_, 'gc, '_>, ) -> Result, Error> { - StageObject::derive(constr, proto, activation.context.gc_context) + let base = ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr)); + + Ok(StageObject(GcCell::allocate( + activation.context.gc_context, + StageObjectData { + base, + display_object: None, + }, + )) + .into()) } #[derive(Clone, Collect, Debug, Copy)] @@ -38,14 +47,31 @@ pub struct StageObjectData<'gc> { } impl<'gc> StageObject<'gc> { + /// Allocate the AVM2 side of a display object intended to be of a given + /// constructor's type. + /// + /// This function makes no attempt to construct the returned object. You + /// are responsible for calling the native initializer of the given + /// constructor at a later time. Typically, a display object that can + /// contain movie-constructed children must first allocate itself (using + /// this function), construct it's children, and then finally initialize + /// itself. Display objects that do not need to use this flow should use + /// `for_display_object_childless`. pub fn for_display_object( - mc: MutationContext<'gc, '_>, + activation: &mut Activation<'_, 'gc, '_>, display_object: DisplayObject<'gc>, - constr: Object<'gc>, - proto: Object<'gc>, - ) -> Self { - Self(GcCell::allocate( - mc, + mut constr: Object<'gc>, + ) -> Result { + let proto = constr + .get_property( + constr, + &QName::new(Namespace::public(), "prototype"), + activation, + )? + .coerce_to_object(activation)?; + + Ok(Self(GcCell::allocate( + activation.context.gc_context, StageObjectData { base: ScriptObjectData::base_new( Some(proto), @@ -53,26 +79,47 @@ impl<'gc> StageObject<'gc> { ), display_object: Some(display_object), }, - )) + ))) } - /// Construct a stage object subclass. - pub fn derive( + /// Allocate and construct the AVM2 side of a display object intended to be + /// of a given constructor's type. + /// + /// This function is intended for display objects that do not have children + /// and thus do not need to be allocated and initialized in separate phases. + pub fn for_display_object_childless( + activation: &mut Activation<'_, 'gc, '_>, + display_object: DisplayObject<'gc>, constr: Object<'gc>, - proto: Object<'gc>, - mc: MutationContext<'gc, '_>, - ) -> Result, Error> { - let base = - ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr)); + ) -> Result { + let this = Self::for_display_object(activation, display_object, constr)?; - Ok(StageObject(GcCell::allocate( - mc, + constr.call_native_initializer(Some(this.into()), &[], activation, Some(constr))?; + + Ok(this) + } + + /// Create a `graphics` object for a given display object. + pub fn graphics_of( + activation: &mut Activation<'_, 'gc, '_>, + display_object: DisplayObject<'gc>, + ) -> Result { + let constr = activation.avm2().constructors().graphics; + let proto = activation.avm2().prototypes().graphics; + let this = Self(GcCell::allocate( + activation.context.gc_context, StageObjectData { - base, - display_object: None, + base: ScriptObjectData::base_new( + Some(proto), + ScriptObjectClass::ClassInstance(constr), + ), + display_object: Some(display_object), }, - )) - .into()) + )); + + constr.call_native_initializer(Some(this.into()), &[], activation, Some(constr))?; + + Ok(this) } } diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs index a3a875da3..84b5e0b04 100644 --- a/core/src/display_object/avm2_button.rs +++ b/core/src/display_object/avm2_button.rs @@ -1,5 +1,8 @@ use crate::avm1::Object as Avm1Object; -use crate::avm2::{Object as Avm2Object, StageObject as Avm2StageObject, Value as Avm2Value}; +use crate::avm2::{ + Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject, + Value as Avm2Value, +}; use crate::backend::ui::MouseCursor; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::container::{dispatch_added_event, dispatch_removed_event}; @@ -426,15 +429,16 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { } if self.0.read().object.is_none() { - let simplebutton_proto = context.avm2.prototypes().simplebutton; let simplebutton_constr = context.avm2.constructors().simplebutton; - let object = Avm2StageObject::for_display_object( - context.gc_context, + let mut activation = Avm2Activation::from_nothing(context.reborrow()); + match Avm2StageObject::for_display_object( + &mut activation, (*self).into(), simplebutton_constr, - simplebutton_proto, - ); - self.0.write(context.gc_context).object = Some(object.into()); + ) { + Ok(object) => self.0.write(context.gc_context).object = Some(object.into()), + Err(e) => log::error!("Got {} when constructing AVM2 side of button", e), + }; let (up_state, up_should_fire) = self.create_state(context, swf::ButtonState::UP); let (over_state, over_should_fire) = self.create_state(context, swf::ButtonState::OVER); diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 317cdfb78..c06c0c53e 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -7,7 +7,6 @@ use crate::avm1::{ }; use crate::avm2::{ Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject, - TObject as Avm2TObject, }; use crate::backend::ui::MouseCursor; use crate::context::{RenderContext, UpdateContext}; @@ -1509,29 +1508,23 @@ impl<'gc> EditText<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, display_object: DisplayObject<'gc>, ) { - let textfield_proto = context.avm2.prototypes().textfield; let textfield_constr = context.avm2.constructors().textfield; - - let object: Avm2Object<'gc> = Avm2StageObject::for_display_object( - context.gc_context, - display_object, - textfield_constr, - textfield_proto, - ) - .into(); - let mut activation = Avm2Activation::from_nothing(context.reborrow()); - if let Err(e) = - textfield_constr.call(Some(object), &[], &mut activation, Some(textfield_constr)) - { - log::error!( + match Avm2StageObject::for_display_object_childless( + &mut activation, + display_object, + textfield_constr, + ) { + Ok(object) => { + let object: Avm2Object<'gc> = object.into(); + self.0.write(activation.context.gc_context).object = Some(object.into()) + } + Err(e) => log::error!( "Got {} when constructing AVM2 side of dynamic text field", e - ); + ), } - - self.0.write(activation.context.gc_context).object = Some(object.into()); } } diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index 6a40bec1e..964fd9ee7 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -1,7 +1,6 @@ use crate::avm1::Object as Avm1Object; use crate::avm2::{ - Activation as Avm2Activation, Error as Avm2Error, Object as Avm2Object, - StageObject as Avm2StageObject, TObject as Avm2TObject, + Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject, }; use crate::backend::render::ShapeHandle; use crate::context::{RenderContext, UpdateContext}; @@ -115,27 +114,17 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> { fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) { if self.avm_type() == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) { - let mut allocator = || { - let shape_proto = context.avm2.prototypes().shape; - let shape_constr = context.avm2.constructors().shape; + let shape_constr = context.avm2.constructors().shape; + let mut activation = Avm2Activation::from_nothing(context.reborrow()); - let object = Avm2StageObject::for_display_object( - context.gc_context, - (*self).into(), - shape_constr, - shape_proto, - ) - .into(); - - let mut activation = Avm2Activation::from_nothing(context.reborrow()); - shape_constr.call(Some(object), &[], &mut activation, Some(shape_constr))?; - - Ok(object) - }; - let result: Result, Avm2Error> = allocator(); - - match result { - Ok(object) => self.0.write(context.gc_context).avm2_object = Some(object), + match Avm2StageObject::for_display_object_childless( + &mut activation, + (*self).into(), + shape_constr, + ) { + Ok(object) => { + self.0.write(activation.context.gc_context).avm2_object = Some(object.into()) + } Err(e) => log::error!("Got {} when constructing AVM2 side of display object", e), } } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 7c7ffbfbb..45e132ad9 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -1513,7 +1513,7 @@ impl<'gc> MovieClip<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, display_object: DisplayObject<'gc>, ) { - let mut constructor = self + let constructor = self .0 .read() .avm2_constructor @@ -1521,20 +1521,9 @@ impl<'gc> MovieClip<'gc> { let mut constr_thing = || { let mut activation = Avm2Activation::from_nothing(context.reborrow()); - let mc_proto = constructor - .get_property( - constructor, - &Avm2QName::new(Avm2Namespace::public(), "prototype"), - &mut activation, - )? - .coerce_to_object(&mut activation)?; - let object = Avm2StageObject::for_display_object( - activation.context.gc_context, - display_object, - constructor, - mc_proto, - ) - .into(); + let object = + Avm2StageObject::for_display_object(&mut activation, display_object, constructor)? + .into(); Ok(object) }; diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index c73b7d975..d3ef92f12 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -3,8 +3,7 @@ use crate::avm1::Object as Avm1Object; use crate::avm2::{ Activation as Avm2Activation, Event as Avm2Event, Object as Avm2Object, - ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, TObject as Avm2TObject, - Value as Avm2Value, + ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, Value as Avm2Value, }; use crate::backend::ui::UiBackend; use crate::config::Letterbox; @@ -518,29 +517,24 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> { _instantiated_by: Instantiator, _run_frame: bool, ) { - let stage_proto = context.avm2.prototypes().stage; let stage_constr = context.avm2.constructors().stage; - let avm2_stage = Avm2StageObject::for_display_object( - 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. let mut activation = Avm2Activation::from_nothing(context.reborrow()); - if let Err(e) = stage_constr.call_native_initializer( - Some(avm2_stage.into()), - &[], + let avm2_stage = Avm2StageObject::for_display_object_childless( &mut activation, - Some(stage_constr), - ) { - log::error!("Unable to construct AVM2 Stage: {}", e); - } + (*self).into(), + stage_constr, + ); - self.0.write(activation.context.gc_context).avm2_object = avm2_stage.into(); + match avm2_stage { + Ok(avm2_stage) => { + self.0.write(activation.context.gc_context).avm2_object = avm2_stage.into() + } + Err(e) => log::error!("Unable to construct AVM2 Stage: {}", e), + } } fn id(&self) -> CharacterId { diff --git a/core/src/display_object/video.rs b/core/src/display_object/video.rs index 44fa118cd..15a9f377d 100644 --- a/core/src/display_object/video.rs +++ b/core/src/display_object/video.rs @@ -1,7 +1,9 @@ //! 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, Object as Avm2Object, StageObject as Avm2StageObject, +}; use crate::backend::render::BitmapInfo; use crate::backend::video::{EncodedFrame, VideoStreamHandle}; use crate::bounding_box::BoundingBox; @@ -381,17 +383,19 @@ 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 video_proto = context.avm2.prototypes().video; let video_constr = context.avm2.constructors().video; - - let object: Avm2Object<'_> = Avm2StageObject::for_display_object( - context.gc_context, + let mut activation = Avm2Activation::from_nothing(context.reborrow()); + match Avm2StageObject::for_display_object_childless( + &mut activation, (*self).into(), video_constr, - video_proto, - ) - .into(); - self.0.write(context.gc_context).object = Some(object.into()); + ) { + Ok(object) => { + let object: Avm2Object<'gc> = object.into(); + self.0.write(context.gc_context).object = Some(object.into()) + } + Err(e) => log::error!("Got {} when constructing AVM2 side of video player", e), + } } }