diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 446f334965b1..ad35cdea5df3 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -3,9 +3,11 @@ use std::rc::Rc; use crate::avm2::class::{AllocatorFn, CustomConstructorFn}; +use crate::avm2::e4x::XmlSettings; use crate::avm2::error::{make_error_1014, make_error_1107, type_error, Error1014Type}; use crate::avm2::globals::{ - init_builtin_system_classes, init_native_system_classes, SystemClassDefs, SystemClasses, + init_builtin_system_class_defs, init_builtin_system_classes, init_native_system_classes, + SystemClassDefs, SystemClasses, }; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::scope::ScopeChain; @@ -175,6 +177,9 @@ pub struct Avm2<'gc> { alias_to_class_map: FnvHashMap, ClassObject<'gc>>, class_to_alias_map: FnvHashMap, AvmString<'gc>>, + #[collect(require_static)] + pub xml_settings: XmlSettings, + /// The api version of our root movie clip. Note - this is used as the /// api version for swfs loaded via `Loader`, overriding the api version /// specified in the loaded SWF. This is only used for API versioning (hiding @@ -230,6 +235,8 @@ impl<'gc> Avm2<'gc> { alias_to_class_map: Default::default(), class_to_alias_map: Default::default(), + xml_settings: XmlSettings::new_default(), + // Set the lowest version for now - this will be overridden when we set our movie root_api_version: ApiVersion::AllVersions, @@ -410,7 +417,7 @@ impl<'gc> Avm2<'gc> { /// Returns `true` if the event has been handled. pub fn dispatch_event( context: &mut UpdateContext<'gc>, - event: Object<'gc>, + event: EventObject<'gc>, target: Object<'gc>, ) -> bool { Self::dispatch_event_internal(context, event, target, false) @@ -424,7 +431,7 @@ impl<'gc> Avm2<'gc> { /// Returns `true` when the event would have been handled if not simulated. pub fn simulate_event_dispatch( context: &mut UpdateContext<'gc>, - event: Object<'gc>, + event: EventObject<'gc>, target: Object<'gc>, ) -> bool { Self::dispatch_event_internal(context, event, target, true) @@ -432,18 +439,15 @@ impl<'gc> Avm2<'gc> { fn dispatch_event_internal( context: &mut UpdateContext<'gc>, - event: Object<'gc>, + event: EventObject<'gc>, target: Object<'gc>, simulate_dispatch: bool, ) -> bool { - let event_name = event - .as_event() - .map(|e| e.event_type()) - .unwrap_or_else(|| panic!("cannot dispatch non-event object: {:?}", event)); - let mut activation = Activation::from_nothing(context); match events::dispatch_event(&mut activation, target, event, simulate_dispatch) { Err(err) => { + let event_name = event.event().event_type(); + tracing::error!( "Encountered AVM2 error when dispatching `{}` event: {:?}", event_name, @@ -503,13 +507,10 @@ impl<'gc> Avm2<'gc> { /// Attempts to broadcast a non-event object will panic. pub fn broadcast_event( context: &mut UpdateContext<'gc>, - event: Object<'gc>, + event: EventObject<'gc>, on_type: ClassObject<'gc>, ) { - let event_name = event - .as_event() - .map(|e| e.event_type()) - .unwrap_or_else(|| panic!("cannot broadcast non-event object: {:?}", event)); + let event_name = event.event().event_type(); if !BROADCAST_WHITELIST .iter() @@ -697,6 +698,10 @@ impl<'gc> Avm2<'gc> { .load_classes(&mut activation) .expect("Classes should load"); + // These Classes are absolutely critical to the runtime, so make sure + // we've registered them before anything else. + init_builtin_system_class_defs(&mut activation); + // The second script (script #1) is Toplevel.as, and includes important // builtin classes such as Namespace, QName, and XML. tunit @@ -753,25 +758,7 @@ impl<'gc> Avm2<'gc> { /// Push a value onto the operand stack. #[inline(always)] - fn push(&mut self, mut value: Value<'gc>) { - if let Value::Object(o) = value { - // this is hot, so let's avoid a non-inlined call here - if let Object::PrimitiveObject(_) = o { - if let Some(prim) = o.as_primitive() { - value = *prim; - } - } - } - - avm_debug!(self, "Stack push {}: {value:?}", self.stack.len()); - - self.push_internal(value); - } - - /// Push a value onto the operand stack. - /// This is like `push`, but does not handle `PrimitiveObject`. - #[inline(always)] - fn push_raw(&mut self, value: Value<'gc>) { + fn push(&mut self, value: Value<'gc>) { avm_debug!(self, "Stack push {}: {value:?}", self.stack.len()); self.push_internal(value); diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 89b1d4f32228..7fa2c0c96aca 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -279,15 +279,18 @@ impl<'a, 'gc> Activation<'a, 'gc> { pub fn find_definition( &mut self, name: &Multiname<'gc>, - ) -> Result>, Error<'gc>> { + ) -> Result>, Error<'gc>> { let outer_scope = self.outer; - if let Some(obj) = search_scope_stack(self.scope_frame(), name, outer_scope.is_empty())? { + if let Some(obj) = search_scope_stack(self, name, outer_scope.is_empty())? { Ok(Some(obj)) } else if let Some(obj) = outer_scope.find(name, self)? { Ok(Some(obj)) } else if let Some(global) = self.global_scope() { - if global.base().has_own_dynamic_property(name) { + if global + .as_object() + .is_some_and(|o| o.base().has_own_dynamic_property(name)) + { Ok(Some(global)) } else { Ok(None) @@ -392,11 +395,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { &mut self, method: Gc<'gc, BytecodeMethod<'gc>>, outer: ScopeChain<'gc>, - this: Object<'gc>, + this: Value<'gc>, user_arguments: &[Value<'gc>], bound_superclass_object: Option>, bound_class: Option>, - callee: Object<'gc>, + callee: Value<'gc>, ) -> Result<(), Error<'gc>> { let body: Result<_, Error<'gc>> = method .body() @@ -406,7 +409,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let has_rest_or_args = method.is_variadic(); let mut local_registers = RegisterSet::new(num_locals + 1); - *local_registers.get_unchecked_mut(0) = this.into(); + *local_registers.get_unchecked_mut(0) = this; let activation_class = BytecodeMethod::get_or_init_activation_class(method, self.gc(), || { @@ -494,7 +497,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { .flags .contains(AbcMethodFlags::NEED_ARGUMENTS) { - args_object.set_string_property_local("callee", callee.into(), self)?; + args_object.set_string_property_local("callee", callee, self)?; args_object.set_local_property_is_enumerable(self.gc(), "callee".into(), false); } @@ -539,16 +542,18 @@ impl<'a, 'gc> Activation<'a, 'gc> { } /// Call the superclass's instance initializer. + /// + /// This method may panic if called with a Null or Undefined receiver. pub fn super_init( &mut self, - receiver: Object<'gc>, + receiver: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { let bound_superclass_object = self .bound_superclass_object .expect("Superclass object is required to run super_init"); - bound_superclass_object.call_init(receiver.into(), args, self) + bound_superclass_object.call_init(receiver, args, self) } /// Retrieve a local register. @@ -605,7 +610,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { /// /// A return value of `None` implies that both the outer scope, and /// the current scope stack were both empty. - pub fn global_scope(&self) -> Option> { + pub fn global_scope(&self) -> Option> { let outer_scope = self.outer; outer_scope .get(0) @@ -627,12 +632,6 @@ impl<'a, 'gc> Activation<'a, 'gc> { self.avm2().push(value.into()); } - /// Pushes a value onto the operand stack, without running some checks. - #[inline] - pub fn push_raw(&mut self, value: impl Into>) { - self.avm2().push_raw(value.into()); - } - /// Pops a value off the operand stack. #[inline] #[must_use] @@ -1138,7 +1137,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { // However, the optimizer can still generate it. let args = self.pop_stack_args(arg_count); - let receiver = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let receiver = self.pop_stack().null_check(self, None)?; let value = receiver.call_method(index, &args, self)?; @@ -1156,9 +1155,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { ) -> Result, Error<'gc>> { let args = self.pop_stack_args(arg_count); let multiname = multiname.fill_with_runtime_params(self)?; - let receiver = self - .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + let receiver = self.pop_stack().null_check(self, Some(&multiname))?; let value = receiver.call_property(&multiname, &args, self)?; @@ -1174,9 +1171,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { ) -> Result, Error<'gc>> { let args = self.pop_stack_args(arg_count); let multiname = multiname.fill_with_runtime_params(self)?; - let receiver = self - .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + let receiver = self.pop_stack().null_check(self, Some(&multiname))?; let function = receiver.get_property(&multiname, self)?; let value = function.call(self, Value::Null, &args)?; @@ -1192,9 +1187,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { ) -> Result, Error<'gc>> { let args = self.pop_stack_args(arg_count); let multiname = multiname.fill_with_runtime_params(self)?; - let receiver = self - .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + let receiver = self.pop_stack().null_check(self, Some(&multiname))?; receiver.call_property(&multiname, &args, self)?; @@ -1229,7 +1222,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { let multiname = multiname.fill_with_runtime_params(self)?; let receiver = self .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + .null_check(self, Some(&multiname))? + .as_object() + .expect("Super ops should not appear in primitive functions"); let bound_superclass_object = self.bound_superclass_object(&multiname); @@ -1249,7 +1244,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { let multiname = multiname.fill_with_runtime_params(self)?; let receiver = self .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + .null_check(self, Some(&multiname))? + .as_object() + .expect("Super ops should not appear in primitive functions"); let bound_superclass_object = self.bound_superclass_object(&multiname); @@ -1290,10 +1287,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { ) -> Result, Error<'gc>> { // default path for static names if !multiname.has_lazy_component() { - let object = self.pop_stack(); - let object = object.coerce_to_object_or_typeerror(self, Some(&multiname))?; + let object = self.pop_stack().null_check(self, Some(&multiname))?; + let value = object.get_property(&multiname, self)?; self.push_stack(value); + return Ok(FrameControl::Continue); } @@ -1336,8 +1334,8 @@ impl<'a, 'gc> Activation<'a, 'gc> { // main path for dynamic names let multiname = multiname.fill_with_runtime_params(self)?; - let object = self.pop_stack(); - let object = object.coerce_to_object_or_typeerror(self, Some(&multiname))?; + let object = self.pop_stack().null_check(self, Some(&multiname))?; + let value = object.get_property(&multiname, self)?; self.push_stack(value); @@ -1352,8 +1350,8 @@ impl<'a, 'gc> Activation<'a, 'gc> { // default path for static names if !multiname.has_lazy_component() { - let object = self.pop_stack(); - let object = object.coerce_to_object_or_typeerror(self, Some(&multiname))?; + let object = self.pop_stack().null_check(self, Some(&multiname))?; + object.set_property(&multiname, value, self)?; return Ok(FrameControl::Continue); } @@ -1396,8 +1394,8 @@ impl<'a, 'gc> Activation<'a, 'gc> { // main path for dynamic names let multiname = multiname.fill_with_runtime_params(self)?; - let object = self.pop_stack(); - let object = object.coerce_to_object_or_typeerror(self, Some(&multiname))?; + let object = self.pop_stack().null_check(self, Some(&multiname))?; + object.set_property(&multiname, value, self)?; Ok(FrameControl::Continue) @@ -1411,9 +1409,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let multiname = multiname.fill_with_runtime_params(self)?; - let object = self - .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + let object = self.pop_stack().null_check(self, Some(&multiname))?; object.init_property(&multiname, value, self)?; @@ -1426,10 +1422,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { ) -> Result, Error<'gc>> { // default path for static names if !multiname.has_lazy_component() { - let object = self.pop_stack(); - let object = object.coerce_to_object_or_typeerror(self, Some(&multiname))?; + let object = self.pop_stack().null_check(self, Some(&multiname))?; + let did_delete = object.delete_property(self, &multiname)?; - self.push_raw(did_delete); + self.push_stack(did_delete); + return Ok(FrameControl::Continue); } @@ -1442,13 +1439,13 @@ impl<'a, 'gc> Activation<'a, 'gc> { let name_value = self.context.avm2.peek(0); let object = self.context.avm2.peek(1); if let Some(name_object) = name_value.as_object() { - let object = object.coerce_to_object_or_typeerror(self, None)?; - if let Some(dictionary) = object.as_dictionary_object() { + if let Some(dictionary) = object.as_object().and_then(|o| o.as_dictionary_object()) + { let _ = self.pop_stack(); let _ = self.pop_stack(); dictionary.delete_property_by_object(name_object, self.gc()); - self.push_raw(true); + self.push_stack(true); return Ok(FrameControl::Continue); } } @@ -1468,11 +1465,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { } } let multiname = multiname.fill_with_runtime_params(self)?; - let object = self.pop_stack(); - let object = object.coerce_to_object_or_typeerror(self, Some(&multiname))?; + let object = self.pop_stack().null_check(self, Some(&multiname))?; + let did_delete = object.delete_property(self, &multiname)?; - self.push_raw(did_delete); + self.push_stack(did_delete); Ok(FrameControl::Continue) } @@ -1484,7 +1481,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { let multiname = multiname.fill_with_runtime_params(self)?; let object = self .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + .null_check(self, Some(&multiname))? + .as_object() + .expect("Super ops should not appear in primitive functions"); let bound_superclass_object = self.bound_superclass_object(&multiname); @@ -1503,7 +1502,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { let multiname = multiname.fill_with_runtime_params(self)?; let object = self .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + .null_check(self, Some(&multiname))? + .as_object() + .expect("Super ops should not appear in primitive functions"); let bound_superclass_object = self.bound_superclass_object(&multiname); @@ -1513,22 +1514,41 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn op_in(&mut self) -> Result, Error<'gc>> { - let obj = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let value = self.pop_stack().null_check(self, None)?; let name_value = self.pop_stack(); - if let Some(dictionary) = obj.as_dictionary_object() { - if let Some(name_object) = name_value.as_object() { - self.push_raw(dictionary.has_property_by_object(name_object)); + let has_prop = match value { + Value::Object(obj) => { + if let Some(dictionary) = obj.as_dictionary_object() { + if let Some(name_object) = name_value.as_object() { + self.push_stack(dictionary.has_property_by_object(name_object)); + + return Ok(FrameControl::Continue); + } + } - return Ok(FrameControl::Continue); + let name = name_value.coerce_to_string(self)?; + let multiname = Multiname::new(self.avm2().find_public_namespace(), name); + + obj.has_property_via_in(self, &multiname)? } - } + _ => { + let name = name_value.coerce_to_string(self)?; + let multiname = Multiname::new(self.avm2().find_public_namespace(), name); - let name = name_value.coerce_to_string(self)?; - let multiname = Multiname::new(self.avm2().find_public_namespace(), name); - let has_prop = obj.has_property_via_in(self, &multiname)?; + if value.has_trait(self, &multiname) { + true + } else if let Some(proto) = value.proto(self) { + proto.has_property(&multiname) + } else { + // This condition can't be met, since `Value::proto` always + // returns `Some` for primitives) + unreachable!() + } + } + }; - self.push_raw(has_prop); + self.push_stack(has_prop); Ok(FrameControl::Continue) } @@ -1557,14 +1577,14 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn op_push_scope(&mut self) -> Result, Error<'gc>> { - let object = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let object = self.pop_stack().null_check(self, None)?; self.push_scope(Scope::new(object)); Ok(FrameControl::Continue) } fn op_push_with(&mut self) -> Result, Error<'gc>> { - let object = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let object = self.pop_stack().null_check(self, None)?; self.push_scope(Scope::new_with(object)); Ok(FrameControl::Continue) @@ -1599,11 +1619,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn op_get_global_scope(&mut self) -> Result, Error<'gc>> { - self.push_stack( - self.global_scope() - .map(|gs| gs.into()) - .unwrap_or(Value::Null), - ); + self.push_stack(self.global_scope().unwrap_or(Value::Null)); Ok(FrameControl::Continue) } @@ -1632,7 +1648,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { .find_definition(&multiname)? .or_else(|| self.global_scope()); - self.push_stack(result.map(|o| o.into()).unwrap_or(Value::Undefined)); + self.push_stack(result.unwrap_or(Value::Undefined)); Ok(FrameControl::Continue) } @@ -1644,10 +1660,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { avm_debug!(self.context.avm2, "Resolving {:?}", *multiname); let multiname = multiname.fill_with_runtime_params(self)?; - let found: Result, Error<'gc>> = self + let result = self .find_definition(&multiname)? - .ok_or_else(|| make_error_1065(self, &multiname)); - let result: Value<'gc> = found?.into(); + .ok_or_else(|| make_error_1065(self, &multiname))?; self.push_stack(result); @@ -1670,15 +1685,20 @@ impl<'a, 'gc> Activation<'a, 'gc> { multiname: Gc<'gc, Multiname<'gc>>, ) -> Result, Error<'gc>> { let multiname = multiname.fill_with_runtime_params(self)?; - let object = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; - if let Some(descendants) = object.xml_descendants(self, &multiname) { + let object = self.pop_stack().null_check(self, None)?; + + if let Some(descendants) = object + .as_object() + .and_then(|o| o.xml_descendants(self, &multiname)) + { self.push_stack(descendants); } else { // Even if it's an object with the "descendants" property, we won't support it. let class_name = object - .instance_class() + .instance_class(self) .name() .to_qualified_name_err_message(self.gc()); + return Err(Error::AvmError(type_error( self, &format!( @@ -1734,7 +1754,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_get_global_slot(&mut self, index: u32) -> Result, Error<'gc>> { let value = self .global_scope() - .map(|global| global.get_slot(index)) + .map(|global| global.as_object().unwrap().get_slot(index)) .unwrap_or(Value::Undefined); self.push_stack(value); @@ -1746,7 +1766,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack(); self.global_scope() - .map(|global| global.set_slot(index, value, self)) + .map(|global| global.as_object().unwrap().set_slot(index, value, self)) .transpose()?; Ok(FrameControl::Continue) @@ -1770,20 +1790,19 @@ impl<'a, 'gc> Activation<'a, 'gc> { ) -> Result, Error<'gc>> { let args = self.pop_stack_args(arg_count); let multiname = multiname.fill_with_runtime_params(self)?; - let source = self - .pop_stack() - .coerce_to_object_or_typeerror(self, Some(&multiname))?; + let source = self.pop_stack().null_check(self, Some(&multiname))?; - let object = source.construct_prop(&multiname, &args, self)?; + let ctor = source.get_property(&multiname, self)?; + let constructed_object = ctor.construct(self, &args)?; - self.push_stack(object); + self.push_stack(constructed_object); Ok(FrameControl::Continue) } fn op_construct_super(&mut self, arg_count: u32) -> Result, Error<'gc>> { let args = self.pop_stack_args(arg_count); - let receiver = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let receiver = self.pop_stack().null_check(self, None)?; self.super_init(receiver, &args)?; @@ -1844,7 +1863,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let new_class = ClassObject::from_class(self, class, base_class)?; - self.push_raw(new_class); + self.push_stack(new_class); Ok(FrameControl::Continue) } @@ -1876,7 +1895,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_coerce_b(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_boolean(); - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } @@ -1884,7 +1903,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_coerce_d(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_number(self)?; - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } @@ -1893,7 +1912,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack().coerce_to_number(self)?; let _ = self.pop_stack(); - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } @@ -1901,7 +1920,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_coerce_i(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } @@ -1910,7 +1929,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack().coerce_to_i32(self)?; let _ = self.pop_stack(); - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } @@ -1937,7 +1956,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { _ => value.coerce_to_string(self)?.into(), }; - self.push_raw(coerced); + self.push_stack(coerced); Ok(FrameControl::Continue) } @@ -1945,7 +1964,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_coerce_u(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_u32(self)?; - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } @@ -1954,16 +1973,14 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack().coerce_to_u32(self)?; let _ = self.pop_stack(); - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } fn op_convert_o(&mut self) -> Result, Error<'gc>> { - let value = self.pop_stack(); - if matches!(value, Value::Null | Value::Undefined) { - return Err(make_null_or_undefined_error(self, value, None)); - } + let value = self.pop_stack().null_check(self, None)?; + self.push_stack(value); Ok(FrameControl::Continue) @@ -1972,7 +1989,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_convert_s(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_string(self)?; - self.push_raw(value); + self.push_stack(value); Ok(FrameControl::Continue) } @@ -2059,7 +2076,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_i32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1.wrapping_add(value2)); + self.push_stack(value1.wrapping_add(value2)); Ok(FrameControl::Continue) } @@ -2068,7 +2085,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_i32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1 & value2); + self.push_stack(value1 & value2); Ok(FrameControl::Continue) } @@ -2076,7 +2093,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_bitnot(&mut self) -> Result, Error<'gc>> { let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(!value1); + self.push_stack(!value1); Ok(FrameControl::Continue) } @@ -2085,7 +2102,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_i32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1 | value2); + self.push_stack(value1 | value2); Ok(FrameControl::Continue) } @@ -2094,7 +2111,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_i32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1 ^ value2); + self.push_stack(value1 ^ value2); Ok(FrameControl::Continue) } @@ -2118,7 +2135,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_decrement(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_number(self)?; - self.push_raw(value - 1.0); + self.push_stack(value - 1.0); Ok(FrameControl::Continue) } @@ -2126,7 +2143,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_decrement_i(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value.wrapping_sub(1)); + self.push_stack(value.wrapping_sub(1)); Ok(FrameControl::Continue) } @@ -2135,7 +2152,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_number(self)?; let value1 = self.pop_stack().coerce_to_number(self)?; - self.push_raw(value1 / value2); + self.push_stack(value1 / value2); Ok(FrameControl::Continue) } @@ -2159,7 +2176,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_increment(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_number(self)?; - self.push_raw(value + 1.0); + self.push_stack(value + 1.0); Ok(FrameControl::Continue) } @@ -2167,7 +2184,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_increment_i(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value.wrapping_add(1)); + self.push_stack(value.wrapping_add(1)); Ok(FrameControl::Continue) } @@ -2176,7 +2193,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_u32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1 << (value2 & 0x1F)); + self.push_stack(value1 << (value2 & 0x1F)); Ok(FrameControl::Continue) } @@ -2185,7 +2202,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_number(self)?; let value1 = self.pop_stack().coerce_to_number(self)?; - self.push_raw(value1 % value2); + self.push_stack(value1 % value2); Ok(FrameControl::Continue) } @@ -2194,7 +2211,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_number(self)?; let value1 = self.pop_stack().coerce_to_number(self)?; - self.push_raw(value1 * value2); + self.push_stack(value1 * value2); Ok(FrameControl::Continue) } @@ -2203,7 +2220,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_i32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1.wrapping_mul(value2)); + self.push_stack(value1.wrapping_mul(value2)); Ok(FrameControl::Continue) } @@ -2211,7 +2228,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_negate(&mut self) -> Result, Error<'gc>> { let value1 = self.pop_stack().coerce_to_number(self)?; - self.push_raw(-value1); + self.push_stack(-value1); Ok(FrameControl::Continue) } @@ -2219,7 +2236,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_negate_i(&mut self) -> Result, Error<'gc>> { let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1.wrapping_neg()); + self.push_stack(value1.wrapping_neg()); Ok(FrameControl::Continue) } @@ -2228,7 +2245,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_u32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1 >> (value2 & 0x1F)); + self.push_stack(value1 >> (value2 & 0x1F)); Ok(FrameControl::Continue) } @@ -2257,7 +2274,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_i32(self)?; let value1 = self.pop_stack().coerce_to_i32(self)?; - self.push_raw(value1.wrapping_sub(value2)); + self.push_stack(value1.wrapping_sub(value2)); Ok(FrameControl::Continue) } @@ -2276,7 +2293,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value2 = self.pop_stack().coerce_to_u32(self)?; let value1 = self.pop_stack().coerce_to_u32(self)?; - self.push_raw(value1 >> (value2 & 0x1F)); + self.push_stack(value1 >> (value2 & 0x1F)); Ok(FrameControl::Continue) } @@ -2442,7 +2459,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_strict_equals(&mut self) -> Result, Error<'gc>> { let value2 = self.pop_stack(); let value1 = self.pop_stack(); - self.push_raw(value1.strict_eq(&value2)); + self.push_stack(value1.strict_eq(&value2)); Ok(FrameControl::Continue) } @@ -2453,7 +2470,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let result = value1.abstract_eq(&value2, self)?; - self.push_raw(result); + self.push_stack(result); Ok(FrameControl::Continue) } @@ -2464,7 +2481,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let result = !value1.abstract_lt(&value2, self)?.unwrap_or(true); - self.push_raw(result); + self.push_stack(result); Ok(FrameControl::Continue) } @@ -2475,7 +2492,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let result = value2.abstract_lt(&value1, self)?.unwrap_or(false); - self.push_raw(result); + self.push_stack(result); Ok(FrameControl::Continue) } @@ -2486,7 +2503,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let result = !value2.abstract_lt(&value1, self)?.unwrap_or(true); - self.push_raw(result); + self.push_stack(result); Ok(FrameControl::Continue) } @@ -2497,7 +2514,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let result = value1.abstract_lt(&value2, self)?.unwrap_or(false); - self.push_raw(result); + self.push_stack(result); Ok(FrameControl::Continue) } @@ -2505,7 +2522,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_not(&mut self) -> Result, Error<'gc>> { let value = self.pop_stack().coerce_to_boolean(); - self.push_raw(!value); + self.push_stack(!value); Ok(FrameControl::Continue) } @@ -2514,7 +2531,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let cur_index = self.pop_stack().coerce_to_i32(self)?; if cur_index < 0 { - self.push_raw(false); + self.push_stack(false); return Ok(FrameControl::Continue); } @@ -2530,9 +2547,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { if let Some(object) = object { let next_index = object.get_next_enumerant(cur_index as u32, self)?; - self.push_raw(next_index); + self.push_stack(next_index); } else { - self.push_raw(0); + self.push_stack(0); } Ok(FrameControl::Continue) @@ -2545,7 +2562,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let cur_index = self.local_register(index_register).coerce_to_i32(self)?; if cur_index < 0 { - self.push_raw(false); + self.push_stack(false); return Ok(FrameControl::Continue); } @@ -2584,7 +2601,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { result_value = Value::Null; } - self.push_raw(cur_index != 0); + self.push_stack(cur_index != 0); self.set_local_register(index_register, cur_index); self.set_local_register(object_register, result_value); @@ -2647,7 +2664,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack(); let is_instance_of = value.is_of_type(self, class); - self.push_raw(is_instance_of); + self.push_stack(is_instance_of); Ok(FrameControl::Continue) } @@ -2667,7 +2684,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack(); let is_instance_of = value.is_of_type(self, type_object.inner_class_definition()); - self.push_raw(is_instance_of); + self.push_stack(is_instance_of); Ok(FrameControl::Continue) } @@ -2678,7 +2695,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { if value.is_of_type(self, class) { self.push_stack(value); } else { - self.push_raw(Value::Null); + self.push_stack(Value::Null); } Ok(FrameControl::Continue) @@ -2704,7 +2721,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { if value.is_of_type(self, class.inner_class_definition()) { self.push_stack(value); } else { - self.push_raw(Value::Null); + self.push_stack(Value::Null); } Ok(FrameControl::Continue) @@ -2735,11 +2752,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { match value { Value::Undefined => return Err(make_null_or_undefined_error(self, value, None)), - Value::Null => self.push_raw(false), + Value::Null => self.push_stack(false), value => { let is_instance_of = value.is_instance_of(self, type_object); - self.push_raw(is_instance_of); + self.push_stack(is_instance_of); } } @@ -2782,7 +2799,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { Value::String(_) => "string", }; - self.push_raw(Value::String(type_name.into())); + self.push_stack(Value::String(type_name.into())); Ok(FrameControl::Continue) } @@ -2793,7 +2810,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { // Implementation of `EscapeAttributeValue` from ECMA-357(10.2.1.2) let r = escape_attribute_value(s); - self.push_raw(AvmString::new(self.gc(), r)); + self.push_stack(AvmString::new(self.gc(), r)); Ok(FrameControl::Continue) } @@ -2808,7 +2825,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { x => AvmString::new(self.gc(), escape_element_value(x.coerce_to_string(self)?)), }; - self.push_raw(r); + self.push_stack(r); Ok(FrameControl::Continue) } @@ -2982,7 +2999,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let val = dm.get(address); if let Some(val) = val { - self.push_raw(val); + self.push_stack(val); } else { return Err(make_error_1506(self)); } @@ -3002,7 +3019,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { } let val = dm.read_at(2, address).map_err(|e| e.to_avm(self))?; - self.push_raw(u16::from_le_bytes(val.try_into().unwrap())); + self.push_stack(u16::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } @@ -3019,7 +3036,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { } let val = dm.read_at(4, address).map_err(|e| e.to_avm(self))?; - self.push_raw(i32::from_le_bytes(val.try_into().unwrap())); + self.push_stack(i32::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } @@ -3035,7 +3052,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { } let val = dm.read_at(4, address).map_err(|e| e.to_avm(self))?; - self.push_raw(f32::from_le_bytes(val.try_into().unwrap())); + self.push_stack(f32::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } @@ -3052,7 +3069,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { } let val = dm.read_at(8, address).map_err(|e| e.to_avm(self))?; - self.push_raw(f64::from_le_bytes(val.try_into().unwrap())); + self.push_stack(f64::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } @@ -3062,7 +3079,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let val = val.wrapping_shl(31).wrapping_shr(31); - self.push_raw(Value::Integer(val)); + self.push_stack(Value::Integer(val)); Ok(FrameControl::Continue) } @@ -3073,7 +3090,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let val = (val.wrapping_shl(23).wrapping_shr(23) & 0xFF) as i8 as i32; - self.push_raw(Value::Integer(val)); + self.push_stack(Value::Integer(val)); Ok(FrameControl::Continue) } @@ -3084,7 +3101,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let val = (val.wrapping_shl(15).wrapping_shr(15) & 0xFFFF) as i16 as i32; - self.push_raw(Value::Integer(val)); + self.push_stack(Value::Integer(val)); Ok(FrameControl::Continue) } diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 46f271e56352..fd1b3fadd5b3 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -200,7 +200,7 @@ pub fn recursive_serialize<'gc>( continue; } } - let value = obj.get_public_property(name, activation)?; + let value = Value::from(obj).get_public_property(name, activation)?; let name = name.to_utf8_lossy().to_string(); if let Some(elem) = get_or_create_element(activation, name.clone(), value, object_table, amf_version) @@ -344,7 +344,7 @@ pub fn deserialize_value_impl<'gc>( activation.avm2().classes().object }; let obj = target_class.construct(activation, &[])?; - object_map.insert(*id, obj); + object_map.insert(*id, obj.as_object().unwrap()); for entry in elements { let name = entry.name(); @@ -370,23 +370,17 @@ pub fn deserialize_value_impl<'gc>( } } - obj.into() + obj } AmfValue::Date(time, _) => activation .avm2() .classes() .date - .construct(activation, &[(*time).into()])? - .into(), - AmfValue::XML(content, _) => activation - .avm2() - .classes() - .xml - .construct( - activation, - &[Value::String(AvmString::new_utf8(activation.gc(), content))], - )? - .into(), + .construct(activation, &[(*time).into()])?, + AmfValue::XML(content, _) => activation.avm2().classes().xml.construct( + activation, + &[Value::String(AvmString::new_utf8(activation.gc(), content))], + )?, AmfValue::VectorDouble(vec, is_fixed) => { let storage = VectorStorage::from_values( vec.iter().map(|v| (*v).into()).collect(), @@ -452,7 +446,10 @@ pub fn deserialize_value_impl<'gc>( .avm2() .classes() .dictionary - .construct(activation, &[(*has_weak_keys).into()])?; + .construct(activation, &[(*has_weak_keys).into()])? + .as_object() + .unwrap(); + object_map.insert(*id, obj); let dict_obj = obj .as_dictionary_object() diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index e559127da168..5d71af3e38f7 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -74,7 +74,7 @@ pub struct Allocator(pub AllocatorFn); /// This function should be passed an Activation, and the arguments passed to the /// constructor, and will return an Object. pub type CustomConstructorFn = - for<'gc> fn(&mut Activation<'_, 'gc>, &[Value<'gc>]) -> Result, Error<'gc>>; + for<'gc> fn(&mut Activation<'_, 'gc>, &[Value<'gc>]) -> Result, Error<'gc>>; #[derive(Clone, Copy)] pub struct CustomConstructor(pub CustomConstructorFn); diff --git a/core/src/avm2/domain.rs b/core/src/avm2/domain.rs index e340940496b8..ca6a4f12dd9c 100644 --- a/core/src/avm2/domain.rs +++ b/core/src/avm2/domain.rs @@ -266,7 +266,7 @@ impl<'gc> Domain<'gc> { let (name, script) = self.find_defining_script(activation, &name.into())?; let globals = script.globals(activation.context)?; - globals.get_property(&name.into(), activation) + Value::from(globals).get_property(&name.into(), activation) } /// Retrieve a value from this domain, with special handling for 'Vector.'. @@ -395,7 +395,10 @@ impl<'gc> Domain<'gc> { ) -> Result<(), Error<'gc>> { let bytearray_class = activation.avm2().classes().bytearray; - let domain_memory = bytearray_class.construct(activation, &[])?; + let domain_memory = bytearray_class + .construct(activation, &[])? + .as_object() + .unwrap(); domain_memory .as_bytearray_mut() .unwrap() diff --git a/core/src/avm2/e4x.rs b/core/src/avm2/e4x.rs index d1526b135110..c80b1b319c24 100644 --- a/core/src/avm2/e4x.rs +++ b/core/src/avm2/e4x.rs @@ -1,5 +1,4 @@ use crate::avm2::error::{make_error_1010, make_error_1085, make_error_1118, type_error}; -use crate::avm2::globals::slots::xml as xml_class_slots; use crate::avm2::object::{E4XOrXml, FunctionObject, NamespaceObject}; use crate::avm2::{Activation, Error, Multiname, TObject, Value}; use crate::string::{AvmString, WStr, WString}; @@ -132,7 +131,10 @@ impl<'gc> E4XNamespace<'gc> { .classes() .namespace .construct(activation, &args)?; + Ok(obj + .as_object() + .unwrap() .as_namespace_object() .expect("just constructed a namespace")) } @@ -758,7 +760,7 @@ impl<'gc> E4XNode<'gc> { val => { if let Some(obj) = val.as_object() { if obj.as_xml_object().is_some() || obj.as_xml_list_object().is_some() { - value = obj.call_public_property("toXMLString", &[], activation)?; + value = val.call_public_property("toXMLString", &[], activation)?; } } value.coerce_to_string(activation)? @@ -1649,21 +1651,10 @@ pub fn to_xml_string<'gc>( xml: E4XOrXml<'gc>, activation: &mut Activation<'_, 'gc>, ) -> AvmString<'gc> { - let pretty_printing = activation - .avm2() - .classes() - .xml - .get_slot(xml_class_slots::PRETTY_PRINTING) - .coerce_to_boolean(); + let pretty_printing = activation.avm2().xml_settings.pretty_printing; let pretty = if pretty_printing { - let pretty_indent = activation - .avm2() - .classes() - .xml - .get_slot(xml_class_slots::PRETTY_INDENT) - .coerce_to_i32(activation) - .expect("shouldn't error"); + let pretty_indent = activation.avm2().xml_settings.pretty_indent; // NOTE: Negative values are invalid and are ignored. if pretty_indent < 0 { @@ -1754,7 +1745,7 @@ pub fn maybe_escape_child<'gc>( .classes() .xml .construct(activation, &[string.into()])?; - return Ok(xml.into()); + return Ok(xml); } } @@ -1775,3 +1766,23 @@ pub fn maybe_escape_child<'gc>( Ok(child) } + +pub struct XmlSettings { + pub ignore_comments: bool, + pub ignore_processing_instructions: bool, + pub ignore_whitespace: bool, + pub pretty_printing: bool, + pub pretty_indent: i32, +} + +impl XmlSettings { + pub fn new_default() -> Self { + XmlSettings { + ignore_comments: true, + ignore_processing_instructions: true, + ignore_whitespace: true, + pretty_printing: true, + pretty_indent: 2, + } + } +} diff --git a/core/src/avm2/error.rs b/core/src/avm2/error.rs index 85b32d06881e..95f12aeba37a 100644 --- a/core/src/avm2/error.rs +++ b/core/src/avm2/error.rs @@ -835,9 +835,8 @@ fn error_constructor<'gc>( code: u32, ) -> Result, Error<'gc>> { let message = AvmString::new_utf8(activation.gc(), message); - Ok(class - .construct(activation, &[message.into(), code.into()])? - .into()) + + class.construct(activation, &[message.into(), code.into()]) } impl std::fmt::Display for Error<'_> { diff --git a/core/src/avm2/events.rs b/core/src/avm2/events.rs index 1343f6e75628..4ee90391398f 100644 --- a/core/src/avm2/events.rs +++ b/core/src/avm2/events.rs @@ -3,7 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::error::make_error_2007; use crate::avm2::globals::slots::flash_events_event_dispatcher as slots; -use crate::avm2::object::{Object, TObject}; +use crate::avm2::object::{EventObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::display_object::TDisplayObject; @@ -374,13 +374,13 @@ fn dispatch_event_to_target<'gc>( activation: &mut Activation<'_, 'gc>, dispatcher: Object<'gc>, target: Object<'gc>, - event: Object<'gc>, + event: EventObject<'gc>, simulate_dispatch: bool, ) -> Result<(), Error<'gc>> { avm_debug!( activation.context.avm2, "Event dispatch: {} to {target:?}", - event.as_event().unwrap().event_type(), + event.event().event_type(), ); let dispatch_list = dispatcher.get_slot(slots::DISPATCH_LIST).as_object(); @@ -392,7 +392,7 @@ fn dispatch_event_to_target<'gc>( let dispatch_list = dispatch_list.unwrap(); - let mut evtmut = event.as_event_mut(activation.gc()).unwrap(); + let mut evtmut = event.event_mut(activation.gc()); let name = evtmut.event_type(); let use_capture = evtmut.phase() == EventPhase::Capturing; @@ -415,11 +415,7 @@ fn dispatch_event_to_target<'gc>( } for handler in handlers.iter() { - if event - .as_event() - .unwrap() - .is_propagation_stopped_immediately() - { + if event.event().is_propagation_stopped_immediately() { break; } @@ -441,7 +437,7 @@ fn dispatch_event_to_target<'gc>( pub fn dispatch_event<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, - event: Object<'gc>, + event_object: EventObject<'gc>, simulate_dispatch: bool, ) -> Result> { let target = this.get_slot(slots::TARGET).as_object().unwrap_or(this); @@ -459,18 +455,20 @@ pub fn dispatch_event<'gc>( parent = parent_dobj.parent(); } - let dispatched = event.as_event().unwrap().dispatched; + let dispatched = event_object.event().dispatched; let event = if dispatched { - event + Value::from(event_object) .call_public_property("clone", &[], activation)? .as_object() .ok_or_else(|| make_error_2007(activation, "event"))? + .as_event_object() + .expect("Event.clone should return an Event") } else { - event + event_object }; - let mut evtmut = event.as_event_mut(activation.gc()).unwrap(); + let mut evtmut = event.event_mut(activation.gc()); evtmut.set_phase(EventPhase::Capturing); evtmut.set_target(target); @@ -478,7 +476,7 @@ pub fn dispatch_event<'gc>( drop(evtmut); for ancestor in ancestor_list.iter().rev() { - if event.as_event().unwrap().is_propagation_stopped() { + if event.event().is_propagation_stopped() { break; } @@ -486,22 +484,20 @@ pub fn dispatch_event<'gc>( } event - .as_event_mut(activation.gc()) - .unwrap() + .event_mut(activation.gc()) .set_phase(EventPhase::AtTarget); - if !event.as_event().unwrap().is_propagation_stopped() { + if !event.event().is_propagation_stopped() { dispatch_event_to_target(activation, this, target, event, simulate_dispatch)?; } event - .as_event_mut(activation.gc()) - .unwrap() + .event_mut(activation.context.gc_context) .set_phase(EventPhase::Bubbling); - if event.as_event().unwrap().is_bubbling() { + if event.event().is_bubbling() { for ancestor in ancestor_list.iter() { - if event.as_event().unwrap().is_propagation_stopped() { + if event.event().is_propagation_stopped() { break; } @@ -509,6 +505,6 @@ pub fn dispatch_event<'gc>( } } - let handled = event.as_event().unwrap().dispatched; + let handled = event.event().dispatched; Ok(handled) } diff --git a/core/src/avm2/filters.rs b/core/src/avm2/filters.rs index 7175ea830b71..8af5ea5b962d 100644 --- a/core/src/avm2/filters.rs +++ b/core/src/avm2/filters.rs @@ -34,7 +34,7 @@ pub trait FilterAvm2Ext { fn as_avm2_object<'gc>( &self, activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>>; + ) -> Result, Error<'gc>>; } #[derive(Clone, Collect)] @@ -178,7 +178,7 @@ impl FilterAvm2Ext for Filter { fn as_avm2_object<'gc>( &self, activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { match self { Filter::BevelFilter(filter) => bevel_filter_to_avm2(activation, filter), Filter::BlurFilter(filter) => blur_filter_to_avm2(activation, filter), @@ -265,7 +265,7 @@ fn avm2_to_bevel_filter<'gc>( fn bevel_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &BevelFilter, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { activation.avm2().classes().bevelfilter.construct( activation, &[ @@ -315,7 +315,7 @@ fn avm2_to_blur_filter<'gc>( fn blur_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &BlurFilter, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { activation.avm2().classes().blurfilter.construct( activation, &[ @@ -350,7 +350,7 @@ fn avm2_to_color_matrix_filter<'gc>( fn color_matrix_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &ColorMatrixFilter, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let matrix = ArrayObject::from_storage( activation, filter.matrix.iter().map(|v| Value::from(*v)).collect(), @@ -425,7 +425,7 @@ fn avm2_to_convolution_filter<'gc>( fn convolution_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &ConvolutionFilter, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let matrix = ArrayObject::from_storage( activation, filter @@ -530,7 +530,7 @@ fn avm2_to_displacement_map_filter<'gc>( fn displacement_map_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &DisplacementMapFilter, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let point = activation.avm2().classes().point; let map_point = point.construct( activation, @@ -546,7 +546,7 @@ fn displacement_map_filter_to_avm2<'gc>( activation, &[ Value::Null, // TODO: This should be a BitmapData... - map_point.into(), + map_point, filter.component_x.into(), filter.component_y.into(), filter.scale_x.into(), @@ -616,7 +616,7 @@ fn avm2_to_drop_shadow_filter<'gc>( fn drop_shadow_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &DropShadowFilter, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { activation.avm2().classes().dropshadowfilter.construct( activation, &[ @@ -679,7 +679,7 @@ fn avm2_to_glow_filter<'gc>( fn glow_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &GlowFilter, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { activation.avm2().classes().glowfilter.construct( activation, &[ @@ -763,7 +763,7 @@ fn gradient_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &GradientFilter, class: ClassObject<'gc>, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let colors = ArrayObject::from_storage( activation, filter @@ -855,7 +855,7 @@ fn avm2_to_shader_filter<'gc>( fn shader_filter_to_avm2<'gc>( activation: &mut Activation<'_, 'gc>, filter: &ShaderFilter<'static>, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let object_wrapper: &ObjectWrapper = filter .shader_object .downcast_ref::() diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index b7dffcf5d161..2d7c4f32700c 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,7 +1,7 @@ use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::method::{Method, ParamConfig}; -use crate::avm2::object::{ClassObject, Object}; +use crate::avm2::object::ClassObject; use crate::avm2::scope::ScopeChain; use crate::avm2::traits::TraitKind; use crate::avm2::value::Value; @@ -24,7 +24,9 @@ pub struct BoundMethod<'gc> { /// /// If `None`, then the receiver provided by the caller is used. A /// `Some` value indicates a bound executable. - bound_receiver: Option>, + /// + /// This should never be `Value::Null` or `Value::Undefined`. + bound_receiver: Option>, /// The superclass of the bound class for this method. /// @@ -40,7 +42,7 @@ impl<'gc> BoundMethod<'gc> { pub fn from_method( method: Method<'gc>, scope: ScopeChain<'gc>, - receiver: Option>, + receiver: Option>, superclass: Option>, class: Option>, ) -> Self { @@ -58,7 +60,7 @@ impl<'gc> BoundMethod<'gc> { unbound_receiver: Value<'gc>, arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc>, - callee: Object<'gc>, + callee: Value<'gc>, ) -> Result, Error<'gc>> { let receiver = if let Some(receiver) = self.bound_receiver { receiver @@ -68,7 +70,7 @@ impl<'gc> BoundMethod<'gc> { .expect("No global scope for function call") .values() } else { - unbound_receiver.coerce_to_object(activation)? + unbound_receiver }; exec( @@ -137,16 +139,19 @@ impl<'gc> BoundMethod<'gc> { /// /// Passed-in arguments will be conformed to the set of method parameters /// declared on the function. +/// +/// It is the caller's responsibility to ensure that the `receiver` passed +/// to this method is not Value::Null or Value::Undefined. #[allow(clippy::too_many_arguments)] pub fn exec<'gc>( method: Method<'gc>, scope: ScopeChain<'gc>, - receiver: Object<'gc>, + receiver: Value<'gc>, bound_superclass: Option>, bound_class: Option>, mut arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc>, - callee: Object<'gc>, + callee: Value<'gc>, ) -> Result, Error<'gc>> { let ret = match method { Method::Native(bm) => { @@ -200,7 +205,7 @@ pub fn exec<'gc>( .context .avm2 .push_call(activation.gc(), method, bound_class); - (bm.method)(&mut activation, Value::Object(receiver), &arguments) + (bm.method)(&mut activation, receiver, &arguments) } Method::Bytecode(bm) => { if bm.is_unchecked() { diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 1e4bf9656550..0ffcd80e6fa2 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -8,10 +8,7 @@ use crate::avm2::script::Script; use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::vtable::VTable; -use crate::avm2::Avm2; -use crate::avm2::Error; -use crate::avm2::Namespace; -use crate::avm2::QName; +use crate::avm2::{Avm2, Error, Multiname, Namespace, QName}; use crate::tag_utils::{self, ControlFlow, SwfMovie, SwfSlice, SwfStream}; use gc_arena::Collect; use std::sync::Arc; @@ -400,7 +397,7 @@ fn define_fn_on_global<'gc>( .get_defined_value(activation, qname) .expect("Function being defined on global should be defined in domain!"); - global + Value::from(global) .init_property(&qname.into(), func, activation) .expect("Should set property"); } @@ -418,7 +415,7 @@ fn dynamic_class<'gc>( let class = class_object.inner_class_definition(); let name = class.name(); - global + Value::from(global) .init_property(&name.into(), class_object.into(), activation) .expect("Should set property"); @@ -451,7 +448,7 @@ fn class<'gc>( let class_object = ClassObject::from_class(activation, class_def, super_class)?; - global + Value::from(global) .init_property(&class_name.into(), class_object.into(), activation) .expect("Should set property"); @@ -482,7 +479,7 @@ fn vector_class<'gc>( let legacy_name = QName::new(namespaces.vector_internal, legacy_name); - global + Value::from(global) .init_property(&legacy_name.into(), vector_cls.into(), activation) .expect("Should set property"); @@ -572,7 +569,6 @@ pub fn load_player_globals<'gc>( )); let string_class = string::create_class(activation); - let boolean_class = boolean::create_class(activation); let number_class = number::create_class(activation); let int_class = int::create_class(activation); let uint_class = uint::create_class(activation); @@ -597,7 +593,6 @@ pub fn load_player_globals<'gc>( (public_ns, "Class", class_i_class), (public_ns, "Function", fn_classdef), (public_ns, "String", string_class), - (public_ns, "Boolean", boolean_class), (public_ns, "Number", number_class), (public_ns, "int", int_class), (public_ns, "uint", uint_class), @@ -653,7 +648,7 @@ pub fn load_player_globals<'gc>( let globals = ScriptObject::custom_object(mc, global_classdef, None, global_classdef.vtable()); let scope = ScopeChain::new(domain); - let gs = scope.chain(mc, &[Scope::new(globals)]); + let gs = scope.chain(mc, &[Scope::new(globals.into())]); activation.set_outer(gs); let object_class = ClassObject::from_class_partial(activation, object_i_class, None)?; @@ -707,7 +702,7 @@ pub fn load_player_globals<'gc>( let fn_class = fn_class.into_finished_class(activation)?; // Function's prototype is an instance of itself - let fn_proto = fn_class.construct(activation, &[])?; + let fn_proto = fn_class.construct(activation, &[])?.as_object().unwrap(); fn_class.link_prototype(activation, fn_proto)?; // Object prototype is enough @@ -740,7 +735,6 @@ pub fn load_player_globals<'gc>( // Make sure to initialize superclasses *before* their subclasses! avm2_system_class!(string, activation, string_class, script); - avm2_system_class!(boolean, activation, boolean_class, script); avm2_system_class!(number, activation, number_class, script); avm2_system_class!(int, activation, int_class, script); avm2_system_class!(uint, activation, uint_class, script); @@ -833,11 +827,12 @@ macro_rules! avm2_system_class_defs_playerglobal { ($activation:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { let activation = $activation; $( + let domain = activation.domain(); + // Lookup with the highest version, so we we see all defined classes here let ns = Namespace::package($package, ApiVersion::VM_INTERNAL, activation.strings()); - let name = QName::new(ns, $class_name); - let class_object = activation.domain().get_defined_value(activation, name).unwrap_or_else(|e| panic!("Failed to lookup {name:?}: {e:?}")); - let class_def = class_object.as_object().unwrap().as_class_object().unwrap().inner_class_definition(); + let name = Multiname::new(ns, $class_name); + let class_def = domain.get_class(activation.context, &name).unwrap_or_else(|| panic!("Failed to lookup {name:?}")); let sc = activation.avm2().system_class_defs.as_mut().unwrap(); sc.$field = class_def; )* @@ -848,11 +843,12 @@ pub fn init_builtin_system_classes(activation: &mut Activation<'_, '_>) { avm2_system_classes_playerglobal!( &mut *activation, [ - ("", "Error", error), ("", "ArgumentError", argumenterror), - ("", "QName", qname), + ("", "Boolean", boolean), + ("", "Error", error), ("", "EvalError", evalerror), ("", "Namespace", namespace), + ("", "QName", qname), ("", "RangeError", rangeerror), ("", "ReferenceError", referenceerror), ("", "SecurityError", securityerror), @@ -864,10 +860,13 @@ pub fn init_builtin_system_classes(activation: &mut Activation<'_, '_>) { ("", "XMLList", xml_list), ] ); +} +pub fn init_builtin_system_class_defs(activation: &mut Activation<'_, '_>) { avm2_system_class_defs_playerglobal!( &mut *activation, [ + ("", "Boolean", boolean), ("", "Namespace", namespace), ("", "XML", xml), ("", "XMLList", xml_list), diff --git a/core/src/avm2/globals/Boolean.as b/core/src/avm2/globals/Boolean.as index 360ffe7c1bcd..0d45b42ec67c 100644 --- a/core/src/avm2/globals/Boolean.as +++ b/core/src/avm2/globals/Boolean.as @@ -1,5 +1,51 @@ -// This is a stub - the actual class is defined in `boolean.rs` package { - public class Boolean { - } + [Ruffle(CustomConstructor)] + [Ruffle(CallHandler)] + public final class Boolean { + public function Boolean(value:* = void 0) { + // The Boolean constructor is implemented natively: + // this AS-defined method does nothing + } + + prototype.toString = function():String { + if (this === Boolean.prototype) { + return "false"; + } + + if (!(this is Boolean)) { + throw new TypeError("Error #1004: Method Boolean.prototype.toString was invoked on an incompatible object.", 1004); + } + + return this.AS3::toString(); + }; + + prototype.valueOf = function():* { + if (this === Boolean.prototype) { + return false; + } + + if (!(this is Boolean)) { + throw new TypeError("Error #1004: Method Boolean.prototype.valueOf was invoked on an incompatible object.", 1004); + } + + return this; + }; + + prototype.setPropertyIsEnumerable("toString", false); + prototype.setPropertyIsEnumerable("valueOf", false); + + AS3 function toString():String { + if (this) { + return "true"; + } else { + return "false"; + } + } + + AS3 function valueOf():Boolean { + return this; + } + + public static const length:int = 1; + } } diff --git a/core/src/avm2/globals/Toplevel.as b/core/src/avm2/globals/Toplevel.as index 9787710f062e..db4be5c71e31 100644 --- a/core/src/avm2/globals/Toplevel.as +++ b/core/src/avm2/globals/Toplevel.as @@ -32,6 +32,8 @@ package { include "Error.as" +include "Boolean.as" + include "ArgumentError.as" include "DefinitionError.as" include "EvalError.as" diff --git a/core/src/avm2/globals/XML.as b/core/src/avm2/globals/XML.as index 4c73cba25db0..06876fe03a1f 100644 --- a/core/src/avm2/globals/XML.as +++ b/core/src/avm2/globals/XML.as @@ -103,15 +103,20 @@ package { AS3 native function notification():Function; AS3 native function setNotification(f:Function):*; - public static var ignoreComments:Boolean = true; - public static var ignoreProcessingInstructions:Boolean = true; - public static var ignoreWhitespace:Boolean = true; + public static native function get ignoreComments():Boolean; + public static native function set ignoreComments(value:Boolean):void; - [Ruffle(InternalSlot)] - public static var prettyPrinting:Boolean = true; + public static native function get ignoreProcessingInstructions():Boolean; + public static native function set ignoreProcessingInstructions(value:Boolean):void; - [Ruffle(InternalSlot)] - public static var prettyIndent:int = 2; + public static native function get ignoreWhitespace():Boolean; + public static native function set ignoreWhitespace(value:Boolean):void; + + public static native function get prettyPrinting():Boolean; + public static native function set prettyPrinting(value:Boolean):void; + + public static native function get prettyIndent():int; + public static native function set prettyIndent(value:int):void; prototype.hasComplexContent = function():Boolean { var self:XML = this; diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index f2e23733c5bc..c491c55ce5c9 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -71,8 +71,6 @@ pub fn instance_init<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - activation.super_init(this, &[])?; - if let Some(mut array) = this.as_array_storage_mut(activation.gc()) { if args.len() == 1 { if let Some(expected_len) = args.get(0).filter(|v| v.is_number()).map(|v| v.as_f64()) { @@ -105,12 +103,11 @@ pub fn class_call<'gc>( _this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok(activation + activation .avm2() .classes() .array - .construct(activation, args)? - .into()) + .construct(activation, args) } /// Implements `Array`'s class initializer. @@ -229,6 +226,8 @@ pub fn resolve_array_hole<'gc>( } if let Some(proto) = this.proto() { + let proto = Value::from(proto); + proto.get_public_property( AvmString::new_utf8(activation.gc(), i.to_string()), activation, @@ -307,10 +306,10 @@ pub fn to_locale_string<'gc>( let this = this.as_object().unwrap(); join_inner(act, this, &[",".into()], |v, activation| { - if let Ok(o) = v.coerce_to_object(activation) { - o.call_public_property("toLocaleString", &[], activation) - } else { + if matches!(v, Value::Null | Value::Undefined) { Ok(v) + } else { + v.call_public_property("toLocaleString", &[], activation) } }) } @@ -334,7 +333,7 @@ pub fn to_locale_string<'gc>( /// array while this happens would cause a panic; this code exists to prevent /// that. pub struct ArrayIter<'gc> { - array_object: Object<'gc>, + array_object: Value<'gc>, pub index: u32, pub rev_index: u32, } @@ -355,12 +354,12 @@ impl<'gc> ArrayIter<'gc> { start_index: u32, end_index: u32, ) -> Result> { - let length = array_object + let length = Value::from(array_object) .get_public_property("length", activation)? .coerce_to_u32(activation)?; Ok(Self { - array_object, + array_object: Value::from(array_object), index: start_index.min(length), rev_index: end_index.saturating_add(1).min(length), }) @@ -1300,10 +1299,10 @@ pub fn sort_on<'gc>( // if the object is null/undefined or does not have the field, // it's treated as if the field's value was undefined. // TODO: verify this and fix it - let a_object = a.coerce_to_object(activation)?; + let a_object = a.null_check(activation, None)?; let a_field = a_object.get_public_property(*field_name, activation)?; - let b_object = b.coerce_to_object(activation)?; + let b_object = b.null_check(activation, None)?; let b_field = b_object.get_public_property(*field_name, activation)?; let ord = if options.contains(SortOptions::NUMERIC) { diff --git a/core/src/avm2/globals/boolean.rs b/core/src/avm2/globals/boolean.rs index 43cb8ecbbc77..bc1f78bd598e 100644 --- a/core/src/avm2/globals/boolean.rs +++ b/core/src/avm2/globals/boolean.rs @@ -1,79 +1,20 @@ //! `Boolean` impl use crate::avm2::activation::Activation; -use crate::avm2::class::{Class, ClassAttributes}; -use crate::avm2::error::make_error_1004; -use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::object::{primitive_allocator, FunctionObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::avm2::QName; -/// Implements `Boolean`'s instance initializer. -fn instance_init<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, +pub fn boolean_constructor<'gc>( + _activation: &mut Activation<'_, 'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(mut prim) = this.as_primitive_mut(activation.gc()) { - if matches!(*prim, Value::Undefined | Value::Null) { - *prim = args - .get(0) - .cloned() - .unwrap_or(Value::Bool(false)) - .coerce_to_boolean() - .into(); - } - } - - Ok(Value::Undefined) -} - -/// Implements `Boolean`'s class initializer. -fn class_init<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let scope = activation.create_scopechain(); - let gc_context = activation.gc(); - let this_class = this.as_class_object().unwrap(); - let boolean_proto = this_class.prototype(); - - boolean_proto.set_string_property_local( - "toString", - FunctionObject::from_method( - activation, - Method::from_builtin(to_string, "toString", gc_context), - scope, - None, - None, - None, - ) - .into(), - activation, - )?; - boolean_proto.set_string_property_local( - "valueOf", - FunctionObject::from_method( - activation, - Method::from_builtin(value_of, "valueOf", gc_context), - scope, - None, - None, - None, - ) - .into(), - activation, - )?; - boolean_proto.set_local_property_is_enumerable(gc_context, "toString".into(), false); - boolean_proto.set_local_property_is_enumerable(gc_context, "valueOf".into(), false); + let bool_value = args + .get(0) + .copied() + .unwrap_or(Value::Bool(false)) + .coerce_to_boolean(); - Ok(Value::Undefined) + Ok(bool_value.into()) } pub fn call_handler<'gc>( @@ -88,85 +29,3 @@ pub fn call_handler<'gc>( .coerce_to_boolean() .into()) } - -/// Implements `Boolean.prototype.toString` -fn to_string<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(this) = this.as_primitive() { - match *this { - Value::Bool(true) => return Ok("true".into()), - Value::Bool(false) => return Ok("false".into()), - _ => {} - }; - } - - let boolean_proto = activation.avm2().classes().boolean.prototype(); - if Object::ptr_eq(boolean_proto, this) { - return Ok("false".into()); - } - - Err(make_error_1004(activation, "Boolean.prototype.toString")) -} - -/// Implements `Boolean.valueOf` -fn value_of<'gc>( - _activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(this) = this.as_primitive() { - return Ok(*this); - } - - Ok(false.into()) -} - -/// Construct `Boolean`'s class. -pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { - let mc = activation.gc(); - let namespaces = activation.avm2().namespaces; - - let class = Class::new( - QName::new(namespaces.public_all(), "Boolean"), - Some(activation.avm2().class_defs().object), - Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), - activation.avm2().class_defs().class, - mc, - ); - - class.set_attributes(mc, ClassAttributes::FINAL | ClassAttributes::SEALED); - class.set_instance_allocator(mc, primitive_allocator); - class.set_call_handler( - mc, - Method::from_builtin(call_handler, "", mc), - ); - - const AS3_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = - &[("toString", to_string), ("valueOf", value_of)]; - class.define_builtin_instance_methods(mc, namespaces.as3, AS3_INSTANCE_METHODS); - - const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)]; - class.define_constant_int_class_traits(namespaces.public_all(), CONSTANTS_INT, activation); - - class.mark_traits_loaded(activation.gc()); - class - .init_vtable(activation.context) - .expect("Native class's vtable should initialize"); - - let c_class = class.c_class().expect("Class::new returns an i_class"); - - c_class.mark_traits_loaded(activation.gc()); - c_class - .init_vtable(activation.context) - .expect("Native class's vtable should initialize"); - - class -} diff --git a/core/src/avm2/globals/date.rs b/core/src/avm2/globals/date.rs index d0cb954152d9..e5718a565f9c 100644 --- a/core/src/avm2/globals/date.rs +++ b/core/src/avm2/globals/date.rs @@ -247,24 +247,24 @@ pub fn call_handler<'gc>( _this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok(activation - .avm2() - .classes() - .date - .construct(activation, &[])? - .into()) + activation.avm2().classes().date.construct(activation, &[]) } /// Implements `getTime` method. pub fn get_time<'gc>( - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); let this = this.as_date_object().unwrap(); - this.value_of(activation.strings()) + + if let Some(date) = this.date_time() { + Ok((date.timestamp_millis() as f64).into()) + } else { + Ok(f64::NAN.into()) + } } /// Implements `setTime` method. diff --git a/core/src/avm2/globals/error.rs b/core/src/avm2/globals/error.rs index 442720c77f3e..181590c38d62 100644 --- a/core/src/avm2/globals/error.rs +++ b/core/src/avm2/globals/error.rs @@ -10,12 +10,11 @@ pub fn call_handler<'gc>( _this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok(activation + activation .avm2() .classes() .error - .construct(activation, args)? - .into()) + .construct(activation, args) } pub fn get_stack_trace<'gc>( diff --git a/core/src/avm2/globals/flash/crypto.rs b/core/src/avm2/globals/flash/crypto.rs index 53821b2b16af..2b1bee8d7203 100644 --- a/core/src/avm2/globals/flash/crypto.rs +++ b/core/src/avm2/globals/flash/crypto.rs @@ -19,8 +19,14 @@ pub fn generate_random_bytes<'gc>( return Err(make_error_2004(activation, Error2004Type::Error)); } - let ba_class = activation.context.avm2.classes().bytearray; - let ba = ba_class.construct(activation, &[])?; + let ba = activation + .avm2() + .classes() + .bytearray + .construct(activation, &[])? + .as_object() + .unwrap(); + let mut ba_write = ba.as_bytearray_mut().unwrap(); ba_write.set_length(length as usize); diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index 738fdd19dc12..be72552781fa 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -754,8 +754,8 @@ pub fn get_color_bounds_rect<'gc>( .avm2() .classes() .rectangle - .construct(activation, &[x.into(), y.into(), w.into(), h.into()])? - .into(); + .construct(activation, &[x.into(), y.into(), w.into(), h.into()])?; + return Ok(rect); } } @@ -1144,20 +1144,15 @@ pub fn get_rect<'gc>( let this = this.as_object().unwrap(); if let Some(bitmap_data) = this.as_bitmap_data() { - return Ok(activation - .avm2() - .classes() - .rectangle - .construct( - activation, - &[ - 0.into(), - 0.into(), - bitmap_data.width().into(), - bitmap_data.height().into(), - ], - )? - .into()); + return activation.avm2().classes().rectangle.construct( + activation, + &[ + 0.into(), + 0.into(), + bitmap_data.width().into(), + bitmap_data.height().into(), + ], + ); } Ok(Value::Undefined) } diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index ae63b879c480..adb51d3a0883 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -58,10 +58,10 @@ pub fn display_object_initializer<'gc>( this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - activation.super_init(this, &[])?; + let this = this.as_object().unwrap(); + if let Some(dobj) = this.as_display_object() { if let Some(clip) = dobj.as_movie_clip() { clip.set_constructing_frame(true, activation.gc()); @@ -157,8 +157,7 @@ pub fn get_scale9grid<'gc>( if let Some(dobj) = this.as_display_object() { let rect = dobj.scaling_grid(); return if rect.is_valid() { - let rect = new_rectangle(activation, rect)?; - Ok(rect.into()) + new_rectangle(activation, rect) } else { Ok(Value::Null) }; @@ -812,12 +811,11 @@ pub fn get_transform<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - Ok(activation + activation .avm2() .classes() .transform - .construct(activation, &[this.into()])? - .into()) + .construct(activation, &[this.into()]) } pub fn set_transform<'gc>( @@ -887,7 +885,7 @@ pub fn set_blend_mode<'gc>( fn new_rectangle<'gc>( activation: &mut Activation<'_, 'gc>, rectangle: Rectangle, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let x = rectangle.x_min.to_pixels(); let y = rectangle.y_min.to_pixels(); let width = rectangle.width().to_pixels(); @@ -909,7 +907,7 @@ pub fn get_scroll_rect<'gc>( if let Some(dobj) = this.as_display_object() { if dobj.has_scroll_rect() { - return Ok(new_rectangle(activation, dobj.next_scroll_rect())?.into()); + return new_rectangle(activation, dobj.next_scroll_rect()); } else { return Ok(Value::Null); } @@ -987,15 +985,10 @@ pub fn local_to_global<'gc>( let local = Point::from_pixels(x, y); let global = dobj.local_to_global(local); - return Ok(activation - .avm2() - .classes() - .point - .construct( - activation, - &[global.x.to_pixels().into(), global.y.to_pixels().into()], - )? - .into()); + return activation.avm2().classes().point.construct( + activation, + &[global.x.to_pixels().into(), global.y.to_pixels().into()], + ); } Ok(Value::Undefined) @@ -1019,15 +1012,10 @@ pub fn global_to_local<'gc>( let global = Point::from_pixels(x, y); let local = dobj.global_to_local(global).unwrap_or(global); - return Ok(activation - .avm2() - .classes() - .point - .construct( - activation, - &[local.x.to_pixels().into(), local.y.to_pixels().into()], - )? - .into()); + return activation.avm2().classes().point.construct( + activation, + &[local.x.to_pixels().into(), local.y.to_pixels().into()], + ); } Ok(Value::Undefined) @@ -1062,7 +1050,7 @@ pub fn get_bounds<'gc>( out_bounds = Rectangle::ZERO; } - return Ok(new_rectangle(activation, out_bounds)?.into()); + return new_rectangle(activation, out_bounds); } Ok(Value::Undefined) } diff --git a/core/src/avm2/globals/flash/display/loader.rs b/core/src/avm2/globals/flash/display/loader.rs index 353699d844d4..55a902cdb059 100644 --- a/core/src/avm2/globals/flash/display/loader.rs +++ b/core/src/avm2/globals/flash/display/loader.rs @@ -149,7 +149,7 @@ pub fn request_from_url_request<'gc>( let headers = headers.as_array_storage().unwrap(); for i in 0..headers.length() { - let Some(header) = headers.get(i).and_then(|val| val.as_object()) else { + let Some(header) = headers.get(i) else { continue; }; diff --git a/core/src/avm2/globals/flash/display/loader_info.rs b/core/src/avm2/globals/flash/display/loader_info.rs index fac88bea45ea..86f534e5a815 100644 --- a/core/src/avm2/globals/flash/display/loader_info.rs +++ b/core/src/avm2/globals/flash/display/loader_info.rs @@ -418,13 +418,11 @@ pub fn get_bytes<'gc>( let (root, dobj) = match &*loader_stream { LoaderStream::NotYetLoaded(_, None, _) => { if loader_info.errored() { - return Ok(activation - .context - .avm2 + return activation + .avm2() .classes() .bytearray - .construct(activation, &[])? - .into()); + .construct(activation, &[]); } // If we haven't even started loading yet (we have no root clip), // then return null. FIXME - we should probably store the ByteArray @@ -435,8 +433,13 @@ pub fn get_bytes<'gc>( LoaderStream::Swf(root, dobj) => (root, dobj), }; - let ba_class = activation.context.avm2.classes().bytearray; - let ba = ba_class.construct(activation, &[])?; + let ba = activation + .avm2() + .classes() + .bytearray + .construct(activation, &[])? + .as_object() + .unwrap(); if root.data().is_empty() { return Ok(ba.into()); diff --git a/core/src/avm2/globals/flash/display/movie_clip.rs b/core/src/avm2/globals/flash/display/movie_clip.rs index 796f72539dee..b017920dca7c 100644 --- a/core/src/avm2/globals/flash/display/movie_clip.rs +++ b/core/src/avm2/globals/flash/display/movie_clip.rs @@ -135,7 +135,7 @@ fn labels_for_scene<'gc>( let args = [name, local_frame.into()]; let frame_label = frame_label_class.construct(activation, &args)?; - frame_labels.push(Some(frame_label.into())); + frame_labels.push(Some(frame_label)); } Ok(( @@ -195,7 +195,7 @@ pub fn get_current_scene<'gc>( let scene = scene_class.construct(activation, &args)?; - return Ok(scene.into()); + return Ok(scene); } Ok(Value::Undefined) @@ -271,7 +271,7 @@ pub fn get_scenes<'gc>( let scene = scene_class.construct(activation, &args)?; - scene_objects.push(Some(scene.into())); + scene_objects.push(Some(scene)); } return Ok(ArrayObject::from_storage( diff --git a/core/src/avm2/globals/flash/display/shader_parameter.rs b/core/src/avm2/globals/flash/display/shader_parameter.rs index f5f106d8b23d..7ab1880abf8a 100644 --- a/core/src/avm2/globals/flash/display/shader_parameter.rs +++ b/core/src/avm2/globals/flash/display/shader_parameter.rs @@ -19,37 +19,43 @@ pub fn make_shader_parameter<'gc>( metadata, .. } => { - let obj = activation + let param_value = activation .avm2() .classes() .shaderparameter .construct(activation, &[])?; + + let param_object = param_value.as_object().unwrap(); + let type_name = AvmString::new_utf8(activation.gc(), param_type.to_string()); - obj.set_slot(parameter_slots::_INDEX, index.into(), activation)?; - obj.set_slot(parameter_slots::_TYPE, type_name.into(), activation)?; + param_object.set_slot(parameter_slots::_INDEX, index.into(), activation)?; + param_object.set_slot(parameter_slots::_TYPE, type_name.into(), activation)?; for meta in metadata { let name = AvmString::new_utf8(activation.gc(), &meta.key); let value = meta.value.clone().as_avm2_value(activation, false)?; - obj.set_public_property(name, value, activation)?; + param_value.set_public_property(name, value, activation)?; if &*name == b"defaultValue" { - obj.set_slot(parameter_slots::_VALUE, value, activation)?; + param_object.set_slot(parameter_slots::_VALUE, value, activation)?; } } - obj.set_string_property_local( + param_object.set_string_property_local( "name", AvmString::new_utf8(activation.gc(), name).into(), activation, )?; - Ok(obj.into()) + Ok(param_value) } PixelBenderParam::Texture { name, channels, .. } => { let obj = activation .avm2() .classes() .shaderinput - .construct(activation, &[])?; + .construct(activation, &[])? + .as_object() + .unwrap(); + obj.set_slot(input_slots::_CHANNELS, (*channels).into(), activation)?; obj.set_slot(input_slots::_INDEX, index.into(), activation)?; obj.set_string_property_local( diff --git a/core/src/avm2/globals/flash/display/stage_3d.rs b/core/src/avm2/globals/flash/display/stage_3d.rs index a753de22ae04..0a90eb5d1975 100644 --- a/core/src/avm2/globals/flash/display/stage_3d.rs +++ b/core/src/avm2/globals/flash/display/stage_3d.rs @@ -35,9 +35,9 @@ pub fn request_context3d_internal<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); + let this_object = this.as_object().unwrap(); - let this_stage3d = this.as_stage_3d().unwrap(); + let this_stage3d = this_object.as_stage_3d().unwrap(); let profiles = args.get_object(activation, 1, "profiles")?; let profiles = profiles.as_vector_storage().unwrap(); @@ -68,9 +68,7 @@ pub fn request_context3d_internal<'gc>( .event .construct(activation, &["context3DCreate".into()])?; - // FIXME - fire this at least one frame later, - // since some seems to expect this (e.g. the adobe triangle example) - this.call_public_property("dispatchEvent", &[event.into()], activation)?; + this.call_public_property("dispatchEvent", &[event], activation)?; } Ok(Value::Undefined) diff --git a/core/src/avm2/globals/flash/display3D/context_3d.rs b/core/src/avm2/globals/flash/display3D/context_3d.rs index 43dd4c9e287f..d07c8b295341 100644 --- a/core/src/avm2/globals/flash/display3D/context_3d.rs +++ b/core/src/avm2/globals/flash/display3D/context_3d.rs @@ -320,12 +320,12 @@ pub fn set_program_constants_from_matrix<'gc>( // in column-major order. // See https://github.com/openfl/openfl/blob/971a4c9e43b5472fd84d73920a2b7c1b3d8d9257/src/openfl/display3D/Context3D.hx#L1532-L1550 if user_transposed_matrix { - matrix = matrix + matrix = Value::from(matrix) .call_public_property("clone", &[], activation)? .as_object() .expect("Matrix3D.clone returns Object"); - matrix.call_public_property("transpose", &[], activation)?; + Value::from(matrix).call_public_property("transpose", &[], activation)?; } let matrix_raw_data = matrix diff --git a/core/src/avm2/globals/flash/events/event_dispatcher.rs b/core/src/avm2/globals/flash/events/event_dispatcher.rs index c516689d45ff..d3b2a405188c 100644 --- a/core/src/avm2/globals/flash/events/event_dispatcher.rs +++ b/core/src/avm2/globals/flash/events/event_dispatcher.rs @@ -128,11 +128,11 @@ pub fn dispatch_event<'gc>( let event = args.get_object(activation, 0, "event")?; - if event.as_event().is_none() { - return Err("Dispatched Events must be subclasses of Event.".into()); - } + // AS3-side typing guarantees that the event is actually an Event + let event = event.as_event_object().unwrap(); dispatch_event_internal(activation, this, event, false)?; - let not_canceled = !event.as_event().unwrap().is_cancelled(); + + let not_canceled = !event.event().is_cancelled(); Ok(not_canceled.into()) } diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index 2f8e2a1ebe0c..d8b217f0d7a9 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -188,7 +188,7 @@ pub fn color_transform_to_object<'gc>( ]; let ct_class = activation.avm2().classes().colortransform; let object = ct_class.construct(activation, &args)?; - Ok(object.into()) + Ok(object) } pub fn matrix_to_object<'gc>( @@ -208,7 +208,7 @@ pub fn matrix_to_object<'gc>( .classes() .matrix .construct(activation, &args)?; - Ok(object.into()) + Ok(object) } pub fn object_to_matrix<'gc>( @@ -265,7 +265,7 @@ fn rectangle_to_object<'gc>( rectangle.height().to_pixels().into(), ], )?; - Ok(object.into()) + Ok(object) } pub fn get_matrix_3d<'gc>( @@ -284,7 +284,7 @@ pub fn get_matrix_3d<'gc>( .classes() .matrix3d .construct(activation, &[])?; - Ok(object.into()) + Ok(object) } else { Ok(Value::Null) } @@ -332,7 +332,8 @@ pub fn get_perspective_projection<'gc>( .classes() .perspectiveprojection .construct(activation, &[])?; - Ok(object.into()) + + Ok(object) } else { Ok(Value::Null) } diff --git a/core/src/avm2/globals/flash/media/sound.rs b/core/src/avm2/globals/flash/media/sound.rs index 96ded9c0679d..5c8e7ee1693d 100644 --- a/core/src/avm2/globals/flash/media/sound.rs +++ b/core/src/avm2/globals/flash/media/sound.rs @@ -2,7 +2,7 @@ use crate::avm2::activation::Activation; use crate::avm2::globals::slots::flash_net_url_request as url_request_slots; -use crate::avm2::object::{QueuedPlay, SoundChannelObject, TObject}; +use crate::avm2::object::{EventObject, QueuedPlay, SoundChannelObject, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Avm2; @@ -10,7 +10,6 @@ use crate::avm2::Error; use crate::backend::navigator::Request; use crate::character::Character; use crate::display_object::SoundTransform; -use crate::string::AvmString; use crate::{avm2_stub_getter, avm2_stub_method}; use swf::{SoundEvent, SoundInfo}; @@ -22,10 +21,8 @@ pub fn init<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(sound_object) = this.as_sound_object() { - let class_def = this.instance_class(); + if let Some(sound_object) = this.as_object().and_then(|o| o.as_sound_object()) { + let class_def = this.instance_class(activation); if let Some((movie, symbol)) = activation .context @@ -286,21 +283,8 @@ pub fn load_compressed_data_from_byte_array<'gc>( Error::RustError(format!("Failed to register sound from bytearray: {e:?}").into()) })?; - let progress_evt = activation - .avm2() - .classes() - .progressevent - .construct( - activation, - &[ - "progress".into(), - false.into(), - false.into(), - bytes.len().into(), - bytes.len().into(), - ], - ) - .map_err(|e| Error::AvmError(AvmString::new_utf8(activation.gc(), e.to_string()).into()))?; + let progress_evt = + EventObject::progress_event(activation, "progress", bytes.len(), bytes.len()); Avm2::dispatch_event(activation.context, progress_evt, this); diff --git a/core/src/avm2/globals/flash/net.rs b/core/src/avm2/globals/flash/net.rs index 75c1be895007..e4c5bb5bb045 100644 --- a/core/src/avm2/globals/flash/net.rs +++ b/core/src/avm2/globals/flash/net.rs @@ -30,7 +30,7 @@ fn object_to_index_map<'gc>( .get_enumerant_name(last_index, activation)? .coerce_to_string(activation)?; let value = obj - .get_public_property(name, activation)? + .get_enumerant_value(last_index, activation)? .coerce_to_string(activation)? .to_utf8_lossy() .to_string(); diff --git a/core/src/avm2/globals/flash/net/file_reference.rs b/core/src/avm2/globals/flash/net/file_reference.rs index 49ff0c3c3bba..db2380a8b65e 100644 --- a/core/src/avm2/globals/flash/net/file_reference.rs +++ b/core/src/avm2/globals/flash/net/file_reference.rs @@ -222,17 +222,18 @@ pub fn load<'gc>( FileReference::FileDialogResult(ref dialog_result) => dialog_result.size().unwrap_or(0), }; + let size = size as usize; + let open_evt = EventObject::bare_default_event(activation.context, "open"); Avm2::dispatch_event(activation.context, open_evt, this.into()); - let progress_evt = EventObject::progress_event(activation, "progress", 0, size, false, false); + let progress_evt = EventObject::progress_event(activation, "progress", 0, size); Avm2::dispatch_event(activation.context, progress_evt, this.into()); let open_evt2 = EventObject::bare_default_event(activation.context, "open"); Avm2::dispatch_event(activation.context, open_evt2, this.into()); - let progress_evt2 = - EventObject::progress_event(activation, "progress", size, size, false, false); + let progress_evt2 = EventObject::progress_event(activation, "progress", size, size); Avm2::dispatch_event(activation.context, progress_evt2, this.into()); this.set_loaded(true); diff --git a/core/src/avm2/globals/flash/net/shared_object.rs b/core/src/avm2/globals/flash/net/shared_object.rs index 47328648bf90..af65cc5b4f42 100644 --- a/core/src/avm2/globals/flash/net/shared_object.rs +++ b/core/src/avm2/globals/flash/net/shared_object.rs @@ -1,8 +1,8 @@ //! `flash.net.SharedObject` builtin/prototype use crate::avm2::error::error; -use crate::avm2::object::TObject; -pub use crate::avm2::object::{shared_object_allocator, SharedObjectObject}; +pub use crate::avm2::object::shared_object_allocator; +use crate::avm2::object::{ScriptObject, SharedObjectObject, TObject}; use crate::avm2::{Activation, Error, Object, Value}; use crate::{avm2_stub_getter, avm2_stub_method, avm2_stub_setter}; use flash_lso::types::{AMFVersion, Lso}; @@ -159,11 +159,7 @@ pub fn get_local<'gc>( data } else { // No data; create a fresh data object. - activation - .avm2() - .classes() - .object - .construct(activation, &[])? + ScriptObject::new_object(activation) }; let created_shared_object = diff --git a/core/src/avm2/globals/flash/text/engine/text_block.rs b/core/src/avm2/globals/flash/text/engine/text_block.rs index 9fd206c6088c..450e6f0b2b3b 100644 --- a/core/src/avm2/globals/flash/text/engine/text_block.rs +++ b/core/src/avm2/globals/flash/text/engine/text_block.rs @@ -31,7 +31,7 @@ pub fn create_text_line<'gc>( let content = if matches!(content, Value::Null) { return Ok(Value::Null); } else { - content.as_object().unwrap() + content }; let text = match previous_text_line { @@ -76,6 +76,7 @@ pub fn create_text_line<'gc>( // of the provided text, and set the width of the EditText to that. // Some games depend on this (e.g. Realm Grinder). + let content = content.as_object().unwrap(); let element_format = content.get_slot(element_slots::_ELEMENT_FORMAT).as_object(); apply_format(activation, display_object, text.as_wstr(), element_format)?; diff --git a/core/src/avm2/globals/flash/text/text_field.rs b/core/src/avm2/globals/flash/text/text_field.rs index 33e392f3220b..11fe905d81ea 100644 --- a/core/src/avm2/globals/flash/text/text_field.rs +++ b/core/src/avm2/globals/flash/text/text_field.rs @@ -1245,19 +1245,17 @@ pub fn get_line_metrics<'gc>( }; let metrics_class = activation.avm2().classes().textlinemetrics; - Ok(metrics_class - .construct( - activation, - &[ - metrics.x.to_pixels().into(), - metrics.width.to_pixels().into(), - metrics.height.to_pixels().into(), - metrics.ascent.to_pixels().into(), - metrics.descent.to_pixels().into(), - metrics.leading.to_pixels().into(), - ], - )? - .into()) + metrics_class.construct( + activation, + &[ + metrics.x.to_pixels().into(), + metrics.width.to_pixels().into(), + metrics.height.to_pixels().into(), + metrics.ascent.to_pixels().into(), + metrics.descent.to_pixels().into(), + metrics.leading.to_pixels().into(), + ], + ) } pub fn get_line_length<'gc>( @@ -1788,19 +1786,15 @@ pub fn get_char_boundaries<'gc>( return Ok(Value::Null); } - let rect = activation - .avm2() - .classes() - .rectangle - .construct( - activation, - &[ - bounds.x_min.to_pixels().into(), - bounds.y_min.to_pixels().into(), - bounds.width().to_pixels().into(), - bounds.height().to_pixels().into(), - ], - )? - .into(); + let rect = activation.avm2().classes().rectangle.construct( + activation, + &[ + bounds.x_min.to_pixels().into(), + bounds.y_min.to_pixels().into(), + bounds.width().to_pixels().into(), + bounds.height().to_pixels().into(), + ], + )?; + Ok(rect) } diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index 39467608462c..7a0c5eb2cef1 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -13,11 +13,9 @@ use crate::avm2::QName; /// Implements `Function`'s instance initializer. pub fn instance_init<'gc>( activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, + _this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - if !args.is_empty() { return Err(Error::AvmError(eval_error( activation, @@ -26,8 +24,6 @@ pub fn instance_init<'gc>( )?)); } - activation.super_init(this, &[])?; - Ok(Value::Undefined) } @@ -36,12 +32,11 @@ pub fn class_call<'gc>( _this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok(activation + activation .avm2() .classes() .function - .construct(activation, args)? - .into()) + .construct(activation, args) } /// Implements `Function`'s class initializer. diff --git a/core/src/avm2/globals/int.rs b/core/src/avm2/globals/int.rs index 8204639f9fdf..671dbf9ebfb0 100644 --- a/core/src/avm2/globals/int.rs +++ b/core/src/avm2/globals/int.rs @@ -5,30 +5,32 @@ use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::error::{make_error_1003, make_error_1004}; use crate::avm2::globals::number::print_with_radix; use crate::avm2::method::{Method, NativeMethodImpl, ParamConfig}; -use crate::avm2::object::{primitive_allocator, FunctionObject, Object, TObject}; +use crate::avm2::object::{FunctionObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::{AvmString, Error, QName}; /// Implements `int`'s instance initializer. +/// +/// Because of the presence of a custom constructor, this method is unreachable. fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + unreachable!() +} + +fn int_constructor<'gc>( activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(mut prim) = this.as_primitive_mut(activation.gc()) { - if matches!(*prim, Value::Undefined | Value::Null) { - *prim = args - .get(0) - .cloned() - .unwrap_or(Value::Undefined) - .coerce_to_i32(activation)? - .into(); - } - } + let int_value = args + .get(0) + .copied() + .unwrap_or(Value::Integer(0)) + .coerce_to_i32(activation)?; - Ok(Value::Undefined) + Ok(int_value.into()) } /// Implements `int`'s class initializer. @@ -161,20 +163,16 @@ fn to_string<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let int_proto = activation.avm2().classes().int.prototype(); - if Object::ptr_eq(int_proto, this) { - return Ok("0".into()); + if let Some(this) = this.as_object() { + let int_proto = activation.avm2().classes().int.prototype(); + if Object::ptr_eq(int_proto, this) { + return Ok("0".into()); + } } - let number = if let Some(this) = this.as_primitive() { - match *this { - Value::Integer(o) => o, - _ => return Err(make_error_1004(activation, "int.prototype.toString")), - } - } else { - return Err(make_error_1004(activation, "int.prototype.toString")); + let number = match this { + Value::Integer(o) => o, + _ => return Err(make_error_1004(activation, "int.prototype.toString")), }; let radix = args @@ -196,22 +194,16 @@ fn value_of<'gc>( this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let int_proto = activation.avm2().classes().int.prototype(); - if Object::ptr_eq(int_proto, this) { - return Ok(0.into()); + if let Some(this) = this.as_object() { + let int_proto = activation.avm2().classes().int.prototype(); + if Object::ptr_eq(int_proto, this) { + return Ok(0.into()); + } } - let primitive = this.as_primitive(); - - if let Some(this) = primitive { - match *this { - Value::Integer(_) => Ok(*this), - _ => Err(make_error_1004(activation, "int.prototype.valueOf")), - } - } else { - Err(make_error_1004(activation, "int.prototype.valueOf")) + match this { + Value::Integer(_) => Ok(this), + _ => Err(make_error_1004(activation, "int.prototype.valueOf")), } } @@ -241,7 +233,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { ); class.set_attributes(mc, ClassAttributes::FINAL | ClassAttributes::SEALED); - class.set_instance_allocator(mc, primitive_allocator); + class.set_custom_constructor(mc, int_constructor); class.set_call_handler( mc, Method::from_builtin(call_handler, "", mc), diff --git a/core/src/avm2/globals/json.rs b/core/src/avm2/globals/json.rs index 57b417df9f14..0e4a527cc6ab 100644 --- a/core/src/avm2/globals/json.rs +++ b/core/src/avm2/globals/json.rs @@ -117,12 +117,12 @@ impl<'gc> AvmSerializer<'gc> { key: impl Fn() -> AvmString<'gc>, value: Value<'gc>, ) -> Result, Error<'gc>> { - let (eval_key, value) = if let Some(obj) = value.as_object() { - if obj.has_public_property("toJSON", activation) { + let (eval_key, value) = if value.as_object().is_some() { + if value.has_public_property("toJSON", activation) { let key = key(); ( Some(key), - obj.call_public_property("toJSON", &[key.into()], activation)?, + value.call_public_property("toJSON", &[key.into()], activation)?, ) } else { (None, value) @@ -154,7 +154,7 @@ impl<'gc> AvmSerializer<'gc> { while let Some(r) = iter.next(activation) { let item = r?.1; let key = item.coerce_to_string(activation)?; - let value = obj.get_public_property(key, activation)?; + let value = Value::from(obj).get_public_property(key, activation)?; let mapped = self.map_value(activation, || key, value)?; if !matches!(mapped, Value::Undefined) { js_obj.insert( @@ -178,7 +178,7 @@ impl<'gc> AvmSerializer<'gc> { Value::Null => break, name_val => { let name = name_val.coerce_to_string(activation)?; - let value = obj.get_public_property(name, activation)?; + let value = obj.get_enumerant_value(i, activation)?; let mapped = self.map_value(activation, || name, value)?; if !matches!(mapped, Value::Undefined) { js_obj.insert( @@ -225,10 +225,6 @@ impl<'gc> AvmSerializer<'gc> { Value::Bool(b) => JsonValue::from(b), Value::String(s) => JsonValue::from(s.to_utf8_lossy().deref()), Value::Object(obj) => { - // special case for boxed primitives - if let Some(prim) = obj.as_primitive() { - return self.serialize_value(activation, *prim); - } if self.obj_stack.contains(&obj) { return Err(Error::AvmError(type_error( activation, @@ -271,7 +267,8 @@ pub fn parse<'gc>( ) -> Result, Error<'gc>> { let input = args.get_string(activation, 0)?; let reviver = args - .try_get_object(activation, 1) + .get_value(1) + .as_object() .map(|o| o.as_function_object().unwrap()); let parsed = if let Ok(parsed) = serde_json::from_str(&input.to_utf8_lossy()) { @@ -294,11 +291,11 @@ pub fn stringify<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { let val = args.get_value(0); - let replacer = args.try_get_object(activation, 1); + let replacer = args.get_value(1).as_object(); let spaces = args.get_value(2); // If the replacer is None, that means it was either undefined or null. - if replacer.is_none() && !matches!(args.get(1).unwrap(), Value::Null) { + if replacer.is_none() && !matches!(args.get_value(1), Value::Null) { return Err(Error::AvmError(type_error( activation, "Error #1131: Replacer argument to JSON stringifier must be an array or a two parameter function.", diff --git a/core/src/avm2/globals/namespace.rs b/core/src/avm2/globals/namespace.rs index 59ec3915b508..b1f982c4d80f 100644 --- a/core/src/avm2/globals/namespace.rs +++ b/core/src/avm2/globals/namespace.rs @@ -12,7 +12,7 @@ use crate::avm2::Namespace; pub fn namespace_constructor<'gc>( activation: &mut Activation<'_, 'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let api_version = activation.avm2().root_api_version; let namespaces = activation.avm2().namespaces; @@ -80,12 +80,11 @@ pub fn call_handler<'gc>( _this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok(activation + activation .avm2() .classes() .namespace - .construct(activation, args)? - .into()) + .construct(activation, args) } /// Implements `Namespace.prefix`'s getter diff --git a/core/src/avm2/globals/number.rs b/core/src/avm2/globals/number.rs index 9a19ce543219..b58c93fd7df2 100644 --- a/core/src/avm2/globals/number.rs +++ b/core/src/avm2/globals/number.rs @@ -4,31 +4,33 @@ use crate::avm2::activation::Activation; use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::error::{make_error_1002, make_error_1003, make_error_1004}; use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::object::{primitive_allocator, FunctionObject, Object, TObject}; +use crate::avm2::object::{FunctionObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::QName; use crate::avm2::{AvmString, Error}; /// Implements `Number`'s instance initializer. +/// +/// Because of the presence of a custom constructor, this method is unreachable. fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + unreachable!() +} + +fn number_constructor<'gc>( activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(mut prim) = this.as_primitive_mut(activation.gc()) { - if matches!(*prim, Value::Undefined | Value::Null) { - *prim = args - .get(0) - .cloned() - .unwrap_or(Value::Number(0.0)) - .coerce_to_number(activation)? - .into(); - } - } + let number_value = args + .get(0) + .copied() + .unwrap_or(Value::Integer(0)) + .coerce_to_number(activation)?; - Ok(Value::Undefined) + Ok(number_value.into()) } /// Implements `Number`'s class initializer. @@ -304,21 +306,17 @@ fn to_string<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let number_proto = activation.avm2().classes().number.prototype(); - if Object::ptr_eq(number_proto, this) { - return Ok("0".into()); + if let Some(this) = this.as_object() { + let number_proto = activation.avm2().classes().number.prototype(); + if Object::ptr_eq(number_proto, this) { + return Ok("0".into()); + } } - let number = if let Some(this) = this.as_primitive() { - match *this { - Value::Integer(o) => o as f64, - Value::Number(o) => o, - _ => return Err(make_error_1004(activation, "Number.prototype.toString")), - } - } else { - return Err(make_error_1004(activation, "Number.prototype.toString")); + let number = match this { + Value::Integer(o) => o as f64, + Value::Number(o) => o, + _ => return Err(make_error_1004(activation, "Number.prototype.toString")), }; let radix = args @@ -340,23 +338,17 @@ fn value_of<'gc>( this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let number_proto = activation.avm2().classes().number.prototype(); - if Object::ptr_eq(number_proto, this) { - return Ok(0.into()); + if let Some(this) = this.as_object() { + let number_proto = activation.avm2().classes().number.prototype(); + if Object::ptr_eq(number_proto, this) { + return Ok(0.into()); + } } - let primitive = this.as_primitive(); - - if let Some(this) = primitive { - match *this { - Value::Integer(_) => Ok(*this), - Value::Number(_) => Ok(*this), - _ => Err(make_error_1004(activation, "Number.prototype.valueOf")), - } - } else { - Err(make_error_1004(activation, "Number.prototype.valueOf")) + match this { + Value::Integer(_) => Ok(this), + Value::Number(_) => Ok(this), + _ => Err(make_error_1004(activation, "Number.prototype.valueOf")), } } @@ -375,7 +367,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { ); class.set_attributes(mc, ClassAttributes::FINAL | ClassAttributes::SEALED); - class.set_instance_allocator(mc, primitive_allocator); + class.set_custom_constructor(mc, number_constructor); class.set_call_handler( mc, Method::from_builtin(call_handler, "", mc), diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 7e02248a7b6b..e97f82c1bdf6 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -2,12 +2,13 @@ use crate::avm2::activation::Activation; use crate::avm2::class::{Class, ClassAttributes}; +use crate::avm2::error; use crate::avm2::method::{Method, NativeMethodImpl, ParamConfig}; use crate::avm2::object::{FunctionObject, Object, TObject}; use crate::avm2::traits::Trait; use crate::avm2::value::Value; -use crate::avm2::Error; -use crate::avm2::QName; +use crate::avm2::{Error, Multiname, QName}; +use crate::string::AvmString; /// Implements `Object`'s instance initializer. pub fn instance_init<'gc>( @@ -26,11 +27,11 @@ fn class_call<'gc>( let object_class = activation.avm2().classes().object; if args.is_empty() { - return object_class.construct(activation, args).map(|o| o.into()); + return object_class.construct(activation, args); } let arg = args.get(0).cloned().unwrap(); if matches!(arg, Value::Undefined) || matches!(arg, Value::Null) { - return object_class.construct(activation, args).map(|o| o.into()); + return object_class.construct(activation, args); } Ok(arg) } @@ -121,7 +122,7 @@ pub fn class_init<'gc>( "toLocaleString", FunctionObject::from_method( activation, - Method::from_builtin(to_locale_string, "toLocaleString", gc_context), + Method::from_builtin(to_string, "toLocaleString", gc_context), scope, None, None, @@ -165,31 +166,22 @@ fn to_string<'gc>( this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - this.to_string(activation) -} - -/// Implements `Object.prototype.toLocaleString` -fn to_locale_string<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); + if let Some(this) = this.as_object() { + this.to_string(activation) + } else { + let class_name = this.instance_class(activation).name().local_name(); - this.to_locale_string(activation) + Ok(AvmString::new_utf8(activation.gc(), format!("[object {class_name}]")).into()) + } } /// Implements `Object.prototype.valueOf` fn value_of<'gc>( - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - this.value_of(activation.strings()) + Ok(this) } /// `Object.prototype.hasOwnProperty` @@ -198,13 +190,16 @@ pub fn has_own_property<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); + let name = args.get(0).expect("No name specified"); + let name = name.coerce_to_string(activation)?; - let name: Result<&Value<'gc>, Error<'gc>> = - args.get(0).ok_or_else(|| "No name specified".into()); - let name = name?.coerce_to_string(activation)?; + if let Some(this) = this.as_object() { + Ok(this.has_own_property_string(name, activation)?.into()) + } else { + let name = Multiname::new(activation.avm2().find_public_namespace(), name); - Ok(this.has_own_property_string(name, activation)?.into()) + Ok(this.has_trait(activation, &name).into()) + } } /// `Object.prototype.isPrototypeOf` @@ -213,16 +208,16 @@ pub fn is_prototype_of<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); + if let Some(this) = this.as_object() { + let mut target_proto = args.get(0).cloned().unwrap_or(Value::Undefined); - let mut target_proto = args.get(0).cloned().unwrap_or(Value::Undefined); + while let Value::Object(proto) = target_proto { + if Object::ptr_eq(this, proto) { + return Ok(true.into()); + } - while let Value::Object(proto) = target_proto { - if Object::ptr_eq(this, proto) { - return Ok(true.into()); + target_proto = proto.proto().map(|o| o.into()).unwrap_or(Value::Undefined); } - - target_proto = proto.proto().map(|o| o.into()).unwrap_or(Value::Undefined); } Ok(false.into()) @@ -234,13 +229,14 @@ pub fn property_is_enumerable<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let name: Result<&Value<'gc>, Error<'gc>> = - args.get(0).ok_or_else(|| "No name specified".into()); - let name = name?.coerce_to_string(activation)?; + if let Some(this) = this.as_object() { + let name = args.get(0).expect("No name specified"); + let name = name.coerce_to_string(activation)?; - Ok(this.property_is_enumerable(name).into()) + Ok(this.property_is_enumerable(name).into()) + } else { + Ok(false.into()) + } } /// `Object.prototype.setPropertyIsEnumerable` @@ -249,14 +245,23 @@ pub fn set_property_is_enumerable<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); + let name = args.get(0).expect("No name specified"); + let name = name.coerce_to_string(activation)?; - let name: Result<&Value<'gc>, Error<'gc>> = - args.get(0).ok_or_else(|| "No name specified".into()); - let name = name?.coerce_to_string(activation)?; + if let Some(this) = this.as_object() { + if let Some(Value::Bool(is_enum)) = args.get(1) { + this.set_local_property_is_enumerable(activation.gc(), name, *is_enum); + } + } else { + let instance_class = this.instance_class(activation); + let multiname = Multiname::new(activation.avm2().find_public_namespace(), name); - if let Some(Value::Bool(is_enum)) = args.get(1) { - this.set_local_property_is_enumerable(activation.gc(), name, *is_enum); + return Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidWrite, + &multiname, + instance_class, + )); } Ok(Value::Undefined) diff --git a/core/src/avm2/globals/q_name.rs b/core/src/avm2/globals/q_name.rs index d018b6012ab9..c8eb21771114 100644 --- a/core/src/avm2/globals/q_name.rs +++ b/core/src/avm2/globals/q_name.rs @@ -26,19 +26,18 @@ pub fn call_handler<'gc>( // 2. Create and return a new QName object exactly as if the QName constructor had been called with the // same arguments (section 13.3.2). - Ok(activation + activation .avm2() .classes() .qname - .construct(activation, args)? - .into()) + .construct(activation, args) } /// Implements a custom constructor for `QName`. pub fn q_name_constructor<'gc>( activation: &mut Activation<'_, 'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this = QNameObject::new_empty(activation); let namespace = if args.len() >= 2 { diff --git a/core/src/avm2/globals/reg_exp.rs b/core/src/avm2/globals/reg_exp.rs index fa5ecbafd85c..fc18181f6403 100644 --- a/core/src/avm2/globals/reg_exp.rs +++ b/core/src/avm2/globals/reg_exp.rs @@ -76,7 +76,7 @@ pub fn call_handler<'gc>( return Ok(arg); } } - this_class.construct(activation, args).map(|o| o.into()) + this_class.construct(activation, args) } /// Implements `RegExp.dotall` diff --git a/core/src/avm2/globals/string.rs b/core/src/avm2/globals/string.rs index a4646f712391..41b88e2df2c8 100644 --- a/core/src/avm2/globals/string.rs +++ b/core/src/avm2/globals/string.rs @@ -4,7 +4,7 @@ use crate::avm2::activation::Activation; use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::error::make_error_1004; use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::object::{primitive_allocator, FunctionObject, Object, TObject}; +use crate::avm2::object::{FunctionObject, Object, TObject}; use crate::avm2::regexp::{RegExp, RegExpFlags}; use crate::avm2::value::Value; use crate::avm2::Error; @@ -37,25 +37,26 @@ const PUBLIC_INSTANCE_AND_PROTO_METHODS: &[(&str, NativeMethodImpl)] = &[ ]; /// Implements `String`'s instance initializer. -pub fn instance_init<'gc>( +/// +/// Because of the presence of a custom constructor, this method is unreachable. +fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + unreachable!() +} + +fn string_constructor<'gc>( activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - activation.super_init(this, &[])?; - - if let Some(mut value) = this.as_primitive_mut(activation.gc()) { - if !matches!(*value, Value::String(_)) { - *value = match args.get(0) { - Some(arg) => arg.coerce_to_string(activation)?.into(), - None => activation.strings().empty().into(), - } - } - } + let string_value = match args.get(0) { + Some(arg) => arg.coerce_to_string(activation)?, + None => activation.strings().empty(), + }; - Ok(Value::Undefined) + Ok(string_value.into()) } /// Implements `String`'s class initializer. @@ -103,13 +104,11 @@ pub fn call_handler<'gc>( /// Implements `length` property's getter fn length<'gc>( - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Value::String(s) = this.value_of(activation.strings())? { + if let Value::String(s) = this { return Ok(s.len().into()); } @@ -122,9 +121,7 @@ fn char_at<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Value::String(s) = this.value_of(activation.strings())? { + if let Value::String(s) = this { // This function takes Number, so if we use coerce_to_i32 instead of coerce_to_number, the value may overflow. let n = args .get(0) @@ -152,9 +149,7 @@ fn char_code_at<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Value::String(s) = this.value_of(activation.strings())? { + if let Value::String(s) = this { // This function takes Number, so if we use coerce_to_i32 instead of coerce_to_number, the value may overflow. let n = args .get(0) @@ -297,15 +292,14 @@ fn match_s<'gc>( let pattern = args.get(0).unwrap_or(&Value::Undefined); let regexp_class = activation.avm2().classes().regexp; - let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) { + let pattern = if pattern.is_of_type(activation, regexp_class.inner_class_definition()) { + *pattern + } else { let string = pattern.coerce_to_string(activation)?; regexp_class.construct(activation, &[Value::String(string)])? - } else { - pattern - .as_object() - .expect("Regexp objects must be Value::Object") }; + let pattern = pattern.as_object().unwrap(); if let Some(mut regexp) = pattern.as_regexp_mut(activation.gc()) { let mut storage = ArrayStorage::new(0); if regexp.flags().contains(RegExpFlags::GLOBAL) { @@ -406,15 +400,14 @@ fn search<'gc>( let pattern = args.get(0).unwrap_or(&Value::Undefined); let regexp_class = activation.avm2().classes().regexp; - let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) { + let pattern = if pattern.is_of_type(activation, regexp_class.inner_class_definition()) { + *pattern + } else { let string = pattern.coerce_to_string(activation)?; regexp_class.construct(activation, &[Value::String(string)])? - } else { - pattern - .as_object() - .expect("Regexp objects must be Value::Object") }; + let pattern = pattern.as_object().unwrap(); if let Some(mut regexp) = pattern.as_regexp_mut(activation.gc()) { let old = regexp.last_index(); regexp.set_last_index(0); @@ -623,17 +616,15 @@ fn to_string<'gc>( this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(this) = this.as_primitive() { - if let Value::String(v) = *this { - return Ok(v.into()); - } + if let Value::String(v) = this { + return Ok(v.into()); } - let string_proto = activation.avm2().classes().string.prototype(); - if Object::ptr_eq(string_proto, this) { - return Ok(activation.strings().empty().into()); + if let Some(this) = this.as_object() { + let string_proto = activation.avm2().classes().string.prototype(); + if Object::ptr_eq(string_proto, this) { + return Ok(activation.strings().empty().into()); + } } Err(make_error_1004(activation, "String.prototype.toString")) @@ -641,13 +632,11 @@ fn to_string<'gc>( /// Implements `String.valueOf` fn value_of<'gc>( - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - this.value_of(activation.strings()) + Ok(this) } /// Implements `String.toUpperCase` @@ -673,13 +662,10 @@ fn is_dependent<'gc>( this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(prim) = this.as_primitive() { - if let Value::String(s) = *prim { - return Ok(s.is_dependent().into()); - } + if let Value::String(s) = this { + return Ok(s.is_dependent().into()); } + panic!(); } @@ -698,7 +684,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { ); class.set_attributes(mc, ClassAttributes::FINAL | ClassAttributes::SEALED); - class.set_instance_allocator(mc, primitive_allocator); + class.set_custom_constructor(mc, string_constructor); class.set_call_handler( mc, Method::from_builtin(call_handler, "", mc), diff --git a/core/src/avm2/globals/stubs.as b/core/src/avm2/globals/stubs.as index ba8aec6991f4..b9444b7b3ffc 100644 --- a/core/src/avm2/globals/stubs.as +++ b/core/src/avm2/globals/stubs.as @@ -6,7 +6,6 @@ include "Object.as" // List is ordered alphabetically, except where superclasses // are listed before subclasses include "Array.as" -include "Boolean.as" include "Class.as" include "Function.as" diff --git a/core/src/avm2/globals/uint.rs b/core/src/avm2/globals/uint.rs index 37c7c8dadf71..f641fea79dfb 100644 --- a/core/src/avm2/globals/uint.rs +++ b/core/src/avm2/globals/uint.rs @@ -5,30 +5,32 @@ use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::error::{make_error_1003, make_error_1004}; use crate::avm2::globals::number::print_with_radix; use crate::avm2::method::{Method, NativeMethodImpl, ParamConfig}; -use crate::avm2::object::{primitive_allocator, FunctionObject, Object, TObject}; +use crate::avm2::object::{FunctionObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::{AvmString, Error, QName}; /// Implements `uint`'s instance initializer. +/// +/// Because of the presence of a custom constructor, this method is unreachable. fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + unreachable!() +} + +fn uint_constructor<'gc>( activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - if let Some(mut prim) = this.as_primitive_mut(activation.gc()) { - if matches!(*prim, Value::Undefined | Value::Null) { - *prim = args - .get(0) - .cloned() - .unwrap_or(Value::Undefined) - .coerce_to_u32(activation)? - .into(); - } - } + let uint_value = args + .get(0) + .copied() + .unwrap_or(Value::Integer(0)) + .coerce_to_u32(activation)?; - Ok(Value::Undefined) + Ok(uint_value.into()) } /// Implements `uint`'s class initializer. @@ -161,21 +163,17 @@ fn to_string<'gc>( this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let uint_proto = activation.avm2().classes().uint.prototype(); - if Object::ptr_eq(uint_proto, this) { - return Ok("0".into()); + if let Some(this) = this.as_object() { + let uint_proto = activation.avm2().classes().uint.prototype(); + if Object::ptr_eq(uint_proto, this) { + return Ok("0".into()); + } } - let number = if let Some(this) = this.as_primitive() { - match *this { - Value::Integer(o) => o as f64, - Value::Number(o) => o, - _ => return Err(make_error_1004(activation, "uint.prototype.toString")), - } - } else { - return Err(make_error_1004(activation, "uint.prototype.toString")); + let number = match this { + Value::Integer(o) => o as f64, + Value::Number(o) => o, + _ => return Err(make_error_1004(activation, "uint.prototype.toString")), }; let radix = args @@ -197,22 +195,16 @@ fn value_of<'gc>( this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let uint_proto = activation.avm2().classes().uint.prototype(); - if Object::ptr_eq(uint_proto, this) { - return Ok(0.into()); + if let Some(this) = this.as_object() { + let uint_proto = activation.avm2().classes().uint.prototype(); + if Object::ptr_eq(uint_proto, this) { + return Ok(0.into()); + } } - let primitive = this.as_primitive(); - - if let Some(this) = primitive { - match *this { - Value::Integer(_) => Ok(*this), - _ => Err(make_error_1004(activation, "uint.prototype.valueOf")), - } - } else { - Err(make_error_1004(activation, "uint.prototype.valueOf")) + match this { + Value::Integer(_) => Ok(this), + _ => Err(make_error_1004(activation, "uint.prototype.valueOf")), } } @@ -242,7 +234,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { ); class.set_attributes(mc, ClassAttributes::FINAL | ClassAttributes::SEALED); - class.set_instance_allocator(mc, primitive_allocator); + class.set_custom_constructor(mc, uint_constructor); class.set_call_handler( mc, Method::from_builtin(call_handler, "", mc), diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 37b3bce13ae2..5aaec97c6d99 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -37,8 +37,6 @@ pub fn instance_init<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - activation.super_init(this, &[])?; - if let Some(mut vector) = this.as_vector_storage_mut(activation.gc()) { let length = args .get(0) @@ -83,16 +81,17 @@ fn class_call<'gc>( .expect("Cannot convert to unparametrized Vector"); // technically unreachable let arg = args.get(0).cloned().unwrap(); - let arg = arg.as_object().ok_or("Cannot convert to Vector")?; - if arg.instance_class() == this_class { - return Ok(arg.into()); + if arg.instance_class(activation) == this_class { + return Ok(arg); } let length = arg .get_public_property("length", activation)? .coerce_to_i32(activation)?; + let arg = arg.as_object().ok_or("Cannot convert to Vector")?; + let mut new_storage = VectorStorage::new(0, false, value_type, activation); new_storage.reserve_exact(length as usize); @@ -110,13 +109,11 @@ fn class_call<'gc>( } pub fn generic_init<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - args: &[Value<'gc>], + _activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - activation.super_init(this, args) + Ok(Value::Undefined) } fn class_init<'gc>( @@ -263,7 +260,7 @@ pub fn concat<'gc>( let val_class = new_vector_storage.value_type_for_coercion(activation); for arg in args { - let arg_obj = arg.coerce_to_object_or_typeerror(activation, None)?; + let arg = arg.null_check(activation, None)?; // this is Vector. let my_base_vector_class = activation @@ -275,20 +272,24 @@ pub fn concat<'gc>( .name() .to_qualified_name_err_message(activation.gc()); + let instance_of_class_name = arg.instance_of_class_name(activation); + return Err(Error::AvmError(type_error( activation, &format!( "Error #1034: Type Coercion failed: cannot convert {}@00000000000 to {}.", - arg_obj.instance_of_class_name(activation.gc()), - base_vector_name, + instance_of_class_name, base_vector_name, ), 1034, )?)); } - let old_vec = arg_obj.as_vector_storage(); - let old_vec: Vec> = if let Some(old_vec) = old_vec { - old_vec.iter().collect() + let old_vec: Vec> = if let Some(old_vec) = arg.as_object() { + if let Some(old_vec) = old_vec.as_vector_storage() { + old_vec.iter().collect() + } else { + continue; + } } else { continue; }; @@ -376,11 +377,11 @@ pub fn to_locale_string<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - join_inner(activation, this, &[",".into()], |v, act| { - if let Ok(o) = v.coerce_to_object(act) { - o.call_public_property("toLocaleString", &[], act) - } else { + join_inner(activation, this, &[",".into()], |v, activation| { + if matches!(v, Value::Null | Value::Undefined) { Ok(v) + } else { + v.call_public_property("toLocaleString", &[], activation) } }) } diff --git a/core/src/avm2/globals/xml.rs b/core/src/avm2/globals/xml.rs index d9f636ac7170..c7ad44cbb0e8 100644 --- a/core/src/avm2/globals/xml.rs +++ b/core/src/avm2/globals/xml.rs @@ -102,6 +102,106 @@ pub fn init<'gc>( Ok(Value::Undefined) } +pub fn get_ignore_comments<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + Ok(Value::Bool(activation.avm2().xml_settings.ignore_comments)) +} + +pub fn set_ignore_comments<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + activation.avm2().xml_settings.ignore_comments = args.get_bool(0); + + Ok(Value::Undefined) +} + +pub fn get_ignore_processing_instructions<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + Ok(Value::Bool( + activation + .avm2() + .xml_settings + .ignore_processing_instructions, + )) +} + +pub fn set_ignore_processing_instructions<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + activation + .avm2() + .xml_settings + .ignore_processing_instructions = args.get_bool(0); + + Ok(Value::Undefined) +} + +pub fn get_ignore_whitespace<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + Ok(Value::Bool( + activation.avm2().xml_settings.ignore_whitespace, + )) +} + +pub fn set_ignore_whitespace<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + activation.avm2().xml_settings.ignore_whitespace = args.get_bool(0); + + Ok(Value::Undefined) +} + +pub fn get_pretty_printing<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + Ok(Value::Bool(activation.avm2().xml_settings.pretty_printing)) +} + +pub fn set_pretty_printing<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + activation.avm2().xml_settings.pretty_printing = args.get_bool(0); + + Ok(Value::Undefined) +} + +pub fn get_pretty_indent<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + Ok(Value::Integer(activation.avm2().xml_settings.pretty_indent)) +} + +pub fn set_pretty_indent<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Value<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + activation.avm2().xml_settings.pretty_indent = args.get_i32(activation, 0)?; + + Ok(Value::Undefined) +} + pub fn normalize<'gc>( activation: &mut Activation<'_, 'gc>, this: Value<'gc>, @@ -164,6 +264,8 @@ pub fn set_name<'gc>( .classes() .qname .construct(activation, &[name])? + .as_object() + .unwrap() .as_qname_object() .unwrap(); @@ -276,6 +378,8 @@ pub fn add_namespace<'gc>( .classes() .namespace .construct(activation, &[value])? + .as_object() + .unwrap() .as_namespace_object() .unwrap(); @@ -321,8 +425,11 @@ pub fn set_namespace<'gc>( .classes() .namespace .construct(activation, &[value])? + .as_object() + .unwrap() .as_namespace_object() .unwrap(); + let ns = E4XNamespace { prefix: ns.prefix(), uri: ns.namespace().as_uri(activation.strings()), @@ -372,6 +479,8 @@ pub fn remove_namespace<'gc>( .classes() .namespace .construct(activation, &[value])? + .as_object() + .unwrap() .as_namespace_object() .unwrap(); let ns = E4XNamespace { @@ -725,7 +834,7 @@ pub fn call_handler<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if args.len() == 1 { - if let Some(obj) = args.try_get_object(activation, 0) { + if let Some(obj) = args.get_value(0).as_object() { // We do *not* create a new object when AS does 'XML(someXML)' if let Some(xml) = obj.as_xml_object() { return Ok(xml.into()); @@ -742,12 +851,7 @@ pub fn call_handler<'gc>( } } - Ok(activation - .avm2() - .classes() - .xml - .construct(activation, args)? - .into()) + activation.avm2().classes().xml.construct(activation, args) } pub fn node_kind<'gc>( @@ -1124,7 +1228,6 @@ pub fn replace<'gc>( .classes() .xml .construct(activation, &[value])? - .into() } else { value } diff --git a/core/src/avm2/globals/xml_list.rs b/core/src/avm2/globals/xml_list.rs index d0b1ed0841ba..1fc47c6b0cad 100644 --- a/core/src/avm2/globals/xml_list.rs +++ b/core/src/avm2/globals/xml_list.rs @@ -81,19 +81,18 @@ pub fn call_handler<'gc>( ) -> Result, Error<'gc>> { if args.len() == 1 { // We do *not* create a new object when AS does 'XMLList(someXMLList)' - if let Some(obj) = args.try_get_object(activation, 0) { + if let Some(obj) = args.get_value(0).as_object() { if let Some(xml_list) = obj.as_xml_list_object() { return Ok(xml_list.into()); } } } - Ok(activation + activation .avm2() .classes() .xml_list - .construct(activation, args)? - .into()) + .construct(activation, args) } // ECMA-357 13.5.4.11 XMLList.prototype.elements ([name]) @@ -593,9 +592,9 @@ macro_rules! define_xml_proxy { let mut children = list.children_mut(activation.gc()); match &mut children[..] { [child] => { - child - .get_or_create_xml(activation) - .call_property(&Multiname::new(namespaces.as3, $as_name), args, activation) + let child = child.get_or_create_xml(activation); + + Value::from(child).call_property(&Multiname::new(namespaces.as3, $as_name), args, activation) } _ => Err(make_error_1086(activation, $as_name)), } @@ -644,7 +643,7 @@ pub fn namespace_internal_impl<'gc>( }; match &mut children[..] { - [child] => child.get_or_create_xml(activation).call_property( + [child] => Value::from(child.get_or_create_xml(activation)).call_property( &Multiname::new(namespaces.as3, "namespace"), args, activation, diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 54bf5b3ac0cc..a24359794b03 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -7,12 +7,12 @@ use crate::avm2::class::Class; use crate::avm2::domain::Domain; use crate::avm2::error; use crate::avm2::events::{DispatchList, Event}; -use crate::avm2::function::{exec, BoundMethod}; +use crate::avm2::function::BoundMethod; use crate::avm2::property::Property; use crate::avm2::regexp::RegExp; use crate::avm2::value::{Hint, Value}; use crate::avm2::vector::VectorStorage; -use crate::avm2::vtable::{ClassBoundMethod, VTable}; +use crate::avm2::vtable::VTable; use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; @@ -20,7 +20,7 @@ use crate::bitmap::bitmap_data::BitmapDataWrapper; use crate::display_object::DisplayObject; use crate::html::TextFormat; use crate::streams::NetStream; -use crate::string::{AvmString, StringContext}; +use crate::string::AvmString; use gc_arena::{Collect, Gc, GcWeak, Mutation}; use ruffle_macros::enum_trait_object; use std::cell::{Ref, RefMut}; @@ -47,7 +47,6 @@ mod local_connection_object; mod namespace_object; mod net_connection_object; mod netstream_object; -mod primitive_object; mod program_3d_object; mod proxy_object; mod qname_object; @@ -111,9 +110,6 @@ pub use crate::avm2::object::net_connection_object::{ pub use crate::avm2::object::netstream_object::{ netstream_allocator, NetStreamObject, NetStreamObjectWeak, }; -pub use crate::avm2::object::primitive_object::{ - primitive_allocator, PrimitiveObject, PrimitiveObjectWeak, -}; pub use crate::avm2::object::program_3d_object::{Program3DObject, Program3DObjectWeak}; pub use crate::avm2::object::proxy_object::{proxy_allocator, ProxyObject, ProxyObjectWeak}; pub use crate::avm2::object::qname_object::{QNameObject, QNameObjectWeak}; @@ -122,7 +118,8 @@ pub use crate::avm2::object::responder_object::{ responder_allocator, ResponderObject, ResponderObjectWeak, }; pub use crate::avm2::object::script_object::{ - scriptobject_allocator, ScriptObject, ScriptObjectData, ScriptObjectWeak, ScriptObjectWrapper, + maybe_int_property, scriptobject_allocator, ScriptObject, ScriptObjectData, ScriptObjectWeak, + ScriptObjectWrapper, }; pub use crate::avm2::object::shader_data_object::{ shader_data_allocator, ShaderDataObject, ShaderDataObjectWeak, @@ -167,7 +164,6 @@ use crate::font::Font; pub enum Object<'gc> { ScriptObject(ScriptObject<'gc>), FunctionObject(FunctionObject<'gc>), - PrimitiveObject(PrimitiveObject<'gc>), NamespaceObject(NamespaceObject<'gc>), ArrayObject(ArrayObject<'gc>), StageObject(StageObject<'gc>), @@ -245,72 +241,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy self.get_property_local(&name, activation) } - /// Retrieve a property by Multiname lookup. - /// - /// This method should not be overridden. - /// - /// This corresponds directly to the AVM2 operation `getproperty`, with the - /// exception that it does not special-case object lookups on dictionary - /// structured objects. - #[allow(unused_mut)] //Not unused. - #[no_dynamic] - fn get_property( - mut self, - multiname: &Multiname<'gc>, - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - match self.vtable().get_trait(multiname) { - Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => { - Ok(self.base().get_slot(slot_id)) - } - Some(Property::Method { disp_id }) => { - // avmplus has a special case for XML and XMLList objects, so we need one as well - // https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/Toplevel.cpp#L629-L634 - if (self.as_xml_object().is_some() || self.as_xml_list_object().is_some()) - && multiname.contains_public_namespace() - { - return self.get_property_local(multiname, activation); - } - - if let Some(bound_method) = self.get_bound_method(disp_id) { - return Ok(bound_method.into()); - } - - let bound_method = self - .vtable() - .make_bound_method(activation, self.into(), disp_id) - .ok_or_else(|| format!("Method not found with id {disp_id}"))?; - - self.install_bound_method(activation.gc(), disp_id, bound_method); - - Ok(bound_method.into()) - } - Some(Property::Virtual { get: Some(get), .. }) => { - self.call_method(get, &[], activation) - } - Some(Property::Virtual { get: None, .. }) => Err(error::make_reference_error( - activation, - error::ReferenceErrorCode::ReadFromWriteOnly, - multiname, - self.instance_class(), - )), - None => self.get_property_local(multiname, activation), - } - } - - /// Same as get_property, but constructs a public Multiname for you. - #[no_dynamic] - fn get_public_property( - self, - name: impl Into>, - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - self.get_property( - &Multiname::new(activation.avm2().find_public_namespace(), name), - activation, - ) - } - /// Purely an optimization for "array-like" access. This should return /// `None` when the lookup needs to be forwarded to the base or throw. fn get_index_property(self, _index: usize) -> Option> { @@ -347,72 +277,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy self.set_property_local(&name, value, activation) } - /// Set a property by Multiname lookup. - /// - /// This method should not be overridden. - /// - /// This corresponds directly with the AVM2 operation `setproperty`, with - /// the exception that it does not special-case object lookups on - /// dictionary structured objects. - #[no_dynamic] - fn set_property( - &self, - multiname: &Multiname<'gc>, - value: Value<'gc>, - activation: &mut Activation<'_, 'gc>, - ) -> Result<(), Error<'gc>> { - match self.vtable().get_trait(multiname) { - Some(Property::Slot { slot_id }) => { - let value = self - .vtable() - .coerce_trait_value(slot_id, value, activation)?; - - self.base().set_slot(slot_id, value, activation.gc()); - - Ok(()) - } - Some(Property::Method { .. }) => { - // Similar to the get_property special case for XML/XMLList. - if (self.as_xml_object().is_some() || self.as_xml_list_object().is_some()) - && multiname.contains_public_namespace() - { - return self.set_property_local(multiname, value, activation); - } - - Err(error::make_reference_error( - activation, - error::ReferenceErrorCode::AssignToMethod, - multiname, - self.instance_class(), - )) - } - Some(Property::Virtual { set: Some(set), .. }) => { - self.call_method(set, &[value], activation).map(|_| ()) - } - Some(Property::ConstSlot { .. }) | Some(Property::Virtual { set: None, .. }) => { - Err(error::make_reference_error( - activation, - error::ReferenceErrorCode::WriteToReadOnly, - multiname, - self.instance_class(), - )) - } - None => self.set_property_local(multiname, value, activation), - } - } - - /// Same as set_property, but constructs a public Multiname for you. - #[no_dynamic] - fn set_public_property( - &self, - name: impl Into>, - value: Value<'gc>, - activation: &mut Activation<'_, 'gc>, - ) -> Result<(), Error<'gc>> { - let name = Multiname::new(activation.avm2().namespaces.public_vm_internal(), name); - self.set_property(&name, value, activation) - } - /// Init a local property of the object. The Multiname should always be public. /// /// This skips class field lookups and looks at: @@ -431,47 +295,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy base.init_property_local(name, value, activation) } - /// Initialize a property by Multiname lookup. - /// - /// This method should not be overridden. - /// - /// This corresponds directly with the AVM2 operation `initproperty`. - #[no_dynamic] - fn init_property( - &self, - multiname: &Multiname<'gc>, - value: Value<'gc>, - activation: &mut Activation<'_, 'gc>, - ) -> Result<(), Error<'gc>> { - match self.vtable().get_trait(multiname) { - Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => { - let value = self - .vtable() - .coerce_trait_value(slot_id, value, activation)?; - - self.base().set_slot(slot_id, value, activation.gc()); - - Ok(()) - } - Some(Property::Method { .. }) => Err(error::make_reference_error( - activation, - error::ReferenceErrorCode::AssignToMethod, - multiname, - self.instance_class(), - )), - Some(Property::Virtual { set: Some(set), .. }) => { - self.call_method(set, &[value], activation).map(|_| ()) - } - Some(Property::Virtual { set: None, .. }) => Err(error::make_reference_error( - activation, - error::ReferenceErrorCode::WriteToReadOnly, - multiname, - self.instance_class(), - )), - None => self.init_property_local(multiname, value, activation), - } - } - /// Call a local property of the object. The Multiname should always be public. /// /// This skips class field lookups and looks at: @@ -493,56 +316,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy result.call(activation, self_val, arguments) } - /// Call a named property on the object. - /// - /// This method should not be overridden. - /// - /// This corresponds directly to the `callproperty` operation in AVM2. - #[allow(unused_mut)] - #[no_dynamic] - fn call_property( - mut self, - multiname: &Multiname<'gc>, - arguments: &[Value<'gc>], - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - match self.vtable().get_trait(multiname) { - Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => { - let obj = self.base().get_slot(slot_id); - - obj.call(activation, Value::from(self.into()), arguments) - } - Some(Property::Method { disp_id }) => self.call_method(disp_id, arguments, activation), - Some(Property::Virtual { get: Some(get), .. }) => { - let obj = self.call_method(get, &[], activation)?; - - obj.call(activation, Value::from(self.into()), arguments) - } - Some(Property::Virtual { get: None, .. }) => Err(error::make_reference_error( - activation, - error::ReferenceErrorCode::ReadFromWriteOnly, - multiname, - self.instance_class(), - )), - None => self.call_property_local(multiname, arguments, activation), - } - } - - /// Same as call_property, but constructs a public Multiname for you. - #[no_dynamic] - fn call_public_property( - self, - name: impl Into>, - arguments: &[Value<'gc>], - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - self.call_property( - &Multiname::new(activation.avm2().find_public_namespace(), name), - arguments, - activation, - ) - } - /// Retrieve a slot by its index. #[no_dynamic] #[inline(always)] @@ -575,53 +348,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy base.set_slot(id, value, mc); } - /// Call a method by its index. - /// - /// This directly corresponds with the AVM2 operation `callmethod`. - #[no_dynamic] - fn call_method( - self, - id: u32, - arguments: &[Value<'gc>], - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - if let Some(bound_method) = self.get_bound_method(id) { - return bound_method.call(activation, Value::from(self.into()), arguments); - } - - let full_method = self - .vtable() - .get_full_method(id) - .ok_or_else(|| format!("Cannot call unknown method id {id}"))?; - - // Execute immediately if this method doesn't require binding - if !full_method.method.needs_arguments_object() { - let ClassBoundMethod { - class, - super_class_obj, - scope, - method, - } = full_method; - - return exec( - method, - scope.expect("Scope should exist here"), - self.into(), - super_class_obj, - Some(class), - arguments, - activation, - self.into(), // Callee deliberately invalid. - ); - } - - let bound_method = VTable::bind_method(activation, self.into(), full_method); - - self.install_bound_method(activation.gc(), id, bound_method); - - bound_method.call(activation, Value::from(self.into()), arguments) - } - /// Implements the `in` opcode and AS3 operator. /// /// By default, this just calls `has_property`, but may be overridden by @@ -646,19 +372,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } } - /// Same as has_property, but constructs a public Multiname for you. - #[no_dynamic] - fn has_public_property( - self, - name: impl Into>, - activation: &mut Activation<'_, 'gc>, - ) -> bool { - self.has_property(&Multiname::new( - activation.avm2().find_public_namespace(), - name, - )) - } - /// Indicates whether or not a property or trait exists on an object and is /// not part of the prototype chain. fn has_own_property(self, name: &Multiname<'gc>) -> bool { @@ -680,8 +393,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Returns true if an object has one or more traits of a given name. #[no_dynamic] fn has_trait(self, name: &Multiname<'gc>) -> bool { - let base = self.base(); - base.has_trait(name) + self.vtable().has_trait(name) } /// Delete a property by QName, after multiname resolution and all other @@ -710,44 +422,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy self.delete_property_local(activation, &name) } - /// Delete a named property from the object. - /// - /// Returns false if the property cannot be deleted. - #[no_dynamic] - fn delete_property( - &self, - activation: &mut Activation<'_, 'gc>, - multiname: &Multiname<'gc>, - ) -> Result> { - if self.as_primitive().is_some() { - return Err(error::make_reference_error( - activation, - error::ReferenceErrorCode::InvalidDelete, - multiname, - self.instance_class(), - )); - } - - match self.vtable().get_trait(multiname) { - None => { - if self.instance_class().is_sealed() { - Ok(false) - } else { - self.delete_property_local(activation, multiname) - } - } - _ => { - // Similar to the get_property special case for XML/XMLList. - if (self.as_xml_object().is_some() || self.as_xml_list_object().is_some()) - && multiname.contains_public_namespace() - { - return self.delete_property_local(activation, multiname); - } - Ok(false) - } - } - } - /// Retrieve the `__proto__` of a given object. /// /// The proto is another object used to resolve methods across a class of @@ -821,8 +495,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy let name = self .get_enumerant_name(index, activation)? .coerce_to_string(activation)?; + // todo: this probably doesn't need non-public accesses - self.get_public_property(name, activation) + Value::from(self.into()).get_public_property(name, activation) } /// Determine if a property is currently enumerable. @@ -859,21 +534,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy base.install_bound_method(mc, disp_id, function) } - /// Construct a property of this object by Multiname lookup. - /// - /// This corresponds directly to the AVM2 operation `constructprop`. - #[no_dynamic] - fn construct_prop( - self, - multiname: &Multiname<'gc>, - args: &[Value<'gc>], - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - let ctor = self.get_property(multiname, activation)?; - - ctor.construct(activation, args) - } - /// Construct a parameterization of this particular type and return it. /// /// This is called specifically to parameterize generic types, of which @@ -921,35 +581,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(AvmString::new_utf8(activation.gc(), format!("[object {class_name}]")).into()) } - /// Implement the result of calling `Object.prototype.toLocaleString` on this - /// object class. - /// - /// `toLocaleString` is a method used to request an object be coerced to a - /// locale-dependent string value. The default implementation appears to - /// generate a debug-style string based on the name of the class this - /// object is, in the format of `[object Class]` (where `Class` is the name - /// of the class that created this object). - fn to_locale_string( - &self, - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - let class_name = self.instance_class().name().local_name(); - - Ok(AvmString::new_utf8(activation.gc(), format!("[object {class_name}]")).into()) - } - - /// Implement the result of calling `Object.prototype.valueOf` on this - /// object class. - /// - /// `valueOf` is a method used to request an object be coerced to a - /// primitive value. Typically, this would be a number of some kind. - /// - /// The default implementation wraps the object in a `Value`, using the - /// `Into>` implementation. - fn value_of(&self, _context: &mut StringContext<'gc>) -> Result, Error<'gc>> { - Ok(Value::Object((*self).into())) - } - /// Returns all public properties from this object's vtable, together with their values. /// This includes normal fields, const fields, and getter methods /// This is used for JSON serialization. @@ -967,9 +598,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Property::Slot { slot_id } | Property::ConstSlot { slot_id } => { values.push((name, self.base().get_slot(slot_id))); } - Property::Virtual { get: Some(get), .. } => { - values.push((name, self.call_method(get, &[], activation)?)) - } + Property::Virtual { get: Some(get), .. } => values.push(( + name, + Value::from((*self).into()).call_method(get, &[], activation)?, + )), _ => {} } } @@ -1110,6 +742,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy None } + /// Unwrap this object as an event. + fn as_event_object(self) -> Option> { + None + } + /// Unwrap this object as an event. fn as_event(&self) -> Option>> { None @@ -1130,20 +767,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy None } - /// Unwrap this object as an immutable primitive value. - /// - /// This function should not be called in cases where a normal `Value` - /// coercion would do. It *only* accounts for boxed primitives, and not - /// `valueOf`. - fn as_primitive(&self) -> Option>> { - None - } - - /// Unwrap this object as a mutable primitive value. - fn as_primitive_mut(&self, _mc: &Mutation<'gc>) -> Option>> { - None - } - /// Unwrap this object as a regexp. fn as_regexp_object(&self) -> Option> { None @@ -1300,7 +923,6 @@ impl<'gc> Object<'gc> { match self { Self::ScriptObject(o) => WeakObject::ScriptObject(ScriptObjectWeak(Gc::downgrade(o.0))), Self::FunctionObject(o) => WeakObject::FunctionObject(FunctionObjectWeak(Gc::downgrade(o.0))), - Self::PrimitiveObject(o) => WeakObject::PrimitiveObject(PrimitiveObjectWeak(Gc::downgrade(o.0))), Self::NamespaceObject(o) => WeakObject::NamespaceObject(NamespaceObjectWeak(Gc::downgrade(o.0))), Self::ArrayObject(o) => WeakObject::ArrayObject(ArrayObjectWeak(Gc::downgrade(o.0))), Self::StageObject(o) => WeakObject::StageObject(StageObjectWeak(Gc::downgrade(o.0))), @@ -1363,7 +985,6 @@ impl Hash for Object<'_> { pub enum WeakObject<'gc> { ScriptObject(ScriptObjectWeak<'gc>), FunctionObject(FunctionObjectWeak<'gc>), - PrimitiveObject(PrimitiveObjectWeak<'gc>), NamespaceObject(NamespaceObjectWeak<'gc>), ArrayObject(ArrayObjectWeak<'gc>), StageObject(StageObjectWeak<'gc>), @@ -1409,7 +1030,6 @@ impl<'gc> WeakObject<'gc> { match self { Self::ScriptObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, Self::FunctionObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, - Self::PrimitiveObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, Self::NamespaceObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, Self::ArrayObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, Self::StageObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, @@ -1455,7 +1075,6 @@ impl<'gc> WeakObject<'gc> { Some(match self { Self::ScriptObject(o) => ScriptObject(o.0.upgrade(mc)?).into(), Self::FunctionObject(o) => FunctionObject(o.0.upgrade(mc)?).into(), - Self::PrimitiveObject(o) => PrimitiveObject(o.0.upgrade(mc)?).into(), Self::NamespaceObject(o) => NamespaceObject(o.0.upgrade(mc)?).into(), Self::ArrayObject(o) => ArrayObject(o.0.upgrade(mc)?).into(), Self::StageObject(o) => StageObject(o.0.upgrade(mc)?).into(), diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index 096026493f5a..6f9d37bd6c08 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -7,7 +7,7 @@ use crate::avm2::function::exec; use crate::avm2::method::Method; use crate::avm2::object::function_object::FunctionObject; use crate::avm2::object::script_object::ScriptObjectData; -use crate::avm2::object::{Object, ObjectPtr, TObject}; +use crate::avm2::object::{Object, ObjectPtr, ScriptObject, TObject}; use crate::avm2::property::Property; use crate::avm2::scope::{Scope, ScopeChain}; use crate::avm2::value::Value; @@ -90,11 +90,7 @@ impl<'gc> ClassObject<'gc> { activation: &mut Activation<'_, 'gc>, superclass_object: Option>, ) -> Result, Error<'gc>> { - let proto = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let proto = ScriptObject::new_object(activation); if let Some(superclass_object) = superclass_object { let base_proto = superclass_object.prototype(); @@ -323,7 +319,7 @@ impl<'gc> ClassObject<'gc> { self, activation: &mut Activation<'_, 'gc>, ) -> Result<(), Error<'gc>> { - let object: Object<'gc> = self.into(); + let self_value: Value<'gc> = self.into(); let class_classobject = activation.avm2().classes().class; let scope = self.0.class_scope; @@ -337,17 +333,19 @@ impl<'gc> ClassObject<'gc> { activation, class_initializer, scope, - Some(object), + Some(self_value), Some(class_classobject), Some(c_class), ); - class_init_fn.call(activation, object.into(), &[])?; + class_init_fn.call(activation, self_value, &[])?; Ok(()) } /// Call the instance initializer. + /// + /// This method may panic if called with a Null or Undefined receiver. pub fn call_init( self, receiver: Value<'gc>, @@ -359,7 +357,7 @@ impl<'gc> ClassObject<'gc> { exec( method, scope, - receiver.coerce_to_object(activation)?, + receiver, self.superclass_object(), Some(self.inner_class_definition()), arguments, @@ -429,14 +427,14 @@ impl<'gc> ClassObject<'gc> { activation, method, scope.expect("Scope should exist here"), - Some(receiver), + Some(receiver.into()), super_class_obj, Some(class), ); callee.call(activation, receiver.into(), arguments) } else { - receiver.call_property(multiname, arguments, activation) + Value::from(receiver).call_property(multiname, arguments, activation) } } @@ -490,7 +488,7 @@ impl<'gc> ClassObject<'gc> { activation, method, scope.expect("Scope should exist here"), - Some(receiver), + Some(receiver.into()), super_class_obj, Some(class), ); @@ -508,7 +506,7 @@ impl<'gc> ClassObject<'gc> { ) .into()), Some(Property::Slot { .. } | Property::ConstSlot { .. }) => { - receiver.get_property(multiname, activation) + Value::from(receiver).get_property(multiname, activation) } None => Err(format!( "Attempted to supercall method {:?}, which does not exist", @@ -571,13 +569,13 @@ impl<'gc> ClassObject<'gc> { method, } = self.instance_vtable().get_full_method(disp_id).unwrap(); let callee = - FunctionObject::from_method(activation, method, scope.expect("Scope should exist here"), Some(receiver), super_class_obj, Some(class)); + FunctionObject::from_method(activation, method, scope.expect("Scope should exist here"), Some(receiver.into()), super_class_obj, Some(class)); callee.call(activation, receiver.into(), &[value])?; Ok(()) } Some(Property::Slot { .. }) => { - receiver.set_property(multiname, value, activation)?; + Value::from(receiver).set_property(multiname, value, activation)?; Ok(()) } _ => { @@ -669,7 +667,7 @@ impl<'gc> ClassObject<'gc> { self, activation: &mut Activation<'_, 'gc>, arguments: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { if let Some(custom_constructor) = self.custom_constructor() { custom_constructor(activation, arguments) } else { @@ -679,7 +677,7 @@ impl<'gc> ClassObject<'gc> { self.call_init(instance.into(), arguments, activation)?; - Ok(instance) + Ok(instance.into()) } } @@ -771,13 +769,6 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { .into()) } - fn to_locale_string( - &self, - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - self.to_string(activation) - } - fn as_class_object(&self) -> Option> { Some(*self) } diff --git a/core/src/avm2/object/date_object.rs b/core/src/avm2/object/date_object.rs index 8b7d22095b05..6baad8c99582 100644 --- a/core/src/avm2/object/date_object.rs +++ b/core/src/avm2/object/date_object.rs @@ -1,9 +1,8 @@ use crate::avm2::activation::Activation; use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; -use crate::avm2::value::{Hint, Value}; +use crate::avm2::value::Hint; use crate::avm2::Error; -use crate::string::StringContext; use chrono::{DateTime, Utc}; use core::fmt; use gc_arena::{Collect, Gc, GcWeak}; @@ -97,14 +96,6 @@ impl<'gc> TObject<'gc> for DateObject<'gc> { Gc::as_ptr(self.0) as *const ObjectPtr } - fn value_of(&self, _context: &mut StringContext<'gc>) -> Result, Error<'gc>> { - if let Some(date) = self.date_time() { - Ok((date.timestamp_millis() as f64).into()) - } else { - Ok(f64::NAN.into()) - } - } - fn default_hint(&self) -> Hint { Hint::String } diff --git a/core/src/avm2/object/event_object.rs b/core/src/avm2/object/event_object.rs index 386b6178f842..18da68d9cb2b 100644 --- a/core/src/avm2/object/event_object.rs +++ b/core/src/avm2/object/event_object.rs @@ -3,7 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::events::Event; use crate::avm2::object::script_object::ScriptObjectData; -use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; +use crate::avm2::object::{ClassObject, Object, ObjectPtr, ScriptObject, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; @@ -61,7 +61,10 @@ impl<'gc> EventObject<'gc> { /// It's just slightly faster and doesn't require an Activation. /// This is equivalent to /// classes.event.construct(activation, &[event_type, false, false]) - pub fn bare_default_event(context: &mut UpdateContext<'gc>, event_type: S) -> Object<'gc> + pub fn bare_default_event( + context: &mut UpdateContext<'gc>, + event_type: S, + ) -> EventObject<'gc> where S: Into>, { @@ -76,7 +79,7 @@ impl<'gc> EventObject<'gc> { event_type: S, bubbles: bool, cancelable: bool, - ) -> Object<'gc> + ) -> EventObject<'gc> where S: Into>, { @@ -87,15 +90,30 @@ impl<'gc> EventObject<'gc> { event.set_bubbles(bubbles); event.set_cancelable(cancelable); - let event_object = EventObject(Gc::new( + EventObject(Gc::new( context.gc(), EventObjectData { base, event: RefLock::new(event), }, - )); + )) + } - event_object.into() + #[inline] + pub fn from_class_and_args( + activation: &mut Activation<'_, 'gc>, + class: ClassObject<'gc>, + args: &[Value<'gc>], + ) -> EventObject<'gc> { + // We don't expect Event classes to error in their constructors or to + // return anything other than an EventObject + class + .construct(activation, args) + .unwrap() + .as_object() + .unwrap() + .as_event_object() + .unwrap() } pub fn mouse_event( @@ -106,7 +124,7 @@ impl<'gc> EventObject<'gc> { delta: i32, bubbles: bool, button: MouseButton, - ) -> Object<'gc> + ) -> EventObject<'gc> where S: Into>, { @@ -115,47 +133,46 @@ impl<'gc> EventObject<'gc> { let event_type: AvmString<'gc> = event_type.into(); let mouse_event_cls = activation.avm2().classes().mouseevent; - mouse_event_cls - .construct( - activation, - &[ - event_type.into(), - // bubbles - bubbles.into(), - // cancellable - false.into(), - // localX - local.x.to_pixels().into(), - // localY - local.y.to_pixels().into(), - // relatedObject - related_object - .map(|o| o.as_displayobject().object2()) - .unwrap_or(Value::Null), - // ctrlKey - activation - .context - .input - .is_key_down(KeyCode::CONTROL) - .into(), - // altKey - activation.context.input.is_key_down(KeyCode::ALT).into(), - // shiftKey - activation.context.input.is_key_down(KeyCode::SHIFT).into(), - // buttonDown - activation.context.input.is_key_down(button.into()).into(), - // delta - delta.into(), - ], - ) - .unwrap() // we don't expect to break here + Self::from_class_and_args( + activation, + mouse_event_cls, + &[ + event_type.into(), + // bubbles + bubbles.into(), + // cancellable + false.into(), + // localX + local.x.to_pixels().into(), + // localY + local.y.to_pixels().into(), + // relatedObject + related_object + .map(|o| o.as_displayobject().object2()) + .unwrap_or(Value::Null), + // ctrlKey + activation + .context + .input + .is_key_down(KeyCode::CONTROL) + .into(), + // altKey + activation.context.input.is_key_down(KeyCode::ALT).into(), + // shiftKey + activation.context.input.is_key_down(KeyCode::SHIFT).into(), + // buttonDown + activation.context.input.is_key_down(button.into()).into(), + // delta + delta.into(), + ], + ) } pub fn mouse_event_down( activation: &mut Activation<'_, 'gc>, target: DisplayObject<'gc>, button: MouseButton, - ) -> Object<'gc> { + ) -> EventObject<'gc> { Self::mouse_event( activation, match button { @@ -176,7 +193,7 @@ impl<'gc> EventObject<'gc> { activation: &mut Activation<'_, 'gc>, target: DisplayObject<'gc>, button: MouseButton, - ) -> Object<'gc> { + ) -> EventObject<'gc> { Self::mouse_event( activation, match button { @@ -197,7 +214,7 @@ impl<'gc> EventObject<'gc> { activation: &mut Activation<'_, 'gc>, target: DisplayObject<'gc>, button: MouseButton, - ) -> Object<'gc> { + ) -> EventObject<'gc> { Self::mouse_event( activation, match button { @@ -220,97 +237,81 @@ impl<'gc> EventObject<'gc> { text: AvmString<'gc>, bubbles: bool, cancelable: bool, - ) -> Object<'gc> + ) -> EventObject<'gc> where S: Into>, { let event_type: AvmString<'gc> = event_type.into(); let text_event_cls = activation.avm2().classes().textevent; - text_event_cls - .construct( - activation, - &[ - event_type.into(), - // bubbles - bubbles.into(), - // cancelable - cancelable.into(), - // text - text.into(), - ], - ) - .unwrap() // we don't expect to break here + Self::from_class_and_args( + activation, + text_event_cls, + &[ + event_type.into(), + // bubbles + bubbles.into(), + // cancelable + cancelable.into(), + // text + text.into(), + ], + ) } - pub fn net_status_event( + pub fn net_status_event( activation: &mut Activation<'_, 'gc>, - event_type: S, info: Vec<(impl Into>, impl Into>)>, - ) -> Object<'gc> - where - S: Into>, - { - let info_object = activation - .avm2() - .classes() - .object - .construct(activation, &[]) - .unwrap(); + ) -> EventObject<'gc> { + let info_object = ScriptObject::new_object(activation); for (key, value) in info { info_object .set_string_property_local(key.into(), Value::String(value.into()), activation) .unwrap(); } - let event_type: AvmString<'gc> = event_type.into(); - let net_status_cls = activation.avm2().classes().netstatusevent; - net_status_cls - .construct( - activation, - &[ - event_type.into(), - //bubbles - false.into(), - //cancelable - false.into(), - info_object.into(), - ], - ) - .unwrap() // we don't expect to break here + Self::from_class_and_args( + activation, + net_status_cls, + &[ + "netStatus".into(), + //bubbles + false.into(), + //cancelable + false.into(), + info_object.into(), + ], + ) } pub fn progress_event( activation: &mut Activation<'_, 'gc>, event_type: S, - bytes_loaded: u64, - bytes_total: u64, - bubbles: bool, - cancelable: bool, - ) -> Object<'gc> + bytes_loaded: usize, + bytes_total: usize, + ) -> EventObject<'gc> where S: Into>, { let event_type: AvmString<'gc> = event_type.into(); let progress_event_cls = activation.avm2().classes().progressevent; - progress_event_cls - .construct( - activation, - &[ - event_type.into(), - // bubbles - bubbles.into(), - // cancelable - cancelable.into(), - // bytesLoaded - (bytes_loaded as f64).into(), - // bytesToal - (bytes_total as f64).into(), - ], - ) - .unwrap() // we don't expect to break here + Self::from_class_and_args( + activation, + progress_event_cls, + &[ + event_type.into(), + // bubbles + false.into(), + // cancelable + false.into(), + // bytesLoaded + (bytes_loaded as f64).into(), + // bytesToal + (bytes_total as f64).into(), + ], + ) } pub fn focus_event( @@ -319,30 +320,75 @@ impl<'gc> EventObject<'gc> { cancelable: bool, related_object: Option>, key_code: u32, - ) -> Object<'gc> + ) -> EventObject<'gc> where S: Into>, { let event_type: AvmString<'gc> = event_type.into(); let shift_key = activation.context.input.is_key_down(KeyCode::SHIFT); - let class = activation.avm2().classes().focusevent; - class - .construct( - activation, - &[ - event_type.into(), - true.into(), - cancelable.into(), - related_object - .map(|o| o.as_displayobject().object2()) - .unwrap_or(Value::Null), - shift_key.into(), - key_code.into(), - "none".into(), // TODO implement direction - ], - ) - .unwrap() + let focus_event_cls = activation.avm2().classes().focusevent; + Self::from_class_and_args( + activation, + focus_event_cls, + &[ + event_type.into(), + true.into(), + cancelable.into(), + related_object + .map(|o| o.as_displayobject().object2()) + .unwrap_or(Value::Null), + shift_key.into(), + key_code.into(), + "none".into(), // TODO implement direction + ], + ) + } + + pub fn io_error_event( + activation: &mut Activation<'_, 'gc>, + error_msg: AvmString<'gc>, + error_code: u32, + ) -> EventObject<'gc> { + let io_error_event_cls = activation.avm2().classes().ioerrorevent; + Self::from_class_and_args( + activation, + io_error_event_cls, + &[ + "ioError".into(), + false.into(), + false.into(), + error_msg.into(), + error_code.into(), + ], + ) + } + + pub fn http_status_event( + activation: &mut Activation<'_, 'gc>, + status: u16, + redirected: bool, + ) -> EventObject<'gc> { + let http_status_event_cls = activation.avm2().classes().httpstatusevent; + Self::from_class_and_args( + activation, + http_status_event_cls, + &[ + "httpStatus".into(), + false.into(), + false.into(), + status.into(), + redirected.into(), + ], + ) + } + + pub fn event(&self) -> Ref> { + self.0.event.borrow() + } + + pub fn event_mut(&self, mc: &Mutation<'gc>) -> RefMut> { + unlock!(Gc::write(mc, self.0), EventObjectData, event).borrow_mut() } } @@ -359,6 +405,10 @@ impl<'gc> TObject<'gc> for EventObject<'gc> { Gc::as_ptr(self.0) as *const ObjectPtr } + fn as_event_object(self) -> Option> { + Some(self) + } + fn as_event(&self) -> Option>> { Some(self.0.event.borrow()) } diff --git a/core/src/avm2/object/function_object.rs b/core/src/avm2/object/function_object.rs index 499094738aca..8ebcc82dc88f 100644 --- a/core/src/avm2/object/function_object.rs +++ b/core/src/avm2/object/function_object.rs @@ -106,11 +106,14 @@ impl<'gc> FunctionObject<'gc> { /// This associated constructor will also create and initialize an empty /// `Object` prototype for the function. The given `receiver`, if supplied, /// will override any user-specified `this` parameter. + /// + /// It is the caller's responsibility to ensure that the `receiver` passed + /// to this method is not Value::Null or Value::Undefined. pub fn from_method( activation: &mut Activation<'_, 'gc>, method: Method<'gc>, scope: ScopeChain<'gc>, - receiver: Option>, + receiver: Option>, bound_superclass_object: Option>, bound_class: Option>, ) -> FunctionObject<'gc> { @@ -200,13 +203,6 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { Gc::as_ptr(self.0) as *const ObjectPtr } - fn to_locale_string( - &self, - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - self.to_string(activation) - } - fn as_executable(&self) -> Option>> { Some(self.0.exec.borrow()) } diff --git a/core/src/avm2/object/loaderinfo_object.rs b/core/src/avm2/object/loaderinfo_object.rs index 17823ed52fd2..de62f3a07ed3 100644 --- a/core/src/avm2/object/loaderinfo_object.rs +++ b/core/src/avm2/object/loaderinfo_object.rs @@ -134,13 +134,17 @@ impl<'gc> LoaderInfoObject<'gc> { .avm2 .classes() .eventdispatcher - .construct(activation, &[])?, + .construct(activation, &[])? + .as_object() + .unwrap(), uncaught_error_events: activation .context .avm2 .classes() .uncaughterrorevents - .construct(activation, &[])?, + .construct(activation, &[])? + .as_object() + .unwrap(), cached_avm1movie: Lock::new(None), content_type: Cell::new(ContentType::Swf), expose_content: Cell::new(false), @@ -181,13 +185,17 @@ impl<'gc> LoaderInfoObject<'gc> { .avm2 .classes() .eventdispatcher - .construct(activation, &[])?, + .construct(activation, &[])? + .as_object() + .unwrap(), uncaught_error_events: activation .context .avm2 .classes() .uncaughterrorevents - .construct(activation, &[])?, + .construct(activation, &[])? + .as_object() + .unwrap(), cached_avm1movie: Lock::new(None), content_type: Cell::new(ContentType::Unknown), expose_content: Cell::new(false), @@ -276,21 +284,8 @@ impl<'gc> LoaderInfoObject<'gc> { if should_complete { let mut activation = Activation::from_nothing(context); if from_url { - let http_status_evt = activation - .avm2() - .classes() - .httpstatusevent - .construct( - &mut activation, - &[ - "httpStatus".into(), - false.into(), - false.into(), - status.into(), - redirected.into(), - ], - ) - .unwrap(); + let http_status_evt = + EventObject::http_status_event(&mut activation, status, redirected); Avm2::dispatch_event(context, http_status_evt, (*self).into()); } diff --git a/core/src/avm2/object/local_connection_object.rs b/core/src/avm2/object/local_connection_object.rs index 518c535ef468..3248fbfcea94 100644 --- a/core/src/avm2/object/local_connection_object.rs +++ b/core/src/avm2/object/local_connection_object.rs @@ -1,7 +1,7 @@ use crate::avm2::activation::Activation; use crate::avm2::amf::deserialize_value; use crate::avm2::object::script_object::ScriptObjectData; -use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; +use crate::avm2::object::{ClassObject, EventObject, Object, ObjectPtr, TObject}; use crate::avm2::value::Value; use crate::avm2::{Avm2, Domain, Error}; use crate::context::UpdateContext; @@ -105,8 +105,11 @@ impl<'gc> LocalConnectionObject<'gc> { pub fn send_status(&self, context: &mut UpdateContext<'gc>, status: &'static str) { let mut activation = Activation::from_nothing(context); - if let Ok(event) = activation.avm2().classes().statusevent.construct( + + let status_event_cls = activation.avm2().classes().statusevent; + let event = EventObject::from_class_and_args( &mut activation, + status_event_cls, &[ "status".into(), false.into(), @@ -114,9 +117,9 @@ impl<'gc> LocalConnectionObject<'gc> { Value::Null, status.into(), ], - ) { - Avm2::dispatch_event(activation.context, event, (*self).into()); - } + ); + + Avm2::dispatch_event(activation.context, event, (*self).into()); } pub fn run_method( @@ -134,12 +137,14 @@ impl<'gc> LocalConnectionObject<'gc> { .push(deserialize_value(&mut activation, &argument).unwrap_or(Value::Undefined)); } - let client = self.client(); + let client = Value::from(self.client()); if let Err(e) = client.call_public_property(method_name, &arguments, &mut activation) { match e { Error::AvmError(error) => { - if let Ok(event) = activation.avm2().classes().asyncerrorevent.construct( + let async_error_event_cls = activation.avm2().classes().asyncerrorevent; + let event = EventObject::from_class_and_args( &mut activation, + async_error_event_cls, &[ "asyncError".into(), false.into(), @@ -147,9 +152,9 @@ impl<'gc> LocalConnectionObject<'gc> { error, error, ], - ) { - Avm2::dispatch_event(activation.context, event, (*self).into()); - } + ); + + Avm2::dispatch_event(activation.context, event, (*self).into()); } _ => { tracing::error!("Unhandled error dispatching AVM2 LocalConnection method call to '{method_name}': {e}"); diff --git a/core/src/avm2/object/namespace_object.rs b/core/src/avm2/object/namespace_object.rs index 837ef8d93c24..36d386b494ce 100644 --- a/core/src/avm2/object/namespace_object.rs +++ b/core/src/avm2/object/namespace_object.rs @@ -6,7 +6,7 @@ use crate::avm2::object::{ObjectPtr, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::Namespace; -use crate::string::{AvmString, StringContext}; +use crate::string::AvmString; use core::fmt; use gc_arena::{Collect, Gc, GcWeak}; @@ -106,10 +106,6 @@ impl<'gc> TObject<'gc> for NamespaceObject<'gc> { Gc::as_ptr(self.0) as *const ObjectPtr } - fn value_of(&self, context: &mut StringContext<'gc>) -> Result, Error<'gc>> { - Ok(self.0.namespace.as_uri(context).into()) - } - fn as_namespace(&self) -> Option> { Some(self.0.namespace) } diff --git a/core/src/avm2/object/primitive_object.rs b/core/src/avm2/object/primitive_object.rs deleted file mode 100644 index 4bf99320de73..000000000000 --- a/core/src/avm2/object/primitive_object.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Boxed primitives - -use core::fmt; -use std::cell::{Ref, RefMut}; - -use crate::avm2::activation::Activation; -use crate::avm2::object::script_object::ScriptObjectData; -use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; -use crate::avm2::value::Value; -use crate::avm2::Error; -use crate::string::{AvmString, StringContext}; -use gc_arena::barrier::unlock; -use gc_arena::{lock::RefLock, Collect, Gc, GcWeak, Mutation}; - -/// A class instance allocator that allocates primitive objects. -pub fn primitive_allocator<'gc>( - class: ClassObject<'gc>, - activation: &mut Activation<'_, 'gc>, -) -> Result, Error<'gc>> { - let base = ScriptObjectData::new(class); - - Ok(PrimitiveObject(Gc::new( - activation.gc(), - PrimitiveObjectData { - base, - primitive: RefLock::new(Value::Undefined), - }, - )) - .into()) -} - -/// An Object which represents a primitive value of some other kind. -#[derive(Collect, Clone, Copy)] -#[collect(no_drop)] -pub struct PrimitiveObject<'gc>(pub Gc<'gc, PrimitiveObjectData<'gc>>); - -#[derive(Collect, Clone, Copy, Debug)] -#[collect(no_drop)] -pub struct PrimitiveObjectWeak<'gc>(pub GcWeak<'gc, PrimitiveObjectData<'gc>>); - -impl fmt::Debug for PrimitiveObject<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PrimitiveObject") - .field("ptr", &Gc::as_ptr(self.0)) - .finish() - } -} - -#[derive(Collect, Clone)] -#[collect(no_drop)] -#[repr(C, align(8))] -pub struct PrimitiveObjectData<'gc> { - /// All normal script data. - base: ScriptObjectData<'gc>, - - /// The primitive value this object represents. - primitive: RefLock>, -} - -const _: () = assert!(std::mem::offset_of!(PrimitiveObjectData, base) == 0); -const _: () = assert!( - std::mem::align_of::() == std::mem::align_of::() -); - -impl<'gc> PrimitiveObject<'gc> { - /// Box a primitive into an object. - /// - /// This function will yield an error if `primitive` is `Undefined`, `Null`, - /// or an object already. - /// - /// In order to prevent stack overflow, this function does *not* call the - /// initializer of the primitive class being constructed. - pub fn from_primitive( - primitive: Value<'gc>, - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - if !primitive.is_primitive() { - return Err("Attempted to box an object as a primitive".into()); - } - - if matches!(primitive, Value::Undefined) { - return Err("Cannot box an undefined value".into()); - } else if matches!(primitive, Value::Null) { - return Err("Cannot box a null value".into()); - } - - let class = match primitive { - Value::Bool(_) => activation.avm2().classes().boolean, - Value::Number(_) => activation.avm2().classes().number, - Value::Integer(_) => activation.avm2().classes().int, - Value::String(_) => activation.avm2().classes().string, - _ => unreachable!(), - }; - - let base = ScriptObjectData::new(class); - let this: Object<'gc> = PrimitiveObject(Gc::new( - activation.gc(), - PrimitiveObjectData { - base, - primitive: RefLock::new(primitive), - }, - )) - .into(); - - //We explicitly DO NOT CALL the native initializers of primitives here. - //If we did so, then those primitive initializers' method types would - //trigger the construction of primitive objects... which would need to - //be initialized, which forms a cycle. - - Ok(this) - } -} - -impl<'gc> TObject<'gc> for PrimitiveObject<'gc> { - fn gc_base(&self) -> Gc<'gc, ScriptObjectData<'gc>> { - // SAFETY: Object data is repr(C), and a compile-time assert ensures - // that the ScriptObjectData stays at offset 0 of the struct- so the - // layouts are compatible - - unsafe { Gc::cast(self.0) } - } - - fn as_ptr(&self) -> *const ObjectPtr { - Gc::as_ptr(self.0) as *const ObjectPtr - } - - fn to_locale_string( - &self, - activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { - match *self.0.primitive.borrow() { - val @ Value::Integer(_) => Ok(val), - _ => { - let class_name = self.instance_class().name().local_name(); - - Ok(AvmString::new_utf8(activation.gc(), format!("[object {class_name}]")).into()) - } - } - } - - fn value_of(&self, _context: &mut StringContext<'gc>) -> Result, Error<'gc>> { - Ok(*self.0.primitive.borrow()) - } - - fn as_primitive(&self) -> Option>> { - Some(self.0.primitive.borrow()) - } - - fn as_primitive_mut(&self, mc: &Mutation<'gc>) -> Option>> { - Some(unlock!(Gc::write(mc, self.0), PrimitiveObjectData, primitive).borrow_mut()) - } -} diff --git a/core/src/avm2/object/proxy_object.rs b/core/src/avm2/object/proxy_object.rs index 4a43297ff6bc..9c9758a96c81 100644 --- a/core/src/avm2/object/proxy_object.rs +++ b/core/src/avm2/object/proxy_object.rs @@ -66,9 +66,11 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { multiname: &Multiname<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { + let self_val = Value::from(self); + let qname = QNameObject::from_name(activation, multiname.clone()); let prop = Multiname::new(activation.avm2().namespaces.proxy, "getProperty"); - self.call_property(&prop, &[qname.into()], activation) + self_val.call_property(&prop, &[qname.into()], activation) } fn set_property_local( @@ -77,9 +79,11 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { value: Value<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result<(), Error<'gc>> { + let self_val = Value::from(self); + let qname = QNameObject::from_name(activation, multiname.clone()); let prop = Multiname::new(activation.avm2().namespaces.proxy, "setProperty"); - self.call_property(&prop, &[qname.into(), value], activation)?; + self_val.call_property(&prop, &[qname.into(), value], activation)?; Ok(()) } @@ -90,12 +94,14 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { + let self_val = Value::from(self); + let qname = QNameObject::from_name(activation, multiname.clone()); let prop = Multiname::new(activation.avm2().namespaces.proxy, "callProperty"); let mut args = vec![qname.into()]; args.extend_from_slice(arguments); - self.call_property(&prop, &args, activation) + self_val.call_property(&prop, &args, activation) } fn delete_property_local( @@ -103,10 +109,12 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { activation: &mut Activation<'_, 'gc>, multiname: &Multiname<'gc>, ) -> Result> { + let self_val = Value::from(self); + let qname = QNameObject::from_name(activation, multiname.clone()); let prop = Multiname::new(activation.avm2().namespaces.proxy, "deleteProperty"); - Ok(self + Ok(self_val .call_property(&prop, &[qname.into()], activation)? .coerce_to_boolean()) } @@ -116,8 +124,10 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { activation: &mut Activation<'_, 'gc>, name: &Multiname<'gc>, ) -> Result> { + let self_val = Value::from(self); + let prop = Multiname::new(activation.avm2().namespaces.proxy, "hasProperty"); - Ok(self + Ok(self_val .call_property( &prop, &[name @@ -134,9 +144,11 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { name: impl Into>, activation: &mut Activation<'_, 'gc>, ) -> Result> { + let self_val = Value::from(self); + let name = name.into(); let prop = Multiname::new(activation.avm2().namespaces.proxy, "hasProperty"); - Ok(self + Ok(self_val .call_property(&prop, &[name.into()], activation)? .coerce_to_boolean()) } @@ -146,9 +158,12 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { last_index: u32, activation: &mut Activation<'_, 'gc>, ) -> Result> { + let self_val = Value::from(self); + let prop = Multiname::new(activation.avm2().namespaces.proxy, "nextNameIndex"); - self.call_property(&prop, &[last_index.into()], activation)? + self_val + .call_property(&prop, &[last_index.into()], activation)? .coerce_to_u32(activation) } @@ -157,8 +172,10 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { index: u32, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { + let self_val = Value::from(self); + let prop = Multiname::new(activation.avm2().namespaces.proxy, "nextName"); - self.call_property(&prop, &[index.into()], activation) + self_val.call_property(&prop, &[index.into()], activation) } fn get_enumerant_value( @@ -166,7 +183,9 @@ impl<'gc> TObject<'gc> for ProxyObject<'gc> { index: u32, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { + let self_val = Value::from(self); + let prop = Multiname::new(activation.avm2().namespaces.proxy, "nextValue"); - self.call_property(&prop, &[index.into()], activation) + self_val.call_property(&prop, &[index.into()], activation) } } diff --git a/core/src/avm2/object/regexp_object.rs b/core/src/avm2/object/regexp_object.rs index 23cd9d5460b1..ad1706ae0b2f 100644 --- a/core/src/avm2/object/regexp_object.rs +++ b/core/src/avm2/object/regexp_object.rs @@ -3,10 +3,8 @@ use crate::avm2::activation::Activation; use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; -use crate::avm2::regexp::{RegExp, RegExpFlags}; -use crate::avm2::value::Value; +use crate::avm2::regexp::RegExp; use crate::avm2::Error; -use crate::string::{AvmString, StringContext, WString}; use core::fmt; use gc_arena::barrier::unlock; use gc_arena::{lock::RefLock, Collect, Gc, GcWeak, Mutation}; @@ -95,34 +93,6 @@ impl<'gc> TObject<'gc> for RegExpObject<'gc> { Gc::as_ptr(self.0) as *const ObjectPtr } - fn value_of(&self, context: &mut StringContext<'gc>) -> Result, Error<'gc>> { - let regexp = self.0.regexp.borrow(); - let mut s = WString::new(); - s.push_byte(b'/'); - s.push_str(®exp.source()); - s.push_byte(b'/'); - - let flags = regexp.flags(); - - if flags.contains(RegExpFlags::GLOBAL) { - s.push_byte(b'g'); - } - if flags.contains(RegExpFlags::IGNORE_CASE) { - s.push_byte(b'i'); - } - if flags.contains(RegExpFlags::MULTILINE) { - s.push_byte(b'm'); - } - if flags.contains(RegExpFlags::DOTALL) { - s.push_byte(b's'); - } - if flags.contains(RegExpFlags::EXTENDED) { - s.push_byte(b'x'); - } - - Ok(AvmString::new(context.gc(), s).into()) - } - /// Unwrap this object as a regexp. fn as_regexp_object(&self) -> Option> { Some(*self) diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index e5cf8aaf4a6f..1fdc51845a2b 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -75,7 +75,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { } } -fn maybe_int_property(name: AvmString<'_>) -> DynamicKey<'_> { +pub fn maybe_int_property(name: AvmString<'_>) -> DynamicKey<'_> { // TODO: this should use a custom implementation, not parse() // FP is much stricter here, only allowing pure natural numbers without sign or leading zeros if let Ok(val) = name.parse::() { @@ -349,12 +349,6 @@ impl<'gc> ScriptObjectWrapper<'gc> { self.bound_methods().get(id as usize).and_then(|v| *v) } - pub fn has_trait(&self, name: &Multiname<'gc>) -> bool { - // Class instances have instance traits from any class in the base - // class chain. - self.vtable().has_trait(name) - } - pub fn has_own_dynamic_property(&self, name: &Multiname<'gc>) -> bool { if name.contains_public_namespace() { if let Some(name) = name.local_name() { @@ -366,7 +360,7 @@ impl<'gc> ScriptObjectWrapper<'gc> { } pub fn has_own_property(&self, name: &Multiname<'gc>) -> bool { - self.has_trait(name) || self.has_own_dynamic_property(name) + self.vtable().has_trait(name) || self.has_own_dynamic_property(name) } pub fn proto(&self) -> Option> { diff --git a/core/src/avm2/object/sound_object.rs b/core/src/avm2/object/sound_object.rs index 22023e396677..c7dc52aa9d0a 100644 --- a/core/src/avm2/object/sound_object.rs +++ b/core/src/avm2/object/sound_object.rs @@ -173,7 +173,10 @@ impl<'gc> SoundObject<'gc> { .classes() .id3info .construct(activation, &[]) - .expect("failed to construct ID3Info object"); + .expect("failed to construct ID3Info object") + .as_object() + .unwrap(); + let tag = Tag::read_from2(Cursor::new(bytes)); if let Ok(ref tag) = tag { if let Some(v) = tag.album() { diff --git a/core/src/avm2/object/xml_list_object.rs b/core/src/avm2/object/xml_list_object.rs index 7c76310a334c..9c1a5d52327f 100644 --- a/core/src/avm2/object/xml_list_object.rs +++ b/core/src/avm2/object/xml_list_object.rs @@ -572,9 +572,7 @@ impl<'gc> TObject<'gc> for XmlListObject<'gc> { arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let method = self - .proto() - .expect("XMLList missing prototype") + let method = Value::from(self.proto().expect("XMLList missing prototype")) .get_property(multiname, activation)?; // See https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/XMLListObject.cpp#L50 @@ -604,11 +602,10 @@ impl<'gc> TObject<'gc> for XmlListObject<'gc> { if let Some(list) = prop.as_object().and_then(|obj| obj.as_xml_list_object()) { if list.length() == 0 && self.length() == 1 { let mut children = self.children_mut(activation.gc()); - return children - .first_mut() - .unwrap() - .get_or_create_xml(activation) - .call_property(multiname, arguments, activation); + + let child = children.first_mut().unwrap().get_or_create_xml(activation); + + return Value::from(child).call_property(multiname, arguments, activation); } } } @@ -977,6 +974,8 @@ impl<'gc> TObject<'gc> for XmlListObject<'gc> { .classes() .xml .construct(activation, &[value])? + .as_object() + .unwrap() .as_xml_object() .expect("Should be XML Object"); children[index] = E4XOrXml::Xml(xml); diff --git a/core/src/avm2/object/xml_object.rs b/core/src/avm2/object/xml_object.rs index e4e5bf74a552..afe774875c83 100644 --- a/core/src/avm2/object/xml_object.rs +++ b/core/src/avm2/object/xml_object.rs @@ -17,7 +17,6 @@ use gc_arena::{lock::Lock, Collect, Gc, GcWeak, Mutation}; use ruffle_wstr::WString; use super::xml_list_object::{E4XOrXml, XmlOrXmlListObject}; -use super::PrimitiveObject; /// A class instance allocator that allocates XML objects. pub fn xml_allocator<'gc>( @@ -357,9 +356,7 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> { ) -> Result, Error<'gc>> { let this = self.as_xml_object().unwrap(); - let method = self - .proto() - .expect("XMLList missing prototype") + let method = Value::from(self.proto().expect("XMLList missing prototype")) .get_property(multiname, activation)?; // If the method doesn't exist on the prototype, and we have simple content, @@ -374,10 +371,8 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> { let prop = self.get_property_local(multiname, activation)?; if let Some(list) = prop.as_object().and_then(|obj| obj.as_xml_list_object()) { if list.length() == 0 && this.node().has_simple_content() { - let receiver = PrimitiveObject::from_primitive( - this.node().xml_to_string(activation).into(), - activation, - )?; + let receiver = Value::String(this.node().xml_to_string(activation)); + return receiver.call_property(multiname, arguments, activation); } } diff --git a/core/src/avm2/optimize.rs b/core/src/avm2/optimize.rs index 2fa4f74b200b..3e0cbe54f70c 100644 --- a/core/src/avm2/optimize.rs +++ b/core/src/avm2/optimize.rs @@ -1,7 +1,6 @@ use crate::avm2::error::verify_error; use crate::avm2::method::{BytecodeMethod, ResolvedParamConfig}; use crate::avm2::multiname::Multiname; -use crate::avm2::object::TObject; use crate::avm2::op::Op; use crate::avm2::property::Property; use crate::avm2::verify::{Exception, JumpSource}; @@ -1140,7 +1139,7 @@ pub fn optimize<'gc>( .outer() .get_unchecked(*index as usize) .values() - .instance_class(); + .instance_class(activation); stack.push_class(activation, class)?; } Op::Pop => { @@ -1190,7 +1189,9 @@ pub fn optimize<'gc>( } if !stack_push_done { - if let Some(info) = outer_scope.get_entry_for_multiname(&multiname) { + if let Some(info) = + outer_scope.get_entry_for_multiname(activation, &multiname) + { if let Some((class, index)) = info { *op = Op::GetOuterScope { index }; @@ -1682,8 +1683,9 @@ pub fn optimize<'gc>( let outer_scope = activation.outer(); if !outer_scope.is_empty() { let global_scope = outer_scope.get_unchecked(0); + let global_class = global_scope.values().instance_class(activation); - stack.push_class(activation, global_scope.values().instance_class())?; + stack.push_class(activation, global_class)?; } else if has_simple_scoping { stack.push(activation, this_value)?; } else { @@ -1713,7 +1715,7 @@ pub fn optimize<'gc>( if !outer_scope.is_empty() { let global_scope = outer_scope.get_unchecked(0); - let class = global_scope.values().instance_class(); + let class = global_scope.values().instance_class(activation); let mut value_class = class.vtable().slot_classes()[*slot_id as usize]; let resolved_value_class = value_class.get_class(activation); if let Ok(class) = resolved_value_class { diff --git a/core/src/avm2/parameters.rs b/core/src/avm2/parameters.rs index abd006c302c6..b6d89fefa49e 100644 --- a/core/src/avm2/parameters.rs +++ b/core/src/avm2/parameters.rs @@ -1,5 +1,4 @@ use crate::avm2::error::make_error_2007; -use crate::avm2::object::PrimitiveObject; use crate::avm2::Object; use crate::avm2::{Activation, Error, Value}; use crate::string::AvmString; @@ -107,25 +106,21 @@ impl<'gc> ParametersExt<'gc> for &[Value<'gc>] { name: &'static str, ) -> Result, Error<'gc>> { match self[index] { - Value::Null | Value::Undefined => Err(make_error_2007(activation, name)), + Value::Null => Err(make_error_2007(activation, name)), Value::Object(o) => Ok(o), - primitive => Ok(PrimitiveObject::from_primitive(primitive, activation) - .expect("Primitive object is infallible at this point")), + _ => panic!("get_object should read Object or Null"), } } fn try_get_object( &self, - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, index: usize, ) -> Option> { match self[index] { - Value::Null | Value::Undefined => None, + Value::Null => None, Value::Object(o) => Some(o), - primitive => Some( - PrimitiveObject::from_primitive(primitive, activation) - .expect("Primitive object is infallible at this point"), - ), + _ => panic!("try_get_object should read Object or Null"), } } diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index ec0aef8f7cf1..c89d18565d87 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -3,7 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::domain::Domain; -use crate::avm2::object::{Object, TObject}; +use crate::avm2::object::TObject; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::{Multiname, Namespace}; @@ -19,7 +19,7 @@ use super::property_map::PropertyMap; #[collect(no_drop)] pub struct Scope<'gc> { /// The underlying object of this Scope - values: Object<'gc>, + values: Value<'gc>, /// Indicates whether or not this is a `with` scope. /// @@ -29,16 +29,22 @@ pub struct Scope<'gc> { } impl<'gc> Scope<'gc> { - /// Creates a new regular Scope - pub fn new(values: Object<'gc>) -> Self { + /// Creates a new regular Scope. + /// + /// It is the caller's responsibility to ensure that the `values` passed + /// to this method is not Value::Null or Value::Undefined. + pub fn new(values: Value<'gc>) -> Self { Self { values, with: false, } } - /// Creates a new `with` Scope - pub fn new_with(values: Object<'gc>) -> Self { + /// Creates a new `with` Scope. + /// + /// It is the caller's responsibility to ensure that the `values` passed + /// to this method is not Value::Null or Value::Undefined. + pub fn new_with(values: Value<'gc>) -> Self { Self { values, with: true } } @@ -46,7 +52,7 @@ impl<'gc> Scope<'gc> { self.with } - pub fn values(&self) -> Object<'gc> { + pub fn values(&self) -> Value<'gc> { self.values } } @@ -60,7 +66,7 @@ struct ScopeContainer<'gc> { /// The cache of this ScopeChain. A value of None indicates that caching is disabled /// for this ScopeChain. - cache: Option>>>, + cache: Option>>>, } impl<'gc> ScopeContainer<'gc> { @@ -174,20 +180,20 @@ impl<'gc> ScopeChain<'gc> { &self, multiname: &Multiname<'gc>, activation: &mut Activation<'_, 'gc>, - ) -> Result>, Object<'gc>)>, Error<'gc>> { + ) -> Result>, Value<'gc>)>, Error<'gc>> { if let Some(container) = self.container { // We skip the scope at depth 0 (the global scope). The global scope will be checked in a different phase. for scope in container.scopes.iter().skip(1).rev() { // NOTE: We are manually searching the vtable's traits so we can figure out which namespace the trait // belongs to. let values = scope.values(); - let vtable = values.vtable(); + let vtable = values.vtable(activation); if let Some((namespace, _)) = vtable.get_trait_with_ns(multiname) { return Ok(Some((Some(namespace), values))); } // Wasn't in the objects traits, let's try dynamic properties if this is a with scope. - if scope.with() && values.has_own_property(multiname) { + if scope.with() && values.has_own_property(activation, multiname) { // NOTE: We return the QName as `None` to indicate that we should never cache this result. // We NEVER cache the result of dynamic properties (and can't anyway because of the check // in ScopeContainer::new). @@ -199,7 +205,7 @@ impl<'gc> ScopeChain<'gc> { if let Some((qname, script)) = self.domain.get_defining_script(multiname)? { return Ok(Some(( Some(qname.namespace()), - script.globals(activation.context)?, + script.globals(activation.context)?.into(), ))); } Ok(None) @@ -209,7 +215,7 @@ impl<'gc> ScopeChain<'gc> { &self, multiname: &Multiname<'gc>, activation: &mut Activation<'_, 'gc>, - ) -> Result>, Error<'gc>> { + ) -> Result>, Error<'gc>> { // First we check the cache of our container if let Some(container) = self.container { if let Some(cache) = &container.cache { @@ -237,6 +243,7 @@ impl<'gc> ScopeChain<'gc> { pub fn get_entry_for_multiname( &self, + activation: &mut Activation<'_, 'gc>, multiname: &Multiname<'gc>, ) -> Option, u32)>> { if let Some(container) = self.container { @@ -248,8 +255,8 @@ impl<'gc> ScopeChain<'gc> { } let values = scope.values(); - if values.has_trait(multiname) { - return Some(Some((values.instance_class(), index as u32))); + if values.has_trait(activation, multiname) { + return Some(Some((values.instance_class(activation), index as u32))); } } } @@ -277,24 +284,73 @@ impl<'gc> ScopeChain<'gc> { /// The `global` parameter indicates whether we are on global$init (script initializer). /// When the `global` parameter is true, the scope at depth 0 is considered the global scope, and is skipped. pub fn search_scope_stack<'gc>( - scopes: &[Scope<'gc>], + activation: &mut Activation<'_, 'gc>, multiname: &Multiname<'gc>, global: bool, -) -> Result>, Error<'gc>> { +) -> Result>, Error<'gc>> { + let classes = activation.context.avm2.classes(); + + let scopes = activation.scope_frame(); + for (depth, scope) in scopes.iter().enumerate().rev() { if depth == 0 && global { continue; } let values = scope.values(); - if values.has_trait(multiname) { + if value_has_trait(classes, values, multiname) { return Ok(Some(values)); } else if scope.with() { // We search the dynamic properties if this is a with scope. - if values.has_own_property(multiname) { + if value_has_own_property(classes, values, multiname) { return Ok(Some(values)); } } } Ok(None) } + +use crate::avm2::globals::SystemClasses; + +// Check if a Value has a trait without using an Activation. Used only in `search_scope_stack`. +fn value_has_trait<'gc>( + classes: &SystemClasses<'gc>, + value: Value<'gc>, + multiname: &Multiname<'gc>, +) -> bool { + let vtable = match value { + Value::Bool(_) => classes.boolean.instance_vtable(), + Value::Number(_) | Value::Integer(_) => classes.number.instance_vtable(), + Value::String(_) => classes.string.instance_vtable(), + Value::Object(obj) => obj.vtable(), + + Value::Undefined | Value::Null => { + unreachable!("Should not have Undefined or Null scope") + } + }; + + vtable.has_trait(multiname) +} + +// Check if a Value has a property without using an Activation. Used only in `search_scope_stack`. +fn value_has_own_property<'gc>( + classes: &SystemClasses<'gc>, + value: Value<'gc>, + multiname: &Multiname<'gc>, +) -> bool { + let vtable = match value { + Value::Bool(_) => classes.boolean.instance_vtable(), + Value::Number(_) | Value::Integer(_) => classes.number.instance_vtable(), + Value::String(_) => classes.string.instance_vtable(), + Value::Object(obj) => obj.vtable(), + + Value::Undefined | Value::Null => { + unreachable!("Should not have Undefined or Null scope") + } + }; + + match value { + Value::Object(object) => object.has_own_property(multiname), + _ => vtable.has_trait(multiname), + } +} diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 6e4eedfbbd03..e1a91dfb98ca 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -3,11 +3,12 @@ use crate::avm2::activation::Activation; use crate::avm2::error; use crate::avm2::error::type_error; -use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject}; +use crate::avm2::function::exec; +use crate::avm2::object::{NamespaceObject, Object, TObject}; +use crate::avm2::property::Property; use crate::avm2::script::TranslationUnit; -use crate::avm2::Error; -use crate::avm2::Multiname; -use crate::avm2::Namespace; +use crate::avm2::vtable::{ClassBoundMethod, VTable}; +use crate::avm2::{Error, Multiname, Namespace}; use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32}; use crate::string::{AvmAtom, AvmString, WStr}; use gc_arena::Collect; @@ -600,13 +601,7 @@ impl<'gc> Value<'gc> { Value::Number(f) => !f.is_nan() && *f != 0.0, Value::Integer(i) => *i != 0, Value::String(s) => !s.is_empty(), - Value::Object(o) => { - if let Some(prim) = o.as_primitive() { - prim.coerce_to_boolean() - } else { - true - } - } + Value::Object(_) => true, } } @@ -633,9 +628,8 @@ impl<'gc> Value<'gc> { }); match self { - Value::Object(Object::PrimitiveObject(o)) => o.value_of(activation.strings()), Value::Object(o) if hint == Hint::String => { - let object = *o; + let object = Value::from(*o); let prim = object.call_public_property("toString", &[], activation)?; if prim.is_primitive() { @@ -657,7 +651,7 @@ impl<'gc> Value<'gc> { )?)) } Value::Object(o) if hint == Hint::Number => { - let object = *o; + let object = Value::from(*o); let prim = object.call_public_property("valueOf", &[], activation)?; if prim.is_primitive() { @@ -849,55 +843,543 @@ impl<'gc> Value<'gc> { }) } - /// Coerce the value to an Object. - /// - /// TODO: Once `PrimitiveObject` is removed, this method will be able - /// to be removed too, since all that this will do then is a null/undefined check. - pub fn coerce_to_object( + #[inline(always)] + pub fn null_check( &self, activation: &mut Activation<'_, 'gc>, - ) -> Result, Error<'gc>> { + name: Option<&Multiname<'gc>>, + ) -> Result, Error<'gc>> { + if matches!(self, Value::Null | Value::Undefined) { + return Err(error::make_null_or_undefined_error(activation, *self, name)); + } + + Ok(*self) + } + + pub fn as_object(&self) -> Option> { match self { - Value::Undefined => return Err("TypeError: undefined is not an Object".into()), - Value::Null => return Err("TypeError: null is not an Object".into()), - Value::Object(o) => return Ok(*o), - _ => {} - }; + Value::Object(o) => Some(*o), + _ => None, + } + } - PrimitiveObject::from_primitive(*self, activation) + /// Retrieve a property by Multiname lookup. + /// + /// This corresponds directly to the AVM2 operation `getproperty`, with the + /// exception that it does not special-case object lookups on dictionary + /// structured objects. + /// + /// This method will panic if called on null or undefined. + pub fn get_property( + &self, + multiname: &Multiname<'gc>, + activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + let vtable = self.vtable(activation); + + match vtable.get_trait(multiname) { + Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => { + // Only objects can have slots + let object = self.as_object().unwrap(); + + Ok(object.get_slot(slot_id)) + } + Some(Property::Method { disp_id }) => { + if let Some(object) = self.as_object() { + // avmplus has a special case for XML and XMLList objects, so we need one as well + // https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/Toplevel.cpp#L629-L634 + if (object.as_xml_object().is_some() || object.as_xml_list_object().is_some()) + && multiname.contains_public_namespace() + { + return object.get_property_local(multiname, activation); + } + + if let Some(bound_method) = object.get_bound_method(disp_id) { + return Ok(bound_method.into()); + } + + let bound_method = vtable + .make_bound_method(activation, *self, disp_id) + .expect("Method should exist"); + + // TODO: Bound methods should be cached on the Method in a + // WeakKeyHashMap, not on the Object + object.install_bound_method(activation.gc(), disp_id, bound_method); + + Ok(bound_method.into()) + } else { + let bound_method = vtable + .make_bound_method(activation, *self, disp_id) + .expect("Method should exist"); + + // TODO: Bound methods should be cached on the Method in a + // WeakKeyHashMap, not on the Object + + Ok(bound_method.into()) + } + } + Some(Property::Virtual { get: Some(get), .. }) => { + self.call_method(get, &[], activation) + } + Some(Property::Virtual { get: None, .. }) => { + let instance_class = self.instance_class(activation); + + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::ReadFromWriteOnly, + multiname, + instance_class, + )) + } + None => { + if let Some(object) = self.as_object() { + object.get_property_local(multiname, activation) + } else { + let instance_class = self.instance_class(activation); + + if !multiname.contains_public_namespace() { + return Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidRead, + multiname, + instance_class, + )); + } + + let Some(local_name) = multiname.local_name() else { + // when can this happen? + return Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidRead, + multiname, + instance_class, + )); + }; + + let key = crate::avm2::object::maybe_int_property(local_name); + + // `get_property` also checks prototype chain + let mut proto = self.proto(activation); + + while let Some(obj) = proto { + let obj = obj.base(); + let values = obj.values(); + let value = values.as_hashmap().get(&key); + if let Some(value) = value { + return Ok(value.value); + } + proto = obj.proto(); + } + + // Primitive classes are sealed + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidRead, + multiname, + instance_class, + )) + } + } + } } - /// Coerce the value to an object, and throw a TypeError relating to object - /// receivers being null or undefined otherwise. - /// Note: The error may contain a non-spec info about the way in which it was to be used. - pub fn coerce_to_object_or_typeerror( + /// Same as get_property, but constructs a public Multiname for you. + pub fn get_public_property( &self, + name: impl Into>, activation: &mut Activation<'_, 'gc>, - name: Option<&Multiname<'gc>>, - ) -> Result, Error<'gc>> { - if matches!(self, Value::Null | Value::Undefined) { - return Err(error::make_null_or_undefined_error(activation, *self, name)); + ) -> Result, Error<'gc>> { + self.get_property( + &Multiname::new(activation.avm2().find_public_namespace(), name), + activation, + ) + } + + /// Set a property by Multiname lookup. + /// + /// This corresponds directly with the AVM2 operation `setproperty`, with + /// the exception that it does not special-case object lookups on + /// dictionary structured objects. + /// + /// This method will panic if called on null or undefined. + pub fn set_property( + &self, + multiname: &Multiname<'gc>, + value: Value<'gc>, + activation: &mut Activation<'_, 'gc>, + ) -> Result<(), Error<'gc>> { + let vtable = self.vtable(activation); + + match vtable.get_trait(multiname) { + Some(Property::Slot { slot_id }) => { + // Only objects can have slots + let object = self.as_object().unwrap(); + + object.set_slot(slot_id, value, activation) + } + Some(Property::Method { .. }) => { + if let Some(object) = self.as_object() { + // Similar to the get_property special case for XML/XMLList. + if (object.as_xml_object().is_some() || object.as_xml_list_object().is_some()) + && multiname.contains_public_namespace() + { + return object.set_property_local(multiname, value, activation); + } + } + + let instance_class = self.instance_class(activation); + + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::AssignToMethod, + multiname, + instance_class, + )) + } + Some(Property::Virtual { set: Some(set), .. }) => { + self.call_method(set, &[value], activation).map(|_| ()) + } + Some(Property::ConstSlot { .. }) | Some(Property::Virtual { set: None, .. }) => { + let instance_class = self.instance_class(activation); + + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::WriteToReadOnly, + multiname, + instance_class, + )) + } + None => { + if let Some(object) = self.as_object() { + object.set_property_local(multiname, value, activation) + } else { + let instance_class = self.instance_class(activation); + + // Primitive classes are sealed + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidWrite, + multiname, + instance_class, + )) + } + } } - self.coerce_to_object(activation) } - #[inline(always)] - pub fn null_check( + /// Same as set_property, but constructs a public Multiname for you. + pub fn set_public_property( &self, + name: impl Into>, + value: Value<'gc>, + activation: &mut Activation<'_, 'gc>, + ) -> Result<(), Error<'gc>> { + let name = Multiname::new(activation.avm2().namespaces.public_vm_internal(), name); + self.set_property(&name, value, activation) + } + + /// Initialize a property by Multiname lookup. + /// + /// This corresponds directly with the AVM2 operation `initproperty`. + /// + /// This method will panic if called on null or undefined. + pub fn init_property( + &self, + multiname: &Multiname<'gc>, + value: Value<'gc>, + activation: &mut Activation<'_, 'gc>, + ) -> Result<(), Error<'gc>> { + let vtable = self.vtable(activation); + + match vtable.get_trait(multiname) { + Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => { + // Only objects can have slots + let object = self.as_object().unwrap(); + + object.set_slot(slot_id, value, activation) + } + Some(Property::Method { .. }) => { + let instance_class = self.instance_class(activation); + + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::AssignToMethod, + multiname, + instance_class, + )) + } + Some(Property::Virtual { set: Some(set), .. }) => { + self.call_method(set, &[value], activation).map(|_| ()) + } + Some(Property::Virtual { set: None, .. }) => { + let instance_class = self.instance_class(activation); + + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::WriteToReadOnly, + multiname, + instance_class, + )) + } + None => { + if let Some(object) = self.as_object() { + object.init_property_local(multiname, value, activation) + } else { + let instance_class = self.instance_class(activation); + + // Primitive classes are sealed + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidWrite, + multiname, + instance_class, + )) + } + } + } + } + + /// Call a named property on the object. + /// + /// This corresponds directly to the `callproperty` operation in AVM2. + /// + /// This method will panic if called on null or undefined. + pub fn call_property( + &self, + multiname: &Multiname<'gc>, + arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc>, - name: Option<&Multiname<'gc>>, ) -> Result, Error<'gc>> { - if matches!(self, Value::Null | Value::Undefined) { - return Err(error::make_null_or_undefined_error(activation, *self, name)); + let vtable = self.vtable(activation); + + match vtable.get_trait(multiname) { + Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => { + // Only objects can have slots + let object = self.as_object().unwrap(); + + let func = object.get_slot(slot_id); + func.call(activation, *self, arguments) + } + Some(Property::Method { disp_id }) => self.call_method(disp_id, arguments, activation), + Some(Property::Virtual { get: Some(get), .. }) => { + let obj = self.call_method(get, &[], activation)?; + + obj.call(activation, *self, arguments) + } + Some(Property::Virtual { get: None, .. }) => { + let instance_class = self.instance_class(activation); + + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::ReadFromWriteOnly, + multiname, + instance_class, + )) + } + None => { + if let Some(object) = self.as_object() { + object.call_property_local(multiname, arguments, activation) + } else { + let instance_class = self.instance_class(activation); + + if !multiname.contains_public_namespace() { + return Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidRead, + multiname, + instance_class, + )); + } + + let Some(local_name) = multiname.local_name() else { + // when can this happen? + return Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidRead, + multiname, + instance_class, + )); + }; + + let key = crate::avm2::object::maybe_int_property(local_name); + + // Check prototype chain + let mut proto = self.proto(activation); + + while let Some(obj) = proto { + let obj = obj.base(); + let values = obj.values(); + let value = values.as_hashmap().get(&key); + if let Some(value) = value { + return value.value.call(activation, *self, arguments); + } + proto = obj.proto(); + } + + Err(Error::AvmError(type_error( + activation, + "Error #1006: value is not a function.", + 1006, + )?)) + } + } } + } - Ok(*self) + /// Same as call_property, but constructs a public Multiname for you. + pub fn call_public_property( + &self, + name: impl Into>, + arguments: &[Value<'gc>], + activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + self.call_property( + &Multiname::new(activation.avm2().find_public_namespace(), name), + arguments, + activation, + ) } - pub fn as_object(&self) -> Option> { + /// Call a method by its index. + /// + /// This directly corresponds with the AVM2 operation `callmethod`. + /// + /// This method will panic if called on null or undefined. + pub fn call_method( + &self, + id: u32, + arguments: &[Value<'gc>], + activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + // TODO: Bound methods should be cached on the Method in a + // WeakKeyHashMap, not on the Object + if let Some(object) = self.as_object() { + if let Some(bound_method) = object.get_bound_method(id) { + return bound_method.call(activation, *self, arguments); + } + } + + let vtable = self.vtable(activation); + + let full_method = vtable.get_full_method(id).expect("Method should exist"); + + // Execute immediately if this method doesn't require binding + if !full_method.method.needs_arguments_object() { + let ClassBoundMethod { + class, + super_class_obj, + scope, + method, + } = full_method; + + return exec( + method, + scope.expect("Scope should exist here"), + *self, + super_class_obj, + Some(class), + arguments, + activation, + *self, // Callee deliberately invalid. + ); + } + + let bound_method = VTable::bind_method(activation, *self, full_method); + + // TODO: Bound methods should be cached on the Method in a + // WeakKeyHashMap, not on the Object + if let Some(object) = self.as_object() { + object.install_bound_method(activation.gc(), id, bound_method); + } + + bound_method.call(activation, *self, arguments) + } + + /// Delete a named property from the value. + /// + /// Returns false if the property cannot be deleted. + /// + /// This method will return unexpected results if called on null or undefined! + /// The value should be `null_check`ed before calling this method on it! + pub fn delete_property( + &self, + activation: &mut Activation<'_, 'gc>, + multiname: &Multiname<'gc>, + ) -> Result> { match self { - Value::Object(o) => Some(*o), - _ => None, + Value::Object(object) => { + match object.vtable().get_trait(multiname) { + None => { + if object.instance_class().is_sealed() { + Ok(false) + } else { + object.delete_property_local(activation, multiname) + } + } + _ => { + // Similar to the get_property special case for XML/XMLList. + if (object.as_xml_object().is_some() + || object.as_xml_list_object().is_some()) + && multiname.contains_public_namespace() + { + return object.delete_property_local(activation, multiname); + } + + Ok(false) + } + } + } + _ => { + let instance_class = self.instance_class(activation); + + Err(error::make_reference_error( + activation, + error::ReferenceErrorCode::InvalidDelete, + multiname, + instance_class, + )) + } + } + } + + /// Returns true if the value has one or more traits of a given name. + /// + /// This method will panic if called on null or undefined. + pub fn has_trait(&self, activation: &mut Activation<'_, 'gc>, name: &Multiname<'gc>) -> bool { + self.vtable(activation).has_trait(name) + } + + /// Returns true if the value has one or more traits of a given name. + /// + /// This method will panic if called on null or undefined. + pub fn has_own_property( + &self, + activation: &mut Activation<'_, 'gc>, + name: &Multiname<'gc>, + ) -> bool { + match self { + Value::Object(object) => object.has_own_property(name), + _ => self.vtable(activation).has_trait(name), + } + } + + pub fn has_public_property( + self, + name: impl Into>, + activation: &mut Activation<'_, 'gc>, + ) -> bool { + let name = Multiname::new(activation.avm2().find_public_namespace(), name); + + if let Some(object) = self.as_object() { + if object.has_own_property(&name) { + return true; + } + } + + if let Some(proto) = self.proto(activation) { + proto.has_property(&name) + } else { + false } } @@ -930,11 +1412,11 @@ impl<'gc> Value<'gc> { &self, activation: &mut Activation<'_, 'gc>, args: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { match self.as_object() { Some(Object::ClassObject(class_object)) => class_object.construct(activation, args), Some(Object::FunctionObject(function_object)) => { - function_object.construct(activation, args) + function_object.construct(activation, args).map(Into::into) } _ => { let error = if activation.context.swf.version() < 11 { @@ -1005,7 +1487,7 @@ impl<'gc> Value<'gc> { let name = class.name().to_qualified_name_err_message(activation.gc()); let debug_str = match self { - Value::Object(obj) if obj.as_primitive().is_none() => { + Value::Object(obj) => { // Flash prints the class name (ignoring the toString() impl on the object), // followed by something that looks like an address (it varies between executions). // For now, we just set the "address" to all zeroes, on the off chance that some @@ -1027,12 +1509,7 @@ impl<'gc> Value<'gc> { /// Determine if this value is any kind of number. pub fn is_number(&self) -> bool { - match self { - Value::Number(_) => true, - Value::Integer(_) => true, - Value::Object(o) => o.as_primitive().is_some_and(|p| p.is_number()), - _ => false, - } + matches!(self, Value::Number(_) | Value::Integer(_)) } /// Determine if this value is a number representable as a u32 without loss @@ -1042,7 +1519,6 @@ impl<'gc> Value<'gc> { match self { Value::Number(n) => *n == (*n as u32 as f64), Value::Integer(i) => *i >= 0, - Value::Object(o) => o.as_primitive().is_some_and(|p| p.is_u32()), _ => false, } } @@ -1054,7 +1530,6 @@ impl<'gc> Value<'gc> { match self { Value::Number(n) => *n == (*n as i32 as f64), Value::Integer(_) => true, - Value::Object(o) => o.as_primitive().is_some_and(|p| p.is_i32()), _ => false, } } @@ -1099,6 +1574,24 @@ impl<'gc> Value<'gc> { } } + /// Get the vtable associated with this value. + /// + /// This function will panic if called on null or undefined. + pub fn vtable(&self, activation: &mut Activation<'_, 'gc>) -> VTable<'gc> { + let classes = activation.avm2().classes(); + + match self { + Value::Bool(_) => classes.boolean.instance_vtable(), + Value::Number(_) | Value::Integer(_) => classes.number.instance_vtable(), + Value::String(_) => classes.string.instance_vtable(), + Value::Object(obj) => obj.vtable(), + + Value::Undefined | Value::Null => { + unreachable!("Should not have Undefined or Null in `vtable`") + } + } + } + /// Get the class that this Value is of. /// /// This function will panic if called on null or undefined. diff --git a/core/src/avm2/vtable.rs b/core/src/avm2/vtable.rs index 3c10c481f8d2..a86902783c40 100644 --- a/core/src/avm2/vtable.rs +++ b/core/src/avm2/vtable.rs @@ -1,7 +1,7 @@ use crate::avm2::activation::Activation; use crate::avm2::metadata::Metadata; use crate::avm2::method::Method; -use crate::avm2::object::{ClassObject, FunctionObject, Object}; +use crate::avm2::object::{ClassObject, FunctionObject}; use crate::avm2::property::{Property, PropertyClass}; use crate::avm2::property_map::PropertyMap; use crate::avm2::scope::ScopeChain; @@ -492,7 +492,7 @@ impl<'gc> VTable<'gc> { /// Retrieve a bound instance method suitable for use as a value. /// - /// This returns the bound method object itself, as well as it's dispatch + /// This returns the bound method object itself, as well as its dispatch /// ID. You will need the additional properties in order to install the /// method into your object. /// @@ -500,10 +500,13 @@ impl<'gc> VTable<'gc> { /// the result. Otherwise, code that relies on bound methods having stable /// object identitities (e.g. `EventDispatcher.removeEventListener`) will /// fail. + /// + /// It is the caller's responsibility to ensure that the `receiver` passed + /// to this method is not Value::Null or Value::Undefined. pub fn make_bound_method( self, activation: &mut Activation<'_, 'gc>, - receiver: Object<'gc>, + receiver: Value<'gc>, disp_id: u32, ) -> Option> { self.get_full_method(disp_id) @@ -511,9 +514,12 @@ impl<'gc> VTable<'gc> { } /// Bind an instance method to a receiver, allowing it to be used as a value. See `VTable::make_bound_method` + /// + /// It is the caller's responsibility to ensure that the `receiver` passed + /// to this method is not Value::Null or Value::Undefined. pub fn bind_method( activation: &mut Activation<'_, 'gc>, - receiver: Object<'gc>, + receiver: Value<'gc>, method: ClassBoundMethod<'gc>, ) -> FunctionObject<'gc> { let ClassBoundMethod { diff --git a/core/src/debug_ui/avm2.rs b/core/src/debug_ui/avm2.rs index 69538bb4b295..8744fab7bb0b 100644 --- a/core/src/debug_ui/avm2.rs +++ b/core/src/debug_ui/avm2.rs @@ -359,6 +359,7 @@ impl Avm2ObjectWindow { }); }); }; + match prop { Property::Slot { slot_id } | Property::ConstSlot { slot_id } => { body.row(18.0, |mut row| { @@ -379,7 +380,7 @@ impl Avm2ObjectWindow { label_col(&mut row); row.col(|ui| { if self.call_getters { - let value = object.call_method(get, &[], activation); + let value = Value::from(object).call_method(get, &[], activation); ValueResultWidget::new(activation, value).show(ui, messages); } else { let value = self.getter_values.get_mut(&key); @@ -387,7 +388,8 @@ impl Avm2ObjectWindow { // Empty entry means we want to refresh it, // so let's do that now let widget = value.get_or_insert_with(|| { - let value = object.call_method(get, &[], activation); + let value = + Value::from(object).call_method(get, &[], activation); ValueResultWidget::new(activation, value) }); widget.show(ui, messages); diff --git a/core/src/display_object.rs b/core/src/display_object.rs index c6f5843a0486..14e837ba0e1d 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -2049,7 +2049,9 @@ pub trait TDisplayObject<'gc>: let mut activation = Avm2Activation::from_domain(context, domain); let name = Avm2Multiname::new(activation.avm2().find_public_namespace(), self.name()); - if let Err(e) = p.init_property(&name, c.into(), &mut activation) { + if let Err(e) = + Avm2Value::from(p).init_property(&name, c.into(), &mut activation) + { tracing::error!( "Got error when setting AVM2 child named \"{}\": {}", &self.name(), @@ -2743,6 +2745,8 @@ impl SoundTransform { .classes() .soundtransform .construct(activation, &[])? + .as_object() + .unwrap() .as_sound_transform() .unwrap(); diff --git a/core/src/display_object/container.rs b/core/src/display_object/container.rs index 9bad656dba85..d4929dcbb791 100644 --- a/core/src/display_object/container.rs +++ b/core/src/display_object/container.rs @@ -3,7 +3,7 @@ use crate::avm1::{Activation, ActivationIdentifier, TObject}; use crate::avm2::{ Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject, Multiname as Avm2Multiname, - TObject as _, Value as Avm2Value, + Value as Avm2Value, }; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::avm1_button::Avm1Button; @@ -719,6 +719,8 @@ impl<'gc> ChildContainer<'gc> { ); if child.has_explicit_name() { if let Avm2Value::Object(parent_obj) = parent.object2() { + let parent_obj = Avm2Value::from(parent_obj); + let mut activation = Avm2Activation::from_nothing(context); let name = Avm2Multiname::new( activation.avm2().find_public_namespace(), diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index f02123e0cac9..0605dc1f8f28 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -7,11 +7,12 @@ use crate::avm1::{ Object as Avm1Object, StageObject as Avm1StageObject, TObject as Avm1TObject, Value as Avm1Value, }; -use crate::avm2::Avm2; -use crate::avm2::{ - Activation as Avm2Activation, ClassObject as Avm2ClassObject, EventObject as Avm2EventObject, - Object as Avm2Object, StageObject as Avm2StageObject, TObject as _, +use crate::avm2::object::{ + ClassObject as Avm2ClassObject, EventObject as Avm2EventObject, Object as Avm2Object, + StageObject as Avm2StageObject, }; +use crate::avm2::Activation as Avm2Activation; +use crate::avm2::Avm2; use crate::backend::ui::MouseCursor; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::interactive::{ @@ -1878,7 +1879,7 @@ impl<'gc> EditText<'gc> { ); Avm2::dispatch_event(activation.context, text_evt, target); - if text_evt.as_event().unwrap().is_cancelled() { + if text_evt.event().is_cancelled() { return; } } diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index 209b48ad9c53..b109974c9c88 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -717,20 +717,20 @@ impl<'gc> Stage<'gc> { ); } } else if let Avm2Value::Object(stage) = self.object2() { - let full_screen_event_cls = context.avm2.classes().fullscreenevent; let mut activation = Avm2Activation::from_nothing(context); - let full_screen_event = full_screen_event_cls - .construct( - &mut activation, - &[ - "fullScreen".into(), - false.into(), - false.into(), - self.is_fullscreen().into(), - true.into(), - ], - ) - .unwrap(); // we don't expect to break here + + let full_screen_event_cls = activation.avm2().classes().fullscreenevent; + let full_screen_event = Avm2EventObject::from_class_and_args( + &mut activation, + full_screen_event_cls, + &[ + "fullScreen".into(), + false.into(), + false.into(), + self.is_fullscreen().into(), + true.into(), + ], + ); Avm2::dispatch_event(context, full_screen_event, stage); } @@ -794,6 +794,8 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> { .stage3d .construct(&mut activation, &[]) .expect("Failed to construct Stage3D") + .as_object() + .expect("Stage3D is an Object") }) .collect(); let mut write = self.0.write(activation.gc()); diff --git a/core/src/focus_tracker.rs b/core/src/focus_tracker.rs index a3cbfa8b298f..234764fde8cb 100644 --- a/core/src/focus_tracker.rs +++ b/core/src/focus_tracker.rs @@ -1,6 +1,6 @@ use crate::avm1::Avm1; use crate::avm1::Value; -use crate::avm2::{Activation, Avm2, EventObject, TObject}; +use crate::avm2::{Activation, Avm2, EventObject}; use crate::context::{RenderContext, UpdateContext}; pub use crate::display_object::{ DisplayObject, TDisplayObject, TDisplayObjectContainer, TextSelection, @@ -253,7 +253,7 @@ impl<'gc> FocusTracker<'gc> { EventObject::focus_event(&mut activation, event_type, true, related_object, key_code); Avm2::dispatch_event(activation.context, event, target); - let canceled = event.as_event().unwrap().is_cancelled(); + let canceled = event.event().is_cancelled(); canceled } diff --git a/core/src/loader.rs b/core/src/loader.rs index bcb67a94380e..57f7cf19d2b7 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -1587,7 +1587,6 @@ impl<'gc> Loader<'gc> { .urlvariables .construct(activation, &[string_value.into()]) .ok() - .map(|o| o.into()) } } else { if &data_format != b"text" { @@ -1624,39 +1623,17 @@ impl<'gc> Loader<'gc> { // FIXME - we should fire "progress" events as we receive data, not // just at the end - let progress_evt = activation - .avm2() - .classes() - .progressevent - .construct( - &mut activation, - &[ - "progress".into(), - false.into(), - false.into(), - total_len.into(), - total_len.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + let progress_evt = Avm2EventObject::progress_event( + &mut activation, + "progress", + total_len, + total_len, + ); Avm2::dispatch_event(activation.context, progress_evt, target); - let http_status_evt = activation - .avm2() - .classes() - .httpstatusevent - .construct( - &mut activation, - &[ - "httpStatus".into(), - false.into(), - false.into(), - status.into(), - redirected.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + let http_status_evt = + Avm2EventObject::http_status_event(&mut activation, status, redirected); Avm2::dispatch_event(activation.context, http_status_evt, target); @@ -1683,39 +1660,20 @@ impl<'gc> Loader<'gc> { } else { (0, false) }; - let http_status_evt = activation - .avm2() - .classes() - .httpstatusevent - .construct( - &mut activation, - &[ - "httpStatus".into(), - false.into(), - false.into(), - status_code.into(), - redirected.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + let http_status_evt = Avm2EventObject::http_status_event( + &mut activation, + status_code, + redirected, + ); Avm2::dispatch_event(activation.context, http_status_evt, target); // FIXME - Match the exact error message generated by Flash - - let io_error_evt_cls = activation.avm2().classes().ioerrorevent; - let io_error_evt = io_error_evt_cls - .construct( - &mut activation, - &[ - "ioError".into(), - false.into(), - false.into(), - "Error #2032: Stream Error".into(), - 2032.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + let io_error_evt = Avm2EventObject::io_error_event( + &mut activation, + "Error #2032: Stream Error".into(), + 2032, + ); Avm2::dispatch_event(uc, io_error_evt, target); } @@ -1844,21 +1802,12 @@ impl<'gc> Loader<'gc> { // FIXME - As in load_url_loader, we should fire "progress" events as we receive data, // not just at the end - let progress_evt = activation - .avm2() - .classes() - .progressevent - .construct( - &mut activation, - &[ - "progress".into(), - false.into(), - false.into(), - total_len.into(), - total_len.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + let progress_evt = Avm2EventObject::progress_event( + &mut activation, + "progress", + total_len, + total_len, + ); Avm2::dispatch_event(activation.context, progress_evt, sound_object); @@ -1872,21 +1821,14 @@ impl<'gc> Loader<'gc> { Avm2::dispatch_event(activation.context, complete_evt, sound_object); } Err(_err) => { - // FIXME: Match the exact error message generated by Flash. let mut activation = Avm2Activation::from_nothing(uc); - let io_error_evt_cls = activation.avm2().classes().ioerrorevent; - let io_error_evt = io_error_evt_cls - .construct( - &mut activation, - &[ - "ioError".into(), - false.into(), - false.into(), - "Error #2032: Stream Error".into(), - 2032.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + + // FIXME: Match the exact error message generated by Flash. + let io_error_evt = Avm2EventObject::io_error_event( + &mut activation, + "Error #2032: Stream Error".into(), + 2032, + ); Avm2::dispatch_event(uc, io_error_evt, sound_object); } @@ -2227,7 +2169,10 @@ impl<'gc> Loader<'gc> { .classes() .bitmap .construct(&mut activation, &[bitmapdata_avm2.into()]) + .unwrap() + .as_object() .unwrap(); + let bitmap_dobj = bitmap_avm2.as_display_object().unwrap(); if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data { @@ -2386,21 +2331,12 @@ impl<'gc> Loader<'gc> { MovieLoaderVMData::Avm2 { loader_info, .. } => { let mut activation = Avm2Activation::from_nothing(uc); - let progress_evt = activation - .avm2() - .classes() - .progressevent - .construct( - &mut activation, - &[ - "progress".into(), - false.into(), - false.into(), - cur_len.into(), - total_len.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + let progress_evt = Avm2EventObject::progress_event( + &mut activation, + "progress", + cur_len, + total_len, + ); Avm2::dispatch_event(uc, progress_evt, loader_info.into()); } @@ -2610,39 +2546,13 @@ impl<'gc> Loader<'gc> { MovieLoaderVMData::Avm2 { loader_info, .. } => { let mut activation = Avm2Activation::from_nothing(uc); - let http_status_evt = activation - .avm2() - .classes() - .httpstatusevent - .construct( - &mut activation, - &[ - "httpStatus".into(), - false.into(), - false.into(), - status.into(), - redirected.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + let http_status_evt = + Avm2EventObject::http_status_event(&mut activation, status, redirected); Avm2::dispatch_event(activation.context, http_status_evt, loader_info.into()); - // FIXME - Match the exact error message generated by Flash - - let io_error_evt_cls = activation.avm2().classes().ioerrorevent; - let io_error_evt = io_error_evt_cls - .construct( - &mut activation, - &[ - "ioError".into(), - false.into(), - false.into(), - msg.into(), - 0.into(), - ], - ) - .map_err(|e| Error::Avm2Error(e.to_string()))?; + // FIXME - Match the exact error message and code generated by Flash + let io_error_evt = Avm2EventObject::io_error_event(&mut activation, msg, 0); Avm2::dispatch_event(uc, io_error_evt, loader_info.into()); } @@ -2932,14 +2842,11 @@ impl<'gc> Loader<'gc> { target_object.into(), ); - let size = data.len() as u64; let progress_evt = Avm2EventObject::progress_event( &mut activation, "progress", - size, - size, - false, - false, + data.len(), + data.len(), ); Avm2::dispatch_event( activation.context, diff --git a/core/src/net_connection.rs b/core/src/net_connection.rs index da0d07937a05..140512d54dae 100644 --- a/core/src/net_connection.rs +++ b/core/src/net_connection.rs @@ -144,7 +144,6 @@ impl<'gc> NetConnections<'gc> { let mut activation = Avm2Activation::from_nothing(context); let event = Avm2EventObject::net_status_event( &mut activation, - "netStatus", vec![ ("code", "NetConnection.Connect.Success"), ("level", "status"), @@ -197,7 +196,6 @@ impl<'gc> NetConnections<'gc> { let mut activation = Avm2Activation::from_nothing(context); let event = Avm2EventObject::net_status_event( &mut activation, - "netStatus", vec![ ("code", "NetConnection.Connect.Closed"), ("level", "status"), @@ -211,7 +209,6 @@ impl<'gc> NetConnections<'gc> { // [NA] I have no idea why, but a NetConnection receives a second and nonsensical event on close let event = Avm2EventObject::net_status_event( &mut activation, - "netStatus", vec![ ("code", ""), ("description", ""), @@ -546,7 +543,6 @@ impl FlashRemoting { let url = AvmString::new_utf8(activation.gc(), response.url); let event = Avm2EventObject::net_status_event( &mut activation, - "netStatus", vec![ ("code", "NetConnection.Call.Failed".into()), ("level", "error".into()), diff --git a/core/src/player.rs b/core/src/player.rs index 3c750a216950..e32bf7373c7c 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -5,7 +5,8 @@ use crate::avm1::SystemProperties; use crate::avm1::VariableDumper; use crate::avm1::{Activation, ActivationIdentifier}; use crate::avm1::{TObject, Value}; -use crate::avm2::{Activation as Avm2Activation, Avm2, CallStack, Object as Avm2Object}; +use crate::avm2::object::{EventObject as Avm2EventObject, Object as Avm2Object}; +use crate::avm2::{Activation as Avm2Activation, Avm2, CallStack}; use crate::backend::ui::FontDefinition; use crate::backend::{ audio::{AudioBackend, AudioManager}, @@ -655,21 +656,18 @@ impl Player { if let Some(menu_object) = menu_object { // TODO: contextMenuOwner and mouseTarget might not be the same - let menu_evt = activation - .avm2() - .classes() - .contextmenuevent - .construct( - &mut activation, - &[ - "menuSelect".into(), - false.into(), - false.into(), - hit_obj.into(), - hit_obj.into(), - ], - ) - .expect("Context menu event should be constructed!"); + let context_menu_event_cls = activation.avm2().classes().contextmenuevent; + let menu_evt = Avm2EventObject::from_class_and_args( + &mut activation, + context_menu_event_cls, + &[ + "menuSelect".into(), + false.into(), + false.into(), + hit_obj.into(), + hit_obj.into(), + ], + ); Avm2::dispatch_event(activation.context, menu_evt, menu_object); } @@ -719,21 +717,19 @@ impl Player { if menu_obj.is_some() { // TODO: contextMenuOwner and mouseTarget might not be the same (see above comment) - let menu_evt = activation - .avm2() - .classes() - .contextmenuevent - .construct( - &mut activation, - &[ - "menuItemSelect".into(), - false.into(), - false.into(), - display_obj.object2(), - display_obj.object2(), - ], - ) - .expect("Context menu event should be constructed!"); + let context_menu_event_cls = + activation.avm2().classes().contextmenuevent; + let menu_evt = Avm2EventObject::from_class_and_args( + &mut activation, + context_menu_event_cls, + &[ + "menuItemSelect".into(), + false.into(), + false.into(), + display_obj.object2(), + display_obj.object2(), + ], + ); Avm2::dispatch_event(context, menu_evt, menu_item); } @@ -1157,23 +1153,23 @@ impl Player { // TODO: keyLocation should not be a dummy value. // ctrlKey and controlKey can be different from each other on Mac. // commandKey should be supported. - let keyboard_event = keyboardevent_class - .construct( - &mut activation, - &[ - event_name_val, /* type */ - true.into(), /* bubbles */ - false.into(), /* cancelable */ - key_char.map_or(0, |c| c as u32).into(), /* charCode */ - key_code.value().into(), /* keyCode */ - 0.into(), /* keyLocation */ - ctrl_key.into(), /* ctrlKey */ - alt_key.into(), /* altKey */ - shift_key.into(), /* shiftKey */ - ctrl_key.into(), /* controlKey */ - ], - ) - .expect("Failed to construct KeyboardEvent"); + let keyboard_event = Avm2EventObject::from_class_and_args( + &mut activation, + keyboardevent_class, + &[ + event_name_val, /* type */ + true.into(), /* bubbles */ + false.into(), /* cancelable */ + key_char.map_or(0, |c| c as u32).into(), /* charCode */ + key_code.value().into(), /* keyCode */ + 0.into(), /* keyLocation */ + ctrl_key.into(), /* ctrlKey */ + alt_key.into(), /* altKey */ + shift_key.into(), /* shiftKey */ + ctrl_key.into(), /* controlKey */ + ], + ); + let target_object = activation .context .focus_tracker @@ -1184,7 +1180,7 @@ impl Player { if target_object.movie().is_action_script_3() { let target = target_object .object2() - .coerce_to_object(&mut activation) + .as_object() .expect("DisplayObject is not an object!"); Avm2::dispatch_event(activation.context, keyboard_event, target); @@ -1880,26 +1876,14 @@ impl Player { if let Some(loader_info) = root.loader_info().filter(|_| !was_root_movie_loaded) { let mut activation = Avm2Activation::from_nothing(context); - let progress_evt = activation.avm2().classes().progressevent.construct( + let progress_evt = Avm2EventObject::progress_event( &mut activation, - &[ - "progress".into(), - false.into(), - false.into(), - root.compressed_loaded_bytes().into(), - root.compressed_total_bytes().into(), - ], + "progress", + root.compressed_loaded_bytes() as usize, + root.compressed_total_bytes() as usize, ); - match progress_evt { - Err(e) => tracing::error!( - "Encountered AVM2 error when constructing `progress` event: {}", - e, - ), - Ok(progress_evt) => { - Avm2::dispatch_event(context, progress_evt, loader_info); - } - } + Avm2::dispatch_event(context, progress_evt, loader_info); } } diff --git a/core/src/socket.rs b/core/src/socket.rs index 083d5ab105a2..251c3eede4c2 100644 --- a/core/src/socket.rs +++ b/core/src/socket.rs @@ -1,13 +1,13 @@ -use crate::{ - avm1::{ - globals::xml_socket::XmlSocket, Activation as Avm1Activation, ActivationIdentifier, - ExecutionReason, Object as Avm1Object, TObject as Avm1TObject, - }, - avm2::{object::SocketObject, Activation as Avm2Activation, Avm2, EventObject}, - backend::navigator::NavigatorBackend, - context::UpdateContext, - string::AvmString, +use crate::avm1::{ + globals::xml_socket::XmlSocket, Activation as Avm1Activation, ActivationIdentifier, + ExecutionReason, Object as Avm1Object, TObject as Avm1TObject, }; +use crate::avm2::object::{EventObject, SocketObject}; +use crate::avm2::{Activation as Avm2Activation, Avm2}; +use crate::backend::navigator::NavigatorBackend; +use crate::context::UpdateContext; +use crate::string::AvmString; + use async_channel::{unbounded, Receiver, Sender as AsyncSender, Sender}; use gc_arena::Collect; use slotmap::{new_key_type, SlotMap}; @@ -258,21 +258,11 @@ impl<'gc> Sockets<'gc> { SocketKind::Avm2(target) => { let mut activation = Avm2Activation::from_nothing(context); - let io_error_evt = activation - .avm2() - .classes() - .ioerrorevent - .construct( - &mut activation, - &[ - "ioError".into(), - false.into(), - false.into(), - "Error #2031: Socket Error.".into(), - 2031.into(), - ], - ) - .expect("IOErrorEvent should be constructed"); + let io_error_evt = EventObject::io_error_event( + &mut activation, + "Error #2031: Socket Error.".into(), + 2031, + ); Avm2::dispatch_event(activation.context, io_error_evt, target.into()); } @@ -306,22 +296,12 @@ impl<'gc> Sockets<'gc> { let bytes_loaded = data.len(); target.read_buffer().extend(data); - let progress_evt = activation - .avm2() - .classes() - .progressevent - .construct( - &mut activation, - &[ - "socketData".into(), - false.into(), - false.into(), - bytes_loaded.into(), - //NOTE: bytesTotal is not used by socketData event. - 0.into(), - ], - ) - .expect("ProgressEvent should be constructed"); + let progress_evt = EventObject::progress_event( + &mut activation, + "socketData", + bytes_loaded, + 0, // NOTE: bytesTotal is not used by socketData event. + ); Avm2::dispatch_event(activation.context, progress_evt, target.into()); } diff --git a/core/src/streams.rs b/core/src/streams.rs index 514808da2f4e..1c9ad4b15429 100644 --- a/core/src/streams.rs +++ b/core/src/streams.rs @@ -5,10 +5,9 @@ use crate::avm1::{ ExecutionReason as Avm1ExecutionReason, FlvValueAvm1Ext, ScriptObject as Avm1ScriptObject, TObject as Avm1TObject, Value as Avm1Value, }; -use crate::avm2::object::TObject as Avm2TObject; use crate::avm2::{ Activation as Avm2Activation, Avm2, Error as Avm2Error, EventObject as Avm2EventObject, - FlvValueAvm2Ext, Object as Avm2Object, + FlvValueAvm2Ext, Object as Avm2Object, Value as Avm2Value, }; use crate::backend::audio::{ DecodeError, SoundInstanceHandle, SoundStreamInfo, SoundStreamWrapping, @@ -1316,8 +1315,7 @@ impl<'gc> NetStream<'gc> { Some(AvmObject::Avm2(object)) => { let domain = context.avm2.stage_domain(); let mut activation = Avm2Activation::from_domain(context, domain); - let net_status_event = - Avm2EventObject::net_status_event(&mut activation, "netStatus", values); + let net_status_event = Avm2EventObject::net_status_event(&mut activation, values); Avm2::dispatch_event(activation.context, net_status_event, object); } None => {} @@ -1365,7 +1363,7 @@ impl<'gc> NetStream<'gc> { let data_object = variable_data.to_avm2_value(&mut activation); - client_object.call_public_property( + Avm2Value::from(client_object).call_public_property( AvmString::new_utf8_bytes(activation.gc(), variable_name), &[data_object], &mut activation, diff --git a/tests/tests/swfs/avm2/verify_abnormal_loop/output.txt b/tests/tests/swfs/avm2/verify_abnormal_loop/output.txt index c46babdf9277..da421e24ed76 100644 --- a/tests/tests/swfs/avm2/verify_abnormal_loop/output.txt +++ b/tests/tests/swfs/avm2/verify_abnormal_loop/output.txt @@ -1 +1 @@ -ReferenceError: Error #1069: Property field not found on int and there is no default value. +ReferenceError: Error #1069: Property field not found on Number and there is no default value. diff --git a/tests/tests/swfs/from_avmplus/as3/Vector/every/test.toml b/tests/tests/swfs/from_avmplus/as3/Vector/every/test.toml index 661aaf82f605..cf6123969a1d 100644 --- a/tests/tests/swfs/from_avmplus/as3/Vector/every/test.toml +++ b/tests/tests/swfs/from_avmplus/as3/Vector/every/test.toml @@ -1,2 +1 @@ num_ticks = 1 -known_failure = true # https://github.com/ruffle-rs/ruffle/issues/12321 diff --git a/tests/tests/swfs/from_avmplus/ecma3/TypeConversion/e9_9_1_rt/test.toml b/tests/tests/swfs/from_avmplus/ecma3/TypeConversion/e9_9_1_rt/test.toml index 29f3cef79022..cf6123969a1d 100644 --- a/tests/tests/swfs/from_avmplus/ecma3/TypeConversion/e9_9_1_rt/test.toml +++ b/tests/tests/swfs/from_avmplus/ecma3/TypeConversion/e9_9_1_rt/test.toml @@ -1,2 +1 @@ num_ticks = 1 -known_failure = true