From db41bec91eac35d3aeafc4f5c682c060909ccb55 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 14 Jan 2020 19:58:32 -0500 Subject: [PATCH] Implement `MovieClipLoader`'s `addListener`, `removeListener`, and `broadcastMessage` methods. Interestingly, this constitutes an implementation of `AsBroadcaster`. It appears Macromedia decided to implement event handling on `MovieClipLoader` in a very similar fashion to `AsBroadcaster`, down to invoking `broadcastMessage` and searching a `_listeners` property for listeners. --- core/src/avm1/globals.rs | 16 ++ core/src/avm1/globals/movie_clip_loader.rs | 162 ++++++++++++++++++ core/tests/regression_tests.rs | 1 + .../swfs/avm1/mcl_as_broadcaster/output.txt | 12 ++ .../swfs/avm1/mcl_as_broadcaster/test.fla | Bin 0 -> 37376 bytes .../swfs/avm1/mcl_as_broadcaster/test.swf | Bin 0 -> 354 bytes 6 files changed, 191 insertions(+) create mode 100644 core/src/avm1/globals/movie_clip_loader.rs create mode 100644 core/tests/swfs/avm1/mcl_as_broadcaster/output.txt create mode 100644 core/tests/swfs/avm1/mcl_as_broadcaster/test.fla create mode 100644 core/tests/swfs/avm1/mcl_as_broadcaster/test.swf diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 491e63ce2..93a30c0b2 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -17,6 +17,7 @@ mod key; mod math; pub(crate) mod mouse; pub(crate) mod movie_clip; +mod movie_clip_loader; pub(crate) mod number; mod object; mod sound; @@ -154,6 +155,9 @@ pub fn create_globals<'gc>( let movie_clip_proto: Object<'gc> = movie_clip::create_proto(gc_context, object_proto, function_proto); + let movie_clip_loader_proto: Object<'gc> = + movie_clip_loader::create_proto(gc_context, object_proto, function_proto); + let sound_proto: Object<'gc> = sound::create_proto(gc_context, object_proto, function_proto); let text_field_proto: Object<'gc> = @@ -200,6 +204,12 @@ pub fn create_globals<'gc>( Some(function_proto), Some(movie_clip_proto), ); + let movie_clip_loader = FunctionObject::function( + gc_context, + Executable::Native(movie_clip_loader::constructor), + Some(function_proto), + Some(movie_clip_loader_proto), + ); let sound = FunctionObject::function( gc_context, Executable::Native(sound::constructor), @@ -249,6 +259,12 @@ pub fn create_globals<'gc>( globals.define_value(gc_context, "Object", object.into(), EnumSet::empty()); globals.define_value(gc_context, "Function", function.into(), EnumSet::empty()); globals.define_value(gc_context, "MovieClip", movie_clip.into(), EnumSet::empty()); + globals.define_value( + gc_context, + "MovieClipLoader", + movie_clip_loader.into(), + EnumSet::empty(), + ); globals.define_value(gc_context, "Sound", sound.into(), EnumSet::empty()); globals.define_value(gc_context, "TextField", text_field.into(), EnumSet::empty()); globals.define_value( diff --git a/core/src/avm1/globals/movie_clip_loader.rs b/core/src/avm1/globals/movie_clip_loader.rs new file mode 100644 index 000000000..34ddff673 --- /dev/null +++ b/core/src/avm1/globals/movie_clip_loader.rs @@ -0,0 +1,162 @@ +//! `MovieClipLoader` impl + +use crate::avm1::object::TObject; +use crate::avm1::property::Attribute; +use crate::avm1::return_value::ReturnValue; +use crate::avm1::script_object::ScriptObject; +use crate::avm1::{Avm1, Error, Object, UpdateContext, Value}; +use enumset::EnumSet; +use gc_arena::MutationContext; + +pub fn constructor<'gc>( + avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + let listeners = ScriptObject::array(context.gc_context, Some(avm.prototypes().array)); + this.define_value( + context.gc_context, + "_listeners", + Value::Object(listeners.into()), + Attribute::DontEnum.into(), + ); + listeners.set("0", Value::Object(this), avm, context)?; + + Ok(Value::Undefined.into()) +} + +pub fn add_listener<'gc>( + avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error> { + let new_listener = args.get(0).cloned().unwrap_or(Value::Undefined); + let listeners = this + .get("_listeners", avm, context)? + .resolve(avm, context)?; + + if let Value::Object(listeners) = listeners { + let length = listeners.length(); + listeners.set_length(context.gc_context, length + 1); + listeners.set_array_element(length, new_listener, context.gc_context); + } + + Ok(true.into()) +} + +pub fn remove_listener<'gc>( + avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error> { + let old_listener = args.get(0).cloned().unwrap_or(Value::Undefined); + let listeners = this + .get("_listeners", avm, context)? + .resolve(avm, context)?; + + if let Value::Object(listeners) = listeners { + let length = listeners.length(); + let mut position = None; + + for i in 0..length { + let other_listener = listeners + .get(&format!("{}", i), avm, context)? + .resolve(avm, context)?; + if old_listener == other_listener { + position = Some(i); + break; + } + } + + if let Some(position) = position { + if length > 0 { + let new_length = length - 1; + for i in position..new_length { + listeners.set_array_element( + i, + listeners.array_element(i + 1), + context.gc_context, + ); + } + + listeners.delete_array_element(new_length, context.gc_context); + listeners.delete(context.gc_context, &new_length.to_string()); + + listeners.set_length(context.gc_context, new_length); + } + } + } + + Ok(true.into()) +} + +pub fn broadcast_message<'gc>( + avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error> { + let event_name = args + .get(0) + .cloned() + .unwrap_or(Value::Undefined) + .coerce_to_string(avm, context)?; + let call_args = &args[0..]; + + let listeners = this + .get("_listeners", avm, context)? + .resolve(avm, context)?; + if let Value::Object(listeners) = listeners { + for i in 0..listeners.length() { + let listener = listeners + .get(&format!("{}", i), avm, context)? + .resolve(avm, context)?; + + if let Value::Object(listener) = listener { + let handler = listener + .get(&event_name, avm, context)? + .resolve(avm, context)?; + handler + .call(avm, context, listener, call_args)? + .resolve(avm, context)?; + } + } + } + + Ok(Value::Undefined.into()) +} + +pub fn create_proto<'gc>( + gc_context: MutationContext<'gc, '_>, + proto: Object<'gc>, + fn_proto: Object<'gc>, +) -> Object<'gc> { + let mcl_proto = ScriptObject::object(gc_context, Some(proto)); + + mcl_proto.as_script_object().unwrap().force_set_function( + "addListener", + add_listener, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + mcl_proto.as_script_object().unwrap().force_set_function( + "removeListener", + remove_listener, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + mcl_proto.as_script_object().unwrap().force_set_function( + "broadcastMessage", + broadcast_message, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + + mcl_proto.into() +} diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index c3ebd4336..098bde10c 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -158,6 +158,7 @@ swf_tests! { (undefined_to_string_swf6, "avm1/undefined_to_string_swf6", 1), (define_function2_preload, "avm1/define_function2_preload", 1), (define_function2_preload_order, "avm1/define_function2_preload_order", 1), + (mcl_as_broadcaster, "avm1/mcl_as_broadcaster", 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/avm1/mcl_as_broadcaster/output.txt b/core/tests/swfs/avm1/mcl_as_broadcaster/output.txt new file mode 100644 index 000000000..c8bf05af1 --- /dev/null +++ b/core/tests/swfs/avm1/mcl_as_broadcaster/output.txt @@ -0,0 +1,12 @@ +Called from MovieClipLoader +[object Object] +true +false +Called from New Listener +[object Object] +false +true +Called from New Listener +[object Object] +false +true diff --git a/core/tests/swfs/avm1/mcl_as_broadcaster/test.fla b/core/tests/swfs/avm1/mcl_as_broadcaster/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..324f8c0bad4c5eef87f0fd0fda092423b055a46c GIT binary patch literal 37376 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 b6l;s;w{!#q(rPxm_nkqtkN*8T`TzbOIVfiV literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm1/mcl_as_broadcaster/test.swf b/core/tests/swfs/avm1/mcl_as_broadcaster/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..7c1b211006eb335713dd2c1d93ac9f0bfaa07f44 GIT binary patch literal 354 zcmV-o0iFIsS5pXm0ssJboU349jb~usU%MZO zDRVS4bP6wsFfpkxHqlGbw+QtxWVpcpc7ExsnvF-@x^!-xmo9jA-JV&2iGiWx5Cb1W zZgLKTZ+=;3s&h_eflq#7N@@{9GLXqol95@=kX)3SSd!|RTToI7SHzH)S{|R1SzMBu z2UMG%2h!wPRFq%D;GCF~lbWKCR+OKsfNmzToL_3Wf)C7q#FP{`+buIERlzU6M8Pe; zG%tn0#K43hsR*b)IkC9JH?_DpF+G)`C^a{~EETS08WRHt!!%9?HU=mF2{1a?fG8$( zai(dk4D3u$4zmo1WC;Q*Wkpu%3>IPl%d^d91qo?_wXmx}G^!!=GsvVN@mPU8e+UCZ zGY2V}f$n64x)b6K&LD9R#RVd`!G3|*08+_0PpXJDns^~ AJpcdz literal 0 HcmV?d00001