diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index eb0d6dbec..3f5dda031 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -1799,7 +1799,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { ); } - constructor.call("[ctor]", self, context, this, None, &args)?; + constructor.construct("[ctor]", self, context, this, None, &args)?; self.avm.push(this); diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index 5cc6be7f1..a409dcf79 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -419,6 +419,8 @@ pub struct FunctionObject<'gc> { struct FunctionObjectData<'gc> { /// The code that will be invoked when this object is called. function: Option>, + /// The code that will be invoked when this object is constructed. + constructor: Option>, /// The value to be returned by `toString` and `valueOf`. primitive: Value<'gc>, @@ -428,24 +430,29 @@ impl<'gc> FunctionObject<'gc> { /// Construct a function sans prototype. pub fn bare_function( gc_context: MutationContext<'gc, '_>, - function: impl Into>, + function: Option>>, + constructor: Option>>, fn_proto: Option>, ) -> Self { let base = ScriptObject::object(gc_context, fn_proto); + let func = function.map(|x| x.into()); + let cons = constructor.map(|x| x.into()); + FunctionObject { base, data: GcCell::allocate( gc_context, FunctionObjectData { - function: Some(function.into()), + function: func, primitive: "[type Function]".into(), + constructor: cons, }, ), } } - /// Construct a function from an executable and associated protos. + /// Construct a function with any combination of regular and constructor parts. /// /// Since prototypes need to link back to themselves, this function builds /// both objects itself and returns the function to you, fully allocated. @@ -453,13 +460,14 @@ impl<'gc> FunctionObject<'gc> { /// `fn_proto` refers to the implicit proto of the function object, and the /// `prototype` refers to the explicit prototype of the function. If /// provided, the function and it's prototype will be linked to each other. - pub fn function( + fn allocate_function( context: MutationContext<'gc, '_>, - function: impl Into>, + function: Option>>, + constructor: Option>>, fn_proto: Option>, prototype: Option>, ) -> Object<'gc> { - let function = Self::bare_function(context, function, fn_proto).into(); + let function = Self::bare_function(context, function, constructor, fn_proto).into(); if let Some(p) = prototype { p.define_value( @@ -473,6 +481,47 @@ impl<'gc> FunctionObject<'gc> { function } + + /// Construct a regular function from an executable and associated protos. + pub fn function( + context: MutationContext<'gc, '_>, + function: impl Into>, + fn_proto: Option>, + prototype: Option>, + ) -> Object<'gc> { + // Avoid type inference issues + let none: Option = None; + Self::allocate_function(context, Some(function), none, fn_proto, prototype) + } + + /// Construct a constructor function from an executable and associated protos. + pub fn constructor( + context: MutationContext<'gc, '_>, + constructor: impl Into>, + fn_proto: Option>, + prototype: Option>, + ) -> Object<'gc> { + // Avoid type inference issues + let none: Option = None; + Self::allocate_function(context, none, Some(constructor), fn_proto, prototype) + } + + /// Construct a regular and constructor function from an executable and associated protos. + pub fn function_and_constructor( + context: MutationContext<'gc, '_>, + function: impl Into>, + constructor: impl Into>, + fn_proto: Option>, + prototype: Option>, + ) -> Object<'gc> { + Self::allocate_function( + context, + Some(function), + Some(constructor), + fn_proto, + prototype, + ) + } } impl<'gc> TObject<'gc> for FunctionObject<'gc> { @@ -521,6 +570,32 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { } } + fn construct( + &self, + name: &str, + activation: &mut Activation<'_, 'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + base_proto: Option>, + args: &[Value<'gc>], + ) -> Result, Error<'gc>> { + println!("Constructing a function {}: {:?}", name, &self.data.read()); + if let Some(exec) = &self.data.read().constructor { + exec.exec( + name, + activation, + context, + this, + base_proto, + args, + ExecutionReason::FunctionCall, + (*self).into(), + ) + } else { + self.call(name, activation, context, this, base_proto, args) + } + } + fn call_setter( &self, name: &str, @@ -547,6 +622,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { FunctionObjectData { function: None, primitive: "[type Function]".into(), + constructor: None, }, ), }; diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index c9494d5d8..159ab4e0d 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -282,74 +282,74 @@ pub fn create_globals<'gc>( let context_menu_item_proto = context_menu_item::create_proto(gc_context, object_proto, function_proto); - let button = FunctionObject::function( + let button = FunctionObject::constructor( gc_context, Executable::Native(button::constructor), Some(function_proto), Some(button_proto), ); - let color = FunctionObject::function( + let color = FunctionObject::constructor( gc_context, Executable::Native(color::constructor), Some(function_proto), Some(color_proto), ); - let error = FunctionObject::function( + let error = FunctionObject::constructor( gc_context, Executable::Native(error::constructor), Some(function_proto), Some(error_proto), ); - let function = FunctionObject::function( + let function = FunctionObject::constructor( gc_context, Executable::Native(function::constructor), Some(function_proto), Some(function_proto), ); - let load_vars = FunctionObject::function( + let load_vars = FunctionObject::constructor( gc_context, Executable::Native(load_vars::constructor), Some(function_proto), Some(load_vars_proto), ); - let movie_clip = FunctionObject::function( + let movie_clip = FunctionObject::constructor( gc_context, Executable::Native(movie_clip::constructor), Some(function_proto), Some(movie_clip_proto), ); - let movie_clip_loader = FunctionObject::function( + let movie_clip_loader = FunctionObject::constructor( gc_context, Executable::Native(movie_clip_loader::constructor), Some(function_proto), Some(movie_clip_loader_proto), ); - let sound = FunctionObject::function( + let sound = FunctionObject::constructor( gc_context, Executable::Native(sound::constructor), Some(function_proto), Some(sound_proto), ); - let text_field = FunctionObject::function( + let text_field = FunctionObject::constructor( gc_context, Executable::Native(text_field::constructor), Some(function_proto), Some(text_field_proto), ); - let text_format = FunctionObject::function( + let text_format = FunctionObject::constructor( gc_context, Executable::Native(text_format::constructor), Some(function_proto), Some(text_format_proto), ); let array = array::create_array_object(gc_context, Some(array_proto), Some(function_proto)); - let xmlnode = FunctionObject::function( + let xmlnode = FunctionObject::constructor( gc_context, Executable::Native(xml::xmlnode_constructor), Some(function_proto), Some(xmlnode_proto), ); - let xml = FunctionObject::function( + let xml = FunctionObject::constructor( gc_context, Executable::Native(xml::xml_constructor), Some(function_proto), @@ -434,7 +434,7 @@ pub fn create_globals<'gc>( globals.define_value( gc_context, "ContextMenu", - FunctionObject::function( + FunctionObject::constructor( gc_context, Executable::Native(context_menu::constructor), Some(function_proto), @@ -447,7 +447,7 @@ pub fn create_globals<'gc>( globals.define_value( gc_context, "ContextMenuItem", - FunctionObject::function( + FunctionObject::constructor( gc_context, Executable::Native(context_menu_item::constructor), Some(function_proto), diff --git a/core/src/avm1/globals/array.rs b/core/src/avm1/globals/array.rs index 038de554d..86261c95a 100644 --- a/core/src/avm1/globals/array.rs +++ b/core/src/avm1/globals/array.rs @@ -38,7 +38,7 @@ pub fn create_array_object<'gc>( array_proto: Option>, fn_proto: Option>, ) -> Object<'gc> { - let array = FunctionObject::function( + let array = FunctionObject::constructor( gc_context, Executable::Native(constructor), fn_proto, @@ -87,7 +87,7 @@ pub fn create_array_object<'gc>( array } -/// Implements `Array` +/// Implements `Array` constructor pub fn constructor<'gc>( activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, diff --git a/core/src/avm1/globals/boolean.rs b/core/src/avm1/globals/boolean.rs index eb6854f4e..54a5fb038 100644 --- a/core/src/avm1/globals/boolean.rs +++ b/core/src/avm1/globals/boolean.rs @@ -9,25 +9,40 @@ use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; -/// `Boolean` constructor/function -pub fn boolean<'gc>( +/// `Boolean` constructor +pub fn constructor<'gc>( activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let (ret_value, cons_value) = if let Some(val) = args.get(0) { - let b = Value::Bool(val.as_bool(activation.current_swf_version())); - (b.clone(), b) + let cons_value = if let Some(val) = args.get(0) { + Value::Bool(val.as_bool(activation.current_swf_version())) } else { - (Value::Undefined, Value::Bool(false)) + Value::Bool(false) }; - // If called from a constructor, populate `this`. + // Called from a constructor, populate `this`. if let Some(mut vbox) = this.as_value_object() { vbox.replace_value(context.gc_context, cons_value); } + Ok(Value::Undefined) +} + +/// `Boolean` function +pub fn boolean_function<'gc>( + activation: &mut Activation<'_, 'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let ret_value = if let Some(val) = args.get(0) { + Value::Bool(val.as_bool(activation.current_swf_version())) + } else { + Value::Undefined + }; + // If called as a function, return the value. // Boolean() with no argument returns undefined. Ok(ret_value) @@ -38,9 +53,10 @@ pub fn create_boolean_object<'gc>( boolean_proto: Option>, fn_proto: Option>, ) -> Object<'gc> { - FunctionObject::function( + FunctionObject::function_and_constructor( gc_context, - Executable::Native(boolean), + Executable::Native(boolean_function), + Executable::Native(constructor), fn_proto, boolean_proto, ) diff --git a/core/src/avm1/globals/number.rs b/core/src/avm1/globals/number.rs index a9ffd798f..d44936ae0 100644 --- a/core/src/avm1/globals/number.rs +++ b/core/src/avm1/globals/number.rs @@ -10,7 +10,7 @@ use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; -/// `Number` constructor/function +/// `Number` constructor pub fn number<'gc>( activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -28,6 +28,22 @@ pub fn number<'gc>( vbox.replace_value(context.gc_context, value.into()); } + Ok(Value::Undefined) +} + +/// `Number` function +pub fn number_function<'gc>( + activation: &mut Activation<'_, 'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let value = if let Some(val) = args.get(0) { + val.coerce_to_f64(activation, context)? + } else { + 0.0 + }; + // If Number is called as a function, return the value. Ok(value.into()) } @@ -37,8 +53,9 @@ pub fn create_number_object<'gc>( number_proto: Option>, fn_proto: Option>, ) -> Object<'gc> { - let number = FunctionObject::function( + let number = FunctionObject::function_and_constructor( gc_context, + Executable::Native(number_function), Executable::Native(number), fn_proto, number_proto, diff --git a/core/src/avm1/globals/object.rs b/core/src/avm1/globals/object.rs index 126cb33a7..5a486edcf 100644 --- a/core/src/avm1/globals/object.rs +++ b/core/src/avm1/globals/object.rs @@ -372,7 +372,7 @@ pub fn create_object_object<'gc>( proto: Object<'gc>, fn_proto: Object<'gc>, ) -> Object<'gc> { - let object_function = FunctionObject::function( + let object_function = FunctionObject::constructor( gc_context, Executable::Native(constructor), Some(fn_proto), diff --git a/core/src/avm1/globals/string.rs b/core/src/avm1/globals/string.rs index a9fafb64d..cd4d5a885 100644 --- a/core/src/avm1/globals/string.rs +++ b/core/src/avm1/globals/string.rs @@ -30,6 +30,22 @@ pub fn string<'gc>( vbox.replace_value(ac.gc_context, value.clone().into()); } + Ok(Value::Undefined) +} + +/// `String` function +pub fn string_function<'gc>( + activation: &mut Activation<'_, 'gc>, + ac: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let value = match args.get(0).cloned() { + Some(Value::String(s)) => s, + Some(v) => v.coerce_to_string(activation, ac)?, + _ => AvmString::new(ac.gc_context, String::new()), + }; + Ok(value.into()) } @@ -38,8 +54,9 @@ pub fn create_string_object<'gc>( string_proto: Option>, fn_proto: Option>, ) -> Object<'gc> { - let string = FunctionObject::function( + let string = FunctionObject::function_and_constructor( gc_context, + Executable::Native(string_function), Executable::Native(string), fn_proto, string_proto, diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index 455f29913..0194467b9 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -108,6 +108,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy args: &[Value<'gc>], ) -> Result, Error<'gc>>; + /// Construct the underlying object. + fn construct( + &self, + _name: &str, + _activation: &mut Activation<'_, 'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _base_proto: Option>, + _args: &[Value<'gc>], + ) -> Result, Error<'gc>> { + Ok(Value::Undefined) + } + /// Call a method on the object. /// /// It is highly recommended to use this convenience method to perform diff --git a/core/src/avm1/object/value_object.rs b/core/src/avm1/object/value_object.rs index 454bb40d9..06875f3b1 100644 --- a/core/src/avm1/object/value_object.rs +++ b/core/src/avm1/object/value_object.rs @@ -62,7 +62,7 @@ impl<'gc> ValueObject<'gc> { // Constructor populates the boxed object with the value. match &value { Value::Bool(_) => { - let _ = crate::avm1::globals::boolean::boolean( + let _ = crate::avm1::globals::boolean::constructor( activation, context, obj.into(),