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};
|
2021-09-12 10:20:51 +00:00
|
|
|
use crate::avm1::{Avm1, Object, TObject, Value};
|
2021-05-22 02:46:17 +00:00
|
|
|
use crate::avm2::{Activation as Avm2Activation, Domain as Avm2Domain};
|
2022-03-25 14:54:48 +00:00
|
|
|
use crate::backend::navigator::{OwnedFuture, RequestOptions};
|
2022-02-13 02:00:53 +00:00
|
|
|
use crate::backend::render::{determine_jpeg_tag_format, JpegTagFormat};
|
2022-03-27 23:17:26 +00:00
|
|
|
use crate::context::{ActionQueue, ActionType, UpdateContext};
|
2022-02-13 02:00:53 +00:00
|
|
|
use crate::display_object::{
|
|
|
|
Bitmap, DisplayObject, MorphShape, TDisplayObject, TDisplayObjectContainer,
|
|
|
|
};
|
2020-01-16 00:25:50 +00:00
|
|
|
use crate::player::{Player, NEWEST_PLAYER_VERSION};
|
2021-09-12 10:20:51 +00:00
|
|
|
use crate::string::AvmString;
|
2020-01-10 23:28:49 +00:00
|
|
|
use crate::tag_utils::SwfMovie;
|
2020-07-21 04:24:02 +00:00
|
|
|
use crate::vminterface::Instantiator;
|
2020-11-26 20:34:13 +00:00
|
|
|
use encoding_rs::UTF_8;
|
2021-02-05 16:04:23 +00:00
|
|
|
use gc_arena::{Collect, CollectionContext};
|
2020-01-10 23:28:49 +00:00
|
|
|
use generational_arena::{Arena, Index};
|
2022-03-27 19:58:33 +00:00
|
|
|
use std::fmt;
|
2020-01-10 23:28:49 +00:00
|
|
|
use std::sync::{Arc, Mutex, Weak};
|
2022-02-13 02:00:53 +00:00
|
|
|
use swf::read::read_compression_type;
|
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;
|
|
|
|
|
2022-02-13 02:00:53 +00:00
|
|
|
/// Enumeration of all content types that `Loader` can handle.
|
|
|
|
///
|
|
|
|
/// This is a superset of `JpegTagFormat`.
|
2022-03-27 19:58:33 +00:00
|
|
|
#[derive(PartialEq, Debug)]
|
2022-02-13 02:00:53 +00:00
|
|
|
pub enum ContentType {
|
|
|
|
Swf,
|
|
|
|
Jpeg,
|
|
|
|
Png,
|
|
|
|
Gif,
|
|
|
|
Unknown,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<JpegTagFormat> for ContentType {
|
|
|
|
fn from(jtf: JpegTagFormat) -> Self {
|
|
|
|
match jtf {
|
|
|
|
JpegTagFormat::Jpeg => Self::Jpeg,
|
|
|
|
JpegTagFormat::Png => Self::Png,
|
|
|
|
JpegTagFormat::Gif => Self::Gif,
|
|
|
|
JpegTagFormat::Unknown => Self::Unknown,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-27 19:58:33 +00:00
|
|
|
impl fmt::Display for ContentType {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::Swf => write!(f, "SWF"),
|
|
|
|
Self::Jpeg => write!(f, "JPEG"),
|
|
|
|
Self::Png => write!(f, "PNG"),
|
|
|
|
Self::Gif => write!(f, "GIF"),
|
|
|
|
Self::Unknown => write!(f, "Unknown"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 02:00:53 +00:00
|
|
|
impl ContentType {
|
|
|
|
fn sniff(data: &[u8]) -> ContentType {
|
|
|
|
if read_compression_type(data).is_ok() {
|
|
|
|
ContentType::Swf
|
|
|
|
} else {
|
|
|
|
determine_jpeg_tag_format(data).into()
|
|
|
|
}
|
|
|
|
}
|
2022-03-27 19:58:33 +00:00
|
|
|
|
|
|
|
/// Assert that content is of a given type, and error otherwise.
|
|
|
|
fn expect(self, expected: Self) -> Result<Self, Error> {
|
|
|
|
if self == expected {
|
|
|
|
Ok(self)
|
|
|
|
} else {
|
|
|
|
Err(Error::UnexpectedData(expected, self))
|
|
|
|
}
|
|
|
|
}
|
2022-02-13 02:00:53 +00:00
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
|
2022-03-17 23:38:31 +00:00
|
|
|
#[error("Could not fetch: {0}")]
|
2020-07-23 23:00:05 +00:00
|
|
|
FetchError(String),
|
|
|
|
|
2020-06-18 08:33:58 +00:00
|
|
|
#[error("Invalid SWF")]
|
|
|
|
InvalidSwf(#[from] crate::tag_utils::Error),
|
|
|
|
|
2022-03-27 19:58:33 +00:00
|
|
|
#[error("Unexpected content of type {1}, expected {0}")]
|
|
|
|
UnexpectedData(ContentType, ContentType),
|
|
|
|
|
|
|
|
#[error("Unknown or corrupted data")]
|
|
|
|
UnknownData,
|
2022-02-13 02:00:53 +00:00
|
|
|
|
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),
|
|
|
|
}
|
|
|
|
|
|
|
|
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`.
|
|
|
|
///
|
2022-01-06 22:45:46 +00:00
|
|
|
/// 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).
|
2020-01-10 23:28:49 +00:00
|
|
|
pub fn add_loader(&mut self, loader: Loader<'gc>) -> Handle {
|
|
|
|
let handle = self.0.insert(loader);
|
2022-01-06 22:45:46 +00:00
|
|
|
match self.get_loader_mut(handle).unwrap() {
|
|
|
|
Loader::RootMovie { self_handle, .. }
|
|
|
|
| Loader::Movie { self_handle, .. }
|
|
|
|
| Loader::Form { self_handle, .. }
|
|
|
|
| Loader::LoadVars { self_handle, .. } => *self_handle = Some(handle),
|
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
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
|
2020-11-11 09:55:46 +00:00
|
|
|
/// kick off its root movie load.
|
2020-07-23 03:18:30 +00:00
|
|
|
pub fn load_root_movie(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2022-03-25 14:54:48 +00:00
|
|
|
url: &str,
|
|
|
|
options: RequestOptions,
|
2021-05-03 18:11:38 +00:00
|
|
|
parameters: Vec<(String, String)>,
|
2021-05-23 00:19:45 +00:00
|
|
|
on_metadata: Box<dyn FnOnce(&swf::HeaderExt)>,
|
2020-07-23 03:18:30 +00:00
|
|
|
) -> OwnedFuture<(), Error> {
|
|
|
|
let loader = Loader::RootMovie { self_handle: None };
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
2022-03-25 14:54:48 +00:00
|
|
|
loader.root_movie_loader(player, url.to_owned(), options, parameters, on_metadata)
|
2020-07-23 03:18:30 +00:00
|
|
|
}
|
|
|
|
|
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>,
|
2022-03-25 15:05:48 +00:00
|
|
|
url: &str,
|
|
|
|
options: RequestOptions,
|
2021-03-22 23:41:40 +00:00
|
|
|
loader_url: Option<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,
|
2021-01-06 12:08:15 +00:00
|
|
|
loader_status: LoaderStatus::Pending,
|
2020-01-10 23:28:49 +00:00
|
|
|
};
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
2022-03-25 15:05:48 +00:00
|
|
|
loader.movie_loader(player, url.to_owned(), options, loader_url)
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
|
2020-11-11 09:55:46 +00:00
|
|
|
/// Indicates that a movie clip has initialized (ran its first frame).
|
2020-01-16 19:20:07 +00:00
|
|
|
///
|
|
|
|
/// Interested loaders will be invoked from here.
|
2022-01-14 19:11:00 +00:00
|
|
|
pub fn movie_clip_on_load(&mut self, queue: &mut ActionQueue<'gc>) {
|
2020-01-16 19:20:07 +00:00
|
|
|
let mut invalidated_loaders = vec![];
|
|
|
|
|
2022-01-14 19:11:00 +00:00
|
|
|
for (index, loader) in self.0.iter_mut().rev() {
|
|
|
|
if loader.movie_clip_loaded(queue) {
|
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>,
|
2022-03-25 15:09:51 +00:00
|
|
|
url: &str,
|
|
|
|
options: RequestOptions,
|
2020-06-18 08:36:04 +00:00
|
|
|
) -> 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();
|
2022-03-25 15:09:51 +00:00
|
|
|
loader.form_loader(player, url.to_owned(), options)
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
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>,
|
2022-03-25 15:18:12 +00:00
|
|
|
url: &str,
|
|
|
|
options: RequestOptions,
|
2020-07-23 01:22:23 +00:00
|
|
|
) -> 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();
|
2022-03-25 15:18:12 +00:00
|
|
|
loader.load_vars_loader(player, url.to_owned(), options)
|
2020-07-23 01:22:23 +00:00
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Default for LoadManager<'gc> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-06 12:08:15 +00:00
|
|
|
/// The completion status of a `Loader` loading a movie.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Collect)]
|
|
|
|
#[collect(require_static)]
|
2021-01-06 12:08:15 +00:00
|
|
|
pub enum LoaderStatus {
|
|
|
|
/// The movie hasn't been loaded yet.
|
|
|
|
Pending,
|
|
|
|
/// The movie loaded successfully.
|
|
|
|
Succeeded,
|
|
|
|
/// An error occurred while loading the movie.
|
|
|
|
Failed,
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
/// A struct that holds garbage-collected pointers for asynchronous code.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
2020-01-10 23:28:49 +00:00
|
|
|
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.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2020-07-23 03:18:30 +00:00
|
|
|
self_handle: Option<Handle>,
|
|
|
|
},
|
|
|
|
|
2021-06-24 10:42:38 +00:00
|
|
|
/// Loader that is loading a new movie into a MovieClip.
|
2020-01-10 23:28:49 +00:00
|
|
|
Movie {
|
|
|
|
/// The handle to refer to this loader instance.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2020-01-10 23:28:49 +00:00
|
|
|
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
|
|
|
|
2021-01-06 12:08:15 +00:00
|
|
|
/// Indicates the completion status of this loader.
|
2020-02-26 23:03:45 +00:00
|
|
|
///
|
|
|
|
/// This flag exists to prevent a situation in which loading a movie
|
2020-11-11 09:55:46 +00:00
|
|
|
/// into a clip that has not yet fired its Load event causes the
|
2020-02-26 23:03:45 +00:00
|
|
|
/// loader to be prematurely removed. This flag is only set when either
|
|
|
|
/// the movie has been replaced (and thus Load events can be trusted)
|
2020-09-19 14:27:24 +00:00
|
|
|
/// or an error has occurred (in which case we don't care about the
|
2020-02-26 23:03:45 +00:00
|
|
|
/// loader anymore).
|
2021-01-06 12:08:15 +00:00
|
|
|
loader_status: LoaderStatus,
|
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.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2020-01-10 23:28:49 +00:00
|
|
|
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.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2020-07-23 01:22:23 +00:00
|
|
|
self_handle: Option<Handle>,
|
|
|
|
|
|
|
|
/// The target AVM1 object to load form data into.
|
|
|
|
target_object: Object<'gc>,
|
|
|
|
},
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Loader<'gc> {
|
2020-07-23 03:18:30 +00:00
|
|
|
/// Construct a future for the root movie loader.
|
2022-03-25 14:54:48 +00:00
|
|
|
fn root_movie_loader(
|
2020-07-23 03:18:30 +00:00
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2022-03-25 14:54:48 +00:00
|
|
|
url: String,
|
|
|
|
options: RequestOptions,
|
2021-05-03 18:11:38 +00:00
|
|
|
parameters: Vec<(String, String)>,
|
2021-05-23 00:19:45 +00:00
|
|
|
on_metadata: Box<dyn FnOnce(&swf::HeaderExt)>,
|
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 {
|
2022-03-25 14:54:48 +00:00
|
|
|
// clippy reports a false positive for explicitly dropped guards:
|
|
|
|
// https://github.com/rust-lang/rust-clippy/issues/6446
|
|
|
|
// A workaround for this is to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
|
|
|
|
let fetch;
|
|
|
|
let url = {
|
|
|
|
let player_lock = player.lock().unwrap();
|
|
|
|
let url = player_lock.navigator().resolve_relative_url(&url);
|
|
|
|
fetch = player_lock.navigator().fetch(&url, options);
|
|
|
|
url
|
|
|
|
};
|
|
|
|
|
|
|
|
let data = fetch.await.map_err(|error| {
|
2021-06-24 20:29:34 +00:00
|
|
|
player
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.ui()
|
|
|
|
.display_root_movie_download_failed_message();
|
2022-03-25 14:54:48 +00:00
|
|
|
error
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let mut movie = SwfMovie::from_data(&data, Some(url.into_owned()), None)?;
|
|
|
|
on_metadata(movie.header());
|
|
|
|
movie.append_parameters(parameters);
|
|
|
|
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
|
|
|
Ok(())
|
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.
|
2022-03-25 15:05:48 +00:00
|
|
|
fn movie_loader(
|
2020-01-10 23:28:49 +00:00
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2022-03-25 15:05:48 +00:00
|
|
|
url: String,
|
|
|
|
options: RequestOptions,
|
2021-03-22 23:41:40 +00:00
|
|
|
loader_url: Option<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 {
|
2022-03-25 15:05:48 +00:00
|
|
|
// clippy reports a false positive for explicitly dropped guards:
|
|
|
|
// https://github.com/rust-lang/rust-clippy/issues/6446
|
|
|
|
// A workaround for this is to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
|
|
|
|
let fetch;
|
|
|
|
let url = {
|
|
|
|
let player_lock = player.lock().unwrap();
|
|
|
|
let url = player_lock.navigator().resolve_relative_url(&url);
|
|
|
|
fetch = player_lock.navigator().fetch(&url, options);
|
|
|
|
url
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut replacing_root_movie = false;
|
|
|
|
player.lock().unwrap().update(|uc| -> Result<(), Error> {
|
2022-03-27 23:17:26 +00:00
|
|
|
let clip = match uc.load_manager.get_loader(handle) {
|
|
|
|
Some(Loader::Movie { target_clip, .. }) => *target_clip,
|
2022-03-25 15:05:48 +00:00
|
|
|
None => return Err(Error::Cancelled),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
replacing_root_movie = DisplayObject::ptr_eq(clip, uc.stage.root_clip());
|
|
|
|
|
|
|
|
if let Some(mut mc) = clip.as_movie_clip() {
|
|
|
|
mc.unload(uc);
|
|
|
|
mc.replace_with_movie(uc.gc_context, None);
|
|
|
|
}
|
2020-07-23 22:50:00 +00:00
|
|
|
|
2022-03-27 23:17:26 +00:00
|
|
|
Loader::movie_loader_start(handle, uc)
|
2022-03-25 15:05:48 +00:00
|
|
|
})?;
|
|
|
|
|
|
|
|
if let Ok(data) = fetch.await {
|
2022-02-13 02:00:53 +00:00
|
|
|
let sniffed_type = ContentType::sniff(&data);
|
|
|
|
let length = data.len();
|
|
|
|
|
2022-03-27 19:58:33 +00:00
|
|
|
if replacing_root_movie {
|
|
|
|
sniffed_type.expect(ContentType::Swf)?;
|
|
|
|
|
2022-02-13 02:00:53 +00:00
|
|
|
let movie =
|
|
|
|
SwfMovie::from_data(&data, Some(url.into_owned()), loader_url.clone())?;
|
|
|
|
let movie = Arc::new(movie);
|
2022-03-25 15:05:48 +00:00
|
|
|
player.lock().unwrap().set_root_movie(movie);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
player.lock().unwrap().update(|uc| {
|
2022-03-27 23:17:26 +00:00
|
|
|
let clip = match uc.load_manager.get_loader(handle) {
|
|
|
|
Some(Loader::Movie { target_clip, .. }) => *target_clip,
|
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
|
|
|
|
2022-02-13 02:00:53 +00:00
|
|
|
match sniffed_type {
|
|
|
|
ContentType::Swf => {
|
|
|
|
let movie = Arc::new(SwfMovie::from_data(
|
|
|
|
&data,
|
|
|
|
Some(url.into_owned()),
|
|
|
|
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);
|
2022-03-25 15:05:48 +00:00
|
|
|
uc.library
|
|
|
|
.library_for_movie_mut(movie.clone())
|
2022-02-13 02:00:53 +00:00
|
|
|
.set_avm2_domain(domain);
|
|
|
|
|
2022-03-27 23:17:26 +00:00
|
|
|
Loader::movie_loader_progress(handle, uc, data.len(), data.len())?;
|
2022-02-13 02:00:53 +00:00
|
|
|
|
|
|
|
if let Some(mut mc) = clip.as_movie_clip() {
|
|
|
|
mc.replace_with_movie(uc.gc_context, Some(movie.clone()));
|
|
|
|
mc.post_instantiation(uc, None, Instantiator::Movie, false);
|
|
|
|
|
|
|
|
let mut morph_shapes = fnv::FnvHashMap::default();
|
|
|
|
mc.preload(uc, &mut morph_shapes);
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ContentType::Gif | ContentType::Jpeg | ContentType::Png => {
|
|
|
|
let bitmap = uc.renderer.register_bitmap_jpeg_2(&data)?;
|
|
|
|
let bitmap_obj =
|
|
|
|
Bitmap::new(uc, 0, bitmap.handle, bitmap.width, bitmap.height);
|
|
|
|
|
2022-03-27 23:17:26 +00:00
|
|
|
Loader::movie_loader_progress(handle, uc, length, length)?;
|
2022-02-13 02:00:53 +00:00
|
|
|
|
|
|
|
if let Some(mc) = clip.as_movie_clip() {
|
|
|
|
mc.replace_at_depth(uc, bitmap_obj.into(), 1);
|
|
|
|
}
|
2020-01-16 00:25:50 +00:00
|
|
|
}
|
2022-03-27 19:58:33 +00:00
|
|
|
ContentType::Unknown => return Err(Error::UnknownData),
|
2022-03-25 15:05:48 +00:00
|
|
|
}
|
2020-01-16 00:25:50 +00:00
|
|
|
|
2022-03-27 23:17:26 +00:00
|
|
|
Loader::movie_loader_complete(handle, uc)?;
|
2020-02-26 23:03:45 +00:00
|
|
|
|
2022-03-25 15:05:48 +00:00
|
|
|
Ok(())
|
2022-02-13 02:00:53 +00:00
|
|
|
})?; //TODO: content sniffing errors need to be reported somehow
|
2020-01-16 00:25:50 +00:00
|
|
|
} else {
|
2022-03-27 23:17:26 +00:00
|
|
|
player
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.update(|uc| -> Result<(), Error> { Loader::movie_loader_error(handle, uc) })?;
|
2020-01-16 00:25:50 +00:00
|
|
|
}
|
2022-02-13 02:00:53 +00:00
|
|
|
|
|
|
|
Ok(())
|
2020-01-10 23:28:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-25 15:09:51 +00:00
|
|
|
fn form_loader(
|
2020-01-10 23:28:49 +00:00
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2022-03-25 15:09:51 +00:00
|
|
|
url: String,
|
|
|
|
options: RequestOptions,
|
2020-06-18 08:36:04 +00:00
|
|
|
) -> 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 {
|
2022-03-25 15:09:51 +00:00
|
|
|
// clippy reports a false positive for explicitly dropped guards:
|
|
|
|
// https://github.com/rust-lang/rust-clippy/issues/6446
|
|
|
|
// A workaround for this is to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
|
|
|
|
let fetch;
|
|
|
|
{
|
|
|
|
let player_lock = player.lock().unwrap();
|
|
|
|
let url = player_lock.navigator().resolve_relative_url(&url);
|
|
|
|
fetch = player_lock.navigator().fetch(&url, options);
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
let data = fetch.await?;
|
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
// Fire the load handler.
|
2020-07-28 03:19:43 +00:00
|
|
|
player.lock().unwrap().update(|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-26 02:11:38 +00:00
|
|
|
let mut activation = Activation::from_stub(
|
2020-07-28 01:27:02 +00:00
|
|
|
uc.reborrow(),
|
2020-07-02 21:37:18 +00:00
|
|
|
ActivationIdentifier::root("[Form Loader]"),
|
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) {
|
2021-10-05 22:21:21 +00:00
|
|
|
let k = AvmString::new_utf8(activation.context.gc_context, k);
|
|
|
|
let v = AvmString::new_utf8(activation.context.gc_context, v);
|
2021-05-09 10:14:54 +00:00
|
|
|
that.set(k, v.into(), &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.
|
2022-03-25 15:18:12 +00:00
|
|
|
fn load_vars_loader(
|
2020-07-23 01:22:23 +00:00
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2022-03-25 15:18:12 +00:00
|
|
|
url: String,
|
|
|
|
options: RequestOptions,
|
2020-07-23 01:22:23 +00:00
|
|
|
) -> 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 {
|
2022-03-25 15:18:12 +00:00
|
|
|
// clippy reports a false positive for explicitly dropped guards:
|
|
|
|
// https://github.com/rust-lang/rust-clippy/issues/6446
|
|
|
|
// A workaround for this is to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
|
|
|
|
let fetch;
|
|
|
|
{
|
|
|
|
let player_lock = player.lock().unwrap();
|
|
|
|
let url = player_lock.navigator().resolve_relative_url(&url);
|
|
|
|
fetch = player_lock.navigator().fetch(&url, options);
|
|
|
|
}
|
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
let data = fetch.await;
|
|
|
|
|
|
|
|
// Fire the load handler.
|
2020-07-28 03:19:43 +00:00
|
|
|
player.lock().unwrap().update(|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),
|
|
|
|
};
|
|
|
|
|
2021-12-31 13:29:05 +00:00
|
|
|
let mut activation =
|
|
|
|
Activation::from_stub(uc.reborrow(), ActivationIdentifier::root("[Loader]"));
|
2020-07-23 01:22:23 +00:00
|
|
|
|
|
|
|
match data {
|
|
|
|
Ok(data) => {
|
2021-12-31 13:29:05 +00:00
|
|
|
let _ =
|
|
|
|
that.call_method("onHTTPStatus".into(), &[200.into()], &mut activation);
|
|
|
|
|
2020-07-23 01:22:23 +00:00
|
|
|
// Fire the onData method with the loaded string.
|
2021-10-05 21:12:41 +00:00
|
|
|
let string_data = AvmString::new_utf8(
|
|
|
|
activation.context.gc_context,
|
|
|
|
UTF_8.decode(&data).0,
|
|
|
|
);
|
2021-05-09 10:14:54 +00:00
|
|
|
let _ = that.call_method(
|
|
|
|
"onData".into(),
|
|
|
|
&[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.
|
2021-05-09 10:14:54 +00:00
|
|
|
let _ =
|
|
|
|
that.call_method("onHTTPStatus".into(), &[404.into()], &mut activation);
|
2020-07-23 01:22:23 +00:00
|
|
|
|
|
|
|
// Fire the onData method with no data to indicate an unsuccessful load.
|
2021-05-09 10:14:54 +00:00
|
|
|
let _ =
|
|
|
|
that.call_method("onData".into(), &[Value::Undefined], &mut activation);
|
2020-07-23 01:22:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-27 23:17:26 +00:00
|
|
|
/// Report a movie loader start event to script code.
|
|
|
|
fn movie_loader_start(handle: Index, uc: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
|
|
|
let me = uc.load_manager.get_loader_mut(handle);
|
|
|
|
if me.is_none() {
|
|
|
|
return Err(Error::Cancelled);
|
|
|
|
}
|
|
|
|
|
|
|
|
let me = me.unwrap();
|
|
|
|
|
|
|
|
let (clip, broadcaster) = match me {
|
|
|
|
Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
} => (*target_clip, *target_broadcaster),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
|
|
|
Avm1::run_stack_frame_for_method(
|
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"broadcastMessage".into(),
|
|
|
|
&["onLoadStart".into(), clip.object()],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Report a movie loader progress event to script code.
|
|
|
|
fn movie_loader_progress(
|
|
|
|
handle: Index,
|
|
|
|
uc: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
cur_len: usize,
|
|
|
|
total_len: usize,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let me = uc.load_manager.get_loader_mut(handle);
|
|
|
|
if me.is_none() {
|
|
|
|
return Err(Error::Cancelled);
|
|
|
|
}
|
|
|
|
|
|
|
|
let me = me.unwrap();
|
|
|
|
|
|
|
|
let (clip, broadcaster) = match me {
|
|
|
|
Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
} => (*target_clip, *target_broadcaster),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
|
|
|
Avm1::run_stack_frame_for_method(
|
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"broadcastMessage".into(),
|
|
|
|
&[
|
|
|
|
"onLoadProgress".into(),
|
|
|
|
clip.object(),
|
|
|
|
cur_len.into(),
|
|
|
|
total_len.into(),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Report a movie loader completion to script code.
|
|
|
|
fn movie_loader_complete(
|
|
|
|
handle: Index,
|
|
|
|
uc: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let (clip, broadcaster) = match uc.load_manager.get_loader_mut(handle) {
|
|
|
|
Some(Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
}) => (*target_clip, *target_broadcaster),
|
|
|
|
None => return Err(Error::Cancelled),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
|
|
|
Avm1::run_stack_frame_for_method(
|
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"broadcastMessage".into(),
|
|
|
|
// TODO: Pass an actual httpStatus argument instead of 0.
|
|
|
|
&["onLoadComplete".into(), clip.object(), 0.into()],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Loader::Movie { loader_status, .. } = uc.load_manager.get_loader_mut(handle).unwrap()
|
|
|
|
{
|
|
|
|
*loader_status = LoaderStatus::Succeeded;
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Report a movie loader error to script code.
|
|
|
|
///
|
|
|
|
/// This is an associated function because we cannot borrow both the update
|
|
|
|
/// context and one of it's loaders.
|
|
|
|
fn movie_loader_error(handle: Index, uc: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
|
|
|
//TODO: Inspect the fetch error.
|
|
|
|
//This requires cooperation from the backend to send abstract
|
|
|
|
//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) {
|
|
|
|
Some(Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
|
|
|
..
|
|
|
|
}) => (*target_clip, *target_broadcaster),
|
|
|
|
None => return Err(Error::Cancelled),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
|
|
|
Avm1::run_stack_frame_for_method(
|
|
|
|
clip,
|
|
|
|
broadcaster,
|
|
|
|
NEWEST_PLAYER_VERSION,
|
|
|
|
uc,
|
|
|
|
"broadcastMessage".into(),
|
|
|
|
&[
|
|
|
|
"onLoadError".into(),
|
|
|
|
clip.object(),
|
|
|
|
"LoadNeverCompleted".into(),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Loader::Movie { loader_status, .. } = uc.load_manager.get_loader_mut(handle).unwrap()
|
|
|
|
{
|
|
|
|
*loader_status = LoaderStatus::Failed;
|
|
|
|
};
|
|
|
|
|
|
|
|
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.
|
2022-01-14 19:11:00 +00:00
|
|
|
fn movie_clip_loaded(&mut self, queue: &mut ActionQueue<'gc>) -> bool {
|
2021-01-06 12:08:15 +00:00
|
|
|
let (clip, broadcaster, loader_status) = match self {
|
2020-01-16 19:20:07 +00:00
|
|
|
Loader::Movie {
|
|
|
|
target_clip,
|
|
|
|
target_broadcaster,
|
2021-01-06 12:08:15 +00:00
|
|
|
loader_status,
|
2020-01-16 19:20:07 +00:00
|
|
|
..
|
2021-01-06 12:08:15 +00:00
|
|
|
} => (*target_clip, *target_broadcaster, *loader_status),
|
2020-01-16 19:20:07 +00:00
|
|
|
_ => return false,
|
|
|
|
};
|
|
|
|
|
2021-01-06 12:08:15 +00:00
|
|
|
match loader_status {
|
|
|
|
LoaderStatus::Pending => false,
|
|
|
|
LoaderStatus::Failed => true,
|
|
|
|
LoaderStatus::Succeeded => {
|
|
|
|
if let Some(broadcaster) = broadcaster {
|
|
|
|
queue.queue_actions(
|
|
|
|
clip,
|
|
|
|
ActionType::Method {
|
|
|
|
object: broadcaster,
|
|
|
|
name: "broadcastMessage",
|
2022-01-14 19:11:00 +00:00
|
|
|
args: vec!["onLoadInit".into(), clip.object()],
|
2021-01-06 12:08:15 +00:00
|
|
|
},
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
2020-01-16 19:20:07 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-10 23:28:49 +00:00
|
|
|
}
|