diff --git a/core/src/avm2.rs b/core/src/avm2.rs index f82044f23d24..b4a22e6ec4bb 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -63,8 +63,8 @@ pub use crate::avm2::globals::flash::ui::context_menu::make_context_menu_state; pub use crate::avm2::multiname::Multiname; pub use crate::avm2::namespace::Namespace; pub use crate::avm2::object::{ - ArrayObject, ClassObject, EventObject, Object, ScriptObject, SoundChannelObject, StageObject, - TObject, + ArrayObject, BitmapDataObject, ClassObject, EventObject, Object, ScriptObject, + SoundChannelObject, StageObject, TObject, }; pub use crate::avm2::qname::QName; pub use crate::avm2::value::Value; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 60e5ebe2e3b1..9f1f4dadafbc 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -77,6 +77,7 @@ pub struct SystemClasses<'gc> { pub graphicstrianglepath: ClassObject<'gc>, pub graphicssolidfill: ClassObject<'gc>, pub graphicsstroke: ClassObject<'gc>, + pub loader: ClassObject<'gc>, pub loaderinfo: ClassObject<'gc>, pub bytearray: ClassObject<'gc>, pub stage: ClassObject<'gc>, @@ -188,6 +189,7 @@ impl<'gc> SystemClasses<'gc> { graphicstrianglepath: object, graphicssolidfill: object, graphicsstroke: object, + loader: object, loaderinfo: object, bytearray: object, stage: object, @@ -661,6 +663,7 @@ fn load_playerglobal<'gc>( ("flash.display", "GraphicsSolidFill", graphicssolidfill), ("flash.display", "GraphicsStroke", graphicsstroke), ("flash.display", "Graphics", graphics), + ("flash.display", "Loader", loader), ("flash.display", "LoaderInfo", loaderinfo), ("flash.display", "MorphShape", morphshape), ("flash.display", "MovieClip", movieclip), diff --git a/core/src/avm2/globals/flash/display.rs b/core/src/avm2/globals/flash/display.rs index 49fbb70b1185..60f61f372b68 100644 --- a/core/src/avm2/globals/flash/display.rs +++ b/core/src/avm2/globals/flash/display.rs @@ -8,6 +8,7 @@ pub mod graphics; pub mod interactive_object; pub mod loader; pub mod loader_info; +pub mod morph_shape; pub mod movie_clip; pub mod shape; pub mod simple_button; diff --git a/core/src/avm2/globals/flash/display/Bitmap.as b/core/src/avm2/globals/flash/display/Bitmap.as index f3a0493608a5..c8f035b49469 100644 --- a/core/src/avm2/globals/flash/display/Bitmap.as +++ b/core/src/avm2/globals/flash/display/Bitmap.as @@ -1,5 +1,6 @@ package flash.display { + [Ruffle(InstanceAllocator)] public class Bitmap extends DisplayObject { public native function get bitmapData():BitmapData; public native function set bitmapData(value:BitmapData):void; diff --git a/core/src/avm2/globals/flash/display/Loader.as b/core/src/avm2/globals/flash/display/Loader.as index 7cbe5549561f..ad35d74be912 100644 --- a/core/src/avm2/globals/flash/display/Loader.as +++ b/core/src/avm2/globals/flash/display/Loader.as @@ -1,4 +1,6 @@ package flash.display { + + [Ruffle(InstanceAllocator)] public class Loader extends DisplayObjectContainer { import flash.display.LoaderInfo; import flash.display.DisplayObject; @@ -14,12 +16,6 @@ package flash.display { return this._contentLoaderInfo; } - private native function init(); - - public function Loader() { - this.init() - } - public function get content():DisplayObject { if (this.numChildren == 0) { return null; diff --git a/core/src/avm2/globals/flash/display/MorphShape.as b/core/src/avm2/globals/flash/display/MorphShape.as index f7917c139abd..9b4ad5454646 100644 --- a/core/src/avm2/globals/flash/display/MorphShape.as +++ b/core/src/avm2/globals/flash/display/MorphShape.as @@ -1,7 +1,8 @@ package flash.display { + [Ruffle(InstanceAllocator)] public final class MorphShape extends DisplayObject { public function MorphShape() { - throw new ArgumentError("Error #2012: MorphShape$ class cannot be instantiated.", 2012) + // We throw an error in `morph_shape_allocator` } } } \ No newline at end of file diff --git a/core/src/avm2/globals/flash/display/Shape.as b/core/src/avm2/globals/flash/display/Shape.as index 3965df996ed2..6f15320abaed 100644 --- a/core/src/avm2/globals/flash/display/Shape.as +++ b/core/src/avm2/globals/flash/display/Shape.as @@ -1,10 +1,7 @@ package flash.display { - public class Shape extends DisplayObject { - public function Shape() { - this.init(); - } - private native function init(); + [Ruffle(InstanceAllocator)] + public class Shape extends DisplayObject { public native function get graphics():Graphics; internal var _graphics:Graphics; diff --git a/core/src/avm2/globals/flash/display/SimpleButton.as b/core/src/avm2/globals/flash/display/SimpleButton.as index 6657a876fa1a..b3215655dcc9 100644 --- a/core/src/avm2/globals/flash/display/SimpleButton.as +++ b/core/src/avm2/globals/flash/display/SimpleButton.as @@ -6,6 +6,7 @@ package flash.display { import flash.display.DisplayObject; import flash.media.SoundTransform; + [Ruffle(InstanceAllocator)] public class SimpleButton extends InteractiveObject { public function SimpleButton(upState:DisplayObject = null, overState:DisplayObject = null, downState:DisplayObject = null, hitTestState:DisplayObject = null) { this.init(upState, overState, downState, hitTestState) diff --git a/core/src/avm2/globals/flash/display/Sprite.as b/core/src/avm2/globals/flash/display/Sprite.as index a893b2494d1e..66a2f999e2ce 100644 --- a/core/src/avm2/globals/flash/display/Sprite.as +++ b/core/src/avm2/globals/flash/display/Sprite.as @@ -4,15 +4,10 @@ package flash.display { import flash.geom.Rectangle; import flash.media.SoundTransform; + [Ruffle(InstanceAllocator)] public class Sprite extends DisplayObjectContainer { internal var _graphics:Graphics; - - public function Sprite() { - this.init(); - } - - private native function init(); public native function get graphics():Graphics; public native function get dropTarget():DisplayObject; diff --git a/core/src/avm2/globals/flash/display/bitmap.rs b/core/src/avm2/globals/flash/display/bitmap.rs index 693eeb8b7432..385c56ad2e2b 100644 --- a/core/src/avm2/globals/flash/display/bitmap.rs +++ b/core/src/avm2/globals/flash/display/bitmap.rs @@ -2,7 +2,8 @@ use crate::avm2::activation::Activation; use crate::avm2::globals::flash::display::bitmap_data::fill_bitmap_data_from_symbol; -use crate::avm2::object::{BitmapDataObject, Object, TObject}; +use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; +use crate::avm2::object::{BitmapDataObject, ClassObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; @@ -12,15 +13,70 @@ use crate::character::Character; use crate::display_object::{Bitmap, TDisplayObject}; use crate::{avm2_stub_getter, avm2_stub_setter}; +pub fn bitmap_allocator<'gc>( + class: ClassObject<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + let bitmap_cls = activation.avm2().classes().bitmap; + let bitmapdata_cls = activation.context.avm2.classes().bitmapdata; + + let mut class_object = Some(class); + let orig_class = class; + while let Some(class) = class_object { + if class == bitmap_cls { + let bitmap_data = BitmapDataWrapper::dummy(activation.context.gc_context); + let display_object = + Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, false).into(); + return initialize_for_allocator(activation, display_object, orig_class); + } + + if let Some((movie, symbol)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(class) + { + if let Some(Character::Bitmap(bitmap)) = activation + .context + .library + .library_for_movie_mut(movie) + .character_by_id(symbol) + .cloned() + { + let new_bitmap_data = fill_bitmap_data_from_symbol(activation, &bitmap); + let bitmap_data_obj = BitmapDataObject::from_bitmap_data_internal( + activation, + BitmapDataWrapper::dummy(activation.context.gc_context), + bitmapdata_cls, + )?; + bitmap_data_obj.init_bitmap_data(activation.context.gc_context, new_bitmap_data); + new_bitmap_data.init_object2(activation.context.gc_context, bitmap_data_obj); + + let child = Bitmap::new_with_bitmap_data( + &mut activation.context, + 0, + new_bitmap_data, + false, + ) + .into(); + + let mut obj = initialize_for_allocator(activation, child, orig_class)?; + obj.set_public_property("bitmapData", bitmap_data_obj.into(), activation)?; + return Ok(obj); + } + } + class_object = class.superclass_object(); + } + unreachable!("A Bitmap subclass should have Bitmap in superclass chain"); +} + /// Implements `flash.display.Bitmap`'s `init` method, which is called from the constructor pub fn init<'gc>( activation: &mut Activation<'_, 'gc>, this: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(mut this) = this { - activation.super_init(this, &[])?; - + if let Some(this) = this { let bitmap_data = args .try_get_object(activation, 0) .and_then(|o| o.as_bitmap_data()); @@ -29,69 +85,12 @@ pub fn init<'gc>( let smoothing = args.get_bool(2); if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) { - //We are being initialized by the movie. This means that we - //need to create bitmap data right away, since all AVM2 bitmaps - //hold bitmap data. - - let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() { - // We call the custom BitmapData class with width and height... - // but, it always seems to be 1 in Flash Player when constructed from timeline? - bd_class.construct(activation, &[1.into(), 1.into()])? - } else if let Some(b_class) = bitmap.avm2_bitmap_class() { - // Instantiating Bitmap from a Flex-style bitmap asset. - // Contrary to the above comment, this code path DOES - // trigger from AVM2, since the DisplayObject instantiation - // logic does its job in this case. - if let Some((movie, symbol_id)) = activation - .context - .library - .avm2_class_registry() - .class_symbol(b_class) - { - if let Some(Character::Bitmap(bitmap)) = activation - .context - .library - .library_for_movie_mut(movie) - .character_by_id(symbol_id) - .cloned() - { - let new_bitmap_data = - fill_bitmap_data_from_symbol(activation.context.gc_context, bitmap); - BitmapDataObject::from_bitmap_data( - activation, - new_bitmap_data, - activation.context.avm2.classes().bitmapdata, - )? - } else { - //Class association not to a Bitmap - return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into()); - } - } else { - //Class association not bidirectional - return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into()); - } - } else { - // No class association - return Err( - "Cannot instantiate Bitmap from timeline without associated symbol class" - .into(), - ); - }; - - this.set_public_property("bitmapData", bd_object.into(), activation)?; - + if let Some(bitmap_data) = bitmap_data { + bitmap.set_bitmap_data(&mut activation.context, bitmap_data); + } bitmap.set_smoothing(activation.context.gc_context, smoothing); } else { - //We are being initialized by AVM2 (and aren't associated with a - //Bitmap subclass). - - let bitmap_data = bitmap_data - .unwrap_or_else(|| BitmapDataWrapper::dummy(activation.context.gc_context)); - - let bitmap = - Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, smoothing); - - this.init_display_object(&mut activation.context, bitmap.into()); + unreachable!(); } } diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index e770bf6e399a..dd93c5c9edab 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -18,7 +18,7 @@ use crate::character::Character; use crate::display_object::Bitmap; use crate::display_object::TDisplayObject; use crate::swf::BlendMode; -use gc_arena::{GcCell, MutationContext}; +use gc_arena::GcCell; use ruffle_render::filters::Filter; use ruffle_render::transform::Transform; use std::str::FromStr; @@ -48,17 +48,19 @@ fn get_rectangle_x_y_width_height<'gc>( /// `bd` is assumed to be an uninstantiated library symbol, associated with the /// class named by `name`. pub fn fill_bitmap_data_from_symbol<'gc>( - gc_context: MutationContext<'gc, '_>, - bitmap: Bitmap<'gc>, + activation: &mut Activation<'_, 'gc>, + bd: &Bitmap<'gc>, ) -> BitmapDataWrapper<'gc> { - let transparency = true; - let bitmap_data = BitmapData::new_with_pixels( - bitmap.width().into(), - bitmap.height().into(), - transparency, - bitmap.bitmap_data().read().pixels().to_vec(), + let new_bitmap_data = GcCell::allocate( + activation.context.gc_context, + BitmapData::new_with_pixels( + Bitmap::width(*bd).into(), + Bitmap::height(*bd).into(), + true, + bd.bitmap_data().read().pixels().to_vec(), + ), ); - BitmapDataWrapper::new(GcCell::allocate(gc_context, bitmap_data)) + BitmapDataWrapper::new(new_bitmap_data) } /// Implements `flash.display.BitmapData`'s 'init' method (invoked from the AS3 constructor) @@ -68,64 +70,62 @@ pub fn init<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(this) = this { - activation.super_init(this, &[])?; - - if this.as_bitmap_data().is_none() { - let name = this.instance_of_class_definition().map(|c| c.read().name()); - let character = this - .instance_of() - .and_then(|t| { - activation - .context - .library - .avm2_class_registry() - .class_symbol(t) - }) - .and_then(|(movie, chara_id)| { - activation - .context - .library - .library_for_movie_mut(movie) - .character_by_id(chara_id) - .cloned() - }); - - let new_bitmap_data = if let Some(Character::Bitmap(bitmap)) = character { - // Instantiating BitmapData from an Animate-style bitmap asset - fill_bitmap_data_from_symbol(activation.context.gc_context, bitmap) - } else { - if character.is_some() { - //TODO: Determine if mismatched symbols will still work as a - //regular BitmapData subclass, or if this should throw - tracing::warn!( - "BitmapData subclass {:?} is associated with a non-bitmap symbol", - name - ); - } + // We set the underlying BitmapData instance - we start out with a dummy BitmapDataWrapper, + // which makes custom classes see a disposed BitmapData before they call super() + let name = this.instance_of_class_definition().map(|c| c.read().name()); + let character = this + .instance_of() + .and_then(|t| { + activation + .context + .library + .avm2_class_registry() + .class_symbol(t) + }) + .and_then(|(movie, chara_id)| { + activation + .context + .library + .library_for_movie_mut(movie) + .character_by_id(chara_id) + .cloned() + }); + + let new_bitmap_data = if let Some(Character::Bitmap(bitmap)) = character { + // Instantiating BitmapData from an Animate-style bitmap asset + fill_bitmap_data_from_symbol(activation, &bitmap) + } else { + if character.is_some() { + //TODO: Determine if mismatched symbols will still work as a + //regular BitmapData subclass, or if this should throw + tracing::warn!( + "BitmapData subclass {:?} is associated with a non-bitmap symbol", + name + ); + } - let width = args.get_u32(activation, 0)?; - let height = args.get_u32(activation, 1)?; - let transparency = args.get_bool(2); - let fill_color = args.get_i32(activation, 3)?; + let width = args.get_u32(activation, 0)?; + let height = args.get_u32(activation, 1)?; + let transparency = args.get_bool(2); + let fill_color = args.get_i32(activation, 3)?; - if !is_size_valid(activation.context.swf.version(), width, height) { - return Err(Error::AvmError(argument_error( - activation, - "Error #2015: Invalid BitmapData.", - 2015, - )?)); - } + if !is_size_valid(activation.context.swf.version(), width, height) { + return Err(Error::AvmError(argument_error( + activation, + "Error #2015: Invalid BitmapData.", + 2015, + )?)); + } - let new_bitmap_data = BitmapData::new(width, height, transparency, fill_color); - BitmapDataWrapper::new(GcCell::allocate( - activation.context.gc_context, - new_bitmap_data, - )) - }; + let new_bitmap_data = BitmapData::new(width, height, transparency, fill_color); + BitmapDataWrapper::new(GcCell::allocate( + activation.context.gc_context, + new_bitmap_data, + )) + }; - new_bitmap_data.init_object2(activation.context.gc_context, this); - this.init_bitmap_data(activation.context.gc_context, new_bitmap_data); - } + new_bitmap_data.init_object2(activation.context.gc_context, this); + this.init_bitmap_data(activation.context.gc_context, new_bitmap_data); } Ok(Value::Undefined) @@ -1026,7 +1026,7 @@ pub fn clone<'gc>( let new_bitmap_data = operations::clone(bitmap_data); let class = activation.avm2().classes().bitmapdata; - let new_bitmap_data_object = BitmapDataObject::from_bitmap_data( + let new_bitmap_data_object = BitmapDataObject::from_bitmap_data_internal( activation, BitmapDataWrapper::new(GcCell::allocate( activation.context.gc_context, @@ -1292,7 +1292,7 @@ pub fn compare<'gc>( match operations::compare(this_bitmap_data, other_bitmap_data) { Some(bitmap_data) => { let class = activation.avm2().classes().bitmapdata; - Ok(BitmapDataObject::from_bitmap_data( + Ok(BitmapDataObject::from_bitmap_data_internal( activation, BitmapDataWrapper::new(GcCell::allocate( activation.context.gc_context, diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index 27497fcf3b02..13744628fab8 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -1,16 +1,15 @@ //! `flash.display.DisplayObject` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::error::make_error_2008; +use crate::avm2::error::{argument_error, make_error_2008}; use crate::avm2::filters::FilterAvm2Ext; -pub use crate::avm2::object::stage_allocator as display_object_allocator; use crate::avm2::object::{Object, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; -use crate::avm2::Error; -use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::{ArrayObject, ArrayStorage}; +use crate::avm2::{ClassObject, Error}; +use crate::avm2::{Multiname, StageObject}; use crate::display_object::{DisplayObject, HitTestOptions, TDisplayObject}; use crate::ecma_conversions::round_to_even; use crate::frame_lifecycle::catchup_display_object_to_frame; @@ -21,7 +20,48 @@ use crate::vminterface::Instantiator; use crate::{avm2_stub_getter, avm2_stub_setter}; use ruffle_render::filters::Filter; use std::str::FromStr; -use swf::BlendMode; +use swf::Twips; +use swf::{BlendMode, Rectangle}; + +pub fn display_object_allocator<'gc>( + class: ClassObject<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + let class_name = class.inner_class_definition().read().name().local_name(); + + return Err(Error::AvmError(argument_error( + activation, + &format!("Error #2012: {class_name}$ class cannot be instantiated."), + 2012, + )?)); +} + +/// Initializes a DisplayObject created from ActionScript. +/// This should be called from the AVM2 class's native allocator +/// (e.g. `sprite_allocator`) +pub fn initialize_for_allocator<'gc>( + activation: &mut Activation<'_, 'gc>, + dobj: DisplayObject<'gc>, + class: ClassObject<'gc>, +) -> Result, Error<'gc>> { + let obj: StageObject = StageObject::for_display_object(activation, dobj, class)?; + dobj.set_placed_by_script(activation.context.gc_context, true); + dobj.set_object2(&mut activation.context, obj.into()); + + // [NA] Should these run for everything? + dobj.post_instantiation(&mut activation.context, None, Instantiator::Avm2, false); + catchup_display_object_to_frame(&mut activation.context, dobj); + + // Movie clips created from ActionScript skip the next enterFrame, + // and consequently are observed to have their currentFrame lag one + // frame behind objects placed by the timeline (even if they were + // both placed in the same frame to begin with). + dobj.base_mut(activation.context.gc_context) + .set_skip_next_enter_frame(true); + dobj.on_construction_complete(&mut activation.context); + + Ok(obj.into()) +} /// Implements `flash.display.DisplayObject`'s native instance constructor. pub fn native_instance_init<'gc>( @@ -32,49 +72,6 @@ pub fn native_instance_init<'gc>( if let Some(this) = this { activation.super_init(this, &[])?; - if this.as_display_object().is_none() { - let mut class_object = this.instance_of(); - - // Iterate the inheritance chain, starting from `this` and working backwards through `super`s - // This accounts for the cases where a super may be linked to symbol, but `this` may not be - while let Some(class) = class_object { - if let Some((movie, symbol)) = activation - .context - .library - .avm2_class_registry() - .class_symbol(class) - { - let child = activation - .context - .library - .library_for_movie_mut(movie) - .instantiate_by_id(symbol, activation.context.gc_context)?; - - this.init_display_object(&mut activation.context, child); - - child.post_instantiation( - &mut activation.context, - None, - Instantiator::Avm2, - false, - ); - catchup_display_object_to_frame(&mut activation.context, child); - child.set_placed_by_script(activation.context.gc_context, true); - - // Movie clips created from ActionScript skip the next enterFrame, - // and consequently are observed to have their currentFrame lag one - // frame behind objects placed by the timeline (even if they were - // both placed in the same frame to begin with). - child - .base_mut(activation.context.gc_context) - .set_skip_next_enter_frame(true); - child.on_construction_complete(&mut activation.context); - break; - } - class_object = class.superclass_object(); - } - } - if let Some(dobj) = this.as_display_object() { if let Some(container) = dobj.as_container() { for child in container.iter_render_list() { diff --git a/core/src/avm2/globals/flash/display/loader.rs b/core/src/avm2/globals/flash/display/loader.rs index 2b8147162890..ea119d469c53 100644 --- a/core/src/avm2/globals/flash/display/loader.rs +++ b/core/src/avm2/globals/flash/display/loader.rs @@ -1,10 +1,12 @@ //! `flash.display.Loader` builtin/prototype use crate::avm2::activation::Activation; +use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; use crate::avm2::object::LoaderInfoObject; use crate::avm2::object::TObject; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; +use crate::avm2::ClassObject; use crate::avm2::Multiname; use crate::avm2::{Error, Object}; use crate::backend::navigator::{NavigationMethod, Request}; @@ -14,39 +16,38 @@ use crate::loader::{Avm2LoaderData, MovieLoaderEventHandler}; use crate::tag_utils::SwfMovie; use std::sync::Arc; -pub fn init<'gc>( +pub fn loader_allocator<'gc>( + class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, - this: Option>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(mut this) = this { - if this.as_display_object().is_none() { - let new_do = - LoaderDisplay::new_with_avm2(activation, this, activation.context.swf.clone()); - this.init_display_object(&mut activation.context, new_do.into()); - } - - // Some LoaderInfo properties (such as 'bytesLoaded' and 'bytesTotal') are always - // accessible, even before the 'init' event has fired. Using an empty movie gives - // us the correct value (0) for them. - let loader_info = LoaderInfoObject::not_yet_loaded( - activation, - Arc::new(SwfMovie::empty(activation.context.swf.version())), - Some(this), - None, - false, - )?; - this.set_property( - &Multiname::new( - activation.avm2().flash_display_internal, - "_contentLoaderInfo", - ), - loader_info.into(), - activation, - )?; - } - - Ok(Value::Undefined) +) -> Result, Error<'gc>> { + // Loader does not have an associated `Character` variant, and can never be + // instantiated from the timeline. + let display_object = LoaderDisplay::empty(activation, activation.context.swf.clone()).into(); + let mut loader = initialize_for_allocator(activation, display_object, class)?; + + // Note that the initialization of `_contentLoaderInfo` is intentionally done here, + // and not in the Loader constructor - subclasess of Loader can observe 'contentLoaderInfo' + // being set before super() is called. + + // Some LoaderInfo properties (such as 'bytesLoaded' and 'bytesTotal') are always + // accessible, even before the 'init' event has fired. Using an empty movie gives + // us the correct value (0) for them. + let loader_info = LoaderInfoObject::not_yet_loaded( + activation, + Arc::new(SwfMovie::empty(activation.context.swf.version())), + Some(loader), + None, + false, + )?; + loader.set_property( + &Multiname::new( + activation.avm2().flash_display_internal, + "_contentLoaderInfo", + ), + loader_info.into(), + activation, + )?; + Ok(loader) } pub fn load<'gc>( diff --git a/core/src/avm2/globals/flash/display/morph_shape.rs b/core/src/avm2/globals/flash/display/morph_shape.rs new file mode 100644 index 000000000000..e250728eccec --- /dev/null +++ b/core/src/avm2/globals/flash/display/morph_shape.rs @@ -0,0 +1,13 @@ +use crate::avm2::{error::argument_error, Activation, ClassObject, Error, Object}; + +pub fn morph_shape_allocator<'gc>( + _class: ClassObject<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + // The actual instantiation happens in `MorphShape::construct_frame` + return Err(Error::AvmError(argument_error( + activation, + "Error #2012: MorphShape$ class cannot be instantiated.", + 2012, + )?)); +} diff --git a/core/src/avm2/globals/flash/display/shape.rs b/core/src/avm2/globals/flash/display/shape.rs index 459daa71f354..06f100a14fe0 100644 --- a/core/src/avm2/globals/flash/display/shape.rs +++ b/core/src/avm2/globals/flash/display/shape.rs @@ -1,29 +1,44 @@ //! `flash.display.Shape` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, StageObject, TObject}; +use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; +use crate::avm2::object::{ClassObject, Object, StageObject, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::Multiname; use crate::display_object::Graphic; -/// Implements `flash.display.Shape`'s 'init' method, which is called from the constructor -pub fn init<'gc>( +pub fn shape_allocator<'gc>( + class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, - this: Option>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this { - activation.super_init(this, &[])?; +) -> Result, Error<'gc>> { + let shape_cls = activation.avm2().classes().shape; - if this.as_display_object().is_none() { - let new_do = Graphic::new_with_avm2(&mut activation.context, this); + let mut class_object = Some(class); + let orig_class = class; + while let Some(class) = class_object { + if class == shape_cls { + let display_object = Graphic::empty(&mut activation.context).into(); + return initialize_for_allocator(activation, display_object, orig_class); + } - this.init_display_object(&mut activation.context, new_do.into()); + if let Some((movie, symbol)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(class) + { + let child = activation + .context + .library + .library_for_movie_mut(movie) + .instantiate_by_id(symbol, activation.context.gc_context)?; + + return initialize_for_allocator(activation, child, orig_class); } + class_object = class.superclass_object(); } - - Ok(Value::Undefined) + unreachable!("A Shape subclass should have Shape in superclass chain"); } /// Implements `graphics`. diff --git a/core/src/avm2/globals/flash/display/simple_button.rs b/core/src/avm2/globals/flash/display/simple_button.rs index 493b39f1b0fc..236d8362feea 100644 --- a/core/src/avm2/globals/flash/display/simple_button.rs +++ b/core/src/avm2/globals/flash/display/simple_button.rs @@ -1,11 +1,11 @@ //! `flash.display.SimpleButton` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, TObject}; +use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; +use crate::avm2::object::{ClassObject, Object, StageObject, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::display_object::{Avm2Button, ButtonTracking, TDisplayObject}; -use crate::vminterface::Instantiator; use swf::ButtonState; pub use crate::avm2::globals::flash::media::sound_mixer::{ @@ -13,6 +13,46 @@ pub use crate::avm2::globals::flash::media::sound_mixer::{ }; use crate::avm2::parameters::ParametersExt; +pub fn simple_button_allocator<'gc>( + class: ClassObject<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + use crate::vminterface::Instantiator; + + let simplebutton_cls = activation.avm2().classes().simplebutton; + + let mut class_object = Some(class); + let orig_class = class; + while let Some(class) = class_object { + if class == simplebutton_cls { + let button = Avm2Button::empty_button(&mut activation.context); + // [NA] Buttons specifically need to PO'd + button.post_instantiation(&mut activation.context, None, Instantiator::Avm2, false); + let display_object = button.into(); + let obj = StageObject::for_display_object(activation, display_object, orig_class)?; + display_object.set_object2(&mut activation.context, obj.into()); + return Ok(obj.into()); + } + + if let Some((movie, symbol)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(class) + { + let child = activation + .context + .library + .library_for_movie_mut(movie) + .instantiate_by_id(symbol, activation.context.gc_context)?; + + return initialize_for_allocator(activation, child, orig_class); + } + class_object = class.superclass_object(); + } + unreachable!("A SimpleButton subclass should have SimpleButton in superclass chain"); +} + /// Implements `flash.display.SimpleButton`'s 'init' method. which is called from the constructor pub fn init<'gc>( activation: &mut Activation<'_, 'gc>, @@ -20,33 +60,42 @@ pub fn init<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(this) = this { - activation.super_init(this, &[])?; - - if this.as_display_object().is_none() { - let new_do = Avm2Button::empty_button(&mut activation.context); - - new_do.post_instantiation(&mut activation.context, None, Instantiator::Avm2, false); - this.init_display_object(&mut activation.context, new_do.into()); - + if let Some(new_do) = this + .as_display_object() + .and_then(|this| this.as_avm2_button()) + { let up_state = args .try_get_object(activation, 0) .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::UP, up_state); + if up_state.is_some() { + new_do.set_state_child(&mut activation.context, ButtonState::UP, up_state); + } let over_state = args .try_get_object(activation, 1) .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::OVER, over_state); + if over_state.is_some() { + new_do.set_state_child(&mut activation.context, ButtonState::OVER, over_state); + } let down_state = args .try_get_object(activation, 2) .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::DOWN, down_state); + if down_state.is_some() { + new_do.set_state_child(&mut activation.context, ButtonState::DOWN, down_state); + } let hit_state = args .try_get_object(activation, 3) .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::HIT_TEST, hit_state); + if hit_state.is_some() { + new_do.set_state_child(&mut activation.context, ButtonState::HIT_TEST, hit_state); + } + + // This performs the child state construction. + new_do.construct_frame(&mut activation.context); + } else { + unreachable!(); } } diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 65b2327ef489..70bc0dae26f9 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -1,29 +1,47 @@ //! `flash.display.Sprite` builtin/prototype use crate::avm2::activation::Activation; +use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; use crate::avm2::object::{Object, StageObject, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; -use crate::avm2::Error; use crate::avm2::Multiname; +use crate::avm2::{ClassObject, Error}; use crate::display_object::{MovieClip, SoundTransform, TDisplayObject}; use swf::{Rectangle, Twips}; -/// Implements `flash.display.Sprite`'s `init` method, which is called from the constructor -pub fn init<'gc>( +pub fn sprite_allocator<'gc>( + class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, - this: Option>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this { - activation.super_init(this, &[])?; +) -> Result, Error<'gc>> { + let sprite_cls = activation.avm2().classes().sprite; + + let mut class_object = Some(class); + let orig_class = class; + while let Some(class) = class_object { + if class == sprite_cls { + let movie = activation.context.swf.clone(); + let display_object = MovieClip::new(movie, activation.context.gc_context).into(); + return initialize_for_allocator(activation, display_object, orig_class); + } - if this.as_display_object().is_none() { - init_empty_sprite(activation, this)?; + if let Some((movie, symbol)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(class) + { + let child = activation + .context + .library + .library_for_movie_mut(movie) + .instantiate_by_id(symbol, activation.context.gc_context)?; + + return initialize_for_allocator(activation, child, orig_class); } + class_object = class.superclass_object(); } - - Ok(Value::Undefined) + unreachable!("A Sprite subclass should have Sprite in superclass chain"); } pub fn init_empty_sprite<'gc>( diff --git a/core/src/avm2/globals/flash/media/sound.rs b/core/src/avm2/globals/flash/media/sound.rs index 955ad67c3b93..5735df6796b1 100644 --- a/core/src/avm2/globals/flash/media/sound.rs +++ b/core/src/avm2/globals/flash/media/sound.rs @@ -19,8 +19,6 @@ pub fn init<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(this) = this { - activation.super_init(this, &[])?; - if !args.is_empty() { avm2_stub_constructor!(activation, "flash.media.Sound", "with arguments"); } diff --git a/core/src/avm2/globals/flash/system/application_domain.rs b/core/src/avm2/globals/flash/system/application_domain.rs index d7de7a5d09e0..77f559a3585d 100644 --- a/core/src/avm2/globals/flash/system/application_domain.rs +++ b/core/src/avm2/globals/flash/system/application_domain.rs @@ -18,8 +18,6 @@ pub fn init<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(this) = this { - activation.super_init(this, &[])?; - let parent_domain = if matches!(args[0], Value::Null) { activation.avm2().global_domain() } else { diff --git a/core/src/avm2/globals/flash/text/StaticText.as b/core/src/avm2/globals/flash/text/StaticText.as index 772dc89c2d9f..9fcb1f352684 100644 --- a/core/src/avm2/globals/flash/text/StaticText.as +++ b/core/src/avm2/globals/flash/text/StaticText.as @@ -1,11 +1,8 @@ package flash.text { import flash.display.DisplayObject; - [Ruffle(NativeInstanceInit)] - public class StaticText extends DisplayObject { - public function StaticText() { - throw new ArgumentError("Error #2012: StaticText$ class cannot be instantiated.", 2012); - } + [Ruffle(InstanceAllocator)] + public final class StaticText extends DisplayObject { public native function get text():String; } } \ No newline at end of file diff --git a/core/src/avm2/globals/flash/text/TextField.as b/core/src/avm2/globals/flash/text/TextField.as index 6080855c49fc..f96ed715f1af 100644 --- a/core/src/avm2/globals/flash/text/TextField.as +++ b/core/src/avm2/globals/flash/text/TextField.as @@ -2,15 +2,11 @@ package flash.text { import flash.display.InteractiveObject; import __ruffle__.stub_setter; + + [Ruffle(InstanceAllocator)] public class TextField extends InteractiveObject { internal var _styleSheet:StyleSheet; internal var _useRichTextClipboard:Boolean; - - public function TextField() { - this.init(); - } - - private native function init(); public native function get alwaysShowSelection():Boolean; public native function set alwaysShowSelection(value:Boolean):void; diff --git a/core/src/avm2/globals/flash/text/static_text.rs b/core/src/avm2/globals/flash/text/static_text.rs index 4d1dc6c6aa50..f0a10c58b5d1 100644 --- a/core/src/avm2/globals/flash/text/static_text.rs +++ b/core/src/avm2/globals/flash/text/static_text.rs @@ -1,17 +1,18 @@ -use crate::avm2::{Activation, Error, Object, Value}; +use crate::avm2::error::argument_error; +use crate::avm2::{Activation, ClassObject, Error, Object, Value}; use crate::avm2_stub_getter; -pub fn native_instance_init<'gc>( +pub fn static_text_allocator<'gc>( + _class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, - this: Option>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this { - activation.super_init(this, args)?; - } - - Ok(Value::Undefined) +) -> Result, Error<'gc>> { + return Err(Error::AvmError(argument_error( + activation, + "Error #2012: StaticText$ class cannot be instantiated.", + 2012, + )?)); } + /// Implements `StaticText.text` pub fn get_text<'gc>( activation: &mut Activation<'_, 'gc>, diff --git a/core/src/avm2/globals/flash/text/text_field.rs b/core/src/avm2/globals/flash/text/text_field.rs index 9db50b03ea48..38c85c733afb 100644 --- a/core/src/avm2/globals/flash/text/text_field.rs +++ b/core/src/avm2/globals/flash/text/text_field.rs @@ -1,7 +1,8 @@ //! `flash.text.TextField` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, TObject, TextFormatObject}; +use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; +use crate::avm2::object::{ClassObject, Object, TObject, TextFormatObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Error; @@ -11,24 +12,39 @@ use crate::string::AvmString; use crate::{avm2_stub_getter, avm2_stub_setter}; use swf::Color; -/// Implements `flash.text.TextField`'s `init` method, which is called from the constructor. -pub fn init<'gc>( +pub fn text_field_allocator<'gc>( + class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, - this: Option>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this { - activation.super_init(this, &[])?; +) -> Result, Error<'gc>> { + let textfield_cls = activation.avm2().classes().textfield; - if this.as_display_object().is_none() { + let mut class_object = Some(class); + let orig_class = class; + while let Some(class) = class_object { + if class == textfield_cls { let movie = activation.context.swf.clone(); - let new_do = EditText::new(&mut activation.context, movie, 0.0, 0.0, 100.0, 100.0); + let display_object = + EditText::new(&mut activation.context, movie, 0.0, 0.0, 100.0, 100.0).into(); + return initialize_for_allocator(activation, display_object, orig_class); + } - this.init_display_object(&mut activation.context, new_do.into()); + if let Some((movie, symbol)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(class) + { + let child = activation + .context + .library + .library_for_movie_mut(movie) + .instantiate_by_id(symbol, activation.context.gc_context)?; + + return initialize_for_allocator(activation, child, orig_class); } + class_object = class.superclass_object(); } - - Ok(Value::Undefined) + unreachable!("A TextField subclass should have TextField in superclass chain"); } pub fn get_always_show_selection<'gc>( diff --git a/core/src/avm2/globals/flash/utils/ByteArray.as b/core/src/avm2/globals/flash/utils/ByteArray.as index aae329889eeb..08bf8b1b7122 100644 --- a/core/src/avm2/globals/flash/utils/ByteArray.as +++ b/core/src/avm2/globals/flash/utils/ByteArray.as @@ -25,12 +25,9 @@ package flash.utils { public native function set position(value:uint):void; public function ByteArray() { - this.init(); this.objectEncoding = _defaultObjectEncoding; } - private native function init():void; - public native function clear():void; public function deflate(): void { diff --git a/core/src/avm2/globals/flash/utils/byte_array.rs b/core/src/avm2/globals/flash/utils/byte_array.rs index 9f88ecc0d348..342ed25286b7 100644 --- a/core/src/avm2/globals/flash/utils/byte_array.rs +++ b/core/src/avm2/globals/flash/utils/byte_array.rs @@ -4,7 +4,6 @@ pub use crate::avm2::object::byte_array_allocator; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::character::Character; use crate::string::AvmString; use encoding_rs::Encoding; use encoding_rs::UTF_8; @@ -12,40 +11,6 @@ use flash_lso::amf0::read::AMF0Decoder; use flash_lso::amf3::read::AMF3Decoder; use flash_lso::types::{AMFVersion, Element}; -/// Implements `flash.utils.ByteArray`'s instance constructor. -pub fn init<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Option>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(this) = this { - activation.super_init(this, &[])?; - - let class_object = this - .instance_of() - .ok_or("Attempted to construct ByteArray on a bare object")?; - if let Some((movie, id)) = activation - .context - .library - .avm2_class_registry() - .class_symbol(class_object) - { - if let Some(lib) = activation.context.library.library_for_movie(movie) { - if let Some(Character::BinaryData(binary_data)) = lib.character_by_id(id) { - let mut byte_array = this - .as_bytearray_mut(activation.context.gc_context) - .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - byte_array.clear(); - byte_array.write_bytes(binary_data.as_ref())?; - byte_array.set_position(0); - } - } - } - } - - Ok(Value::Undefined) -} - /// Writes a single byte to the bytearray pub fn write_byte<'gc>( activation: &mut Activation<'_, 'gc>, diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 5464969a8434..66bc163aba88 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -89,7 +89,7 @@ pub use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData}; pub use crate::avm2::object::sound_object::{sound_allocator, QueuedPlay, SoundData, SoundObject}; pub use crate::avm2::object::soundchannel_object::{sound_channel_allocator, SoundChannelObject}; pub use crate::avm2::object::stage3d_object::{stage_3d_allocator, Stage3DObject}; -pub use crate::avm2::object::stage_object::{stage_allocator, StageObject}; +pub use crate::avm2::object::stage_object::StageObject; pub use crate::avm2::object::textformat_object::{textformat_allocator, TextFormatObject}; pub use crate::avm2::object::texture_object::TextureObject; pub use crate::avm2::object::vector_object::{vector_allocator, VectorObject}; diff --git a/core/src/avm2/object/bitmapdata_object.rs b/core/src/avm2/object/bitmapdata_object.rs index 7c7d37b67bfe..d77e0071f0dd 100644 --- a/core/src/avm2/object/bitmapdata_object.rs +++ b/core/src/avm2/object/bitmapdata_object.rs @@ -21,7 +21,10 @@ pub fn bitmap_data_allocator<'gc>( activation.context.gc_context, BitmapDataObjectData { base, - bitmap_data: None, + // This always starts out as a dummy (invalid) BitmapDataWrapper, so + // that custom subclasses see a disposed BitmapData before they call super(). + // The real BitmapDataWrapper is set by BitmapData.init() + bitmap_data: Some(BitmapDataWrapper::dummy(activation.context.gc_context)), }, )) .into()) @@ -49,7 +52,16 @@ pub struct BitmapDataObjectData<'gc> { } impl<'gc> BitmapDataObject<'gc> { - pub fn from_bitmap_data( + // Constructs a BitmapData object from a BitmapDataWrapper. + // This is *not* used when explicitly constructing a BitmapData + // instance from ActionScript (e.g. `new BitmapData(100, 100)`, + // or `new MyBitmapDataSubclass(100, 100)`). + // + // Instead, this is used when constructing a `Bitmap` object, + // (from ActionScript or from the timeline), or when we need + // to produce a new BitmapData object from a `BitmapData` method + // like `clone()` + pub fn from_bitmap_data_internal( activation: &mut Activation<'_, 'gc>, bitmap_data: BitmapDataWrapper<'gc>, class: ClassObject<'gc>, @@ -64,7 +76,15 @@ impl<'gc> BitmapDataObject<'gc> { bitmap_data.init_object2(activation.context.gc_context, instance.into()); instance.install_instance_slots(activation.context.gc_context); - class.call_native_init(Some(instance.into()), &[], activation)?; + + // We call the custom BitmapData class with width and height... + // but, it always seems to be 1 in Flash Player when constructed from timeline? + // This will not actually cause us to create a BitmapData with dimensions (1, 1) - + // when the custom class makes a super() call, the BitmapData constructor will + // load in the real data from the linked SymbolClass. + if class != activation.avm2().classes().bitmapdata { + class.call_native_init(Some(instance.into()), &[1.into(), 1.into()], activation)?; + } Ok(instance.into()) } diff --git a/core/src/avm2/object/bytearray_object.rs b/core/src/avm2/object/bytearray_object.rs index 75c45754bd52..a0eb32f44e66 100644 --- a/core/src/avm2/object/bytearray_object.rs +++ b/core/src/avm2/object/bytearray_object.rs @@ -5,6 +5,7 @@ use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::Multiname; +use crate::character::Character; use core::fmt; use gc_arena::{Collect, GcCell, MutationContext}; use std::cell::{Ref, RefMut}; @@ -14,14 +15,34 @@ pub fn byte_array_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { + let storage = if let Some((movie, id)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(class) + { + if let Some(lib) = activation.context.library.library_for_movie(movie) { + if let Some(Character::BinaryData(binary_data)) = lib.character_by_id(id) { + Some(ByteArrayStorage::from_vec(binary_data.as_ref().to_vec())) + } else { + None + } + } else { + None + } + } else { + Some(ByteArrayStorage::new()) + }; + + let storage = storage.unwrap_or_else(|| { + unreachable!("A ByteArray subclass should have ByteArray in superclass chain") + }); + let base = ScriptObjectData::new(class); Ok(ByteArrayObject(GcCell::allocate( activation.context.gc_context, - ByteArrayObjectData { - base, - storage: ByteArrayStorage::new(), - }, + ByteArrayObjectData { base, storage }, )) .into()) } diff --git a/core/src/avm2/object/stage_object.rs b/core/src/avm2/object/stage_object.rs index c8ec68de6af4..e1cb8ec06007 100644 --- a/core/src/avm2/object/stage_object.rs +++ b/core/src/avm2/object/stage_object.rs @@ -12,23 +12,6 @@ use gc_arena::{Collect, GcCell, MutationContext}; use std::cell::{Ref, RefMut}; use std::fmt::Debug; -/// A class instance allocator that allocates Stage objects. -pub fn stage_allocator<'gc>( - class: ClassObject<'gc>, - activation: &mut Activation<'_, 'gc>, -) -> Result, Error<'gc>> { - let base = ScriptObjectData::new(class); - - Ok(StageObject(GcCell::allocate( - activation.context.gc_context, - StageObjectData { - base, - display_object: None, - }, - )) - .into()) -} - #[derive(Clone, Collect, Copy)] #[collect(no_drop)] pub struct StageObject<'gc>(GcCell<'gc, StageObjectData<'gc>>); diff --git a/core/src/display_object.rs b/core/src/display_object.rs index e31ea588f51f..c7f6524a783b 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -1345,9 +1345,9 @@ pub trait TDisplayObject<'gc>: /// if possible, set a named property on the parent matching the name of /// the object. /// - /// If the child was placed by AVM2, this function is a no-op, since AVM2 - /// will already trip these events. Objects that cannot be constructed by - /// the timeline do not need to call this method. + /// This still needs to be called for objects placed by AVM2, since we + /// need to stop the underlying MovieClip if the constructed class + /// does not extend MovieClip. /// /// Since we construct AVM2 display objects after they are allocated and /// placed on the render list, these steps have to be done by the child @@ -1393,14 +1393,16 @@ pub trait TDisplayObject<'gc>: } if let Some(movie) = self.as_movie_clip() { - if let Some(obj) = movie.object2().as_object() { - let movieclip_class = context.avm2.classes().movieclip; - // It's possible to have a DefineSprite tag with multiple frames, but have - // the corresponding `SymbolClass` *not* extend `MovieClip` (e.g. extending `Sprite` directly.) - // When this occurs, Flash Player will run the first frame, and immediately stop. - if !obj.is_of_type(movieclip_class, context) { - movie.stop(context); - } + let obj = movie + .object2() + .as_object() + .expect("MovieClip object should have been constructed"); + let movieclip_class = context.avm2.classes().movieclip; + // It's possible to have a DefineSprite tag with multiple frames, but have + // the corresponding `SymbolClass` *not* extend `MovieClip` (e.g. extending `Sprite` directly.) + // When this occurs, Flash Player will run the first frame, and immediately stop. + if !obj.is_of_type(movieclip_class, context) { + movie.stop(context); } } } diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs index f5cdbf1009dd..6f1d1db5acfc 100644 --- a/core/src/display_object/avm2_button.rs +++ b/core/src/display_object/avm2_button.rs @@ -227,17 +227,21 @@ impl<'gc> Avm2Button<'gc> { } } + // We manually call `construct_frame` for `child` and `state_sprite` - normally + // this would be done in the `DisplayObject` constructor, but SimpleButton does + // not have children in the normal DisplayObjectContainer sense. + if children.len() == 1 { let child = children.first().cloned().unwrap().0; child.set_parent(context, Some(self.into())); child.post_instantiation(context, None, Instantiator::Movie, false); catchup_display_object_to_frame(context, child); + child.construct_frame(context); (child, false) } else { let state_sprite = MovieClip::new(movie, context.gc_context); - state_sprite.set_avm2_class(context.gc_context, Some(sprite_class)); state_sprite.set_parent(context, Some(self.into())); catchup_display_object_to_frame(context, state_sprite.into()); @@ -252,8 +256,11 @@ impl<'gc> Avm2Button<'gc> { child.post_instantiation(context, None, Instantiator::Movie, false); catchup_display_object_to_frame(context, child); child.set_parent(context, Some(state_sprite.into())); + child.construct_frame(context); } + state_sprite.construct_frame(context); + (state_sprite.into(), true) } } diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index dd9e84b0e202..b5e31a1351a3 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -2,8 +2,9 @@ use crate::avm1; use crate::avm2::{ - Activation as Avm2Activation, ClassObject as Avm2ClassObject, Object as Avm2Object, - StageObject as Avm2StageObject, Value as Avm2Value, + Activation as Avm2Activation, BitmapDataObject as Avm2BitmapDataObject, + ClassObject as Avm2ClassObject, Object as Avm2Object, StageObject as Avm2StageObject, TObject, + Value as Avm2Value, }; use crate::bitmap::bitmap_data::{BitmapData, BitmapDataWrapper}; use crate::context::{RenderContext, UpdateContext}; @@ -272,23 +273,40 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> { &self, context: &mut UpdateContext<'_, 'gc>, _init_object: Option>, - _instantiated_by: Instantiator, + instantiated_by: Instantiator, run_frame: bool, ) { if context.is_action_script_3() { let mut activation = Avm2Activation::from_nothing(context.reborrow()); - let bitmap = self - .avm2_bitmap_class() - .unwrap_or_else(|| activation.context.avm2.classes().bitmap); - match Avm2StageObject::for_display_object_childless( - &mut activation, - (*self).into(), - bitmap, - ) { - Ok(object) => { - self.0.write(activation.context.gc_context).avm2_object = Some(object.into()) - } - Err(e) => tracing::error!("Got error when creating AVM2 side of bitmap: {}", e), + if !instantiated_by.is_avm() { + let bitmap_cls = self + .avm2_bitmap_class() + .unwrap_or_else(|| activation.context.avm2.classes().bitmap); + let bitmapdata_cls = self + .avm2_bitmapdata_class() + .unwrap_or_else(|| activation.context.avm2.classes().bitmapdata); + + let mc = activation.context.gc_context; + + let bitmap = Avm2StageObject::for_display_object_childless( + &mut activation, + (*self).into(), + bitmap_cls, + ) + .expect("can't throw from post_instantiation -_-"); + self.0.write(mc).avm2_object = Some(bitmap.into()); + + let bitmap_data_obj = Avm2BitmapDataObject::from_bitmap_data_internal( + &mut activation, + BitmapDataWrapper::dummy(mc), + bitmapdata_cls, + ) + .expect("can't throw from post_instantiation -_-"); + + self.set_bitmap_data( + &mut activation.context, + bitmap_data_obj.as_bitmap_data().unwrap(), + ); } self.on_construction_complete(context); diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index e36000f12292..58fc3aba9cb3 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -71,10 +71,7 @@ impl<'gc> Graphic<'gc> { } /// Construct an empty `Graphic`. - pub fn new_with_avm2( - context: &mut UpdateContext<'_, 'gc>, - avm2_object: Avm2Object<'gc>, - ) -> Self { + pub fn empty(context: &mut UpdateContext<'_, 'gc>) -> Self { let static_data = GraphicStatic { id: 0, bounds: Default::default(), @@ -100,7 +97,7 @@ impl<'gc> Graphic<'gc> { GraphicData { base: Default::default(), static_data: gc_arena::Gc::allocate(context.gc_context, static_data), - avm2_object: Some(avm2_object), + avm2_object: None, drawing: Some(drawing), }, )) diff --git a/core/src/display_object/loader_display.rs b/core/src/display_object/loader_display.rs index 77a4b1a3b405..459faa4489cd 100644 --- a/core/src/display_object/loader_display.rs +++ b/core/src/display_object/loader_display.rs @@ -36,25 +36,22 @@ impl fmt::Debug for LoaderDisplay<'_> { pub struct LoaderDisplayData<'gc> { base: InteractiveObjectBase<'gc>, container: ChildContainer<'gc>, - avm2_object: Avm2Object<'gc>, + avm2_object: Option>, movie: Arc, } impl<'gc> LoaderDisplay<'gc> { - pub fn new_with_avm2( - activation: &mut Activation<'_, 'gc>, - avm2_object: Avm2Object<'gc>, - movie: Arc, - ) -> Self { + pub fn empty(activation: &mut Activation<'_, 'gc>, movie: Arc) -> Self { let obj = LoaderDisplay(GcCell::allocate( activation.context.gc_context, LoaderDisplayData { base: Default::default(), container: ChildContainer::new(), - avm2_object, + avm2_object: None, // Set later after assignment movie, }, )); + obj.set_placed_by_script(activation.context.gc_context, true); activation.context.avm2.add_orphan_obj(obj.into()); obj @@ -95,7 +92,15 @@ impl<'gc> TDisplayObject<'gc> for LoaderDisplay<'gc> { } fn object2(&self) -> Avm2Value<'gc> { - self.0.read().avm2_object.into() + self.0 + .read() + .avm2_object + .map(Avm2Value::from) + .unwrap_or(Avm2Value::Null) + } + + fn set_object2(&self, context: &mut UpdateContext<'_, 'gc>, to: Avm2Object<'gc>) { + self.0.write(context.gc_context).avm2_object = Some(to); } fn as_container(self) -> Option> { diff --git a/core/src/display_object/morph_shape.rs b/core/src/display_object/morph_shape.rs index 8c25aabaf67f..740476e74901 100644 --- a/core/src/display_object/morph_shape.rs +++ b/core/src/display_object/morph_shape.rs @@ -121,7 +121,11 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> { if context.is_action_script_3() && matches!(self.object2(), Avm2Value::Null) { let class = context.avm2.classes().morphshape; let mut activation = Avm2Activation::from_nothing(context.reborrow()); - match Avm2StageObject::for_display_object(&mut activation, (*self).into(), class) { + match Avm2StageObject::for_display_object_childless( + &mut activation, + (*self).into(), + class, + ) { Ok(object) => self.0.write(context.gc_context).object = Some(object.into()), Err(e) => tracing::error!("Got {} when constructing AVM2 side of MorphShape", e), }; diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index e9791e88a2d1..8e0d46aed919 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -2469,7 +2469,14 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { if needs_construction { self.construct_as_avm2_object(context); self.on_construction_complete(context); - } else { + // If we're in the load frame and we were constructed by ActionScript, + // then we want to wait for the DisplayObject constructor to run + // 'construct_frame' on children. This is observable by ActionScript - + // before calling super(), 'this.numChildren' will show a non-zero number + // when we have children placed on the load frame, but 'this.getChildAt(0)' + // will return 'null' since the children haven't had their AVM2 objects + // constructed by `construct_frame` yet. + } else if !(is_load_frame && self.placed_by_script()) { // The supercall constructor for display objects is responsible // for triggering construct_frame on frame 1. for child in self.iter_render_list() { diff --git a/core/src/loader.rs b/core/src/loader.rs index 07b1d7b796ba..629c87f770f1 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -1504,7 +1504,7 @@ impl<'gc> Loader<'gc> { bitmap_data, )); let bitmapdata_class = activation.context.avm2.classes().bitmapdata; - let bitmapdata_avm2 = BitmapDataObject::from_bitmap_data( + let bitmapdata_avm2 = BitmapDataObject::from_bitmap_data_internal( &mut activation, bitmapdata_wrapper, bitmapdata_class, diff --git a/tests/tests/swfs/avm2/displayobject_early_init/BitmapTest.as b/tests/tests/swfs/avm2/displayobject_early_init/BitmapTest.as new file mode 100644 index 000000000000..c134aed98a6d --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/BitmapTest.as @@ -0,0 +1,16 @@ +package { + + import flash.display.BitmapData; + + + // FIXME - this should extend Bitmap + public class BitmapTest extends BitmapData { + + + public function BitmapTest() { + //trace("BitmapTest before super(): this.bitmapData.width = " + this.bitmapData.width); + super(100, 100); + } + } + +} diff --git a/tests/tests/swfs/avm2/displayobject_early_init/BoundByteArray.as b/tests/tests/swfs/avm2/displayobject_early_init/BoundByteArray.as new file mode 100644 index 000000000000..90fd4dbef675 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/BoundByteArray.as @@ -0,0 +1,12 @@ +package { + import flash.utils.ByteArray; + + [Embed(source="data.txt", mimeType="application/octet-stream")] + public class BoundByteArray extends ByteArray { + public function BoundByteArray() { + trace("BoundByteArray before super(): this.bytesAvailable = " + this.bytesAvailable); + super(); + trace("BoundByteArray after super(): this.bytseAvailable = " + this.bytesAvailable); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/BoundSound.as b/tests/tests/swfs/avm2/displayobject_early_init/BoundSound.as new file mode 100644 index 000000000000..7c9d73bc0a69 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/BoundSound.as @@ -0,0 +1,12 @@ +package { + import flash.media.Sound; + + [Embed(source="noise.mp3", mimeType="audio/mpeg")] + public class BoundSound extends Sound { + public function BoundSound() { + trace("BoundSound before super(): this.bytesLoaded = " + this.bytesLoaded + " this.bytesTotal = " + this.bytesTotal); + super(); + trace("BoundSound after super(): this.bytesLoaded = " + this.bytesLoaded + " this.bytesTotal = " + this.bytesTotal); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/DetachedSprite.as b/tests/tests/swfs/avm2/displayobject_early_init/DetachedSprite.as new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/tests/swfs/avm2/displayobject_early_init/Main.as b/tests/tests/swfs/avm2/displayobject_early_init/Main.as new file mode 100644 index 000000000000..06537ce71009 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/Main.as @@ -0,0 +1,44 @@ +package { + import flash.display.MovieClip; + + public class Main extends MovieClip { + // FIXME - add 'MyButton' as a timeline child once we fix + // Ruffle's SimpleButton construction handling. + public function Main() { + trace("Constructing MyImage from ActionScript"); + var image = new MyImage(42, 24); + trace("Image: " + image); + + new UnlinkedBitmapData(); + + trace("Constructing MySprite from ActionScript"); + var mySprite = new MySprite(); + trace("mySprite: " + mySprite); + + trace("Constructing MyMovieClip from ActionScript"); + var myMovieClip = new MyMovieClip(); + trace("myMovieClip: " + myMovieClip); + + new UnlinkedSprite(); + new UnlinkedMovieClip(); + new UnlinkedLoader(); + new UnlinkedShape(); + new UnlinkedButton(); + new UnlinkedTextField(); + new UnlinkedByteArray(); + new UnlinkedSound(); + + trace("Constructing MyButton from ActionScript"); + var myButton = new MyButton(); + trace("myButton: " + myButton); + + trace("Constructing BoundByteArray from ActionScript"); + var boundByteArray = new BoundByteArray(); + trace("boundByteArray: " + boundByteArray); + + trace("Constructing BoundSound from ActionScriot"); + var boundSound = new BoundSound(); + trace("boundSound: " + boundSound); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/MyButton.as b/tests/tests/swfs/avm2/displayobject_early_init/MyButton.as new file mode 100644 index 000000000000..bd129df6ce38 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/MyButton.as @@ -0,0 +1,17 @@ +package { + + import flash.display.SimpleButton; + + + public class MyButton extends SimpleButton { + + + public function MyButton() { + // FIXME - test button states when we fix SimpleButton construction to match Flash Player + trace("MyButton before super(): this.mouseEnabled = " + this.mouseEnabled + " this.parent = " + this.parent); + super(); + trace("MyButton after super(): this.mouseEnabled = " + this.mouseEnabled + " this.parent = " + this.parent); + } + } + +} diff --git a/tests/tests/swfs/avm2/displayobject_early_init/MyImage.as b/tests/tests/swfs/avm2/displayobject_early_init/MyImage.as new file mode 100644 index 000000000000..426b873bae17 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/MyImage.as @@ -0,0 +1,21 @@ +package { + + import flash.display.BitmapData; + + + public class MyImage extends BitmapData { + + + public function MyImage(width: int, height: int) { + trace("MyImage before super(): width=" + width + " height=" + height); + try { + this.width; + } catch (e) { + trace("Caught error: " + e); + } + super(width, height) + trace("MyImage after super(): this.width=" + this.width + " this.height=" + this.height + " pixel: " + this.getPixel(0, 0)); + } + } + +} diff --git a/tests/tests/swfs/avm2/displayobject_early_init/MyMovieClip.as b/tests/tests/swfs/avm2/displayobject_early_init/MyMovieClip.as new file mode 100644 index 000000000000..f6249d65e0b3 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/MyMovieClip.as @@ -0,0 +1,21 @@ +package { + + import flash.display.MovieClip; + import flash.text.TextField; + + + public class MyMovieClip extends MovieClip { + + + public function MyMovieClip() { + var earlyGraphics = this.graphics; + trace("MyMovieClip before super(): this.graphics: " + this.graphics + " this.numChildren = " + this.numChildren + " this.getChildAt(0) = " + this.getChildAt(0) + " this.parent = " + this.parent); + var textChild = new TextField(); + textChild.text = "Text before super()"; + this.addChild(textChild); + super(); + trace("MyMovieClip after super(): this.graphics === earlyGraphics: " + (this.graphics === earlyGraphics) + " this.numChildren = " + this.numChildren + " this.getChildAt(0) = " + this.getChildAt(0) + " this.parent = " + this.parent); + } + } + +} diff --git a/tests/tests/swfs/avm2/displayobject_early_init/MySprite.as b/tests/tests/swfs/avm2/displayobject_early_init/MySprite.as new file mode 100644 index 000000000000..75ea70c7d823 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/MySprite.as @@ -0,0 +1,21 @@ +package { + + import flash.display.Sprite; + import flash.text.TextField; + + + public class MySprite extends Sprite { + + + public function MySprite() { + var earlyGraphics = this.graphics; + trace("MySprite before super(): this.graphics: " + this.graphics + " this.numChildren = " + this.numChildren + " this.getChildAt(0) = " + this.getChildAt(0) + " this.parent = " + this.parent); + var textChild = new TextField(); + textChild.text = "Text before super()"; + this.addChild(textChild); + super(); + trace("MySprite after super(): this.graphics === earlyGraphics: " + (this.graphics === earlyGraphics) + " this.numChildren = " + this.numChildren + " this.getChildAt(0) = " + this.getChildAt(0) + " this.parent = " + this.parent); + } + } + +} diff --git a/tests/tests/swfs/avm2/displayobject_early_init/OverButtonState.as b/tests/tests/swfs/avm2/displayobject_early_init/OverButtonState.as new file mode 100644 index 000000000000..fe5d79165c78 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/OverButtonState.as @@ -0,0 +1,14 @@ +package { + + import flash.display.MovieClip; + + + public class OverButtonState extends MovieClip { + + + public function OverButtonState() { + trace("Constructed OverButtonState"); + } + } + +} diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedBitmapData.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedBitmapData.as new file mode 100644 index 000000000000..1d472737c295 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedBitmapData.as @@ -0,0 +1,16 @@ +package { + import flash.display.BitmapData; + + public class UnlinkedBitmapData extends BitmapData { + public function UnlinkedBitmapData() { + trace("UnlinkedBitmapData before super()"); + try { + this.width; + } catch (e) { + trace("Caught error: " + e); + } + super(100, 100); + trace("UnlinkedBitmapData after super(): this.width=" + this.width); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedButton.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedButton.as new file mode 100644 index 000000000000..7cd7952c0964 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedButton.as @@ -0,0 +1,17 @@ +package { + + import flash.display.SimpleButton; + + + public class UnlinkedButton extends SimpleButton { + + + public function UnlinkedButton() { + // FIXME - test button states when we fix SimpleButton construction to match Flash Player + trace("UnlinkedButton before super(): this.mouseEnabled = " + this.mouseEnabled); + super(); + trace("UnlinkedButton after super(): this.mouseEnabled = " + this.mouseEnabled); + } + } + +} diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedByteArray.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedByteArray.as new file mode 100644 index 000000000000..d050488065bc --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedByteArray.as @@ -0,0 +1,11 @@ +package { + import flash.utils.ByteArray; + + public class UnlinkedByteArray extends ByteArray { + public function UnlinkedByteArray() { + trace("UnlinkedByteArray before super(): this.objectEncoding = " + this.objectEncoding); + super(); + trace("UnlinkedByteArray after super(): this.objectEncoding = " + this.objectEncoding); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedLoader.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedLoader.as new file mode 100644 index 000000000000..f9bdece1fd12 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedLoader.as @@ -0,0 +1,11 @@ +package { + import flash.display.Loader; + + public class UnlinkedLoader extends Loader { + public function UnlinkedLoader() { + trace("UnlinkedLoader before super(): this.contentLoaderInfo = " + this.contentLoaderInfo + " this.numChildren = " + this.numChildren); + super(); + trace("UnlinkedLoader after super(): this.contentLoaderInfo = " + this.contentLoaderInfo + " this.numChildren = " + this.numChildren); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedMovieClip.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedMovieClip.as new file mode 100644 index 000000000000..bc9995845152 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedMovieClip.as @@ -0,0 +1,11 @@ +package { + import flash.display.MovieClip; + + public class UnlinkedMovieClip extends MovieClip { + public function UnlinkedMovieClip() { + trace("UnlinkedMovieClip before super(): this.graphics: " + this.graphics + " this.numChildren = " + this.numChildren); + super(); + trace("UnlinkedMovieClip after super(): this.graphics: " + this.graphics + " this.numChildren = " + this.numChildren); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedShape.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedShape.as new file mode 100644 index 000000000000..a80034266a39 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedShape.as @@ -0,0 +1,11 @@ +package { + import flash.display.Shape; + + public class UnlinkedShape extends Shape { + public function UnlinkedShape() { + trace("UnlinkedShape before super(): this.graphics: " + this.graphics); + super(); + trace("UnlinkedShape after super(): this.graphics: " + this.graphics); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedSound.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedSound.as new file mode 100644 index 000000000000..ef66cb14afdf --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedSound.as @@ -0,0 +1,11 @@ +package { + import flash.media.Sound; + + public class UnlinkedSound extends Sound { + public function UnlinkedSound() { + trace("UnlinkedSound before super(): this.bytesLoaded = " + this.bytesLoaded); + super(); + trace("UnlinkedSound after super(): this.bytesLoaded = " + this.bytesLoaded); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedSprite.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedSprite.as new file mode 100644 index 000000000000..b539de0fb712 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedSprite.as @@ -0,0 +1,11 @@ +package { + import flash.display.Sprite; + + public class UnlinkedSprite extends Sprite { + public function UnlinkedSprite() { + trace("UnlinkedSprite before super(): this.graphics: " + this.graphics + " this.numChildren = " + this.numChildren); + super(); + trace("UnlinkedSprite after super(): this.graphics: " + this.graphics + " this.numChildren = " + this.numChildren); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedTextField.as b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedTextField.as new file mode 100644 index 000000000000..cf44d147272b --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/UnlinkedTextField.as @@ -0,0 +1,11 @@ +package { + import flash.text.TextField; + + public class UnlinkedTextField extends TextField { + public function UnlinkedTextField() { + trace("UnlinkedTextField before super(): this.mouseEnabled = " + this.gridFitType); + super(); + trace("UnlinkedTextField after super(): this.mouseEnabled = " + this.gridFitType); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_early_init/bitmap_test.png b/tests/tests/swfs/avm2/displayobject_early_init/bitmap_test.png new file mode 100644 index 000000000000..2ccb10f624a4 Binary files /dev/null and b/tests/tests/swfs/avm2/displayobject_early_init/bitmap_test.png differ diff --git a/tests/tests/swfs/avm2/displayobject_early_init/data.txt b/tests/tests/swfs/avm2/displayobject_early_init/data.txt new file mode 100644 index 000000000000..a46c98ae9979 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/data.txt @@ -0,0 +1 @@ +This is some text diff --git a/tests/tests/swfs/avm2/displayobject_early_init/noise.mp3 b/tests/tests/swfs/avm2/displayobject_early_init/noise.mp3 new file mode 100644 index 000000000000..862f41452ee4 Binary files /dev/null and b/tests/tests/swfs/avm2/displayobject_early_init/noise.mp3 differ diff --git a/tests/tests/swfs/avm2/displayobject_early_init/output.txt b/tests/tests/swfs/avm2/displayobject_early_init/output.txt new file mode 100644 index 000000000000..d339cf1e39f8 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/output.txt @@ -0,0 +1,54 @@ +MyImage before super(): width=1 height=1 +Caught error: ArgumentError: Error #2015: Invalid BitmapData. +MyImage after super(): this.width=300 this.height=300 pixel: 39679 +MySprite before super(): this.graphics: [object Graphics] this.numChildren = 1 this.getChildAt(0) = null this.parent = [object Main] +MySprite after super(): this.graphics === earlyGraphics: true this.numChildren = 2 this.getChildAt(0) = [object Shape] this.parent = [object Main] +MyMovieClip before super(): this.graphics: [object Graphics] this.numChildren = 1 this.getChildAt(0) = null this.parent = [object Main] +MyMovieClip after super(): this.graphics === earlyGraphics: true this.numChildren = 2 this.getChildAt(0) = [object Shape] this.parent = [object Main] +Constructing MyImage from ActionScript +MyImage before super(): width=42 height=24 +Caught error: ArgumentError: Error #2015: Invalid BitmapData. +MyImage after super(): this.width=300 this.height=300 pixel: 39679 +Image: [object MyImage] +UnlinkedBitmapData before super() +Caught error: ArgumentError: Error #2015: Invalid BitmapData. +UnlinkedBitmapData after super(): this.width=100 +Constructing MySprite from ActionScript +MySprite before super(): this.graphics: [object Graphics] this.numChildren = 1 this.getChildAt(0) = null this.parent = null +MySprite after super(): this.graphics === earlyGraphics: true this.numChildren = 2 this.getChildAt(0) = [object Shape] this.parent = null +mySprite: [object MySprite] +Constructing MyMovieClip from ActionScript +MyMovieClip before super(): this.graphics: [object Graphics] this.numChildren = 1 this.getChildAt(0) = null this.parent = null +MyMovieClip after super(): this.graphics === earlyGraphics: true this.numChildren = 2 this.getChildAt(0) = [object Shape] this.parent = null +myMovieClip: [object MyMovieClip] +UnlinkedSprite before super(): this.graphics: [object Graphics] this.numChildren = 0 +UnlinkedSprite after super(): this.graphics: [object Graphics] this.numChildren = 0 +UnlinkedMovieClip before super(): this.graphics: [object Graphics] this.numChildren = 0 +UnlinkedMovieClip after super(): this.graphics: [object Graphics] this.numChildren = 0 +UnlinkedLoader before super(): this.contentLoaderInfo = [object LoaderInfo] this.numChildren = 0 +UnlinkedLoader after super(): this.contentLoaderInfo = [object LoaderInfo] this.numChildren = 0 +UnlinkedShape before super(): this.graphics: [object Graphics] +UnlinkedShape after super(): this.graphics: [object Graphics] +UnlinkedButton before super(): this.mouseEnabled = true +UnlinkedButton after super(): this.mouseEnabled = true +UnlinkedTextField before super(): this.mouseEnabled = pixel +UnlinkedTextField after super(): this.mouseEnabled = pixel +UnlinkedByteArray before super(): this.objectEncoding = 3 +UnlinkedByteArray after super(): this.objectEncoding = 3 +UnlinkedSound before super(): this.bytesLoaded = 0 +UnlinkedSound after super(): this.bytesLoaded = 0 +Constructing MyButton from ActionScript +Constructed OverButtonState +Constructed OverButtonState +MyButton before super(): this.mouseEnabled = true this.parent = null +MyButton after super(): this.mouseEnabled = true this.parent = null +myButton: [object MyButton] +Constructing BoundByteArray from ActionScript +BoundByteArray before super(): this.bytesAvailable = 18 +BoundByteArray after super(): this.bytseAvailable = 18 +boundByteArray: This is some text + +Constructing BoundSound from ActionScriot +BoundSound before super(): this.bytesLoaded = 0 this.bytesTotal = 0 +BoundSound after super(): this.bytesLoaded = 4180 this.bytesTotal = 4180 +boundSound: [object BoundSound] diff --git a/tests/tests/swfs/avm2/displayobject_early_init/test.fla b/tests/tests/swfs/avm2/displayobject_early_init/test.fla new file mode 100644 index 000000000000..4dde25a93e1f Binary files /dev/null and b/tests/tests/swfs/avm2/displayobject_early_init/test.fla differ diff --git a/tests/tests/swfs/avm2/displayobject_early_init/test.png b/tests/tests/swfs/avm2/displayobject_early_init/test.png new file mode 100644 index 000000000000..2fcefedf9596 Binary files /dev/null and b/tests/tests/swfs/avm2/displayobject_early_init/test.png differ diff --git a/tests/tests/swfs/avm2/displayobject_early_init/test.swf b/tests/tests/swfs/avm2/displayobject_early_init/test.swf new file mode 100644 index 000000000000..1b3fbc51925e Binary files /dev/null and b/tests/tests/swfs/avm2/displayobject_early_init/test.swf differ diff --git a/tests/tests/swfs/avm2/displayobject_early_init/test.toml b/tests/tests/swfs/avm2/displayobject_early_init/test.toml new file mode 100644 index 000000000000..dbee897f5863 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_early_init/test.toml @@ -0,0 +1 @@ +num_frames = 1 diff --git a/tests/tests/swfs/avm2/displayobject_subclass/Main.as b/tests/tests/swfs/avm2/displayobject_subclass/Main.as new file mode 100644 index 000000000000..e2c2aade823e --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_subclass/Main.as @@ -0,0 +1,18 @@ +package { + import flash.display.DisplayObject; + + public class Main { + public function Main() { + try { + new MyDisplayObjectSubclass(); + } catch (e) { + trace("Caught err: " + e); + } + try { + new DisplayObject(); + } catch (e) { + trace("Caught err: " + e); + } + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_subclass/MyDisplayObjectSubclass.as b/tests/tests/swfs/avm2/displayobject_subclass/MyDisplayObjectSubclass.as new file mode 100644 index 000000000000..5bfe84a48248 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_subclass/MyDisplayObjectSubclass.as @@ -0,0 +1,10 @@ +package { + import flash.display.DisplayObject; + + public class MyDisplayObjectSubclass extends DisplayObject { + public function MyDisplayObjectSubclass() { + trace("ERR - constructor should not be reachable"); + super(); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_subclass/output.txt b/tests/tests/swfs/avm2/displayobject_subclass/output.txt new file mode 100644 index 000000000000..b67800f4b0e8 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_subclass/output.txt @@ -0,0 +1,2 @@ +Caught err: ArgumentError: Error #2012: MyDisplayObjectSubclass$ class cannot be instantiated. +Caught err: ArgumentError: Error #2012: DisplayObject$ class cannot be instantiated. diff --git a/tests/tests/swfs/avm2/displayobject_subclass/test.fla b/tests/tests/swfs/avm2/displayobject_subclass/test.fla new file mode 100644 index 000000000000..7cf5792a9835 Binary files /dev/null and b/tests/tests/swfs/avm2/displayobject_subclass/test.fla differ diff --git a/tests/tests/swfs/avm2/displayobject_subclass/test.swf b/tests/tests/swfs/avm2/displayobject_subclass/test.swf new file mode 100644 index 000000000000..361041f986a4 Binary files /dev/null and b/tests/tests/swfs/avm2/displayobject_subclass/test.swf differ diff --git a/tests/tests/swfs/avm2/displayobject_subclass/test.toml b/tests/tests/swfs/avm2/displayobject_subclass/test.toml new file mode 100644 index 000000000000..dbee897f5863 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_subclass/test.toml @@ -0,0 +1 @@ +num_frames = 1 diff --git a/tests/tests/swfs/avm2/simplebutton_multi_children/Main.as b/tests/tests/swfs/avm2/simplebutton_multi_children/Main.as new file mode 100644 index 000000000000..711df8013924 --- /dev/null +++ b/tests/tests/swfs/avm2/simplebutton_multi_children/Main.as @@ -0,0 +1,26 @@ +package { + import flash.events.Event; + import flash.display.DisplayObjectContainer; + import flash.display.MovieClip; + + public class Main extends MovieClip { + public function Main() { + this.addEventListener("enterFrame", this.onEnterFrame); + } + + private function onEnterFrame(e: Event) { + var button = new MyButton(); + printChildren("upState", button.upState); + printChildren("downState", button.downState); + printChildren("overState", button.overState); + printChildren("hitTestState", button.hitTestState); + } + + private function printChildren(name: String, container: DisplayObjectContainer) { + trace(name + ": " + container); + for (var i = 0; i < container.numChildren; i++) { + trace("Child " + i + ": " + container.getChildAt(i) + " text: " + container.getChildAt(i)["text"]); + } + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/simplebutton_multi_children/MyButton.as b/tests/tests/swfs/avm2/simplebutton_multi_children/MyButton.as new file mode 100644 index 000000000000..4d8fc3d571c2 --- /dev/null +++ b/tests/tests/swfs/avm2/simplebutton_multi_children/MyButton.as @@ -0,0 +1,14 @@ +package { + + import flash.display.SimpleButton; + + + public class MyButton extends SimpleButton { + + + public function MyButton() { + super(); + } + } + +} diff --git a/tests/tests/swfs/avm2/simplebutton_multi_children/output.txt b/tests/tests/swfs/avm2/simplebutton_multi_children/output.txt new file mode 100644 index 000000000000..ae0219a23d25 --- /dev/null +++ b/tests/tests/swfs/avm2/simplebutton_multi_children/output.txt @@ -0,0 +1,20 @@ +upState: [object Sprite] +Child 0: [object TextField] text: Up state child 1 + +Child 1: [object TextField] text: Up state child 2 + +downState: [object Sprite] +Child 0: [object TextField] text: Down state child 1 + +Child 1: [object TextField] text: Down state child 2 + +overState: [object Sprite] +Child 0: [object TextField] text: Over state child 1 + +Child 1: [object TextField] text: Over state child 2 + +hitTestState: [object Sprite] +Child 0: [object TextField] text: Hit state child 1 + +Child 1: [object TextField] text: Hit state child 2 + diff --git a/tests/tests/swfs/avm2/simplebutton_multi_children/test.fla b/tests/tests/swfs/avm2/simplebutton_multi_children/test.fla new file mode 100644 index 000000000000..63de1eaa3101 Binary files /dev/null and b/tests/tests/swfs/avm2/simplebutton_multi_children/test.fla differ diff --git a/tests/tests/swfs/avm2/simplebutton_multi_children/test.swf b/tests/tests/swfs/avm2/simplebutton_multi_children/test.swf new file mode 100644 index 000000000000..01668371b7a8 Binary files /dev/null and b/tests/tests/swfs/avm2/simplebutton_multi_children/test.swf differ diff --git a/tests/tests/swfs/avm2/simplebutton_multi_children/test.toml b/tests/tests/swfs/avm2/simplebutton_multi_children/test.toml new file mode 100644 index 000000000000..ded2e363a928 --- /dev/null +++ b/tests/tests/swfs/avm2/simplebutton_multi_children/test.toml @@ -0,0 +1 @@ +num_frames = 2 diff --git a/tests/tests/swfs/avm2/sprite_with_frames/expected.png b/tests/tests/swfs/avm2/sprite_with_frames/expected.png index dfbe61c2e752..1e413205a7d7 100644 Binary files a/tests/tests/swfs/avm2/sprite_with_frames/expected.png and b/tests/tests/swfs/avm2/sprite_with_frames/expected.png differ diff --git a/tests/tests/swfs/avm2/sprite_with_frames/test.fla b/tests/tests/swfs/avm2/sprite_with_frames/test.fla index cf590f77b6c8..8d0a83217840 100644 Binary files a/tests/tests/swfs/avm2/sprite_with_frames/test.fla and b/tests/tests/swfs/avm2/sprite_with_frames/test.fla differ diff --git a/tests/tests/swfs/avm2/sprite_with_frames/test.swf b/tests/tests/swfs/avm2/sprite_with_frames/test.swf index 126e575b0eb0..2318486008bf 100644 Binary files a/tests/tests/swfs/avm2/sprite_with_frames/test.swf and b/tests/tests/swfs/avm2/sprite_with_frames/test.swf differ