diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 9ab11dad2c9c3..01fa09431207c 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -1008,7 +1008,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { // SWF19: "If A is zero, the result NaN, Infinity, or -Infinity is pushed to the stack in SWF 5 and later. // In SWF 4, the result is the string #ERROR#." let result: Value<'gc> = if a == 0.0 && self.swf_version() < 5 { - "#ERROR#".into() + AvmString::new_utf8(self.gc(), "#ERROR#").into() } else { (b / a).into() }; diff --git a/core/src/avm1/globals/error.rs b/core/src/avm1/globals/error.rs index 9932238d319e5..46cce3fa8622b 100644 --- a/core/src/avm1/globals/error.rs +++ b/core/src/avm1/globals/error.rs @@ -5,6 +5,7 @@ use crate::avm1::error::Error; use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::string::StringContext; +use ruffle_macros::istr; const PROTO_DECLS: &[Declaration] = declare_properties! { "message" => string("Error"); @@ -20,7 +21,7 @@ pub fn constructor<'gc>( let message: Value<'gc> = args.get(0).cloned().unwrap_or(Value::Undefined); if message != Value::Undefined { - this.set("message", message, activation)?; + this.set(istr!("message"), message, activation)?; } Ok(this.into()) @@ -41,6 +42,6 @@ fn to_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let message = this.get("message", activation)?; + let message = this.get(istr!("message"), activation)?; Ok(message.coerce_to_string(activation)?.into()) } diff --git a/core/src/avm1/globals/file_reference.rs b/core/src/avm1/globals/file_reference.rs index 58aca31d13e4a..27b6528550420 100644 --- a/core/src/avm1/globals/file_reference.rs +++ b/core/src/avm1/globals/file_reference.rs @@ -233,15 +233,17 @@ pub fn browse<'gc>( for i in 0..length { if let Value::Object(element) = array.get_element(activation, i) { - let mac_type = - if let Some(val) = element.get_local_stored("macType", activation, false) { - Some(val.coerce_to_string(activation)?.to_string()) - } else { - None - }; + let mac_type = if let Some(val) = + element.get_local_stored(istr!("macType"), activation, false) + { + Some(val.coerce_to_string(activation)?.to_string()) + } else { + None + }; - let description = element.get_local_stored("description", activation, false); - let extension = element.get_local_stored("extension", activation, false); + let description = + element.get_local_stored(istr!("description"), activation, false); + let extension = element.get_local_stored(istr!("extension"), activation, false); if let (Some(description), Some(extension)) = (description, extension) { let description = description.coerce_to_string(activation)?.to_string(); @@ -262,7 +264,8 @@ pub fn browse<'gc>( return Ok(false.into()); } } else { - return Err(Error::ThrownValue("Unexpected filter value".into())); + // Method will abort if any non-Object elements are in the list + return Ok(false.into()); } } diff --git a/core/src/avm1/globals/netconnection.rs b/core/src/avm1/globals/netconnection.rs index 883441cfa0a66..492b8ce2b3dc0 100644 --- a/core/src/avm1/globals/netconnection.rs +++ b/core/src/avm1/globals/netconnection.rs @@ -66,6 +66,7 @@ impl<'gc> NetConnection<'gc> { let event = constructor .construct(&mut activation, &[])? .coerce_to_object(&mut activation); + let code = AvmString::new_utf8(activation.gc(), code); event.set(istr!("code"), code.into(), &mut activation)?; event.set(istr!("level"), istr!("status").into(), &mut activation)?; this.call_method( @@ -189,7 +190,7 @@ fn protocol<'gc>( .handle() .and_then(|handle| activation.context.net_connections.get_protocol(handle)) { - Ok(protocol.into()) + Ok(AvmString::new_utf8(activation.gc(), protocol).into()) } else { Ok(Value::Undefined) }; diff --git a/core/src/avm1/globals/object.rs b/core/src/avm1/globals/object.rs index f566b21bab435..f634b8290e7ec 100644 --- a/core/src/avm1/globals/object.rs +++ b/core/src/avm1/globals/object.rs @@ -59,39 +59,40 @@ pub fn add_property<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let name = args - .get(0) - .and_then(|v| v.coerce_to_string(activation).ok()) - .unwrap_or_else(|| "undefined".into()); - let getter = args.get(1).unwrap_or(&Value::Undefined); - let setter = args.get(2).unwrap_or(&Value::Undefined); - - match getter { - Value::Object(get) if !name.is_empty() => { - if let Value::Object(set) = setter { - this.add_property_with_case( - activation, - name, - get.to_owned(), - Some(set.to_owned()), - Attribute::empty(), - ); - } else if let Value::Null = setter { - this.add_property_with_case( - activation, - name, - get.to_owned(), - None, - Attribute::READ_ONLY, - ); - } else { - return Ok(false.into()); - } + if let Some(name) = args.get(0) { + let name = name.coerce_to_string(activation)?; + let getter = args.get(1).unwrap_or(&Value::Undefined); + let setter = args.get(2).unwrap_or(&Value::Undefined); - Ok(true.into()) + match getter { + Value::Object(get) if !name.is_empty() => { + if let Value::Object(set) = setter { + this.add_property_with_case( + activation, + name, + get.to_owned(), + Some(set.to_owned()), + Attribute::empty(), + ); + } else if let Value::Null = setter { + this.add_property_with_case( + activation, + name, + get.to_owned(), + None, + Attribute::READ_ONLY, + ); + } else { + return Ok(false.into()); + } + + return Ok(true.into()); + } + _ => return Ok(false.into()), } - _ => Ok(false.into()), } + + Ok(false.into()) } /// Implements `Object.prototype.hasOwnProperty` @@ -110,14 +111,14 @@ pub fn has_own_property<'gc>( /// Implements `Object.prototype.toString` fn to_string<'gc>( - _activation: &mut Activation<'_, 'gc>, + activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { if this.as_executable().is_some() { - Ok("[type Function]".into()) + Ok(AvmString::new_utf8(activation.gc(), "[type Function]").into()) } else { - Ok("[object Object]".into()) + Ok(AvmString::new_utf8(activation.gc(), "[object Object]").into()) } } diff --git a/core/src/avm1/globals/point.rs b/core/src/avm1/globals/point.rs index 30097d44e9376..8dd96094f0a93 100644 --- a/core/src/avm1/globals/point.rs +++ b/core/src/avm1/globals/point.rs @@ -180,12 +180,14 @@ fn distance<'gc>( .coerce_to_object(activation); let b = args.get(1).unwrap_or(&Value::Undefined); let delta = a.call_method( - "subtract".into(), + istr!("subtract"), &[b.to_owned()], activation, ExecutionReason::FunctionCall, )?; - delta.coerce_to_object(activation).get("length", activation) + delta + .coerce_to_object(activation) + .get(istr!("length"), activation) } fn polar<'gc>( diff --git a/core/src/avm1/globals/stage.rs b/core/src/avm1/globals/stage.rs index e67900cc14c90..4f75005371f48 100644 --- a/core/src/avm1/globals/stage.rs +++ b/core/src/avm1/globals/stage.rs @@ -9,6 +9,7 @@ use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{Object, ScriptObject, Value}; use crate::display_object::StageDisplayState; use crate::string::{AvmString, StringContext, WStr, WString}; +use ruffle_macros::istr; const OBJECT_DECLS: &[Declaration] = declare_properties! { "align" => property(align, set_align); @@ -121,11 +122,13 @@ fn display_state<'gc>( _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if activation.context.stage.is_fullscreen() { - Ok("fullScreen".into()) + let state = if activation.context.stage.is_fullscreen() { + istr!("fullScreen") } else { - Ok("normal".into()) - } + istr!("normal") + }; + + Ok(state.into()) } fn set_display_state<'gc>( diff --git a/core/src/avm1/globals/system_ime.rs b/core/src/avm1/globals/system_ime.rs index 1c4e137be47a6..6c2854f98cc9a 100644 --- a/core/src/avm1/globals/system_ime.rs +++ b/core/src/avm1/globals/system_ime.rs @@ -4,7 +4,7 @@ use crate::avm1::globals::as_broadcaster::BroadcasterFunctions; use crate::avm1::object::Object; use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{ScriptObject, Value}; -use crate::string::StringContext; +use crate::string::{AvmString, StringContext}; const OBJECT_DECLS: &[Declaration] = declare_properties! { "ALPHANUMERIC_FULL" => string("ALPHANUMERIC_FULL"; DONT_ENUM | DONT_DELETE | READ_ONLY); @@ -40,11 +40,11 @@ fn do_conversion<'gc>( } fn get_conversion_mode<'gc>( - _activation: &mut Activation<'_, 'gc>, + activation: &mut Activation<'_, 'gc>, _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok("KOREAN".into()) + Ok(AvmString::new_utf8(activation.gc(), "KOREAN").into()) } fn get_enabled<'gc>( diff --git a/core/src/avm1/globals/transform.rs b/core/src/avm1/globals/transform.rs index 35b19e7a94b43..50abd73048cd9 100644 --- a/core/src/avm1/globals/transform.rs +++ b/core/src/avm1/globals/transform.rs @@ -8,8 +8,9 @@ use crate::avm1::object_reference::MovieClipReference; use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{Activation, Error, Object, ScriptObject, TObject, Value}; use crate::display_object::{DisplayObject, TDisplayObject}; -use crate::string::StringContext; +use crate::string::{AvmString, StringContext}; use gc_arena::Collect; +use ruffle_macros::istr; use swf::{Rectangle, Twips}; #[derive(Copy, Clone, Debug, Collect)] @@ -85,12 +86,21 @@ fn method<'gc>( Ok(match index { GET_MATRIX => matrix_to_value(clip.base().matrix(), activation)?, SET_MATRIX => { + let matrix_props: &[AvmString<'_>] = &[ + istr!("a"), + istr!("b"), + istr!("c"), + istr!("d"), + istr!("tx"), + istr!("ty"), + ]; + if let [value] = args { let object = value.coerce_to_object(activation); // Assignment only occurs for an object with Matrix properties (a, b, c, d, tx, ty). - let is_matrix = ["a", "b", "c", "d", "tx", "ty"] + let is_matrix = matrix_props .iter() - .all(|p| object.has_own_property(activation, (*p).into())); + .all(|p| object.has_own_property(activation, *p)); if is_matrix { let matrix = object_to_matrix(object, activation)?; clip.set_matrix(activation.gc(), matrix); diff --git a/core/src/avm1/object/stage_object.rs b/core/src/avm1/object/stage_object.rs index 7096188bf007a..91840683ecac7 100644 --- a/core/src/avm1/object/stage_object.rs +++ b/core/src/avm1/object/stage_object.rs @@ -815,7 +815,7 @@ fn set_sound_buf_time<'gc>( fn quality<'gc>(activation: &mut Activation<'_, 'gc>, _this: DisplayObject<'gc>) -> Value<'gc> { let quality = activation.context.stage.quality().into_avm_str(); - quality.into() + AvmString::new_utf8(activation.gc(), quality).into() } fn set_quality<'gc>( diff --git a/core/src/avm1/runtime.rs b/core/src/avm1/runtime.rs index a7caf17643d74..fe303a27047ec 100644 --- a/core/src/avm1/runtime.rs +++ b/core/src/avm1/runtime.rs @@ -494,7 +494,7 @@ impl<'gc> Avm1<'gc> { // Fire "onLoadInit" events and remove completed movie loaders. context .load_manager - .movie_clip_on_load(context.action_queue); + .movie_clip_on_load(context.action_queue, &context.strings); *context.frame_phase = FramePhase::Idle; } diff --git a/core/src/avm1/value.rs b/core/src/avm1/value.rs index 8f9d424ff61b7..0dd9a71731118 100644 --- a/core/src/avm1/value.rs +++ b/core/src/avm1/value.rs @@ -29,13 +29,8 @@ pub enum Value<'gc> { } // This type is used very frequently, so make sure it doesn't unexpectedly grow. -// On 32-bit x86 Android, it's 12 bytes. On most other 32-bit platforms it's 16. -#[cfg(target_pointer_width = "32")] const _: () = assert!(size_of::>() <= 16); -#[cfg(target_pointer_width = "64")] -const _: () = assert!(size_of::>() == 24); - impl<'gc> From> for Value<'gc> { fn from(string: AvmString<'gc>) -> Self { Value::String(string) @@ -48,12 +43,6 @@ impl<'gc> From> for Value<'gc> { } } -impl From<&'static str> for Value<'_> { - fn from(string: &'static str) -> Self { - Value::String(string.into()) - } -} - impl From for Value<'_> { fn from(value: bool) -> Self { Value::Bool(value) @@ -434,9 +423,9 @@ impl<'gc> Value<'gc> { Value::String(s) => s, _ => { if object.as_executable().is_some() { - "[type Function]".into() + AvmString::new_utf8(activation.gc(), "[type Function]") } else { - "[type Object]".into() + AvmString::new_utf8(activation.gc(), "[type Object]") } } } diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 5d541d9827e48..174c9328b09f2 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -708,11 +708,17 @@ impl<'gc> Avm2<'gc> { // The second script (script #1) is Toplevel.as, and includes important // builtin classes such as Namespace, QName, and XML. - tunit + let toplevel_script = tunit .load_script(1, &mut activation) .expect("Script should load"); init_builtin_system_classes(&mut activation); + activation.avm2().toplevel_global_object = Some( + toplevel_script + .globals(activation.context) + .expect("Script should load"), + ); + // The first script (script #0) is globals.as, and includes other builtin // classes that are less critical for the AVM to load. tunit diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index bb0efeaca267f..f8a9ce0538060 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -13,7 +13,7 @@ use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::QName; use crate::context::UpdateContext; -use crate::string::WString; +use crate::string::{StringContext, WStr, WString}; use bitflags::bitflags; use fnv::FnvHashMap; use gc_arena::{Collect, Gc, GcCell, Mutation}; @@ -1066,15 +1066,18 @@ impl<'gc> Class<'gc> { #[inline(never)] pub fn define_builtin_instance_methods( self, - mc: &Mutation<'gc>, + context: &mut StringContext<'gc>, namespace: Namespace<'gc>, items: &[(&'static str, NativeMethodImpl)], ) { + let mc = context.gc(); for &(name, value) in items { + let interned_name = context.intern_static(WStr::from_units(name.as_bytes())); + self.define_instance_trait( mc, Trait::from_method( - QName::new(namespace, name), + QName::new(namespace, interned_name), Method::from_builtin(value, name, mc), ), ); @@ -1085,7 +1088,7 @@ impl<'gc> Class<'gc> { #[inline(never)] pub fn define_builtin_instance_methods_with_sig( self, - mc: &Mutation<'gc>, + context: &mut StringContext<'gc>, namespace: Namespace<'gc>, items: Vec<( &'static str, @@ -1094,11 +1097,14 @@ impl<'gc> Class<'gc> { Option>>, )>, ) { + let mc = context.gc(); for (name, value, params, return_type) in items { + let interned_name = context.intern_static(WStr::from_units(name.as_bytes())); + self.define_instance_trait( mc, Trait::from_method( - QName::new(namespace, name), + QName::new(namespace, interned_name), Method::from_builtin_and_params(value, name, params, return_type, false, mc), ), ); @@ -1108,7 +1114,7 @@ impl<'gc> Class<'gc> { #[inline(never)] pub fn define_builtin_instance_properties( self, - mc: &Mutation<'gc>, + context: &mut StringContext<'gc>, namespace: Namespace<'gc>, items: &[( &'static str, @@ -1116,12 +1122,15 @@ impl<'gc> Class<'gc> { Option, )], ) { + let mc = context.gc(); for &(name, getter, setter) in items { + let interned_name = context.intern_static(WStr::from_units(name.as_bytes())); + if let Some(getter) = getter { self.define_instance_trait( mc, Trait::from_getter( - QName::new(namespace, name), + QName::new(namespace, interned_name), Method::from_builtin(getter, name, mc), ), ); @@ -1130,7 +1139,7 @@ impl<'gc> Class<'gc> { self.define_instance_trait( mc, Trait::from_setter( - QName::new(namespace, name), + QName::new(namespace, interned_name), Method::from_builtin(setter, name, mc), ), ); @@ -1145,11 +1154,16 @@ impl<'gc> Class<'gc> { items: &[(&'static str, i32)], activation: &mut Activation<'_, 'gc>, ) { + let mc = activation.gc(); for &(name, value) in items { + let interned_name = activation + .strings() + .intern_static(WStr::from_units(name.as_bytes())); + self.define_instance_trait( - activation.gc(), + mc, Trait::from_const( - QName::new(namespace, name), + QName::new(namespace, interned_name), Some(activation.avm2().multinames.int), Some(value.into()), ), diff --git a/core/src/avm2/domain.rs b/core/src/avm2/domain.rs index 5ebca6d9eb3cd..f9b7f4c44f1fd 100644 --- a/core/src/avm2/domain.rs +++ b/core/src/avm2/domain.rs @@ -290,7 +290,7 @@ impl<'gc> Domain<'gc> { activation.gc(), &name[(start + 2)..(name.len() - 1)], )); - name = "__AS3__.vec::Vector".into(); + name = AvmString::new_utf8(activation.gc(), "__AS3__.vec::Vector"); } // FIXME - is this the correct api version? let api_version = activation.avm2().root_api_version; diff --git a/core/src/avm2/error.rs b/core/src/avm2/error.rs index fa99d6023dce1..c7e45d2f73a87 100644 --- a/core/src/avm2/error.rs +++ b/core/src/avm2/error.rs @@ -38,12 +38,8 @@ impl Debug for Error<'_> { } // This type is used very frequently, so make sure it doesn't unexpectedly grow. -#[cfg(target_family = "wasm")] const _: () = assert!(size_of::, Error<'_>>>() == 24); -#[cfg(target_pointer_width = "64")] -const _: () = assert!(size_of::, Error<'_>>>() == 32); - #[inline(never)] #[cold] pub fn make_null_or_undefined_error<'gc>( diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 3d04a6482ed34..59d8f9af7a96b 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -12,6 +12,7 @@ use crate::avm2::{Avm2, Error, Multiname, Namespace, QName}; use crate::string::{AvmString, WStr}; use crate::tag_utils::{self, ControlFlow, SwfMovie, SwfSlice, SwfStream}; use gc_arena::Collect; +use ruffle_macros::istr; use std::sync::Arc; use swf::TagCode; @@ -212,6 +213,7 @@ pub struct SystemClassDefs<'gc> { pub rectangletexture: Class<'gc>, pub display_object: Class<'gc>, pub sprite: Class<'gc>, + pub urlrequestheader: Class<'gc>, pub contextmenuitem: Class<'gc>, } @@ -377,30 +379,12 @@ impl<'gc> SystemClassDefs<'gc> { rectangletexture: object, display_object: object, sprite: object, + urlrequestheader: object, contextmenuitem: object, } } } -/// Looks up a function defined in the script domain, and defines it on the global object. -/// -/// This expects the looked-up value to be a function. -fn define_fn_on_global<'gc>( - activation: &mut Activation<'_, 'gc>, - name: &'static str, - script: Script<'gc>, -) { - let (_, global, domain) = script.init(); - let qname = QName::new(activation.avm2().namespaces.public_all(), name); - let func = domain - .get_defined_value(activation, qname) - .expect("Function being defined on global should be defined in domain!"); - - Value::from(global) - .init_property(&qname.into(), func, activation) - .expect("Should set property"); -} - /// Add a fully-formed class object builtin to the global scope. /// /// This allows the caller to pre-populate the class's prototype with dynamic @@ -483,30 +467,14 @@ pub fn load_player_globals<'gc>( void_def, )); - // Unfortunately we need to specify the global traits manually, at least until - // all the builtin classes are defined in AS. + // Unfortunately we need to specify the global traits manually let mut global_traits = Vec::new(); let public_ns = activation.avm2().namespaces.public_all(); let class_trait_list = &[ - (public_ns, "Object", object_i_class), - (public_ns, "Class", class_i_class), - ]; - - // "trace" is the only builtin function not defined on the toplevel global object - let function_trait_list = &[ - "decodeURI", - "decodeURIComponent", - "encodeURI", - "encodeURIComponent", - "escape", - "unescape", - "isXMLName", - "isFinite", - "isNaN", - "parseFloat", - "parseInt", + (public_ns, istr!("Object"), object_i_class), + (public_ns, istr!("Class"), class_i_class), ]; for (namespace, name, class) in class_trait_list { @@ -515,19 +483,6 @@ pub fn load_player_globals<'gc>( global_traits.push(Trait::from_class(qname, *class)); } - for function_name in function_trait_list { - let qname = QName::new(public_ns, *function_name); - - // FIXME: These should be TraitKind::Methods, to match how they are when - // defined on the AS global object, but we don't have the actual Methods - // right now. - global_traits.push(Trait::from_const( - qname, - Some(activation.avm2().multinames.function), - Some(Value::Null), - )); - } - // Create the builtin globals' classdef let global_classdef = global_scope::create_class(activation, global_traits); @@ -593,8 +548,6 @@ pub fn load_player_globals<'gc>( globals.set_vtable(mc, global_obj_vtable); - activation.context.avm2.toplevel_global_object = Some(globals); - // Initialize the script let script = Script::empty_script(mc, globals, domain); @@ -611,11 +564,6 @@ pub fn load_player_globals<'gc>( // this call. load_playerglobal(activation, domain)?; - for function_name in function_trait_list { - // Now copy those functions to the global object. - define_fn_on_global(activation, function_name, script); - } - Ok(()) } @@ -977,6 +925,7 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) { "RectangleTexture", rectangletexture ), + ("flash.net", "URLRequestHeader", urlrequestheader), ("flash.ui", "ContextMenuItem", contextmenuitem), ] ); diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 0f953a8c281c5..8737675065224 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -233,7 +233,7 @@ impl<'gc> ArrayIter<'gc> { end_index: u32, ) -> Result> { let length = Value::from(array_object) - .get_public_property("length", activation)? + .get_public_property(istr!("length"), activation)? .coerce_to_u32(activation)?; Ok(Self { diff --git a/core/src/avm2/globals/class.rs b/core/src/avm2/globals/class.rs index e4224a9184aa6..6a3cd67fc77c3 100644 --- a/core/src/avm2/globals/class.rs +++ b/core/src/avm2/globals/class.rs @@ -8,6 +8,8 @@ use crate::avm2::object::{ClassObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::QName; +use crate::string::AvmString; +use ruffle_macros::istr; pub fn class_allocator<'gc>( _class: ClassObject<'gc>, @@ -61,8 +63,9 @@ pub fn create_i_class<'gc>( let gc_context = activation.gc(); let namespaces = activation.avm2().namespaces; + let class_name = istr!("Class"); let class_i_class = Class::custom_new( - QName::new(namespaces.public_all(), "Class"), + QName::new(namespaces.public_all(), class_name), Some(object_i_class), Method::from_builtin(instance_init, "", gc_context), gc_context, @@ -79,7 +82,7 @@ pub fn create_i_class<'gc>( Option, )] = &[("prototype", Some(prototype), None)]; class_i_class.define_builtin_instance_properties( - gc_context, + activation.strings(), namespaces.public_all(), PUBLIC_INSTANCE_PROPERTIES, ); @@ -100,8 +103,9 @@ pub fn create_c_class<'gc>( let gc_context = activation.gc(); let namespaces = activation.avm2().namespaces; + let class_name = AvmString::new_utf8(gc_context, "Class$"); let class_c_class = Class::custom_new( - QName::new(namespaces.public_all(), "Class$"), + QName::new(namespaces.public_all(), class_name), Some(class_i_class), Method::from_builtin(class_init, "", gc_context), gc_context, diff --git a/core/src/avm2/globals/flash/display/loader.rs b/core/src/avm2/globals/flash/display/loader.rs index 55a902cdb059a..ffe31e80bd352 100644 --- a/core/src/avm2/globals/flash/display/loader.rs +++ b/core/src/avm2/globals/flash/display/loader.rs @@ -7,6 +7,7 @@ use crate::avm2::error::make_error_2007; use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; use crate::avm2::globals::slots::flash_display_loader as loader_slots; use crate::avm2::globals::slots::flash_net_url_request as url_request_slots; +use crate::avm2::globals::slots::flash_net_url_request_header as url_request_header_slots; use crate::avm2::object::LoaderInfoObject; use crate::avm2::object::LoaderStream; use crate::avm2::object::TObject; @@ -144,28 +145,29 @@ pub fn request_from_url_request<'gc>( let mut string_headers = IndexMap::default(); if let Some(headers) = headers { - let headers = headers.as_array_object().unwrap(); - let headers = headers.as_array_storage().unwrap(); for i in 0..headers.length() { - let Some(header) = headers.get(i) else { + let Some(header) = headers.get(i).and_then(|h| h.as_object()) else { continue; }; - let name = header - .get_public_property("name", activation)? - .coerce_to_string(activation)? - .to_string(); - let value = header - .get_public_property("value", activation)? - .coerce_to_string(activation)? - .to_string(); - - // Note - testing with Flash Player shows that later entries in the array - // overwrite earlier ones with the same name. Flash Player never sends an HTTP - // request with duplicate headers - string_headers.insert(name, value); + // Non-URLRequestHeader objects are skipped + if header.is_of_type(activation.avm2().class_defs().urlrequestheader) { + let name = header + .get_slot(url_request_header_slots::NAME) + .coerce_to_string(activation)? + .to_string(); + let value = header + .get_slot(url_request_header_slots::VALUE) + .coerce_to_string(activation)? + .to_string(); + + // Note - testing with Flash Player shows that later entries in the array + // overwrite earlier ones with the same name. Flash Player never sends an HTTP + // request with duplicate headers + string_headers.insert(name, value); + } } } diff --git a/core/src/avm2/globals/flash/net/URLRequestHeader.as b/core/src/avm2/globals/flash/net/URLRequestHeader.as index 5708953768601..f8f90f6cf7a28 100644 --- a/core/src/avm2/globals/flash/net/URLRequestHeader.as +++ b/core/src/avm2/globals/flash/net/URLRequestHeader.as @@ -1,6 +1,9 @@ package flash.net { public final class URLRequestHeader { + [Ruffle(NativeAccessible)] public var name: String; + + [Ruffle(NativeAccessible)] public var value: String; public function URLRequestHeader(name: String = "", value: String = "") { diff --git a/core/src/avm2/globals/global_scope.rs b/core/src/avm2/globals/global_scope.rs index 677c2525f541c..4b77b9e3ce897 100644 --- a/core/src/avm2/globals/global_scope.rs +++ b/core/src/avm2/globals/global_scope.rs @@ -11,6 +11,7 @@ use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::QName; +use ruffle_macros::istr; /// Implements `global`'s instance constructor. pub fn instance_init<'gc>( @@ -28,7 +29,7 @@ pub fn create_class<'gc>( ) -> Class<'gc> { let mc = activation.gc(); let class = Class::custom_new( - QName::new(activation.avm2().namespaces.public_all(), "global"), + QName::new(activation.avm2().namespaces.public_all(), istr!("global")), Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), mc, diff --git a/core/src/avm2/globals/null.rs b/core/src/avm2/globals/null.rs index c145d902f4703..7505fc4e982e2 100644 --- a/core/src/avm2/globals/null.rs +++ b/core/src/avm2/globals/null.rs @@ -4,6 +4,7 @@ use crate::avm2::method::Method; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::QName; +use ruffle_macros::istr; fn null_init<'gc>( _activation: &mut Activation<'_, 'gc>, @@ -16,7 +17,7 @@ fn null_init<'gc>( pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.gc(); let class = Class::custom_new( - QName::new(activation.avm2().namespaces.public_all(), "null"), + QName::new(activation.avm2().namespaces.public_all(), istr!("null")), None, Method::from_builtin(null_init, "", mc), mc, diff --git a/core/src/avm2/globals/number.rs b/core/src/avm2/globals/number.rs index f889179bb117b..3502b2e685738 100644 --- a/core/src/avm2/globals/number.rs +++ b/core/src/avm2/globals/number.rs @@ -5,6 +5,7 @@ use crate::avm2::error::{make_error_1002, make_error_1003}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::{AvmString, Error}; +use ruffle_macros::istr; pub fn number_constructor<'gc>( activation: &mut Activation<'_, 'gc>, @@ -130,14 +131,14 @@ pub fn print_with_radix<'gc>( } if number.is_nan() { - return Ok("NaN".into()); + return Ok(istr!("NaN")); } if number.is_infinite() { if number < 0.0 { - return Ok("-Infinity".into()); + return Ok(AvmString::new_utf8(activation.gc(), "-Infinity")); } else if number > 0.0 { - return Ok("Infinity".into()); + return Ok(istr!("Infinity")); } } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 14af414d6a0a0..9f935fce04c5f 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -9,6 +9,7 @@ use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::{Error, Multiname, QName}; use crate::string::AvmString; +use ruffle_macros::istr; /// Implements `Object`'s instance initializer. fn instance_init<'gc>( @@ -64,7 +65,7 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul let object_proto = object_class.prototype(); object_proto.set_string_property_local( - "hasOwnProperty", + istr!("hasOwnProperty"), FunctionObject::from_method( activation, Method::from_builtin(has_own_property, "hasOwnProperty", gc_context), @@ -77,7 +78,7 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul activation, )?; object_proto.set_string_property_local( - "propertyIsEnumerable", + istr!("propertyIsEnumerable"), FunctionObject::from_method( activation, Method::from_builtin(property_is_enumerable, "propertyIsEnumerable", gc_context), @@ -90,7 +91,7 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul activation, )?; object_proto.set_string_property_local( - "setPropertyIsEnumerable", + istr!("setPropertyIsEnumerable"), FunctionObject::from_method( activation, Method::from_builtin( @@ -107,7 +108,7 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul activation, )?; object_proto.set_string_property_local( - "isPrototypeOf", + istr!("isPrototypeOf"), FunctionObject::from_method( activation, Method::from_builtin(is_prototype_of, "isPrototypeOf", gc_context), @@ -120,7 +121,7 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul activation, )?; object_proto.set_string_property_local( - "toString", + istr!("toString"), FunctionObject::from_method( activation, Method::from_builtin(to_string, "toString", gc_context), @@ -133,7 +134,7 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul activation, )?; object_proto.set_string_property_local( - "toLocaleString", + istr!("toLocaleString"), FunctionObject::from_method( activation, Method::from_builtin(to_string, "toLocaleString", gc_context), @@ -146,7 +147,7 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul activation, )?; object_proto.set_string_property_local( - "valueOf", + istr!("valueOf"), FunctionObject::from_method( activation, Method::from_builtin(value_of, "valueOf", gc_context), @@ -159,17 +160,17 @@ pub fn init_object_prototype<'gc>(activation: &mut Activation<'_, 'gc>) -> Resul activation, )?; - object_proto.set_local_property_is_enumerable(gc_context, "hasOwnProperty".into(), false); - object_proto.set_local_property_is_enumerable(gc_context, "propertyIsEnumerable".into(), false); + object_proto.set_local_property_is_enumerable(gc_context, istr!("hasOwnProperty"), false); + object_proto.set_local_property_is_enumerable(gc_context, istr!("propertyIsEnumerable"), false); object_proto.set_local_property_is_enumerable( gc_context, - "setPropertyIsEnumerable".into(), + istr!("setPropertyIsEnumerable"), false, ); - object_proto.set_local_property_is_enumerable(gc_context, "isPrototypeOf".into(), false); - object_proto.set_local_property_is_enumerable(gc_context, "toString".into(), false); - object_proto.set_local_property_is_enumerable(gc_context, "toLocaleString".into(), false); - object_proto.set_local_property_is_enumerable(gc_context, "valueOf".into(), false); + object_proto.set_local_property_is_enumerable(gc_context, istr!("isPrototypeOf"), false); + object_proto.set_local_property_is_enumerable(gc_context, istr!("toString"), false); + object_proto.set_local_property_is_enumerable(gc_context, istr!("toLocaleString"), false); + object_proto.set_local_property_is_enumerable(gc_context, istr!("valueOf"), false); Ok(()) } @@ -295,8 +296,9 @@ pub fn create_i_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let gc_context = activation.gc(); let namespaces = activation.avm2().namespaces; + let class_name = istr!("Object"); let object_i_class = Class::custom_new( - QName::new(namespaces.public_all(), "Object"), + QName::new(namespaces.public_all(), class_name), None, Method::from_builtin(instance_init, "", gc_context), gc_context, @@ -332,7 +334,7 @@ pub fn create_i_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { ), ]; object_i_class.define_builtin_instance_methods_with_sig( - gc_context, + activation.strings(), namespaces.as3, as3_instance_methods, ); @@ -353,8 +355,9 @@ pub fn create_c_class<'gc>( let gc_context = activation.gc(); let namespaces = activation.avm2().namespaces; + let class_name = AvmString::new_utf8(gc_context, "Object$"); let object_c_class = Class::custom_new( - QName::new(namespaces.public_all(), "Object$"), + QName::new(namespaces.public_all(), class_name), Some(class_i_class), Method::from_builtin(class_init, "", gc_context), gc_context, @@ -364,7 +367,7 @@ pub fn create_c_class<'gc>( object_c_class.define_instance_trait( gc_context, Trait::from_const( - QName::new(activation.avm2().namespaces.public_all(), "length"), + QName::new(activation.avm2().namespaces.public_all(), istr!("length")), Some(activation.avm2().multinames.int), Some(1.into()), ), @@ -372,7 +375,7 @@ pub fn create_c_class<'gc>( const INTERNAL_INIT_METHOD: &[(&str, NativeMethodImpl)] = &[("init", init)]; object_c_class.define_builtin_instance_methods( - gc_context, + activation.strings(), namespaces.internal, INTERNAL_INIT_METHOD, ); diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 41bd814cdf3a1..d6b5b7d23d904 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -77,7 +77,7 @@ pub fn call_handler<'gc>( } let length = arg - .get_public_property("length", activation)? + .get_public_property(istr!("length"), activation)? .coerce_to_i32(activation)?; let arg = arg.as_object().ok_or("Cannot convert to Vector")?; diff --git a/core/src/avm2/globals/void.rs b/core/src/avm2/globals/void.rs index 5c6a496a43d3f..03ca6edd087bf 100644 --- a/core/src/avm2/globals/void.rs +++ b/core/src/avm2/globals/void.rs @@ -4,6 +4,7 @@ use crate::avm2::method::Method; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::QName; +use ruffle_macros::istr; fn void_init<'gc>( _activation: &mut Activation<'_, 'gc>, @@ -16,7 +17,7 @@ fn void_init<'gc>( pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.gc(); let class = Class::custom_new( - QName::new(activation.avm2().namespaces.public_all(), "void"), + QName::new(activation.avm2().namespaces.public_all(), istr!("void")), None, Method::from_builtin(void_init, "", mc), mc, diff --git a/core/src/avm2/namespace.rs b/core/src/avm2/namespace.rs index 4ce2195ae66bc..0a30ba4fcccc8 100644 --- a/core/src/avm2/namespace.rs +++ b/core/src/avm2/namespace.rs @@ -340,8 +340,6 @@ pub struct CommonNamespaces<'gc> { pub(super) internal: Namespace<'gc>, pub(super) as3: Namespace<'gc>, pub(super) vector_internal: Namespace<'gc>, - - pub(super) __ruffle__: Namespace<'gc>, } impl<'gc> CommonNamespaces<'gc> { @@ -350,19 +348,17 @@ impl<'gc> CommonNamespaces<'gc> { pub fn new(context: &mut StringContext<'gc>) -> Self { let empty_string = context.empty(); + let as3_namespace_string = + AvmString::new_utf8(context.gc(), "http://adobe.com/AS3/2006/builtin"); + let vector_namespace_string = AvmString::new_utf8(context.gc(), "__AS3__.vec"); + Self { public_namespaces: std::array::from_fn(|val| { Namespace::package(empty_string, ApiVersion::from_usize(val).unwrap(), context) }), internal: Namespace::internal(empty_string, context), - as3: Namespace::package( - "http://adobe.com/AS3/2006/builtin", - ApiVersion::AllVersions, - context, - ), - vector_internal: Namespace::internal("__AS3__.vec", context), - - __ruffle__: Namespace::package("__ruffle__", ApiVersion::AllVersions, context), + as3: Namespace::package(as3_namespace_string, ApiVersion::AllVersions, context), + vector_internal: Namespace::internal(vector_namespace_string, context), } } diff --git a/core/src/avm2/object/event_object.rs b/core/src/avm2/object/event_object.rs index fb96311ffca78..67622e283bbd8 100644 --- a/core/src/avm2/object/event_object.rs +++ b/core/src/avm2/object/event_object.rs @@ -318,10 +318,11 @@ impl<'gc> EventObject<'gc> { pub fn io_error_event( activation: &mut Activation<'_, 'gc>, - error_msg: AvmString<'gc>, + error_msg: &str, error_code: u32, ) -> EventObject<'gc> { let event_name = istr!("ioError"); + let error_msg = AvmString::new_utf8(activation.gc(), error_msg); let io_error_event_cls = activation.avm2().classes().ioerrorevent; Self::from_class_and_args( activation, diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index a6832a4a9613c..b1f36d9df7aa3 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -51,11 +51,7 @@ pub enum Value<'gc> { } // This type is used very frequently, so make sure it doesn't unexpectedly grow. -#[cfg(target_family = "wasm")] -const _: () = assert!(size_of::>() == 16); - -#[cfg(target_pointer_width = "64")] -const _: () = assert!(size_of::>() == 24); +const _: () = assert!(size_of::>() <= 16); impl<'gc> From> for Value<'gc> { fn from(string: AvmString<'gc>) -> Self { diff --git a/core/src/avm2/verify.rs b/core/src/avm2/verify.rs index 2dfa0b19f6a38..ae81bfbc60778 100644 --- a/core/src/avm2/verify.rs +++ b/core/src/avm2/verify.rs @@ -8,7 +8,7 @@ use crate::avm2::multiname::Multiname; use crate::avm2::op::Op; use crate::avm2::script::TranslationUnit; use crate::avm2::{Activation, Error, QName}; -use crate::string::AvmAtom; +use crate::string::{AvmAtom, AvmString}; use gc_arena::{Collect, Gc}; use std::collections::{HashMap, HashSet}; @@ -420,7 +420,7 @@ pub fn verify_method<'gc>( return Err(make_error_1014( activation, Error1014Type::VerifyError, - "[]".into(), + AvmString::new_utf8(activation.gc(), "[]"), )); } @@ -486,7 +486,7 @@ pub fn verify_method<'gc>( return Err(make_error_1014( activation, Error1014Type::VerifyError, - "[]".into(), + AvmString::new_utf8(activation.gc(), "[]"), )); } @@ -742,7 +742,7 @@ pub fn resolve_param_config<'gc>( return Err(make_error_1014( activation, Error1014Type::VerifyError, - "[]".into(), + AvmString::new_utf8(activation.gc(), "[]"), )); } @@ -780,7 +780,7 @@ fn resolve_return_type<'gc>( return Err(make_error_1014( activation, Error1014Type::VerifyError, - "[]".into(), + AvmString::new_utf8(activation.gc(), "[]"), )); } diff --git a/core/src/context.rs b/core/src/context.rs index 94f554e3a8bd5..794b04a40b33b 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -422,7 +422,7 @@ impl<'gc> UpdateContext<'gc> { .get_version_string(activation.context.avm1); object.define_value( activation.gc(), - "$version", + AvmString::new_utf8(activation.gc(), "$version"), AvmString::new_utf8(activation.gc(), version_string).into(), Attribute::empty(), ); diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs index b050790f066a3..e284e6f69c142 100644 --- a/core/src/display_object/avm1_button.rs +++ b/core/src/display_object/avm1_button.rs @@ -12,12 +12,14 @@ use crate::display_object::interactive::{ use crate::display_object::{DisplayObjectBase, DisplayObjectPtr}; use crate::events::{ClipEvent, ClipEventResult}; use crate::prelude::*; +use crate::string::AvmString; use crate::tag_utils::{SwfMovie, SwfSlice}; use crate::vminterface::Instantiator; use core::fmt; use gc_arena::barrier::unlock; use gc_arena::lock::{Lock, RefLock}; use gc_arena::{Collect, Gc, Mutation}; +use ruffle_macros::istr; use ruffle_render::filters::Filter; use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::BTreeMap; @@ -207,9 +209,9 @@ impl<'gc> Avm1Button<'gc> { fn get_boolean_property( self, - context: &mut UpdateContext<'gc>, - name: &'static str, + name: AvmString<'gc>, default: bool, + context: &mut UpdateContext<'gc>, ) -> bool { if let Value::Object(object) = self.object() { let mut activation = Activation::from_nothing( @@ -231,11 +233,11 @@ impl<'gc> Avm1Button<'gc> { } fn enabled(self, context: &mut UpdateContext<'gc>) -> bool { - self.get_boolean_property(context, "enabled", true) + self.get_boolean_property(istr!(context, "enabled"), true, context) } fn use_hand_cursor(self, context: &mut UpdateContext<'gc>) -> bool { - self.get_boolean_property(context, "useHandCursor", true) + self.get_boolean_property(istr!(context, "useHandCursor"), true, context) } } @@ -523,12 +525,12 @@ impl<'gc> TInteractiveObject<'gc> for Avm1Button<'gc> { // Queue ActionScript-defined event handlers after the SWF defined ones. // (e.g., clip.onRelease = foo). if self.should_fire_event_handlers(context, event) { - if let Some(name) = event.method_name() { + if let Some(name) = event.method_name(&context.strings) { context.action_queue.queue_action( self_display_object, ActionType::Method { object: self.0.object.get().unwrap(), - name: name.into(), + name, args: vec![], }, false, diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index be7b733043dd0..ae69f26ecbe88 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -2330,7 +2330,10 @@ impl<'gc> MovieClip<'gc> { ClipEvent::BUTTON_EVENT_METHODS .iter() .copied() - .any(|handler| object.has_property(&mut activation, handler.into())) + .any(|handler| { + let handler = AvmString::new_utf8(activation.gc(), handler); + object.has_property(&mut activation, handler) + }) } else { false } @@ -2989,12 +2992,12 @@ impl<'gc> TInteractiveObject<'gc> for MovieClip<'gc> { // Queue ActionScript-defined event handlers after the SWF defined ones. // (e.g., clip.onEnterFrame = foo). if self.should_fire_event_handlers(context, event) { - if let Some(name) = event.method_name() { + if let Some(name) = event.method_name(&context.strings) { context.action_queue.queue_action( self.into(), ActionType::Method { object, - name: name.into(), + name, args: vec![], }, event == ClipEvent::Unload, diff --git a/core/src/events.rs b/core/src/events.rs index 4eac437521329..d25ea44525763 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -1,4 +1,7 @@ -use crate::{display_object::InteractiveObject, input::InputEvent}; +use crate::display_object::InteractiveObject; +use crate::input::InputEvent; +use crate::string::{AvmString, StringContext}; +use ruffle_macros::istr; use std::str::FromStr; use swf::ClipEventFlag; @@ -391,27 +394,27 @@ impl ClipEvent<'_> { /// `ClipEvent::Data` returns `None` rather than `onData` because its behavior /// differs from the other events: the method must fire before the SWF-defined /// event handler, so we'll explicitly call `onData` in the appropriate places. - pub const fn method_name(self) -> Option<&'static str> { + pub fn method_name<'gc>(self, ctx: &StringContext<'gc>) -> Option> { match self { ClipEvent::Construct => None, ClipEvent::Data => None, - ClipEvent::DragOut { .. } => Some("onDragOut"), - ClipEvent::DragOver { .. } => Some("onDragOver"), - ClipEvent::EnterFrame => Some("onEnterFrame"), + ClipEvent::DragOut { .. } => Some(istr!(ctx, "onDragOut")), + ClipEvent::DragOver { .. } => Some(istr!(ctx, "onDragOver")), + ClipEvent::EnterFrame => Some(istr!(ctx, "onEnterFrame")), ClipEvent::Initialize => None, - ClipEvent::KeyDown => Some("onKeyDown"), + ClipEvent::KeyDown => Some(istr!(ctx, "onKeyDown")), ClipEvent::KeyPress { .. } => None, - ClipEvent::KeyUp => Some("onKeyUp"), - ClipEvent::Load => Some("onLoad"), - ClipEvent::MouseDown => Some("onMouseDown"), - ClipEvent::MouseMove => Some("onMouseMove"), - ClipEvent::MouseUp => Some("onMouseUp"), - ClipEvent::Press { .. } => Some("onPress"), - ClipEvent::RollOut { .. } => Some("onRollOut"), - ClipEvent::RollOver { .. } => Some("onRollOver"), - ClipEvent::Release { .. } => Some("onRelease"), - ClipEvent::ReleaseOutside => Some("onReleaseOutside"), - ClipEvent::Unload => Some("onUnload"), + ClipEvent::KeyUp => Some(istr!(ctx, "onKeyUp")), + ClipEvent::Load => Some(istr!(ctx, "onLoad")), + ClipEvent::MouseDown => Some(istr!(ctx, "onMouseDown")), + ClipEvent::MouseMove => Some(istr!(ctx, "onMouseMove")), + ClipEvent::MouseUp => Some(istr!(ctx, "onMouseUp")), + ClipEvent::Press { .. } => Some(istr!(ctx, "onPress")), + ClipEvent::RollOut { .. } => Some(istr!(ctx, "onRollOut")), + ClipEvent::RollOver { .. } => Some(istr!(ctx, "onRollOver")), + ClipEvent::Release { .. } => Some(istr!(ctx, "onRelease")), + ClipEvent::ReleaseOutside => Some(istr!(ctx, "onReleaseOutside")), + ClipEvent::Unload => Some(istr!(ctx, "onUnload")), _ => None, } } diff --git a/core/src/loader.rs b/core/src/loader.rs index c1ccef417526a..2c5bd8fad74fa 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -28,7 +28,7 @@ use crate::frame_lifecycle::catchup_display_object_to_frame; use crate::limits::ExecutionLimit; use crate::player::{Player, PostFrameCallback}; use crate::streams::NetStream; -use crate::string::AvmString; +use crate::string::{AvmString, StringContext}; use crate::tag_utils::SwfMovie; use crate::vminterface::Instantiator; use chardetng::EncodingDetector; @@ -429,7 +429,11 @@ impl<'gc> LoadManager<'gc> { /// initialized (ran its first frame). /// /// This also removes all movie loaders that have completed. - pub fn movie_clip_on_load(&mut self, queue: &mut ActionQueue<'gc>) { + pub fn movie_clip_on_load( + &mut self, + queue: &mut ActionQueue<'gc>, + strings: &StringContext<'gc>, + ) { // FIXME: This relies on the iteration order of the slotmap, which // is not defined. The container should be replaced with something // that preserves insertion order, such as `LinkedHashMap` - @@ -443,7 +447,7 @@ impl<'gc> LoadManager<'gc> { self.0 .get_mut(*handle) .expect("valid key") - .movie_clip_loaded(queue) + .movie_clip_loaded(queue, strings) }); // Cleaning up the loaders that are done. @@ -1228,7 +1232,7 @@ impl<'gc> Loader<'gc> { Loader::movie_loader_error( handle, uc, - "Movie loader error".into(), + "Movie loader error", status_code, redirected, response.url, @@ -1672,7 +1676,7 @@ impl<'gc> Loader<'gc> { // FIXME - Match the exact error message generated by Flash let io_error_evt = Avm2EventObject::io_error_event( &mut activation, - "Error #2032: Stream Error".into(), + "Error #2032: Stream Error", 2032, ); @@ -1826,7 +1830,7 @@ impl<'gc> Loader<'gc> { // FIXME: Match the exact error message generated by Flash. let io_error_evt = Avm2EventObject::io_error_event( &mut activation, - "Error #2032: Stream Error".into(), + "Error #2032: Stream Error", 2032, ); @@ -2269,14 +2273,7 @@ impl<'gc> Loader<'gc> { error += &format!(" URL: {url}"); } - Loader::movie_loader_error( - handle, - uc, - AvmString::new_utf8(uc.gc(), error), - status, - redirected, - url, - )?; + Loader::movie_loader_error(handle, uc, &error, status, redirected, url)?; } } } @@ -2506,7 +2503,7 @@ impl<'gc> Loader<'gc> { fn movie_loader_error( handle: LoaderHandle, uc: &mut UpdateContext<'gc>, - msg: AvmString<'gc>, + msg: &str, status: u16, redirected: bool, swf_url: String, @@ -2643,7 +2640,11 @@ impl<'gc> Loader<'gc> { /// Returns `true` if the loader has completed and should be removed. /// /// Used to fire listener events on clips and terminate completed loaders. - fn movie_clip_loaded(&mut self, queue: &mut ActionQueue<'gc>) -> bool { + fn movie_clip_loaded( + &mut self, + queue: &mut ActionQueue<'gc>, + strings: &StringContext<'gc>, + ) -> bool { let (clip, vm_data, loader_status) = match self { Loader::Movie { target_clip, @@ -2668,8 +2669,8 @@ impl<'gc> Loader<'gc> { clip, ActionType::Method { object: broadcaster, - name: "broadcastMessage".into(), - args: vec!["onLoadInit".into(), clip.object()], + name: istr!(strings, "broadcastMessage"), + args: vec![istr!(strings, "onLoadInit").into(), clip.object()], }, false, ); diff --git a/core/src/socket.rs b/core/src/socket.rs index 027eec8fe6055..1517eb69f117c 100644 --- a/core/src/socket.rs +++ b/core/src/socket.rs @@ -261,7 +261,7 @@ impl<'gc> Sockets<'gc> { let io_error_evt = EventObject::io_error_event( &mut activation, - "Error #2031: Socket Error.".into(), + "Error #2031: Socket Error.", 2031, ); diff --git a/core/src/string/avm_string.rs b/core/src/string/avm_string.rs index 1fed2f4e9a41e..9d4a8546dc443 100644 --- a/core/src/string/avm_string.rs +++ b/core/src/string/avm_string.rs @@ -8,40 +8,16 @@ use crate::string::{AvmAtom, AvmStringRepr}; #[derive(Clone, Copy, Collect)] #[collect(no_drop)] -enum Source<'gc> { - Managed(Gc<'gc, AvmStringRepr<'gc>>), - Static(&'static WStr), -} - -#[derive(Clone, Copy, Collect)] -#[collect(no_drop)] -pub struct AvmString<'gc> { - source: Source<'gc>, -} +pub struct AvmString<'gc>(Gc<'gc, AvmStringRepr<'gc>>); impl<'gc> AvmString<'gc> { /// Turns a string to a fully owned (non-dependent) managed string. pub(super) fn to_fully_owned(self, mc: &Mutation<'gc>) -> Gc<'gc, AvmStringRepr<'gc>> { - match self.source { - Source::Managed(s) => { - if s.is_dependent() { - let repr = AvmStringRepr::from_raw(WString::from(self.as_wstr()), false); - Gc::new(mc, repr) - } else { - s - } - } - Source::Static(s) => { - let repr = AvmStringRepr::from_raw_static(s, false); - Gc::new(mc, repr) - } - } - } - - pub fn as_managed(self) -> Option>> { - match self.source { - Source::Managed(s) => Some(s), - Source::Static(_) => None, + if self.0.is_dependent() { + let repr = AvmStringRepr::from_raw(WString::from(self.as_wstr()), false); + Gc::new(mc, repr) + } else { + self.0 } } @@ -51,9 +27,7 @@ impl<'gc> AvmString<'gc> { Cow::Borrowed(utf8) => WString::from_utf8(utf8), }; let repr = AvmStringRepr::from_raw(buf, false); - Self { - source: Source::Managed(Gc::new(gc_context, repr)), - } + Self(Gc::new(gc_context, repr)) } pub fn new_utf8_bytes(gc_context: &Mutation<'gc>, bytes: &[u8]) -> Self { @@ -63,43 +37,27 @@ impl<'gc> AvmString<'gc> { pub fn new>(gc_context: &Mutation<'gc>, string: S) -> Self { let repr = AvmStringRepr::from_raw(string.into(), false); - Self { - source: Source::Managed(Gc::new(gc_context, repr)), - } + Self(Gc::new(gc_context, repr)) } pub fn substring(mc: &Mutation<'gc>, string: AvmString<'gc>, start: usize, end: usize) -> Self { - match string.source { - Source::Managed(repr) => { - let repr = AvmStringRepr::new_dependent(repr, start, end); - Self { - source: Source::Managed(Gc::new(mc, repr)), - } - } - Source::Static(s) => Self { - source: Source::Static(&s[start..end]), - }, - } + let repr = AvmStringRepr::new_dependent(string.0, start, end); + Self(Gc::new(mc, repr)) } pub fn is_dependent(&self) -> bool { - match &self.source { - Source::Managed(s) => s.is_dependent(), - Source::Static(_) => false, - } + self.0.is_dependent() } pub fn as_wstr(&self) -> &'gc WStr { - match self.source { - Source::Managed(s) => Gc::as_ref(s).as_wstr(), - Source::Static(s) => s, - } + Gc::as_ref(self.0).as_wstr() } pub fn as_interned(&self) -> Option> { - match self.source { - Source::Managed(s) if s.is_interned() => Some(AvmAtom(s)), - _ => None, + if self.0.is_interned() { + Some(AvmAtom(self.0)) + } else { + None } } @@ -112,13 +70,8 @@ impl<'gc> AvmString<'gc> { right } else if right.is_empty() { left - } else if let Some(repr) = left - .as_managed() - .and_then(|l| AvmStringRepr::try_append_inline(l, &right)) - { - Self { - source: Source::Managed(Gc::new(mc, repr)), - } + } else if let Some(repr) = AvmStringRepr::try_append_inline(left.0, &right) { + Self(Gc::new(mc, repr)) } else { // When doing a non-in-place append, // Overallocate a bit so that further appends can be in-place. @@ -150,28 +103,14 @@ impl<'gc> AvmString<'gc> { impl<'gc> From> for AvmString<'gc> { #[inline] fn from(atom: AvmAtom<'gc>) -> Self { - Self { - source: Source::Managed(atom.0), - } + Self(atom.0) } } impl<'gc> From>> for AvmString<'gc> { #[inline] fn from(repr: Gc<'gc, AvmStringRepr<'gc>>) -> Self { - Self { - source: Source::Managed(repr), - } - } -} - -impl From<&'static str> for AvmString<'_> { - #[inline] - fn from(str: &'static str) -> Self { - // TODO(moulins): actually check that `str` is valid ASCII. - Self { - source: Source::Static(WStr::from_units(str.as_bytes())), - } + Self(repr) } } @@ -186,18 +125,16 @@ impl Deref for AvmString<'_> { // Manual equality implementation with fast paths for owned strings. impl PartialEq for AvmString<'_> { fn eq(&self, other: &Self) -> bool { - if let (Source::Managed(left), Source::Managed(right)) = (self.source, other.source) { + if Gc::ptr_eq(self.0, other.0) { // Fast accept for identical strings. - if Gc::ptr_eq(left, right) { - return true; + true + } else if self.0.is_interned() && other.0.is_interned() { // Fast reject for distinct interned strings. - } else if left.is_interned() && right.is_interned() { - return false; - } + false + } else { + // Fallback case. + self.as_wstr() == other.as_wstr() } - - // Fallback case. - self.as_wstr() == other.as_wstr() } } diff --git a/core/src/string/common.rs b/core/src/string/common.rs index 127e646c93609..dbb717e697348 100644 --- a/core/src/string/common.rs +++ b/core/src/string/common.rs @@ -91,6 +91,7 @@ define_common_strings! { str_caption: b"caption", str_center: b"center", str_clamp: b"clamp", + str_Class: b"Class", str_click: b"click", str_code: b"code", str_color: b"color", @@ -101,6 +102,7 @@ define_common_strings! { str_declaredBy: b"declaredBy", str_decode: b"decode", str_descent: b"descent", + str_description: b"description", str_device: b"device", str_doubleClick: b"doubleClick", str_dynamic: b"dynamic", @@ -108,6 +110,7 @@ define_common_strings! { str_embeddedCFF: b"embeddedCFF", str_enabled: b"enabled", str_error: b"error", + str_extension: b"extension", str_false: b"false", str_flushed: b"flushed", str_focusEnabled: b"focusEnabled", @@ -119,8 +122,10 @@ define_common_strings! { str_function: b"function", str_ga: b"ga", str_gb: b"gb", + str_global: b"global", str_greenMultiplier: b"greenMultiplier", str_greenOffset: b"greenOffset", + str_hasOwnProperty: b"hasOwnProperty", str_height: b"height", str_httpStatus: b"httpStatus", str_ignore: b"ignore", @@ -134,6 +139,7 @@ define_common_strings! { str_ioError: b"ioError", str_isDynamic: b"isDynamic", str_isFinal: b"isFinal", + str_isPrototypeOf: b"isPrototypeOf", str_isStatic: b"isStatic", str_italic: b"italic", str_justify: b"justify", @@ -151,10 +157,12 @@ define_common_strings! { str_localName: b"localName", str_loop: b"loop", str_lr: b"lr", + str_macType: b"macType", str_matrixType: b"matrixType", str_menu: b"menu", str_menuItemSelect: b"menuItemSelect", str_menuSelect: b"menuSelect", + str_message: b"message", str_metadata: b"metadata", str_methods: b"methods", str_middleClick: b"middleClick", @@ -176,6 +184,7 @@ define_common_strings! { str_normal: b"normal", str_null: b"null", str_number: b"number", + str_Object: b"Object", str_object: b"object", str_onCancel: b"onCancel", str_onChanged: b"onChanged", @@ -183,6 +192,9 @@ define_common_strings! { str_onComplete: b"onComplete", str_onConnect: b"onConnect", str_onData: b"onData", + str_onDragOut: b"onDragOut", + str_onDragOver: b"onDragOver", + str_onEnterFrame: b"onEnterFrame", str_onFullScreen: b"onFullScreen", str_onIOError: b"onIOError", str_onHTTPError: b"onHTTPError", @@ -192,6 +204,7 @@ define_common_strings! { str_onLoad: b"onLoad", str_onLoadComplete: b"onLoadComplete", str_onLoadError: b"onLoadError", + str_onLoadInit: b"onLoadInit", str_onLoadProgress: b"onLoadProgress", str_onLoadStart: b"onLoadStart", str_onMouseDown: b"onMouseDown", @@ -199,9 +212,14 @@ define_common_strings! { str_onMouseUp: b"onMouseUp", str_onMouseWheel: b"onMouseWheel", str_onOpen: b"onOpen", + str_onPress: b"onPress", str_onProgress: b"onProgress", + str_onRelease: b"onRelease", + str_onReleaseOutside: b"onReleaseOutside", str_onResize: b"onResize", str_onResult: b"onResult", + str_onRollOut: b"onRollOut", + str_onRollOver: b"onRollOver", str_onScroller: b"onScroller", str_onSelect: b"onSelect", str_onSetFocus: b"onSetFocus", @@ -217,6 +235,7 @@ define_common_strings! { str_play: b"play", str_prefix: b"prefix", str_print: b"print", + str_propertyIsEnumerable: b"propertyIsEnumerable", str_prototype: b"prototype", str_push: b"push", str_quality: b"quality", @@ -242,6 +261,7 @@ define_common_strings! { str_save: b"save", str_Selection: b"Selection", str_separatorBefore: b"separatorBefore", + str_setPropertyIsEnumerable: b"setPropertyIsEnumerable", str_splice: b"splice", str_standard: b"standard", str_standardConstrained: b"standardConstrained", @@ -250,6 +270,7 @@ define_common_strings! { str_status: b"status", str_string: b"string", str_subpixel: b"subpixel", + str_subtract: b"subtract", str_success: b"success", str_super: b"super", str_tabChildren: b"tabChildren", @@ -258,6 +279,7 @@ define_common_strings! { str_textFieldHeight: b"textFieldHeight", str_textFieldWidth: b"textFieldWidth", str_toJSON: b"toJSON", + str_toLocaleString: b"toLocaleString", str_toString: b"toString", str_toXMLString: b"toXMLString", str_traits: b"traits", @@ -273,6 +295,7 @@ define_common_strings! { str_valueOf: b"valueOf", str_variables: b"variables", str_visible: b"visible", + str_void: b"void", str_width: b"width", str_wrap: b"wrap", str_writeonly: b"writeonly",