avm2: Store `LoaderInfo` object on `MovieClip` and `Stage`

Previously, we would create a fresh `LoaderInfo` object each
time the `loaderInfo` property was accessed. However, users can
add event handlers to a `LoaderInfo`, so we need to create and
store exactly one `LoaderInfo` object per movie (and stage).

To verify that we're correctly handling the storage of `LoaderInfo`,
I've implemented firing the "init" event. This required a new
`on_frame_exit` hook, so that we can properly fire the "init"
event after the "exitFrame" for the initial frame but before
the "enterFrame" of the next frame.
This commit is contained in:
Aaron Hill 2022-06-27 22:56:14 -05:00 committed by Mike Welsh
parent df07f610e7
commit 49d1a985ca
16 changed files with 198 additions and 80 deletions

View File

@ -1148,7 +1148,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if url.is_empty() { if url.is_empty() {
//Blank URL on movie loads = unload! //Blank URL on movie loads = unload!
if let Some(mut mc) = level.as_movie_clip() { if let Some(mut mc) = level.as_movie_clip() {
mc.replace_with_movie(self.context.gc_context, None) mc.replace_with_movie(&mut self.context, None)
} }
} else { } else {
let future = self.context.load_manager.load_movie_into_clip( let future = self.context.load_manager.load_movie_into_clip(
@ -1264,7 +1264,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if url.is_empty() { if url.is_empty() {
// Blank URL on movie loads = unload! // Blank URL on movie loads = unload!
if let Some(mut mc) = clip_target.as_movie_clip() { if let Some(mut mc) = clip_target.as_movie_clip() {
mc.replace_with_movie(self.context.gc_context, None) mc.replace_with_movie(&mut self.context, None)
} }
} else { } else {
let request = self.locals_into_request( let request = self.locals_into_request(

View File

@ -1352,7 +1352,7 @@ fn unload_movie<'gc>(
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
target.unload(&mut activation.context); target.unload(&mut activation.context);
target.replace_with_movie(activation.context.gc_context, None); target.replace_with_movie(&mut activation.context, None);
Ok(Value::Undefined) Ok(Value::Undefined)
} }

View File

@ -99,7 +99,7 @@ fn unload_clip<'gc>(
if let Some(target) = target { if let Some(target) = target {
target.unload(&mut activation.context); target.unload(&mut activation.context);
if let Some(mut mc) = target.as_movie_clip() { if let Some(mut mc) = target.as_movie_clip() {
mc.replace_with_movie(activation.context.gc_context, None); mc.replace_with_movie(&mut activation.context, None);
} }
return Ok(true.into()); return Ok(true.into());
} }

View File

@ -4,11 +4,11 @@ use crate::avm2::activation::Activation;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{stage_allocator, LoaderInfoObject, Object, TObject}; use crate::avm2::object::{stage_allocator, Object, TObject};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::ArrayObject; use crate::avm2::ArrayObject;
use crate::avm2::Error; use crate::avm2::Error;
use crate::display_object::{DisplayObject, HitTestOptions, TDisplayObject}; use crate::display_object::{HitTestOptions, TDisplayObject};
use crate::types::{Degrees, Percent}; use crate::types::{Degrees, Percent};
use crate::vminterface::Instantiator; use crate::vminterface::Instantiator;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
@ -574,26 +574,15 @@ pub fn hit_test_object<'gc>(
/// Implements `loaderInfo` getter /// Implements `loaderInfo` getter
pub fn loader_info<'gc>( pub fn loader_info<'gc>(
activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(dobj) = this.and_then(|this| this.as_display_object()) { if let Some(dobj) = this.and_then(|this| this.as_display_object()) {
if let Some(root) = dobj.avm2_root(&mut activation.context) { if let Some(loader_info) = dobj.loader_info() {
let movie = dobj.movie(); return Ok(loader_info.into());
if let Some(movie) = movie {
let obj = LoaderInfoObject::from_movie(activation, movie, root)?;
return Ok(obj.into());
}
}
if DisplayObject::ptr_eq(dobj, activation.context.stage.into()) {
return Ok(LoaderInfoObject::from_stage(activation)?.into());
} }
} }
Ok(Value::Undefined) Ok(Value::Undefined)
} }

View File

@ -16,6 +16,7 @@ use crate::avm2::vtable::{ClassBoundMethod, VTable};
use crate::avm2::Error; use crate::avm2::Error;
use crate::backend::audio::{SoundHandle, SoundInstanceHandle}; use crate::backend::audio::{SoundHandle, SoundInstanceHandle};
use crate::bitmap::bitmap_data::BitmapData; use crate::bitmap::bitmap_data::BitmapData;
use crate::context::UpdateContext;
use crate::display_object::DisplayObject; use crate::display_object::DisplayObject;
use crate::html::TextFormat; use crate::html::TextFormat;
use crate::string::AvmString; use crate::string::AvmString;
@ -1003,6 +1004,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
None None
} }
fn loader_stream_init(&self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
/// Unwrap this object's loader stream /// Unwrap this object's loader stream
fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> { fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> {
None None

View File

@ -4,9 +4,11 @@ use crate::avm2::activation::Activation;
use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Avm2;
use crate::avm2::Error; use crate::avm2::Error;
use crate::avm2::{Event, EventData};
use crate::context::UpdateContext;
use crate::display_object::DisplayObject; use crate::display_object::DisplayObject;
use crate::string::AvmString;
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell, MutationContext};
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
@ -24,6 +26,7 @@ pub fn loaderinfo_allocator<'gc>(
LoaderInfoObjectData { LoaderInfoObjectData {
base, base,
loaded_stream: None, loaded_stream: None,
init_fired: false,
}, },
)) ))
.into()) .into())
@ -60,6 +63,9 @@ pub struct LoaderInfoObjectData<'gc> {
/// The loaded stream that this gets it's info from. /// The loaded stream that this gets it's info from.
loaded_stream: Option<LoaderStream<'gc>>, loaded_stream: Option<LoaderStream<'gc>>,
/// Whether or not we've fired an 'init' event
init_fired: bool,
} }
impl<'gc> LoaderInfoObject<'gc> { impl<'gc> LoaderInfoObject<'gc> {
@ -78,6 +84,7 @@ impl<'gc> LoaderInfoObject<'gc> {
LoaderInfoObjectData { LoaderInfoObjectData {
base, base,
loaded_stream, loaded_stream,
init_fired: false,
}, },
)) ))
.into(); .into();
@ -98,6 +105,9 @@ impl<'gc> LoaderInfoObject<'gc> {
LoaderInfoObjectData { LoaderInfoObjectData {
base, base,
loaded_stream: Some(LoaderStream::Stage), loaded_stream: Some(LoaderStream::Stage),
// We never want to fire an "init" event for the special
// Stagee loaderInfo
init_fired: true,
}, },
)) ))
.into(); .into();
@ -122,14 +132,23 @@ impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
self.0.as_ptr() as *const ObjectPtr self.0.as_ptr() as *const ObjectPtr
} }
fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> { fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
if let Some(class) = self.instance_of_class_definition() { Ok(Value::Object((*self).into()))
Ok( }
AvmString::new_utf8(mc, format!("[object {}]", class.read().name().local_name()))
.into(), fn loader_stream_init(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
) if !self.0.read().init_fired {
} else { self.0.write(context.gc_context).init_fired = true;
Ok("[object Object]".into()) let mut init_evt = Event::new("init", EventData::Empty);
init_evt.set_bubbles(false);
init_evt.set_cancelable(false);
if let Err(e) = Avm2::dispatch_event(context, init_evt, (*self).into()) {
log::error!(
"Encountered AVM2 error when broadcasting `init` event: {}",
e
);
}
} }
} }

View File

@ -1165,6 +1165,16 @@ pub trait TDisplayObject<'gc>:
e e
); );
} }
self.on_exit_frame(context);
}
fn on_exit_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
if let Some(container) = self.as_container() {
for child in container.iter_render_list() {
child.on_exit_frame(context);
}
}
} }
fn render_self(&self, _context: &mut RenderContext<'_, 'gc>) {} fn render_self(&self, _context: &mut RenderContext<'_, 'gc>) {}
@ -1338,6 +1348,10 @@ pub trait TDisplayObject<'gc>:
self.parent().and_then(|p| p.movie()) self.parent().and_then(|p| p.movie())
} }
fn loader_info(&self) -> Option<Avm2Object<'gc>> {
None
}
fn instantiate(&self, gc_context: MutationContext<'gc, '_>) -> DisplayObject<'gc>; fn instantiate(&self, gc_context: MutationContext<'gc, '_>) -> DisplayObject<'gc>;
fn as_ptr(&self) -> *const DisplayObjectPtr; fn as_ptr(&self) -> *const DisplayObjectPtr;

View File

@ -2,6 +2,7 @@
use crate::avm1::{ use crate::avm1::{
Avm1, Object as Avm1Object, StageObject, TObject as Avm1TObject, Value as Avm1Value, Avm1, Object as Avm1Object, StageObject, TObject as Avm1TObject, Value as Avm1Value,
}; };
use crate::avm2::object::LoaderInfoObject;
use crate::avm2::Activation as Avm2Activation; use crate::avm2::Activation as Avm2Activation;
use crate::avm2::{ use crate::avm2::{
Avm2, ClassObject as Avm2ClassObject, Error as Avm2Error, Namespace as Avm2Namespace, Avm2, ClassObject as Avm2ClassObject, Error as Avm2Error, Namespace as Avm2Namespace,
@ -205,15 +206,20 @@ impl<'gc> MovieClip<'gc> {
} }
/// Construct a movie clip that represents an entire movie. /// Construct a movie clip that represents an entire movie.
pub fn from_movie(gc_context: MutationContext<'gc, '_>, movie: Arc<SwfMovie>) -> Self { pub fn from_movie(context: &mut UpdateContext<'_, 'gc, '_>, movie: Arc<SwfMovie>) -> Self {
let num_frames = movie.num_frames(); let num_frames = movie.num_frames();
let mc = MovieClip(GcCell::allocate( let mc = MovieClip(GcCell::allocate(
gc_context, context.gc_context,
MovieClipData { MovieClipData {
base: Default::default(), base: Default::default(),
static_data: Gc::allocate( static_data: Gc::allocate(
gc_context, context.gc_context,
MovieClipStatic::with_data(0, movie.into(), num_frames, gc_context), MovieClipStatic::with_data(
0,
movie.clone().into(),
num_frames,
context.gc_context,
),
), ),
tag_stream_pos: 0, tag_stream_pos: 0,
current_frame: 0, current_frame: 0,
@ -237,23 +243,64 @@ impl<'gc> MovieClip<'gc> {
drop_target: None, drop_target: None,
}, },
)); ));
mc.set_is_root(gc_context, true); mc.set_is_root(context.gc_context, true);
mc.set_loader_info(context, movie);
mc mc
} }
/// Replace the current MovieClip with a completely new SwfMovie. /// Replace the current MovieClipData with a completely new SwfMovie.
/// ///
/// Playback will start at position zero, any existing streamed audio will /// Playback will start at position zero, any existing streamed audio will
/// be terminated, and so on. Children and AVM data will be kept across the /// be terminated, and so on. Children and AVM data will NOT be kept across
/// load boundary. /// the load boundary.
///
/// If no movie is provided, then the movie clip will be replaced with an
/// empty movie of the same SWF version.
pub fn replace_with_movie( pub fn replace_with_movie(
&mut self, &mut self,
gc_context: MutationContext<'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
movie: Option<Arc<SwfMovie>>, movie: Option<Arc<SwfMovie>>,
) { ) {
self.0 let mut mc = self.0.write(context.gc_context);
.write(gc_context) let is_swf = movie.is_some();
.replace_with_movie(gc_context, movie) let movie = movie.unwrap_or_else(|| Arc::new(SwfMovie::empty(mc.movie().version())));
let total_frames = movie.num_frames();
mc.base.base.reset_for_movie_load();
mc.static_data = Gc::allocate(
context.gc_context,
MovieClipStatic::with_data(0, movie.clone().into(), total_frames, context.gc_context),
);
mc.tag_stream_pos = 0;
mc.flags = MovieClipFlags::PLAYING;
mc.base.base.set_is_root(is_swf);
mc.current_frame = 0;
mc.audio_stream = None;
mc.container = ChildContainer::new();
drop(mc);
self.set_loader_info(context, movie);
}
fn set_loader_info(&self, context: &mut UpdateContext<'_, 'gc, '_>, movie: Arc<SwfMovie>) {
if movie.avm_type() == AvmType::Avm2 {
let gc_context = context.gc_context;
let mc = self.0.write(gc_context);
if mc.base.base.is_root() {
let mut activation = Avm2Activation::from_nothing(context.reborrow());
match LoaderInfoObject::from_movie(&mut activation, movie, (*self).into()) {
Ok(loader_info) => {
*mc.static_data.loader_info.write(gc_context) = Some(loader_info);
}
Err(e) => {
log::error!(
"Error contructing LoaderInfoObject for movie {:?}: {:?}",
mc,
e
);
}
}
}
}
} }
pub fn preload(self, context: &mut UpdateContext<'_, 'gc, '_>) { pub fn preload(self, context: &mut UpdateContext<'_, 'gc, '_>) {
@ -1771,9 +1818,8 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
// we can't fire the events until we run our first frame, so we // we can't fire the events until we run our first frame, so we
// have to actually check if we've just built the root and act // have to actually check if we've just built the root and act
// like it just got added to the timeline. // like it just got added to the timeline.
let root = self.avm2_root(context);
let self_dobj: DisplayObject<'gc> = (*self).into(); let self_dobj: DisplayObject<'gc> = (*self).into();
if DisplayObject::option_ptr_eq(Some(self_dobj), root) { if self_dobj.is_root() {
dispatch_added_event_only(self_dobj, context); dispatch_added_event_only(self_dobj, context);
dispatch_added_to_stage_event_only(self_dobj, context); dispatch_added_to_stage_event_only(self_dobj, context);
} }
@ -1783,7 +1829,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
fn run_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) { fn run_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
// Run my load/enterFrame clip event. // Run my load/enterFrame clip event.
let is_load_frame = !self.0.read().initialized(); let is_load_frame = !self.0.read().flags.contains(MovieClipFlags::INITIALIZED);
if is_load_frame { if is_load_frame {
self.event_dispatch(context, ClipEvent::Load); self.event_dispatch(context, ClipEvent::Load);
self.0.write(context.gc_context).set_initialized(true); self.0.write(context.gc_context).set_initialized(true);
@ -1866,6 +1912,23 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
} }
} }
fn on_exit_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
// Attempt to fire an "init" event on our `LoaderInfo`.
// This fires after we've exited our first frame, but before
// but before we enter a new frame. `loader_strean_init`
// keeps track if an "init" event has already been fired,
// so this becomes a no-op after the event has been fired.
if self.0.read().initialized() {
if let Some(loader_info) = self.loader_info() {
loader_info.loader_stream_init(context);
}
}
for child in self.iter_render_list() {
child.on_exit_frame(context);
}
}
fn render_self(&self, context: &mut RenderContext<'_, 'gc>) { fn render_self(&self, context: &mut RenderContext<'_, 'gc>) {
self.0.read().drawing.render(context); self.0.read().drawing.render(context);
self.render_children(context); self.render_children(context);
@ -2017,6 +2080,10 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
self.set_removed(context.gc_context, true); self.set_removed(context.gc_context, true);
} }
fn loader_info(&self) -> Option<Avm2Object<'gc>> {
*self.0.read().static_data.loader_info.read()
}
fn allow_as_mask(&self) -> bool { fn allow_as_mask(&self) -> bool {
!self.is_empty() !self.is_empty()
} }
@ -2225,36 +2292,6 @@ impl<'gc> TInteractiveObject<'gc> for MovieClip<'gc> {
} }
impl<'gc> MovieClipData<'gc> { impl<'gc> MovieClipData<'gc> {
/// Replace the current MovieClipData with a completely new SwfMovie.
///
/// Playback will start at position zero, any existing streamed audio will
/// be terminated, and so on. Children and AVM data will NOT be kept across
/// the load boundary.
///
/// If no movie is provided, then the movie clip will be replaced with an
/// empty movie of the same SWF version.
pub fn replace_with_movie(
&mut self,
gc_context: MutationContext<'gc, '_>,
movie: Option<Arc<SwfMovie>>,
) {
let is_swf = movie.is_some();
let movie = movie.unwrap_or_else(|| Arc::new(SwfMovie::empty(self.movie().version())));
let total_frames = movie.num_frames();
self.base.base.reset_for_movie_load();
self.static_data = Gc::allocate(
gc_context,
MovieClipStatic::with_data(0, movie.into(), total_frames, gc_context),
);
self.tag_stream_pos = 0;
self.flags = MovieClipFlags::PLAYING;
self.base.base.set_is_root(is_swf);
self.current_frame = 0;
self.audio_stream = None;
self.container = ChildContainer::new();
}
fn id(&self) -> CharacterId { fn id(&self) -> CharacterId {
self.static_data.id self.static_data.id
} }
@ -3283,6 +3320,10 @@ struct MovieClipStatic<'gc> {
/// The last known symbol name under which this movie clip was exported. /// The last known symbol name under which this movie clip was exported.
/// Used for looking up constructors registered with `Object.registerClass`. /// Used for looking up constructors registered with `Object.registerClass`.
exported_name: GcCell<'gc, Option<AvmString<'gc>>>, exported_name: GcCell<'gc, Option<AvmString<'gc>>>,
/// Only set if this MovieClip is the root movie in an SWF
/// (either the root SWF initially loaded by the player,
/// or an SWF dynamically loaded by `Loader`)
loader_info: GcCell<'gc, Option<Avm2Object<'gc>>>,
} }
impl<'gc> MovieClipStatic<'gc> { impl<'gc> MovieClipStatic<'gc> {
@ -3305,6 +3346,7 @@ impl<'gc> MovieClipStatic<'gc> {
audio_stream_info: None, audio_stream_info: None,
audio_stream_handle: None, audio_stream_handle: None,
exported_name: GcCell::allocate(gc_context, None), exported_name: GcCell::allocate(gc_context, None),
loader_info: GcCell::allocate(gc_context, None),
} }
} }
} }

