core: `Request` is now a trait. Body download is deferred to a second async method.

This commit is contained in:
David Wendt 2023-03-29 20:40:54 -04:00 committed by kmeisthax
parent 36210c9f20
commit 06eb2e1ee8
6 changed files with 237 additions and 101 deletions

View File

@ -6,6 +6,7 @@ use crate::string::WStr;
use async_channel::Receiver; use async_channel::Receiver;
use indexmap::IndexMap; use indexmap::IndexMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::fmt::Display; use std::fmt::Display;
use std::future::Future; use std::future::Future;
@ -186,18 +187,20 @@ impl Request {
} }
/// A response to a successful fetch request. /// A response to a successful fetch request.
pub struct SuccessResponse { pub trait SuccessResponse {
/// The final URL obtained after any redirects. /// The final URL obtained after any redirects.
pub url: String, fn url(&self) -> Cow<str>;
/// The contents of the response body. /// Retrieve the contents of the response body.
pub body: Vec<u8>, ///
/// This method consumes the response.
fn body(self: Box<Self>) -> OwnedFuture<Vec<u8>, Error>;
/// The status code of the response. /// The status code of the response.
pub status: u16, fn status(&self) -> u16;
/// The field to indicate if the request has been redirected. /// Indicates if the request has been redirected.
pub redirected: bool, fn redirected(&self) -> bool;
} }
/// A response to a non-successful fetch request. /// A response to a non-successful fetch request.
@ -245,7 +248,7 @@ pub trait NavigatorBackend {
); );
/// Fetch data and return it some time in the future. /// Fetch data and return it some time in the future.
fn fetch(&self, request: Request) -> OwnedFuture<SuccessResponse, ErrorResponse>; fn fetch(&self, request: Request) -> OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse>;
/// Take a URL string and resolve it to the actual URL from which a file /// Take a URL string and resolve it to the actual URL from which a file
/// can be fetched. This includes handling of relative links and pre-processing. /// can be fetched. This includes handling of relative links and pre-processing.
@ -402,7 +405,7 @@ impl NavigatorBackend for NullNavigatorBackend {
) { ) {
} }
fn fetch(&self, request: Request) -> OwnedFuture<SuccessResponse, ErrorResponse> { fn fetch(&self, request: Request) -> OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse> {
fetch_path(self, "NullNavigatorBackend", request.url(), None) fetch_path(self, "NullNavigatorBackend", request.url(), None)
} }
@ -449,7 +452,7 @@ pub fn async_return<SuccessType: 'static, ErrorType: 'static>(
pub fn create_fetch_error<ErrorType: Display>( pub fn create_fetch_error<ErrorType: Display>(
url: &str, url: &str,
error: ErrorType, error: ErrorType,
) -> Result<SuccessResponse, ErrorResponse> { ) -> Result<Box<dyn SuccessResponse>, ErrorResponse> {
create_specific_fetch_error("Invalid URL", url, error) create_specific_fetch_error("Invalid URL", url, error)
} }
@ -459,7 +462,7 @@ pub fn create_specific_fetch_error<ErrorType: Display>(
reason: &str, reason: &str,
url: &str, url: &str,
error: ErrorType, error: ErrorType,
) -> Result<SuccessResponse, ErrorResponse> { ) -> Result<Box<dyn SuccessResponse>, ErrorResponse> {
let message = if error.to_string() == "" { let message = if error.to_string() == "" {
format!("{reason} {url}") format!("{reason} {url}")
} else { } else {
@ -543,7 +546,34 @@ pub fn fetch_path<NavigatorType: NavigatorBackend>(
navigator_name: &str, navigator_name: &str,
url: &str, url: &str,
base_path: Option<&Path>, base_path: Option<&Path>,
) -> OwnedFuture<SuccessResponse, ErrorResponse> { ) -> OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse> {
struct LocalResponse {
url: String,
path: PathBuf,
status: u16,
redirected: bool,
}
impl SuccessResponse for LocalResponse {
fn url(&self) -> Cow<str> {
Cow::Borrowed(&self.url)
}
fn body(self: Box<Self>) -> OwnedFuture<Vec<u8>, Error> {
Box::pin(async move {
std::fs::read(self.path).map_err(|e| Error::FetchError(e.to_string()))
})
}
fn status(&self) -> u16 {
self.status
}
fn redirected(&self) -> bool {
self.redirected
}
}
let url = match navigator.resolve_url(url) { let url = match navigator.resolve_url(url) {
Ok(url) => url, Ok(url) => url,
Err(e) => return async_return(create_fetch_error(url, e)), Err(e) => return async_return(create_fetch_error(url, e)),
@ -584,15 +614,13 @@ pub fn fetch_path<NavigatorType: NavigatorBackend>(
}; };
Box::pin(async move { Box::pin(async move {
let body = match std::fs::read(path) { let response: Box<dyn SuccessResponse> = Box::new(LocalResponse {
Ok(body) => body,
Err(e) => return create_specific_fetch_error("Can't open file", url.as_str(), e),
};
Ok(SuccessResponse {
url: url.to_string(), url: url.to_string(),
body, path,
status: 0, status: 0,
redirected: false, redirected: false,
}) });
Ok(response)
}) })
} }

View File

@ -13,7 +13,7 @@ use crate::avm2::{
Activation as Avm2Activation, Avm2, BitmapDataObject, Domain as Avm2Domain, Activation as Avm2Activation, Avm2, BitmapDataObject, Domain as Avm2Domain,
Object as Avm2Object, Value as Avm2Value, Object as Avm2Object, Value as Avm2Value,
}; };
use crate::backend::navigator::{OwnedFuture, Request}; use crate::backend::navigator::{ErrorResponse, OwnedFuture, Request, SuccessResponse};
use crate::backend::ui::DialogResultFuture; use crate::backend::ui::DialogResultFuture;
use crate::bitmap::bitmap_data::Color; use crate::bitmap::bitmap_data::Color;
use crate::bitmap::bitmap_data::{BitmapData, BitmapDataWrapper}; use crate::bitmap::bitmap_data::{BitmapData, BitmapDataWrapper};
@ -854,6 +854,21 @@ impl<'gc> Loader<'gc> {
Ok(did_finish) Ok(did_finish)
} }
async fn wait_for_full_response(
response: OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse>,
) -> Result<(Vec<u8>, String, u16, bool), ErrorResponse> {
let response = response.await?;
let url = response.url().to_string();
let status = response.status();
let redirected = response.redirected();
let body = response.body().await;
match body {
Ok(body) => Ok((body, url, status, redirected)),
Err(error) => Err(ErrorResponse { url, error }),
}
}
/// Construct a future for the root movie loader. /// Construct a future for the root movie loader.
fn root_movie_loader( fn root_movie_loader(
&mut self, &mut self,
@ -875,7 +890,6 @@ impl<'gc> Loader<'gc> {
Box::pin(async move { Box::pin(async move {
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let response = fetch.await.map_err(|error| { let response = fetch.await.map_err(|error| {
player player
.lock() .lock()
@ -884,13 +898,22 @@ impl<'gc> Loader<'gc> {
.display_root_movie_download_failed_message(false); .display_root_movie_download_failed_message(false);
error.error error.error
})?; })?;
let url = response.url().into_owned();
let body = response.body().await.map_err(|error| {
player
.lock()
.unwrap()
.ui()
.display_root_movie_download_failed_message(true);
error
})?;
// The spoofed root movie URL takes precedence over the actual URL. // The spoofed root movie URL takes precedence over the actual URL.
let swf_url = player let swf_url = player
.lock() .lock()
.unwrap() .unwrap()
.compatibility_rules() .compatibility_rules()
.rewrite_swf_url(response.url); .rewrite_swf_url(url);
let spoofed_or_swf_url = player let spoofed_or_swf_url = player
.lock() .lock()
.unwrap() .unwrap()
@ -899,7 +922,7 @@ impl<'gc> Loader<'gc> {
.unwrap_or(swf_url); .unwrap_or(swf_url);
let mut movie = let mut movie =
SwfMovie::from_data(&response.body, spoofed_or_swf_url, None).map_err(|error| { SwfMovie::from_data(&body, spoofed_or_swf_url, None).map_err(|error| {
player player
.lock() .lock()
.unwrap() .unwrap()
@ -970,25 +993,25 @@ impl<'gc> Loader<'gc> {
Loader::movie_loader_start(handle, uc) Loader::movie_loader_start(handle, uc)
})?; })?;
match fetch.await { match Self::wait_for_full_response(fetch).await {
Ok(response) if replacing_root_movie => { Ok((body, url, _status, _redirected)) if replacing_root_movie => {
ContentType::sniff(&response.body).expect(ContentType::Swf)?; ContentType::sniff(&body).expect(ContentType::Swf)?;
let movie = SwfMovie::from_data(&response.body, response.url, loader_url)?; let movie = SwfMovie::from_data(&body, url.to_string(), loader_url)?;
player.lock().unwrap().mutate_with_update_context(|uc| { player.lock().unwrap().mutate_with_update_context(|uc| {
uc.set_root_movie(movie); uc.set_root_movie(movie);
}); });
return Ok(()); return Ok(());
} }
Ok(response) => { Ok((body, url, status, redirected)) => {
player.lock().unwrap().mutate_with_update_context(|uc| { player.lock().unwrap().mutate_with_update_context(|uc| {
Loader::movie_loader_data( Loader::movie_loader_data(
handle, handle,
uc, uc,
&response.body, &body,
response.url, url.to_string(),
response.status, status,
response.redirected, redirected,
loader_url, loader_url,
) )
})?; })?;
@ -1084,6 +1107,7 @@ impl<'gc> Loader<'gc> {
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let response = fetch.await.map_err(|e| e.error)?; let response = fetch.await.map_err(|e| e.error)?;
let body = response.body().await?;
// Fire the load handler. // Fire the load handler.
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
@ -1099,7 +1123,7 @@ impl<'gc> Loader<'gc> {
ActivationIdentifier::root("[Form Loader]"), ActivationIdentifier::root("[Form Loader]"),
); );
for (k, v) in form_urlencoded::parse(&response.body) { for (k, v) in form_urlencoded::parse(&body) {
let k = AvmString::new_utf8(activation.context.gc_context, k); let k = AvmString::new_utf8(activation.context.gc_context, k);
let v = AvmString::new_utf8(activation.context.gc_context, v); let v = AvmString::new_utf8(activation.context.gc_context, v);
that.set(k, v.into(), &mut activation)?; that.set(k, v.into(), &mut activation)?;
@ -1145,8 +1169,7 @@ impl<'gc> Loader<'gc> {
Box::pin(async move { Box::pin(async move {
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let response = Self::wait_for_full_response(fetch).await;
let data = fetch.await;
// Fire the load handler. // Fire the load handler.
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
@ -1160,9 +1183,9 @@ impl<'gc> Loader<'gc> {
let mut activation = let mut activation =
Activation::from_stub(uc.reborrow(), ActivationIdentifier::root("[Loader]")); Activation::from_stub(uc.reborrow(), ActivationIdentifier::root("[Loader]"));
match data { match response {
Ok(response) => { Ok((body, _, status, _)) => {
let length = response.body.len(); let length = body.len();
// Set the properties used by the getBytesTotal and getBytesLoaded methods. // Set the properties used by the getBytesTotal and getBytesLoaded methods.
that.set("_bytesTotal", length.into(), &mut activation)?; that.set("_bytesTotal", length.into(), &mut activation)?;
@ -1172,7 +1195,7 @@ impl<'gc> Loader<'gc> {
let _ = that.call_method( let _ = that.call_method(
"onHTTPStatus".into(), "onHTTPStatus".into(),
&[response.status.into()], &[status.into()],
&mut activation, &mut activation,
ExecutionReason::Special, ExecutionReason::Special,
); );
@ -1184,7 +1207,7 @@ impl<'gc> Loader<'gc> {
} else { } else {
AvmString::new_utf8( AvmString::new_utf8(
activation.context.gc_context, activation.context.gc_context,
UTF_8.decode(&response.body).0, UTF_8.decode(&body).0,
) )
.into() .into()
}; };
@ -1247,7 +1270,7 @@ impl<'gc> Loader<'gc> {
Box::pin(async move { Box::pin(async move {
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let response = fetch.await; let response = Self::wait_for_full_response(fetch).await;
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
let loader = uc.load_manager.get_loader(handle); let loader = uc.load_manager.get_loader(handle);
@ -1299,8 +1322,8 @@ impl<'gc> Loader<'gc> {
} }
match response { match response {
Ok(response) => { Ok((body, _, status, redirected)) => {
let total_len = response.body.len(); let total_len = body.len();
// FIXME - the "open" event should be fired earlier, just before // FIXME - the "open" event should be fired earlier, just before
// we start to fetch the data. // we start to fetch the data.
@ -1314,7 +1337,7 @@ impl<'gc> Loader<'gc> {
let open_evt = let open_evt =
Avm2EventObject::bare_default_event(&mut activation.context, "open"); Avm2EventObject::bare_default_event(&mut activation.context, "open");
Avm2::dispatch_event(&mut activation.context, open_evt, target); Avm2::dispatch_event(&mut activation.context, open_evt, target);
set_data(response.body, &mut activation, target, data_format); set_data(body, &mut activation, target, data_format);
// FIXME - we should fire "progress" events as we receive data, not // FIXME - we should fire "progress" events as we receive data, not
// just at the end // just at the end
@ -1346,8 +1369,8 @@ impl<'gc> Loader<'gc> {
"httpStatus".into(), "httpStatus".into(),
false.into(), false.into(),
false.into(), false.into(),
response.status.into(), status.into(),
response.redirected.into(), redirected.into(),
], ],
) )
.map_err(|e| Error::Avm2Error(e.to_string()))?; .map_err(|e| Error::Avm2Error(e.to_string()))?;
@ -1436,7 +1459,7 @@ impl<'gc> Loader<'gc> {
Box::pin(async move { Box::pin(async move {
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let data = fetch.await; let response = Self::wait_for_full_response(fetch).await;
// Fire the load handler. // Fire the load handler.
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
@ -1447,10 +1470,10 @@ impl<'gc> Loader<'gc> {
_ => return Err(Error::NotSoundLoader), _ => return Err(Error::NotSoundLoader),
}; };
let success = data let success = response
.map_err(|e| e.error) .map_err(|e| e.error)
.and_then(|data| { .and_then(|(body, _, _, _)| {
let handle = uc.audio.register_mp3(&data.body)?; let handle = uc.audio.register_mp3(&body)?;
sound_object.set_sound(uc.gc_context, Some(handle)); sound_object.set_sound(uc.gc_context, Some(handle));
let duration = uc let duration = uc
.audio .audio
@ -1499,7 +1522,7 @@ impl<'gc> Loader<'gc> {
Box::pin(async move { Box::pin(async move {
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let response = fetch.await; let response = Self::wait_for_full_response(fetch).await;
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
let loader = uc.load_manager.get_loader(handle); let loader = uc.load_manager.get_loader(handle);
@ -1510,8 +1533,8 @@ impl<'gc> Loader<'gc> {
}; };
match response { match response {
Ok(response) => { Ok((body, _, _, _)) => {
let handle = uc.audio.register_mp3(&response.body)?; let handle = uc.audio.register_mp3(&body)?;
if let Err(e) = sound_object if let Err(e) = sound_object
.as_sound_object() .as_sound_object()
.expect("Not a sound object") .expect("Not a sound object")
@ -1520,7 +1543,7 @@ impl<'gc> Loader<'gc> {
tracing::error!("Encountered AVM2 error when setting sound: {}", e); tracing::error!("Encountered AVM2 error when setting sound: {}", e);
} }
let total_len = response.body.len(); let total_len = body.len();
// FIXME - the "open" event should be fired earlier, and not fired in case of ioerror. // FIXME - the "open" event should be fired earlier, and not fired in case of ioerror.
let mut activation = Avm2Activation::from_nothing(uc.reborrow()); let mut activation = Avm2Activation::from_nothing(uc.reborrow());
@ -1598,7 +1621,7 @@ impl<'gc> Loader<'gc> {
Box::pin(async move { Box::pin(async move {
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let response = fetch.await; let response = Self::wait_for_full_response(fetch).await;
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
let loader = uc.load_manager.get_loader(handle); let loader = uc.load_manager.get_loader(handle);
@ -1609,9 +1632,9 @@ impl<'gc> Loader<'gc> {
}; };
match response { match response {
Ok(mut response) => { Ok((mut body, _, _, _)) => {
stream.reset_buffer(uc); stream.reset_buffer(uc);
stream.load_buffer(uc, &mut response.body); stream.load_buffer(uc, &mut body);
} }
Err(response) => { Err(response) => {
stream.report_error(response.error); stream.report_error(response.error);
@ -2702,7 +2725,7 @@ impl<'gc> Loader<'gc> {
let req = Request::get(url.clone()); let req = Request::get(url.clone());
// Doing this in two steps to prevent holding the player lock during fetch // Doing this in two steps to prevent holding the player lock during fetch
let future = player.lock().unwrap().navigator().fetch(req); let future = player.lock().unwrap().navigator().fetch(req);
let download_res = future.await; let download_res = Self::wait_for_full_response(future).await;
// Fire the load handler. // Fire the load handler.
player.lock().unwrap().update(|uc| -> Result<(), Error> { player.lock().unwrap().update(|uc| -> Result<(), Error> {
@ -2740,7 +2763,7 @@ impl<'gc> Loader<'gc> {
)?; )?;
match download_res { match download_res {
Ok(download_res) => { Ok((body, _, _, _)) => {
as_broadcaster::broadcast_internal( as_broadcaster::broadcast_internal(
&mut activation, &mut activation,
target_object, target_object,
@ -2753,14 +2776,14 @@ impl<'gc> Loader<'gc> {
// perspective of AS, we want to refresh the file_ref internal data // perspective of AS, we want to refresh the file_ref internal data
// before invoking the callbacks // before invoking the callbacks
dialog_result.write(&download_res.body); dialog_result.write(&body);
dialog_result.refresh(); dialog_result.refresh();
file_ref.init_from_dialog_result( file_ref.init_from_dialog_result(
&mut activation, &mut activation,
dialog_result.borrow(), dialog_result.borrow(),
); );
let total_bytes = download_res.body.len(); let total_bytes = body.len();
as_broadcaster::broadcast_internal( as_broadcaster::broadcast_internal(
&mut activation, &mut activation,

View File

@ -2,7 +2,7 @@ use crate::avm2::object::{
NetConnectionObject as Avm2NetConnectionObject, ResponderObject as Avm2ResponderObject, NetConnectionObject as Avm2NetConnectionObject, ResponderObject as Avm2ResponderObject,
}; };
use crate::avm2::{Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject}; use crate::avm2::{Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject};
use crate::backend::navigator::{NavigatorBackend, OwnedFuture, Request}; use crate::backend::navigator::{ErrorResponse, NavigatorBackend, OwnedFuture, Request};
use crate::context::UpdateContext; use crate::context::UpdateContext;
use crate::loader::Error; use crate::loader::Error;
use crate::string::AvmString; use crate::string::AvmString;
@ -455,7 +455,18 @@ impl FlashRemoting {
.expect("Must be able to serialize a packet"); .expect("Must be able to serialize a packet");
let request = Request::post(url, Some((bytes, "application/x-amf".to_string()))); let request = Request::post(url, Some((bytes, "application/x-amf".to_string())));
let fetch = player.lock().unwrap().navigator().fetch(request); let fetch = player.lock().unwrap().navigator().fetch(request);
let response = match fetch.await { let response: Result<_, ErrorResponse> = async {
let response = fetch.await?;
let url = response.url().to_string();
let body = response
.body()
.await
.map_err(|error| ErrorResponse { url, error })?;
Ok(body)
}
.await;
let response = match response {
Ok(response) => response, Ok(response) => response,
Err(response) => { Err(response) => {
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
@ -497,7 +508,7 @@ impl FlashRemoting {
}; };
// Flash completely ignores invalid responses, it seems // Flash completely ignores invalid responses, it seems
if let Ok(response_packet) = flash_lso::packet::read::parse(&response.body) { if let Ok(response_packet) = flash_lso::packet::read::parse(&response) {
player.lock().unwrap().update(|uc| { player.lock().unwrap().update(|uc| {
for message in response_packet.messages { for message in response_packet.messages {
if let Some(target_uri) = message.target_uri.strip_prefix('/') { if let Some(target_uri) = message.target_uri.strip_prefix('/') {

View File

@ -171,7 +171,32 @@ impl NavigatorBackend for ExternalNavigatorBackend {
}; };
} }
fn fetch(&self, request: Request) -> OwnedFuture<SuccessResponse, ErrorResponse> { fn fetch(&self, request: Request) -> OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse> {
struct DesktopResponse {
url: String,
body: Vec<u8>,
status: u16,
redirected: bool,
}
impl SuccessResponse for DesktopResponse {
fn url(&self) -> std::borrow::Cow<str> {
std::borrow::Cow::Borrowed(&self.url)
}
fn body(self: Box<Self>) -> OwnedFuture<Vec<u8>, Error> {
Box::pin(async { Ok(self.body) })
}
fn status(&self) -> u16 {
self.status
}
fn redirected(&self) -> bool {
self.redirected
}
}
// TODO: honor sandbox type (local-with-filesystem, local-with-network, remote, ...) // TODO: honor sandbox type (local-with-filesystem, local-with-network, remote, ...)
let mut processed_url = match self.resolve_url(request.url()) { let mut processed_url = match self.resolve_url(request.url()) {
Ok(url) => url, Ok(url) => url,
@ -236,12 +261,14 @@ impl NavigatorBackend for ExternalNavigatorBackend {
} }
}; };
Ok(SuccessResponse { let response: Box<dyn SuccessResponse> = Box::new(DesktopResponse {
url: response_url.to_string(), url: response_url.to_string(),
body, body,
status: 0, status: 0,
redirected: false, redirected: false,
}) });
Ok(response)
}), }),
_ => Box::pin(async move { _ => Box::pin(async move {
let client = client.ok_or_else(|| ErrorResponse { let client = client.ok_or_else(|| ErrorResponse {
@ -321,12 +348,13 @@ impl NavigatorBackend for ExternalNavigatorBackend {
error: Error::FetchError(e.to_string()), error: Error::FetchError(e.to_string()),
})?; })?;
Ok(SuccessResponse { let response: Box<dyn SuccessResponse> = Box::new(DesktopResponse {
url, url,
body, body,
status, status,
redirected, redirected,
}) });
Ok(response)
}), }),
} }
} }

View File

@ -11,11 +11,37 @@ use ruffle_core::indexmap::IndexMap;
use ruffle_core::loader::Error; use ruffle_core::loader::Error;
use ruffle_core::socket::{ConnectionState, SocketAction, SocketHandle}; use ruffle_core::socket::{ConnectionState, SocketAction, SocketHandle};
use ruffle_socket_format::SocketEvent; use ruffle_socket_format::SocketEvent;
use std::borrow::Cow;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::time::Duration; use std::time::Duration;
use url::{ParseError, Url}; use url::{ParseError, Url};
use vfs::VfsPath; use vfs::VfsPath;
struct TestResponse {
url: String,
body: Vec<u8>,
status: u16,
redirected: bool,
}
impl SuccessResponse for TestResponse {
fn url(&self) -> Cow<str> {
Cow::Borrowed(&self.url)
}
fn body(self: Box<Self>) -> OwnedFuture<Vec<u8>, Error> {
Box::pin(async move { Ok(self.body) })
}
fn status(&self) -> u16 {
self.status
}
fn redirected(&self) -> bool {
self.redirected
}
}
/// A `NavigatorBackend` used by tests that supports logging fetch requests. /// A `NavigatorBackend` used by tests that supports logging fetch requests.
/// ///
/// This can be used by tests that fetch data to verify that the request is correct. /// This can be used by tests that fetch data to verify that the request is correct.
@ -71,15 +97,17 @@ impl NavigatorBackend for TestNavigatorBackend {
} }
} }
fn fetch(&self, request: Request) -> OwnedFuture<SuccessResponse, ErrorResponse> { fn fetch(&self, request: Request) -> OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse> {
if request.url().contains("?debug-success") { if request.url().contains("?debug-success") {
return Box::pin(async move { return Box::pin(async move {
Ok(SuccessResponse { let response: Box<dyn SuccessResponse> = Box::new(TestResponse {
url: request.url().to_string(), url: request.url().to_string(),
body: b"Hello, World!".to_vec(), body: b"Hello, World!".to_vec(),
status: 200, status: 200,
redirected: false, redirected: false,
}) });
Ok(response)
}); });
} }
@ -184,12 +212,15 @@ impl NavigatorBackend for TestNavigatorBackend {
url: url.to_string(), url: url.to_string(),
error: Error::FetchError(error.to_string()), error: Error::FetchError(error.to_string()),
})?; })?;
Ok(SuccessResponse {
let response: Box<dyn SuccessResponse> = Box::new(TestResponse {
url: url.to_string(), url: url.to_string(),
body, body,
status: 0, status: 0,
redirected: false, redirected: false,
}) });
Ok(response)
}) })
} }

View File

@ -3,7 +3,7 @@ use crate::SocketProxy;
use async_channel::Receiver; use async_channel::Receiver;
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
use gloo_net::websocket::{futures::WebSocket, Message}; use gloo_net::websocket::{futures::WebSocket, Message};
use js_sys::{Array, ArrayBuffer, Uint8Array}; use js_sys::{Array, Uint8Array};
use ruffle_core::backend::navigator::{ use ruffle_core::backend::navigator::{
async_return, create_fetch_error, create_specific_fetch_error, ErrorResponse, NavigationMethod, async_return, create_fetch_error, create_specific_fetch_error, ErrorResponse, NavigationMethod,
NavigatorBackend, OpenURLMode, OwnedFuture, Request, SuccessResponse, NavigatorBackend, OpenURLMode, OwnedFuture, Request, SuccessResponse,
@ -12,6 +12,7 @@ use ruffle_core::config::NetworkingAccessMode;
use ruffle_core::indexmap::IndexMap; use ruffle_core::indexmap::IndexMap;
use ruffle_core::loader::Error; use ruffle_core::loader::Error;
use ruffle_core::socket::{ConnectionState, SocketAction, SocketHandle}; use ruffle_core::socket::{ConnectionState, SocketAction, SocketHandle};
use std::borrow::Cow;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@ -223,7 +224,7 @@ impl NavigatorBackend for WebNavigatorBackend {
}; };
} }
fn fetch(&self, request: Request) -> OwnedFuture<SuccessResponse, ErrorResponse> { fn fetch(&self, request: Request) -> OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse> {
let url = match self.resolve_url(request.url()) { let url = match self.resolve_url(request.url()) {
Ok(url) => { Ok(url) => {
if url.scheme() == "file" { if url.scheme() == "file" {
@ -326,32 +327,9 @@ impl NavigatorBackend for WebNavigatorBackend {
return Err(ErrorResponse { url, error }); return Err(ErrorResponse { url, error });
} }
let body: ArrayBuffer = JsFuture::from(response.array_buffer().map_err(|_| { let wrapper: Box<dyn SuccessResponse> = Box::new(WebResponseWrapper(response));
ErrorResponse {
url: url.clone(),
error: Error::FetchError("Got JS error".to_string()),
}
})?)
.await
.map_err(|_| ErrorResponse {
url: url.clone(),
error: Error::FetchError(
"Could not allocate array buffer for response".to_string(),
),
})?
.dyn_into()
.map_err(|_| ErrorResponse {
url: url.clone(),
error: Error::FetchError("array_buffer result wasn't an ArrayBuffer".to_string()),
})?;
let body = Uint8Array::new(&body).to_vec();
Ok(SuccessResponse { Ok(wrapper)
url,
body,
status,
redirected,
})
}) })
} }
@ -462,3 +440,40 @@ impl NavigatorBackend for WebNavigatorBackend {
})); }));
} }
} }
struct WebResponseWrapper(WebResponse);
impl SuccessResponse for WebResponseWrapper {
fn url(&self) -> Cow<str> {
Cow::Owned(self.0.url())
}
fn body(self: Box<Self>) -> OwnedFuture<Vec<u8>, Error> {
Box::pin(async move {
let body = JsFuture::from(
self.0
.array_buffer()
.map_err(|_| Error::FetchError("Got JS error".to_string()))?,
)
.await
.map_err(|_| {
Error::FetchError("Could not allocate array buffer for response".to_string())
})?
.dyn_into()
.map_err(|_| {
Error::FetchError("array_buffer result wasn't an ArrayBuffer".to_string())
})?;
let body = Uint8Array::new(&body).to_vec();
Ok(body)
})
}
fn status(&self) -> u16 {
self.0.status()
}
fn redirected(&self) -> bool {
self.0.redirected()
}
}