Add a backend for controlling the enclosing web browser.

This commit is contained in:
David Wendt 2019-09-01 13:24:04 -06:00 committed by Mike Welsh
parent 2d2b473fe1
commit 0f9db1744b
10 changed files with 126 additions and 15 deletions

View File

@ -22,6 +22,7 @@ pub struct ActionContext<'a, 'gc, 'gc_context> {
pub start_clip: DisplayNode<'gc>, pub start_clip: DisplayNode<'gc>,
pub active_clip: DisplayNode<'gc>, pub active_clip: DisplayNode<'gc>,
pub audio: &'a mut dyn crate::backend::audio::AudioBackend, pub audio: &'a mut dyn crate::backend::audio::AudioBackend,
pub navigator: &'a mut dyn crate::backend::navigator::NavigatorBackend
} }
pub struct Avm1<'gc> { pub struct Avm1<'gc> {

View File

@ -74,6 +74,7 @@ mod tests {
use super::*; use super::*;
use crate::avm1::Error; use crate::avm1::Error;
use crate::backend::audio::NullAudioBackend; use crate::backend::audio::NullAudioBackend;
use crate::backend::navigator::NullNavigatorBackend;
use crate::display_object::DisplayObject; use crate::display_object::DisplayObject;
use crate::movie_clip::MovieClip; use crate::movie_clip::MovieClip;
use gc_arena::rootless_arena; use gc_arena::rootless_arena;
@ -110,6 +111,7 @@ mod tests {
start_clip: root, start_clip: root,
active_clip: root, active_clip: root,
audio: &mut NullAudioBackend::new(), audio: &mut NullAudioBackend::new(),
navigator: &mut NullNavigatorBackend::new()
}; };
test(&mut context) test(&mut context)
}) })

View File

@ -1,2 +1,3 @@
pub mod audio; pub mod audio;
pub mod render; pub mod render;
pub mod navigator;

View File

