Add a backend for controlling the enclosing web browser.
This commit is contained in:
parent
2d2b473fe1
commit
0f9db1744b
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod audio;
|
||||
pub mod render;
|
||||
pub mod navigator;
|
|
@ -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>)>) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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<Audio: AudioBackend, Renderer: RenderBackend> {
|
||||
pub struct Player<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend> {
|
||||
swf_data: Arc<Vec<u8>>,
|
||||
swf_version: u8,
|
||||
|
||||
|
@ -28,6 +28,7 @@ pub struct Player<Audio: AudioBackend, Renderer: RenderBackend> {
|
|||
|
||||
audio: Audio,
|
||||
renderer: Renderer,
|
||||
navigator: Navigator,
|
||||
transform_stack: TransformStack,
|
||||
view_matrix: Matrix,
|
||||
inverse_view_matrix: Matrix,
|
||||
|
@ -49,10 +50,11 @@ pub struct Player<Audio: AudioBackend, Renderer: RenderBackend> {
|
|||
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(
|
||||
renderer: Renderer,
|
||||
audio: Audio,
|
||||
navigator: Navigator,
|
||||
swf_data: Vec<u8>,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
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,
|
||||
audio,
|
||||
navigator,
|
||||
|
||||
background_color: Color {
|
||||
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,
|
||||
&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<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
avm: gc_root.avm.write(gc_context),
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
|
@ -264,13 +269,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
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<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
avm: gc_root.avm.write(gc_context),
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
|
@ -323,13 +330,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
}
|
||||
|
||||
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<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
avm: gc_root.avm.write(gc_context),
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
|
@ -367,13 +376,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
}
|
||||
|
||||
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<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
avm: gc_root.avm.write(gc_context),
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
|
@ -468,6 +479,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
|
|||
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>,
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<dyn std::error::Error>> {
|
|||
.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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<dyn FnMut(f64)>;
|
||||
|
||||
struct RuffleInstance {
|
||||
core: ruffle_core::Player<WebAudioBackend, WebCanvasRenderBackend>,
|
||||
core: ruffle_core::Player<WebAudioBackend, WebCanvasRenderBackend, WebNavigatorBackend>,
|
||||
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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue