avm2: Add support for playerglobal instance allocators

This commit adds support for combining instance allocators with
ActionScript playerglobal class definitions. This is activated
by defining the metadata `[Ruffle(InstanceAllocator = "true")]`
on the ActionScript class definition.

The implementation of this feature is very similar to native methods:
`build_playerglobal` checks for the metadata described above,
and defines a const `NATIVE_INSTANCE_ALLOCATOR_TABLE` mapping
class ids to function pointers.

To demonstrate this feature, I've converted `Event` to ActionScript
(keeping the existing instance allocator function).
I've also converted `ActivityEvent` and `ContextMenuEvent` to
`ActionScript`, to demonstrate how this simplifies inheritance.
In a future PR, we can convert the remaining events to ActionScript,
and remove the `EventData` enum entirely.

Unfortunately, `flex-sdk`'s `asc.jar` compiler strips out all metadata
when the `-optimize` option is passed. As a result, I forked
`flex-sdk` and disabled this behavior:
https://github.com/ruffle-rs/flex-sdk/releases/tag/ruffle-1.0.0

The modified `asc.jar` (built from the forked repository)
is included in this PR, and replaces the our previous 'asc.jar'
downloaded from the official Flex SDK release.

* Change metadata to `[Ruffle(InstanceAllocator)]`

* Strip out metadata before saving bytecode
This commit is contained in:
Aaron Hill 2022-07-21 01:11:46 -05:00 committed by GitHub
parent 01c8c38762
commit bb6f07ee1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 528 additions and 514 deletions

Binary file not shown.

View File

