diff --git a/core/src/avm1/globals/external_interface.rs b/core/src/avm1/globals/external_interface.rs index 0ebe18852671..1b6872712006 100644 --- a/core/src/avm1/globals/external_interface.rs +++ b/core/src/avm1/globals/external_interface.rs @@ -4,7 +4,7 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{Object, ScriptObject, Value}; -use crate::external::{Callback, Value as ExternalValue}; +use crate::external::{Callback, ExternalInterface, Value as ExternalValue}; use crate::string::StringContext; const OBJECT_DECLS: &[Declaration] = declare_properties! { @@ -64,23 +64,16 @@ pub fn call<'gc>( }; let name = name.coerce_to_string(activation)?; - if let Some(method) = activation - .context - .external_interface - .get_method_for(&name.to_utf8_lossy()) - { - let mut external_args = Vec::with_capacity(external_args_len); - if external_args_len > 0 { - for arg in &args[1..] { - external_args.push(ExternalValue::from_avm1(activation, arg.to_owned())?); - } + let mut external_args = Vec::with_capacity(external_args_len); + if external_args_len > 0 { + for arg in &args[1..] { + external_args.push(ExternalValue::from_avm1(activation, arg.to_owned())?); } - Ok(method - .call(activation.context, &external_args) - .into_avm1(activation)) - } else { - Ok(Value::Null) } + Ok( + ExternalInterface::call_method(activation.context, &name.to_utf8_lossy(), &external_args) + .into_avm1(activation), + ) } pub fn create_external_interface_object<'gc>( diff --git a/core/src/avm2/globals/flash/external/external_interface.rs b/core/src/avm2/globals/flash/external/external_interface.rs index 0c478ee0dbb4..1bcdc606157b 100644 --- a/core/src/avm2/globals/flash/external/external_interface.rs +++ b/core/src/avm2/globals/flash/external/external_interface.rs @@ -1,7 +1,7 @@ use crate::avm2::error::error; use crate::avm2::parameters::ParametersExt; use crate::avm2::{Activation, Error, Object, Value}; -use crate::external::{Callback, Value as ExternalValue}; +use crate::external::{Callback, ExternalInterface, Value as ExternalValue}; use crate::string::AvmString; pub fn call<'gc>( @@ -12,21 +12,14 @@ pub fn call<'gc>( let name = args.get_string(activation, 0)?; check_available(activation)?; - if let Some(method) = activation - .context - .external_interface - .get_method_for(&name.to_utf8_lossy()) - { - let mut external_args = Vec::with_capacity(args.len() - 1); - for arg in &args[1..] { - external_args.push(ExternalValue::from_avm2(arg.to_owned())); - } - Ok(method - .call(activation.context, &external_args) - .into_avm2(activation)) - } else { - Ok(Value::Null) + let mut external_args = Vec::with_capacity(args.len() - 1); + for arg in &args[1..] { + external_args.push(ExternalValue::from_avm2(arg.to_owned())); } + Ok( + ExternalInterface::call_method(activation.context, &name.to_utf8_lossy(), &external_args) + .into_avm2(activation), + ) } fn check_available<'gc>(activation: &mut Activation<'_, 'gc>) -> Result<(), Error<'gc>> { diff --git a/core/src/external.rs b/core/src/external.rs index 1fb323ec097d..e165a9d3e514 100644 --- a/core/src/external.rs +++ b/core/src/external.rs @@ -13,6 +13,7 @@ use crate::context::UpdateContext; use crate::string::AvmString; use gc_arena::Collect; use std::collections::BTreeMap; +use std::rc::Rc; /// An intermediate format of representing shared data between ActionScript and elsewhere. /// @@ -333,31 +334,20 @@ impl FsCommandProvider for NullFsCommandProvider { } pub trait ExternalInterfaceProvider { - fn get_method(&self, name: &str) -> Option>; + fn call_method(&self, context: &mut UpdateContext<'_>, name: &str, args: &[Value]) -> Value; fn on_callback_available(&self, name: &str); fn get_id(&self) -> Option; } -pub trait ExternalInterfaceMethod { - fn call(&self, context: &mut UpdateContext<'_>, args: &[Value]) -> Value; -} - -impl ExternalInterfaceMethod for F -where - F: Fn(&mut UpdateContext<'_>, &[Value]) -> Value, -{ - fn call(&self, context: &mut UpdateContext<'_>, args: &[Value]) -> Value { - self(context, args) - } -} +pub struct NullExternalInterfaceProvider; #[derive(Collect)] #[collect(no_drop)] pub struct ExternalInterface<'gc> { #[collect(require_static)] - providers: Vec>, + provider: Option>>, callbacks: BTreeMap>, #[collect(require_static)] fs_commands: Box, @@ -365,23 +355,23 @@ pub struct ExternalInterface<'gc> { impl<'gc> ExternalInterface<'gc> { pub fn new( - providers: Vec>, + provider: Option>, fs_commands: Box, ) -> Self { Self { - providers, + provider: provider.map(Rc::new), callbacks: Default::default(), fs_commands, } } - pub fn add_provider(&mut self, provider: Box) { - self.providers.push(provider); + pub fn set_provider(&mut self, provider: Option>) { + self.provider = provider.map(Rc::new); } pub fn add_callback(&mut self, name: String, callback: Callback<'gc>) { self.callbacks.insert(name.clone(), callback); - for provider in &self.providers { + if let Some(provider) = &self.provider { provider.on_callback_available(&name); } } @@ -390,26 +380,21 @@ impl<'gc> ExternalInterface<'gc> { self.callbacks.get(name).cloned() } - pub fn get_method_for(&self, name: &str) -> Option> { - for provider in &self.providers { - if let Some(method) = provider.get_method(name) { - return Some(method); - } + pub fn call_method(context: &mut UpdateContext<'gc>, name: &str, args: &[Value]) -> Value { + let provider = context.external_interface.provider.clone(); + if let Some(provider) = &provider { + provider.call_method(context, name, args) + } else { + Value::Undefined } - None } pub fn available(&self) -> bool { - !self.providers.is_empty() + self.provider.is_some() } pub fn any_id(&self) -> Option { - for provider in &self.providers { - if let Some(id) = provider.get_id() { - return Some(id); - } - } - None + self.provider.as_ref().and_then(|p| p.get_id()) } pub fn invoke_fs_command(&self, command: &str, args: &str) -> bool { diff --git a/core/src/player.rs b/core/src/player.rs index 8c11d39abed9..435cf151ef3e 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -2368,9 +2368,12 @@ impl Player { self.mutate_with_update_context(|context| context.avm1.has_mouse_listener()) } - pub fn add_external_interface(&mut self, provider: Box) { + pub fn set_external_interface_provider( + &mut self, + provider: Option>, + ) { self.mutate_with_update_context(|context| { - context.external_interface.add_provider(provider) + context.external_interface.set_provider(provider) }); } @@ -2467,7 +2470,7 @@ pub struct PlayerBuilder { quality: StageQuality, page_url: Option, frame_rate: Option, - external_interface_providers: Vec>, + external_interface_provider: Option>, fs_command_provider: Box, #[cfg(feature = "known_stubs")] stub_report_output: Option, @@ -2518,7 +2521,7 @@ impl PlayerBuilder { quality: StageQuality::High, page_url: None, frame_rate: None, - external_interface_providers: vec![], + external_interface_provider: None, fs_command_provider: Box::new(NullFsCommandProvider), #[cfg(feature = "known_stubs")] stub_report_output: None, @@ -2703,7 +2706,7 @@ impl PlayerBuilder { /// Adds an External Interface provider for movies to communicate with pub fn with_external_interface(mut self, provider: Box) -> Self { - self.external_interface_providers.push(provider); + self.external_interface_provider = Some(provider); self } @@ -2737,7 +2740,7 @@ impl PlayerBuilder { player_runtime: PlayerRuntime, fullscreen: bool, fake_movie: Arc, - external_interface_providers: Vec>, + external_interface_provider: Option>, fs_command_provider: Box, ) -> GcRoot<'gc> { let mut interner = AvmStringInterner::new(gc_context); @@ -2761,7 +2764,7 @@ impl PlayerBuilder { current_context_menu: None, drag_object: None, external_interface: ExternalInterface::new( - external_interface_providers, + external_interface_provider, fs_command_provider, ), library: Library::empty(), @@ -2888,7 +2891,7 @@ impl PlayerBuilder { self.player_runtime, self.fullscreen, fake_movie.clone(), - self.external_interface_providers, + self.external_interface_provider, self.fs_command_provider, ) }))), diff --git a/desktop/src/backends/external_interface.rs b/desktop/src/backends/external_interface.rs index 734bb973c8f2..b1d884e1b90b 100644 --- a/desktop/src/backends/external_interface.rs +++ b/desktop/src/backends/external_interface.rs @@ -1,21 +1,11 @@ use ruffle_core::context::UpdateContext; -use ruffle_core::external::{ - ExternalInterfaceMethod, ExternalInterfaceProvider, Value as ExternalValue, -}; +use ruffle_core::external::{ExternalInterfaceProvider, Value as ExternalValue}; use url::Url; pub struct DesktopExternalInterfaceProvider { pub spoof_url: Option, } -struct FakeLocationHrefToString(Url); - -impl ExternalInterfaceMethod for FakeLocationHrefToString { - fn call(&self, _context: &mut UpdateContext<'_>, _args: &[ExternalValue]) -> ExternalValue { - ExternalValue::String(self.0.to_string()) - } -} - fn is_location_href(code: &str) -> bool { matches!( code, @@ -23,40 +13,37 @@ fn is_location_href(code: &str) -> bool { ) } -struct FakeEval(Option); - -impl ExternalInterfaceMethod for FakeEval { - fn call(&self, _context: &mut UpdateContext<'_>, args: &[ExternalValue]) -> ExternalValue { - if let Some(ref url) = self.0 { - if let [ExternalValue::String(ref code)] = args { - if is_location_href(code) { - return ExternalValue::String(url.to_string()); - } - } - } - - tracing::warn!("Trying to call eval with ExternalInterface: {args:?}"); - ExternalValue::Undefined - } -} - impl ExternalInterfaceProvider for DesktopExternalInterfaceProvider { - fn get_method(&self, name: &str) -> Option> { + fn call_method( + &self, + _context: &mut UpdateContext<'_>, + name: &str, + args: &[ExternalValue], + ) -> ExternalValue { if let Some(ref url) = self.spoof_url { // Check for e.g. "window.location.href.toString" if let Some(name) = name.strip_suffix(".toString") { if is_location_href(name) { - return Some(Box::new(FakeLocationHrefToString(url.clone()))); + return url.to_string().into(); } } } if name == "eval" { - return Some(Box::new(FakeEval(self.spoof_url.clone()))); + if let Some(ref url) = self.spoof_url { + if let [ExternalValue::String(ref code)] = args { + if is_location_href(code) { + return ExternalValue::String(url.to_string()); + } + } + } + + tracing::warn!("Trying to call eval with ExternalInterface: {args:?}"); + return ExternalValue::Undefined; } tracing::warn!("Trying to call unknown ExternalInterface method: {name}"); - None + ExternalValue::Undefined } fn on_callback_available(&self, _name: &str) {} diff --git a/tests/tests/external_interface/mod.rs b/tests/tests/external_interface/mod.rs index 40cfa8a20915..28e16464b588 100644 --- a/tests/tests/external_interface/mod.rs +++ b/tests/tests/external_interface/mod.rs @@ -3,8 +3,8 @@ #![allow(clippy::needless_pass_by_ref_mut)] use ruffle_core::context::UpdateContext; -use ruffle_core::external::Value as ExternalValue; -use ruffle_core::external::{ExternalInterfaceMethod, ExternalInterfaceProvider}; +use ruffle_core::external::ExternalInterfaceProvider; +use ruffle_core::external::{Value as ExternalValue, Value}; pub mod tests; @@ -41,12 +41,17 @@ fn do_reentry(context: &mut UpdateContext<'_>, _args: &[ExternalValue]) -> Exter } impl ExternalInterfaceProvider for ExternalInterfaceTestProvider { - fn get_method(&self, name: &str) -> Option> { + fn call_method( + &self, + context: &mut UpdateContext<'_>, + name: &str, + args: &[ExternalValue], + ) -> ExternalValue { match name { - "trace" => Some(Box::new(do_trace)), - "ping" => Some(Box::new(do_ping)), - "reentry" => Some(Box::new(do_reentry)), - _ => None, + "trace" => do_trace(context, args), + "ping" => do_ping(context, args), + "reentry" => do_reentry(context, args), + _ => Value::Null, } } diff --git a/tests/tests/external_interface/tests.rs b/tests/tests/external_interface/tests.rs index e643d98722c0..0a691f4a689d 100644 --- a/tests/tests/external_interface/tests.rs +++ b/tests/tests/external_interface/tests.rs @@ -25,7 +25,7 @@ pub fn external_interface_avm1( .player() .lock() .unwrap() - .add_external_interface(Box::new(ExternalInterfaceTestProvider::new())); + .set_external_interface_provider(Some(Box::new(ExternalInterfaceTestProvider::new()))); let mut first = true; @@ -93,7 +93,7 @@ pub fn external_interface_avm2( .player() .lock() .unwrap() - .add_external_interface(Box::new(ExternalInterfaceTestProvider::new())); + .set_external_interface_provider(Some(Box::new(ExternalInterfaceTestProvider::new()))); let mut first = true; diff --git a/web/src/external_interface.rs b/web/src/external_interface.rs index 875d44141897..91d2a0ecab37 100644 --- a/web/src/external_interface.rs +++ b/web/src/external_interface.rs @@ -2,8 +2,7 @@ use crate::{JavascriptPlayer, CURRENT_CONTEXT}; use js_sys::{Array, Object}; use ruffle_core::context::UpdateContext; use ruffle_core::external::{ - ExternalInterfaceMethod, ExternalInterfaceProvider, FsCommandProvider, Value as ExternalValue, - Value, + ExternalInterfaceProvider, FsCommandProvider, Value as ExternalValue, Value, }; use std::collections::BTreeMap; use wasm_bindgen::prelude::wasm_bindgen; @@ -23,10 +22,14 @@ pub struct JavascriptInterface { js_player: JavascriptPlayer, } -struct JavascriptMethod(String); +impl JavascriptInterface { + pub fn new(js_player: JavascriptPlayer) -> Self { + Self { js_player } + } +} -impl ExternalInterfaceMethod for JavascriptMethod { - fn call(&self, context: &mut UpdateContext<'_>, args: &[ExternalValue]) -> ExternalValue { +impl ExternalInterfaceProvider for JavascriptInterface { + fn call_method(&self, context: &mut UpdateContext<'_>, name: &str, args: &[Value]) -> Value { let old_context = CURRENT_CONTEXT.with(|v| { v.replace(Some(unsafe { std::mem::transmute::<&mut UpdateContext, &mut UpdateContext<'static>>(context) @@ -37,7 +40,7 @@ impl ExternalInterfaceMethod for JavascriptMethod { .cloned() .map(external_to_js_value) .collect::>(); - let result = if let Ok(result) = call_external_interface(&self.0, args.into_boxed_slice()) { + let result = if let Ok(result) = call_external_interface(name, args.into_boxed_slice()) { js_to_external_value(&result) } else { ExternalValue::Undefined @@ -46,18 +49,6 @@ impl ExternalInterfaceMethod for JavascriptMethod { CURRENT_CONTEXT.with(|v| v.replace(old_context)); result } -} - -impl JavascriptInterface { - pub fn new(js_player: JavascriptPlayer) -> Self { - Self { js_player } - } -} - -impl ExternalInterfaceProvider for JavascriptInterface { - fn get_method(&self, name: &str) -> Option> { - Some(Box::new(JavascriptMethod(name.to_string()))) - } fn on_callback_available(&self, name: &str) { self.js_player.on_callback_available(name);