@ -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 `<a>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<String>, vars_method: Option<(NavigationMethod, HashMap<String, String>)>);
}
/// 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<String>, _vars_method: Option<(NavigationMethod, HashMap<String, String>)>) {
}
}

View File

@ -1,5 +1,5 @@
use crate::avm1::Avm1; 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::events::{ButtonEvent, PlayerEvent};
use crate::library::Library; use crate::library::Library;
use crate::movie_clip::MovieClip; use crate::movie_clip::MovieClip;
@ -20,7 +20,7 @@ struct GcRoot<'gc> {
make_arena!(GcArena, GcRoot); make_arena!(GcArena, GcRoot);
pub struct Player<Audio: AudioBackend, Renderer: RenderBackend> { pub struct Player<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend> {
swf_data: Arc<Vec<u8>>, swf_data: Arc<Vec<u8>>,
swf_version: u8, swf_version: u8,
@ -28,6 +28,7 @@ pub struct Player<Audio: AudioBackend, Renderer: RenderBackend> {
audio: Audio, audio: Audio,
renderer: Renderer, renderer: Renderer,
navigator: Navigator,
transform_stack: TransformStack, transform_stack: TransformStack,
view_matrix: Matrix, view_matrix: Matrix,
inverse_view_matrix: Matrix, inverse_view_matrix: Matrix,
@ -49,10 +50,11 @@ pub struct Player<Audio: AudioBackend, Renderer: RenderBackend> {
is_mouse_down: bool, is_mouse_down: bool,
} }
impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> { impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend> Player<Audio, Renderer, Navigator> {
pub fn new( pub fn new(
renderer: Renderer, renderer: Renderer,
audio: Audio, audio: Audio,
navigator: Navigator,
swf_data: Vec<u8>, swf_data: Vec<u8>,
) -> Result<Self, Box<dyn std::error::Error>> { ) -> Result<Self, Box<dyn std::error::Error>> {
let (header, mut reader) = swf::read::read_swf_header(&swf_data[..]).unwrap(); let (header, mut reader) = swf::read::read_swf_header(&swf_data[..]).unwrap();
@ -74,6 +76,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
renderer, renderer,
audio, audio,
navigator,
background_color: Color { background_color: Color {
r: 255, r: 255,
@ -202,13 +205,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
} }
} }
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, self.global_time,
&mut self.swf_data, &mut self.swf_data,
self.swf_version, self.swf_version,
&mut self.background_color, &mut self.background_color,
&mut self.renderer, &mut self.renderer,
&mut self.audio, &mut self.audio,
&mut self.navigator,
&mut self.is_mouse_down, &mut self.is_mouse_down,
); );
@ -222,6 +226,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
avm: gc_root.avm.write(gc_context), avm: gc_root.avm.write(gc_context),
renderer, renderer,
audio, audio,
navigator,
actions: vec![], actions: vec![],
gc_context, gc_context,
active_clip: gc_root.root, active_clip: gc_root.root,
@ -264,13 +269,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
return false; 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, self.global_time,
&mut self.swf_data, &mut self.swf_data,
self.swf_version, self.swf_version,
&mut self.background_color, &mut self.background_color,
&mut self.renderer, &mut self.renderer,
&mut self.audio, &mut self.audio,
&mut self.navigator
); );
let mouse_pos = &self.mouse_pos; let mouse_pos = &self.mouse_pos;
@ -291,6 +297,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
avm: gc_root.avm.write(gc_context), avm: gc_root.avm.write(gc_context),
renderer, renderer,
audio, audio,
navigator,
actions: vec![], actions: vec![],
gc_context, gc_context,
active_clip: gc_root.root, active_clip: gc_root.root,
@ -323,13 +330,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
} }
fn preload(&mut self) { 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, self.global_time,
&mut self.swf_data, &mut self.swf_data,
self.swf_version, self.swf_version,
&mut self.background_color, &mut self.background_color,
&mut self.renderer, &mut self.renderer,
&mut self.audio, &mut self.audio,
&mut self.navigator,
); );
self.gc_arena.mutate(|gc_context, gc_root| { self.gc_arena.mutate(|gc_context, gc_root| {
@ -342,6 +350,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
avm: gc_root.avm.write(gc_context), avm: gc_root.avm.write(gc_context),
renderer, renderer,
audio, audio,
navigator,
actions: vec![], actions: vec![],
gc_context, gc_context,
active_clip: gc_root.root, active_clip: gc_root.root,
@ -367,13 +376,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
} }
pub fn run_frame(&mut self) { 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, self.global_time,
&mut self.swf_data, &mut self.swf_data,
self.swf_version, self.swf_version,
&mut self.background_color, &mut self.background_color,
&mut self.renderer, &mut self.renderer,
&mut self.audio, &mut self.audio,
&mut self.navigator
); );
self.gc_arena.mutate(|gc_context, gc_root| { self.gc_arena.mutate(|gc_context, gc_root| {
@ -386,6 +396,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
avm: gc_root.avm.write(gc_context), avm: gc_root.avm.write(gc_context),
renderer, renderer,
audio, audio,
navigator,
actions: vec![], actions: vec![],
gc_context, gc_context,
active_clip: gc_root.root, active_clip: gc_root.root,
@ -468,6 +479,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
start_clip: root, start_clip: root,
active_clip: root, active_clip: root,
audio: update_context.audio, audio: update_context.audio,
navigator: update_context.navigator,
}; };
for (active_clip, action) in actions { for (active_clip, action) in actions {
action_context.start_clip = active_clip; 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 avm: std::cell::RefMut<'a, Avm1<'gc>>,
pub renderer: &'a mut dyn RenderBackend, pub renderer: &'a mut dyn RenderBackend,
pub audio: &'a mut dyn AudioBackend, pub audio: &'a mut dyn AudioBackend,
pub navigator: &'a mut dyn NavigatorBackend,
pub actions: Vec<(DisplayNode<'gc>, crate::tag_utils::SwfSlice)>, pub actions: Vec<(DisplayNode<'gc>, crate::tag_utils::SwfSlice)>,
pub active_clip: DisplayNode<'gc>, pub active_clip: DisplayNode<'gc>,
} }

View File

@ -3,7 +3,7 @@
//! Trace output can be compared with correct output from the official Flash Payer. //! Trace output can be compared with correct output from the official Flash Payer.
use log::{Metadata, Record}; 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 ruffle_core::Player;
use std::cell::RefCell; 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 expected_output = std::fs::read_to_string(expected_output_path)?.replace("\r\n", "\n");
let swf_data = std::fs::read(swf_path)?; 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 { for _ in 0..num_frames {
player.run_frame(); player.run_frame();

View File

@ -6,7 +6,7 @@ use glutin::{
dpi::{LogicalSize, PhysicalPosition}, dpi::{LogicalSize, PhysicalPosition},
ContextBuilder, ElementState, EventsLoop, MouseButton, WindowBuilder, WindowEvent, 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::path::PathBuf;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use structopt::StructOpt; use structopt::StructOpt;
@ -44,8 +44,9 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
.build_windowed(window_builder, &events_loop)?; .build_windowed(window_builder, &events_loop)?;
let audio = audio::RodioAudioBackend::new()?; let audio = audio::RodioAudioBackend::new()?;
let renderer = GliumRenderBackend::new(windowed_context)?; let renderer = GliumRenderBackend::new(windowed_context)?;
let navigator = NullNavigatorBackend::new(); //TODO: actually implement this backend type
let display = renderer.display().clone(); 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. player.set_is_playing(true); // Desktop player will auto-play.
let logical_size: LogicalSize = (player.movie_width(), player.movie_height()).into(); let logical_size: LogicalSize = (player.movie_width(), player.movie_height()).into();

View File

@ -38,7 +38,8 @@ version = "0.3.25"
features = [ features = [
"AudioBuffer", "AudioBufferSourceNode", "AudioProcessingEvent", "AudioContext", "AudioDestinationNode", "AudioNode", "AudioBuffer", "AudioBufferSourceNode", "AudioProcessingEvent", "AudioContext", "AudioDestinationNode", "AudioNode",
"CanvasRenderingContext2d", "CssStyleDeclaration", "Document", "Element", "Event", "EventTarget", "HtmlCanvasElement", "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] [dev-dependencies]
wasm-bindgen-test = "0.2.48" wasm-bindgen-test = "0.2.48"

View File

@ -1,9 +1,10 @@
//! Ruffle web frontend. //! Ruffle web frontend.
mod audio; mod audio;
mod render; mod render;
mod navigator;
mod utils; mod utils;
use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend}; use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend, navigator::WebNavigatorBackend};
use generational_arena::{Arena, Index}; use generational_arena::{Arena, Index};
use js_sys::Uint8Array; use js_sys::Uint8Array;
use ruffle_core::{backend::render::RenderBackend, PlayerEvent}; use ruffle_core::{backend::render::RenderBackend, PlayerEvent};
@ -21,7 +22,7 @@ thread_local! {
type AnimationHandler = Closure<dyn FnMut(f64)>; type AnimationHandler = Closure<dyn FnMut(f64)>;
struct RuffleInstance { struct RuffleInstance {
core: ruffle_core::Player<WebAudioBackend, WebCanvasRenderBackend>, core: ruffle_core::Player<WebAudioBackend, WebCanvasRenderBackend, WebNavigatorBackend>,
canvas: HtmlCanvasElement, canvas: HtmlCanvasElement,
canvas_width: i32, canvas_width: i32,
canvas_height: i32, canvas_height: i32,
@ -82,8 +83,9 @@ impl Ruffle {
let window = web_sys::window().ok_or_else(|| "Expected window")?; let window = web_sys::window().ok_or_else(|| "Expected window")?;
let renderer = WebCanvasRenderBackend::new(&canvas)?; let renderer = WebCanvasRenderBackend::new(&canvas)?;
let audio = WebAudioBackend::new()?; 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. // Create instance.
let instance = RuffleInstance { let instance = RuffleInstance {

27
web/src/navigator.rs Normal file
View File

@ -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<String>, _vars_method: Option<(NavigationMethod, HashMap<String, String>)>) {
if let Some(window) = window() {
//TODO: Support `window`
//TODO: Support `vars_method`
window.location().assign(&url);
}
}
}