diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index fd40d18f7..84844fad3 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -19,6 +19,7 @@ pub(crate) mod movie_clip; mod object; mod sound; mod stage; +mod string; pub(crate) mod text_field; mod xml; @@ -140,6 +141,7 @@ pub struct SystemPrototypes<'gc> { pub text_field: Object<'gc>, pub array: Object<'gc>, pub xml_node: Object<'gc>, + pub string: Object<'gc>, } unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> { @@ -152,6 +154,7 @@ unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> { self.text_field.trace(cc); self.array.trace(cc); self.xml_node.trace(cc); + self.string.trace(cc); } } @@ -180,6 +183,8 @@ pub fn create_globals<'gc>( let xml_proto: Object<'gc> = xml::create_xml_proto(gc_context, xmlnode_proto, function_proto); + let string_proto: Object<'gc> = string::create_proto(gc_context, object_proto, function_proto); + //TODO: These need to be constructors and should also set `.prototype` on each one let object = ScriptObject::function( gc_context, @@ -236,6 +241,12 @@ pub fn create_globals<'gc>( Some(function_proto), Some(xml_proto), ); + let string = ScriptObject::function( + gc_context, + Executable::Native(string::string_constructor), + Some(function_proto), + Some(string_proto), + ); let listeners = SystemListeners::new(gc_context, Some(array_proto)); @@ -249,6 +260,7 @@ pub fn create_globals<'gc>( globals.define_value(gc_context, "TextField", text_field.into(), EnumSet::empty()); globals.define_value(gc_context, "XMLNode", xmlnode.into(), EnumSet::empty()); globals.define_value(gc_context, "XML", xml.into(), EnumSet::empty()); + globals.define_value(gc_context, "String", string.into(), EnumSet::empty()); globals.force_set_function( "Number", number, @@ -357,6 +369,7 @@ pub fn create_globals<'gc>( text_field: text_field_proto, array: array_proto, xml_node: xmlnode_proto, + string: string_proto, }, globals.into(), listeners, diff --git a/core/src/avm1/globals/string.rs b/core/src/avm1/globals/string.rs new file mode 100644 index 000000000..8ca2ead8c --- /dev/null +++ b/core/src/avm1/globals/string.rs @@ -0,0 +1,90 @@ +//! `String` class impl + +use crate::avm1::return_value::ReturnValue; +use crate::avm1::value_object::ValueObject; +use crate::avm1::{Avm1, Error, Object, TObject, Value}; +use crate::context::UpdateContext; +use enumset::EnumSet; +use gc_arena::MutationContext; + +/// `String` constructor +pub fn string_constructor<'gc>( + avm: &mut Avm1<'gc>, + ac: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error> { + let arg = args + .get(0) + .cloned() + .unwrap_or(Value::Undefined) + .coerce_to_string(avm, ac)?; + + if let Some(mut vbox) = this.as_value_object() { + vbox.replace_value(ac.gc_context, arg.into()); + } + + Ok(Value::Undefined.into()) +} + +/// `String.toString` / `String.valueOf` impl +pub fn to_string_value_of<'gc>( + avm: &mut Avm1<'gc>, + ac: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + if let Some(vbox) = this.as_value_object() { + return Ok(vbox.unbox().coerce_to_string(avm, ac)?.into()); + } + + //TODO: This normally falls back to `[object Object]` or `[type Function]`, + //implying that `toString` and `valueOf` are inherent object properties and + //not just methods. + Ok(Value::Undefined.into()) +} + +/// `String.toUpperCase` impl +pub fn to_upper_case<'gc>( + avm: &mut Avm1<'gc>, + ac: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + let sval = Value::Object(this).coerce_to_string(avm, ac)?; + + Ok(sval.to_uppercase().into()) +} + +/// `String.prototype` definition +pub fn create_proto<'gc>( + gc_context: MutationContext<'gc, '_>, + proto: Object<'gc>, + fn_proto: Object<'gc>, +) -> Object<'gc> { + let string_proto = ValueObject::empty_box(gc_context, Some(proto)); + + string_proto.as_script_object().unwrap().force_set_function( + "toString", + to_string_value_of, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + string_proto.as_script_object().unwrap().force_set_function( + "valueOf", + to_string_value_of, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + string_proto.as_script_object().unwrap().force_set_function( + "toUpperCase", + to_upper_case, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + + string_proto +} diff --git a/core/src/avm1/value_object.rs b/core/src/avm1/value_object.rs index 8fe33a165..bf1198d5f 100644 --- a/core/src/avm1/value_object.rs +++ b/core/src/avm1/value_object.rs @@ -42,16 +42,21 @@ impl<'gc> ValueObject<'gc> { pub fn boxed( gc_context: MutationContext<'gc, '_>, value: Value<'gc>, - _system_prototypes: &SystemPrototypes<'gc>, + system_prototypes: &SystemPrototypes<'gc>, ) -> Object<'gc> { if let Value::Object(ob) = value { ob } else { + let proto = match value { + Value::String(_) => Some(system_prototypes.string), + _ => None, + }; + ValueObject(GcCell::allocate( gc_context, ValueObjectData { - base: ScriptObject::object(gc_context, None), - value: value, + base: ScriptObject::object(gc_context, proto), + value, }, )) .into()