From 0f9db1744b911d5e91a11a15ed2c77e2e1beaf5e Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 1 Sep 2019 13:24:04 -0600 Subject: [PATCH] Add a backend for controlling the enclosing web browser. --- core/src/avm1.rs | 1 + core/src/avm1/globals/math.rs | 2 ++ core/src/backend.rs | 1 + core/src/backend/navigator.rs | 63 +++++++++++++++++++++++++++++++++ core/src/player.rs | 27 ++++++++++---- core/tests/integration_tests.rs | 4 +-- desktop/src/main.rs | 5 +-- web/Cargo.toml | 3 +- web/src/lib.rs | 8 +++-- web/src/navigator.rs | 27 ++++++++++++++ 10 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 core/src/backend/navigator.rs create mode 100644 web/src/navigator.rs diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 4e56c0687..ae04ec732 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -22,6 +22,7 @@ pub struct ActionContext<'a, 'gc, 'gc_context> { pub start_clip: DisplayNode<'gc>, pub active_clip: DisplayNode<'gc>, pub audio: &'a mut dyn crate::backend::audio::AudioBackend, + pub navigator: &'a mut dyn crate::backend::navigator::NavigatorBackend } pub struct Avm1<'gc> { diff --git a/core/src/avm1/globals/math.rs b/core/src/avm1/globals/math.rs index 18f110ee8..b2f8c34d7 100644 --- a/core/src/avm1/globals/math.rs +++ b/core/src/avm1/globals/math.rs @@ -74,6 +74,7 @@ mod tests { use super::*; use crate::avm1::Error; use crate::backend::audio::NullAudioBackend; + use crate::backend::navigator::NullNavigatorBackend; use crate::display_object::DisplayObject; use crate::movie_clip::MovieClip; use gc_arena::rootless_arena; @@ -110,6 +111,7 @@ mod tests { start_clip: root, active_clip: root, audio: &mut NullAudioBackend::new(), + navigator: &mut NullNavigatorBackend::new() }; test(&mut context) }) diff --git a/core/src/backend.rs b/core/src/backend.rs index 5f874853b..431f0bb29 100644 --- a/core/src/backend.rs +++ b/core/src/backend.rs @@ -1,2 +1,3 @@ pub mod audio; pub mod render; +pub mod navigator; \ No newline at end of file diff --git a/core/src/backend/navigator.rs b/core/src/backend/navigator.rs new file mode 100644 index 000000000..731cb9a2a --- /dev/null +++ b/core/src/backend/navigator.rs @@ -0,0 +1,63 @@ +//! Browser-related platform functions + +use std::collections::HashMap; + +/// Enumerates all possible navigation methods. +pub enum NavigationMethod { + /// Indicates that navigation should generate a GET request. + GET, + + /// Indicates that navigation should generate a POST request. + POST +} + +/// A backend interacting with a browser environment. +pub trait NavigatorBackend { + /// Cause a browser navigation to a given URL. + /// + /// The URL given may be any URL scheme a browser can support. This may not + /// be meaningful for all environments: for example, `javascript:` URLs may + /// not be executable in a desktop context. + /// + /// The `window` parameter, if provided, should be treated identically to + /// the `window` parameter on an HTML `nchor` tag. + /// + /// This function may be used to send variables to an eligible target. If + /// desired, the `vars_method` will be specified with a suitable + /// `NavigationMethod` and a key-value representation of the variables to + /// be sent. What the backend needs to do depends on the `NavigationMethod`: + /// + /// * `GET` - Variables are appended onto the query parameters of the given + /// URL. + /// * `POST` - Variables are sent as form data in a POST request, as if the + /// user had filled out and submitted an HTML form. + /// + /// Flash Player implemented sandboxing to prevent certain kinds of XSS + /// attacks. The `NavigatorBackend` is not responsible for enforcing this + /// sandbox. + fn navigate_to_url(&self, url: String, window: Option, vars_method: Option<(NavigationMethod, HashMap)>); +} + +/// A null implementation for platforms that do not live in a web browser. +pub struct NullNavigatorBackend { +} + +impl NullNavigatorBackend { + pub fn new() -> Self { + NullNavigatorBackend { + + } + } +} + +impl Default for NullNavigatorBackend { + fn default() -> Self { + Self::new() + } +} + +impl NavigatorBackend for NullNavigatorBackend { + fn navigate_to_url(&self, _url: String, _window: Option, _vars_method: Option<(NavigationMethod, HashMap)>) { + + } +} \ No newline at end of file diff --git a/core/src/player.rs b/core/src/player.rs index e48c1ed18..fd729b84a 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -1,5 +1,5 @@ use crate::avm1::Avm1; -use crate::backend::{audio::AudioBackend, render::Letterbox, render::RenderBackend}; +use crate::backend::{audio::AudioBackend, render::Letterbox, render::RenderBackend, navigator::NavigatorBackend}; use crate::events::{ButtonEvent, PlayerEvent}; use crate::library::Library; use crate::movie_clip::MovieClip; @@ -20,7 +20,7 @@ struct GcRoot<'gc> { make_arena!(GcArena, GcRoot); -pub struct Player { +pub struct Player { swf_data: Arc>, swf_version: u8, @@ -28,6 +28,7 @@ pub struct Player { audio: Audio, renderer: Renderer, + navigator: Navigator, transform_stack: TransformStack, view_matrix: Matrix, inverse_view_matrix: Matrix, @@ -49,10 +50,11 @@ pub struct Player { is_mouse_down: bool, } -impl Player { +impl Player { pub fn new( renderer: Renderer, audio: Audio, + navigator: Navigator, swf_data: Vec, ) -> Result> { let (header, mut reader) = swf::read::read_swf_header(&swf_data[..]).unwrap(); @@ -74,6 +76,7 @@ impl Player { renderer, audio, + navigator, background_color: Color { r: 255, @@ -202,13 +205,14 @@ impl Player { } } - let (global_time, swf_data, swf_version, background_color, renderer, audio, is_mouse_down) = ( + let (global_time, swf_data, swf_version, background_color, renderer, audio, navigator, is_mouse_down) = ( self.global_time, &mut self.swf_data, self.swf_version, &mut self.background_color, &mut self.renderer, &mut self.audio, + &mut self.navigator, &mut self.is_mouse_down, ); @@ -222,6 +226,7 @@ impl Player { avm: gc_root.avm.write(gc_context), renderer, audio, + navigator, actions: vec![], gc_context, active_clip: gc_root.root, @@ -264,13 +269,14 @@ impl Player { return false; } - let (global_time, swf_data, swf_version, background_color, renderer, audio) = ( + let (global_time, swf_data, swf_version, background_color, renderer, audio, navigator) = ( self.global_time, &mut self.swf_data, self.swf_version, &mut self.background_color, &mut self.renderer, &mut self.audio, + &mut self.navigator ); let mouse_pos = &self.mouse_pos; @@ -291,6 +297,7 @@ impl Player { avm: gc_root.avm.write(gc_context), renderer, audio, + navigator, actions: vec![], gc_context, active_clip: gc_root.root, @@ -323,13 +330,14 @@ impl Player { } fn preload(&mut self) { - let (global_time, swf_data, swf_version, background_color, renderer, audio) = ( + let (global_time, swf_data, swf_version, background_color, renderer, audio, navigator) = ( self.global_time, &mut self.swf_data, self.swf_version, &mut self.background_color, &mut self.renderer, &mut self.audio, + &mut self.navigator, ); self.gc_arena.mutate(|gc_context, gc_root| { @@ -342,6 +350,7 @@ impl Player { avm: gc_root.avm.write(gc_context), renderer, audio, + navigator, actions: vec![], gc_context, active_clip: gc_root.root, @@ -367,13 +376,14 @@ impl Player { } pub fn run_frame(&mut self) { - let (global_time, swf_data, swf_version, background_color, renderer, audio) = ( + let (global_time, swf_data, swf_version, background_color, renderer, audio, navigator) = ( self.global_time, &mut self.swf_data, self.swf_version, &mut self.background_color, &mut self.renderer, &mut self.audio, + &mut self.navigator ); self.gc_arena.mutate(|gc_context, gc_root| { @@ -386,6 +396,7 @@ impl Player { avm: gc_root.avm.write(gc_context), renderer, audio, + navigator, actions: vec![], gc_context, active_clip: gc_root.root, @@ -468,6 +479,7 @@ impl Player { start_clip: root, active_clip: root, audio: update_context.audio, + navigator: update_context.navigator, }; for (active_clip, action) in actions { action_context.start_clip = active_clip; @@ -535,6 +547,7 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> { pub avm: std::cell::RefMut<'a, Avm1<'gc>>, pub renderer: &'a mut dyn RenderBackend, pub audio: &'a mut dyn AudioBackend, + pub navigator: &'a mut dyn NavigatorBackend, pub actions: Vec<(DisplayNode<'gc>, crate::tag_utils::SwfSlice)>, pub active_clip: DisplayNode<'gc>, } diff --git a/core/tests/integration_tests.rs b/core/tests/integration_tests.rs index 5fd7985b3..7ff2e6db0 100644 --- a/core/tests/integration_tests.rs +++ b/core/tests/integration_tests.rs @@ -3,7 +3,7 @@ //! Trace output can be compared with correct output from the official Flash Payer. use log::{Metadata, Record}; -use ruffle_core::backend::{audio::NullAudioBackend, render::NullRenderer}; +use ruffle_core::backend::{audio::NullAudioBackend, render::NullRenderer, navigator::NullNavigatorBackend}; use ruffle_core::Player; use std::cell::RefCell; @@ -45,7 +45,7 @@ fn test_swf(swf_path: &str, num_frames: u32, expected_output_path: &str) -> Resu let expected_output = std::fs::read_to_string(expected_output_path)?.replace("\r\n", "\n"); let swf_data = std::fs::read(swf_path)?; - let mut player = Player::new(NullRenderer, NullAudioBackend::new(), swf_data)?; + let mut player = Player::new(NullRenderer, NullAudioBackend::new(), NullNavigatorBackend::new(), swf_data)?; for _ in 0..num_frames { player.run_frame(); diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 9a8587928..4acdcaac4 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -6,7 +6,7 @@ use glutin::{ dpi::{LogicalSize, PhysicalPosition}, ContextBuilder, ElementState, EventsLoop, MouseButton, WindowBuilder, WindowEvent, }; -use ruffle_core::{backend::render::RenderBackend, Player}; +use ruffle_core::{backend::render::RenderBackend, Player, backend::navigator::NullNavigatorBackend}; use std::path::PathBuf; use std::time::{Duration, Instant}; use structopt::StructOpt; @@ -44,8 +44,9 @@ fn run_player(input_path: PathBuf) -> Result<(), Box> { .build_windowed(window_builder, &events_loop)?; let audio = audio::RodioAudioBackend::new()?; let renderer = GliumRenderBackend::new(windowed_context)?; + let navigator = NullNavigatorBackend::new(); //TODO: actually implement this backend type let display = renderer.display().clone(); - let mut player = Player::new(renderer, audio, swf_data)?; + let mut player = Player::new(renderer, audio, navigator, swf_data)?; player.set_is_playing(true); // Desktop player will auto-play. let logical_size: LogicalSize = (player.movie_width(), player.movie_height()).into(); diff --git a/web/Cargo.toml b/web/Cargo.toml index 490e45e24..de6542d19 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -38,7 +38,8 @@ version = "0.3.25" features = [ "AudioBuffer", "AudioBufferSourceNode", "AudioProcessingEvent", "AudioContext", "AudioDestinationNode", "AudioNode", "CanvasRenderingContext2d", "CssStyleDeclaration", "Document", "Element", "Event", "EventTarget", "HtmlCanvasElement", - "HtmlElement", "HtmlImageElement", "MouseEvent", "Navigator", "Node", "Performance", "ScriptProcessorNode", "Window"] + "HtmlElement", "HtmlImageElement", "MouseEvent", "Navigator", "Node", "Performance", "ScriptProcessorNode", "Window", + "Location"] [dev-dependencies] wasm-bindgen-test = "0.2.48" diff --git a/web/src/lib.rs b/web/src/lib.rs index 2fa04b7f3..77f9f1749 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1,9 +1,10 @@ //! Ruffle web frontend. mod audio; mod render; +mod navigator; mod utils; -use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend}; +use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend, navigator::WebNavigatorBackend}; use generational_arena::{Arena, Index}; use js_sys::Uint8Array; use ruffle_core::{backend::render::RenderBackend, PlayerEvent}; @@ -21,7 +22,7 @@ thread_local! { type AnimationHandler = Closure; struct RuffleInstance { - core: ruffle_core::Player, + core: ruffle_core::Player, canvas: HtmlCanvasElement, canvas_width: i32, canvas_height: i32, @@ -82,8 +83,9 @@ impl Ruffle { let window = web_sys::window().ok_or_else(|| "Expected window")?; let renderer = WebCanvasRenderBackend::new(&canvas)?; let audio = WebAudioBackend::new()?; + let navigator = WebNavigatorBackend::new(); - let core = ruffle_core::Player::new(renderer, audio, data)?; + let core = ruffle_core::Player::new(renderer, audio, navigator, data)?; // Create instance. let instance = RuffleInstance { diff --git a/web/src/navigator.rs b/web/src/navigator.rs new file mode 100644 index 000000000..1bb068470 --- /dev/null +++ b/web/src/navigator.rs @@ -0,0 +1,27 @@ +//! Navigator backend for web + +use std::collections::HashMap; +use web_sys::window; +use ruffle_core::backend::navigator::{NavigatorBackend, NavigationMethod}; + +pub struct WebNavigatorBackend { + +} + +impl WebNavigatorBackend { + pub fn new() -> Self { + WebNavigatorBackend { + + } + } +} + +impl NavigatorBackend for WebNavigatorBackend { + fn navigate_to_url(&self, url: String, _window_spec: Option, _vars_method: Option<(NavigationMethod, HashMap)>) { + if let Some(window) = window() { + //TODO: Support `window` + //TODO: Support `vars_method` + window.location().assign(&url); + } + } +} \ No newline at end of file