@ -10,11 +10,19 @@ use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::str::FromStr; use std::str::FromStr;
use swf::avm2::types::*; use swf::avm2::types::*;
use swf::avm2::write::Writer;
use swf::DoAbc; use swf::DoAbc;
use swf::Header; use swf::Header;
use swf::SwfStr; use swf::SwfStr;
use swf::Tag; use swf::Tag;
// The metadata name - all metadata in our .as files
// should be of the form `[Ruffle(key1 = value1, key2 = value2)]`
const RUFFLE_METADATA_NAME: &str = "Ruffle";
// Indicates that we should generate a reference to an instance allocator
// method (used as a metadata key with `Ruffle` metadata)
const METADATA_INSTANCE_ALLOCATOR: &str = "InstanceAllocator";
/// If successful, returns a list of paths that were used. If this is run /// If successful, returns a list of paths that were used. If this is run
/// from a build script, these paths should be printed with /// from a build script, these paths should be printed with
/// cargo:rerun-if-changed /// cargo:rerun-if-changed
@ -49,14 +57,14 @@ pub fn build_playerglobal(
} }
let playerglobal = out_dir.join("playerglobal"); let playerglobal = out_dir.join("playerglobal");
let bytes = std::fs::read(playerglobal.with_extension("abc"))?; let mut bytes = std::fs::read(playerglobal.with_extension("abc"))?;
// Cleanup the temporary files written out by 'asc.jar' // Cleanup the temporary files written out by 'asc.jar'
std::fs::remove_file(playerglobal.with_extension("abc"))?; std::fs::remove_file(playerglobal.with_extension("abc"))?;
std::fs::remove_file(playerglobal.with_extension("cpp"))?; std::fs::remove_file(playerglobal.with_extension("cpp"))?;
std::fs::remove_file(playerglobal.with_extension("h"))?; std::fs::remove_file(playerglobal.with_extension("h"))?;
write_native_table(&bytes, &out_dir)?; bytes = write_native_table(&bytes, &out_dir)?;
let tags = vec![Tag::DoAbc(DoAbc { let tags = vec![Tag::DoAbc(DoAbc {
name: SwfStr::from_utf8_str(""), name: SwfStr::from_utf8_str(""),
@ -95,6 +103,91 @@ fn resolve_multiname_ns<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a str
} }
} }
fn flash_to_rust_path(path: &str) -> String {
// Convert each component of the path to snake-case.
// This correctly handles sequences of upper-case letters,
// so 'URLLoader' becomes 'url_loader'
let components = path
.split('.')
.map(|component| component.to_case(Case::Snake))
.collect::<Vec<_>>();
// Form a Rust path from the snake-case components
components.join("::")
}
fn rust_method_path(
abc: &AbcFile,
trait_: &Trait,
parent: Option<Index<Multiname>>,
prefix: &str,
suffix: &str,
) -> TokenStream {
let mut path = "crate::avm2::globals::".to_string();
let trait_name = &abc.constant_pool.multinames[trait_.name.0 as usize - 1];
if let Some(parent) = parent {
// This is a method defined inside the class. Append the class namespace
// (the package) and the class name.
// For example, a namespace of "flash.system" and a name of "Security"
// turns into the path "flash::system::security"
let multiname = &abc.constant_pool.multinames[parent.0 as usize - 1];
path += &flash_to_rust_path(resolve_multiname_ns(&abc, multiname));
path += "::";
path += &flash_to_rust_path(resolve_multiname_name(&abc, multiname));
path += "::";
} else {
// This is a freestanding function. Append its namespace (the package).
// For example, the freestanding function "flash.utils.getDefinitionByName"
// has a namespace of "flash.utils", which turns into the path
// "flash::utils"
path += &flash_to_rust_path(resolve_multiname_ns(&abc, trait_name));
path += "::";
}
// Append the trait name - this corresponds to the actual method
// name (e.g. `getDefinitionByName`)
path += prefix;
path += &flash_to_rust_path(resolve_multiname_name(&abc, trait_name));
path += suffix;
// Now that we've built up the path, convert it into a `TokenStream`.
// This gives us something like
// `crate::avm2::globals::flash::system::Security::allowDomain`
//
// The resulting `TokenStream` is suitable for usage with `quote!` to
// generate a reference to the function pointer that should exist
// at that path in Rust code.
let path_tokens = TokenStream::from_str(&path).unwrap();
quote! { Some(#path_tokens) }
}
fn strip_metadata(abc: &mut AbcFile) {
abc.metadata.clear();
for instance in &mut abc.instances {
for trait_ in &mut instance.traits {
trait_.metadata.clear();
}
}
for class in &mut abc.classes {
for trait_ in &mut class.traits {
trait_.metadata.clear();
}
}
for script in &mut abc.scripts {
for trait_ in &mut script.traits {
trait_.metadata.clear();
}
}
for body in &mut abc.method_bodies {
for trait_ in &mut body.traits {
trait_.metadata.clear();
}
}
}
/// Handles native functons defined in our `playerglobal` /// Handles native functons defined in our `playerglobal`
/// ///
/// The high-level idea is to generate code (specifically, a `TokenStream`) /// The high-level idea is to generate code (specifically, a `TokenStream`)
@ -109,12 +202,16 @@ fn resolve_multiname_ns<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a str
/// ///
/// See `flash.system.Security.allowDomain` for an example of defining /// See `flash.system.Security.allowDomain` for an example of defining
/// and using a native method. /// and using a native method.
fn write_native_table(data: &[u8], out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> { ///
/// Returns a modified version of 'data' that should be saved to disk
/// in our generated SWF
fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut reader = swf::avm2::read::Reader::new(data); let mut reader = swf::avm2::read::Reader::new(data);
let abc = reader.read()?; let mut abc = reader.read()?;
let none_tokens = quote! { None }; let none_tokens = quote! { None };
let mut rust_paths = vec![none_tokens; abc.methods.len()]; let mut rust_paths = vec![none_tokens.clone(); abc.methods.len()];
let mut rust_instance_allocators = vec![none_tokens; abc.classes.len()];
let mut check_trait = |trait_: &Trait, parent: Option<Index<Multiname>>| { let mut check_trait = |trait_: &Trait, parent: Option<Index<Multiname>>| {
let method_id = match trait_.kind { let method_id = match trait_.kind {
@ -145,55 +242,55 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<(), Box<dyn std::er
_ => "", _ => "",
}; };
let flash_to_rust_path = |path: &str| { rust_paths[method_id.0 as usize] =
// Convert each component of the path to snake-case. rust_method_path(&abc, trait_, parent, method_prefix, "");
// This correctly handles sequences of upper-case letters, };
// so 'URLLoader' becomes 'url_loader'
let components = path // Look for `[Ruffle(InstanceAllocator)]` metadata - if present,
.split('.') // generate a reference to an allocator function in the native instance
.map(|component| component.to_case(Case::Snake)) // allocators table.
.collect::<Vec<_>>(); let mut check_instance_allocator = |trait_: &Trait| {
// Form a Rust path from the snake-case components let class_id = if let TraitKind::Class { slot_id, .. } = trait_.kind {
components.join("::") slot_id
} else {
return;
}; };
let mut path = "crate::avm2::globals::".to_string(); let class_name_idx = abc.instances[class_id as usize - 1].name.0;
let class_name = resolve_multiname_name(
&abc,
&abc.constant_pool.multinames[class_name_idx as usize - 1],
);
let trait_name = &abc.constant_pool.multinames[trait_.name.0 as usize - 1]; let method_name = "::".to_string() + &flash_to_rust_path(class_name) + "_allocator";
if let Some(parent) = parent { for metadata_idx in &trait_.metadata {
// This is a method defined inside the class. Append the class namespace let metadata = &abc.metadata[metadata_idx.0 as usize];
// (the package) and the class name. let name = &abc.constant_pool.strings[metadata.name.0 as usize - 1];
// For example, a namespace of "flash.system" and a name of "Security" match name.as_str() {
// turns into the path "flash::system::security" RUFFLE_METADATA_NAME => {}
let multiname = &abc.constant_pool.multinames[parent.0 as usize - 1]; _ => panic!("Unexpected class metadata {:?}", name),
path += &flash_to_rust_path(resolve_multiname_ns(&abc, multiname)); }
path += "::";
path += &flash_to_rust_path(resolve_multiname_name(&abc, multiname)); for item in &metadata.items {
path += "::"; let key = if item.key.0 != 0 {
} else { Some(abc.constant_pool.strings[item.key.0 as usize - 1].as_str())
// This is a freestanding function. Append its namespace (the package). } else {
// For example, the freestanding function "flash.utils.getDefinitionByName" None
// has a namespace of "flash.utils", which turns into the path };
// "flash::utils" let value = &abc.constant_pool.strings[item.value.0 as usize - 1];
path += &flash_to_rust_path(resolve_multiname_ns(&abc, trait_name)); match (key, value.as_str()) {
path += "::"; // Match `[Ruffle(InstanceAllocator)]`
(None, METADATA_INSTANCE_ALLOCATOR) => {
// This results in a path of the form
// `crate::avm2::globals::<path::to::class>::<class_allocator>`
rust_instance_allocators[class_id as usize - 1] =
rust_method_path(&abc, trait_, None, "", &method_name);
}
_ => panic!("Unexpected metadata pair ({:?}, {})", key, value),
}
}
} }
// Append the trait name - this corresponds to the actual method
// name (e.g. `getDefinitionByName`)
path += method_prefix;
path += &flash_to_rust_path(resolve_multiname_name(&abc, trait_name));
// Now that we've built up the path, convert it into a `TokenStream`.
// This gives us something like
// `crate::avm2::globals::flash::system::Security::allowDomain`
//
// The resulting `TokenStream` is suitable for usage with `quote!` to
// generate a reference to the function pointer that should exist
// at that path in Rust code.
let path_tokens = TokenStream::from_str(&path).unwrap();
rust_paths[method_id.0 as usize] = quote! { Some(#path_tokens) };
}; };
// We support three kinds of native methods: // We support three kinds of native methods:
@ -216,29 +313,37 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<(), Box<dyn std::er
for script in &abc.scripts { for script in &abc.scripts {
for trait_ in &script.traits { for trait_ in &script.traits {
check_trait(trait_, None); check_trait(trait_, None);
check_instance_allocator(trait_);
} }
} }
// Finally, generate the actual code.
// Finally, generate the actual code. This is a Rust array -
// the entry at index `i` is a Rust function pointer for the native
// method with id `i`. Not all methods in playerglobal will be native
// methods, so we store `None` in the entries corresponding to non-native
// functions. We expect the majority of the methods in playerglobal to be
// native, so this should only waste a small amount of memory.
//
// If a function pointer doesn't exist at the expected path,
// then Ruffle compilation will fail
// with an error message that mentions the non-existent path.
//
// When we initially load a method from an ABC file, we check if it's from our playerglobal,
// and if its ID exists in this table.
// If so, we replace it with a `NativeMethod` constructed
// from the function pointer we looked up in the table.
let make_native_table = quote! { let make_native_table = quote! {
const NATIVE_TABLE: &[Option<crate::avm2::method::NativeMethodImpl>] = &[ // This is a Rust array -
// the entry at index `i` is a Rust function pointer for the native
// method with id `i`. Not all methods in playerglobal will be native
// methods, so we store `None` in the entries corresponding to non-native
// functions. We expect the majority of the methods in playerglobal to be
// native, so this should only waste a small amount of memory.
//
// If a function pointer doesn't exist at the expected path,
// then Ruffle compilation will fail
// with an error message that mentions the non-existent path.
//
// When we initially load a method from an ABC file, we check if it's from our playerglobal,
// and if its ID exists in this table.
// If so, we replace it with a `NativeMethod` constructed
// from the function pointer we looked up in the table.
pub const NATIVE_METHOD_TABLE: &[Option<crate::avm2::method::NativeMethodImpl>] = &[
#(#rust_paths,)* #(#rust_paths,)*
]; ];
// This is very similar to `NATIVE_METHOD_TABLE`, but we have one entry per
// class, rather than per method. When an entry is `Some(fn_ptr)`, we use
// `fn_ptr` as the instance allocator for the corresponding class when we
// load it into Ruffle.
pub const NATIVE_INSTANCE_ALLOCATOR_TABLE: &[Option<crate::avm2::class::AllocatorFn>] = &[
#(#rust_instance_allocators,)*
];
} }
.to_string(); .to_string();
@ -249,5 +354,13 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<(), Box<dyn std::er
let mut native_table_file = File::create(out_dir.join("native_table.rs"))?; let mut native_table_file = File::create(out_dir.join("native_table.rs"))?;
native_table_file.write_all(make_native_table.as_bytes())?; native_table_file.write_all(make_native_table.as_bytes())?;
Ok(()) // Ruffle doesn't need metadata items at runtime, so strip
// them out to save space
strip_metadata(&mut abc);
let mut out_bytes = Vec::new();
let mut writer = Writer::new(&mut out_bytes);
writer.write(abc).expect("Failed to write modified ABC");
Ok(out_bytes)
} }

View File

@ -1,5 +1,6 @@
//! ActionScript Virtual Machine 2 (AS3) support //! ActionScript Virtual Machine 2 (AS3) support
use crate::avm2::class::AllocatorFn;
use crate::avm2::globals::SystemClasses; use crate::avm2::globals::SystemClasses;
use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::object::EventObject; use crate::avm2::object::EventObject;
@ -76,7 +77,10 @@ pub struct Avm2<'gc> {
system_classes: Option<SystemClasses<'gc>>, system_classes: Option<SystemClasses<'gc>>,
#[collect(require_static)] #[collect(require_static)]
native_table: &'static [Option<NativeMethodImpl>], native_method_table: &'static [Option<NativeMethodImpl>],
#[collect(require_static)]
native_instance_allocator_table: &'static [Option<AllocatorFn>],
/// A list of objects which are capable of recieving broadcasts. /// A list of objects which are capable of recieving broadcasts.
/// ///
@ -101,7 +105,8 @@ impl<'gc> Avm2<'gc> {
stack: Vec::new(), stack: Vec::new(),
globals, globals,
system_classes: None, system_classes: None,
native_table: Default::default(), native_method_table: Default::default(),
native_instance_allocator_table: Default::default(),
broadcast_list: Default::default(), broadcast_list: Default::default(),
#[cfg(feature = "avm_debug")] #[cfg(feature = "avm_debug")]

View File

@ -354,6 +354,16 @@ impl<'gc> Class<'gc> {
attributes.set(ClassAttributes::FINAL, abc_instance.is_final); attributes.set(ClassAttributes::FINAL, abc_instance.is_final);
attributes.set(ClassAttributes::INTERFACE, abc_instance.is_interface); attributes.set(ClassAttributes::INTERFACE, abc_instance.is_interface);
let mut instance_allocator = None;
// When loading a class from our playerglobal, grab the corresponding native
// allocator function from the table (which may be `None`)
if unit.domain().is_avm2_global_domain(activation) {
instance_allocator = activation.avm2().native_instance_allocator_table
[class_index as usize]
.map(Allocator);
}
Ok(GcCell::allocate( Ok(GcCell::allocate(
activation.context.gc_context, activation.context.gc_context,
Self { Self {
@ -363,7 +373,7 @@ impl<'gc> Class<'gc> {
attributes, attributes,
protected_namespace, protected_namespace,
interfaces, interfaces,
instance_allocator: None, instance_allocator,
instance_init, instance_init,
native_instance_init, native_instance_init,
instance_traits: Vec::new(), instance_traits: Vec::new(),

View File

@ -210,9 +210,10 @@ fn class<'gc>(
let class_read = class_def.read(); let class_read = class_def.read();
let super_class = if let Some(sc_name) = class_read.super_class_name() { let super_class = if let Some(sc_name) = class_read.super_class_name() {
let super_class: Result<Object<'gc>, Error> = global let super_class: Result<Object<'gc>, Error> = activation
.get_property(sc_name, activation) .resolve_definition(sc_name)
.ok() .ok()
.and_then(|v| v)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| { .ok_or_else(|| {
format!( format!(
@ -441,13 +442,6 @@ pub fn load_player_globals<'gc>(
)?; )?;
class(activation, flash::system::system::create_class(mc), script)?; class(activation, flash::system::system::create_class(mc), script)?;
// package `flash.events`
avm2_system_class!(
event,
activation,
flash::events::event::create_class(mc),
script
);
class( class(
activation, activation,
flash::events::ieventdispatcher::create_interface(mc), flash::events::ieventdispatcher::create_interface(mc),
@ -458,62 +452,6 @@ pub fn load_player_globals<'gc>(
flash::events::eventdispatcher::create_class(mc), flash::events::eventdispatcher::create_class(mc),
script, script,
)?; )?;
avm2_system_class!(
mouseevent,
activation,
flash::events::mouseevent::create_class(mc),
script
);
avm2_system_class!(
textevent,
activation,
flash::events::textevent::create_class(mc),
script
);
avm2_system_class!(
errorevent,
activation,
flash::events::errorevent::create_class(mc),
script
);
avm2_system_class!(
securityerrorevent,
activation,
flash::events::securityerrorevent::create_class(mc),
script
);
avm2_system_class!(
ioerrorevent,
activation,
flash::events::ioerrorevent::create_class(mc),
script
);
class(
activation,
flash::events::contextmenuevent::create_class(mc),
script,
)?;
class(
activation,
flash::events::keyboardevent::create_class(mc),
script,
)?;
class(
activation,
flash::events::progressevent::create_class(mc),
script,
)?;
class(
activation,
flash::events::activityevent::create_class(mc),
script,
)?;
avm2_system_class!(
fullscreenevent,
activation,
flash::events::fullscreenevent::create_class(mc),
script
);
class( class(
activation, activation,
flash::events::eventphase::create_class(mc), flash::events::eventphase::create_class(mc),
@ -750,10 +688,64 @@ pub fn load_player_globals<'gc>(
)?; )?;
// Inside this call, the macro `avm2_system_classes_playerglobal` // Inside this call, the macro `avm2_system_classes_playerglobal`
// triggers classloading. Therefore, we run `load_playerglobal` last, // triggers classloading. Therefore, we run `load_playerglobal`
// so that all of our classes have been defined. // relative late, so that it can access classes defined before
// this call.
load_playerglobal(activation, domain)?; load_playerglobal(activation, domain)?;
// These are event definitions, which need to be able to
// load "flash.events.Event", which is defined in our playerglobal.
// Therefore, they need to come after "load_playerglobal"
// FIXME: Convert all of these event classes to ActionScript,
// which will allow us to remove all of these calls.
avm2_system_class!(
mouseevent,
activation,
flash::events::mouseevent::create_class(mc),
script
);
avm2_system_class!(
textevent,
activation,
flash::events::textevent::create_class(mc),
script
);
avm2_system_class!(
errorevent,
activation,
flash::events::errorevent::create_class(mc),
script
);
avm2_system_class!(
securityerrorevent,
activation,
flash::events::securityerrorevent::create_class(mc),
script
);
avm2_system_class!(
ioerrorevent,
activation,
flash::events::ioerrorevent::create_class(mc),
script
);
class(
activation,
flash::events::keyboardevent::create_class(mc),
script,
)?;
class(
activation,
flash::events::progressevent::create_class(mc),
script,
)?;
avm2_system_class!(
fullscreenevent,
activation,
flash::events::fullscreenevent::create_class(mc),
script
);
Ok(()) Ok(())
} }
@ -761,8 +753,9 @@ pub fn load_player_globals<'gc>(
/// See that tool, and 'core/src/avm2/globals/README.md', for more details /// See that tool, and 'core/src/avm2/globals/README.md', for more details
const PLAYERGLOBAL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/playerglobal.swf")); const PLAYERGLOBAL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/playerglobal.swf"));
// This defines a const named `NATIVE_TABLE` mod native {
include!(concat!(env!("OUT_DIR"), "/native_table.rs")); include!(concat!(env!("OUT_DIR"), "/native_table.rs"));
}
/// Loads classes from our custom 'playerglobal' (which are written in ActionScript) /// Loads classes from our custom 'playerglobal' (which are written in ActionScript)
/// into the environment. See 'core/src/avm2/globals/README.md' for more information /// into the environment. See 'core/src/avm2/globals/README.md' for more information
@ -770,7 +763,8 @@ fn load_playerglobal<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
domain: Domain<'gc>, domain: Domain<'gc>,
) -> Result<(), Error> { ) -> Result<(), Error> {
activation.avm2().native_table = NATIVE_TABLE; activation.avm2().native_method_table = native::NATIVE_METHOD_TABLE;
activation.avm2().native_instance_allocator_table = native::NATIVE_INSTANCE_ALLOCATOR_TABLE;
let movie = Arc::new(SwfMovie::from_data(PLAYERGLOBAL, None, None)?); let movie = Arc::new(SwfMovie::from_data(PLAYERGLOBAL, None, None)?);
@ -805,7 +799,14 @@ fn load_playerglobal<'gc>(
// This acts the same way as 'avm2_system_class', but for classes // This acts the same way as 'avm2_system_class', but for classes
// declared in 'playerglobal'. Classes are declared as ("package", "class", field_name), // declared in 'playerglobal'. Classes are declared as ("package", "class", field_name),
// and are stored in 'avm2().system_classes' // and are stored in 'avm2().system_classes'
avm2_system_classes_playerglobal!(activation, script, [("flash.display", "Scene", scene)]); avm2_system_classes_playerglobal!(
activation,
script,
[
("flash.display", "Scene", scene),
("flash.events", "Event", event),
]
);
Ok(()) Ok(())
} }

View File

@ -52,6 +52,17 @@ The ActionScript method and the Rust function are automatically linked
together, and the Rust function will be invoked when the corresponding together, and the Rust function will be invoked when the corresponding
function is called from ActionScript. function is called from ActionScript.
## Custom instance allocator
You can use a custom instance allocator method by applying the metadata
`[Ruffle(InstanceAllocator)]`
to your class definition. A reference to a function named `<classname>_allocator`
will be generated - this should be an `AllocatorFn`, just like when defining
a class in Rust. This allocator will automatically be registered when the corresponding
class is loaded.
See `flash/events/Event.as` for an example
## Compiling ## Compiling
Java must be installed for the build process to complete. Java must be installed for the build process to complete.

View File

@ -0,0 +1,5 @@
// This is a stub - the actual class is defined in `interactiveobject.rs`
package flash.display {
public class InteractiveObject {
}
}

View File

@ -1,7 +1,5 @@
//! `flash.events` namespace //! `flash.events` namespace
pub mod activityevent;
pub mod contextmenuevent;
pub mod errorevent; pub mod errorevent;
pub mod event; pub mod event;
pub mod eventdispatcher; pub mod eventdispatcher;

View File

@ -0,0 +1,19 @@
package flash.events {
import flash.events.Event;
public class ActivityEvent extends Event {
public var activating:Boolean;
public function ActivityEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, activating:Boolean = false) {
super(type, bubbles, cancelable);
this.activating = activating;
}
override public function clone() : Event {
return new ActivityEvent(this.type, this.bubbles, this.cancelable, this.activating);
}
override public function toString(): String {
return formatToString("ActivityEvent","type","bubbles","cancelable","eventPhase","activating");
}
}
}

View File

@ -0,0 +1,49 @@
package flash.events {
import flash.display.InteractiveObject;
public class ContextMenuEvent extends Event {
public static const MENU_ITEM_SELECT:String = "menuItemSelect";
public static const MENU_SELECT:String = "menuSelect";
private var _mouseTarget:InteractiveObject;
private var _contextMenuOwner:InteractiveObject;
private var _isMouseTargetInaccessible:Boolean;
public function ContextMenuEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, mouseTarget:InteractiveObject = null, contextMenuOwner:InteractiveObject = null) {
super(type,bubbles,cancelable);
this._mouseTarget = mouseTarget;
this._contextMenuOwner = contextMenuOwner;
}
override public function clone() : Event {
return new ContextMenuEvent(this.type, this.bubbles, this.cancelable, this._mouseTarget, this._contextMenuOwner);
}
override public function toString() : String {
return this.formatToString("ContextMenuEvent","type","bubbles","cancelable","eventPhase","mouseTarget","contextMenuOwner");
}
public function get mouseTarget() : InteractiveObject {
return this._mouseTarget;
}
public function set mouseTarget(value:InteractiveObject) : void {
this._mouseTarget = value;
}
public function get contextMenuOwner() : InteractiveObject {
return this._contextMenuOwner;
}
public function set contextMenuOwner(value:InteractiveObject) : void {
this._contextMenuOwner = value;
}
public function get isMouseTargetInaccessible() : Boolean {
return this._isMouseTargetInaccessible;
}
public function set isMouseTargetInaccessible(value:Boolean) : void {
this._isMouseTargetInaccessible = value;
}
}
}

View File

@ -0,0 +1,129 @@
package flash.events {
[Ruffle(InstanceAllocator)]
public class Event {
public static const ACTIVATE:String = "activate";
public static const ADDED:String = "added";
public static const ADDED_TO_STAGE:String = "addedToStage";
public static const BROWSER_ZOOM_CHANGE:String = "browserZoomChange";
public static const CANCEL:String = "cancel";
public static const CHANGE:String = "change";
public static const CLEAR:String = "clear";
public static const CLOSE:String = "close";
public static const COMPLETE:String = "complete";
public static const CONNECT:String = "connect";
public static const COPY:String = "copy";
public static const CUT:String = "cut";
public static const DEACTIVATE:String = "deactivate";
public static const ENTER_FRAME:String = "enterFrame";
public static const FRAME_CONSTRUCTED:String = "frameConstructed";
public static const EXIT_FRAME:String = "exitFrame";
public static const FRAME_LABEL:String = "frameLabel";
public static const ID3:String = "id3";
public static const INIT:String = "init";
public static const MOUSE_LEAVE:String = "mouseLeave";
public static const OPEN:String = "open";
public static const PASTE:String = "paste";
public static const REMOVED:String = "removed";
public static const REMOVED_FROM_STAGE:String = "removedFromStage";
public static const RENDER:String = "render";
public static const RESIZE:String = "resize";
public static const SCROLL:String = "scroll";
public static const TEXT_INTERACTION_MODE_CHANGE:String = "textInteractionModeChange";
public static const SELECT:String = "select";
public static const SELECT_ALL:String = "selectAll";
public static const SOUND_COMPLETE:String = "soundComplete";
public static const TAB_CHILDREN_CHANGE:String = "tabChildrenChange";
public static const TAB_ENABLED_CHANGE:String = "tabEnabledChange";
public static const TAB_INDEX_CHANGE:String = "tabIndexChange";
public static const UNLOAD:String = "unload";
public static const FULLSCREEN:String = "fullScreen";
public static const CONTEXT3D_CREATE:String = "context3DCreate";
public static const TEXTURE_READY:String = "textureReady";
public static const VIDEO_FRAME:String = "videoFrame";
public static const SUSPEND:String = "suspend";
public static const CHANNEL_MESSAGE:String = "channelMessage";
public static const CHANNEL_STATE:String = "channelState";
public static const WORKER_STATE:String = "workerState";
public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
this.init(type, bubbles, cancelable);
}
private native function init(type:String, bubbles:Boolean = false, cancelable:Boolean = false):void;
public native function get bubbles():Boolean;
public native function get cancelable():Boolean;
public native function get currentTarget():Object;
public native function get eventPhase():uint;
public native function get target():Object;
public native function get type():String;
public function clone():Event {
return new Event(this.type, this.bubbles, this.cancelable);
}
public function toString(): String {
return this.formatToString("Event","type","bubbles","cancelable","eventPhase");
}
public function formatToString(className:String, ... arguments):String {
var fmt = "[" + className;
for each (var key: String in arguments) {
var val = this[key];
if(val is String) {
fmt += " " + key + "=\"" + val + "\"";
} else {
fmt += " " + key + "=" + val;
}
}
return fmt += "]";
}
public native function isDefaultPrevented(): Boolean;
public native function preventDefault():void;
public native function stopPropagation():void;
public native function stopImmediatePropagation():void;
}
}

View File

@ -1,76 +0,0 @@
use crate::avm2::activation::Activation;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.events.ActivityEvent`'s instance constructor.
pub fn instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
activation.super_init(this, args)?; // Event uses the first three parameters
}
Ok(Value::Undefined)
}
/// Implements `flash.events.ActivityEvent`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
pub fn activating<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
log::warn!("ActivityEvent.activating - not implemented");
Ok(Value::Undefined)
}
pub fn set_activating<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
log::warn!("ActivityEvent.set_activating - not implemented");
Ok(Value::Undefined)
}
/// Construct `ActivityEvent`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new(
QName::new(Namespace::package("flash.events"), "ActivityEvent"),
Some(QName::new(Namespace::package("flash.events"), "Event").into()),
Method::from_builtin(instance_init, "<ActivityEvent instance initializer>", mc),
Method::from_builtin(class_init, "<ActivityEvent class initializer>", mc),
mc,
);
let mut write = class.write(mc);
const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,
Option<NativeMethodImpl>,
)] = &[("activating", Some(activating), Some(set_activating))];
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
write.set_attributes(ClassAttributes::SEALED);
const CONSTANTS: &[(&str, &str)] = &[("ACTIVITY", "activity")];
write.define_public_constant_string_class_traits(CONSTANTS);
class
}

View File

@ -1,53 +0,0 @@
use crate::avm2::activation::Activation;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.events.ContextMenuEvent`'s instance constructor.
pub fn instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
activation.super_init(this, args)?; // ErrorEvent, Event use these
}
Ok(Value::Undefined)
}
/// Implements `flash.events.ContextMenuEvent`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `ContextMenuEvent`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new(
QName::new(Namespace::package("flash.events"), "ContextMenuEvent"),
Some(QName::new(Namespace::package("flash.events"), "Event").into()),
Method::from_builtin(instance_init, "<ContextMenuEvent instance initializer>", mc),
Method::from_builtin(class_init, "<ContextMenuEvent class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_attributes(ClassAttributes::SEALED);
const CONSTANTS: &[(&str, &str)] = &[
("MENU_ITEM_SELECT", "menuItemSelect"),
("MENU_SELECT", "menuSelect"),
];
write.define_public_constant_string_class_traits(CONSTANTS);
class
}

View File

@ -1,60 +1,42 @@
//! `flash.events.Event` builtin/prototype //! `flash.events.Event` builtin/prototype
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::object::{Object, TObject};
use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{event_allocator, EventObject, Object, TObject};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::string::AvmString;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.events.Event`'s instance constructor. pub use crate::avm2::object::event_allocator;
pub fn instance_init<'gc>(
pub fn init<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(this) = this { let this = this.unwrap();
activation.super_init(this, &[])?; let mut evt = this.as_event_mut(activation.context.gc_context).unwrap();
evt.set_event_type(
if let Some(mut evt) = this.as_event_mut(activation.context.gc_context) { args.get(0)
evt.set_event_type( .cloned()
args.get(0) .unwrap_or(Value::Undefined)
.cloned() .coerce_to_string(activation)?,
.unwrap_or(Value::Undefined) );
.coerce_to_string(activation)?, evt.set_bubbles(
); args.get(1)
evt.set_bubbles( .cloned()
args.get(1) .unwrap_or(Value::Bool(false))
.cloned() .coerce_to_boolean(),
.unwrap_or(Value::Bool(false)) );
.coerce_to_boolean(), evt.set_cancelable(
); args.get(2)
evt.set_cancelable( .cloned()
args.get(2) .unwrap_or(Value::Bool(false))
.cloned() .coerce_to_boolean(),
.unwrap_or(Value::Bool(false)) );
.coerce_to_boolean(),
);
}
}
Ok(Value::Undefined)
}
/// Implements `flash.events.Event`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Implements `bubbles` property's getter /// Implements `bubbles` property's getter
pub fn bubbles<'gc>( pub fn get_bubbles<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -67,7 +49,7 @@ pub fn bubbles<'gc>(
} }
/// Implements `cancelable` property's getter /// Implements `cancelable` property's getter
pub fn cancelable<'gc>( pub fn get_cancelable<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -93,7 +75,7 @@ pub fn get_type<'gc>(
} }
/// Implements `target` property's getter /// Implements `target` property's getter
pub fn target<'gc>( pub fn get_target<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -106,7 +88,7 @@ pub fn target<'gc>(
} }
/// Implements `currentTarget` property's getter /// Implements `currentTarget` property's getter
pub fn current_target<'gc>( pub fn get_current_target<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -122,7 +104,7 @@ pub fn current_target<'gc>(
} }
/// Implements `eventPhase` property's getter /// Implements `eventPhase` property's getter
pub fn event_phase<'gc>( pub fn get_event_phase<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -135,66 +117,6 @@ pub fn event_phase<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Implements `clone`
pub fn clone<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(evt) = this.unwrap().as_event() {
return Ok(EventObject::from_event(activation, evt.clone())?.into());
}
Ok(Value::Undefined)
}
/// Implements `formatToString`
pub fn format_to_string<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
use std::fmt::Write;
if let Some(this) = this {
let class_name = args
.get(0)
.cloned()
.unwrap_or(Value::Undefined)
.coerce_to_string(activation)?;
let mut stringified_params = String::new();
if let Some(params) = args.get(1..) {
for param_name in params {
let param_name = QName::dynamic_name(match param_name {
Value::Undefined | Value::Null => "null".into(),
_ => param_name.coerce_to_string(activation)?,
})
.into();
let param_value = this
.get_property(&param_name, activation)?
.coerce_to_debug_string(activation)?;
write!(
stringified_params,
" {}={}",
param_name.local_name().unwrap(),
param_value
)
.unwrap();
}
}
return Ok(AvmString::new_utf8(
activation.context.gc_context,
format!("[{}{}]", class_name, stringified_params),
)
.into());
}
Ok(Value::Undefined)
}
/// Implements `isDefaultPrevented` /// Implements `isDefaultPrevented`
pub fn is_default_prevented<'gc>( pub fn is_default_prevented<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
@ -246,134 +168,3 @@ pub fn stop_immediate_propagation<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Implements `toString`
pub fn to_string<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
if let Some(event) = this.as_event() {
let event_type = event.event_type();
let bubbles = event.is_bubbling();
let cancelable = event.is_cancelable();
let phase = event.phase() as u32;
return Ok(AvmString::new_utf8(
activation.context.gc_context,
format!(
"[Event type=\"{}\" bubbles={} cancelable={} eventPhase={}]",
event_type, bubbles, cancelable, phase
),
)
.into());
}
}
Ok(Value::Undefined)
}
/// Construct `Event`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new(
QName::new(Namespace::package("flash.events"), "Event"),
Some(QName::new(Namespace::public(), "Object").into()),
Method::from_builtin(instance_init, "<Event instance initializer>", mc),
Method::from_builtin(class_init, "<Event class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_attributes(ClassAttributes::SEALED);
write.set_instance_allocator(event_allocator);
const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,
Option<NativeMethodImpl>,
)] = &[
("bubbles", Some(bubbles), None),
("cancelable", Some(cancelable), None),
("type", Some(get_type), None),
("target", Some(target), None),
("currentTarget", Some(current_target), None),
("eventPhase", Some(event_phase), None),
];
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[
("clone", clone),
("formatToString", format_to_string),
("isDefaultPrevented", is_default_prevented),
("preventDefault", prevent_default),
("stopPropagation", stop_propagation),
("stopImmediatePropagation", stop_immediate_propagation),
("toString", to_string),
];
write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS);
const CONSTANTS: &[(&str, &str)] = &[
("ACTIVATE", "activate"),
("ADDED", "added"),
("ADDED_TO_STAGE", "addedToStage"),
("BROWSER_ZOOM_CHANGE", "browserZoomChange"),
("CANCEL", "cancel"),
("CHANGE", "change"),
("CHANNEL_MESSAGE", "channelMessage"),
("CHANNEL_STATE", "channelState"),
("CLEAR", "clear"),
("CLOSE", "close"),
("CLOSING", "closing"),
("COMPLETE", "complete"),
("CONNECT", "connect"),
("CONTEXT3D_CREATE", "context3DCreate"),
("COPY", "copy"),
("CUT", "cut"),
("DEACTIVATE", "deactivate"),
("DISPLAYING", "displaying"),
("ENTER_FRAME", "enterFrame"),
("EXIT_FRAME", "exitFrame"),
("EXITING", "exiting"),
("FRAME_CONSTRUCTED", "frameConstructed"),
("FRAME_LABEL", "frameLabel"),
("FULLSCREEN", "fullScreen"),
("HTML_BOUNDS_CHANGE", "htmlBoundsChange"),
("HTML_DOM_INITIALIZE", "htmlDOMInitialize"),
("HTML_RENDER", "htmlRender"),
("ID3", "id3"),
("INIT", "init"),
("LOCATION_CHANGE", "locationChange"),
("MOUSE_LEAVE", "mouseLeave"),
("NETWORK_CHANGE", "networkChange"),
("OPEN", "open"),
("PASTE", "paste"),
("PREPARING", "preparing"),
("REMOVED", "removed"),
("REMOVED_FROM_STAGE", "removedFromStage"),
("RENDER", "render"),
("RESIZE", "resize"),
("SCROLL", "scroll"),
("SELECT", "select"),
("SELECT_ALL", "selectAll"),
("SOUND_COMPLETE", "soundComplete"),
("STANDARD_ERROR_CLOSE", "standardErrorClose"),
("STANDARD_INPUT_CLOSE", "standardInputClose"),
("STANDARD_OUTPUT_CLOSE", "standardOutputClose"),
("SUSPEND", "suspend"),
("TAB_CHILDREN_CHANGE", "tabChildrenChange"),
("TAB_ENABLED_CHANGE", "tabEnabledChange"),
("TAB_INDEX_CHANGE", "tabIndexChange"),
("TEXT_INTERACTION_MODE_CHANGE", "textInteractionModeChange"),
("TEXTURE_READY", "textureReady"),
("UNLOAD", "unload"),
("USER_IDLE", "userIdle"),
("USER_PRESENT", "userPresent"),
("VIDEO_FRAME", "videoFrame"),
("WORKER_STATE", "workerState"),
];
write.define_public_constant_string_class_traits(CONSTANTS);
class
}

View File

@ -17,7 +17,9 @@ pub fn instance_init<'gc>(
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(this) = this { if let Some(this) = this {
activation.super_init(this, args)?; // Get up to three arguments
let event_args = &args[..(std::cmp::min(args.len(), 2))];
activation.super_init(this, event_args)?;
if let Some(mut evt) = this.as_event_mut(activation.context.gc_context) { if let Some(mut evt) = this.as_event_mut(activation.context.gc_context) {
// This is technically duplicative of `Event`'s initializer, but // This is technically duplicative of `Event`'s initializer, but
// we have different default parameters. // we have different default parameters.

View File

@ -1,4 +1,6 @@
// List is ordered alphabetically. // List is ordered alphabetically, except where superclasses
// need to come before subclasses.
include "flash/accessibility/AccessibilityProperties.as" include "flash/accessibility/AccessibilityProperties.as"
include "flash/display/ActionScriptVersion.as" include "flash/display/ActionScriptVersion.as"
include "flash/display/BitmapDataChannel.as" include "flash/display/BitmapDataChannel.as"
@ -30,6 +32,12 @@ include "flash/display/StageQuality.as"
include "flash/display/StageScaleMode.as" include "flash/display/StageScaleMode.as"
include "flash/display/SWFVersion.as" include "flash/display/SWFVersion.as"
include "flash/display/TriangleCulling.as" include "flash/display/TriangleCulling.as"
// Event needs to come before its subclasses
include "flash/events/Event.as"
include "flash/events/ActivityEvent.as"
include "flash/events/ContextMenuEvent.as"
include "flash/geom/ColorTransform.as" include "flash/geom/ColorTransform.as"
include "flash/geom/Orientation3D.as" include "flash/geom/Orientation3D.as"
include "flash/geom/Matrix.as" include "flash/geom/Matrix.as"

View File

@ -6,6 +6,7 @@ include "Object.as"
// List is ordered alphabetically. // List is ordered alphabetically.
include "Array.as" include "Array.as"
include "Boolean.as" include "Boolean.as"
include "flash/display/InteractiveObject.as"
include "flash/events/EventDispatcher.as" include "flash/events/EventDispatcher.as"
include "Number.as" include "Number.as"
include "String.as" include "String.as"

View File

@ -140,7 +140,8 @@ impl<'gc> TranslationUnit<'gc> {
// allowing us to use 'bc_method' later on without a borrow-checker error. // allowing us to use 'bc_method' later on without a borrow-checker error.
let method = (|| { let method = (|| {
if is_global { if is_global {
if let Some(native) = activation.avm2().native_table[method_index.0 as usize] { if let Some(native) = activation.avm2().native_method_table[method_index.0 as usize]
{
let variadic = bc_method.is_variadic(); let variadic = bc_method.is_variadic();
return Method::from_builtin_and_params( return Method::from_builtin_and_params(
native, native,