core: Introduce LoaderError to all Loader methods

This commit is contained in:
Nathan Adams 2020-06-18 00:35:36 +02:00 committed by Mike Welsh
parent abeece7e78
commit 0f1eef9022
7 changed files with 122 additions and 71 deletions

View File

@ -1,5 +1,6 @@
//! Browser-related platform functions
use crate::loader::LoaderError;
use std::collections::{HashMap, VecDeque};
use std::fs;
use std::future::Future;
@ -11,8 +12,6 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::time::Duration;
use swf::avm1::types::SendVarsMethod;
pub type Error = Box<dyn std::error::Error>;
/// Enumerates all possible navigation methods.
#[derive(Copy, Clone)]
pub enum NavigationMethod {
@ -118,7 +117,11 @@ pub trait NavigatorBackend {
);
/// Fetch data at a given URL and return it some time in the future.
fn fetch(&self, url: &str, request_options: RequestOptions) -> OwnedFuture<Vec<u8>, Error>;
fn fetch(
&self,
url: &str,
request_options: RequestOptions,
) -> OwnedFuture<Vec<u8>, LoaderError>;
/// Get the amount of time since the SWF was launched.
/// Used by the `getTimer` ActionScript call.
@ -132,16 +135,16 @@ pub trait NavigatorBackend {
///
/// TODO: For some reason, `wasm_bindgen_futures` wants unpinnable futures.
/// This seems highly limiting.
fn spawn_future(&mut self, future: OwnedFuture<(), Error>);
fn spawn_future(&mut self, future: OwnedFuture<(), LoaderError>);
}
/// A null implementation of an event loop that only supports blocking.
pub struct NullExecutor {
/// The list of outstanding futures spawned on this executor.
futures_queue: VecDeque<OwnedFuture<(), Error>>,
futures_queue: VecDeque<OwnedFuture<(), LoaderError>>,
/// The source of any additional futures.
channel: Receiver<OwnedFuture<(), Error>>,
channel: Receiver<OwnedFuture<(), LoaderError>>,
}
unsafe fn do_nothing(_data: *const ()) {}
@ -157,7 +160,7 @@ impl NullExecutor {
///
/// The sender yielded as part of construction should be given to a
/// `NullNavigatorBackend` so that it can spawn futures on this executor.
pub fn new() -> (Self, Sender<OwnedFuture<(), Error>>) {
pub fn new() -> (Self, Sender<OwnedFuture<(), LoaderError>>) {
let (send, recv) = channel();
(
@ -191,7 +194,7 @@ impl NullExecutor {
/// stop polling futures and return that error. Otherwise, it will yield
/// `Ok`, indicating that no errors occured. More work may still be
/// available,
pub fn poll_all(&mut self) -> Result<(), Error> {
pub fn poll_all(&mut self) -> Result<(), LoaderError> {
self.flush_channel();
let mut unfinished_futures = VecDeque::new();
@ -226,7 +229,7 @@ impl NullExecutor {
}
/// Block until all futures complete or an error occurs.
pub fn block_all(&mut self) -> Result<(), Error> {
pub fn block_all(&mut self) -> Result<(), LoaderError> {
while self.has_work() {
self.poll_all()?;
}
@ -241,7 +244,7 @@ impl NullExecutor {
/// futures and runs them to completion, blockingly.
pub struct NullNavigatorBackend {
/// The channel upon which all spawned futures will be sent.
channel: Option<Sender<OwnedFuture<(), Error>>>,
channel: Option<Sender<OwnedFuture<(), LoaderError>>>,
/// The base path for all relative fetches.
relative_base_path: PathBuf,
@ -260,7 +263,7 @@ impl NullNavigatorBackend {
/// Construct a navigator backend with fetch and async capability.
pub fn with_base_path<P: AsRef<Path>>(
path: P,
channel: Sender<OwnedFuture<(), Error>>,
channel: Sender<OwnedFuture<(), LoaderError>>,
) -> Self {
let mut relative_base_path = PathBuf::new();
@ -288,18 +291,18 @@ impl NavigatorBackend for NullNavigatorBackend {
) {
}
fn fetch(&self, url: &str, _opts: RequestOptions) -> OwnedFuture<Vec<u8>, Error> {
fn fetch(&self, url: &str, _opts: RequestOptions) -> OwnedFuture<Vec<u8>, LoaderError> {
let mut path = self.relative_base_path.clone();
path.push(url);
Box::pin(async move { fs::read(path).map_err(|e| e.into()) })
Box::pin(async move { fs::read(path).map_err(LoaderError::NetworkError) })
}
fn time_since_launch(&mut self) -> Duration {
Duration::from_millis(0)
}
fn spawn_future(&mut self, future: OwnedFuture<(), Error>) {
fn spawn_future(&mut self, future: OwnedFuture<(), LoaderError>) {
if let Some(channel) = self.channel.as_ref() {
channel.send(future).unwrap();
}

View File

@ -18,7 +18,7 @@ mod drawing;
pub mod events;
mod font;
mod library;
mod loader;
pub mod loader;
mod player;
mod prelude;
mod property_map;

View File

@ -9,12 +9,39 @@ use crate::tag_utils::SwfMovie;
use crate::xml::XMLNode;
use gc_arena::{Collect, CollectionContext};
use generational_arena::{Arena, Index};
use std::fmt;
use std::string::FromUtf8Error;
use std::sync::{Arc, Mutex, Weak};
use url::form_urlencoded;
pub type Handle = Index;
type Error = Box<dyn std::error::Error>;
#[derive(Debug)]
pub enum LoaderError {
Cancelled,
NotMovieLoader,
NotFormLoader,
NotXmlLoader,
InvalidSwf(crate::tag_utils::Error),
InvalidXmlEncoding(FromUtf8Error),
NetworkError(std::io::Error),
Avm1Error(Box<dyn std::error::Error>),
}
impl fmt::Display for LoaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LoaderError::Cancelled => f.write_str("Load cancelled"),
LoaderError::NotMovieLoader => f.write_str("Non-movie loader spawned as movie loader"),
LoaderError::NotFormLoader => f.write_str("Non-form loader spawned as form loader"),
LoaderError::NotXmlLoader => f.write_str("Non-XML loader spawned as XML loader"),
LoaderError::InvalidSwf(error) => write!(f, "Invalid SWF: {}", error),
LoaderError::InvalidXmlEncoding(error) => write!(f, "Invalid XML encoding: {}", error),
LoaderError::NetworkError(error) => write!(f, "Network error: {}", error),
LoaderError::Avm1Error(error) => write!(f, "Could not run avm1 script: {}", error),
}
}
}
/// Holds all in-progress loads for the player.
pub struct LoadManager<'gc>(Arena<Loader<'gc>>);
@ -66,9 +93,9 @@ impl<'gc> LoadManager<'gc> {
&mut self,
player: Weak<Mutex<Player>>,
target_clip: DisplayObject<'gc>,
fetch: OwnedFuture<Vec<u8>, Error>,
fetch: OwnedFuture<Vec<u8>, LoaderError>,
target_broadcaster: Option<Object<'gc>>,
) -> OwnedFuture<(), Error> {
) -> OwnedFuture<(), LoaderError> {
let loader = Loader::Movie {
self_handle: None,
target_clip,
@ -112,8 +139,8 @@ impl<'gc> LoadManager<'gc> {
&mut self,
player: Weak<Mutex<Player>>,
target_object: Object<'gc>,
fetch: OwnedFuture<Vec<u8>, Error>,
) -> OwnedFuture<(), Error> {
fetch: OwnedFuture<Vec<u8>, LoaderError>,
) -> OwnedFuture<(), LoaderError> {
let loader = Loader::Form {
self_handle: None,
target_object,
@ -134,8 +161,8 @@ impl<'gc> LoadManager<'gc> {
player: Weak<Mutex<Player>>,
target_node: XMLNode<'gc>,
active_clip: DisplayObject<'gc>,
fetch: OwnedFuture<Vec<u8>, Error>,
) -> OwnedFuture<(), Error> {
fetch: OwnedFuture<Vec<u8>, LoaderError>,
) -> OwnedFuture<(), LoaderError> {
let loader = Loader::XML {
self_handle: None,
active_clip,
@ -248,11 +275,11 @@ impl<'gc> Loader<'gc> {
pub fn movie_loader(
&mut self,
player: Weak<Mutex<Player>>,
fetch: OwnedFuture<Vec<u8>, Error>,
) -> OwnedFuture<(), Error> {
fetch: OwnedFuture<Vec<u8>, LoaderError>,
) -> OwnedFuture<(), LoaderError> {
let handle = match self {
Loader::Movie { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
_ => return Box::pin(async { Err("Non-movie loader spawned as movie loader".into()) }),
_ => return Box::pin(async { Err(LoaderError::NotMovieLoader) }),
};
let player = player
@ -261,14 +288,14 @@ impl<'gc> Loader<'gc> {
Box::pin(async move {
player.lock().expect("Could not lock player!!").update(
|avm, uc| -> Result<(), Error> {
|avm, uc| -> Result<(), LoaderError> {
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
Some(Loader::Movie {
target_clip,
target_broadcaster,
..
}) => (*target_clip, *target_broadcaster),
None => return Err("Load cancelled".into()),
None => return Err(LoaderError::Cancelled),
_ => unreachable!(),
};
@ -287,14 +314,20 @@ impl<'gc> Loader<'gc> {
"broadcastMessage",
&["onLoadStart".into(), Value::Object(broadcaster)],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
}
Ok(())
},
)?;
let data = (fetch.await).and_then(|data| Ok((data.len(), SwfMovie::from_data(&data)?)));
let data = (fetch.await).and_then(|data| {
Ok((
data.len(),
SwfMovie::from_data(&data).map_err(|e| LoaderError::InvalidSwf(e))?,
))
});
if let Ok((length, movie)) = data {
let movie = Arc::new(movie);
@ -308,7 +341,7 @@ impl<'gc> Loader<'gc> {
target_broadcaster,
..
}) => (*target_clip, *target_broadcaster),
None => return Err("Load cancelled".into()),
None => return Err(LoaderError::Cancelled),
_ => unreachable!(),
};
@ -326,7 +359,8 @@ impl<'gc> Loader<'gc> {
length.into(),
],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
}
let mut mc = clip
@ -359,7 +393,8 @@ impl<'gc> Loader<'gc> {
"broadcastMessage",
&["onLoadComplete".into(), Value::Object(broadcaster)],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
}
if let Some(Loader::Movie { load_complete, .. }) =
@ -377,14 +412,14 @@ impl<'gc> Loader<'gc> {
//This also can get errors from decoding an invalid SWF file,
//too. We should distinguish those to player code.
player.lock().expect("Could not lock player!!").update(
|avm, uc| -> Result<(), Error> {
|avm, uc| -> Result<(), LoaderError> {
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
Some(Loader::Movie {
target_clip,
target_broadcaster,
..
}) => (*target_clip, *target_broadcaster),
None => return Err("Load cancelled".into()),
None => return Err(LoaderError::Cancelled),
_ => unreachable!(),
};
@ -401,7 +436,8 @@ impl<'gc> Loader<'gc> {
"LoadNeverCompleted".into(),
],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
}
if let Some(Loader::Movie { load_complete, .. }) =
@ -420,11 +456,11 @@ impl<'gc> Loader<'gc> {
pub fn form_loader(
&mut self,
player: Weak<Mutex<Player>>,
fetch: OwnedFuture<Vec<u8>, Error>,
) -> OwnedFuture<(), Error> {
fetch: OwnedFuture<Vec<u8>, LoaderError>,
) -> OwnedFuture<(), LoaderError> {
let handle = match self {
Loader::Form { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
_ => return Box::pin(async { Err("Non-form loader spawned as form loader".into()) }),
_ => return Box::pin(async { Err(LoaderError::NotFormLoader) }),
};
let player = player
@ -438,12 +474,13 @@ impl<'gc> Loader<'gc> {
let loader = uc.load_manager.get_loader(handle);
let that = match loader {
Some(Loader::Form { target_object, .. }) => *target_object,
None => return Err("Load cancelled".into()),
_ => return Err("Non-movie loader spawned as movie loader".into()),
None => return Err(LoaderError::Cancelled),
_ => return Err(LoaderError::NotMovieLoader),
};
for (k, v) in form_urlencoded::parse(&data) {
that.set(&k, v.into_owned().into(), avm, uc)?;
that.set(&k, v.into_owned().into(), avm, uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
}
Ok(())
@ -497,11 +534,11 @@ impl<'gc> Loader<'gc> {
pub fn xml_loader(
&mut self,
player: Weak<Mutex<Player>>,
fetch: OwnedFuture<Vec<u8>, Error>,
) -> OwnedFuture<(), Error> {
fetch: OwnedFuture<Vec<u8>, LoaderError>,
) -> OwnedFuture<(), LoaderError> {
let handle = match self {
Loader::XML { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
_ => return Box::pin(async { Err("Non-XML loader spawned as XML loader".into()) }),
_ => return Box::pin(async { Err(LoaderError::NotXmlLoader) }),
};
let player = player
@ -511,17 +548,17 @@ impl<'gc> Loader<'gc> {
Box::pin(async move {
let data = fetch.await;
if let Ok(data) = data {
let xmlstring = String::from_utf8(data)?;
let xmlstring = String::from_utf8(data).map_err(LoaderError::InvalidXmlEncoding)?;
player.lock().expect("Could not lock player!!").update(
|avm, uc| -> Result<(), Error> {
|avm, uc| -> Result<(), LoaderError> {
let (mut node, active_clip) = match uc.load_manager.get_loader(handle) {
Some(Loader::XML {
target_node,
active_clip,
..
}) => (*target_node, *active_clip),
None => return Err("Load cancelled".into()),
None => return Err(LoaderError::Cancelled),
_ => unreachable!(),
};
@ -535,7 +572,8 @@ impl<'gc> Loader<'gc> {
"onHTTPStatus",
&[200.into()],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
avm.insert_stack_frame_for_method(
active_clip,
@ -545,21 +583,22 @@ impl<'gc> Loader<'gc> {
"onData",
&[xmlstring.into()],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
Ok(())
},
)?;
} else {
player.lock().expect("Could not lock player!!").update(
|avm, uc| -> Result<(), Error> {
|avm, uc| -> Result<(), LoaderError> {
let (mut node, active_clip) = match uc.load_manager.get_loader(handle) {
Some(Loader::XML {
target_node,
active_clip,
..
}) => (*target_node, *active_clip),
None => return Err("Load cancelled".into()),
None => return Err(LoaderError::Cancelled),
_ => unreachable!(),
};
@ -573,7 +612,8 @@ impl<'gc> Loader<'gc> {
"onHTTPStatus",
&[404.into()],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
avm.insert_stack_frame_for_method(
active_clip,
@ -583,7 +623,8 @@ impl<'gc> Loader<'gc> {
"onData",
&[],
);
avm.run_stack_till_empty(uc)?;
avm.run_stack_till_empty(uc)
.map_err(|e| LoaderError::Avm1Error(e))?;
Ok(())
},

View File

@ -3,7 +3,8 @@
use crate::custom_event::RuffleEvent;
use crate::task::Task;
use generational_arena::{Arena, Index};
use ruffle_core::backend::navigator::{Error, OwnedFuture};
use ruffle_core::backend::navigator::OwnedFuture;
use ruffle_core::loader::LoaderError;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex, Weak};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
@ -125,7 +126,7 @@ pub struct GlutinAsyncExecutor {
task_queue: Arena<Task>,
/// Source of tasks sent to us by the `NavigatorBackend`.
channel: Receiver<OwnedFuture<(), Error>>,
channel: Receiver<OwnedFuture<(), LoaderError>>,
/// Weak reference to ourselves.
self_ref: Weak<Mutex<Self>>,
@ -144,7 +145,7 @@ impl GlutinAsyncExecutor {
/// to spawn new tasks.
pub fn new(
event_loop: EventLoopProxy<RuffleEvent>,
) -> (Arc<Mutex<Self>>, Sender<OwnedFuture<(), Error>>) {
) -> (Arc<Mutex<Self>>, Sender<OwnedFuture<(), LoaderError>>) {
let (send, recv) = channel();
let new_self = Arc::new(Mutex::new(Self {
task_queue: Arena::new(),

View File

@ -2,8 +2,9 @@
use crate::custom_event::RuffleEvent;
use ruffle_core::backend::navigator::{
Error, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
};
use ruffle_core::loader::LoaderError;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
@ -16,7 +17,7 @@ use winit::event_loop::EventLoopProxy;
/// out to a web browser.
pub struct ExternalNavigatorBackend {
/// Sink for tasks sent to us through `spawn_future`.
channel: Sender<OwnedFuture<(), Error>>,
channel: Sender<OwnedFuture<(), LoaderError>>,
/// Event sink to trigger a new task poll.
event_loop: EventLoopProxy<RuffleEvent>,
@ -31,7 +32,7 @@ pub struct ExternalNavigatorBackend {
impl ExternalNavigatorBackend {
#[allow(dead_code)]
pub fn new(
channel: Sender<OwnedFuture<(), Error>>,
channel: Sender<OwnedFuture<(), LoaderError>>,
event_loop: EventLoopProxy<RuffleEvent>,
) -> Self {
Self {
@ -45,7 +46,7 @@ impl ExternalNavigatorBackend {
/// Construct a navigator backend with fetch and async capability.
pub fn with_base_path<P: AsRef<Path>>(
path: P,
channel: Sender<OwnedFuture<(), Error>>,
channel: Sender<OwnedFuture<(), LoaderError>>,
event_loop: EventLoopProxy<RuffleEvent>,
) -> Self {
let mut relative_base_path = PathBuf::new();
@ -110,16 +111,16 @@ impl NavigatorBackend for ExternalNavigatorBackend {
Instant::now().duration_since(self.start_time)
}
fn fetch(&self, url: &str, _options: RequestOptions) -> OwnedFuture<Vec<u8>, Error> {
fn fetch(&self, url: &str, _options: RequestOptions) -> OwnedFuture<Vec<u8>, LoaderError> {
// Load from local filesystem.
// TODO: Support network loads, honor sandbox type (local-with-filesystem, local-with-network, remote, ...)
let mut path = self.relative_base_path.clone();
path.push(url);
Box::pin(async move { fs::read(path).map_err(|e| e.into()) })
Box::pin(async move { fs::read(path).map_err(LoaderError::NetworkError) })
}
fn spawn_future(&mut self, future: OwnedFuture<(), Error>) {
fn spawn_future(&mut self, future: OwnedFuture<(), LoaderError>) {
self.channel.send(future).expect("working channel send");
if self.event_loop.send_event(RuffleEvent::TaskPoll).is_err() {

View File

@ -1,6 +1,7 @@
//! Task state information
use ruffle_core::backend::navigator::{Error, OwnedFuture};
use ruffle_core::backend::navigator::OwnedFuture;
use ruffle_core::loader::LoaderError;
use std::task::{Context, Poll};
/// Indicates the state of a given task.
@ -22,12 +23,12 @@ pub struct Task {
state: TaskState,
/// The future to poll in order to progress the task.
future: OwnedFuture<(), Error>,
future: OwnedFuture<(), LoaderError>,
}
impl Task {
/// Box an owned future into a task structure.
pub fn from_future(future: OwnedFuture<(), Error>) -> Self {
pub fn from_future(future: OwnedFuture<(), LoaderError>) -> Self {
Self {
state: TaskState::Ready,
future,
@ -54,7 +55,7 @@ impl Task {
///
/// This wrapper function ensures that futures cannot be polled after they
/// have completed. Future polls will return `Ok(())`.
pub fn poll(&mut self, context: &mut Context) -> Poll<Result<(), Error>> {
pub fn poll(&mut self, context: &mut Context) -> Poll<Result<(), LoaderError>> {
if self.is_completed() {
return Poll::Ready(Ok(()));
}

View File

@ -2,8 +2,9 @@
use js_sys::{Array, ArrayBuffer, Uint8Array};
use ruffle_core::backend::navigator::{
Error, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
};
use ruffle_core::loader::LoaderError;
use std::collections::HashMap;
use std::time::Duration;
use wasm_bindgen::JsCast;
@ -92,7 +93,7 @@ impl NavigatorBackend for WebNavigatorBackend {
Duration::from_millis(dt as u64)
}
fn fetch(&self, url: &str, options: RequestOptions) -> OwnedFuture<Vec<u8>, Error> {
fn fetch(&self, url: &str, options: RequestOptions) -> OwnedFuture<Vec<u8>, LoaderError> {
let url = url.to_string();
Box::pin(async move {
let mut init = RequestInit::new();
@ -130,7 +131,10 @@ impl NavigatorBackend for WebNavigatorBackend {
let window = web_sys::window().unwrap();
let fetchval = JsFuture::from(window.fetch_with_request(&request)).await;
if fetchval.is_err() {
return Err("Could not fetch, got JS Error".into());
return Err(LoaderError::NetworkError(std::io::Error::new(
std::io::ErrorKind::Other,
"Could not fetch, got JS Error",
)));
}
let resp: Response = fetchval.unwrap().dyn_into().unwrap();
@ -147,7 +151,7 @@ impl NavigatorBackend for WebNavigatorBackend {
})
}
fn spawn_future(&mut self, future: OwnedFuture<(), Error>) {
fn spawn_future(&mut self, future: OwnedFuture<(), LoaderError>) {
spawn_local(async move {
if let Err(e) = future.await {
log::error!("Asynchronous error occured: {}", e);