avm2: Partially implement Loader.load
This PR implements the `Loader.load` method, as well as the associated `LoaderInfo` properties and events. We can now load in an external AVM2 SWf: it will be added as a child of `Loader` object, and will render properly to the screen. Limitations: * The only supported `URLRequest` property is `url` * `LoaderContext` is not supported at all - we always use the default behavior * Only `Loader.load` is implemented - we do not yet support unloading. * We fire a plain 'Event' for the 'progress' event, instead of using the (not yet implemented) 'ProgressEvent' class The main changes in this PR are: * The AVM2 `Loader` class now has an associated display object, `LoaderDisplay`. This is basically a stub, and just renders its single child (if it exists). * `LoaderStream::Stage` is renamed to `LoaderStream::NotYetLoaded`. This is used for both the `Stage` and an 'uninitialized' `Loader.contentLoaderInfo`. In both cases, certain properties throw errors, while others return actual values. * The rust `Loader` manager now handles both AVM1 and AVM2 movie loads.
This commit is contained in:
parent
60b03bdcf2
commit
86e6983943
|
@ -99,7 +99,7 @@ jobs:
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: swf_images
|
name: swf_images
|
||||||
path: tests/**/actual.png
|
path: tests*/**/actual.png
|
||||||
|
|
||||||
check-required:
|
check-required:
|
||||||
needs: changes
|
needs: changes
|
||||||
|
|
|
@ -1153,7 +1153,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(&mut self.context, None)
|
mc.replace_with_movie(&mut self.context, None, None)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let future = self.context.load_manager.load_movie_into_clip(
|
let future = self.context.load_manager.load_movie_into_clip(
|
||||||
|
@ -1269,7 +1269,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(&mut self.context, None)
|
mc.replace_with_movie(&mut self.context, None, None)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let request = self.locals_into_request(
|
let request = self.locals_into_request(
|
||||||
|
@ -1293,7 +1293,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(&mut self.context, None)
|
mc.replace_with_movie(&mut self.context, None, None)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let future = self.context.load_manager.load_movie_into_clip(
|
let future = self.context.load_manager.load_movie_into_clip(
|
||||||
|
|
|
@ -1361,7 +1361,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(&mut activation.context, None);
|
target.replace_with_movie(&mut activation.context, None, None);
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{ArrayObject, Object, Value};
|
use crate::avm1::{ArrayObject, Object, Value};
|
||||||
use crate::backend::navigator::Request;
|
use crate::backend::navigator::Request;
|
||||||
use crate::display_object::{TDisplayObject, TDisplayObjectContainer};
|
use crate::display_object::{TDisplayObject, TDisplayObjectContainer};
|
||||||
|
use crate::loader::MovieLoaderEventHandler;
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
|
||||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||||
|
@ -63,7 +64,7 @@ fn load_clip<'gc>(
|
||||||
target,
|
target,
|
||||||
Request::get(url.to_utf8_lossy().into_owned()),
|
Request::get(url.to_utf8_lossy().into_owned()),
|
||||||
None,
|
None,
|
||||||
Some(this),
|
Some(MovieLoaderEventHandler::Avm1Broadcast(this)),
|
||||||
);
|
);
|
||||||
activation.context.navigator.spawn_future(future);
|
activation.context.navigator.spawn_future(future);
|
||||||
|
|
||||||
|
@ -99,7 +100,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(&mut activation.context, None);
|
mc.replace_with_movie(&mut activation.context, None, None);
|
||||||
}
|
}
|
||||||
return Ok(true.into());
|
return Ok(true.into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ pub struct SystemClasses<'gc> {
|
||||||
pub qname: ClassObject<'gc>,
|
pub qname: ClassObject<'gc>,
|
||||||
pub sharedobject: ClassObject<'gc>,
|
pub sharedobject: ClassObject<'gc>,
|
||||||
pub mouseevent: ClassObject<'gc>,
|
pub mouseevent: ClassObject<'gc>,
|
||||||
|
pub progressevent: ClassObject<'gc>,
|
||||||
pub textevent: ClassObject<'gc>,
|
pub textevent: ClassObject<'gc>,
|
||||||
pub errorevent: ClassObject<'gc>,
|
pub errorevent: ClassObject<'gc>,
|
||||||
pub ioerrorevent: ClassObject<'gc>,
|
pub ioerrorevent: ClassObject<'gc>,
|
||||||
|
@ -96,6 +97,8 @@ pub struct SystemClasses<'gc> {
|
||||||
pub transform: ClassObject<'gc>,
|
pub transform: ClassObject<'gc>,
|
||||||
pub colortransform: ClassObject<'gc>,
|
pub colortransform: ClassObject<'gc>,
|
||||||
pub matrix: ClassObject<'gc>,
|
pub matrix: ClassObject<'gc>,
|
||||||
|
pub illegaloperationerror: ClassObject<'gc>,
|
||||||
|
pub eventdispatcher: ClassObject<'gc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> SystemClasses<'gc> {
|
impl<'gc> SystemClasses<'gc> {
|
||||||
|
@ -154,6 +157,7 @@ impl<'gc> SystemClasses<'gc> {
|
||||||
qname: object,
|
qname: object,
|
||||||
sharedobject: object,
|
sharedobject: object,
|
||||||
mouseevent: object,
|
mouseevent: object,
|
||||||
|
progressevent: object,
|
||||||
textevent: object,
|
textevent: object,
|
||||||
errorevent: object,
|
errorevent: object,
|
||||||
ioerrorevent: object,
|
ioerrorevent: object,
|
||||||
|
@ -161,6 +165,8 @@ impl<'gc> SystemClasses<'gc> {
|
||||||
transform: object,
|
transform: object,
|
||||||
colortransform: object,
|
colortransform: object,
|
||||||
matrix: object,
|
matrix: object,
|
||||||
|
illegaloperationerror: object,
|
||||||
|
eventdispatcher: object,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,11 +460,12 @@ pub fn load_player_globals<'gc>(
|
||||||
flash::events::ieventdispatcher::create_interface(mc),
|
flash::events::ieventdispatcher::create_interface(mc),
|
||||||
script,
|
script,
|
||||||
)?;
|
)?;
|
||||||
class(
|
avm2_system_class!(
|
||||||
|
eventdispatcher,
|
||||||
activation,
|
activation,
|
||||||
flash::events::eventdispatcher::create_class(mc),
|
flash::events::eventdispatcher::create_class(mc),
|
||||||
script,
|
script
|
||||||
)?;
|
);
|
||||||
|
|
||||||
// package `flash.utils`
|
// package `flash.utils`
|
||||||
avm2_system_class!(
|
avm2_system_class!(
|
||||||
|
@ -567,7 +574,6 @@ pub fn load_player_globals<'gc>(
|
||||||
flash::display::graphics::create_class(mc),
|
flash::display::graphics::create_class(mc),
|
||||||
script
|
script
|
||||||
);
|
);
|
||||||
class(activation, flash::display::loader::create_class(mc), script)?;
|
|
||||||
avm2_system_class!(
|
avm2_system_class!(
|
||||||
loaderinfo,
|
loaderinfo,
|
||||||
activation,
|
activation,
|
||||||
|
@ -734,9 +740,15 @@ fn load_playerglobal<'gc>(
|
||||||
script,
|
script,
|
||||||
[
|
[
|
||||||
("flash.display", "Scene", scene),
|
("flash.display", "Scene", scene),
|
||||||
|
(
|
||||||
|
"flash.errors",
|
||||||
|
"IllegalOperationError",
|
||||||
|
illegaloperationerror
|
||||||
|
),
|
||||||
("flash.events", "Event", event),
|
("flash.events", "Event", event),
|
||||||
("flash.events", "TextEvent", textevent),
|
("flash.events", "TextEvent", textevent),
|
||||||
("flash.events", "ErrorEvent", errorevent),
|
("flash.events", "ErrorEvent", errorevent),
|
||||||
|
("flash.events", "ProgressEvent", progressevent),
|
||||||
("flash.events", "SecurityErrorEvent", securityerrorevent),
|
("flash.events", "SecurityErrorEvent", securityerrorevent),
|
||||||
("flash.events", "IOErrorEvent", ioerrorevent),
|
("flash.events", "IOErrorEvent", ioerrorevent),
|
||||||
("flash.events", "MouseEvent", mouseevent),
|
("flash.events", "MouseEvent", mouseevent),
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
// This is a stub - the actual class is defined in `displayobjectcontainer.rs`
|
// This is a stub - the actual class is defined in `displayobjectcontainer.rs`
|
||||||
package flash.display {
|
package flash.display {
|
||||||
public class DisplayObjectContainer {
|
public class DisplayObjectContainer extends InteractiveObject {
|
||||||
}
|
public native function addChild(child:DisplayObject):void;
|
||||||
|
public native function addChildAt(child:DisplayObject, index:int):void;
|
||||||
|
public native function removeChild(child:DisplayObject, index:int):void
|
||||||
|
public native function removeChildAt(index:int):void
|
||||||
|
public native function setChildIndex(child:DisplayObject, index:int):void
|
||||||
|
public native function getChildAt(index:int):DisplayObject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package flash.display {
|
||||||
|
public class Loader extends DisplayObjectContainer {
|
||||||
|
import flash.display.LoaderInfo;
|
||||||
|
import flash.display.DisplayObject;
|
||||||
|
import flash.errors.IllegalOperationError;
|
||||||
|
import flash.system.LoaderContext;
|
||||||
|
import flash.net.URLRequest;
|
||||||
|
|
||||||
|
private var _contentLoaderInfo: LoaderInfo;
|
||||||
|
|
||||||
|
public function get contentLoaderInfo():LoaderInfo {
|
||||||
|
return this._contentLoaderInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private native function init();
|
||||||
|
|
||||||
|
public function Loader() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get content():DisplayObject {
|
||||||
|
if (this.numChildren == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.getChildAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public native function load(request: URLRequest, context: LoaderContext = null):void;
|
||||||
|
|
||||||
|
override public function addChild(child:DisplayObject):void {
|
||||||
|
throw new IllegalOperationError("Error #2069: The Loader class does not implement this method.", 2069);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function addChildAt(child:DisplayObject, index:int):void {
|
||||||
|
throw new IllegalOperationError("Error #2069: The Loader class does not implement this method.", 2069);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function removeChild(child:DisplayObject, index:int):void {
|
||||||
|
throw new IllegalOperationError("Error #2069: The Loader class does not implement this method.", 2069);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function removeChildAt(index:int):void {
|
||||||
|
throw new IllegalOperationError("Error #2069: The Loader class does not implement this method.", 2069);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function setChildIndex(child:DisplayObject, index:int):void {
|
||||||
|
throw new IllegalOperationError("Error #2069: The Loader class does not implement this method.", 2069);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package flash.display {
|
||||||
|
public class LoaderInfo {}
|
||||||
|
}
|
|
@ -114,7 +114,7 @@ fn remove_child_from_displaylist<'gc>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the `child` to `parent`'s display list.
|
/// Add the `child` to `parent`'s display list.
|
||||||
fn add_child_to_displaylist<'gc>(
|
pub(super) fn add_child_to_displaylist<'gc>(
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
parent: DisplayObject<'gc>,
|
parent: DisplayObject<'gc>,
|
||||||
child: DisplayObject<'gc>,
|
child: DisplayObject<'gc>,
|
||||||
|
|
|
@ -1,71 +1,92 @@
|
||||||
//! `flash.display.Loader` builtin/prototype
|
//! `flash.display.Loader` builtin/prototype
|
||||||
|
|
||||||
use crate::avm2::activation::Activation;
|
use crate::avm2::activation::Activation;
|
||||||
use crate::avm2::class::{Class, ClassAttributes};
|
use crate::avm2::object::LoaderInfoObject;
|
||||||
use crate::avm2::method::{Method, NativeMethodImpl};
|
use crate::avm2::object::TObject;
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
|
use crate::avm2::Multiname;
|
||||||
use crate::avm2::Namespace;
|
use crate::avm2::Namespace;
|
||||||
use crate::avm2::QName;
|
use crate::avm2::QName;
|
||||||
use crate::avm2::{Error, Object};
|
use crate::avm2::{Error, Object};
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use crate::backend::navigator::Request;
|
||||||
|
use crate::display_object::LoaderDisplay;
|
||||||
|
use crate::display_object::MovieClip;
|
||||||
|
use crate::loader::MovieLoaderEventHandler;
|
||||||
|
use crate::tag_utils::SwfMovie;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Implements `flash.display.Loader`'s class constructor.
|
pub fn init<'gc>(
|
||||||
pub fn class_init<'gc>(
|
|
||||||
_activation: &mut Activation<'_, 'gc, '_>,
|
|
||||||
_this: Option<Object<'gc>>,
|
|
||||||
_args: &[Value<'gc>],
|
|
||||||
) -> Result<Value<'gc>, Error> {
|
|
||||||
Ok(Value::Undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implements `flash.display.Loader`'s instance constructor.
|
|
||||||
pub fn instance_init<'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(this) = this {
|
if let Some(mut this) = this {
|
||||||
activation.super_init(this, &[])?;
|
if this.as_display_object().is_none() {
|
||||||
|
let new_do = LoaderDisplay::new_with_avm2(activation.context.gc_context, this);
|
||||||
|
this.init_display_object(activation.context.gc_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),
|
||||||
|
)?;
|
||||||
|
this.set_property(
|
||||||
|
&QName::new(Namespace::private(""), "_contentLoaderInfo").into(),
|
||||||
|
loader_info.into(),
|
||||||
|
activation,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this is entirely stubbed, just to get addEventListener to not crash
|
pub fn load<'gc>(
|
||||||
fn content_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> {
|
||||||
log::warn!("Loader::contentLoaderInfo is a stub");
|
if let Some(this) = this {
|
||||||
Ok(this.unwrap().into())
|
let url_request = args[0].as_object().unwrap();
|
||||||
}
|
|
||||||
|
if let Some(context) = args.get(1) {
|
||||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
if !matches!(context, Value::Null) {
|
||||||
let class = Class::new(
|
log::warn!(
|
||||||
QName::new(Namespace::package("flash.display"), "Loader"),
|
"Loader.load: 'context' argument is not yet implemented: {:?}",
|
||||||
Some(
|
context
|
||||||
QName::new(
|
);
|
||||||
Namespace::package("flash.display"),
|
}
|
||||||
"DisplayObjectContainer",
|
}
|
||||||
)
|
let url = url_request
|
||||||
.into(),
|
.get_property(&Multiname::public("url"), activation)?
|
||||||
),
|
.coerce_to_string(activation)?;
|
||||||
Method::from_builtin(instance_init, "<Loader instance initializer>", mc),
|
|
||||||
Method::from_builtin(class_init, "<Loader class initializer>", mc),
|
// This is a dummy MovieClip, which will get overwritten in `Loader`
|
||||||
mc,
|
let content = MovieClip::new(
|
||||||
);
|
Arc::new(SwfMovie::empty(activation.context.swf.version())),
|
||||||
|
activation.context.gc_context,
|
||||||
let mut write = class.write(mc);
|
);
|
||||||
|
|
||||||
write.set_attributes(ClassAttributes::SEALED);
|
let loader_info = this
|
||||||
|
.get_property(
|
||||||
const PUBLIC_INSTANCE_PROPERTIES: &[(
|
&QName::new(Namespace::private(""), "_contentLoaderInfo").into(),
|
||||||
&str,
|
activation,
|
||||||
Option<NativeMethodImpl>,
|
)?
|
||||||
Option<NativeMethodImpl>,
|
.as_object()
|
||||||
)] = &[("contentLoaderInfo", Some(content_loader_info), None)];
|
.unwrap();
|
||||||
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
|
||||||
|
let future = activation.context.load_manager.load_movie_into_clip(
|
||||||
class
|
activation.context.player.clone(),
|
||||||
|
content.into(),
|
||||||
|
// FIXME - set options from the `URLRequest`
|
||||||
|
Request::get(url.to_string()),
|
||||||
|
Some(url.to_string()),
|
||||||
|
Some(MovieLoaderEventHandler::Avm2LoaderInfo(loader_info)),
|
||||||
|
);
|
||||||
|
activation.context.navigator.spawn_future(future);
|
||||||
|
}
|
||||||
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,13 @@ use crate::avm2::QName;
|
||||||
use crate::avm2::{AvmString, Error};
|
use crate::avm2::{AvmString, Error};
|
||||||
use crate::display_object::TDisplayObject;
|
use crate::display_object::TDisplayObject;
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
|
use std::sync::Arc;
|
||||||
use swf::{write_swf, Compression};
|
use swf::{write_swf, Compression};
|
||||||
|
|
||||||
|
// FIXME - Throw an actual 'Error' with the proper code
|
||||||
|
const INSUFFICIENT: &str =
|
||||||
|
"Error #2099: The loading object is not sufficiently loaded to provide this information.";
|
||||||
|
|
||||||
/// Implements `flash.display.LoaderInfo`'s instance constructor.
|
/// Implements `flash.display.LoaderInfo`'s instance constructor.
|
||||||
pub fn instance_init<'gc>(
|
pub fn instance_init<'gc>(
|
||||||
_activation: &mut Activation<'_, 'gc, '_>,
|
_activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
@ -51,10 +56,13 @@ pub fn action_script_version<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => {
|
LoaderStream::NotYetLoaded(_) => {
|
||||||
return Err("Error: The stage's loader info does not have an AS version".into())
|
return Err(INSUFFICIENT.into());
|
||||||
}
|
}
|
||||||
LoaderStream::Swf(movie, _) => {
|
LoaderStream::Swf(movie, _) => {
|
||||||
let version = if movie.is_action_script_3() { 3 } else { 2 };
|
let version = if movie.is_action_script_3() { 3 } else { 2 };
|
||||||
|
@ -74,9 +82,12 @@ pub fn application_domain<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => {
|
LoaderStream::NotYetLoaded(_) => {
|
||||||
return Ok(DomainObject::from_domain(activation, activation.domain())?.into());
|
return Ok(DomainObject::from_domain(activation, activation.domain())?.into());
|
||||||
}
|
}
|
||||||
LoaderStream::Swf(movie, _) => {
|
LoaderStream::Swf(movie, _) => {
|
||||||
|
@ -99,14 +110,17 @@ pub fn application_domain<'gc>(
|
||||||
/// TODO: This is also the getter for `bytesLoaded` as we don't yet support
|
/// TODO: This is also the getter for `bytesLoaded` as we don't yet support
|
||||||
/// streaming loads yet. When we do, we'll need another property for this.
|
/// streaming loads yet. When we do, we'll need another property for this.
|
||||||
pub fn bytes_total<'gc>(
|
pub fn bytes_total<'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(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => return Ok(activation.context.swf.compressed_len().into()),
|
LoaderStream::NotYetLoaded(swf) => return Ok(swf.compressed_len().into()),
|
||||||
LoaderStream::Swf(movie, _) => {
|
LoaderStream::Swf(movie, _) => {
|
||||||
return Ok(movie.compressed_len().into());
|
return Ok(movie.compressed_len().into());
|
||||||
}
|
}
|
||||||
|
@ -124,9 +138,17 @@ pub fn content<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => return Ok(activation.context.stage.root_clip().object2()),
|
LoaderStream::NotYetLoaded(swf) => {
|
||||||
|
if Arc::ptr_eq(swf, activation.context.swf) {
|
||||||
|
return Ok(activation.context.stage.root_clip().object2());
|
||||||
|
}
|
||||||
|
return Ok(Value::Null);
|
||||||
|
}
|
||||||
LoaderStream::Swf(_, root) => {
|
LoaderStream::Swf(_, root) => {
|
||||||
return Ok(root.object2());
|
return Ok(root.object2());
|
||||||
}
|
}
|
||||||
|
@ -144,9 +166,12 @@ pub fn content_type<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => return Ok(Value::Null),
|
LoaderStream::NotYetLoaded(_) => return Ok(Value::Null),
|
||||||
LoaderStream::Swf(_, _) => {
|
LoaderStream::Swf(_, _) => {
|
||||||
return Ok("application/x-shockwave-flash".into());
|
return Ok("application/x-shockwave-flash".into());
|
||||||
}
|
}
|
||||||
|
@ -164,9 +189,12 @@ pub fn frame_rate<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => {
|
LoaderStream::NotYetLoaded(_) => {
|
||||||
return Err("Error: The stage's loader info does not have a frame rate".into())
|
return Err("Error: The stage's loader info does not have a frame rate".into())
|
||||||
}
|
}
|
||||||
LoaderStream::Swf(root, _) => {
|
LoaderStream::Swf(root, _) => {
|
||||||
|
@ -186,9 +214,12 @@ pub fn height<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => {
|
LoaderStream::NotYetLoaded(_) => {
|
||||||
return Err("Error: The stage's loader info does not have a height".into())
|
return Err("Error: The stage's loader info does not have a height".into())
|
||||||
}
|
}
|
||||||
LoaderStream::Swf(root, _) => {
|
LoaderStream::Swf(root, _) => {
|
||||||
|
@ -217,9 +248,12 @@ pub fn swf_version<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => {
|
LoaderStream::NotYetLoaded(_) => {
|
||||||
return Err("Error: The stage's loader info does not have a SWF version".into())
|
return Err("Error: The stage's loader info does not have a SWF version".into())
|
||||||
}
|
}
|
||||||
LoaderStream::Swf(root, _) => {
|
LoaderStream::Swf(root, _) => {
|
||||||
|
@ -239,14 +273,19 @@ pub fn url<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
let root = match &*loader_stream {
|
let root = match &*loader_stream {
|
||||||
LoaderStream::Stage => activation.context.swf,
|
LoaderStream::NotYetLoaded(swf) => swf,
|
||||||
LoaderStream::Swf(root, _) => root,
|
LoaderStream::Swf(root, _) => root,
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = root.url().unwrap_or("");
|
let url = root.url().map_or(Value::Null, |url| {
|
||||||
return Ok(AvmString::new_utf8(activation.context.gc_context, url).into());
|
AvmString::new_utf8(activation.context.gc_context, url).into()
|
||||||
|
});
|
||||||
|
return Ok(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,9 +299,12 @@ pub fn width<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
match &*loader_stream {
|
match &*loader_stream {
|
||||||
LoaderStream::Stage => {
|
LoaderStream::NotYetLoaded(_) => {
|
||||||
return Err("Error: The stage's loader info does not have a width".into())
|
return Err("Error: The stage's loader info does not have a width".into())
|
||||||
}
|
}
|
||||||
LoaderStream::Swf(root, _) => {
|
LoaderStream::Swf(root, _) => {
|
||||||
|
@ -282,12 +324,19 @@ pub fn bytes<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
let root = match &*loader_stream {
|
let root = match &*loader_stream {
|
||||||
LoaderStream::Stage => activation.context.swf,
|
LoaderStream::NotYetLoaded(swf) => swf,
|
||||||
LoaderStream::Swf(root, _) => root,
|
LoaderStream::Swf(root, _) => root,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if root.data().is_empty() {
|
||||||
|
return Ok(Value::Null);
|
||||||
|
}
|
||||||
|
|
||||||
let ba_class = activation.context.avm2.classes().bytearray;
|
let ba_class = activation.context.avm2.classes().bytearray;
|
||||||
|
|
||||||
let ba = ba_class.construct(activation, &[])?;
|
let ba = ba_class.construct(activation, &[])?;
|
||||||
|
@ -325,6 +374,19 @@ pub fn bytes<'gc>(
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `loader` getter
|
||||||
|
pub fn loader<'gc>(
|
||||||
|
_activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
if let Some(loader_info) = this.as_ref().and_then(|this| this.as_loader_info_object()) {
|
||||||
|
Ok(loader_info.loader().map_or(Value::Null, |v| v.into()))
|
||||||
|
} else {
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `loaderURL` getter
|
/// `loaderURL` getter
|
||||||
pub fn loader_url<'gc>(
|
pub fn loader_url<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
@ -332,9 +394,12 @@ pub fn loader_url<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
let root = match &*loader_stream {
|
let root = match &*loader_stream {
|
||||||
LoaderStream::Stage => activation.context.swf,
|
LoaderStream::NotYetLoaded(swf) => swf,
|
||||||
LoaderStream::Swf(root, _) => root,
|
LoaderStream::Swf(root, _) => root,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -353,9 +418,12 @@ pub fn parameters<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error> {
|
) -> Result<Value<'gc>, Error> {
|
||||||
if let Some(this) = this {
|
if let Some(this) = this {
|
||||||
if let Some(loader_stream) = this.as_loader_stream() {
|
if let Some(loader_stream) = this
|
||||||
|
.as_loader_info_object()
|
||||||
|
.and_then(|o| o.as_loader_stream())
|
||||||
|
{
|
||||||
let root = match &*loader_stream {
|
let root = match &*loader_stream {
|
||||||
LoaderStream::Stage => activation.context.swf,
|
LoaderStream::NotYetLoaded(_) => activation.context.swf,
|
||||||
LoaderStream::Swf(root, _) => root,
|
LoaderStream::Swf(root, _) => root,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -383,6 +451,18 @@ pub fn parameters<'gc>(
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `sharedEvents` getter
|
||||||
|
pub fn shared_events<'gc>(
|
||||||
|
_activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
if let Some(loader_info) = this.as_ref().and_then(|this| this.as_loader_info_object()) {
|
||||||
|
return Ok(loader_info.shared_events().into());
|
||||||
|
}
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct `LoaderInfo`'s class.
|
/// Construct `LoaderInfo`'s class.
|
||||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||||
let class = Class::new(
|
let class = Class::new(
|
||||||
|
@ -421,8 +501,10 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
||||||
("url", Some(url), None),
|
("url", Some(url), None),
|
||||||
("width", Some(width), None),
|
("width", Some(width), None),
|
||||||
("bytes", Some(bytes), None),
|
("bytes", Some(bytes), None),
|
||||||
|
("loader", Some(loader), None),
|
||||||
("loaderURL", Some(loader_url), None),
|
("loaderURL", Some(loader_url), None),
|
||||||
("parameters", Some(parameters), None),
|
("parameters", Some(parameters), None),
|
||||||
|
("sharedEvents", Some(shared_events), None),
|
||||||
];
|
];
|
||||||
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
package flash.system {
|
package flash.system {
|
||||||
|
|
||||||
import flash.display.DisplayObjectContainer;
|
import flash.display.DisplayObjectContainer;
|
||||||
import flash.system.ApplicationDomain;
|
import flash.system.ApplicationDomain;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ include "flash/display/InterpolationMethod.as"
|
||||||
include "flash/display/JointStyle.as"
|
include "flash/display/JointStyle.as"
|
||||||
include "flash/display/JPEGEncoderOptions.as"
|
include "flash/display/JPEGEncoderOptions.as"
|
||||||
include "flash/display/JPEGXREncoderOptions.as"
|
include "flash/display/JPEGXREncoderOptions.as"
|
||||||
|
include "flash/display/Loader.as"
|
||||||
include "flash/display/LineScaleMode.as"
|
include "flash/display/LineScaleMode.as"
|
||||||
include "flash/display/NativeMenu.as"
|
include "flash/display/NativeMenu.as"
|
||||||
include "flash/display/NativeMenuItem.as"
|
include "flash/display/NativeMenuItem.as"
|
||||||
|
|
|
@ -3,12 +3,15 @@
|
||||||
// `Object.as` must come first since the other classes depend on it.
|
// `Object.as` must come first since the other classes depend on it.
|
||||||
include "Object.as"
|
include "Object.as"
|
||||||
|
|
||||||
// List is ordered alphabetically.
|
// List is ordered alphabetically, except where superclasses
|
||||||
|
// are listed before subclasses
|
||||||
include "Array.as"
|
include "Array.as"
|
||||||
include "Boolean.as"
|
include "Boolean.as"
|
||||||
include "flash/display/DisplayObject.as"
|
include "flash/display/DisplayObject.as"
|
||||||
include "flash/display/DisplayObjectContainer.as"
|
|
||||||
include "flash/display/InteractiveObject.as"
|
include "flash/display/InteractiveObject.as"
|
||||||
|
include "flash/display/DisplayObjectContainer.as"
|
||||||
|
|
||||||
|
include "flash/display/LoaderInfo.as"
|
||||||
include "flash/events/EventDispatcher.as"
|
include "flash/events/EventDispatcher.as"
|
||||||
include "flash/system/ApplicationDomain.as"
|
include "flash/system/ApplicationDomain.as"
|
||||||
include "Number.as"
|
include "Number.as"
|
||||||
|
|
|
@ -18,7 +18,6 @@ use crate::avm2::Namespace;
|
||||||
use crate::avm2::QName;
|
use crate::avm2::QName;
|
||||||
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;
|
||||||
|
@ -903,6 +902,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_loader_info_object(&self) -> Option<&LoaderInfoObject<'gc>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn as_array_object(&self) -> Option<ArrayObject<'gc>> {
|
fn as_array_object(&self) -> Option<ArrayObject<'gc>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1011,13 +1014,6 @@ 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
|
|
||||||
fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unwrap this object's sound handle.
|
/// Unwrap this object's sound handle.
|
||||||
fn as_sound(self) -> Option<SoundHandle> {
|
fn as_sound(self) -> Option<SoundHandle> {
|
||||||
None
|
None
|
||||||
|
|
|
@ -26,7 +26,14 @@ pub fn loaderinfo_allocator<'gc>(
|
||||||
LoaderInfoObjectData {
|
LoaderInfoObjectData {
|
||||||
base,
|
base,
|
||||||
loaded_stream: None,
|
loaded_stream: None,
|
||||||
init_fired: false,
|
loader: None,
|
||||||
|
events_fired: false,
|
||||||
|
shared_events: activation
|
||||||
|
.context
|
||||||
|
.avm2
|
||||||
|
.classes()
|
||||||
|
.eventdispatcher
|
||||||
|
.construct(activation, &[])?,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.into())
|
.into())
|
||||||
|
@ -36,12 +43,10 @@ pub fn loaderinfo_allocator<'gc>(
|
||||||
#[derive(Collect, Debug, Clone)]
|
#[derive(Collect, Debug, Clone)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub enum LoaderStream<'gc> {
|
pub enum LoaderStream<'gc> {
|
||||||
/// The current stage.
|
|
||||||
///
|
|
||||||
/// While it makes no sense to actually retrieve loader info properties off
|
/// While it makes no sense to actually retrieve loader info properties off
|
||||||
/// the stage, it's possible to do so. Some properties yield the
|
/// the stage, it's possible to do so. Some properties yield the
|
||||||
/// not-yet-loaded error while others are pulled from the root SWF.
|
/// not-yet-loaded error while others are pulled from the root SWF.
|
||||||
Stage,
|
NotYetLoaded(Arc<SwfMovie>),
|
||||||
|
|
||||||
/// A loaded SWF movie.
|
/// A loaded SWF movie.
|
||||||
///
|
///
|
||||||
|
@ -64,8 +69,15 @@ 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
|
loader: Option<Object<'gc>>,
|
||||||
init_fired: bool,
|
|
||||||
|
/// Whether or not we've fired our 'init' and 'complete' events
|
||||||
|
events_fired: bool,
|
||||||
|
|
||||||
|
/// The `EventDispatcher` used for `LoaderInfo.sharedEvents`.
|
||||||
|
// FIXME: If we ever implement sandboxing, then ensure that we allow
|
||||||
|
// events to be fired across security boundaries using this object.
|
||||||
|
shared_events: Object<'gc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> LoaderInfoObject<'gc> {
|
impl<'gc> LoaderInfoObject<'gc> {
|
||||||
|
@ -74,6 +86,7 @@ impl<'gc> LoaderInfoObject<'gc> {
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
movie: Arc<SwfMovie>,
|
movie: Arc<SwfMovie>,
|
||||||
root: DisplayObject<'gc>,
|
root: DisplayObject<'gc>,
|
||||||
|
loader: Option<Object<'gc>>,
|
||||||
) -> Result<Object<'gc>, Error> {
|
) -> Result<Object<'gc>, Error> {
|
||||||
let class = activation.avm2().classes().loaderinfo;
|
let class = activation.avm2().classes().loaderinfo;
|
||||||
let base = ScriptObjectData::new(class);
|
let base = ScriptObjectData::new(class);
|
||||||
|
@ -84,7 +97,14 @@ impl<'gc> LoaderInfoObject<'gc> {
|
||||||
LoaderInfoObjectData {
|
LoaderInfoObjectData {
|
||||||
base,
|
base,
|
||||||
loaded_stream,
|
loaded_stream,
|
||||||
init_fired: false,
|
loader,
|
||||||
|
events_fired: false,
|
||||||
|
shared_events: activation
|
||||||
|
.context
|
||||||
|
.avm2
|
||||||
|
.classes()
|
||||||
|
.eventdispatcher
|
||||||
|
.construct(activation, &[])?,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
|
@ -96,7 +116,11 @@ impl<'gc> LoaderInfoObject<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a loader info object for the stage.
|
/// Create a loader info object for the stage.
|
||||||
pub fn from_stage(activation: &mut Activation<'_, 'gc, '_>) -> Result<Object<'gc>, Error> {
|
pub fn not_yet_loaded(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
movie: Arc<SwfMovie>,
|
||||||
|
loader: Option<Object<'gc>>,
|
||||||
|
) -> Result<Object<'gc>, Error> {
|
||||||
let class = activation.avm2().classes().loaderinfo;
|
let class = activation.avm2().classes().loaderinfo;
|
||||||
let base = ScriptObjectData::new(class);
|
let base = ScriptObjectData::new(class);
|
||||||
|
|
||||||
|
@ -104,10 +128,15 @@ impl<'gc> LoaderInfoObject<'gc> {
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
LoaderInfoObjectData {
|
LoaderInfoObjectData {
|
||||||
base,
|
base,
|
||||||
loaded_stream: Some(LoaderStream::Stage),
|
loaded_stream: Some(LoaderStream::NotYetLoaded(movie)),
|
||||||
// We never want to fire an "init" event for the special
|
loader,
|
||||||
// Stagee loaderInfo
|
events_fired: false,
|
||||||
init_fired: true,
|
shared_events: activation
|
||||||
|
.context
|
||||||
|
.avm2
|
||||||
|
.classes()
|
||||||
|
.eventdispatcher
|
||||||
|
.construct(activation, &[])?,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
|
@ -117,6 +146,55 @@ impl<'gc> LoaderInfoObject<'gc> {
|
||||||
|
|
||||||
Ok(this)
|
Ok(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn loader(&self) -> Option<Object<'gc>> {
|
||||||
|
return self.0.read().loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shared_events(&self) -> Object<'gc> {
|
||||||
|
return self.0.read().shared_events;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fire_init_and_complete_events(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||||
|
if !self.0.read().events_fired {
|
||||||
|
self.0.write(context.gc_context).events_fired = true;
|
||||||
|
|
||||||
|
// TODO - 'init' should be fired earlier during the download.
|
||||||
|
// Right now, we fire it when downloading is fully completed.
|
||||||
|
let init_evt = EventObject::bare_default_event(context, "init");
|
||||||
|
|
||||||
|
if let Err(e) = Avm2::dispatch_event(context, init_evt, (*self).into()) {
|
||||||
|
log::error!(
|
||||||
|
"Encountered AVM2 error when broadcasting `init` event: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let complete_evt = EventObject::bare_default_event(context, "complete");
|
||||||
|
|
||||||
|
if let Err(e) = Avm2::dispatch_event(context, complete_evt, (*self).into()) {
|
||||||
|
log::error!(
|
||||||
|
"Encountered AVM2 error when broadcasting `complete` event: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwrap this object's loader stream
|
||||||
|
pub fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> {
|
||||||
|
if self.0.read().loaded_stream.is_some() {
|
||||||
|
Some(Ref::map(self.0.read(), |v: &LoaderInfoObjectData<'gc>| {
|
||||||
|
v.loaded_stream.as_ref().unwrap()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_loader_stream(&self, stream: LoaderStream<'gc>, mc: MutationContext<'gc, '_>) {
|
||||||
|
self.0.write(mc).loaded_stream = Some(stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
|
impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
|
||||||
|
@ -136,28 +214,7 @@ impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
|
||||||
Ok(Value::Object((*self).into()))
|
Ok(Value::Object((*self).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loader_stream_init(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
fn as_loader_info_object(&self) -> Option<&LoaderInfoObject<'gc>> {
|
||||||
if !self.0.read().init_fired {
|
Some(self)
|
||||||
self.0.write(context.gc_context).init_fired = true;
|
|
||||||
let init_evt = EventObject::bare_default_event(context, "init");
|
|
||||||
|
|
||||||
if let Err(e) = Avm2::dispatch_event(context, init_evt, (*self).into()) {
|
|
||||||
log::error!(
|
|
||||||
"Encountered AVM2 error when broadcasting `init` event: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unwrap this object's loader stream
|
|
||||||
fn as_loader_stream(&self) -> Option<Ref<LoaderStream<'gc>>> {
|
|
||||||
if self.0.read().loaded_stream.is_some() {
|
|
||||||
Some(Ref::map(self.0.read(), |v| {
|
|
||||||
v.loaded_stream.as_ref().unwrap()
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ mod container;
|
||||||
mod edit_text;
|
mod edit_text;
|
||||||
mod graphic;
|
mod graphic;
|
||||||
mod interactive;
|
mod interactive;
|
||||||
|
mod loader_display;
|
||||||
mod morph_shape;
|
mod morph_shape;
|
||||||
mod movie_clip;
|
mod movie_clip;
|
||||||
mod stage;
|
mod stage;
|
||||||
|
@ -44,6 +45,7 @@ pub use bitmap::Bitmap;
|
||||||
pub use edit_text::{AutoSizeMode, EditText, TextSelection};
|
pub use edit_text::{AutoSizeMode, EditText, TextSelection};
|
||||||
pub use graphic::Graphic;
|
pub use graphic::Graphic;
|
||||||
pub use interactive::{InteractiveObject, TInteractiveObject};
|
pub use interactive::{InteractiveObject, TInteractiveObject};
|
||||||
|
pub use loader_display::LoaderDisplay;
|
||||||
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
||||||
pub use movie_clip::{MovieClip, Scene};
|
pub use movie_clip::{MovieClip, Scene};
|
||||||
pub use stage::{Stage, StageAlign, StageDisplayState, StageQuality, StageScaleMode, WindowMode};
|
pub use stage::{Stage, StageAlign, StageDisplayState, StageQuality, StageScaleMode, WindowMode};
|
||||||
|
@ -520,6 +522,7 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
|
||||||
MovieClip(MovieClip<'gc>),
|
MovieClip(MovieClip<'gc>),
|
||||||
Text(Text<'gc>),
|
Text(Text<'gc>),
|
||||||
Video(Video<'gc>),
|
Video(Video<'gc>),
|
||||||
|
LoaderDisplay(LoaderDisplay<'gc>)
|
||||||
}
|
}
|
||||||
)]
|
)]
|
||||||
pub trait TDisplayObject<'gc>:
|
pub trait TDisplayObject<'gc>:
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::avm2::{Avm2, EventObject as Avm2EventObject, Value as Avm2Value};
|
use crate::avm2::{Avm2, EventObject as Avm2EventObject, Value as Avm2Value};
|
||||||
use crate::context::{RenderContext, UpdateContext};
|
use crate::context::{RenderContext, UpdateContext};
|
||||||
use crate::display_object::avm1_button::Avm1Button;
|
use crate::display_object::avm1_button::Avm1Button;
|
||||||
|
use crate::display_object::loader_display::LoaderDisplay;
|
||||||
use crate::display_object::movie_clip::MovieClip;
|
use crate::display_object::movie_clip::MovieClip;
|
||||||
use crate::display_object::stage::Stage;
|
use crate::display_object::stage::Stage;
|
||||||
use crate::display_object::{Depth, DisplayObject, TDisplayObject};
|
use crate::display_object::{Depth, DisplayObject, TDisplayObject};
|
||||||
|
@ -142,6 +143,7 @@ bitflags! {
|
||||||
Stage(Stage<'gc>),
|
Stage(Stage<'gc>),
|
||||||
Avm1Button(Avm1Button<'gc>),
|
Avm1Button(Avm1Button<'gc>),
|
||||||
MovieClip(MovieClip<'gc>),
|
MovieClip(MovieClip<'gc>),
|
||||||
|
LoaderDisplay(LoaderDisplay<'gc>),
|
||||||
}
|
}
|
||||||
)]
|
)]
|
||||||
pub trait TDisplayObjectContainer<'gc>:
|
pub trait TDisplayObjectContainer<'gc>:
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::context::UpdateContext;
|
||||||
use crate::display_object::avm1_button::Avm1Button;
|
use crate::display_object::avm1_button::Avm1Button;
|
||||||
use crate::display_object::avm2_button::Avm2Button;
|
use crate::display_object::avm2_button::Avm2Button;
|
||||||
use crate::display_object::edit_text::EditText;
|
use crate::display_object::edit_text::EditText;
|
||||||
|
use crate::display_object::loader_display::LoaderDisplay;
|
||||||
use crate::display_object::movie_clip::MovieClip;
|
use crate::display_object::movie_clip::MovieClip;
|
||||||
use crate::display_object::stage::Stage;
|
use crate::display_object::stage::Stage;
|
||||||
use crate::display_object::{
|
use crate::display_object::{
|
||||||
|
@ -109,6 +110,7 @@ impl<'gc> Default for InteractiveObjectBase<'gc> {
|
||||||
Avm2Button(Avm2Button<'gc>),
|
Avm2Button(Avm2Button<'gc>),
|
||||||
MovieClip(MovieClip<'gc>),
|
MovieClip(MovieClip<'gc>),
|
||||||
EditText(EditText<'gc>),
|
EditText(EditText<'gc>),
|
||||||
|
LoaderDisplay(LoaderDisplay<'gc>),
|
||||||
}
|
}
|
||||||
)]
|
)]
|
||||||
pub trait TInteractiveObject<'gc>:
|
pub trait TInteractiveObject<'gc>:
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
use crate::avm2::Object as Avm2Object;
|
||||||
|
use crate::context::RenderContext;
|
||||||
|
use crate::context::UpdateContext;
|
||||||
|
use crate::display_object::InteractiveObject;
|
||||||
|
use crate::display_object::TInteractiveObject;
|
||||||
|
use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject};
|
||||||
|
use crate::events::{ClipEvent, ClipEventResult};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::display_object::container::ChildContainer;
|
||||||
|
use crate::display_object::interactive::InteractiveObjectBase;
|
||||||
|
use gc_arena::{Collect, GcCell, MutationContext};
|
||||||
|
use std::cell::{Ref, RefMut};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Collect, Copy)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct LoaderDisplay<'gc>(GcCell<'gc, LoaderDisplayData<'gc>>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Collect)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct LoaderDisplayData<'gc> {
|
||||||
|
base: InteractiveObjectBase<'gc>,
|
||||||
|
container: ChildContainer<'gc>,
|
||||||
|
avm2_object: Avm2Object<'gc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> LoaderDisplay<'gc> {
|
||||||
|
pub fn new_with_avm2(
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
avm2_object: Avm2Object<'gc>,
|
||||||
|
) -> Self {
|
||||||
|
LoaderDisplay(GcCell::allocate(
|
||||||
|
gc_context,
|
||||||
|
LoaderDisplayData {
|
||||||
|
base: Default::default(),
|
||||||
|
container: ChildContainer::new(),
|
||||||
|
avm2_object,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TDisplayObject<'gc> for LoaderDisplay<'gc> {
|
||||||
|
fn base(&self) -> Ref<DisplayObjectBase<'gc>> {
|
||||||
|
Ref::map(self.0.read(), |r| &r.base.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_mut<'a>(&'a self, mc: MutationContext<'gc, '_>) -> RefMut<'a, DisplayObjectBase<'gc>> {
|
||||||
|
RefMut::map(self.0.write(mc), |w| &mut w.base.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate(&self, gc_context: MutationContext<'gc, '_>) -> DisplayObject<'gc> {
|
||||||
|
Self(GcCell::allocate(gc_context, self.0.read().clone())).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ptr(&self) -> *const DisplayObjectPtr {
|
||||||
|
self.0.as_ptr() as *const DisplayObjectPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> CharacterId {
|
||||||
|
u16::MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_self(&self, context: &mut RenderContext<'_, 'gc>) {
|
||||||
|
self.render_children(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn self_bounds(&self) -> BoundingBox {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn object2(&self) -> Avm2Value<'gc> {
|
||||||
|
self.0.read().avm2_object.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_container(self) -> Option<DisplayObjectContainer<'gc>> {
|
||||||
|
Some(self.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_interactive(self) -> Option<InteractiveObject<'gc>> {
|
||||||
|
Some(self.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TInteractiveObject<'gc> for LoaderDisplay<'gc> {
|
||||||
|
fn ibase(&self) -> Ref<InteractiveObjectBase<'gc>> {
|
||||||
|
Ref::map(self.0.read(), |r| &r.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ibase_mut(&self, mc: MutationContext<'gc, '_>) -> RefMut<InteractiveObjectBase<'gc>> {
|
||||||
|
RefMut::map(self.0.write(mc), |w| &mut w.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_displayobject(self) -> DisplayObject<'gc> {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_clip_event(self, _event: ClipEvent) -> ClipEventResult {
|
||||||
|
ClipEventResult::NotHandled
|
||||||
|
}
|
||||||
|
fn event_dispatch(
|
||||||
|
self,
|
||||||
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
_event: ClipEvent<'gc>,
|
||||||
|
) -> ClipEventResult {
|
||||||
|
ClipEventResult::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TDisplayObjectContainer<'gc> for LoaderDisplay<'gc> {
|
||||||
|
impl_display_object_container!(container);
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ 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::object::LoaderInfoObject;
|
||||||
|
use crate::avm2::object::LoaderStream;
|
||||||
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,
|
||||||
|
@ -177,6 +178,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a non-root movie
|
||||||
pub fn new_with_data(
|
pub fn new_with_data(
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
id: CharacterId,
|
id: CharacterId,
|
||||||
|
@ -189,7 +191,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
base: Default::default(),
|
base: Default::default(),
|
||||||
static_data: Gc::allocate(
|
static_data: Gc::allocate(
|
||||||
gc_context,
|
gc_context,
|
||||||
MovieClipStatic::with_data(id, swf, num_frames, gc_context),
|
MovieClipStatic::with_data(id, swf, num_frames, None, gc_context),
|
||||||
),
|
),
|
||||||
tag_stream_pos: 0,
|
tag_stream_pos: 0,
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
|
@ -218,20 +220,37 @@ impl<'gc> MovieClip<'gc> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a movie clip that represents an entire movie.
|
/// Construct a movie clip that represents the root movie
|
||||||
pub fn from_movie(context: &mut UpdateContext<'_, 'gc, '_>, movie: Arc<SwfMovie>) -> Self {
|
/// for the entire `Player`.
|
||||||
|
pub fn player_root_movie(
|
||||||
|
activation: &mut Avm2Activation<'_, 'gc, '_>,
|
||||||
|
movie: Arc<SwfMovie>,
|
||||||
|
) -> Self {
|
||||||
let num_frames = movie.num_frames();
|
let num_frames = movie.num_frames();
|
||||||
|
|
||||||
|
let loader_info = if movie.is_action_script_3() {
|
||||||
|
// The root movie doesn't have a `Loader`
|
||||||
|
// We will replace this with a `LoaderStream::Swf` later in this function
|
||||||
|
Some(
|
||||||
|
LoaderInfoObject::not_yet_loaded(activation, movie.clone(), None)
|
||||||
|
.expect("Failed to construct LoaderInfoObject"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let mc = MovieClip(GcCell::allocate(
|
let mc = MovieClip(GcCell::allocate(
|
||||||
context.gc_context,
|
activation.context.gc_context,
|
||||||
MovieClipData {
|
MovieClipData {
|
||||||
base: Default::default(),
|
base: Default::default(),
|
||||||
static_data: Gc::allocate(
|
static_data: Gc::allocate(
|
||||||
context.gc_context,
|
activation.context.gc_context,
|
||||||
MovieClipStatic::with_data(
|
MovieClipStatic::with_data(
|
||||||
0,
|
0,
|
||||||
movie.clone().into(),
|
movie.clone().into(),
|
||||||
num_frames,
|
num_frames,
|
||||||
context.gc_context,
|
loader_info,
|
||||||
|
activation.context.gc_context,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tag_stream_pos: 0,
|
tag_stream_pos: 0,
|
||||||
|
@ -259,8 +278,21 @@ impl<'gc> MovieClip<'gc> {
|
||||||
tag_frame_boundaries: Default::default(),
|
tag_frame_boundaries: Default::default(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
mc.set_is_root(context.gc_context, true);
|
|
||||||
mc.set_loader_info(context, movie);
|
if movie.is_action_script_3() {
|
||||||
|
mc.0.read()
|
||||||
|
.static_data
|
||||||
|
.loader_info
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.as_loader_info_object()
|
||||||
|
.unwrap()
|
||||||
|
.set_loader_stream(
|
||||||
|
LoaderStream::Swf(movie, mc.into()),
|
||||||
|
activation.context.gc_context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
mc.set_is_root(activation.context.gc_context, true);
|
||||||
mc
|
mc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,15 +308,27 @@ impl<'gc> MovieClip<'gc> {
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
movie: Option<Arc<SwfMovie>>,
|
movie: Option<Arc<SwfMovie>>,
|
||||||
|
loader_info: Option<LoaderInfoObject<'gc>>,
|
||||||
) {
|
) {
|
||||||
let mut mc = self.0.write(context.gc_context);
|
let mut mc = self.0.write(context.gc_context);
|
||||||
let is_swf = movie.is_some();
|
let is_swf = movie.is_some();
|
||||||
let movie = movie.unwrap_or_else(|| Arc::new(SwfMovie::empty(mc.movie().version())));
|
let movie = movie.unwrap_or_else(|| Arc::new(SwfMovie::empty(mc.movie().version())));
|
||||||
let total_frames = movie.num_frames();
|
let total_frames = movie.num_frames();
|
||||||
|
assert_eq!(
|
||||||
|
mc.static_data.loader_info, None,
|
||||||
|
"Called replace_movie on a clip with LoaderInfo set"
|
||||||
|
);
|
||||||
|
|
||||||
mc.base.base.reset_for_movie_load();
|
mc.base.base.reset_for_movie_load();
|
||||||
mc.static_data = Gc::allocate(
|
mc.static_data = Gc::allocate(
|
||||||
context.gc_context,
|
context.gc_context,
|
||||||
MovieClipStatic::with_data(0, movie.clone().into(), total_frames, context.gc_context),
|
MovieClipStatic::with_data(
|
||||||
|
0,
|
||||||
|
movie.into(),
|
||||||
|
total_frames,
|
||||||
|
loader_info.map(|l| l.into()),
|
||||||
|
context.gc_context,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
mc.tag_stream_pos = 0;
|
mc.tag_stream_pos = 0;
|
||||||
mc.flags = MovieClipFlags::PLAYING;
|
mc.flags = MovieClipFlags::PLAYING;
|
||||||
|
@ -293,30 +337,6 @@ impl<'gc> MovieClip<'gc> {
|
||||||
mc.audio_stream = None;
|
mc.audio_stream = None;
|
||||||
mc.container = ChildContainer::new();
|
mc.container = ChildContainer::new();
|
||||||
drop(mc);
|
drop(mc);
|
||||||
|
|
||||||
self.set_loader_info(context, movie);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_loader_info(&self, context: &mut UpdateContext<'_, 'gc, '_>, movie: Arc<SwfMovie>) {
|
|
||||||
if movie.is_action_script_3() {
|
|
||||||
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, '_>) {
|
||||||
|
@ -1855,7 +1875,6 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
|
||||||
if context.is_action_script_3() {
|
if context.is_action_script_3() {
|
||||||
let needs_construction = if matches!(self.object2(), Avm2Value::Undefined) {
|
let needs_construction = if matches!(self.object2(), Avm2Value::Undefined) {
|
||||||
self.allocate_as_avm2_object(context, (*self).into());
|
self.allocate_as_avm2_object(context, (*self).into());
|
||||||
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -1988,8 +2007,12 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
|
||||||
// keeps track if an "init" event has already been fired,
|
// keeps track if an "init" event has already been fired,
|
||||||
// so this becomes a no-op after the event has been fired.
|
// so this becomes a no-op after the event has been fired.
|
||||||
if self.0.read().initialized() {
|
if self.0.read().initialized() {
|
||||||
if let Some(loader_info) = self.loader_info() {
|
if let Some(loader_info) = self
|
||||||
loader_info.loader_stream_init(context);
|
.loader_info()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|o| o.as_loader_info_object())
|
||||||
|
{
|
||||||
|
loader_info.fire_init_and_complete_events(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2150,7 +2173,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loader_info(&self) -> Option<Avm2Object<'gc>> {
|
fn loader_info(&self) -> Option<Avm2Object<'gc>> {
|
||||||
*self.0.read().static_data.loader_info.read()
|
self.0.read().static_data.loader_info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allow_as_mask(&self) -> bool {
|
fn allow_as_mask(&self) -> bool {
|
||||||
|
@ -3387,18 +3410,23 @@ struct MovieClipStatic<'gc> {
|
||||||
/// Only set if this MovieClip is the root movie in an SWF
|
/// Only set if this MovieClip is the root movie in an SWF
|
||||||
/// (either the root SWF initially loaded by the player,
|
/// (either the root SWF initially loaded by the player,
|
||||||
/// or an SWF dynamically loaded by `Loader`)
|
/// or an SWF dynamically loaded by `Loader`)
|
||||||
loader_info: GcCell<'gc, Option<Avm2Object<'gc>>>,
|
///
|
||||||
|
/// This is always `None` for the AVM1 root movie.
|
||||||
|
/// However, it will be set for an AVM1 movie loaded from AVM2
|
||||||
|
/// via `Loader`
|
||||||
|
loader_info: Option<Avm2Object<'gc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> MovieClipStatic<'gc> {
|
impl<'gc> MovieClipStatic<'gc> {
|
||||||
fn empty(movie: Arc<SwfMovie>, gc_context: MutationContext<'gc, '_>) -> Self {
|
fn empty(movie: Arc<SwfMovie>, gc_context: MutationContext<'gc, '_>) -> Self {
|
||||||
Self::with_data(0, SwfSlice::empty(movie), 1, gc_context)
|
Self::with_data(0, SwfSlice::empty(movie), 1, None, gc_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_data(
|
fn with_data(
|
||||||
id: CharacterId,
|
id: CharacterId,
|
||||||
swf: SwfSlice,
|
swf: SwfSlice,
|
||||||
total_frames: FrameNumber,
|
total_frames: FrameNumber,
|
||||||
|
loader_info: Option<Avm2Object<'gc>>,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -3410,7 +3438,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),
|
loader_info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! 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::object::TObject;
|
use crate::avm2::object::TObject;
|
||||||
use crate::avm2::{
|
use crate::avm2::{
|
||||||
Activation as Avm2Activation, EventObject as Avm2EventObject, Object as Avm2Object,
|
Activation as Avm2Activation, EventObject as Avm2EventObject, Object as Avm2Object,
|
||||||
|
@ -174,6 +173,14 @@ impl<'gc> Stage<'gc> {
|
||||||
self.0.write(gc_context).movie_size = (width, height);
|
self.0.write(gc_context).movie_size = (width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_loader_info(
|
||||||
|
self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
loader_info: Avm2Object<'gc>,
|
||||||
|
) {
|
||||||
|
self.0.write(gc_context).loader_info = loader_info;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the quality setting of the stage.
|
/// Returns the quality setting of the stage.
|
||||||
///
|
///
|
||||||
/// In the Flash Player, the quality setting affects anti-aliasing and smoothing of bitmaps.
|
/// In the Flash Player, the quality setting affects anti-aliasing and smoothing of bitmaps.
|
||||||
|
@ -662,10 +669,6 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
|
||||||
Ok(avm2_stage) => {
|
Ok(avm2_stage) => {
|
||||||
let mut write = self.0.write(activation.context.gc_context);
|
let mut write = self.0.write(activation.context.gc_context);
|
||||||
write.avm2_object = avm2_stage.into();
|
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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ use crate::avm1::{Avm1, Object, TObject, Value};
|
||||||
use crate::avm2::bytearray::ByteArrayStorage;
|
use crate::avm2::bytearray::ByteArrayStorage;
|
||||||
use crate::avm2::object::ByteArrayObject;
|
use crate::avm2::object::ByteArrayObject;
|
||||||
use crate::avm2::object::EventObject as Avm2EventObject;
|
use crate::avm2::object::EventObject as Avm2EventObject;
|
||||||
|
use crate::avm2::object::LoaderStream;
|
||||||
use crate::avm2::object::TObject as _;
|
use crate::avm2::object::TObject as _;
|
||||||
|
use crate::avm2::Multiname;
|
||||||
use crate::avm2::Namespace;
|
use crate::avm2::Namespace;
|
||||||
use crate::avm2::{
|
use crate::avm2::{
|
||||||
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, Object as Avm2Object, QName,
|
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, Object as Avm2Object, QName,
|
||||||
|
@ -119,7 +121,7 @@ pub enum Error {
|
||||||
#[error("Could not fetch: {0}")]
|
#[error("Could not fetch: {0}")]
|
||||||
FetchError(String),
|
FetchError(String),
|
||||||
|
|
||||||
#[error("Invalid SWF")]
|
#[error("Invalid SWF: {0}")]
|
||||||
InvalidSwf(#[from] crate::tag_utils::Error),
|
InvalidSwf(#[from] crate::tag_utils::Error),
|
||||||
|
|
||||||
#[error("Unexpected content of type {1}, expected {0}")]
|
#[error("Unexpected content of type {1}, expected {0}")]
|
||||||
|
@ -209,12 +211,12 @@ impl<'gc> LoadManager<'gc> {
|
||||||
target_clip: DisplayObject<'gc>,
|
target_clip: DisplayObject<'gc>,
|
||||||
request: Request,
|
request: Request,
|
||||||
loader_url: Option<String>,
|
loader_url: Option<String>,
|
||||||
target_broadcaster: Option<Object<'gc>>,
|
event_handler: Option<MovieLoaderEventHandler<'gc>>,
|
||||||
) -> OwnedFuture<(), Error> {
|
) -> OwnedFuture<(), Error> {
|
||||||
let loader = Loader::Movie {
|
let loader = Loader::Movie {
|
||||||
self_handle: None,
|
self_handle: None,
|
||||||
target_clip,
|
target_clip,
|
||||||
target_broadcaster,
|
event_handler,
|
||||||
loader_status: LoaderStatus::Pending,
|
loader_status: LoaderStatus::Pending,
|
||||||
};
|
};
|
||||||
let handle = self.add_loader(loader);
|
let handle = self.add_loader(loader);
|
||||||
|
@ -314,6 +316,13 @@ pub enum LoaderStatus {
|
||||||
Failed,
|
Failed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Collect, Clone, Copy, Debug)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub enum MovieLoaderEventHandler<'gc> {
|
||||||
|
Avm1Broadcast(Object<'gc>),
|
||||||
|
Avm2LoaderInfo(Avm2Object<'gc>),
|
||||||
|
}
|
||||||
|
|
||||||
/// A struct that holds garbage-collected pointers for asynchronous code.
|
/// A struct that holds garbage-collected pointers for asynchronous code.
|
||||||
#[derive(Collect)]
|
#[derive(Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
|
@ -336,7 +345,7 @@ pub enum Loader<'gc> {
|
||||||
|
|
||||||
/// Event broadcaster (typically a `MovieClipLoader`) to fire events
|
/// Event broadcaster (typically a `MovieClipLoader`) to fire events
|
||||||
/// into.
|
/// into.
|
||||||
target_broadcaster: Option<Object<'gc>>,
|
event_handler: Option<MovieLoaderEventHandler<'gc>>,
|
||||||
|
|
||||||
/// Indicates the completion status of this loader.
|
/// Indicates the completion status of this loader.
|
||||||
///
|
///
|
||||||
|
@ -458,78 +467,156 @@ 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, None);
|
mc.replace_with_movie(uc, None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader::movie_loader_start(handle, uc)
|
Loader::movie_loader_start(handle, uc)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Ok(response) = fetch.await {
|
match fetch.await {
|
||||||
let sniffed_type = ContentType::sniff(&response.body);
|
Ok(response) => {
|
||||||
let mut length = response.body.len();
|
let sniffed_type = ContentType::sniff(&response.body);
|
||||||
|
let mut length = response.body.len();
|
||||||
|
|
||||||
if replacing_root_movie {
|
if replacing_root_movie {
|
||||||
sniffed_type.expect(ContentType::Swf)?;
|
sniffed_type.expect(ContentType::Swf)?;
|
||||||
|
|
||||||
let movie =
|
let movie =
|
||||||
SwfMovie::from_data(&response.body, Some(response.url), loader_url)?;
|
SwfMovie::from_data(&response.body, Some(response.url), loader_url)?;
|
||||||
player.lock().unwrap().set_root_movie(movie);
|
player.lock().unwrap().set_root_movie(movie);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
|
||||||
|
|
||||||
player.lock().unwrap().update(|uc| {
|
|
||||||
let clip = match uc.load_manager.get_loader(handle) {
|
|
||||||
Some(Loader::Movie { target_clip, .. }) => *target_clip,
|
|
||||||
None => return Err(Error::Cancelled),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match sniffed_type {
|
|
||||||
ContentType::Swf => {
|
|
||||||
let movie = Arc::new(SwfMovie::from_data(
|
|
||||||
&response.body,
|
|
||||||
Some(response.url),
|
|
||||||
loader_url,
|
|
||||||
)?);
|
|
||||||
|
|
||||||
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
|
|
||||||
let parent_domain = activation.avm2().global_domain();
|
|
||||||
let domain = Avm2Domain::movie_domain(&mut activation, parent_domain);
|
|
||||||
uc.library
|
|
||||||
.library_for_movie_mut(movie.clone())
|
|
||||||
.set_avm2_domain(domain);
|
|
||||||
|
|
||||||
if let Some(mut mc) = clip.as_movie_clip() {
|
|
||||||
mc.replace_with_movie(uc, Some(movie));
|
|
||||||
mc.post_instantiation(uc, None, Instantiator::Movie, false);
|
|
||||||
mc.preload(uc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ContentType::Gif | ContentType::Jpeg | ContentType::Png => {
|
|
||||||
let bitmap = uc.renderer.register_bitmap_jpeg_2(&response.body)?;
|
|
||||||
let bitmap_obj =
|
|
||||||
Bitmap::new(uc, 0, bitmap.handle, bitmap.width, bitmap.height);
|
|
||||||
|
|
||||||
if let Some(mc) = clip.as_movie_clip() {
|
|
||||||
mc.replace_at_depth(uc, bitmap_obj.into(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ContentType::Unknown => {
|
|
||||||
length = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader::movie_loader_progress(handle, uc, length, length)?;
|
player.lock().unwrap().update(|uc| {
|
||||||
|
let (clip, event_handler) = match uc.load_manager.get_loader(handle) {
|
||||||
|
Some(Loader::Movie {
|
||||||
|
target_clip,
|
||||||
|
event_handler,
|
||||||
|
..
|
||||||
|
}) => (*target_clip, *event_handler),
|
||||||
|
None => return Err(Error::Cancelled),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
Loader::movie_loader_complete(handle, uc)?;
|
if let ContentType::Unknown = sniffed_type {
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
// FIXME - properly set 'bytesLoaded' and 'bytesTotal' on our `LoaderInfo` object
|
||||||
})?; //TODO: content sniffing errors need to be reported somehow
|
// to match the values in the event.
|
||||||
} else {
|
if let Some(MovieLoaderEventHandler::Avm2LoaderInfo(_)) = event_handler {
|
||||||
player
|
// Flash always fires an initial 'progress' event with
|
||||||
.lock()
|
// bytesLoaded=0 and bytesTotal set to the proper value.
|
||||||
.unwrap()
|
// This only seems to happen for an AVM2 event handler
|
||||||
.update(|uc| -> Result<(), Error> { Loader::movie_loader_error(handle, uc) })?;
|
Loader::movie_loader_progress(handle, uc, 0, length)?;
|
||||||
|
}
|
||||||
|
Loader::movie_loader_progress(handle, uc, length, length)?;
|
||||||
|
|
||||||
|
match sniffed_type {
|
||||||
|
ContentType::Swf => {
|
||||||
|
let movie = Arc::new(SwfMovie::from_data(
|
||||||
|
&response.body,
|
||||||
|
Some(response.url),
|
||||||
|
loader_url,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
|
||||||
|
let parent_domain = activation.avm2().global_domain();
|
||||||
|
let domain =
|
||||||
|
Avm2Domain::movie_domain(&mut activation, parent_domain);
|
||||||
|
activation
|
||||||
|
.context
|
||||||
|
.library
|
||||||
|
.library_for_movie_mut(movie.clone())
|
||||||
|
.set_avm2_domain(domain);
|
||||||
|
|
||||||
|
if let Some(mut mc) = clip.as_movie_clip() {
|
||||||
|
let loader_info = if let Some(
|
||||||
|
MovieLoaderEventHandler::Avm2LoaderInfo(loader_info),
|
||||||
|
) = event_handler
|
||||||
|
{
|
||||||
|
Some(*loader_info.as_loader_info_object().unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store our downloaded `SwfMovie` into our target `MovieClip`,
|
||||||
|
// and initialize it.
|
||||||
|
|
||||||
|
mc.replace_with_movie(
|
||||||
|
&mut activation.context,
|
||||||
|
Some(movie.clone()),
|
||||||
|
loader_info,
|
||||||
|
);
|
||||||
|
// FIXME - do we need to call 'set_place_frame'
|
||||||
|
mc.preload(&mut activation.context);
|
||||||
|
mc.construct_frame(&mut activation.context);
|
||||||
|
mc.post_instantiation(
|
||||||
|
&mut activation.context,
|
||||||
|
None,
|
||||||
|
Instantiator::Movie,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(loader_info) = loader_info {
|
||||||
|
// Store the real movie into the `LoaderStream`, so that
|
||||||
|
// 'bytesTotal' starts returning the correct value
|
||||||
|
// (we previously had a fake empty SwfMovie).
|
||||||
|
// However, we still use `LoaderStream::NotYetLoaded`, since
|
||||||
|
// the actual MovieClip display object has not run its first
|
||||||
|
// frame yet.
|
||||||
|
loader_info.set_loader_stream(
|
||||||
|
LoaderStream::NotYetLoaded(movie),
|
||||||
|
activation.context.gc_context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(MovieLoaderEventHandler::Avm2LoaderInfo(loader_info)) =
|
||||||
|
event_handler
|
||||||
|
{
|
||||||
|
let mut loader = loader_info
|
||||||
|
.get_property(
|
||||||
|
&Multiname::public("loader"),
|
||||||
|
&mut activation,
|
||||||
|
)?
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.as_display_object()
|
||||||
|
.unwrap()
|
||||||
|
.as_container()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Note that we do *not* use the 'addChild' method here:
|
||||||
|
// Per the flash docs, our implementation always throws
|
||||||
|
// an 'unsupported' error. Also, the AVM2 side of our movie
|
||||||
|
// clip does not yet exist.
|
||||||
|
loader.insert_at_index(&mut activation.context, clip, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContentType::Gif | ContentType::Jpeg | ContentType::Png => {
|
||||||
|
let bitmap = uc.renderer.register_bitmap_jpeg_2(&response.body)?;
|
||||||
|
let bitmap_obj =
|
||||||
|
Bitmap::new(uc, 0, bitmap.handle, bitmap.width, bitmap.height);
|
||||||
|
|
||||||
|
if let Some(mc) = clip.as_movie_clip() {
|
||||||
|
mc.replace_at_depth(uc, bitmap_obj.into(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContentType::Unknown => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader::movie_loader_complete(handle, uc)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?; //TODO: content sniffing errors need to be reported somehow
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error during movie loading: {:?}", e);
|
||||||
|
player.lock().unwrap().update(|uc| -> Result<(), Error> {
|
||||||
|
Loader::movie_loader_error(handle, uc)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -823,23 +910,37 @@ impl<'gc> Loader<'gc> {
|
||||||
|
|
||||||
let me = me.unwrap();
|
let me = me.unwrap();
|
||||||
|
|
||||||
let (clip, broadcaster) = match me {
|
let (clip, event_handler) = match me {
|
||||||
Loader::Movie {
|
Loader::Movie {
|
||||||
target_clip,
|
target_clip,
|
||||||
target_broadcaster,
|
event_handler,
|
||||||
..
|
..
|
||||||
} => (*target_clip, *target_broadcaster),
|
} => (*target_clip, *event_handler),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(broadcaster) = broadcaster {
|
match event_handler {
|
||||||
Avm1::run_stack_frame_for_method(
|
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||||
clip,
|
Avm1::run_stack_frame_for_method(
|
||||||
broadcaster,
|
clip,
|
||||||
uc,
|
broadcaster,
|
||||||
"broadcastMessage".into(),
|
uc,
|
||||||
&["onLoadStart".into(), clip.object()],
|
"broadcastMessage".into(),
|
||||||
);
|
&["onLoadStart".into(), clip.object()],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(MovieLoaderEventHandler::Avm2LoaderInfo(loader_info)) => {
|
||||||
|
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
|
||||||
|
let open_evt = Avm2EventObject::bare_default_event(&mut activation.context, "open");
|
||||||
|
|
||||||
|
if let Err(e) = Avm2::dispatch_event(uc, open_evt, loader_info) {
|
||||||
|
log::error!(
|
||||||
|
"Encountered AVM2 error when broadcasting `open` event: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -859,28 +960,52 @@ impl<'gc> Loader<'gc> {
|
||||||
|
|
||||||
let me = me.unwrap();
|
let me = me.unwrap();
|
||||||
|
|
||||||
let (clip, broadcaster) = match me {
|
let (clip, event_handler) = match me {
|
||||||
Loader::Movie {
|
Loader::Movie {
|
||||||
target_clip,
|
target_clip,
|
||||||
target_broadcaster,
|
event_handler,
|
||||||
..
|
..
|
||||||
} => (*target_clip, *target_broadcaster),
|
} => (*target_clip, *event_handler),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(broadcaster) = broadcaster {
|
match event_handler {
|
||||||
Avm1::run_stack_frame_for_method(
|
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||||
clip,
|
Avm1::run_stack_frame_for_method(
|
||||||
broadcaster,
|
clip,
|
||||||
uc,
|
broadcaster,
|
||||||
"broadcastMessage".into(),
|
uc,
|
||||||
&[
|
"broadcastMessage".into(),
|
||||||
"onLoadProgress".into(),
|
&[
|
||||||
clip.object(),
|
"onLoadProgress".into(),
|
||||||
cur_len.into(),
|
clip.object(),
|
||||||
total_len.into(),
|
cur_len.into(),
|
||||||
],
|
total_len.into(),
|
||||||
);
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(MovieLoaderEventHandler::Avm2LoaderInfo(loader_info)) => {
|
||||||
|
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
|
||||||
|
|
||||||
|
let progress_evt = activation.avm2().classes().progressevent.construct(
|
||||||
|
&mut activation,
|
||||||
|
&[
|
||||||
|
"progress".into(),
|
||||||
|
false.into(),
|
||||||
|
false.into(),
|
||||||
|
cur_len.into(),
|
||||||
|
total_len.into(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Err(e) = Avm2::dispatch_event(uc, progress_evt, loader_info) {
|
||||||
|
log::error!(
|
||||||
|
"Encountered AVM2 error when broadcasting `progress` event: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -891,25 +1016,39 @@ impl<'gc> Loader<'gc> {
|
||||||
handle: Index,
|
handle: Index,
|
||||||
uc: &mut UpdateContext<'_, 'gc, '_>,
|
uc: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let (clip, broadcaster) = match uc.load_manager.get_loader_mut(handle) {
|
let (clip, event_handler) = match uc.load_manager.get_loader_mut(handle) {
|
||||||
Some(Loader::Movie {
|
Some(Loader::Movie {
|
||||||
target_clip,
|
target_clip,
|
||||||
target_broadcaster,
|
event_handler,
|
||||||
..
|
..
|
||||||
}) => (*target_clip, *target_broadcaster),
|
}) => (*target_clip, *event_handler),
|
||||||
None => return Err(Error::Cancelled),
|
None => return Err(Error::Cancelled),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(broadcaster) = broadcaster {
|
match event_handler {
|
||||||
Avm1::run_stack_frame_for_method(
|
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||||
clip,
|
Avm1::run_stack_frame_for_method(
|
||||||
broadcaster,
|
clip,
|
||||||
uc,
|
broadcaster,
|
||||||
"broadcastMessage".into(),
|
uc,
|
||||||
// TODO: Pass an actual httpStatus argument instead of 0.
|
"broadcastMessage".into(),
|
||||||
&["onLoadComplete".into(), clip.object(), 0.into()],
|
// TODO: Pass an actual httpStatus argument instead of 0.
|
||||||
);
|
&["onLoadComplete".into(), clip.object(), 0.into()],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// This is fired after we process the movie's first frame,
|
||||||
|
// in `MovieClip.on_exit_frame`
|
||||||
|
Some(MovieLoaderEventHandler::Avm2LoaderInfo(loader_info)) => {
|
||||||
|
loader_info
|
||||||
|
.as_loader_info_object()
|
||||||
|
.unwrap()
|
||||||
|
.set_loader_stream(
|
||||||
|
LoaderStream::Swf(clip.as_movie_clip().unwrap().movie().unwrap(), clip),
|
||||||
|
uc.gc_context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Loader::Movie { loader_status, .. } = uc.load_manager.get_loader_mut(handle).unwrap()
|
if let Loader::Movie { loader_status, .. } = uc.load_manager.get_loader_mut(handle).unwrap()
|
||||||
|
@ -930,28 +1069,54 @@ impl<'gc> Loader<'gc> {
|
||||||
//error types we can actually inspect.
|
//error types we can actually inspect.
|
||||||
//This also can get errors from decoding an invalid SWF file,
|
//This also can get errors from decoding an invalid SWF file,
|
||||||
//too. We should distinguish those to player code.
|
//too. We should distinguish those to player code.
|
||||||
let (clip, broadcaster) = match uc.load_manager.get_loader_mut(handle) {
|
let (clip, event_handler) = match uc.load_manager.get_loader_mut(handle) {
|
||||||
Some(Loader::Movie {
|
Some(Loader::Movie {
|
||||||
target_clip,
|
target_clip,
|
||||||
target_broadcaster,
|
event_handler,
|
||||||
..
|
..
|
||||||
}) => (*target_clip, *target_broadcaster),
|
}) => (*target_clip, *event_handler),
|
||||||
None => return Err(Error::Cancelled),
|
None => return Err(Error::Cancelled),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(broadcaster) = broadcaster {
|
match event_handler {
|
||||||
Avm1::run_stack_frame_for_method(
|
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||||
clip,
|
Avm1::run_stack_frame_for_method(
|
||||||
broadcaster,
|
clip,
|
||||||
uc,
|
broadcaster,
|
||||||
"broadcastMessage".into(),
|
uc,
|
||||||
&[
|
"broadcastMessage".into(),
|
||||||
"onLoadError".into(),
|
&[
|
||||||
clip.object(),
|
"onLoadError".into(),
|
||||||
"LoadNeverCompleted".into(),
|
clip.object(),
|
||||||
],
|
"LoadNeverCompleted".into(),
|
||||||
);
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(MovieLoaderEventHandler::Avm2LoaderInfo(loader_info)) => {
|
||||||
|
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
|
||||||
|
// FIXME - Match the exact error message generated by Flash
|
||||||
|
|
||||||
|
let io_error_evt_cls = activation.avm2().classes().ioerrorevent;
|
||||||
|
let io_error_evt = io_error_evt_cls.construct(
|
||||||
|
&mut activation,
|
||||||
|
&[
|
||||||
|
"ioError".into(),
|
||||||
|
false.into(),
|
||||||
|
false.into(),
|
||||||
|
"Movie loader error".into(),
|
||||||
|
0.into(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Err(e) = Avm2::dispatch_event(uc, io_error_evt, loader_info) {
|
||||||
|
log::error!(
|
||||||
|
"Encountered AVM2 error when broadcasting `ioError` event: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Loader::Movie { loader_status, .. } = uc.load_manager.get_loader_mut(handle).unwrap()
|
if let Loader::Movie { loader_status, .. } = uc.load_manager.get_loader_mut(handle).unwrap()
|
||||||
|
@ -968,13 +1133,13 @@ impl<'gc> Loader<'gc> {
|
||||||
///
|
///
|
||||||
/// Used to fire listener events on clips and terminate completed loaders.
|
/// Used to fire listener events on clips and terminate completed loaders.
|
||||||
fn movie_clip_loaded(&mut self, queue: &mut ActionQueue<'gc>) -> bool {
|
fn movie_clip_loaded(&mut self, queue: &mut ActionQueue<'gc>) -> bool {
|
||||||
let (clip, broadcaster, loader_status) = match self {
|
let (clip, event_handler, loader_status) = match self {
|
||||||
Loader::Movie {
|
Loader::Movie {
|
||||||
target_clip,
|
target_clip,
|
||||||
target_broadcaster,
|
event_handler,
|
||||||
loader_status,
|
loader_status,
|
||||||
..
|
..
|
||||||
} => (*target_clip, *target_broadcaster, *loader_status),
|
} => (*target_clip, *event_handler, *loader_status),
|
||||||
_ => return false,
|
_ => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -982,7 +1147,8 @@ impl<'gc> Loader<'gc> {
|
||||||
LoaderStatus::Pending => false,
|
LoaderStatus::Pending => false,
|
||||||
LoaderStatus::Failed => true,
|
LoaderStatus::Failed => true,
|
||||||
LoaderStatus::Succeeded => {
|
LoaderStatus::Succeeded => {
|
||||||
if let Some(broadcaster) = broadcaster {
|
// AVM2 is handled separately
|
||||||
|
if let Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) = event_handler {
|
||||||
queue.queue_actions(
|
queue.queue_actions(
|
||||||
clip,
|
clip,
|
||||||
ActionType::Method {
|
ActionType::Method {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::avm1::globals::system::SystemProperties;
|
||||||
use crate::avm1::object::Object;
|
use crate::avm1::object::Object;
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
use crate::avm1::{Avm1, ScriptObject, TObject, Value};
|
use crate::avm1::{Avm1, ScriptObject, TObject, Value};
|
||||||
|
use crate::avm2::object::LoaderInfoObject;
|
||||||
use crate::avm2::{
|
use crate::avm2::{
|
||||||
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, EventObject as Avm2EventObject,
|
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, EventObject as Avm2EventObject,
|
||||||
};
|
};
|
||||||
|
@ -290,15 +291,29 @@ impl Player {
|
||||||
let global_domain = activation.avm2().global_domain();
|
let global_domain = activation.avm2().global_domain();
|
||||||
let domain = Avm2Domain::movie_domain(&mut activation, global_domain);
|
let domain = Avm2Domain::movie_domain(&mut activation, global_domain);
|
||||||
|
|
||||||
drop(activation);
|
activation
|
||||||
|
.context
|
||||||
context
|
|
||||||
.library
|
.library
|
||||||
.library_for_movie_mut(context.swf.clone())
|
.library_for_movie_mut(activation.context.swf.clone())
|
||||||
.set_avm2_domain(domain);
|
.set_avm2_domain(domain);
|
||||||
context.ui.set_mouse_visible(true);
|
activation.context.ui.set_mouse_visible(true);
|
||||||
|
|
||||||
let root: DisplayObject = MovieClip::from_movie(context, context.swf.clone()).into();
|
let swf = activation.context.swf.clone();
|
||||||
|
let root: DisplayObject =
|
||||||
|
MovieClip::player_root_movie(&mut activation, swf.clone()).into();
|
||||||
|
|
||||||
|
// The Stage `LoaderInfo` is permanently in the 'not yet loaded' state,
|
||||||
|
// and has no associated `Loader` instance.
|
||||||
|
// However, some properties are always accessible, and take their values
|
||||||
|
// from the root SWF.
|
||||||
|
let stage_loader_info = LoaderInfoObject::not_yet_loaded(&mut activation, swf, None)
|
||||||
|
.expect("Failed to construct Stage LoaderInfo");
|
||||||
|
activation
|
||||||
|
.context
|
||||||
|
.stage
|
||||||
|
.set_loader_info(activation.context.gc_context, stage_loader_info);
|
||||||
|
|
||||||
|
drop(activation);
|
||||||
|
|
||||||
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() {
|
||||||
|
@ -1936,7 +1951,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, fake_movie);
|
let fake_root = MovieClip::new(fake_movie, context.gc_context);
|
||||||
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");
|
||||||
|
|
|
@ -349,6 +349,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_loader_events, "avm2/loader_events", 3, img = true),
|
||||||
(as3_loaderinfo_events, "avm2/loaderinfo_events", 2),
|
(as3_loaderinfo_events, "avm2/loaderinfo_events", 2),
|
||||||
(as3_loaderinfo_properties, "avm2/loaderinfo_properties", 2),
|
(as3_loaderinfo_properties, "avm2/loaderinfo_properties", 2),
|
||||||
(as3_loaderinfo_root, "avm2/loaderinfo_root", 1),
|
(as3_loaderinfo_root, "avm2/loaderinfo_root", 1),
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package {
|
||||||
|
import flash.display.Stage;
|
||||||
|
import flash.display.Sprite;
|
||||||
|
|
||||||
|
public class Loadable {
|
||||||
|
public function Loadable(stage: Stage) {
|
||||||
|
trace("Hello from loaded SWF!");
|
||||||
|
|
||||||
|
var circle:Sprite = new Sprite();
|
||||||
|
circle.graphics.beginFill(0xFFCC00);
|
||||||
|
circle.graphics.drawCircle(50, 50, 50);
|
||||||
|
|
||||||
|
stage.addChild(circle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package {
|
||||||
|
import flash.display.Stage;
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
public function Test(stage: Stage) {
|
||||||
|
import flash.display.Loader;
|
||||||
|
import flash.net.URLRequest;
|
||||||
|
import flash.errors.IllegalOperationError;
|
||||||
|
import flash.display.Sprite;
|
||||||
|
import flash.events.Event;
|
||||||
|
import flash.events.ProgressEvent;
|
||||||
|
|
||||||
|
var loader = new Loader();
|
||||||
|
stage.addChild(loader);
|
||||||
|
trace("loader.content = " + loader.content);
|
||||||
|
trace("loader.contentLoaderInfo.content = " + loader.contentLoaderInfo.content);
|
||||||
|
trace("loader.contentLoaderInfo.bytesLoaded = " + loader.contentLoaderInfo.bytesLoaded);
|
||||||
|
trace("loader.contentLoaderInfo.bytesTotal = " + loader.contentLoaderInfo.bytesTotal);
|
||||||
|
trace("loader.contentLoaderInfo.bytes = " + loader.contentLoaderInfo.bytes);
|
||||||
|
trace("loader.contentLoaderInfo.url = " + loader.contentLoaderInfo.url);
|
||||||
|
|
||||||
|
function dump(event:Event) {
|
||||||
|
var url = loader.contentLoaderInfo.url;
|
||||||
|
if (url) {
|
||||||
|
// This truncates the path to 'file:///' to make the output
|
||||||
|
// reproducible across deifferent machines
|
||||||
|
url = url.substr(0, 8);
|
||||||
|
}
|
||||||
|
trace("Event " + event + ": "
|
||||||
|
+ "loader.numChildren = " + loader.numChildren
|
||||||
|
+ ", loader.content = " + loader.content
|
||||||
|
+ ", loader.contentLoaderInfo.bytesLoaded = " + loader.contentLoaderInfo.bytesLoaded
|
||||||
|
+ ", loader.contentLoaderInfo.bytesTotal = " + loader.contentLoaderInfo.bytesTotal
|
||||||
|
+ ", loader.contentLoaderInfo.url = " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.contentLoaderInfo.addEventListener(Event.OPEN, function(e) {
|
||||||
|
dump(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, function(e) {
|
||||||
|
// FIXME - the 'bytesLoaded' and 'bytesTotal' values printed here are wrong,
|
||||||
|
// as they are not properly implemented in Ruffle. Once the implementation is fixed,
|
||||||
|
// the output of this test will change.
|
||||||
|
dump(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.contentLoaderInfo.addEventListener(Event.INIT, function(e) {
|
||||||
|
trace("loader.contentLoaderInfo === loader.content.loaderInfo : " + (loader.contentLoaderInfo === loader.content.loaderInfo).toString());
|
||||||
|
trace("loader.contentLoaderInfo.content === loader.content : " + (loader.contentLoaderInfo.content == loader.content).toString());
|
||||||
|
dump(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e) {
|
||||||
|
dump(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.load(new URLRequest("./loadable.swf"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,14 @@
|
||||||
|
loader.content = null
|
||||||
|
loader.contentLoaderInfo.content = null
|
||||||
|
loader.contentLoaderInfo.bytesLoaded = 0
|
||||||
|
loader.contentLoaderInfo.bytesTotal = 0
|
||||||
|
loader.contentLoaderInfo.bytes = null
|
||||||
|
loader.contentLoaderInfo.url = null
|
||||||
|
Event [Event type="open" bubbles=false cancelable=false eventPhase=2]: loader.numChildren = 0, loader.content = null, loader.contentLoaderInfo.bytesLoaded = 0, loader.contentLoaderInfo.bytesTotal = 0, loader.contentLoaderInfo.url = null
|
||||||
|
Event [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=0 bytesTotal=674]: loader.numChildren = 0, loader.content = null, loader.contentLoaderInfo.bytesLoaded = 0, loader.contentLoaderInfo.bytesTotal = 0, loader.contentLoaderInfo.url = null
|
||||||
|
Event [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=674 bytesTotal=674]: loader.numChildren = 0, loader.content = null, loader.contentLoaderInfo.bytesLoaded = 0, loader.contentLoaderInfo.bytesTotal = 0, loader.contentLoaderInfo.url = null
|
||||||
|
Hello from loaded SWF!
|
||||||
|
loader.contentLoaderInfo === loader.content.loaderInfo : true
|
||||||
|
loader.contentLoaderInfo.content === loader.content : true
|
||||||
|
Event [Event type="init" bubbles=false cancelable=false eventPhase=2]: loader.numChildren = 1, loader.content = [object LoadableMainTimeline], loader.contentLoaderInfo.bytesLoaded = 674, loader.contentLoaderInfo.bytesTotal = 674, loader.contentLoaderInfo.url = file:///
|
||||||
|
Event [Event type="complete" bubbles=false cancelable=false eventPhase=2]: loader.numChildren = 1, loader.content = [object LoadableMainTimeline], loader.contentLoaderInfo.bytesLoaded = 674, loader.contentLoaderInfo.bytesTotal = 674, loader.contentLoaderInfo.url = file:///
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue