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};
|
2022-05-11 21:53:50 +00:00
|
|
|
use crate::avm1::function::ExecutionReason;
|
2021-09-12 10:20:51 +00:00
|
|
|
use crate::avm1::{Avm1, Object, TObject, Value};
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
use crate::avm2::bytearray::ByteArrayStorage;
|
|
|
|
use crate::avm2::names::Namespace;
|
|
|
|
use crate::avm2::object::ByteArrayObject;
|
|
|
|
use crate::avm2::object::TObject as _;
|
|
|
|
use crate::avm2::{
|
|
|
|
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, Event as Avm2Event,
|
|
|
|
EventData as Avm2EventData, Object as Avm2Object, QName, Value as Avm2Value,
|
|
|
|
};
|
2022-06-11 08:55:17 +00:00
|
|
|
use crate::backend::navigator::{OwnedFuture, Request};
|
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-06-28 11:25:40 +00:00
|
|
|
use crate::display_object::{
|
|
|
|
Bitmap, DisplayObject, TDisplayObject, TDisplayObjectContainer, TInteractiveObject,
|
|
|
|
};
|
|
|
|
use crate::events::ClipEvent;
|
2022-05-02 21:11:20 +00:00
|
|
|
use crate::player::Player;
|
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-05-22 06:01:34 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
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
|
|
|
}
|
|
|
|
|
2022-05-22 06:01:34 +00:00
|
|
|
#[derive(Clone, Collect, Copy)]
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
#[collect(no_drop)]
|
|
|
|
pub enum DataFormat {
|
|
|
|
Binary,
|
|
|
|
Text,
|
|
|
|
Variables,
|
|
|
|
}
|
|
|
|
|
2022-05-22 06:01:34 +00:00
|
|
|
#[derive(Debug, Error)]
|
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,
|
|
|
|
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
#[error("Non-data loader spawned as data loader")]
|
|
|
|
NotLoadDataLoader,
|
|
|
|
|
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),
|
|
|
|
|
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, .. }
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
| Loader::LoadVars { self_handle, .. }
|
|
|
|
| Loader::LoadURLLoader { self_handle, .. } => *self_handle = Some(handle),
|
2022-01-06 22:45:46 +00:00
|
|
|
}
|
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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
loader.root_movie_loader(player, request, 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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
loader.movie_loader(player, request, 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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
loader.form_loader(player, request)
|
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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
loader.load_vars_loader(player, request)
|
2020-07-23 01:22:23 +00:00
|
|
|
}
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
|
|
|
|
/// Kick off a data load into a `URLLoader`, updating
|
|
|
|
/// its `data` property when the load completes.
|
|
|
|
///
|
|
|
|
/// Returns the loader's async process, which you will need to spawn.
|
|
|
|
pub fn load_data_into_url_loader(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
|
|
|
target_object: Avm2Object<'gc>,
|
2022-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
data_format: DataFormat,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
|
|
|
let loader = Loader::LoadURLLoader {
|
|
|
|
self_handle: None,
|
|
|
|
target_object,
|
|
|
|
};
|
|
|
|
let handle = self.add_loader(loader);
|
|
|
|
let loader = self.get_loader_mut(handle).unwrap();
|
2022-06-11 08:55:17 +00:00
|
|
|
loader.load_url_loader(player, request, data_format)
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +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.
|
2022-05-22 06:01:34 +00:00
|
|
|
#[derive(Clone, Collect, Copy, Debug, Eq, PartialEq)]
|
2021-02-18 02:38:55 +00:00
|
|
|
#[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>,
|
|
|
|
},
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
|
|
|
|
/// Loader that is loading data into a `URLLoader`'s `data` property
|
|
|
|
/// The `data` property is only updated after the data is loaded completely
|
|
|
|
LoadURLLoader {
|
|
|
|
/// The handle to refer to this loader instance.
|
|
|
|
#[collect(require_static)]
|
|
|
|
self_handle: Option<Handle>,
|
|
|
|
|
|
|
|
/// The target `URLLoader` to load data into.
|
|
|
|
target_object: Avm2Object<'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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
let fetch = player.lock().unwrap().navigator().fetch(request);
|
2022-03-25 14:54:48 +00:00
|
|
|
|
2022-04-08 12:40:11 +00:00
|
|
|
let response = 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
|
|
|
|
})?;
|
|
|
|
|
2022-04-08 13:08:13 +00:00
|
|
|
let mut movie = SwfMovie::from_data(&response.body, Some(response.url), None)?;
|
2022-03-25 14:54:48 +00:00
|
|
|
on_metadata(movie.header());
|
|
|
|
movie.append_parameters(parameters);
|
2022-04-08 21:02:07 +00:00
|
|
|
player.lock().unwrap().set_root_movie(movie);
|
2022-03-25 14:54:48 +00:00
|
|
|
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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
let fetch = player.lock().unwrap().navigator().fetch(request);
|
2022-03-25 15:05:48 +00:00
|
|
|
|
|
|
|
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
|
|
|
})?;
|
|
|
|
|
2022-04-08 12:40:11 +00:00
|
|
|
if let Ok(response) = fetch.await {
|
|
|
|
let sniffed_type = ContentType::sniff(&response.body);
|
|
|
|
let mut length = response.body.len();
|
2022-02-13 02:00:53 +00:00
|
|
|
|
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 =
|
2022-04-08 13:08:13 +00:00
|
|
|
SwfMovie::from_data(&response.body, Some(response.url), loader_url)?;
|
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(
|
2022-04-08 12:40:11 +00:00
|
|
|
&response.body,
|
2022-04-08 13:08:13 +00:00
|
|
|
Some(response.url),
|
2022-02-13 02:00:53 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
if let Some(mut mc) = clip.as_movie_clip() {
|
2022-01-30 07:26:52 +00:00
|
|
|
mc.replace_with_movie(uc.gc_context, Some(movie));
|
2022-02-13 02:00:53 +00:00
|
|
|
mc.post_instantiation(uc, None, Instantiator::Movie, false);
|
2022-01-30 07:26:52 +00:00
|
|
|
mc.preload(uc);
|
2022-02-13 02:00:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ContentType::Gif | ContentType::Jpeg | ContentType::Png => {
|
2022-04-08 12:40:11 +00:00
|
|
|
let bitmap = uc.renderer.register_bitmap_jpeg_2(&response.body)?;
|
2022-02-13 02:00:53 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-01-16 00:25:50 +00:00
|
|
|
}
|
2022-04-02 13:11:27 +00:00
|
|
|
ContentType::Unknown => {
|
|
|
|
length = 0;
|
|
|
|
}
|
2022-03-25 15:05:48 +00:00
|
|
|
}
|
2020-01-16 00:25:50 +00:00
|
|
|
|
2022-04-02 13:11:27 +00:00
|
|
|
Loader::movie_loader_progress(handle, uc, length, length)?;
|
|
|
|
|
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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
let fetch = player.lock().unwrap().navigator().fetch(request);
|
2022-03-25 15:09:51 +00:00
|
|
|
|
2022-04-08 12:40:11 +00:00
|
|
|
let response = fetch.await?;
|
2020-01-10 23:28:49 +00:00
|
|
|
|
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
|
|
|
|
2022-04-08 12:40:11 +00:00
|
|
|
for (k, v) in form_urlencoded::parse(&response.body) {
|
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
|
|
|
|
2022-06-28 11:25:40 +00:00
|
|
|
// Fire the onData method and event.
|
|
|
|
if let Some(display_object) = that.as_display_object() {
|
|
|
|
if let Some(movie_clip) = display_object.as_movie_clip() {
|
|
|
|
activation.context.action_queue.queue_actions(
|
|
|
|
movie_clip.into(),
|
|
|
|
ActionType::Method {
|
|
|
|
object: that,
|
|
|
|
name: "onData",
|
|
|
|
args: vec![],
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
movie_clip.event_dispatch(&mut activation.context, ClipEvent::Data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
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-06-11 08:55:17 +00:00
|
|
|
let fetch = player.lock().unwrap().navigator().fetch(request);
|
2022-03-25 15:18:12 +00:00
|
|
|
|
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 {
|
2022-04-08 12:40:11 +00:00
|
|
|
Ok(response) => {
|
2022-05-11 21:53:50 +00:00
|
|
|
let _ = that.call_method(
|
|
|
|
"onHTTPStatus".into(),
|
|
|
|
&[200.into()],
|
|
|
|
&mut activation,
|
|
|
|
ExecutionReason::Special,
|
|
|
|
);
|
2021-12-31 13:29:05 +00:00
|
|
|
|
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,
|
2022-04-08 12:40:11 +00:00
|
|
|
UTF_8.decode(&response.body).0,
|
2021-10-05 21:12:41 +00:00
|
|
|
);
|
2021-05-09 10:14:54 +00:00
|
|
|
let _ = that.call_method(
|
|
|
|
"onData".into(),
|
|
|
|
&[string_data.into()],
|
|
|
|
&mut activation,
|
2022-05-11 21:53:50 +00:00
|
|
|
ExecutionReason::Special,
|
2021-05-09 10:14:54 +00:00
|
|
|
);
|
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.
|
2022-05-11 21:53:50 +00:00
|
|
|
let _ = that.call_method(
|
|
|
|
"onHTTPStatus".into(),
|
|
|
|
&[404.into()],
|
|
|
|
&mut activation,
|
|
|
|
ExecutionReason::Special,
|
|
|
|
);
|
2020-07-23 01:22:23 +00:00
|
|
|
|
|
|
|
// Fire the onData method with no data to indicate an unsuccessful load.
|
2022-05-11 21:53:50 +00:00
|
|
|
let _ = that.call_method(
|
|
|
|
"onData".into(),
|
|
|
|
&[Value::Undefined],
|
|
|
|
&mut activation,
|
|
|
|
ExecutionReason::Special,
|
|
|
|
);
|
2020-07-23 01:22:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
/// Creates a future for a LoadURLLoader load call.
|
|
|
|
fn load_url_loader(
|
|
|
|
&mut self,
|
|
|
|
player: Weak<Mutex<Player>>,
|
2022-06-11 08:55:17 +00:00
|
|
|
request: Request,
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
data_format: DataFormat,
|
|
|
|
) -> OwnedFuture<(), Error> {
|
|
|
|
let handle = match self {
|
|
|
|
Loader::LoadURLLoader { self_handle, .. } => {
|
|
|
|
self_handle.expect("Loader not self-introduced")
|
|
|
|
}
|
|
|
|
_ => return Box::pin(async { Err(Error::NotLoadDataLoader) }),
|
|
|
|
};
|
|
|
|
|
|
|
|
let player = player
|
|
|
|
.upgrade()
|
|
|
|
.expect("Could not upgrade weak reference to player");
|
|
|
|
|
|
|
|
Box::pin(async move {
|
2022-06-11 08:55:17 +00:00
|
|
|
let fetch = player.lock().unwrap().navigator().fetch(request);
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
let response = fetch.await;
|
|
|
|
|
|
|
|
player.lock().unwrap().update(|uc| {
|
|
|
|
let loader = uc.load_manager.get_loader(handle);
|
|
|
|
let target = match loader {
|
|
|
|
Some(&Loader::LoadURLLoader { target_object, .. }) => target_object,
|
|
|
|
// We would have already returned after the previous 'update' call
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
|
|
|
|
|
|
|
|
fn set_data<'a, 'gc: 'a, 'gc_context: 'a>(
|
|
|
|
body: Vec<u8>,
|
|
|
|
activation: &mut Avm2Activation<'a, 'gc, 'gc_context>,
|
|
|
|
mut target: Avm2Object<'gc>,
|
|
|
|
data_format: DataFormat,
|
|
|
|
) {
|
|
|
|
let data_object = match data_format {
|
|
|
|
DataFormat::Binary => {
|
|
|
|
let storage = ByteArrayStorage::from_vec(body);
|
|
|
|
let bytearray =
|
|
|
|
ByteArrayObject::from_storage(activation, storage).unwrap();
|
|
|
|
bytearray.into()
|
|
|
|
}
|
|
|
|
DataFormat::Text => {
|
|
|
|
// FIXME - what do we do if the data is not UTF-8?
|
|
|
|
Avm2Value::String(
|
|
|
|
AvmString::new_utf8_bytes(activation.context.gc_context, body)
|
|
|
|
.unwrap(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
DataFormat::Variables => {
|
|
|
|
log::warn!(
|
|
|
|
"Support for URLLoaderDataFormat.VARIABLES not yet implemented"
|
|
|
|
);
|
|
|
|
Avm2Value::Undefined
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
target
|
|
|
|
.set_property(
|
|
|
|
&QName::new(Namespace::public(), "data").into(),
|
|
|
|
data_object,
|
|
|
|
activation,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
match response {
|
|
|
|
Ok(response) => {
|
|
|
|
// FIXME - the "open" event should be fired earlier, just before
|
|
|
|
// we start to fetch the data.
|
|
|
|
// However, the "open" event should not be fired if an IO error
|
|
|
|
// occurs opening the connection (e.g. if a file does not exist on disk).
|
|
|
|
// We currently have no way of detecting this, so we settle for firing
|
|
|
|
// the event after the entire fetch is complete. This causes there
|
|
|
|
// to a longer delay between the initial load triggered by the script
|
|
|
|
// and the "load" event firing, but it ensures that we match
|
|
|
|
// the Flash behavior w.r.t when an event is fired vs not fired.
|
|
|
|
let mut open_evt = Avm2Event::new("open", Avm2EventData::Empty);
|
|
|
|
open_evt.set_bubbles(false);
|
|
|
|
open_evt.set_cancelable(false);
|
|
|
|
|
|
|
|
if let Err(e) =
|
|
|
|
Avm2::dispatch_event(&mut activation.context, open_evt, target)
|
|
|
|
{
|
|
|
|
log::error!(
|
|
|
|
"Encountered AVM2 error when broadcasting `open` event: {}",
|
|
|
|
e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
set_data(response.body, &mut activation, target, data_format);
|
|
|
|
|
|
|
|
let mut complete_evt = Avm2Event::new("complete", Avm2EventData::Empty);
|
|
|
|
complete_evt.set_bubbles(false);
|
|
|
|
complete_evt.set_cancelable(false);
|
|
|
|
|
|
|
|
if let Err(e) = Avm2::dispatch_event(uc, complete_evt, target) {
|
|
|
|
log::error!(
|
|
|
|
"Encountered AVM2 error when broadcasting `complete` event: {}",
|
|
|
|
e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-05-20 02:56:50 +00:00
|
|
|
Err(_err) => {
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
// Testing with Flash shoes that the 'data' property is cleared
|
|
|
|
// when an error occurs
|
|
|
|
|
|
|
|
set_data(Vec::new(), &mut activation, target, data_format);
|
2022-05-20 02:56:50 +00:00
|
|
|
|
|
|
|
// FIXME - Match the exact error message generated by Flash
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
let mut io_error_evt = Avm2Event::new(
|
|
|
|
"ioError",
|
|
|
|
Avm2EventData::IOError {
|
|
|
|
text: AvmString::new_utf8(
|
|
|
|
activation.context.gc_context,
|
2022-05-20 02:56:50 +00:00
|
|
|
"Error #2032: Stream Error",
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
),
|
2022-05-20 19:35:01 +00:00
|
|
|
error_id: 2032,
|
avm2: Partially implement `URLLoader` and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
2022-04-06 01:27:39 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
io_error_evt.set_bubbles(false);
|
|
|
|
io_error_evt.set_cancelable(false);
|
|
|
|
|
|
|
|
if let Err(e) = Avm2::dispatch_event(uc, io_error_evt, target) {
|
|
|
|
log::error!(
|
|
|
|
"Encountered AVM2 error when broadcasting `ioError` event: {}",
|
|
|
|
e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
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,
|
|
|
|
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,
|
|
|
|
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,
|
|
|
|
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
|
|
|
}
|