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
|
||||
with:
|
||||
name: swf_images
|
||||
path: tests/**/actual.png
|
||||
path: tests*/**/actual.png
|
||||
|
||||
check-required:
|
||||
needs: changes
|
||||
|
|
|
@ -1153,7 +1153,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
if url.is_empty() {
|
||||
//Blank URL on movie loads = unload!
|
||||
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 {
|
||||
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() {
|
||||
// Blank URL on movie loads = unload!
|
||||
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 {
|
||||
let request = self.locals_into_request(
|
||||
|
@ -1293,7 +1293,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
if url.is_empty() {
|
||||
// Blank URL on movie loads = unload!
|
||||
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 {
|
||||
let future = self.context.load_manager.load_movie_into_clip(
|
||||
|
|
|
@ -1361,7 +1361,7 @@ fn unload_movie<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::avm1::property_decl::{define_properties_on, Declaration};
|
|||
use crate::avm1::{ArrayObject, Object, Value};
|
||||
use crate::backend::navigator::Request;
|
||||
use crate::display_object::{TDisplayObject, TDisplayObjectContainer};
|
||||
use crate::loader::MovieLoaderEventHandler;
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||
|
@ -63,7 +64,7 @@ fn load_clip<'gc>(
|
|||
target,
|
||||
Request::get(url.to_utf8_lossy().into_owned()),
|
||||
None,
|
||||
Some(this),
|
||||
Some(MovieLoaderEventHandler::Avm1Broadcast(this)),
|
||||
);
|
||||
activation.context.navigator.spawn_future(future);
|
||||
|
||||
|
@ -99,7 +100,7 @@ fn unload_clip<'gc>(
|
|||
if let Some(target) = target {
|
||||
target.unload(&mut activation.context);
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ pub struct SystemClasses<'gc> {
|
|||
pub qname: ClassObject<'gc>,
|
||||
pub sharedobject: ClassObject<'gc>,
|
||||
pub mouseevent: ClassObject<'gc>,
|
||||
pub progressevent: ClassObject<'gc>,
|
||||
pub textevent: ClassObject<'gc>,
|
||||
pub errorevent: ClassObject<'gc>,
|
||||
pub ioerrorevent: ClassObject<'gc>,
|
||||
|
@ -96,6 +97,8 @@ pub struct SystemClasses<'gc> {
|
|||
pub transform: ClassObject<'gc>,
|
||||
pub colortransform: ClassObject<'gc>,
|
||||
pub matrix: ClassObject<'gc>,
|
||||
pub illegaloperationerror: ClassObject<'gc>,
|
||||
pub eventdispatcher: ClassObject<'gc>,
|
||||
}
|
||||
|
||||
impl<'gc> SystemClasses<'gc> {
|
||||
|
@ -154,6 +157,7 @@ impl<'gc> SystemClasses<'gc> {
|
|||
qname: object,
|
||||
sharedobject: object,
|
||||
mouseevent: object,
|
||||
progressevent: object,
|
||||
textevent: object,
|
||||
errorevent: object,
|
||||
ioerrorevent: object,
|
||||
|
@ -161,6 +165,8 @@ impl<'gc> SystemClasses<'gc> {
|
|||
transform: object,
|
||||
colortransform: object,
|
||||
matrix: object,
|
||||
illegaloperationerror: object,
|
||||
eventdispatcher: object,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -454,11 +460,12 @@ pub fn load_player_globals<'gc>(
|
|||
flash::events::ieventdispatcher::create_interface(mc),
|
||||
script,
|
||||
)?;
|
||||
class(
|
||||
avm2_system_class!(
|
||||
eventdispatcher,
|
||||
activation,
|
||||
flash::events::eventdispatcher::create_class(mc),
|
||||
script,
|
||||
)?;
|
||||
script
|
||||
);
|
||||
|
||||
// package `flash.utils`
|
||||
avm2_system_class!(
|
||||
|
@ -567,7 +574,6 @@ pub fn load_player_globals<'gc>(
|
|||
flash::display::graphics::create_class(mc),
|
||||
script
|
||||
);
|
||||
class(activation, flash::display::loader::create_class(mc), script)?;
|
||||
avm2_system_class!(
|
||||
loaderinfo,
|
||||
activation,
|
||||
|
@ -734,9 +740,15 @@ fn load_playerglobal<'gc>(
|
|||
script,
|
||||
[
|
||||
("flash.display", "Scene", scene),
|
||||
(
|
||||
"flash.errors",
|
||||
"IllegalOperationError",
|
||||
illegaloperationerror
|
||||
),
|
||||
("flash.events", "Event", event),
|
||||
("flash.events", "TextEvent", textevent),
|
||||
("flash.events", "ErrorEvent", errorevent),
|
||||
("flash.events", "ProgressEvent", progressevent),
|
||||
("flash.events", "SecurityErrorEvent", securityerrorevent),
|
||||
("flash.events", "IOErrorEvent", ioerrorevent),
|
||||
("flash.events", "MouseEvent", mouseevent),
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
// This is a stub - the actual class is defined in `displayobjectcontainer.rs`
|
||||
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.
|
||||
fn add_child_to_displaylist<'gc>(
|
||||
pub(super) fn add_child_to_displaylist<'gc>(
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
parent: DisplayObject<'gc>,
|
||||
child: DisplayObject<'gc>,
|
||||
|
|
|
@ -1,71 +1,92 @@
|
|||
//! `flash.display.Loader` builtin/prototype
|
||||
|
||||
use crate::avm2::activation::Activation;
|
||||
use crate::avm2::class::{Class, ClassAttributes};
|
||||
use crate::avm2::method::{Method, NativeMethodImpl};
|
||||
use crate::avm2::object::LoaderInfoObject;
|
||||
use crate::avm2::object::TObject;
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Multiname;
|
||||
use crate::avm2::Namespace;
|
||||
use crate::avm2::QName;
|
||||
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 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>(
|
||||
pub fn init<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(this) = this {
|
||||
activation.super_init(this, &[])?;
|
||||
if let Some(mut this) = 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)
|
||||
}
|
||||
|
||||
// TODO: this is entirely stubbed, just to get addEventListener to not crash
|
||||
fn content_loader_info<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
pub fn load<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
log::warn!("Loader::contentLoaderInfo is a stub");
|
||||
Ok(this.unwrap().into())
|
||||
}
|
||||
|
||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||
let class = Class::new(
|
||||
QName::new(Namespace::package("flash.display"), "Loader"),
|
||||
Some(
|
||||
QName::new(
|
||||
Namespace::package("flash.display"),
|
||||
"DisplayObjectContainer",
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
Method::from_builtin(instance_init, "<Loader instance initializer>", mc),
|
||||
Method::from_builtin(class_init, "<Loader class initializer>", mc),
|
||||
mc,
|
||||
);
|
||||
|
||||
let mut write = class.write(mc);
|
||||
|
||||
write.set_attributes(ClassAttributes::SEALED);
|
||||
|
||||
const PUBLIC_INSTANCE_PROPERTIES: &[(
|
||||
&str,
|
||||
Option<NativeMethodImpl>,
|
||||
Option<NativeMethodImpl>,
|
||||
)] = &[("contentLoaderInfo", Some(content_loader_info), None)];
|
||||
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
||||
|
||||
class
|
||||
if let Some(this) = this {
|
||||
let url_request = args[0].as_object().unwrap();
|
||||
|
||||
if let Some(context) = args.get(1) {
|
||||
if !matches!(context, Value::Null) {
|
||||
log::warn!(
|
||||
"Loader.load: 'context' argument is not yet implemented: {:?}",
|
||||
context
|
||||
);
|
||||
}
|
||||
}
|
||||
let url = url_request
|
||||
.get_property(&Multiname::public("url"), activation)?
|
||||
.coerce_to_string(activation)?;
|
||||
|
||||
// This is a dummy MovieClip, which will get overwritten in `Loader`
|
||||
let content = MovieClip::new(
|
||||
Arc::new(SwfMovie::empty(activation.context.swf.version())),
|
||||
activation.context.gc_context,
|
||||
);
|
||||
|
||||
let loader_info = this
|
||||
.get_property(
|
||||
&QName::new(Namespace::private(""), "_contentLoaderInfo").into(),
|
||||
activation,
|
||||
)?
|
||||
.as_object()
|
||||
.unwrap();
|
||||
|
||||
let future = activation.context.load_manager.load_movie_into_clip(
|
||||
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::display_object::TDisplayObject;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use std::sync::Arc;
|
||||
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.
|
||||
pub fn instance_init<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
|
@ -51,10 +56,13 @@ pub fn action_script_version<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => {
|
||||
return Err("Error: The stage's loader info does not have an AS version".into())
|
||||
LoaderStream::NotYetLoaded(_) => {
|
||||
return Err(INSUFFICIENT.into());
|
||||
}
|
||||
LoaderStream::Swf(movie, _) => {
|
||||
let version = if movie.is_action_script_3() { 3 } else { 2 };
|
||||
|
@ -74,9 +82,12 @@ pub fn application_domain<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => {
|
||||
LoaderStream::NotYetLoaded(_) => {
|
||||
return Ok(DomainObject::from_domain(activation, activation.domain())?.into());
|
||||
}
|
||||
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
|
||||
/// streaming loads yet. When we do, we'll need another property for this.
|
||||
pub fn bytes_total<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => return Ok(activation.context.swf.compressed_len().into()),
|
||||
LoaderStream::NotYetLoaded(swf) => return Ok(swf.compressed_len().into()),
|
||||
LoaderStream::Swf(movie, _) => {
|
||||
return Ok(movie.compressed_len().into());
|
||||
}
|
||||
|
@ -124,9 +138,17 @@ pub fn content<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
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) => {
|
||||
return Ok(root.object2());
|
||||
}
|
||||
|
@ -144,9 +166,12 @@ pub fn content_type<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => return Ok(Value::Null),
|
||||
LoaderStream::NotYetLoaded(_) => return Ok(Value::Null),
|
||||
LoaderStream::Swf(_, _) => {
|
||||
return Ok("application/x-shockwave-flash".into());
|
||||
}
|
||||
|
@ -164,9 +189,12 @@ pub fn frame_rate<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => {
|
||||
LoaderStream::NotYetLoaded(_) => {
|
||||
return Err("Error: The stage's loader info does not have a frame rate".into())
|
||||
}
|
||||
LoaderStream::Swf(root, _) => {
|
||||
|
@ -186,9 +214,12 @@ pub fn height<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => {
|
||||
LoaderStream::NotYetLoaded(_) => {
|
||||
return Err("Error: The stage's loader info does not have a height".into())
|
||||
}
|
||||
LoaderStream::Swf(root, _) => {
|
||||
|
@ -217,9 +248,12 @@ pub fn swf_version<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => {
|
||||
LoaderStream::NotYetLoaded(_) => {
|
||||
return Err("Error: The stage's loader info does not have a SWF version".into())
|
||||
}
|
||||
LoaderStream::Swf(root, _) => {
|
||||
|
@ -239,14 +273,19 @@ pub fn url<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => activation.context.swf,
|
||||
LoaderStream::NotYetLoaded(swf) => swf,
|
||||
LoaderStream::Swf(root, _) => root,
|
||||
};
|
||||
|
||||
let url = root.url().unwrap_or("");
|
||||
return Ok(AvmString::new_utf8(activation.context.gc_context, url).into());
|
||||
let url = root.url().map_or(Value::Null, |url| {
|
||||
AvmString::new_utf8(activation.context.gc_context, url).into()
|
||||
});
|
||||
return Ok(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,9 +299,12 @@ pub fn width<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => {
|
||||
LoaderStream::NotYetLoaded(_) => {
|
||||
return Err("Error: The stage's loader info does not have a width".into())
|
||||
}
|
||||
LoaderStream::Swf(root, _) => {
|
||||
|
@ -282,12 +324,19 @@ pub fn bytes<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => activation.context.swf,
|
||||
LoaderStream::NotYetLoaded(swf) => swf,
|
||||
LoaderStream::Swf(root, _) => root,
|
||||
};
|
||||
|
||||
if root.data().is_empty() {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
|
||||
let ba_class = activation.context.avm2.classes().bytearray;
|
||||
|
||||
let ba = ba_class.construct(activation, &[])?;
|
||||
|
@ -325,6 +374,19 @@ pub fn bytes<'gc>(
|
|||
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
|
||||
pub fn loader_url<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
|
@ -332,9 +394,12 @@ pub fn loader_url<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => activation.context.swf,
|
||||
LoaderStream::NotYetLoaded(swf) => swf,
|
||||
LoaderStream::Swf(root, _) => root,
|
||||
};
|
||||
|
||||
|
@ -353,9 +418,12 @@ pub fn parameters<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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 {
|
||||
LoaderStream::Stage => activation.context.swf,
|
||||
LoaderStream::NotYetLoaded(_) => activation.context.swf,
|
||||
LoaderStream::Swf(root, _) => root,
|
||||
};
|
||||
|
||||
|
@ -383,6 +451,18 @@ pub fn parameters<'gc>(
|
|||
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.
|
||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||
let class = Class::new(
|
||||
|
@ -421,8 +501,10 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
|||
("url", Some(url), None),
|
||||
("width", Some(width), None),
|
||||
("bytes", Some(bytes), None),
|
||||
("loader", Some(loader), None),
|
||||
("loaderURL", Some(loader_url), None),
|
||||
("parameters", Some(parameters), None),
|
||||
("sharedEvents", Some(shared_events), None),
|
||||
];
|
||||
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
package flash.system {
|
||||
|
||||
import flash.display.DisplayObjectContainer;
|
||||
import flash.system.ApplicationDomain;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ include "flash/display/InterpolationMethod.as"
|
|||
include "flash/display/JointStyle.as"
|
||||
include "flash/display/JPEGEncoderOptions.as"
|
||||
include "flash/display/JPEGXREncoderOptions.as"
|
||||
include "flash/display/Loader.as"
|
||||
include "flash/display/LineScaleMode.as"
|
||||
include "flash/display/NativeMenu.as"
|
||||
include "flash/display/NativeMenuItem.as"
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
// `Object.as` must come first since the other classes depend on it.
|
||||
include "Object.as"
|
||||
|
||||
// List is ordered alphabetically.
|
||||
// List is ordered alphabetically, except where superclasses
|
||||
// are listed before subclasses
|
||||
include "Array.as"
|
||||
include "Boolean.as"
|
||||
include "flash/display/DisplayObject.as"
|
||||
include "flash/display/DisplayObjectContainer.as"
|
||||
include "flash/display/InteractiveObject.as"
|
||||
include "flash/display/DisplayObjectContainer.as"
|
||||
|
||||
include "flash/display/LoaderInfo.as"
|
||||
include "flash/events/EventDispatcher.as"
|
||||
include "flash/system/ApplicationDomain.as"
|
||||
include "Number.as"
|
||||
|
|
|
@ -18,7 +18,6 @@ use crate::avm2::Namespace;
|
|||
use crate::avm2::QName;
|
||||
use crate::backend::audio::{SoundHandle, SoundInstanceHandle};
|
||||
use crate::bitmap::bitmap_data::BitmapData;
|
||||
use crate::context::UpdateContext;
|
||||
use crate::display_object::DisplayObject;
|
||||
use crate::html::TextFormat;
|
||||
use crate::string::AvmString;
|
||||
|
@ -903,6 +902,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
None
|
||||
}
|
||||
|
||||
fn as_loader_info_object(&self) -> Option<&LoaderInfoObject<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_array_object(&self) -> Option<ArrayObject<'gc>> {
|
||||
None
|
||||
}
|
||||
|
@ -1011,13 +1014,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
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.
|
||||
fn as_sound(self) -> Option<SoundHandle> {
|
||||
None
|
||||
|
|
|
@ -26,7 +26,14 @@ pub fn loaderinfo_allocator<'gc>(
|
|||
LoaderInfoObjectData {
|
||||
base,
|
||||
loaded_stream: None,
|
||||
init_fired: false,
|
||||
loader: None,
|
||||
events_fired: false,
|
||||
shared_events: activation
|
||||
.context
|
||||
.avm2
|
||||
.classes()
|
||||
.eventdispatcher
|
||||
.construct(activation, &[])?,
|
||||
},
|
||||
))
|
||||
.into())
|
||||
|
@ -36,12 +43,10 @@ pub fn loaderinfo_allocator<'gc>(
|
|||
#[derive(Collect, Debug, Clone)]
|
||||
#[collect(no_drop)]
|
||||
pub enum LoaderStream<'gc> {
|
||||
/// The current stage.
|
||||
///
|
||||
/// While it makes no sense to actually retrieve loader info properties off
|
||||
/// the stage, it's possible to do so. Some properties yield the
|
||||
/// not-yet-loaded error while others are pulled from the root SWF.
|
||||
Stage,
|
||||
NotYetLoaded(Arc<SwfMovie>),
|
||||
|
||||
/// A loaded SWF movie.
|
||||
///
|
||||
|
@ -64,8 +69,15 @@ pub struct LoaderInfoObjectData<'gc> {
|
|||
/// The loaded stream that this gets it's info from.
|
||||
loaded_stream: Option<LoaderStream<'gc>>,
|
||||
|
||||
/// Whether or not we've fired an 'init' event
|
||||
init_fired: bool,
|
||||
loader: Option<Object<'gc>>,
|
||||
|
||||
/// 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> {
|
||||
|
@ -74,6 +86,7 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
movie: Arc<SwfMovie>,
|
||||
root: DisplayObject<'gc>,
|
||||
loader: Option<Object<'gc>>,
|
||||
) -> Result<Object<'gc>, Error> {
|
||||
let class = activation.avm2().classes().loaderinfo;
|
||||
let base = ScriptObjectData::new(class);
|
||||
|
@ -84,7 +97,14 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
LoaderInfoObjectData {
|
||||
base,
|
||||
loaded_stream,
|
||||
init_fired: false,
|
||||
loader,
|
||||
events_fired: false,
|
||||
shared_events: activation
|
||||
.context
|
||||
.avm2
|
||||
.classes()
|
||||
.eventdispatcher
|
||||
.construct(activation, &[])?,
|
||||
},
|
||||
))
|
||||
.into();
|
||||
|
@ -96,7 +116,11 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
}
|
||||
|
||||
/// 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 base = ScriptObjectData::new(class);
|
||||
|
||||
|
@ -104,10 +128,15 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
activation.context.gc_context,
|
||||
LoaderInfoObjectData {
|
||||
base,
|
||||
loaded_stream: Some(LoaderStream::Stage),
|
||||
// We never want to fire an "init" event for the special
|
||||
// Stagee loaderInfo
|
||||
init_fired: true,
|
||||
loaded_stream: Some(LoaderStream::NotYetLoaded(movie)),
|
||||
loader,
|
||||
events_fired: false,
|
||||
shared_events: activation
|
||||
.context
|
||||
.avm2
|
||||
.classes()
|
||||
.eventdispatcher
|
||||
.construct(activation, &[])?,
|
||||
},
|
||||
))
|
||||
.into();
|
||||
|
@ -117,6 +146,55 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
|
||||
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> {
|
||||
|
@ -136,28 +214,7 @@ impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> {
|
|||
Ok(Value::Object((*self).into()))
|
||||
}
|
||||
|
||||
fn loader_stream_init(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||
if !self.0.read().init_fired {
|
||||
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
|
||||
}
|
||||
fn as_loader_info_object(&self) -> Option<&LoaderInfoObject<'gc>> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ mod container;
|
|||
mod edit_text;
|
||||
mod graphic;
|
||||
mod interactive;
|
||||
mod loader_display;
|
||||
mod morph_shape;
|
||||
mod movie_clip;
|
||||
mod stage;
|
||||
|
@ -44,6 +45,7 @@ pub use bitmap::Bitmap;
|
|||
pub use edit_text::{AutoSizeMode, EditText, TextSelection};
|
||||
pub use graphic::Graphic;
|
||||
pub use interactive::{InteractiveObject, TInteractiveObject};
|
||||
pub use loader_display::LoaderDisplay;
|
||||
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
||||
pub use movie_clip::{MovieClip, Scene};
|
||||
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>),
|
||||
Text(Text<'gc>),
|
||||
Video(Video<'gc>),
|
||||
LoaderDisplay(LoaderDisplay<'gc>)
|
||||
}
|
||||
)]
|
||||
pub trait TDisplayObject<'gc>:
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use crate::avm2::{Avm2, EventObject as Avm2EventObject, Value as Avm2Value};
|
||||
use crate::context::{RenderContext, UpdateContext};
|
||||
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::stage::Stage;
|
||||
use crate::display_object::{Depth, DisplayObject, TDisplayObject};
|
||||
|
@ -142,6 +143,7 @@ bitflags! {
|
|||
Stage(Stage<'gc>),
|
||||
Avm1Button(Avm1Button<'gc>),
|
||||
MovieClip(MovieClip<'gc>),
|
||||
LoaderDisplay(LoaderDisplay<'gc>),
|
||||
}
|
||||
)]
|
||||
pub trait TDisplayObjectContainer<'gc>:
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::context::UpdateContext;
|
|||
use crate::display_object::avm1_button::Avm1Button;
|
||||
use crate::display_object::avm2_button::Avm2Button;
|
||||
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::stage::Stage;
|
||||
use crate::display_object::{
|
||||
|
@ -109,6 +110,7 @@ impl<'gc> Default for InteractiveObjectBase<'gc> {
|
|||
Avm2Button(Avm2Button<'gc>),
|
||||
MovieClip(MovieClip<'gc>),
|
||||
EditText(EditText<'gc>),
|
||||
LoaderDisplay(LoaderDisplay<'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,
|
||||
};
|
||||
use crate::avm2::object::LoaderInfoObject;
|
||||
use crate::avm2::object::LoaderStream;
|
||||
use crate::avm2::Activation as Avm2Activation;
|
||||
use crate::avm2::{
|
||||
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(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
id: CharacterId,
|
||||
|
@ -189,7 +191,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
base: Default::default(),
|
||||
static_data: Gc::allocate(
|
||||
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,
|
||||
current_frame: 0,
|
||||
|
@ -218,20 +220,37 @@ impl<'gc> MovieClip<'gc> {
|
|||
))
|
||||
}
|
||||
|
||||
/// Construct a movie clip that represents an entire movie.
|
||||
pub fn from_movie(context: &mut UpdateContext<'_, 'gc, '_>, movie: Arc<SwfMovie>) -> Self {
|
||||
/// Construct a movie clip that represents the root movie
|
||||
/// for the entire `Player`.
|
||||
pub fn player_root_movie(
|
||||
activation: &mut Avm2Activation<'_, 'gc, '_>,
|
||||
movie: Arc<SwfMovie>,
|
||||
) -> Self {
|
||||
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(
|
||||
context.gc_context,
|
||||
activation.context.gc_context,
|
||||
MovieClipData {
|
||||
base: Default::default(),
|
||||
static_data: Gc::allocate(
|
||||
context.gc_context,
|
||||
activation.context.gc_context,
|
||||
MovieClipStatic::with_data(
|
||||
0,
|
||||
movie.clone().into(),
|
||||
num_frames,
|
||||
context.gc_context,
|
||||
loader_info,
|
||||
activation.context.gc_context,
|
||||
),
|
||||
),
|
||||
tag_stream_pos: 0,
|
||||
|
@ -259,8 +278,21 @@ impl<'gc> MovieClip<'gc> {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -276,15 +308,27 @@ impl<'gc> MovieClip<'gc> {
|
|||
&mut self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
movie: Option<Arc<SwfMovie>>,
|
||||
loader_info: Option<LoaderInfoObject<'gc>>,
|
||||
) {
|
||||
let mut mc = self.0.write(context.gc_context);
|
||||
let is_swf = movie.is_some();
|
||||
let movie = movie.unwrap_or_else(|| Arc::new(SwfMovie::empty(mc.movie().version())));
|
||||
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.static_data = Gc::allocate(
|
||||
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.flags = MovieClipFlags::PLAYING;
|
||||
|
@ -293,30 +337,6 @@ impl<'gc> MovieClip<'gc> {
|
|||
mc.audio_stream = None;
|
||||
mc.container = ChildContainer::new();
|
||||
drop(mc);
|
||||
|
||||
self.set_loader_info(context, movie);
|
||||
}
|
||||
|
||||
fn set_loader_info(&self, context: &mut UpdateContext<'_, 'gc, '_>, movie: Arc<SwfMovie>) {
|
||||
if movie.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, '_>) {
|
||||
|
@ -1855,7 +1875,6 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
|
|||
if context.is_action_script_3() {
|
||||
let needs_construction = if matches!(self.object2(), Avm2Value::Undefined) {
|
||||
self.allocate_as_avm2_object(context, (*self).into());
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -1988,8 +2007,12 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
|
|||
// keeps track if an "init" event has already been fired,
|
||||
// so this becomes a no-op after the event has been fired.
|
||||
if self.0.read().initialized() {
|
||||
if let Some(loader_info) = self.loader_info() {
|
||||
loader_info.loader_stream_init(context);
|
||||
if let Some(loader_info) = self
|
||||
.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>> {
|
||||
*self.0.read().static_data.loader_info.read()
|
||||
self.0.read().static_data.loader_info
|
||||
}
|
||||
|
||||
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
|
||||
/// (either the root SWF initially loaded by the player,
|
||||
/// 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> {
|
||||
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(
|
||||
id: CharacterId,
|
||||
swf: SwfSlice,
|
||||
total_frames: FrameNumber,
|
||||
loader_info: Option<Avm2Object<'gc>>,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
@ -3410,7 +3438,7 @@ impl<'gc> MovieClipStatic<'gc> {
|
|||
audio_stream_info: None,
|
||||
audio_stream_handle: None,
|
||||
exported_name: GcCell::allocate(gc_context, None),
|
||||
loader_info: GcCell::allocate(gc_context, None),
|
||||
loader_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! Root stage impl
|
||||
|
||||
use crate::avm1::Object as Avm1Object;
|
||||
use crate::avm2::object::LoaderInfoObject;
|
||||
use crate::avm2::object::TObject;
|
||||
use crate::avm2::{
|
||||
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);
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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) => {
|
||||
let mut write = self.0.write(activation.context.gc_context);
|
||||
write.avm2_object = avm2_stage.into();
|
||||
match LoaderInfoObject::from_stage(&mut activation) {
|
||||
Ok(loader_info) => write.loader_info = loader_info,
|
||||
Err(e) => log::error!("Unable to set AVM2 Stage loaderInfo: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Unable to construct AVM2 Stage: {}", e),
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@ use crate::avm1::{Avm1, Object, TObject, Value};
|
|||
use crate::avm2::bytearray::ByteArrayStorage;
|
||||
use crate::avm2::object::ByteArrayObject;
|
||||
use crate::avm2::object::EventObject as Avm2EventObject;
|
||||
use crate::avm2::object::LoaderStream;
|
||||
use crate::avm2::object::TObject as _;
|
||||
use crate::avm2::Multiname;
|
||||
use crate::avm2::Namespace;
|
||||
use crate::avm2::{
|
||||
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, Object as Avm2Object, QName,
|
||||
|
@ -119,7 +121,7 @@ pub enum Error {
|
|||
#[error("Could not fetch: {0}")]
|
||||
FetchError(String),
|
||||
|
||||
#[error("Invalid SWF")]
|
||||
#[error("Invalid SWF: {0}")]
|
||||
InvalidSwf(#[from] crate::tag_utils::Error),
|
||||
|
||||
#[error("Unexpected content of type {1}, expected {0}")]
|
||||
|
@ -209,12 +211,12 @@ impl<'gc> LoadManager<'gc> {
|
|||
target_clip: DisplayObject<'gc>,
|
||||
request: Request,
|
||||
loader_url: Option<String>,
|
||||
target_broadcaster: Option<Object<'gc>>,
|
||||
event_handler: Option<MovieLoaderEventHandler<'gc>>,
|
||||
) -> OwnedFuture<(), Error> {
|
||||
let loader = Loader::Movie {
|
||||
self_handle: None,
|
||||
target_clip,
|
||||
target_broadcaster,
|
||||
event_handler,
|
||||
loader_status: LoaderStatus::Pending,
|
||||
};
|
||||
let handle = self.add_loader(loader);
|
||||
|
@ -314,6 +316,13 @@ pub enum LoaderStatus {
|
|||
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.
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
|
@ -336,7 +345,7 @@ pub enum Loader<'gc> {
|
|||
|
||||
/// Event broadcaster (typically a `MovieClipLoader`) to fire events
|
||||
/// into.
|
||||
target_broadcaster: Option<Object<'gc>>,
|
||||
event_handler: Option<MovieLoaderEventHandler<'gc>>,
|
||||
|
||||
/// Indicates the completion status of this loader.
|
||||
///
|
||||
|
@ -458,78 +467,156 @@ impl<'gc> Loader<'gc> {
|
|||
|
||||
if let Some(mut mc) = clip.as_movie_clip() {
|
||||
mc.unload(uc);
|
||||
mc.replace_with_movie(uc, None);
|
||||
mc.replace_with_movie(uc, None, None);
|
||||
}
|
||||
|
||||
Loader::movie_loader_start(handle, uc)
|
||||
})?;
|
||||
|
||||
if let Ok(response) = fetch.await {
|
||||
let sniffed_type = ContentType::sniff(&response.body);
|
||||
let mut length = response.body.len();
|
||||
match fetch.await {
|
||||
Ok(response) => {
|
||||
let sniffed_type = ContentType::sniff(&response.body);
|
||||
let mut length = response.body.len();
|
||||
|
||||
if replacing_root_movie {
|
||||
sniffed_type.expect(ContentType::Swf)?;
|
||||
if replacing_root_movie {
|
||||
sniffed_type.expect(ContentType::Swf)?;
|
||||
|
||||
let movie =
|
||||
SwfMovie::from_data(&response.body, Some(response.url), loader_url)?;
|
||||
player.lock().unwrap().set_root_movie(movie);
|
||||
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;
|
||||
}
|
||||
let movie =
|
||||
SwfMovie::from_data(&response.body, Some(response.url), loader_url)?;
|
||||
player.lock().unwrap().set_root_movie(movie);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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(())
|
||||
})?; //TODO: content sniffing errors need to be reported somehow
|
||||
} else {
|
||||
player
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update(|uc| -> Result<(), Error> { Loader::movie_loader_error(handle, uc) })?;
|
||||
// FIXME - properly set 'bytesLoaded' and 'bytesTotal' on our `LoaderInfo` object
|
||||
// to match the values in the event.
|
||||
if let Some(MovieLoaderEventHandler::Avm2LoaderInfo(_)) = event_handler {
|
||||
// Flash always fires an initial 'progress' event with
|
||||
// bytesLoaded=0 and bytesTotal set to the proper value.
|
||||
// This only seems to happen for an AVM2 event handler
|
||||
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(())
|
||||
|
@ -823,23 +910,37 @@ impl<'gc> Loader<'gc> {
|
|||
|
||||
let me = me.unwrap();
|
||||
|
||||
let (clip, broadcaster) = match me {
|
||||
let (clip, event_handler) = match me {
|
||||
Loader::Movie {
|
||||
target_clip,
|
||||
target_broadcaster,
|
||||
event_handler,
|
||||
..
|
||||
} => (*target_clip, *target_broadcaster),
|
||||
} => (*target_clip, *event_handler),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(broadcaster) = broadcaster {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"broadcastMessage".into(),
|
||||
&["onLoadStart".into(), clip.object()],
|
||||
);
|
||||
match event_handler {
|
||||
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"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(())
|
||||
|
@ -859,28 +960,52 @@ impl<'gc> Loader<'gc> {
|
|||
|
||||
let me = me.unwrap();
|
||||
|
||||
let (clip, broadcaster) = match me {
|
||||
let (clip, event_handler) = match me {
|
||||
Loader::Movie {
|
||||
target_clip,
|
||||
target_broadcaster,
|
||||
event_handler,
|
||||
..
|
||||
} => (*target_clip, *target_broadcaster),
|
||||
} => (*target_clip, *event_handler),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(broadcaster) = broadcaster {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"broadcastMessage".into(),
|
||||
&[
|
||||
"onLoadProgress".into(),
|
||||
clip.object(),
|
||||
cur_len.into(),
|
||||
total_len.into(),
|
||||
],
|
||||
);
|
||||
match event_handler {
|
||||
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"broadcastMessage".into(),
|
||||
&[
|
||||
"onLoadProgress".into(),
|
||||
clip.object(),
|
||||
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(())
|
||||
|
@ -891,25 +1016,39 @@ impl<'gc> Loader<'gc> {
|
|||
handle: Index,
|
||||
uc: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> 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 {
|
||||
target_clip,
|
||||
target_broadcaster,
|
||||
event_handler,
|
||||
..
|
||||
}) => (*target_clip, *target_broadcaster),
|
||||
}) => (*target_clip, *event_handler),
|
||||
None => return Err(Error::Cancelled),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(broadcaster) = broadcaster {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"broadcastMessage".into(),
|
||||
// TODO: Pass an actual httpStatus argument instead of 0.
|
||||
&["onLoadComplete".into(), clip.object(), 0.into()],
|
||||
);
|
||||
match event_handler {
|
||||
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"broadcastMessage".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()
|
||||
|
@ -930,28 +1069,54 @@ impl<'gc> Loader<'gc> {
|
|||
//error types we can actually inspect.
|
||||
//This also can get errors from decoding an invalid SWF file,
|
||||
//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 {
|
||||
target_clip,
|
||||
target_broadcaster,
|
||||
event_handler,
|
||||
..
|
||||
}) => (*target_clip, *target_broadcaster),
|
||||
}) => (*target_clip, *event_handler),
|
||||
None => return Err(Error::Cancelled),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(broadcaster) = broadcaster {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"broadcastMessage".into(),
|
||||
&[
|
||||
"onLoadError".into(),
|
||||
clip.object(),
|
||||
"LoadNeverCompleted".into(),
|
||||
],
|
||||
);
|
||||
match event_handler {
|
||||
Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) => {
|
||||
Avm1::run_stack_frame_for_method(
|
||||
clip,
|
||||
broadcaster,
|
||||
uc,
|
||||
"broadcastMessage".into(),
|
||||
&[
|
||||
"onLoadError".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()
|
||||
|
@ -968,13 +1133,13 @@ impl<'gc> Loader<'gc> {
|
|||
///
|
||||
/// Used to fire listener events on clips and terminate completed loaders.
|
||||
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 {
|
||||
target_clip,
|
||||
target_broadcaster,
|
||||
event_handler,
|
||||
loader_status,
|
||||
..
|
||||
} => (*target_clip, *target_broadcaster, *loader_status),
|
||||
} => (*target_clip, *event_handler, *loader_status),
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
|
@ -982,7 +1147,8 @@ impl<'gc> Loader<'gc> {
|
|||
LoaderStatus::Pending => false,
|
||||
LoaderStatus::Failed => true,
|
||||
LoaderStatus::Succeeded => {
|
||||
if let Some(broadcaster) = broadcaster {
|
||||
// AVM2 is handled separately
|
||||
if let Some(MovieLoaderEventHandler::Avm1Broadcast(broadcaster)) = event_handler {
|
||||
queue.queue_actions(
|
||||
clip,
|
||||
ActionType::Method {
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::avm1::globals::system::SystemProperties;
|
|||
use crate::avm1::object::Object;
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::{Avm1, ScriptObject, TObject, Value};
|
||||
use crate::avm2::object::LoaderInfoObject;
|
||||
use crate::avm2::{
|
||||
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, EventObject as Avm2EventObject,
|
||||
};
|
||||
|
@ -290,15 +291,29 @@ impl Player {
|
|||
let global_domain = activation.avm2().global_domain();
|
||||
let domain = Avm2Domain::movie_domain(&mut activation, global_domain);
|
||||
|
||||
drop(activation);
|
||||
|
||||
context
|
||||
activation
|
||||
.context
|
||||
.library
|
||||
.library_for_movie_mut(context.swf.clone())
|
||||
.library_for_movie_mut(activation.context.swf.clone())
|
||||
.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);
|
||||
let flashvars = if !context.swf.parameters().is_empty() {
|
||||
|
@ -1936,7 +1951,7 @@ impl PlayerBuilder {
|
|||
let mut player_lock = player.lock().unwrap();
|
||||
player_lock.mutate_with_update_context(|context| {
|
||||
// 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);
|
||||
context.stage.replace_at_depth(context, fake_root.into(), 0);
|
||||
Avm2::load_player_globals(context).expect("Unable to load AVM2 globals");
|
||||
|
|
|
@ -349,6 +349,7 @@ swf_tests! {
|
|||
(as3_lazyinit, "avm2/lazyinit", 1),
|
||||
(as3_lessequals, "avm2/lessequals", 1),
|
||||
(as3_lessthan, "avm2/lessthan", 1),
|
||||
(as3_loader_events, "avm2/loader_events", 3, img = true),
|
||||
(as3_loaderinfo_events, "avm2/loaderinfo_events", 2),
|
||||
(as3_loaderinfo_properties, "avm2/loaderinfo_properties", 2),
|
||||
(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