avm2: Translate ExternalInterface to AS

This commit is contained in:
Adrian Wielgosik 2022-12-13 23:57:34 +01:00 committed by Adrian Wielgosik
parent 52d94a4a13
commit 92998e2c91
6 changed files with 72 additions and 127 deletions

View File

@ -643,15 +643,6 @@ pub fn load_player_globals<'gc>(
);
class(activation, flash::text::font::create_class(mc), script)?;
// package `flash.crypto`
// package `flash.external`
class(
activation,
flash::external::externalinterface::create_class(mc),
script,
)?;
// Inside this call, the macro `avm2_system_classes_playerglobal`
// triggers classloading. Therefore, we run `load_playerglobal`
// relative late, so that it can access classes defined before

View File

@ -1,3 +1,3 @@
//! `flash.external` namespace
pub mod externalinterface;
pub mod external_interface;

View File

@ -0,0 +1,11 @@
package flash.external
{
public final class ExternalInterface
{
public static native function get available(): Boolean;
public static native function addCallback(functionName: String, closure: Function) : void;
public static native function call(functionName: String, ... arguments) : *;
}
}

View File

@ -0,0 +1,58 @@
use crate::avm2::{Activation, Error, Object, Value};
use crate::external::{Callback, Value as ExternalValue};
pub fn call<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if args.is_empty() {
return Ok(Value::Null);
}
let name = args.get(0).unwrap().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(args.len() - 1);
for arg in &args[1..] {
external_args.push(ExternalValue::from_avm2(arg.to_owned()));
}
Ok(method
.call(&mut activation.context, &external_args)
.into_avm2(activation))
} else {
Ok(Value::Null)
}
}
pub fn get_available<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(activation.context.external_interface.available().into())
}
pub fn add_callback<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if args.len() < 2 {
return Ok(Value::Undefined);
}
let name = args.get(0).unwrap().coerce_to_string(activation)?;
let method = args.get(1).unwrap();
if let Value::Object(method) = method {
activation
.context
.external_interface
.add_callback(name.to_string(), Callback::Avm2 { method: *method });
}
Ok(Value::Undefined)
}

View File

@ -1,117 +0,0 @@
//! `flash.external.ExternalInterface` builtin/prototype
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::Multiname;
use crate::avm2::{Activation, Error, Namespace, Object, QName, Value};
use crate::external::{Callback, Value as ExternalValue};
use gc_arena::{GcCell, MutationContext};
fn instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this {
activation.super_init(this, &[])?;
}
Ok(Value::Undefined)
}
fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(Value::Undefined)
}
pub fn call<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if args.is_empty() {
return Ok(Value::Null);
}
let name = args.get(0).unwrap().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(args.len() - 1);
for arg in &args[1..] {
external_args.push(ExternalValue::from_avm2(arg.to_owned()));
}
Ok(method
.call(&mut activation.context, &external_args)
.into_avm2(activation))
} else {
Ok(Value::Null)
}
}
pub fn available<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(activation.context.external_interface.available().into())
}
pub fn add_callback<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if args.len() < 2 {
return Ok(Value::Undefined);
}
let name = args.get(0).unwrap().coerce_to_string(activation)?;
let method = args.get(1).unwrap();
if let Value::Object(method) = method {
activation
.context
.external_interface
.add_callback(name.to_string(), Callback::Avm2 { method: *method });
}
Ok(Value::Undefined)
}
/// Construct `ExternalInterface`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new(
QName::new(Namespace::package("flash.external"), "ExternalInterface"),
Some(Multiname::public("Object")),
Method::from_builtin(
instance_init,
"<ExternalInterface instance initializer>",
mc,
),
Method::from_builtin(class_init, "<ExternalInterface class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_attributes(ClassAttributes::FINAL | ClassAttributes::SEALED);
const PUBLIC_CLASS_METHODS: &[(&str, NativeMethodImpl)] =
&[("call", call), ("addCallback", add_callback)];
write.define_public_builtin_class_methods(mc, PUBLIC_CLASS_METHODS);
const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,
Option<NativeMethodImpl>,
)] = &[("available", Some(available), None)];
write.define_public_builtin_class_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
class
}

View File

@ -163,6 +163,8 @@ include "flash/media/StageVideoAvailabilityReason.as"
include "flash/media/VideoCodec.as"
include "flash/media/VideoStatus.as"
include "flash/external/ExternalInterface.as"
include "flash/net.as"
include "flash/net/FileFilter.as"
include "flash/net/FileReference.as"