diff --git a/web/Cargo.toml b/web/Cargo.toml index c71899eea5588..e0920a73e54c9 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -74,7 +74,7 @@ features = [ "EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement", "HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent", "Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials", - "Url", "Clipboard", "FocusEvent", "ShadowRoot", "Gamepad", "GamepadButton" + "Url", "WebGlContextEvent", "Clipboard", "FocusEvent", "ShadowRoot", "Gamepad", "GamepadButton" ] [package.metadata.cargo-machete] diff --git a/web/packages/core/src/internal/player/inner.tsx b/web/packages/core/src/internal/player/inner.tsx index d8af904f19b85..4e49bce28371d 100644 --- a/web/packages/core/src/internal/player/inner.tsx +++ b/web/packages/core/src/internal/player/inner.tsx @@ -5,6 +5,7 @@ import { DataLoadOptions, DEFAULT_CONFIG, NetworkingAccessMode, + RenderBackend, UnmuteOverlay, URLLoadOptions, WindowMode, @@ -793,6 +794,24 @@ export class InnerPlayer { } } + /** + * Reloads the player, as if you called {@link RufflePlayer.load} with the same config as the last time it was called, but setting the preferredRenderer to "canvas". + * + * If this player has never been loaded, this method will return an error. + * If this player was already trying to use the canvas render, this method will panic. + */ + protected async reloadWithCanvasRenderer(): Promise { + if (this.loadedConfig && this.loadedConfig.preferredRenderer !== RenderBackend.Canvas) { + const combinedOptions = { ...this.loadedConfig, preferredRenderer: RenderBackend.Canvas}; + await this.load(combinedOptions); + } else if (this.loadedConfig) { + this.panic(new Error(text("error-canvas-reload"))); + } else { + throw new Error("Cannot reload if load wasn't first called"); + } + } + + /** * Loads a specified movie into this player. * diff --git a/web/packages/core/texts/en-US/messages.ftl b/web/packages/core/texts/en-US/messages.ftl index 4534186eadaf6..2239200719ed9 100644 --- a/web/packages/core/texts/en-US/messages.ftl +++ b/web/packages/core/texts/en-US/messages.ftl @@ -24,6 +24,7 @@ clipboard-message-description = clipboard-message-copy = { " " } for copy clipboard-message-cut = { " " } for cut clipboard-message-paste = { " " } for paste +error-canvas-reload = Cannot reload with the canvas renderer when the canvas renderer is already in use. error-file-protocol = It appears you are running Ruffle on the "file:" protocol. This doesn't work as browsers block many features from working for security reasons. diff --git a/web/src/lib.rs b/web/src/lib.rs index b33ba182320c6..babb293afef80 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -39,7 +39,7 @@ use wasm_bindgen::prelude::*; use web_sys::{ AddEventListenerOptions, ClipboardEvent, Element, Event, EventTarget, FocusEvent, Gamepad as WebGamepad, GamepadButton as WebGamepadButton, HtmlCanvasElement, HtmlElement, - KeyboardEvent, Node, PointerEvent, ShadowRoot, WheelEvent, Window, + KeyboardEvent, Node, PointerEvent, ShadowRoot, WebGlContextEvent, WheelEvent, Window, }; static RUFFLE_GLOBAL_PANIC: Once = Once::new(); @@ -137,6 +137,7 @@ struct RuffleInstance { focusin_callback: Option>, focusout_callback: Option>, focus_on_press_callback: Option>, + webglcontextlost_callback: Option>, has_focus: bool, trace_observer: Rc>, log_subscriber: Arc>, @@ -193,6 +194,9 @@ extern "C" { #[wasm_bindgen(method, js_name = "suppressContextMenu")] fn suppress_context_menu(this: &JavascriptPlayer); + + #[wasm_bindgen(method, js_name = "reloadWithCanvasRenderer")] + fn reload_with_canvas_renderer(this: &JavascriptPlayer); } #[derive(Debug, Clone)] @@ -505,6 +509,7 @@ impl RuffleHandle { focusin_callback: None, focusout_callback: None, focus_on_press_callback: None, + webglcontextlost_callback: None, timestamp: None, has_focus: false, trace_observer: player.trace_observer, @@ -771,6 +776,21 @@ impl RuffleHandle { }, )); + // Create webglcontextlost handler. + instance.webglcontextlost_callback = Some(JsCallback::register( + &player.canvas, + "webglcontextlost", + false, + move |_js_event: WebGlContextEvent| { + INSTANCES.with(|instances| { + if instances.borrow().len() >= 8 { + let _ = ruffle.remove_instance(); + js_player.reload_with_canvas_renderer(); + } + }); + }, + )); + instance.unload_callback = Some(JsCallback::register(&window, "unload", false, move |_| { let _ = ruffle.with_core_mut(|core| {