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:
parent
df07f610e7
commit
49d1a985ca
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
Called constructor
|
||||||
|
Called added
|
||||||
|
Called addedToStage
|
||||||
|
Called exitFrame
|
||||||
|
Called init!
|
||||||
|
Called enterFrame
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue