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.
This commit is contained in:
David Wendt 2020-01-14 19:58:32 -05:00
parent b7d318a897
commit db41bec91e
6 changed files with 191 additions and 0 deletions

View File

@ -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(

View File

@ -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<ReturnValue<'gc>, 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<ReturnValue<'gc>, 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<ReturnValue<'gc>, 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<ReturnValue<'gc>, 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()
}

View File

@ -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.

View File

@ -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

Binary file not shown.

Binary file not shown.