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:
parent
b7d318a897
commit
db41bec91e
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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.
Loading…
Reference in New Issue