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::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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
//! `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;
|
||||||
|
|
|
@ -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,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(¶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`
|
/// 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue