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:
parent
01c8c38762
commit
bb6f07ee1a
Binary file not shown.
|
@ -10,11 +10,19 @@ use std::path::{Path, PathBuf};
|
|||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
use swf::avm2::types::*;
|
||||
use swf::avm2::write::Writer;
|
||||
use swf::DoAbc;
|
||||
use swf::Header;
|
||||
use swf::SwfStr;
|
||||
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
|
||||
/// from a build script, these paths should be printed with
|
||||
/// cargo:rerun-if-changed
|
||||
|
@ -49,14 +57,14 @@ pub fn build_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'
|
||||
std::fs::remove_file(playerglobal.with_extension("abc"))?;
|
||||
std::fs::remove_file(playerglobal.with_extension("cpp"))?;
|
||||
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 {
|
||||
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`
|
||||
///
|
||||
/// 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
|
||||
/// 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 abc = reader.read()?;
|
||||
let mut abc = reader.read()?;
|
||||
|
||||
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 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| {
|
||||
// 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("::")
|
||||
rust_paths[method_id.0 as usize] =
|
||||
rust_method_path(&abc, trait_, parent, method_prefix, "");
|
||||
};
|
||||
|
||||
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 += "::";
|
||||
// Look for `[Ruffle(InstanceAllocator)]` metadata - if present,
|
||||
// generate a reference to an allocator function in the native instance
|
||||
// allocators table.
|
||||
let mut check_instance_allocator = |trait_: &Trait| {
|
||||
let class_id = if let TraitKind::Class { slot_id, .. } = trait_.kind {
|
||||
slot_id
|
||||
} 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 += "::";
|
||||
return;
|
||||
};
|
||||
|
||||
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 method_name = "::".to_string() + &flash_to_rust_path(class_name) + "_allocator";
|
||||
|
||||
for metadata_idx in &trait_.metadata {
|
||||
let metadata = &abc.metadata[metadata_idx.0 as usize];
|
||||
let name = &abc.constant_pool.strings[metadata.name.0 as usize - 1];
|
||||
match name.as_str() {
|
||||
RUFFLE_METADATA_NAME => {}
|
||||
_ => panic!("Unexpected class metadata {:?}", name),
|
||||
}
|
||||
|
||||
// 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) };
|
||||
for item in &metadata.items {
|
||||
let key = if item.key.0 != 0 {
|
||||
Some(abc.constant_pool.strings[item.key.0 as usize - 1].as_str())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let value = &abc.constant_pool.strings[item.value.0 as usize - 1];
|
||||
match (key, value.as_str()) {
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// We support three kinds of native methods:
|
||||
|
@ -216,10 +313,12 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<(), Box<dyn std::er
|
|||
for script in &abc.scripts {
|
||||
for trait_ in &script.traits {
|
||||
check_trait(trait_, None);
|
||||
check_instance_allocator(trait_);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, generate the actual code. This is a Rust array -
|
||||
// Finally, generate the actual code.
|
||||
let make_native_table = quote! {
|
||||
// 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
|
||||
|
@ -234,11 +333,17 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<(), Box<dyn std::er
|
|||
// 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! {
|
||||
const NATIVE_TABLE: &[Option<crate::avm2::method::NativeMethodImpl>] = &[
|
||||
pub const NATIVE_METHOD_TABLE: &[Option<crate::avm2::method::NativeMethodImpl>] = &[
|
||||
#(#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();
|
||||
|
||||
|
@ -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"))?;
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! ActionScript Virtual Machine 2 (AS3) support
|
||||
|
||||
use crate::avm2::class::AllocatorFn;
|
||||
use crate::avm2::globals::SystemClasses;
|
||||
use crate::avm2::method::{Method, NativeMethodImpl};
|
||||
use crate::avm2::object::EventObject;
|
||||
|
@ -76,7 +77,10 @@ pub struct Avm2<'gc> {
|
|||
system_classes: Option<SystemClasses<'gc>>,
|
||||
|
||||
#[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.
|
||||
///
|
||||
|
@ -101,7 +105,8 @@ impl<'gc> Avm2<'gc> {
|
|||
stack: Vec::new(),
|
||||
globals,
|
||||
system_classes: None,
|
||||
native_table: Default::default(),
|
||||
native_method_table: Default::default(),
|
||||
native_instance_allocator_table: Default::default(),
|
||||
broadcast_list: Default::default(),
|
||||
|
||||
#[cfg(feature = "avm_debug")]
|
||||
|
|
|
@ -354,6 +354,16 @@ impl<'gc> Class<'gc> {
|
|||
attributes.set(ClassAttributes::FINAL, abc_instance.is_final);
|
||||
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(
|
||||
activation.context.gc_context,
|
||||
Self {
|
||||
|
@ -363,7 +373,7 @@ impl<'gc> Class<'gc> {
|
|||
attributes,
|
||||
protected_namespace,
|
||||
interfaces,
|
||||
instance_allocator: None,
|
||||
instance_allocator,
|
||||
instance_init,
|
||||
native_instance_init,
|
||||
instance_traits: Vec::new(),
|
||||
|
|
|
@ -210,9 +210,10 @@ fn class<'gc>(
|
|||
|
||||
let class_read = class_def.read();
|
||||
let super_class = if let Some(sc_name) = class_read.super_class_name() {
|
||||
let super_class: Result<Object<'gc>, Error> = global
|
||||
.get_property(sc_name, activation)
|
||||
let super_class: Result<Object<'gc>, Error> = activation
|
||||
.resolve_definition(sc_name)
|
||||
.ok()
|
||||
.and_then(|v| v)
|
||||
.and_then(|v| v.as_object())
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
|
@ -441,13 +442,6 @@ pub fn load_player_globals<'gc>(
|
|||
)?;
|
||||
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(
|
||||
activation,
|
||||
flash::events::ieventdispatcher::create_interface(mc),
|
||||
|
@ -458,62 +452,6 @@ pub fn load_player_globals<'gc>(
|
|||
flash::events::eventdispatcher::create_class(mc),
|
||||
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(
|
||||
activation,
|
||||
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`
|
||||
// triggers classloading. Therefore, we run `load_playerglobal` last,
|
||||
// so that all of our classes have been defined.
|
||||
// triggers classloading. Therefore, we run `load_playerglobal`
|
||||
// relative late, so that it can access classes defined before
|
||||
// this call.
|
||||
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(())
|
||||
}
|
||||
|
||||
|
@ -761,8 +753,9 @@ pub fn load_player_globals<'gc>(
|
|||
/// See that tool, and 'core/src/avm2/globals/README.md', for more details
|
||||
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"));
|
||||
}
|
||||
|
||||
/// Loads classes from our custom 'playerglobal' (which are written in ActionScript)
|
||||
/// 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, '_>,
|
||||
domain: Domain<'gc>,
|
||||
) -> 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)?);
|
||||
|
||||
|
@ -805,7 +799,14 @@ fn load_playerglobal<'gc>(
|
|||
// This acts the same way as 'avm2_system_class', but for classes
|
||||
// declared in 'playerglobal'. Classes are declared as ("package", "class", field_name),
|
||||
// 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(())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
||||
Java must be installed for the build process to complete.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// This is a stub - the actual class is defined in `interactiveobject.rs`
|
||||
package flash.display {
|
||||
public class InteractiveObject {
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
//! `flash.events` namespace
|
||||
|
||||
pub mod activityevent;
|
||||
pub mod contextmenuevent;
|
||||
pub mod errorevent;
|
||||
pub mod event;
|
||||
pub mod eventdispatcher;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,25 +1,19 @@
|
|||
//! `flash.events.Event` builtin/prototype
|
||||
|
||||
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::{event_allocator, EventObject, Object, TObject};
|
||||
use crate::avm2::object::{Object, TObject};
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Error;
|
||||
use crate::string::AvmString;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
||||
/// Implements `flash.events.Event`'s instance constructor.
|
||||
pub fn instance_init<'gc>(
|
||||
pub use crate::avm2::object::event_allocator;
|
||||
|
||||
pub fn 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, &[])?;
|
||||
|
||||
if let Some(mut evt) = this.as_event_mut(activation.context.gc_context) {
|
||||
let this = this.unwrap();
|
||||
let mut evt = this.as_event_mut(activation.context.gc_context).unwrap();
|
||||
evt.set_event_type(
|
||||
args.get(0)
|
||||
.cloned()
|
||||
|
@ -38,23 +32,11 @@ pub fn instance_init<'gc>(
|
|||
.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)
|
||||
}
|
||||
|
||||
/// Implements `bubbles` property's getter
|
||||
pub fn bubbles<'gc>(
|
||||
pub fn get_bubbles<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
|
@ -67,7 +49,7 @@ pub fn bubbles<'gc>(
|
|||
}
|
||||
|
||||
/// Implements `cancelable` property's getter
|
||||
pub fn cancelable<'gc>(
|
||||
pub fn get_cancelable<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
|
@ -93,7 +75,7 @@ pub fn get_type<'gc>(
|
|||
}
|
||||
|
||||
/// Implements `target` property's getter
|
||||
pub fn target<'gc>(
|
||||
pub fn get_target<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
|
@ -106,7 +88,7 @@ pub fn target<'gc>(
|
|||
}
|
||||
|
||||
/// Implements `currentTarget` property's getter
|
||||
pub fn current_target<'gc>(
|
||||
pub fn get_current_target<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
|
@ -122,7 +104,7 @@ pub fn current_target<'gc>(
|
|||
}
|
||||
|
||||
/// Implements `eventPhase` property's getter
|
||||
pub fn event_phase<'gc>(
|
||||
pub fn get_event_phase<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
|
@ -135,66 +117,6 @@ pub fn event_phase<'gc>(
|
|||
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(¶m_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`
|
||||
pub fn is_default_prevented<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
|
@ -246,134 +168,3 @@ pub fn stop_immediate_propagation<'gc>(
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@ pub fn instance_init<'gc>(
|
|||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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) {
|
||||
// This is technically duplicative of `Event`'s initializer, but
|
||||
// we have different default parameters.
|
||||
|
|
|
@ -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/display/ActionScriptVersion.as"
|
||||
include "flash/display/BitmapDataChannel.as"
|
||||
|
@ -30,6 +32,12 @@ include "flash/display/StageQuality.as"
|
|||
include "flash/display/StageScaleMode.as"
|
||||
include "flash/display/SWFVersion.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/Orientation3D.as"
|
||||
include "flash/geom/Matrix.as"
|
||||
|
|
|
@ -6,6 +6,7 @@ include "Object.as"
|
|||
// List is ordered alphabetically.
|
||||
include "Array.as"
|
||||
include "Boolean.as"
|
||||
include "flash/display/InteractiveObject.as"
|
||||
include "flash/events/EventDispatcher.as"
|
||||
include "Number.as"
|
||||
include "String.as"
|
||||
|
|
|
@ -140,7 +140,8 @@ impl<'gc> TranslationUnit<'gc> {
|
|||
// allowing us to use 'bc_method' later on without a borrow-checker error.
|
||||
let method = (|| {
|
||||
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();
|
||||
return Method::from_builtin_and_params(
|
||||
native,
|
||||
|
|
Loading…
Reference in New Issue