diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 52e5bcdba..8c19f91d6 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -1267,7 +1267,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { } if let Some(fscommand) = fscommand::parse(&url) { - fscommand::handle(fscommand, self)?; + let fsargs = target; + fscommand::handle(fscommand, fsargs, self)?; } else { self.context .navigator @@ -1290,7 +1291,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let url = url_val.coerce_to_string(self)?; if let Some(fscommand) = fscommand::parse(&url) { - fscommand::handle(fscommand, self)?; + let fsargs = target.coerce_to_string(self)?.to_string(); + fscommand::handle(fscommand, &fsargs, self)?; return Ok(FrameControl::Continue); } diff --git a/core/src/avm1/fscommand.rs b/core/src/avm1/fscommand.rs index 1af2f6fb8..857507ea6 100644 --- a/core/src/avm1/fscommand.rs +++ b/core/src/avm1/fscommand.rs @@ -13,13 +13,17 @@ pub fn parse(url: &str) -> Option<&str> { } } -/// TODO: FSCommand URL handling pub fn handle<'gc>( - fscommand: &str, + command: &str, + args: &str, activation: &mut Activation<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - avm_warn!(activation, "Unhandled FSCommand: {}", fscommand); - - //This should be an error. + if !activation + .context + .external_interface + .invoke_fs_command(command, args) + { + avm_warn!(activation, "Unhandled FSCommand: {}", command); + } Ok(()) } diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 675ec2ef0..998282bc2 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -1129,7 +1129,9 @@ pub fn get_url<'gc>( if let Some(url_val) = args.get(0) { let url = url_val.coerce_to_string(activation)?; if let Some(fscommand) = fscommand::parse(&url) { - fscommand::handle(fscommand, activation); + let fsargs_val = args.get(1).cloned().unwrap_or(Value::Undefined); + let fsargs = fsargs_val.coerce_to_string(activation)?; + fscommand::handle(fscommand, &fsargs, activation); return Ok(Value::Undefined); } diff --git a/core/src/external.rs b/core/src/external.rs index e737867ba..c9f4ba87c 100644 --- a/core/src/external.rs +++ b/core/src/external.rs @@ -235,6 +235,8 @@ pub trait ExternalInterfaceProvider { fn get_method(&self, name: &str) -> Option>; fn on_callback_available(&self, name: &str); + + fn on_fs_command(&self, command: &str, args: &str) -> bool; } pub trait ExternalInterfaceMethod { @@ -295,4 +297,13 @@ impl<'gc> ExternalInterface<'gc> { pub fn available(&self) -> bool { !self.providers.is_empty() } + + pub fn invoke_fs_command(&self, command: &str, args: &str) -> bool { + for provider in &self.providers { + if provider.on_fs_command(command, args) { + return true; + } + } + false + } } diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 8dc8ca02a..b13ee7515 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -811,4 +811,8 @@ impl ExternalInterfaceProvider for ExternalInterfaceTestProvider { } fn on_callback_available(&self, _name: &str) {} + + fn on_fs_command(&self, _command: &str, _args: &str) -> bool { + false + } } diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index 2b96e4853..d6c7eaa1d 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -116,6 +116,16 @@ export class RufflePlayer extends HTMLElement { private ruffleConstructor: Promise<{ new (...args: any[]): Ruffle }>; private panicked = false; + /** + * A movie can communicate with the hosting page using fscommand + * as long as script access is allowed. + * + * @param command A string passed to the host application for any use. + * @param args A string passed to the host application for any use. + * @returns True if the command was handled. + */ + onFSCommand: ((command: string, args: string) => boolean) | null; + /** * Any configuration that should apply to this specific player. * This will be defaulted with any global configuration. @@ -155,6 +165,7 @@ export class RufflePlayer extends HTMLElement { this.instance = null; this.options = null; + this.onFSCommand = null; this._trace_observer = null; this.ruffleConstructor = loadRuffle(); diff --git a/web/src/lib.rs b/web/src/lib.rs index 74d8ca547..934f99457 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -96,6 +96,9 @@ extern "C" { #[wasm_bindgen(method, js_name = "onCallbackAvailable")] fn on_callback_available(this: &JavascriptPlayer, name: &str); + #[wasm_bindgen(method, catch, js_name = "onFSCommand")] + fn on_fs_command(this: &JavascriptPlayer, command: &str, args: &str) -> Result; + #[wasm_bindgen(method)] fn panic(this: &JavascriptPlayer, error: &JsError); @@ -979,6 +982,12 @@ impl ExternalInterfaceProvider for JavascriptInterface { fn on_callback_available(&self, name: &str) { self.js_player.on_callback_available(name); } + + fn on_fs_command(&self, command: &str, args: &str) -> bool { + self.js_player + .on_fs_command(command, args) + .unwrap_or_default() + } } fn js_to_external_value(js: &JsValue) -> ExternalValue {