diff --git a/core/src/avm2/globals/string.rs b/core/src/avm2/globals/string.rs index c235a6919..629061996 100644 --- a/core/src/avm2/globals/string.rs +++ b/core/src/avm2/globals/string.rs @@ -1,12 +1,12 @@ //! `String` impl -use crate::avm2::activation::Activation; -use crate::avm2::class::Class; +use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::method::Method; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; +use crate::avm2::{activation::Activation, traits::Trait}; use gc_arena::{GcCell, MutationContext}; /// Implements `String`'s instance initializer. @@ -39,13 +39,38 @@ pub fn class_init<'gc>( Ok(Value::Undefined) } +/// Implements `length` property's getter +fn length<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + if let Some(this) = this { + if let Value::String(s) = this.value_of(activation.context.gc_context)? { + return Ok(s.encode_utf16().count().into()); + } + } + + Ok(Value::Undefined) +} + /// Construct `String`'s class. pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { - Class::new( + let class = Class::new( QName::new(Namespace::public(), "String"), Some(QName::new(Namespace::public(), "Object").into()), Method::from_builtin(instance_init), Method::from_builtin(class_init), mc, - ) + ); + + let mut write = class.write(mc); + write.set_attributes(ClassAttributes::FINAL | ClassAttributes::SEALED); + + write.define_instance_trait(Trait::from_getter( + QName::new(Namespace::public(), "length"), + Method::from_builtin(length), + )); + + class } diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 61597be45..003a2ccff 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -505,6 +505,7 @@ swf_tests! { (as3_movieclip_dispatchevent_target, "avm2/movieclip_dispatchevent_target", 1), (as3_movieclip_dispatchevent_selfadd, "avm2/movieclip_dispatchevent_selfadd", 1), (as3_string_constr, "avm2/string_constr", 1), + (as3_string_length, "avm2/string_length", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/string_length/Test.as b/core/tests/swfs/avm2/string_length/Test.as new file mode 100644 index 000000000..915700d8e --- /dev/null +++ b/core/tests/swfs/avm2/string_length/Test.as @@ -0,0 +1,20 @@ +package { + public class Test {} +} + +trace("//\"\".length;"); +trace("".length); +trace("//\"\\n\\r\".length;"); +trace("\n\r".length); +trace("//\"\\t\".length;"); +trace("\t".length); +trace("//\"abc012aáâ\".length;"); +trace("abc012aáâ".length); +trace("//\"你好こんにちは\".length;"); +trace("你好こんにちは".length); +trace("//\"مَرحَبًا\".length;"); +trace("مَرحَبًا".length); +trace("//\"😀\".length;"); +trace("😀".length); +trace("//\"👨‍👨‍👧‍👦\".length;"); +trace("👨‍👨‍👧‍👦".length); diff --git a/core/tests/swfs/avm2/string_length/output.txt b/core/tests/swfs/avm2/string_length/output.txt new file mode 100644 index 000000000..bac529ea4 --- /dev/null +++ b/core/tests/swfs/avm2/string_length/output.txt @@ -0,0 +1,16 @@ +//"".length; +0 +//"\n\r".length; +2 +//"\t".length; +1 +//"abc012aáâ".length; +9 +//"你好こんにちは".length; +7 +//"مَرحَبًا".length; +8 +//"😀".length; +2 +//"👨‍👨‍👧‍👦".length; +11 diff --git a/core/tests/swfs/avm2/string_length/test.fla b/core/tests/swfs/avm2/string_length/test.fla new file mode 100644 index 000000000..d382fbe3e Binary files /dev/null and b/core/tests/swfs/avm2/string_length/test.fla differ diff --git a/core/tests/swfs/avm2/string_length/test.swf b/core/tests/swfs/avm2/string_length/test.swf new file mode 100644 index 000000000..222ecbc9c Binary files /dev/null and b/core/tests/swfs/avm2/string_length/test.swf differ