View File

@ -1,6 +1,7 @@
//! Root stage impl //! Root stage impl
use crate::avm1::Object as Avm1Object; use crate::avm1::Object as Avm1Object;
use crate::avm2::object::LoaderInfoObject;
use crate::avm2::{ use crate::avm2::{
Activation as Avm2Activation, Event as Avm2Event, EventData as Avm2EventData, Activation as Avm2Activation, Event as Avm2Event, EventData as Avm2EventData,
Object as Avm2Object, ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, Object as Avm2Object, ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject,
@ -105,6 +106,9 @@ pub struct StageData<'gc> {
/// The AVM2 view of this stage object. /// The AVM2 view of this stage object.
avm2_object: Avm2Object<'gc>, avm2_object: Avm2Object<'gc>,
/// The AVM2 'LoaderInfo' object for this stage object
loader_info: Avm2Object<'gc>,
} }
impl<'gc> Stage<'gc> { impl<'gc> Stage<'gc> {
@ -138,6 +142,7 @@ impl<'gc> Stage<'gc> {
window_mode: Default::default(), window_mode: Default::default(),
show_menu: true, show_menu: true,
avm2_object: Avm2ScriptObject::custom_object(gc_context, None, None), avm2_object: Avm2ScriptObject::custom_object(gc_context, None, None),
loader_info: Avm2ScriptObject::custom_object(gc_context, None, None),
}, },
)); ));
stage.set_is_root(gc_context, true); stage.set_is_root(gc_context, true);
@ -671,7 +676,12 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
match avm2_stage { match avm2_stage {
Ok(avm2_stage) => { Ok(avm2_stage) => {
self.0.write(activation.context.gc_context).avm2_object = avm2_stage.into() let mut write = self.0.write(activation.context.gc_context);
write.avm2_object = avm2_stage.into();
match LoaderInfoObject::from_stage(&mut activation) {
Ok(loader_info) => write.loader_info = loader_info,
Err(e) => log::error!("Unable to set AVM2 Stage loaderInfo: {}", e),
}
} }
Err(e) => log::error!("Unable to construct AVM2 Stage: {}", e), Err(e) => log::error!("Unable to construct AVM2 Stage: {}", e),
} }
@ -729,6 +739,10 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
fn object2(&self) -> Avm2Value<'gc> { fn object2(&self) -> Avm2Value<'gc> {
self.0.read().avm2_object.into() self.0.read().avm2_object.into()
} }
fn loader_info(&self) -> Option<Avm2Object<'gc>> {
Some(self.0.read().loader_info)
}
} }
impl<'gc> TDisplayObjectContainer<'gc> for Stage<'gc> { impl<'gc> TDisplayObjectContainer<'gc> for Stage<'gc> {

View File

@ -457,7 +457,7 @@ impl<'gc> Loader<'gc> {
if let Some(mut mc) = clip.as_movie_clip() { if let Some(mut mc) = clip.as_movie_clip() {
mc.unload(uc); mc.unload(uc);
mc.replace_with_movie(uc.gc_context, None); mc.replace_with_movie(uc, None);
} }
Loader::movie_loader_start(handle, uc) Loader::movie_loader_start(handle, uc)
@ -499,7 +499,7 @@ impl<'gc> Loader<'gc> {
.set_avm2_domain(domain); .set_avm2_domain(domain);
if let Some(mut mc) = clip.as_movie_clip() { if let Some(mut mc) = clip.as_movie_clip() {
mc.replace_with_movie(uc.gc_context, Some(movie)); mc.replace_with_movie(uc, Some(movie));
mc.post_instantiation(uc, None, Instantiator::Movie, false); mc.post_instantiation(uc, None, Instantiator::Movie, false);
mc.preload(uc); mc.preload(uc);
} }

View File

@ -294,8 +294,7 @@ impl Player {
.set_avm2_domain(domain); .set_avm2_domain(domain);
context.ui.set_mouse_visible(true); context.ui.set_mouse_visible(true);
let root: DisplayObject = let root: DisplayObject = MovieClip::from_movie(context, context.swf.clone()).into();
MovieClip::from_movie(context.gc_context, context.swf.clone()).into();
root.set_depth(context.gc_context, 0); root.set_depth(context.gc_context, 0);
let flashvars = if !context.swf.parameters().is_empty() { let flashvars = if !context.swf.parameters().is_empty() {
@ -1922,7 +1921,7 @@ impl PlayerBuilder {
let mut player_lock = player.lock().unwrap(); let mut player_lock = player.lock().unwrap();
player_lock.mutate_with_update_context(|context| { player_lock.mutate_with_update_context(|context| {
// Instantiate an empty root before the main movie loads. // Instantiate an empty root before the main movie loads.
let fake_root = MovieClip::from_movie(context.gc_context, fake_movie); let fake_root = MovieClip::from_movie(context, fake_movie);
fake_root.post_instantiation(context, None, Instantiator::Movie, false); fake_root.post_instantiation(context, None, Instantiator::Movie, false);
context.stage.replace_at_depth(context, fake_root.into(), 0); context.stage.replace_at_depth(context, fake_root.into(), 0);
Avm2::load_player_globals(context).expect("Unable to load AVM2 globals"); Avm2::load_player_globals(context).expect("Unable to load AVM2 globals");

View File

@ -332,6 +332,7 @@ swf_tests! {
(as3_lazyinit, "avm2/lazyinit", 1), (as3_lazyinit, "avm2/lazyinit", 1),
(as3_lessequals, "avm2/lessequals", 1), (as3_lessequals, "avm2/lessequals", 1),
(as3_lessthan, "avm2/lessthan", 1), (as3_lessthan, "avm2/lessthan", 1),
(as3_loaderinfo_events, "avm2/loaderinfo_events", 2),
(as3_loaderinfo_properties, "avm2/loaderinfo_properties", 2), (as3_loaderinfo_properties, "avm2/loaderinfo_properties", 2),
(as3_loaderinfo_quine, "avm2/loaderinfo_quine", 2), (as3_loaderinfo_quine, "avm2/loaderinfo_quine", 2),
(as3_lshift, "avm2/lshift", 1), (as3_lshift, "avm2/lshift", 1),

View File

@ -0,0 +1,31 @@
 package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
loaderInfo.addEventListener("init", function() {
trace("Called init!");
});
this.addEventListener("addedToStage", function() {
trace("Called addedToStage");
});
this.addEventListener("added", function() {
trace("Called added");
});
var clip = this;
this.addEventListener("enterFrame", function() {
trace("Called enterFrame");
});
this.addEventListener("exitFrame", function() {
trace("Called exitFrame");
});
stage.loaderInfo.addEventListener("init", function() {
trace("ERROR: Stage loaderInfo should not fire 'init'");
});
trace("Called constructor");
}
}
}

View File

@ -0,0 +1,6 @@
Called constructor
Called added
Called addedToStage
Called exitFrame
Called init!
Called enterFrame

Binary file not shown.

Binary file not shown.