web: Allow reentrant ExternalInterface callbacks
This commit is contained in:
parent
a49e8d8587
commit
b5531a48d0
|
@ -53,7 +53,7 @@ pub fn call<'gc>(
|
|||
let name = args.get(0).unwrap().coerce_to_string(activation)?;
|
||||
if let Some(method) = activation.context.external_interface.get_method_for(&name) {
|
||||
let mut external_args = Vec::with_capacity(args.len() - 1);
|
||||
for arg in args {
|
||||
for arg in &args[1..] {
|
||||
external_args.push(ExternalValue::from_avm1(activation, arg.to_owned())?);
|
||||
}
|
||||
Ok(method
|
||||
|
|
|
@ -39,7 +39,7 @@ trace
|
|||
successful reentry!
|
||||
|
||||
// ExternalInterface.call(name, payload)
|
||||
[ExternalInterface] trace: [String("trace"), String("successful reentry!")]
|
||||
[ExternalInterface] trace: [String("successful reentry!")]
|
||||
Traced!
|
||||
|
||||
/// callWith() end
|
||||
|
@ -77,7 +77,7 @@ trace
|
|||
[object Object]
|
||||
|
||||
// ExternalInterface.call(name, payload)
|
||||
[ExternalInterface] trace: [String("trace"), Object({"false": Bool(false), "nested": Object({"list": List([String("string"), Number(100.0), Bool(false), Object({})])}), "null": Null, "number": Number(-500.1), "string": String("A string!"), "true": Bool(true)})]
|
||||
[ExternalInterface] trace: [Object({"false": Bool(false), "nested": Object({"list": List([String("string"), Number(100.0), Bool(false), Object({})])}), "null": Null, "number": Number(-500.1), "string": String("A string!"), "true": Bool(true)})]
|
||||
Traced!
|
||||
|
||||
/// callWith() end
|
||||
|
|
|
@ -268,8 +268,8 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
|
|||
*/
|
||||
on_callback_available(name) {
|
||||
const instance = this.instance;
|
||||
this.container[name] = () => {
|
||||
return instance.call_exposed_callback(name, arguments);
|
||||
this.container[name] = (...args) => {
|
||||
return instance.call_exposed_callback(name, args);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -39,6 +39,8 @@ thread_local! {
|
|||
/// This gives us a clear boundary between the JS side and Rust side, avoiding
|
||||
/// issues with lifetimes and type paramters (which cannot be exported with wasm-bindgen).
|
||||
static INSTANCES: RefCell<Arena<RuffleInstance>> = RefCell::new(Arena::new());
|
||||
|
||||
static CURRENT_CONTEXT: RefCell<Option<*mut UpdateContext<'static, 'static, 'static>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
type AnimationHandler = Closure<dyn FnMut(f64)>;
|
||||
|
@ -167,6 +169,19 @@ impl Ruffle {
|
|||
#[allow(clippy::boxed_local)] // for js_bind
|
||||
pub fn call_exposed_callback(&self, name: &str, args: Box<[JsValue]>) -> JsValue {
|
||||
let args: Vec<ExternalValue> = args.iter().map(js_to_external_value).collect();
|
||||
|
||||
// Re-entrant callbacks need to return through the hole that was punched through for them
|
||||
// We record the context of external functions, and then if we get an internal callback
|
||||
// during the same call we'll reuse that.
|
||||
// This is unsafe by nature. I don't know any safe way to do this.
|
||||
if let Some(context) = CURRENT_CONTEXT.with(|v| *v.borrow()) {
|
||||
unsafe {
|
||||
if let Some(callback) = (*context).external_interface.get_callback(name) {
|
||||
return external_to_js_value(callback.call(&mut *context, name, args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANCES.with(move |instances| {
|
||||
if let Ok(mut instances) = instances.try_borrow_mut() {
|
||||
if let Some(instance) = instances.get_mut(self.0) {
|
||||
|
@ -608,10 +623,18 @@ struct JavascriptMethod {
|
|||
impl ExternalInterfaceMethod for JavascriptMethod {
|
||||
fn call(
|
||||
&self,
|
||||
_context: &mut UpdateContext<'_, '_, '_>,
|
||||
context: &mut UpdateContext<'_, '_, '_>,
|
||||
args: &[ExternalValue],
|
||||
) -> ExternalValue {
|
||||
if let Some(function) = self.function.dyn_ref::<Function>() {
|
||||
let old_context = CURRENT_CONTEXT.with(|v| {
|
||||
v.replace(Some(unsafe {
|
||||
std::mem::transmute::<
|
||||
&mut UpdateContext,
|
||||
&mut UpdateContext<'static, 'static, 'static>,
|
||||
>(context)
|
||||
} as *mut UpdateContext))
|
||||
});
|
||||
let result = if let Some(function) = self.function.dyn_ref::<Function>() {
|
||||
let args_array = Array::new();
|
||||
for arg in args {
|
||||
args_array.push(&external_to_js_value(arg.to_owned()));
|
||||
|
@ -623,7 +646,9 @@ impl ExternalInterfaceMethod for JavascriptMethod {
|
|||
}
|
||||
} else {
|
||||
ExternalValue::Null
|
||||
}
|
||||
};
|
||||
CURRENT_CONTEXT.with(|v| v.replace(old_context));
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue