2020-01-10 23:28:49 +00:00
|
|
|
//! Management of async loaders
|
|
|
|
|
2020-07-02 21:37:18 +00:00
|
|
|
use crate::avm1::activation::{Activation, ActivationIdentifier};
|
2020-07-13 10:09:30 +00:00
|
|
|
use crate::avm1::{AvmString, Object, TObject, Value};
|
2020-01-18 04:11:09 +00:00
|
|
|
use crate::backend::navigator::OwnedFuture;
|
2020-07-27 23:19:05 +00:00
|
|
|
use crate::context::{ActionQueue, ActionType};
|
2020-01-10 23:28:49 +00:00
|
|
|
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
|
2020-01-16 00:25:50 +00:00
|
|
|
use crate::player::{Player, NEWEST_PLAYER_VERSION};
|
2020-01-10 23:28:49 +00:00
|
|
|
use crate::tag_utils::SwfMovie;
|
2020-01-18 03:58:48 +00:00
|
|
|
use crate::xml::XMLNode;
|
2020-07-12 22:16:48 +00:00
|
|
|
use gc_arena::{Collect, CollectionContext, MutationContext};
|
2020-01-10 23:28:49 +00:00
|
|
|
use generational_arena::{Arena, Index};
|
2020-06-17 22:35:36 +00:00
|
|
|
use std::string::FromUtf8Error;
|
2020-01-10 23:28:49 +00:00
|
|
|
use std::sync::{Arc, Mutex, Weak};
|
2020-06-18 08:33:58 +00:00
|
|
|
use thiserror::Error;
|
2020-01-10 23:28:49 +00:00
|
|
|
use url::form_urlencoded;
|
|
|
|
|
|
|
|
pub type Handle = Index;
|
|
|
|
|
2020-06-18 08:33:58 +00:00
|
|
|
#[derive(Error, Debug)]
|
2020-06-18 08:36:04 +00:00
|
|
|
pub enum Error {
|
2020-06-18 08:33:58 +00:00
|
|
|
#[error("Load cancelled")]
|
2020-06-17 22:35:36 +00:00
|
|
|
Cancelled,
|
2020-06-18 08:33:58 +00:00
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
#[error("Non-root-movie loader spawned as root movie loader")]
|
|
|
|
NotRootMovieLoader,
|
|
|
|
|
2020-06-18 08:33:58 +00:00
|
|
|
#[error("Non-movie loader spawned as movie loader")]
|
2020-06-17 22:35:36 +00:00
|
|
|
NotMovieLoader,
|
2020-06-18 08:33:58 +00:00
|
|
|
|
|
|
|
#[error("Non-form loader spawned as form loader")]
|
2020-06-17 22:35:36 +00:00
|
|
|
NotFormLoader,
|
2020-06-18 08:33:58 +00:00
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
#[error("Non-load vars loader spawned as load vars loader")]
|
|
|
|
NotLoadVarsLoader,
|
|
|
|
|
2020-06-18 08:33:58 +00:00
|
|
|
#[error("Non-XML loader spawned as XML loader")]
|
2020-06-17 22:35:36 +00:00
|
|
|
NotXmlLoader,
|
|
|
|
|
2020-07-23 23:00:05 +00:00
|
|
|
#[error("Could not fetch movie {0}")]
|
|
|
|
FetchError(String),
|
|
|
|
|
2020-06-18 08:33:58 +00:00
|
|
|
#[error("Invalid SWF")]
|
|
|
|
InvalidSwf(#[from] crate::tag_utils::Error),
|
|
|
|
|
|
|
|
#[error("Invalid XML encoding")]
|
|
|
|
InvalidXmlEncoding(#[from] FromUtf8Error),
|
|
|
|
|
|
|
|
#[error("Network error")]
|
|
|
|
NetworkError(#[from] std::io::Error),
|
|
|
|
|
2020-06-20 23:02:45 +00:00
|
|
|
// TODO: We can't support lifetimes on this error object yet (or we'll need some backends inside
|
|
|
|
// the GC arena). We're losing info here. How do we fix that?
|
2020-06-18 08:33:58 +00:00
|
|
|
#[error("Error running avm1 script: {0}")]
|
2020-06-20 23:02:45 +00:00
|
|
|
Avm1Error(String),
|
|
|
|
}
|
|
|
|
|
2020-07-27 23:19:05 +00:00
|
|
|
pub type FormLoadHandler<'gc> =
|
2020-07-28 01:27:02 +00:00
|
|
|
fn(&mut Activation<'_, 'gc, '_>, Object<'gc>, data: &[u8]) -> Result<(), Error>;
|
2020-07-23 01:22:23 +00:00
|
|
|
|
2020-07-28 01:27:02 +00:00
|
|
|
pub type FormErrorHandler<'gc> = fn(&mut Activation<'_, 'gc, '_>, Object<'gc>) -> Result<(), Error>;
|
2020-07-23 01:22:23 +00:00
|
|
|
|
2020-06-20 23:02:45 +00:00
|
|
|
impl From<crate::avm1::error::Error<'_>> for Error {
|
|
|
|
fn from(error: crate::avm1::error::Error<'_>) -> Self {
|
|
|
|
Error::Avm1Error(error.to_string())
|
|
|
|
}
|
2020-06-17 22:35:36 +00:00
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
|
|
|
|
/// Holds all in-progress loads for the player.
|
|
|
|
pub struct LoadManager<'gc>(Arena<Loader<'gc>>);
|
|
|
|
|
|
|
|
unsafe impl<'gc> Collect for LoadManager<'gc> {
|
|
|
|
fn trace(&self, cc: CollectionContext) {
|
|
|
|
for (_, loader) in self.0.iter() {
|
|
|
|
loader.trace(cc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> LoadManager<'gc> {
|
|
|
|
/// Construct a new `LoadManager`.
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self(Arena::new())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add a new loader to the `LoadManager`.
|
|
|
|
///
|
|
|
|
/// This function returns the loader handle for later inspection. A loader
|
|
|
|
/// handle is valid for as long as the load operation. Once the load
|
|
|
|
/// finishes, the handle will be invalidated (and the underlying loader
|
|
|
|
/// deleted).
|
|
|
|
pub fn add_loader(&mut self, loader: Loader<'gc>) -> Handle {
|
|
|
|
let handle = self.0.insert(loader);
|
|
|
|
self.0
|
|
|
|
.get_mut(handle)
|
|
|
|
.unwrap()
|
|
|
|
.introduce_loader_handle(handle);
|
|
|
|
|
|
|
|
handle
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve a loader by handle.
|
|
|
|
pub fn get_loader(&self, handle: Handle) -> Option<&Loader<'gc>> {
|
|
|
|
self.0.get(handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve a loader by handle for mutation.
|
|
|
|
pub fn get_loader_mut(&mut self, handle: Handle) -> Option<&mut Loader<'gc>> {
|
|
|
|
self.0.get_mut(handle)
|
|
|
|
}
|
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
/// Kick off the root movie load.
|
|
|
|
///
|
|
|
|
/// The root movie is special because it determines a few bits of player
|
|
|
|
/// state, such as the size of the stage and the current frame rate. Ergo,
|
|
|
|
/// this method should only be called once, by the player that is trying to
|
|
|
|
/// kick off it's root movie load.
|
|
|
|
pub fn load_root_movie(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
|
|
|
url: String,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
|
|
|
let loader = Loader::RootMovie { self_handle: None };
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
|
|
|
loader.introduce_loader_handle(handle);
|
|
|
|
|
|
|
|
loader.root_movie_loader(player, fetch, url)
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
/// Kick off a movie clip load.
|
|
|
|
///
|
|
|
|
/// Returns the loader's async process, which you will need to spawn.
|
|
|
|
pub fn load_movie_into_clip(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
target_clip: DisplayObject<'gc>,
|
2020-06-18 08:36:04 +00:00
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
2020-07-22 03:57:43 +00:00
|
|
|
url: String,
|
2020-01-16 00:25:50 +00:00
|
|
|
target_broadcaster: Option<Object<'gc>>,
|
2020-06-18 08:36:04 +00:00
|
|
|
) -> OwnedFuture<(), Error> {
|
2020-01-10 23:28:49 +00:00
|
|
|
let loader = Loader::Movie {
|
|
|
|
self_handle: None,
|
|
|
|
target_clip,
|
2020-01-16 00:25:50 +00:00
|
|
|
target_broadcaster,
|
2020-02-26 23:03:45 +00:00
|
|
|
load_complete: false,
|
2020-01-10 23:28:49 +00:00
|
|
|
};
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
|
|
|
loader.introduce_loader_handle(handle);
|
|
|
|
|
2020-07-22 03:57:43 +00:00
|
|
|
loader.movie_loader(player, fetch, url)
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
|
2020-01-16 19:20:07 +00:00
|
|
|
/// Indicates that a movie clip has initialized (ran it's first frame).
|
|
|
|
///
|
|
|
|
/// Interested loaders will be invoked from here.
|
|
|
|
pub fn movie_clip_on_load(
|
|
|
|
&mut self,
|
|
|
|
loaded_clip: DisplayObject<'gc>,
|
2020-01-17 23:24:06 +00:00
|
|
|
clip_object: Option<Object<'gc>>,
|
2020-01-16 19:20:07 +00:00
|
|
|
queue: &mut ActionQueue<'gc>,
|
2020-07-04 09:25:43 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2020-01-16 19:20:07 +00:00
|
|
|
) {
|
|
|
|
let mut invalidated_loaders = vec![];
|
|
|
|
|
|
|
|
for (index, loader) in self.0.iter_mut() {
|
2020-07-04 09:25:43 +00:00
|
|
|
if loader.movie_clip_loaded(loaded_clip, clip_object, queue, gc_context) {
|
2020-01-16 19:20:07 +00:00
|
|
|
invalidated_loaders.push(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for index in invalidated_loaders {
|
|
|
|
self.0.remove(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
/// Kick off a form data load into an AVM1 object.
|
|
|
|
///
|
|
|
|
/// Returns the loader's async process, which you will need to spawn.
|
|
|
|
pub fn load_form_into_object(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
target_object: Object<'gc>,
|
2020-06-18 08:36:04 +00:00
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
2020-01-10 23:28:49 +00:00
|
|
|
let loader = Loader::Form {
|
|
|
|
self_handle: None,
|
|
|
|
target_object,
|
|
|
|
};
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
|
|
|
loader.introduce_loader_handle(handle);
|
|
|
|
|
|
|
|
loader.form_loader(player, fetch)
|
|
|
|
}
|
2020-01-18 03:58:48 +00:00
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
/// Kick off a form data load into an AVM1 object.
|
|
|
|
///
|
|
|
|
/// Returns the loader's async process, which you will need to spawn.
|
|
|
|
pub fn load_form_into_load_vars(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
target_object: Object<'gc>,
|
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
|
|
|
let loader = Loader::LoadVars {
|
|
|
|
self_handle: None,
|
|
|
|
target_object,
|
|
|
|
};
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
|
|
|
loader.introduce_loader_handle(handle);
|
|
|
|
|
|
|
|
loader.load_vars_loader(player, fetch)
|
|
|
|
}
|
|
|
|
|
2020-01-18 03:58:48 +00:00
|
|
|
/// Kick off an XML data load into an XML node.
|
|
|
|
///
|
|
|
|
/// Returns the loader's async process, which you will need to spawn.
|
|
|
|
pub fn load_xml_into_node(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
target_node: XMLNode<'gc>,
|
|
|
|
active_clip: DisplayObject<'gc>,
|
2020-06-18 08:36:04 +00:00
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
2020-01-18 03:58:48 +00:00
|
|
|
let loader = Loader::XML {
|
|
|
|
self_handle: None,
|
|
|
|
active_clip,
|
|
|
|
target_node,
|
|
|
|
};
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
|
|
|
loader.introduce_loader_handle(handle);
|
|
|
|
|
|
|
|
loader.xml_loader(player, fetch)
|
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Default for LoadManager<'gc> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A struct that holds garbage-collected pointers for asynchronous code.
|
|
|
|
pub enum Loader<'gc> {
|
2020-07-23 03:18:30 +00:00
|
|
|
/// Loader that is loading the root movie of a player.
|
|
|
|
RootMovie {
|
|
|
|
/// The handle to refer to this loader instance.
|
|
|
|
self_handle: Option<Handle>,
|
|
|
|
},
|
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
/// Loader that is loading a new movie into a movieclip.
|
|
|
|
Movie {
|
|
|
|
/// The handle to refer to this loader instance.
|
|
|
|
self_handle: Option<Handle>,
|
|
|
|
|
|
|
|
/// The target movie clip to load the movie into.
|
|
|
|
target_clip: DisplayObject<'gc>,
|
2020-01-16 00:25:50 +00:00
|
|
|
|
|
|
|
/// Event broadcaster (typically a `MovieClipLoader`) to fire events
|
|
|
|
/// into.
|
|
|
|
target_broadcaster: Option<Object<'gc>>,
|
2020-02-26 23:03:45 +00:00
|
|
|
|
|
|
|
/// Indicates that the load has completed.
|
|
|
|
///
|
|
|
|
/// This flag exists to prevent a situation in which loading a movie
|
|
|
|
/// into a clip that has not yet fired it's Load event causes the
|
|
|
|
/// loader to be prematurely removed. This flag is only set when either
|
|
|
|
/// the movie has been replaced (and thus Load events can be trusted)
|
|
|
|
/// or an error has occured (in which case we don't care about the
|
|
|
|
/// loader anymore).
|
|
|
|
load_complete: bool,
|
2020-01-10 23:28:49 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/// Loader that is loading form data into an AVM1 object scope.
|
|
|
|
Form {
|
|
|
|
/// The handle to refer to this loader instance.
|
|
|
|
self_handle: Option<Handle>,
|
|
|
|
|
|
|
|
/// The target AVM1 object to load form data into.
|
|
|
|
target_object: Object<'gc>,
|
|
|
|
},
|
2020-01-18 03:58:48 +00:00
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
/// Loader that is loading form data into an AVM1 LoadVars object.
|
|
|
|
LoadVars {
|
|
|
|
/// The handle to refer to this loader instance.
|
|
|
|
self_handle: Option<Handle>,
|
|
|
|
|
|
|
|
/// The target AVM1 object to load form data into.
|
|
|
|
target_object: Object<'gc>,
|
|
|
|
},
|
|
|
|
|
2020-01-18 03:58:48 +00:00
|
|
|
/// Loader that is loading XML data into an XML tree.
|
|
|
|
XML {
|
|
|
|
/// The handle to refer to this loader instance.
|
|
|
|
self_handle: Option<Handle>,
|
|
|
|
|
|
|
|
/// The active movie clip at the time of load invocation.
|
|
|
|
///
|
|
|
|
/// This property is a technicality: Under normal circumstances, it's
|
|
|
|
/// not supposed to be a load factor, and only exists so that the
|
|
|
|
/// runtime can do *something* in really contrived scenarios where we
|
|
|
|
/// actually need an active clip.
|
|
|
|
active_clip: DisplayObject<'gc>,
|
|
|
|
|
|
|
|
/// The target node whose contents will be replaced with the parsed XML.
|
|
|
|
target_node: XMLNode<'gc>,
|
|
|
|
},
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 23:43:27 +00:00
|
|
|
unsafe impl<'gc> Collect for Loader<'gc> {
|
|
|
|
fn trace(&self, cc: CollectionContext) {
|
|
|
|
match self {
|
2020-07-23 03:18:30 +00:00
|
|
|
Loader::RootMovie { .. } => {}
|
2020-01-18 03:58:48 +00:00
|
|
|
Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
target_clip.trace(cc);
|
|
|
|
target_broadcaster.trace(cc);
|
|
|
|
}
|
2020-01-10 23:43:27 +00:00
|
|
|
Loader::Form { target_object, .. } => target_object.trace(cc),
|
2020-07-23 01:22:23 +00:00
|
|
|
Loader::LoadVars { target_object, .. } => target_object.trace(cc),
|
2020-01-18 03:58:48 +00:00
|
|
|
Loader::XML { target_node, .. } => target_node.trace(cc),
|
2020-01-10 23:43:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
impl<'gc> Loader<'gc> {
|
|
|
|
/// Set the loader handle for this loader.
|
|
|
|
///
|
|
|
|
/// An active loader handle is required before asynchronous loader code can
|
|
|
|
/// run.
|
|
|
|
pub fn introduce_loader_handle(&mut self, handle: Handle) {
|
|
|
|
match self {
|
2020-07-23 03:18:30 +00:00
|
|
|
Loader::RootMovie { self_handle, .. } => *self_handle = Some(handle),
|
2020-01-10 23:28:49 +00:00
|
|
|
Loader::Movie { self_handle, .. } => *self_handle = Some(handle),
|
|
|
|
Loader::Form { self_handle, .. } => *self_handle = Some(handle),
|
2020-07-23 01:22:23 +00:00
|
|
|
Loader::LoadVars { self_handle, .. } => *self_handle = Some(handle),
|
2020-01-18 03:58:48 +00:00
|
|
|
Loader::XML { self_handle, .. } => *self_handle = Some(handle),
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
/// Construct a future for the root movie loader.
|
|
|
|
pub fn root_movie_loader(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
2020-07-23 22:50:00 +00:00
|
|
|
mut url: String,
|
2020-07-23 03:18:30 +00:00
|
|
|
) -> OwnedFuture<(), Error> {
|
|
|
|
let _handle = match self {
|
|
|
|
Loader::RootMovie { self_handle, .. } => {
|
|
|
|
self_handle.expect("Loader not self-introduced")
|
|
|
|
}
|
|
|
|
_ => return Box::pin(async { Err(Error::NotMovieLoader) }),
|
|
|
|
};
|
|
|
|
|
|
|
|
let player = player
|
|
|
|
.upgrade()
|
|
|
|
.expect("Could not upgrade weak reference to player");
|
|
|
|
|
|
|
|
Box::pin(async move {
|
2020-07-23 22:50:00 +00:00
|
|
|
player.lock().expect("Could not lock player!!").update(
|
2020-07-28 00:57:42 +00:00
|
|
|
|_avm2, uc| -> Result<(), Error> {
|
2020-07-23 22:50:00 +00:00
|
|
|
url = uc.navigator.resolve_relative_url(&url).into_owned();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
let data = (fetch.await)
|
|
|
|
.and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?)));
|
|
|
|
|
|
|
|
if let Ok((_length, movie)) = data {
|
|
|
|
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
} else {
|
2020-07-23 23:00:05 +00:00
|
|
|
Err(Error::FetchError(url))
|
2020-07-23 03:18:30 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
/// Construct a future for the given movie loader.
|
|
|
|
///
|
|
|
|
/// The given future should be passed immediately to an executor; it will
|
|
|
|
/// take responsibility for running the loader to completion.
|
|
|
|
///
|
|
|
|
/// If the loader is not a movie then the returned future will yield an
|
|
|
|
/// error immediately once spawned.
|
|
|
|
pub fn movie_loader(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2020-06-18 08:36:04 +00:00
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
2020-07-23 22:50:00 +00:00
|
|
|
mut url: String,
|
2020-06-18 08:36:04 +00:00
|
|
|
) -> OwnedFuture<(), Error> {
|
2020-01-10 23:28:49 +00:00
|
|
|
let handle = match self {
|
|
|
|
Loader::Movie { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
|
2020-06-18 08:36:04 +00:00
|
|
|
_ => return Box::pin(async { Err(Error::NotMovieLoader) }),
|
2020-01-10 23:28:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let player = player
|
|
|
|
.upgrade()
|
|
|
|
.expect("Could not upgrade weak reference to player");
|
|
|
|
|
|
|
|
Box::pin(async move {
|
2020-01-16 00:25:50 +00:00
|
|
|
player.lock().expect("Could not lock player!!").update(
|
2020-07-28 00:57:42 +00:00
|
|
|
|_avm2, uc| -> Result<(), Error> {
|
2020-07-23 22:50:00 +00:00
|
|
|
url = uc.navigator.resolve_relative_url(&url).into_owned();
|
|
|
|
|
2020-01-16 00:25:50 +00:00
|
|
|
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
|
|
|
|
Some(Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
}) => (*target_clip, *target_broadcaster),
|
2020-06-18 08:36:04 +00:00
|
|
|
None => return Err(Error::Cancelled),
|
2020-01-16 00:25:50 +00:00
|
|
|
_ => unreachable!(),
|
2020-01-10 23:28:49 +00:00
|
|
|
};
|
2020-01-16 00:25:50 +00:00
|
|
|
|
2020-01-17 19:50:28 +00:00
|
|
|
clip.as_movie_clip().unwrap().unload(uc);
|
|
|
|
|
|
|
|
clip.as_movie_clip()
|
|
|
|
.unwrap()
|
|
|
|
.replace_with_movie(uc.gc_context, None);
|
|
|
|
|
2020-01-16 00:25:50 +00:00
|
|
|
if let Some(broadcaster) = broadcaster {
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-16 00:25:50 +00:00
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
2020-01-17 23:24:06 +00:00
|
|
|
"broadcastMessage",
|
2020-07-12 22:48:14 +00:00
|
|
|
&["onLoadStart".into(), Value::Object(broadcaster)],
|
2020-01-16 00:25:50 +00:00
|
|
|
);
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2020-01-16 00:25:50 +00:00
|
|
|
},
|
|
|
|
)?;
|
|
|
|
|
2020-07-22 03:57:43 +00:00
|
|
|
let data = (fetch.await)
|
2020-07-23 23:00:05 +00:00
|
|
|
.and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?)));
|
2020-02-26 23:15:01 +00:00
|
|
|
if let Ok((length, movie)) = data {
|
|
|
|
let movie = Arc::new(movie);
|
2020-01-16 00:25:50 +00:00
|
|
|
|
|
|
|
player
|
|
|
|
.lock()
|
|
|
|
.expect("Could not lock player!!")
|
2020-07-28 00:57:42 +00:00
|
|
|
.update(|_avm2, uc| {
|
2020-01-16 00:25:50 +00:00
|
|
|
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
|
|
|
|
Some(Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
}) => (*target_clip, *target_broadcaster),
|
2020-06-18 08:36:04 +00:00
|
|
|
None => return Err(Error::Cancelled),
|
2020-01-16 00:25:50 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-16 00:25:50 +00:00
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
2020-01-17 23:24:06 +00:00
|
|
|
"broadcastMessage",
|
2020-01-16 00:25:50 +00:00
|
|
|
&[
|
2020-07-12 22:48:14 +00:00
|
|
|
"onLoadProgress".into(),
|
2020-01-16 00:25:50 +00:00
|
|
|
Value::Object(broadcaster),
|
2020-02-26 23:15:01 +00:00
|
|
|
length.into(),
|
|
|
|
length.into(),
|
2020-01-16 00:25:50 +00:00
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut mc = clip
|
|
|
|
.as_movie_clip()
|
|
|
|
.expect("Attempted to load movie into not movie clip");
|
|
|
|
|
2020-01-17 19:50:28 +00:00
|
|
|
mc.replace_with_movie(uc.gc_context, Some(movie.clone()));
|
2020-07-28 00:57:42 +00:00
|
|
|
mc.post_instantiation(uc, clip, None, false);
|
2020-01-16 00:25:50 +00:00
|
|
|
|
|
|
|
let mut morph_shapes = fnv::FnvHashMap::default();
|
2020-07-28 00:57:42 +00:00
|
|
|
mc.preload(uc, &mut morph_shapes);
|
2020-01-16 00:25:50 +00:00
|
|
|
|
|
|
|
// Finalize morph shapes.
|
|
|
|
for (id, static_data) in morph_shapes {
|
|
|
|
let morph_shape = MorphShape::new(uc.gc_context, static_data);
|
|
|
|
uc.library
|
|
|
|
.library_for_movie_mut(movie.clone())
|
|
|
|
.register_character(
|
|
|
|
id,
|
|
|
|
crate::character::Character::MorphShape(morph_shape),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-16 00:25:50 +00:00
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
2020-01-17 23:24:06 +00:00
|
|
|
"broadcastMessage",
|
2020-07-12 22:48:14 +00:00
|
|
|
&["onLoadComplete".into(), Value::Object(broadcaster)],
|
2020-01-16 00:25:50 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-26 23:03:45 +00:00
|
|
|
if let Some(Loader::Movie { load_complete, .. }) =
|
|
|
|
uc.load_manager.get_loader_mut(handle)
|
|
|
|
{
|
|
|
|
*load_complete = true;
|
|
|
|
};
|
|
|
|
|
2020-01-16 00:25:50 +00:00
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
//TODO: Inspect the fetch error.
|
|
|
|
//This requires cooperation from the backend to send abstract
|
|
|
|
//error types we can actually inspect.
|
2020-02-26 23:15:01 +00:00
|
|
|
//This also can get errors from decoding an invalid SWF file,
|
|
|
|
//too. We should distinguish those to player code.
|
2020-01-16 00:25:50 +00:00
|
|
|
player.lock().expect("Could not lock player!!").update(
|
2020-07-28 00:57:42 +00:00
|
|
|
|_avm2, uc| -> Result<(), Error> {
|
2020-01-16 00:25:50 +00:00
|
|
|
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
|
|
|
|
Some(Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
}) => (*target_clip, *target_broadcaster),
|
2020-06-18 08:36:04 +00:00
|
|
|
None => return Err(Error::Cancelled),
|
2020-01-16 00:25:50 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-16 00:25:50 +00:00
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
2020-01-17 23:24:06 +00:00
|
|
|
"broadcastMessage",
|
|
|
|
&[
|
2020-07-12 22:48:14 +00:00
|
|
|
"onLoadError".into(),
|
2020-07-12 22:16:48 +00:00
|
|
|
Value::Object(broadcaster),
|
2020-07-12 22:48:14 +00:00
|
|
|
"LoadNeverCompleted".into(),
|
2020-01-17 23:24:06 +00:00
|
|
|
],
|
2020-01-16 00:25:50 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-26 23:03:45 +00:00
|
|
|
if let Some(Loader::Movie { load_complete, .. }) =
|
|
|
|
uc.load_manager.get_loader_mut(handle)
|
|
|
|
{
|
|
|
|
*load_complete = true;
|
|
|
|
};
|
|
|
|
|
2020-01-16 00:25:50 +00:00
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn form_loader(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2020-06-18 08:36:04 +00:00
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
2020-01-10 23:28:49 +00:00
|
|
|
let handle = match self {
|
|
|
|
Loader::Form { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
|
2020-06-18 08:36:04 +00:00
|
|
|
_ => return Box::pin(async { Err(Error::NotFormLoader) }),
|
2020-01-10 23:28:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let player = player
|
|
|
|
.upgrade()
|
|
|
|
.expect("Could not upgrade weak reference to player");
|
|
|
|
|
|
|
|
Box::pin(async move {
|
|
|
|
let data = fetch.await?;
|
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
// Fire the load handler.
|
2020-07-28 00:57:42 +00:00
|
|
|
player.lock().unwrap().update(|_avm2, uc| {
|
2020-01-10 23:28:49 +00:00
|
|
|
let loader = uc.load_manager.get_loader(handle);
|
|
|
|
let that = match loader {
|
2020-07-23 01:22:23 +00:00
|
|
|
Some(&Loader::Form { target_object, .. }) => target_object,
|
2020-06-18 08:36:04 +00:00
|
|
|
None => return Err(Error::Cancelled),
|
2020-07-23 01:22:23 +00:00
|
|
|
_ => return Err(Error::NotFormLoader),
|
2020-01-10 23:28:49 +00:00
|
|
|
};
|
|
|
|
|
2020-07-01 22:09:43 +00:00
|
|
|
let mut activation = Activation::from_nothing(
|
2020-07-28 01:27:02 +00:00
|
|
|
uc.reborrow(),
|
2020-07-02 21:37:18 +00:00
|
|
|
ActivationIdentifier::root("[Form Loader]"),
|
2020-06-28 17:15:01 +00:00
|
|
|
uc.swf.version(),
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.global_object_cell(),
|
2020-06-28 17:15:01 +00:00
|
|
|
*uc.levels.get(&0).unwrap(),
|
2020-06-30 19:57:51 +00:00
|
|
|
);
|
2020-02-04 18:51:18 +00:00
|
|
|
|
2020-06-30 19:57:51 +00:00
|
|
|
for (k, v) in form_urlencoded::parse(&data) {
|
2020-07-04 09:25:43 +00:00
|
|
|
that.set(
|
|
|
|
&k,
|
2020-07-27 23:19:05 +00:00
|
|
|
AvmString::new(activation.context.gc_context, v.into_owned()).into(),
|
2020-07-04 09:25:43 +00:00
|
|
|
&mut activation,
|
|
|
|
)?;
|
2020-06-30 19:57:51 +00:00
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2020-01-16 19:20:07 +00:00
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
/// Creates a future for a LoadVars load call.
|
|
|
|
pub fn load_vars_loader(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
|
|
|
let handle = match self {
|
|
|
|
Loader::LoadVars { self_handle, .. } => {
|
|
|
|
self_handle.expect("Loader not self-introduced")
|
|
|
|
}
|
|
|
|
_ => return Box::pin(async { Err(Error::NotLoadVarsLoader) }),
|
|
|
|
};
|
|
|
|
|
|
|
|
let player = player
|
|
|
|
.upgrade()
|
|
|
|
.expect("Could not upgrade weak reference to player");
|
|
|
|
|
|
|
|
Box::pin(async move {
|
|
|
|
let data = fetch.await;
|
|
|
|
|
|
|
|
// Fire the load handler.
|
2020-07-28 00:57:42 +00:00
|
|
|
player.lock().unwrap().update(|_avm2, uc| {
|
2020-07-23 01:22:23 +00:00
|
|
|
let loader = uc.load_manager.get_loader(handle);
|
|
|
|
let that = match loader {
|
|
|
|
Some(&Loader::LoadVars { target_object, .. }) => target_object,
|
|
|
|
None => return Err(Error::Cancelled),
|
|
|
|
_ => return Err(Error::NotLoadVarsLoader),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut activation = Activation::from_nothing(
|
2020-07-28 01:27:02 +00:00
|
|
|
uc.reborrow(),
|
2020-07-23 01:22:23 +00:00
|
|
|
ActivationIdentifier::root("[Form Loader]"),
|
|
|
|
uc.swf.version(),
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.global_object_cell(),
|
2020-07-23 01:22:23 +00:00
|
|
|
*uc.levels.get(&0).unwrap(),
|
|
|
|
);
|
|
|
|
|
|
|
|
match data {
|
|
|
|
Ok(data) => {
|
|
|
|
// Fire the onData method with the loaded string.
|
|
|
|
let string_data =
|
|
|
|
AvmString::new(uc.gc_context, String::from_utf8_lossy(&data));
|
2020-07-27 23:19:05 +00:00
|
|
|
let _ = that.call_method("onData", &[string_data.into()], &mut activation);
|
2020-07-23 01:22:23 +00:00
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
// TODO: Log "Error opening URL" trace similar to the Flash Player?
|
|
|
|
// Simulate 404 HTTP status. This should probably be fired elsewhere
|
|
|
|
// because a failed local load doesn't fire a 404.
|
2020-07-27 23:19:05 +00:00
|
|
|
let _ = that.call_method("onHTTPStatus", &[404.into()], &mut activation);
|
2020-07-23 01:22:23 +00:00
|
|
|
|
|
|
|
// Fire the onData method with no data to indicate an unsuccessful load.
|
2020-07-27 23:19:05 +00:00
|
|
|
let _ = that.call_method("onData", &[Value::Undefined], &mut activation);
|
2020-07-23 01:22:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-01-16 19:20:07 +00:00
|
|
|
/// Event handler morally equivalent to `onLoad` on a movie clip.
|
|
|
|
///
|
|
|
|
/// Returns `true` if the loader has completed and should be removed.
|
|
|
|
///
|
|
|
|
/// Used to fire listener events on clips and terminate completed loaders.
|
|
|
|
pub fn movie_clip_loaded(
|
|
|
|
&mut self,
|
|
|
|
loaded_clip: DisplayObject<'gc>,
|
2020-01-17 23:24:06 +00:00
|
|
|
clip_object: Option<Object<'gc>>,
|
2020-01-16 19:20:07 +00:00
|
|
|
queue: &mut ActionQueue<'gc>,
|
2020-07-12 22:43:12 +00:00
|
|
|
_gc_context: MutationContext<'gc, '_>,
|
2020-01-16 19:20:07 +00:00
|
|
|
) -> bool {
|
2020-02-26 23:03:45 +00:00
|
|
|
let (clip, broadcaster, load_complete) = match self {
|
2020-01-16 19:20:07 +00:00
|
|
|
Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
2020-02-26 23:03:45 +00:00
|
|
|
load_complete,
|
2020-01-16 19:20:07 +00:00
|
|
|
..
|
2020-02-26 23:03:45 +00:00
|
|
|
} => (*target_clip, *target_broadcaster, *load_complete),
|
2020-01-16 19:20:07 +00:00
|
|
|
_ => return false,
|
|
|
|
};
|
|
|
|
|
2020-02-26 23:03:45 +00:00
|
|
|
if DisplayObject::ptr_eq(loaded_clip, clip) && load_complete {
|
2020-01-16 19:20:07 +00:00
|
|
|
if let Some(broadcaster) = broadcaster {
|
|
|
|
queue.queue_actions(
|
|
|
|
clip,
|
|
|
|
ActionType::Method {
|
|
|
|
object: broadcaster,
|
|
|
|
name: "broadcastMessage",
|
2020-01-17 23:24:06 +00:00
|
|
|
args: vec![
|
2020-07-12 22:48:14 +00:00
|
|
|
"onLoadInit".into(),
|
2020-01-17 23:24:06 +00:00
|
|
|
clip_object.map(|co| co.into()).unwrap_or(Value::Undefined),
|
|
|
|
],
|
2020-01-16 19:20:07 +00:00
|
|
|
},
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2020-01-18 03:58:48 +00:00
|
|
|
|
|
|
|
pub fn xml_loader(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2020-06-18 08:36:04 +00:00
|
|
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
2020-01-18 03:58:48 +00:00
|
|
|
let handle = match self {
|
|
|
|
Loader::XML { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
|
2020-06-18 08:36:04 +00:00
|
|
|
_ => return Box::pin(async { Err(Error::NotXmlLoader) }),
|
2020-01-18 03:58:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let player = player
|
|
|
|
.upgrade()
|
|
|
|
.expect("Could not upgrade weak reference to player");
|
|
|
|
|
|
|
|
Box::pin(async move {
|
|
|
|
let data = fetch.await;
|
|
|
|
if let Ok(data) = data {
|
2020-06-18 08:33:58 +00:00
|
|
|
let xmlstring = String::from_utf8(data)?;
|
2020-01-18 03:58:48 +00:00
|
|
|
|
|
|
|
player.lock().expect("Could not lock player!!").update(
|
2020-07-28 00:57:42 +00:00
|
|
|
|_avm2, uc| -> Result<(), Error> {
|
2020-01-18 03:58:48 +00:00
|
|
|
let (mut node, active_clip) = match uc.load_manager.get_loader(handle) {
|
|
|
|
Some(Loader::XML {
|
|
|
|
target_node,
|
|
|
|
active_clip,
|
|
|
|
..
|
|
|
|
}) => (*target_node, *active_clip),
|
2020-06-18 08:36:04 +00:00
|
|
|
None => return Err(Error::Cancelled),
|
2020-01-18 03:58:48 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let object =
|
2020-07-28 00:57:42 +00:00
|
|
|
node.script_object(uc.gc_context, Some(uc.avm1.prototypes().xml_node));
|
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-18 03:58:48 +00:00
|
|
|
active_clip,
|
|
|
|
object,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"onHTTPStatus",
|
|
|
|
&[200.into()],
|
|
|
|
);
|
|
|
|
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-18 03:58:48 +00:00
|
|
|
active_clip,
|
|
|
|
object,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"onData",
|
2020-07-13 10:09:30 +00:00
|
|
|
&[AvmString::new(uc.gc_context, xmlstring).into()],
|
2020-01-18 03:58:48 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
} else {
|
|
|
|
player.lock().expect("Could not lock player!!").update(
|
2020-07-28 00:57:42 +00:00
|
|
|
|_avm2, uc| -> Result<(), Error> {
|
2020-01-18 03:58:48 +00:00
|
|
|
let (mut node, active_clip) = match uc.load_manager.get_loader(handle) {
|
|
|
|
Some(Loader::XML {
|
|
|
|
target_node,
|
|
|
|
active_clip,
|
|
|
|
..
|
|
|
|
}) => (*target_node, *active_clip),
|
2020-06-18 08:36:04 +00:00
|
|
|
None => return Err(Error::Cancelled),
|
2020-01-18 03:58:48 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let object =
|
2020-07-28 00:57:42 +00:00
|
|
|
node.script_object(uc.gc_context, Some(uc.avm1.prototypes().xml_node));
|
2020-02-04 18:51:18 +00:00
|
|
|
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-18 03:58:48 +00:00
|
|
|
active_clip,
|
|
|
|
object,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"onHTTPStatus",
|
|
|
|
&[404.into()],
|
|
|
|
);
|
|
|
|
|
2020-07-28 00:57:42 +00:00
|
|
|
uc.avm1.run_stack_frame_for_method(
|
2020-01-18 03:58:48 +00:00
|
|
|
active_clip,
|
|
|
|
object,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"onData",
|
|
|
|
&[],
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|