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;
|
mod math;
|
||||||
pub(crate) mod mouse;
|
pub(crate) mod mouse;
|
||||||
pub(crate) mod movie_clip;
|
pub(crate) mod movie_clip;
|
||||||
|
mod movie_clip_loader;
|
||||||
pub(crate) mod number;
|
pub(crate) mod number;
|
||||||
mod object;
|
mod object;
|
||||||
mod sound;
|
mod sound;
|
||||||
|
@ -154,6 +155,9 @@ pub fn create_globals<'gc>(
|
||||||
let movie_clip_proto: Object<'gc> =
|
let movie_clip_proto: Object<'gc> =
|
||||||
movie_clip::create_proto(gc_context, object_proto, function_proto);
|
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 sound_proto: Object<'gc> = sound::create_proto(gc_context, object_proto, function_proto);
|
||||||
|
|
||||||
let text_field_proto: Object<'gc> =
|
let text_field_proto: Object<'gc> =
|
||||||
|
@ -200,6 +204,12 @@ pub fn create_globals<'gc>(
|
||||||
Some(function_proto),
|
Some(function_proto),
|
||||||
Some(movie_clip_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(
|
let sound = FunctionObject::function(
|
||||||
gc_context,
|
gc_context,
|
||||||
Executable::Native(sound::constructor),
|
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, "Object", object.into(), EnumSet::empty());
|
||||||
globals.define_value(gc_context, "Function", function.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, "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, "Sound", sound.into(), EnumSet::empty());
|
||||||
globals.define_value(gc_context, "TextField", text_field.into(), EnumSet::empty());
|
globals.define_value(gc_context, "TextField", text_field.into(), EnumSet::empty());
|
||||||
globals.define_value(
|
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),
|
(undefined_to_string_swf6, "avm1/undefined_to_string_swf6", 1),
|
||||||
(define_function2_preload, "avm1/define_function2_preload", 1),
|
(define_function2_preload, "avm1/define_function2_preload", 1),
|
||||||
(define_function2_preload_order, "avm1/define_function2_preload_order", 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.
|
// 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