From 6998dafdb9c8c443b544f835f101af0c530cecc6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 21 Jul 2020 23:57:43 -0400 Subject: [PATCH 01/14] Store an origin URL on every movie that is loaded. --- core/src/avm1/activation.rs | 2 ++ core/src/avm1/globals/movie_clip.rs | 1 + core/src/avm1/globals/movie_clip_loader.rs | 1 + core/src/display_object/button.rs | 3 ++- core/src/display_object/movie_clip.rs | 2 +- core/src/loader.rs | 7 ++++-- core/src/tag_utils.rs | 26 +++++++++++++++------- web/src/lib.rs | 2 +- 8 files changed, 31 insertions(+), 13 deletions(-) diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 402f451a0..a0c1ba396 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -1246,6 +1246,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context.player.clone().unwrap(), level, fetch, + url, None, ); context.navigator.spawn_future(process); @@ -1336,6 +1337,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context.player.clone().unwrap(), clip_target, fetch, + url.to_string(), None, ); context.navigator.spawn_future(process); diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 05f276f22..a4bd722cc 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -1096,6 +1096,7 @@ fn load_movie<'gc>( context.player.clone().unwrap(), DisplayObject::MovieClip(target), fetch, + url.to_string(), None, ); diff --git a/core/src/avm1/globals/movie_clip_loader.rs b/core/src/avm1/globals/movie_clip_loader.rs index d52b63d0b..e5db9557d 100644 --- a/core/src/avm1/globals/movie_clip_loader.rs +++ b/core/src/avm1/globals/movie_clip_loader.rs @@ -135,6 +135,7 @@ pub fn load_clip<'gc>( context.player.clone().unwrap(), DisplayObject::MovieClip(movieclip), fetch, + url.to_string(), Some(this), ); diff --git a/core/src/display_object/button.rs b/core/src/display_object/button.rs index 4199705f6..ad6bda6b0 100644 --- a/core/src/display_object/button.rs +++ b/core/src/display_object/button.rs @@ -34,7 +34,8 @@ impl<'gc> Button<'gc> { ) -> Self { let mut actions = vec![]; for action in &button.actions { - let action_data = source_movie.owned_subslice(action.action_data.clone()); + let action_data = + source_movie.owned_subslice(action.action_data.clone(), &source_movie.movie); for condition in &action.conditions { let button_action = ButtonAction { action_data: action_data.clone(), diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index c95e76b53..4c7d316e8 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -2567,7 +2567,7 @@ impl ClipAction { let len = other.action_data.len(); let key_code = other.key_code; - let movie = Arc::new(movie.from_movie_and_subdata(other.action_data)); + let movie = Arc::new(movie.from_movie_and_subdata(other.action_data, &movie)); other.events.into_iter().map(move |event| Self { event: match event { ClipEventFlag::Construct => ClipEvent::Construct, diff --git a/core/src/loader.rs b/core/src/loader.rs index 1384d40ce..f15d4ae3f 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -116,6 +116,7 @@ impl<'gc> LoadManager<'gc> { player: Weak>, target_clip: DisplayObject<'gc>, fetch: OwnedFuture, Error>, + url: String, target_broadcaster: Option>, ) -> OwnedFuture<(), Error> { let loader = Loader::Movie { @@ -129,7 +130,7 @@ impl<'gc> LoadManager<'gc> { let loader = self.get_loader_mut(handle).unwrap(); loader.introduce_loader_handle(handle); - loader.movie_loader(player, fetch) + loader.movie_loader(player, fetch, url) } /// Indicates that a movie clip has initialized (ran it's first frame). @@ -331,6 +332,7 @@ impl<'gc> Loader<'gc> { &mut self, player: Weak>, fetch: OwnedFuture, Error>, + url: String, ) -> OwnedFuture<(), Error> { let handle = match self { Loader::Movie { self_handle, .. } => self_handle.expect("Loader not self-introduced"), @@ -375,7 +377,8 @@ impl<'gc> Loader<'gc> { }, )?; - 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, Some(url))?))); if let Ok((length, movie)) = data { let movie = Arc::new(movie); diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index e4f503c71..e6fdbc834 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -17,6 +17,9 @@ pub struct SwfMovie { /// Uncompressed SWF data. data: Vec, + + /// The URL the SWF was downloaded from. + url: Option, } impl SwfMovie { @@ -31,25 +34,32 @@ impl SwfMovie { num_frames: 0, }, data: vec![], + url: None, } } - /// Construct a movie from an existing movie with any particular data on it. - pub fn from_movie_and_subdata(&self, data: Vec) -> Self { + /// Construct a movie from an existing movie with any particular data on + /// it. + /// + /// Use of this method is discouraged. SWF data should be borrowed or + /// sliced as necessary to refer to partial sections of a file. + pub fn from_movie_and_subdata(&self, data: Vec, source: &SwfMovie) -> Self { Self { header: self.header.clone(), data, + url: source.url.clone(), } } /// Utility method to construct a movie from a file on disk. pub fn from_path>(path: P) -> Result { + let url = path.as_ref().to_string_lossy().to_owned().to_string(); let data = std::fs::read(path)?; - Self::from_data(&data) + Self::from_data(&data, Some(url)) } /// Construct a movie based on the contents of the SWF datastream. - pub fn from_data(swf_data: &[u8]) -> Result { + pub fn from_data(swf_data: &[u8], url: Option) -> Result { let swf_stream = swf::read::read_swf_header(&swf_data[..])?; let header = swf_stream.header; let mut reader = swf_stream.reader; @@ -74,7 +84,7 @@ impl SwfMovie { data }; - Ok(Self { header, data }) + Ok(Self { header, data, url }) } pub fn header(&self) -> &Header { @@ -141,12 +151,12 @@ impl SwfSlice { /// Construct a new slice with a given dataset only. /// /// This is used primarily for converting owned data back into a slice: we - /// reattach the SWF data that we can - pub fn owned_subslice(&self, data: Vec) -> Self { + /// reattach the SWF data to a fresh movie and return a new slice into it. + pub fn owned_subslice(&self, data: Vec, source: &SwfMovie) -> Self { let len = data.len(); Self { - movie: Arc::new(self.movie.from_movie_and_subdata(data)), + movie: Arc::new(self.movie.from_movie_and_subdata(data, source)), start: 0, end: len, } diff --git a/web/src/lib.rs b/web/src/lib.rs index 7398d3eb3..31483e910 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -113,7 +113,7 @@ impl Ruffle { let movie = { let mut data = vec![0; swf_data.length() as usize]; swf_data.copy_to(&mut data[..]); - SwfMovie::from_data(&data)? + SwfMovie::from_data(&data, None)? }; let window = web_sys::window().ok_or_else(|| "Expected window")?; From f56d16a68d7518359c896ee59da54a9f6d13b313 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 22 Jul 2020 23:18:30 -0400 Subject: [PATCH 02/14] Separate player creation from root movie setup, and allow users of Ruffle to load in movies synchronously or asynchronously. During the small period of time when a player is created but has no root movie, a temporary empty movie is installed with an assumed stage size and framerate of 550x400@12fps. This is Flash default for new projects, so it seemed appropriate. User ActionScript cannot see these values, and I'm not even sure JavaScript can, either. --- core/src/font.rs | 4 +- core/src/loader.rs | 68 +++++++++++++ core/src/player.rs | 131 +++++++++++++++---------- core/tests/regression_tests.rs | 3 +- desktop/src/main.rs | 4 +- exporter/src/main.rs | 3 +- web/packages/core/src/ruffle-player.js | 75 +++++++++----- web/src/lib.rs | 45 ++++++--- 8 files changed, 239 insertions(+), 94 deletions(-) diff --git a/core/src/font.rs b/core/src/font.rs index 91656d94a..2a3126b27 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -424,6 +424,7 @@ mod tests { use crate::font::{EvalParameters, Font}; use crate::player::{Player, DEVICE_FONT_TAG}; use gc_arena::{rootless_arena, MutationContext}; + use std::ops::DerefMut; use swf::Twips; fn with_device_font(callback: F) @@ -432,7 +433,8 @@ mod tests { { rootless_arena(|mc| { let mut renderer: Box = Box::new(NullRenderer::new()); - let device_font = Player::load_device_font(mc, DEVICE_FONT_TAG, &mut renderer).unwrap(); + let device_font = + Player::load_device_font(mc, DEVICE_FONT_TAG, renderer.deref_mut()).unwrap(); callback(mc, device_font); }) diff --git a/core/src/loader.rs b/core/src/loader.rs index f15d4ae3f..c2fa32da9 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -22,6 +22,9 @@ pub enum Error { #[error("Load cancelled")] Cancelled, + #[error("Non-root-movie loader spawned as root movie loader")] + NotRootMovieLoader, + #[error("Non-movie loader spawned as movie loader")] NotMovieLoader, @@ -108,6 +111,27 @@ impl<'gc> LoadManager<'gc> { self.0.get_mut(handle) } + /// Kick off the root movie load. + /// + /// The root movie is special because it determines a few bits of player + /// state, such as the size of the stage and the current frame rate. Ergo, + /// this method should only be called once, by the player that is trying to + /// kick off it's root movie load. + pub fn load_root_movie( + &mut self, + player: Weak>, + fetch: OwnedFuture, Error>, + url: String, + ) -> OwnedFuture<(), Error> { + let loader = Loader::RootMovie { self_handle: None }; + let handle = self.add_loader(loader); + + let loader = self.get_loader_mut(handle).unwrap(); + loader.introduce_loader_handle(handle); + + loader.root_movie_loader(player, fetch, url) + } + /// Kick off a movie clip load. /// /// Returns the loader's async process, which you will need to spawn. @@ -230,6 +254,12 @@ impl<'gc> Default for LoadManager<'gc> { /// A struct that holds garbage-collected pointers for asynchronous code. pub enum Loader<'gc> { + /// Loader that is loading the root movie of a player. + RootMovie { + /// The handle to refer to this loader instance. + self_handle: Option, + }, + /// Loader that is loading a new movie into a movieclip. Movie { /// The handle to refer to this loader instance. @@ -292,6 +322,7 @@ pub enum Loader<'gc> { unsafe impl<'gc> Collect for Loader<'gc> { fn trace(&self, cc: CollectionContext) { match self { + Loader::RootMovie { .. } => {} Loader::Movie { target_clip, target_broadcaster, @@ -314,6 +345,7 @@ impl<'gc> Loader<'gc> { /// run. pub fn introduce_loader_handle(&mut self, handle: Handle) { match self { + Loader::RootMovie { self_handle, .. } => *self_handle = Some(handle), Loader::Movie { self_handle, .. } => *self_handle = Some(handle), Loader::Form { self_handle, .. } => *self_handle = Some(handle), Loader::LoadVars { self_handle, .. } => *self_handle = Some(handle), @@ -321,6 +353,42 @@ impl<'gc> Loader<'gc> { } } + /// Construct a future for the root movie loader. + pub fn root_movie_loader( + &mut self, + player: Weak>, + fetch: OwnedFuture, Error>, + url: String, + ) -> 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 { + let data = (fetch.await) + .and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?))); + + if let Ok((_length, movie)) = data { + player.lock().unwrap().set_root_movie(Arc::new(movie)); + + Ok(()) + } else { + //TODO: Seriously? + Err(Error::Avm1Error(format!( + "Failed to fetch root movie {}", + url + ))) + } + }) + } + /// Construct a future for the given movie loader. /// /// The given future should be passed immediately to an executor; it will diff --git a/core/src/player.rs b/core/src/player.rs index 0211a90e3..24b471599 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -6,10 +6,9 @@ use crate::avm1::object::Object; use crate::avm1::{Avm1, AvmString, TObject, Timers, Value}; use crate::avm2::Avm2; use crate::backend::input::{InputBackend, MouseCursor}; +use crate::backend::navigator::{NavigatorBackend, RequestOptions}; use crate::backend::storage::StorageBackend; -use crate::backend::{ - audio::AudioBackend, navigator::NavigatorBackend, render::Letterbox, render::RenderBackend, -}; +use crate::backend::{audio::AudioBackend, render::Letterbox, render::RenderBackend}; use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext}; use crate::display_object::{EditText, MorphShape, MovieClip}; use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, PlayerEvent}; @@ -183,29 +182,21 @@ pub struct Player { impl Player { pub fn new( - mut renderer: Renderer, + renderer: Renderer, audio: Audio, navigator: Navigator, input: Input, - movie: SwfMovie, storage: Storage, ) -> Result>, Error> { - let movie = Arc::new(movie); - - info!( - "Loaded SWF version {}, with a resolution of {}x{}", - movie.header().version, - movie.header().stage_size.x_max, - movie.header().stage_size.y_max - ); - - let movie_width = movie.width(); - let movie_height = movie.height(); + let fake_movie = Arc::new(SwfMovie::empty(NEWEST_PLAYER_VERSION)); + let movie_width = 550; + let movie_height = 400; + let frame_rate = 12.0; let mut player = Player { player_version: NEWEST_PLAYER_VERSION, - swf: movie.clone(), + swf: fake_movie, is_playing: false, needs_render: true, @@ -223,26 +214,10 @@ impl Player { rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms. gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| { - // Load and parse the device font. - let device_font = - match Self::load_device_font(gc_context, DEVICE_FONT_TAG, &mut renderer) { - Ok(font) => Some(font), - Err(e) => { - log::error!("Unable to load device font: {}", e); - None - } - }; - - let mut library = Library::default(); - - library - .library_for_movie_mut(movie.clone()) - .set_device_font(device_font); - GcRoot(GcCell::allocate( gc_context, GcRootData { - library, + library: Library::default(), levels: BTreeMap::new(), mouse_hovered_object: None, drag_object: None, @@ -257,7 +232,7 @@ impl Player { )) }), - frame_rate: movie.header().frame_rate.into(), + frame_rate, frame_accumulator: 0.0, movie_width, @@ -281,14 +256,76 @@ impl Player { storage, }; - player.mutate_with_update_context(|avm1, _avm2, context| { + player.build_matrices(); + + let player_box = Arc::new(Mutex::new(player)); + let mut player_lock = player_box.lock().unwrap(); + player_lock.self_reference = Some(Arc::downgrade(&player_box)); + std::mem::drop(player_lock); + + Ok(player_box) + } + + /// Fetch the root movie. + /// + /// This should not be called if a root movie fetch has already been kicked + /// off. + pub fn fetch_root_movie(&mut self, movie_url: &str) { + self.mutate_with_update_context(|_avm1, _avm2, context| { + let fetch = context.navigator.fetch(movie_url, RequestOptions::get()); + let process = context.load_manager.load_root_movie( + context.player.clone().unwrap(), + fetch, + movie_url.to_string(), + ); + + context.navigator.spawn_future(process); + }); + } + + /// Change the root movie. + /// + /// This should only be called once, as it makes no attempt at removing + /// previous stage contents. If you need to load a new root movie, you + /// should destroy and recreate the player instance. + pub fn set_root_movie(&mut self, movie: Arc) { + info!( + "Loaded SWF version {}, with a resolution of {}x{}", + movie.header().version, + movie.header().stage_size.x_max, + movie.header().stage_size.y_max + ); + + self.movie_width = movie.width(); + self.movie_height = movie.height(); + self.frame_rate = movie.header().frame_rate.into(); + self.swf = movie; + + self.mutate_with_update_context(|avm1, _avm2, context| { let mut root: DisplayObject = - MovieClip::from_movie(context.gc_context, movie.clone()).into(); + MovieClip::from_movie(context.gc_context, context.swf.clone()).into(); root.set_depth(context.gc_context, 0); root.post_instantiation(avm1, context, root, None, false); root.set_name(context.gc_context, ""); context.levels.insert(0, root); + // Load and parse the device font. + let device_font = + match Self::load_device_font(context.gc_context, DEVICE_FONT_TAG, context.renderer) + { + Ok(font) => Some(font), + Err(e) => { + log::error!("Unable to load device font: {}", e); + None + } + }; + + context + .library + .library_for_movie_mut(context.swf.clone()) + .set_device_font(device_font); + + // Set the version parameter on the root. let mut activation = Activation::from_nothing( avm1, ActivationIdentifier::root("[Version Setter]"), @@ -310,15 +347,8 @@ impl Player { ); }); - player.build_matrices(); - player.preload(); - - let player_box = Arc::new(Mutex::new(player)); - let mut player_lock = player_box.lock().unwrap(); - player_lock.self_reference = Some(Arc::downgrade(&player_box)); - std::mem::drop(player_lock); - - Ok(player_box) + self.build_matrices(); + self.preload(); } pub fn tick(&mut self, dt: f64) { @@ -1051,14 +1081,11 @@ impl Player { pub fn load_device_font<'gc>( gc_context: gc_arena::MutationContext<'gc, '_>, data: &[u8], - renderer: &mut Renderer, + renderer: &mut dyn RenderBackend, ) -> Result, Error> { let mut reader = swf::read::Reader::new(data, 8); - let device_font = crate::font::Font::from_swf_tag( - gc_context, - renderer.deref_mut(), - &reader.read_define_font_2(3)?, - )?; + let device_font = + crate::font::Font::from_swf_tag(gc_context, renderer, &reader.read_define_font_2(3)?)?; Ok(device_font) } diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index f17056f6f..c95147186 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -13,6 +13,7 @@ use ruffle_core::tag_utils::SwfMovie; use ruffle_core::Player; use std::cell::RefCell; use std::path::Path; +use std::sync::Arc; type Error = Box; @@ -366,9 +367,9 @@ fn run_swf(swf_path: &str, num_frames: u32) -> Result { Box::new(NullAudioBackend::new()), Box::new(NullNavigatorBackend::with_base_path(base_path, channel)), Box::new(NullInputBackend::new()), - movie, Box::new(MemoryStorageBackend::default()), )?; + player.lock().unwrap().set_root_movie(Arc::new(movie)); for _ in 0..num_frames { player.lock().unwrap().run_frame(); diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 6b793381e..f705a821c 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -16,6 +16,7 @@ use ruffle_core::{ }; use ruffle_render_wgpu::WgpuRenderBackend; use std::path::PathBuf; +use std::sync::Arc; use std::time::Instant; use structopt::StructOpt; @@ -92,7 +93,8 @@ fn run_player(input_path: PathBuf) -> Result<(), Box> { let storage = Box::new(DiskStorageBackend::new( input_path.file_name().unwrap_or_default().as_ref(), )); - let player = Player::new(renderer, audio, navigator, input, movie, storage)?; + let player = Player::new(renderer, audio, navigator, input, storage)?; + player.lock().unwrap().set_root_movie(Arc::new(movie)); player.lock().unwrap().set_is_playing(true); // Desktop player will auto-play. player diff --git a/exporter/src/main.rs b/exporter/src/main.rs index 8d8119b0e..458a33078 100644 --- a/exporter/src/main.rs +++ b/exporter/src/main.rs @@ -13,6 +13,7 @@ use std::error::Error; use std::fs::create_dir_all; use std::path::{Path, PathBuf}; use std::rc::Rc; +use std::sync::Arc; use structopt::StructOpt; use walkdir::{DirEntry, WalkDir}; @@ -84,7 +85,6 @@ fn take_screenshot( Box::new(NullAudioBackend::new()), Box::new(NullNavigatorBackend::new()), Box::new(NullInputBackend::new()), - movie, Box::new(MemoryStorageBackend::default()), )?; @@ -92,6 +92,7 @@ fn take_screenshot( .lock() .unwrap() .set_viewport_dimensions(width, height); + player.lock().unwrap().set_root_movie(Arc::new(movie)); let mut result = Vec::new(); let totalframes = frames + skipframes; diff --git a/web/packages/core/src/ruffle-player.js b/web/packages/core/src/ruffle-player.js index 8e2154829..ef02be99d 100644 --- a/web/packages/core/src/ruffle-player.js +++ b/web/packages/core/src/ruffle-player.js @@ -116,28 +116,48 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement { return false; } + /** + * Ensure a fresh Ruffle instance is ready on this player before continuing. + * + * @throws Any exceptions generated by loading Ruffle Core will be logged + * and passed on. + */ + async ensure_fresh_instance() { + if (this.instance) { + this.instance.destroy(); + this.instance = null; + console.log("Ruffle instance destroyed."); + } + + let Ruffle = await this.Ruffle.catch(function (e) { + console.error("Serious error loading Ruffle: " + e); + throw e; + }); + + this.instance = Ruffle.new(this.container); + console.log("New Ruffle instance created."); + } + + /** + * Load a movie into this Ruffle Player instance by URL. + * + * Any existing movie will be immediately stopped, while the new movie's + * load happens asynchronously. There is currently no way to await the file + * being loaded, or any errors that happen loading it. + * + * @param {String} url The URL to stream. + */ async stream_swf_url(url) { //TODO: Actually stream files... try { if (this.isConnected && !this.is_unused_fallback_object()) { - let abs_url = new URL(url, window.location.href).toString(); console.log("Loading SWF file " + url); - let response = await fetch(abs_url); + await this.ensure_fresh_instance(); + this.instance.stream_from(url); - if (response.ok) { - let data = await response.arrayBuffer(); - await this.play_swf_data(data); - console.log("Playing " + url); - } else { - console.error( - "SWF load failed: " + - response.status + - " " + - response.statusText + - " for " + - url - ); + if (this.play_button) { + this.play_button.style.display = "block"; } } else { console.warn( @@ -159,22 +179,23 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement { } } + /** + * Load a movie's data into this Ruffle Player instance. + * + * Any existing movie will be immediately stopped, and the new movie's data + * placed into a fresh Stage on the same stack. + * + * Please note that by doing this, no URL information will be provided to + * the movie being loaded. + * + * @param {String} url The URL to stream. + */ async play_swf_data(data) { if (this.isConnected && !this.is_unused_fallback_object()) { console.log("Got SWF data"); - if (this.instance) { - this.instance.destroy(); - this.instance = null; - console.log("Ruffle instance destroyed."); - } - - let Ruffle = await this.Ruffle.catch(function (e) { - console.error("Serious error loading Ruffle: " + e); - throw e; - }); - - this.instance = Ruffle.new(this.container, new Uint8Array(data)); + await this.ensure_fresh_instance(); + this.instance.load_data(new Uint8Array(data)); console.log("New Ruffle instance created."); if (this.play_button) { diff --git a/web/src/lib.rs b/web/src/lib.rs index 31483e910..6da45cb5d 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -57,8 +57,38 @@ pub struct Ruffle(Index); #[wasm_bindgen] impl Ruffle { - pub fn new(parent: HtmlElement, swf_data: Uint8Array) -> Result { - Ruffle::new_internal(parent, swf_data).map_err(|_| "Error creating player".into()) + pub fn new(parent: HtmlElement) -> Result { + Ruffle::new_internal(parent).map_err(|_| "Error creating player".into()) + } + + /// Stream an arbitrary movie file from (presumably) the Internet. + /// + /// This method should only be called once per player. + pub fn stream_from(&mut self, movie_url: &str) { + INSTANCES.with(|instances| { + let mut instances = instances.borrow_mut(); + let instance = instances.get_mut(self.0).unwrap(); + instance.core.lock().unwrap().fetch_root_movie(movie_url); + }); + } + + /// Play an arbitrary movie on this instance. + /// + /// This method should only be called once per player. + pub fn load_data(&mut self, swf_data: Uint8Array) -> Result<(), JsValue> { + let movie = Arc::new({ + let mut data = vec![0; swf_data.length() as usize]; + swf_data.copy_to(&mut data[..]); + SwfMovie::from_data(&data, None).map_err(|e| format!("Error loading movie: {}", e))? + }); + + INSTANCES.with(|instances| { + let mut instances = instances.borrow_mut(); + let instance = instances.get_mut(self.0).unwrap(); + instance.core.lock().unwrap().set_root_movie(movie); + }); + + Ok(()) } pub fn play(&mut self) { @@ -106,16 +136,10 @@ impl Ruffle { } impl Ruffle { - fn new_internal(parent: HtmlElement, swf_data: Uint8Array) -> Result> { + fn new_internal(parent: HtmlElement) -> Result> { console_error_panic_hook::set_once(); let _ = console_log::init_with_level(log::Level::Trace); - let movie = { - let mut data = vec![0; swf_data.length() as usize]; - swf_data.copy_to(&mut data[..]); - SwfMovie::from_data(&data, None)? - }; - let window = web_sys::window().ok_or_else(|| "Expected window")?; let document = window.document().ok_or("Expected document")?; @@ -138,8 +162,7 @@ impl Ruffle { }) .unwrap_or_else(|| Box::new(MemoryStorageBackend::default())); - let core = - ruffle_core::Player::new(renderer, audio, navigator, input, movie, local_storage)?; + let core = ruffle_core::Player::new(renderer, audio, navigator, input, local_storage)?; let mut core_lock = core.lock().unwrap(); let frame_rate = core_lock.frame_rate(); core_lock.audio_mut().set_frame_rate(frame_rate); From 4813942fe7ce07037bd6346eacf45ae29ebd41d9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 22 Jul 2020 23:28:19 -0400 Subject: [PATCH 03/14] The player should always change the audio backend's framerate itself. --- core/src/player.rs | 2 ++ web/src/lib.rs | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/core/src/player.rs b/core/src/player.rs index 24b471599..561250aae 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -257,6 +257,7 @@ impl Player { }; player.build_matrices(); + player.audio.set_frame_rate(frame_rate); let player_box = Arc::new(Mutex::new(player)); let mut player_lock = player_box.lock().unwrap(); @@ -349,6 +350,7 @@ impl Player { self.build_matrices(); self.preload(); + self.audio.set_frame_rate(self.frame_rate); } pub fn tick(&mut self, dt: f64) { diff --git a/web/src/lib.rs b/web/src/lib.rs index 6da45cb5d..429b3a65a 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -14,7 +14,6 @@ use ruffle_core::backend::storage::StorageBackend; use ruffle_core::tag_utils::SwfMovie; use ruffle_core::PlayerEvent; use ruffle_web_common::JsResult; -use std::mem::drop; use std::sync::{Arc, Mutex}; use std::{cell::RefCell, error::Error, num::NonZeroI32}; use wasm_bindgen::{prelude::*, JsCast, JsValue}; @@ -163,10 +162,6 @@ impl Ruffle { .unwrap_or_else(|| Box::new(MemoryStorageBackend::default())); let core = ruffle_core::Player::new(renderer, audio, navigator, input, local_storage)?; - let mut core_lock = core.lock().unwrap(); - let frame_rate = core_lock.frame_rate(); - core_lock.audio_mut().set_frame_rate(frame_rate); - drop(core_lock); // Create instance. let instance = RuffleInstance { From 7433bfe28f34253ab6802da4ff0ed7ed576a4144 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 00:00:41 -0400 Subject: [PATCH 04/14] Add a `NavigatorBackend` method to resolve relative URLs. --- core/src/backend/navigator.rs | 30 ++++++++++++++++++++++++++++++ desktop/src/navigator.rs | 21 ++++++++++++++++++++- web/src/navigator.rs | 22 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/core/src/backend/navigator.rs b/core/src/backend/navigator.rs index 1b6426473..22588fd9e 100644 --- a/core/src/backend/navigator.rs +++ b/core/src/backend/navigator.rs @@ -1,6 +1,7 @@ //! Browser-related platform functions use crate::loader::Error; +use std::borrow::Cow; use std::collections::{HashMap, VecDeque}; use std::fs; use std::future::Future; @@ -11,6 +12,7 @@ use std::sync::mpsc::{channel, Receiver, Sender}; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; use std::time::Duration; use swf::avm1::types::SendVarsMethod; +use url::{ParseError, Url}; /// Enumerates all possible navigation methods. #[derive(Copy, Clone)] @@ -132,6 +134,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>); + + /// Resolve a relative URL. + /// + /// This function must not change URLs which are already protocol, domain, + /// and path absolute. For URLs that are relative, the implementator of + /// this function may opt to convert them to absolute using an implementor + /// defined base. For a web browser, the most obvious base would be the + /// current document's base URL, while the most obvious base for a desktop + /// client would be the file-URL form of the current path. + fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str>; } /// A null implementation of an event loop that only supports blocking. @@ -303,4 +315,22 @@ impl NavigatorBackend for NullNavigatorBackend { channel.send(future).unwrap(); } } + + fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { + let parsed = Url::parse(url); + if let Err(ParseError::RelativeUrlWithoutBase) = parsed { + if let Ok(cwd) = std::env::current_dir() { + let base = Url::from_directory_path(cwd); + if let Ok(base) = base { + let abs = base.join(url); + + if let Ok(abs) = abs { + return abs.into_string().into(); + } + } + } + } + + url.into() + } } diff --git a/desktop/src/navigator.rs b/desktop/src/navigator.rs index 4052db170..f007636de 100644 --- a/desktop/src/navigator.rs +++ b/desktop/src/navigator.rs @@ -5,12 +5,13 @@ use ruffle_core::backend::navigator::{ NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions, }; use ruffle_core::loader::Error; +use std::borrow::Cow; use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; use std::sync::mpsc::Sender; use std::time::{Duration, Instant}; -use url::Url; +use url::{ParseError, Url}; use winit::event_loop::EventLoopProxy; /// Implementation of `NavigatorBackend` for non-web environments that can call @@ -129,4 +130,22 @@ impl NavigatorBackend for ExternalNavigatorBackend { ); } } + + fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { + let parsed = Url::parse(url); + if let Err(ParseError::RelativeUrlWithoutBase) = parsed { + if let Ok(cwd) = std::env::current_dir() { + let base = Url::from_directory_path(cwd); + if let Ok(base) = base { + let abs = base.join(url); + + if let Ok(abs) = abs { + return abs.into_string().into(); + } + } + } + } + + url.into() + } } diff --git a/web/src/navigator.rs b/web/src/navigator.rs index c31837bcc..488c76ec7 100644 --- a/web/src/navigator.rs +++ b/web/src/navigator.rs @@ -5,8 +5,10 @@ use ruffle_core::backend::navigator::{ NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions, }; use ruffle_core::loader::Error; +use std::borrow::Cow; use std::collections::HashMap; use std::time::Duration; +use url::{ParseError, Url}; use wasm_bindgen::JsCast; use wasm_bindgen_futures::{spawn_local, JsFuture}; use web_sys::{window, Blob, BlobPropertyBag, Performance, Request, RequestInit, Response}; @@ -158,4 +160,24 @@ impl NavigatorBackend for WebNavigatorBackend { } }) } + + fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { + let parsed = Url::parse(url); + if let Err(ParseError::RelativeUrlWithoutBase) = parsed { + let window = web_sys::window().expect("window()"); + let document = window.document().expect("document()"); + if let Ok(Some(base_uri)) = document.base_uri() { + let base = Url::parse(&base_uri); + if let Ok(base) = base { + let abs = base.join(url); + + if let Ok(abs) = abs { + return abs.into_string().into(); + } + } + } + } + + url.into() + } } From c926da8888cfbb33fa8ae289a3ff53cd7127677f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 18:30:39 -0400 Subject: [PATCH 05/14] Refactor URL relativization into two utility methods that backends can provide base URLs and paths to. --- core/src/backend/navigator.rs | 30 ++++++++++++++++++++++++++++++ desktop/src/navigator.rs | 23 +++++++---------------- web/src/navigator.rs | 20 ++++++-------------- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/core/src/backend/navigator.rs b/core/src/backend/navigator.rs index 22588fd9e..ed71bceba 100644 --- a/core/src/backend/navigator.rs +++ b/core/src/backend/navigator.rs @@ -14,6 +14,36 @@ use std::time::Duration; use swf::avm1::types::SendVarsMethod; use url::{ParseError, Url}; +/// Attempt to convert a relative filesystem path into an absolute `file:///` +/// URL. +/// +/// If the relative path is an absolute path, the base will not be used, but it +/// will still be parsed into a `Url`. +pub fn url_from_relative_path>(base: P, relative: &str) -> Result { + let parsed = Url::parse(relative); + if let Err(ParseError::RelativeUrlWithoutBase) = parsed { + let base = + Url::from_directory_path(base).map_err(|_| ParseError::RelativeUrlWithoutBase)?; + return base.join(relative); + } + + parsed +} + +/// Attempt to convert a relative URL into an absolute URL, using the base URL +/// if necessary. +/// +/// If the relative URL is actually absolute, then the base will not be used. +pub fn url_from_relative_url(base: &str, relative: &str) -> Result { + let parsed = Url::parse(relative); + if let Err(ParseError::RelativeUrlWithoutBase) = parsed { + let base = Url::parse(base)?; + return base.join(relative); + } + + parsed +} + /// Enumerates all possible navigation methods. #[derive(Copy, Clone)] pub enum NavigationMethod { diff --git a/desktop/src/navigator.rs b/desktop/src/navigator.rs index f007636de..43867bc46 100644 --- a/desktop/src/navigator.rs +++ b/desktop/src/navigator.rs @@ -2,7 +2,7 @@ use crate::custom_event::RuffleEvent; use ruffle_core::backend::navigator::{ - NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions, + url_from_relative_path, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions, }; use ruffle_core::loader::Error; use std::borrow::Cow; @@ -11,7 +11,7 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::mpsc::Sender; use std::time::{Duration, Instant}; -use url::{ParseError, Url}; +use url::Url; use winit::event_loop::EventLoopProxy; /// Implementation of `NavigatorBackend` for non-web environments that can call @@ -132,20 +132,11 @@ impl NavigatorBackend for ExternalNavigatorBackend { } fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { - let parsed = Url::parse(url); - if let Err(ParseError::RelativeUrlWithoutBase) = parsed { - if let Ok(cwd) = std::env::current_dir() { - let base = Url::from_directory_path(cwd); - if let Ok(base) = base { - let abs = base.join(url); - - if let Ok(abs) = abs { - return abs.into_string().into(); - } - } - } + let relative = url_from_relative_path(&self.relative_base_path, url); + if let Ok(relative) = relative { + relative.into_string().into() + } else { + url.into() } - - url.into() } } diff --git a/web/src/navigator.rs b/web/src/navigator.rs index 488c76ec7..61905658d 100644 --- a/web/src/navigator.rs +++ b/web/src/navigator.rs @@ -2,13 +2,12 @@ use js_sys::{Array, ArrayBuffer, Uint8Array}; use ruffle_core::backend::navigator::{ - NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions, + url_from_relative_url, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions, }; use ruffle_core::loader::Error; use std::borrow::Cow; use std::collections::HashMap; use std::time::Duration; -use url::{ParseError, Url}; use wasm_bindgen::JsCast; use wasm_bindgen_futures::{spawn_local, JsFuture}; use web_sys::{window, Blob, BlobPropertyBag, Performance, Request, RequestInit, Response}; @@ -162,19 +161,12 @@ impl NavigatorBackend for WebNavigatorBackend { } fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { - let parsed = Url::parse(url); - if let Err(ParseError::RelativeUrlWithoutBase) = parsed { - let window = web_sys::window().expect("window()"); - let document = window.document().expect("document()"); - if let Ok(Some(base_uri)) = document.base_uri() { - let base = Url::parse(&base_uri); - if let Ok(base) = base { - let abs = base.join(url); + let window = web_sys::window().expect("window()"); + let document = window.document().expect("document()"); - if let Ok(abs) = abs { - return abs.into_string().into(); - } - } + if let Ok(Some(base_uri)) = document.base_uri() { + if let Ok(new_url) = url_from_relative_url(&base_uri, url) { + return new_url.into_string().into(); } } From 5d15f5bfe3bcc5615ffd062c360f2caf5de10343 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 18:40:43 -0400 Subject: [PATCH 06/14] When loading a movie from the filesystem outside of the core, ensure that the URL is properly made absolute. --- core/src/tag_utils.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index e6fdbc834..522ba80be 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -1,3 +1,4 @@ +use crate::backend::navigator::url_from_relative_path; use gc_arena::Collect; use std::path::Path; use std::sync::Arc; @@ -53,7 +54,12 @@ impl SwfMovie { /// Utility method to construct a movie from a file on disk. pub fn from_path>(path: P) -> Result { - let url = path.as_ref().to_string_lossy().to_owned().to_string(); + let mut url = path.as_ref().to_string_lossy().to_owned().to_string(); + let cwd = std::env::current_dir()?; + if let Ok(abs_url) = url_from_relative_path(cwd, &url) { + url = abs_url.into_string(); + } + let data = std::fs::read(path)?; Self::from_data(&data, Some(url)) } From d1724416635a7cf2a51cb960e0461fca68c420ae Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 18:50:00 -0400 Subject: [PATCH 07/14] Also make absolute URLs for all movie loads passed through the load manager. --- core/src/loader.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/src/loader.rs b/core/src/loader.rs index c2fa32da9..3a7a33430 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -358,7 +358,7 @@ impl<'gc> Loader<'gc> { &mut self, player: Weak>, fetch: OwnedFuture, Error>, - url: String, + mut url: String, ) -> OwnedFuture<(), Error> { let _handle = match self { Loader::RootMovie { self_handle, .. } => { @@ -372,6 +372,14 @@ impl<'gc> Loader<'gc> { .expect("Could not upgrade weak reference to player"); Box::pin(async move { + player.lock().expect("Could not lock player!!").update( + |_avm1, _avm2, uc| -> Result<(), Error> { + url = uc.navigator.resolve_relative_url(&url).into_owned(); + + Ok(()) + }, + )?; + let data = (fetch.await) .and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?))); @@ -400,7 +408,7 @@ impl<'gc> Loader<'gc> { &mut self, player: Weak>, fetch: OwnedFuture, Error>, - url: String, + mut url: String, ) -> OwnedFuture<(), Error> { let handle = match self { Loader::Movie { self_handle, .. } => self_handle.expect("Loader not self-introduced"), @@ -414,6 +422,8 @@ impl<'gc> Loader<'gc> { Box::pin(async move { player.lock().expect("Could not lock player!!").update( |avm1, _avm2, uc| -> Result<(), Error> { + url = uc.navigator.resolve_relative_url(&url).into_owned(); + let (clip, broadcaster) = match uc.load_manager.get_loader(handle) { Some(Loader::Movie { target_clip, From a8877ab63c0735f45a44752802a1fc430f047953 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 19:00:05 -0400 Subject: [PATCH 08/14] Yield the correct error when the root movie load fails. --- core/src/loader.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/src/loader.rs b/core/src/loader.rs index 3a7a33430..a1fc17caf 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -37,6 +37,9 @@ pub enum Error { #[error("Non-XML loader spawned as XML loader")] NotXmlLoader, + #[error("Could not fetch movie {0}")] + FetchError(String), + #[error("Invalid SWF")] InvalidSwf(#[from] crate::tag_utils::Error), @@ -388,11 +391,7 @@ impl<'gc> Loader<'gc> { Ok(()) } else { - //TODO: Seriously? - Err(Error::Avm1Error(format!( - "Failed to fetch root movie {}", - url - ))) + Err(Error::FetchError(url)) } }) } @@ -456,7 +455,7 @@ impl<'gc> Loader<'gc> { )?; let data = (fetch.await) - .and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url))?))); + .and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?))); if let Ok((length, movie)) = data { let movie = Arc::new(movie); From 9b9d4076fe770d58185d2067ce695dc736650f4f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 19:25:51 -0400 Subject: [PATCH 09/14] Expose movie URL to ActionScript. --- core/src/avm1/object/stage_object.rs | 12 ++++++++---- core/src/tag_utils.rs | 5 +++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/avm1/object/stage_object.rs b/core/src/avm1/object/stage_object.rs index 9aabea8fe..31e3f0cd3 100644 --- a/core/src/avm1/object/stage_object.rs +++ b/core/src/avm1/object/stage_object.rs @@ -898,11 +898,15 @@ fn drop_target<'gc>( fn url<'gc>( _activation: &mut Activation<'_, 'gc>, - _context: &mut UpdateContext<'_, 'gc, '_>, - _this: DisplayObject<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { - log::warn!("Unimplemented property _url"); - Ok("".into()) + Ok(this + .as_movie_clip() + .and_then(|mc| mc.movie()) + .and_then(|mov| mov.url().map(|s| s.to_string())) + .map(|s| AvmString::new(context.gc_context, s).into()) + .unwrap_or_else(|| "".into())) } fn high_quality<'gc>( diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index 522ba80be..7c2ce6182 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -113,6 +113,11 @@ impl SwfMovie { pub fn height(&self) -> u32 { (self.header.stage_size.y_max - self.header.stage_size.y_min).to_pixels() as u32 } + + /// Get the URL this SWF was fetched from. + pub fn url(&self) -> Option<&str> { + self.url.as_deref() + } } /// A shared-ownership reference to some portion of an SWF datastream. From f0e2c77c1f02255d27ea4c1fa7af86ea2aa47875 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 22:18:59 -0400 Subject: [PATCH 10/14] URLs from paths is a desktop-only feature. --- core/src/backend/navigator.rs | 22 ++++++++++------------ core/src/tag_utils.rs | 9 +++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/core/src/backend/navigator.rs b/core/src/backend/navigator.rs index ed71bceba..d7c6902db 100644 --- a/core/src/backend/navigator.rs +++ b/core/src/backend/navigator.rs @@ -19,6 +19,7 @@ use url::{ParseError, Url}; /// /// If the relative path is an absolute path, the base will not be used, but it /// will still be parsed into a `Url`. +#[cfg(any(unix, windows, target_os = "redox"))] pub fn url_from_relative_path>(base: P, relative: &str) -> Result { let parsed = Url::parse(relative); if let Err(ParseError::RelativeUrlWithoutBase) = parsed { @@ -346,21 +347,18 @@ impl NavigatorBackend for NullNavigatorBackend { } } + #[cfg(any(unix, windows, target_os = "redox"))] fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { - let parsed = Url::parse(url); - if let Err(ParseError::RelativeUrlWithoutBase) = parsed { - if let Ok(cwd) = std::env::current_dir() { - let base = Url::from_directory_path(cwd); - if let Ok(base) = base { - let abs = base.join(url); - - if let Ok(abs) = abs { - return abs.into_string().into(); - } - } - } + let relative = url_from_relative_path(&self.relative_base_path, url); + if let Ok(relative) = relative { + relative.into_string().into() + } else { + url.into() } + } + #[cfg(not(any(unix, windows, target_os = "redox")))] + fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { url.into() } } diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index 7c2ce6182..8e1db19f1 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -1,3 +1,4 @@ +#[cfg(any(unix, windows, target_os = "redox"))] use crate::backend::navigator::url_from_relative_path; use gc_arena::Collect; use std::path::Path; @@ -53,6 +54,7 @@ impl SwfMovie { } /// Utility method to construct a movie from a file on disk. + #[cfg(any(unix, windows, target_os = "redox"))] pub fn from_path>(path: P) -> Result { let mut url = path.as_ref().to_string_lossy().to_owned().to_string(); let cwd = std::env::current_dir()?; @@ -64,6 +66,13 @@ impl SwfMovie { Self::from_data(&data, Some(url)) } + #[cfg(not(any(unix, windows, target_os = "redox")))] + pub fn from_path>(path: P) -> Result { + let url = path.as_ref().to_string_lossy().to_owned().to_string(); + let data = std::fs::read(path)?; + Self::from_data(&data, Some(url)) + } + /// Construct a movie based on the contents of the SWF datastream. pub fn from_data(swf_data: &[u8], url: Option) -> Result { let swf_stream = swf::read::read_swf_header(&swf_data[..])?; From a34e81a70415bc8a83bf5dd3081918e0de30a483 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 22:43:40 -0400 Subject: [PATCH 11/14] `_url` on desktop should always return a file URL for file-loaded movies. --- core/src/backend/navigator.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/backend/navigator.rs b/core/src/backend/navigator.rs index d7c6902db..28eea2b46 100644 --- a/core/src/backend/navigator.rs +++ b/core/src/backend/navigator.rs @@ -21,14 +21,15 @@ use url::{ParseError, Url}; /// will still be parsed into a `Url`. #[cfg(any(unix, windows, target_os = "redox"))] pub fn url_from_relative_path>(base: P, relative: &str) -> Result { - let parsed = Url::parse(relative); - if let Err(ParseError::RelativeUrlWithoutBase) = parsed { + let parsed = Url::from_file_path(relative); + if let Err(()) = parsed { let base = Url::from_directory_path(base).map_err(|_| ParseError::RelativeUrlWithoutBase)?; + return base.join(relative); } - parsed + Ok(parsed.unwrap()) } /// Attempt to convert a relative URL into an absolute URL, using the base URL From aed47d458d7b8b9c510b92fd39f9d56e049cb61c Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 23 Jul 2020 23:09:08 -0400 Subject: [PATCH 12/14] Level loads in GetURL2 should also propagate origin information. --- core/src/avm1/activation.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index a0c1ba396..abce68c67 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -1355,6 +1355,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context.player.clone().unwrap(), level, fetch, + url.to_string(), None, ); context.navigator.spawn_future(process); From 153b7b78a5c793d75201629bcf10474201541917 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 25 Jul 2020 19:48:32 -0400 Subject: [PATCH 13/14] Add a web version of `url_from_relative_path` that just yields an error. This allows us to remove the conditionals on implementations of `from_path` that need to call this function, as the function is now always guaranteed to be there, even if it's just a no-op/`Err` generator. --- core/src/backend/navigator.rs | 24 ++++++++++++++++++------ core/src/tag_utils.rs | 8 -------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/core/src/backend/navigator.rs b/core/src/backend/navigator.rs index 28eea2b46..1ee7a72e9 100644 --- a/core/src/backend/navigator.rs +++ b/core/src/backend/navigator.rs @@ -19,6 +19,10 @@ use url::{ParseError, Url}; /// /// If the relative path is an absolute path, the base will not be used, but it /// will still be parsed into a `Url`. +/// +/// This is the desktop version of this function, which actually carries out +/// the above instructions. On non-Unix, non-Windows, non-Redox environments, +/// this function always yields an error. #[cfg(any(unix, windows, target_os = "redox"))] pub fn url_from_relative_path>(base: P, relative: &str) -> Result { let parsed = Url::from_file_path(relative); @@ -32,6 +36,20 @@ pub fn url_from_relative_path>(base: P, relative: &str) -> Result Ok(parsed.unwrap()) } +/// Attempt to convert a relative filesystem path into an absolute `file:///` +/// URL. +/// +/// If the relative path is an absolute path, the base will not be used, but it +/// will still be parsed into a `Url`. +/// +/// This is the web version of this function, which always yields an error. On +/// Unix, Windows, or Redox, this function actually carries out the above +/// instructions. +#[cfg(not(any(unix, windows, target_os = "redox")))] +pub fn url_from_relative_path>(base: P, relative: &str) -> Result { + Err(ParseError::RelativeUrlWithoutBase) +} + /// Attempt to convert a relative URL into an absolute URL, using the base URL /// if necessary. /// @@ -348,7 +366,6 @@ impl NavigatorBackend for NullNavigatorBackend { } } - #[cfg(any(unix, windows, target_os = "redox"))] fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { let relative = url_from_relative_path(&self.relative_base_path, url); if let Ok(relative) = relative { @@ -357,9 +374,4 @@ impl NavigatorBackend for NullNavigatorBackend { url.into() } } - - #[cfg(not(any(unix, windows, target_os = "redox")))] - fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> { - url.into() - } } diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index 8e1db19f1..f8e35475e 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -54,7 +54,6 @@ impl SwfMovie { } /// Utility method to construct a movie from a file on disk. - #[cfg(any(unix, windows, target_os = "redox"))] pub fn from_path>(path: P) -> Result { let mut url = path.as_ref().to_string_lossy().to_owned().to_string(); let cwd = std::env::current_dir()?; @@ -66,13 +65,6 @@ impl SwfMovie { Self::from_data(&data, Some(url)) } - #[cfg(not(any(unix, windows, target_os = "redox")))] - pub fn from_path>(path: P) -> Result { - let url = path.as_ref().to_string_lossy().to_owned().to_string(); - let data = std::fs::read(path)?; - Self::from_data(&data, Some(url)) - } - /// Construct a movie based on the contents of the SWF datastream. pub fn from_data(swf_data: &[u8], url: Option) -> Result { let swf_stream = swf::read::read_swf_header(&swf_data[..])?; From acd7ceb706a4ce33107bc0fb82ddc88afbfccb14 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 25 Jul 2020 22:20:30 -0400 Subject: [PATCH 14/14] Fix missing import on web. --- core/src/tag_utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index f8e35475e..7c2ce6182 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -1,4 +1,3 @@ -#[cfg(any(unix, windows, target_os = "redox"))] use crate::backend::navigator::url_from_relative_path; use gc_arena::Collect; use std::path::Path;