diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 0230b5604..64d608c14 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -65,6 +65,16 @@ pub struct Avm2<'gc> { /// System prototypes. system_prototypes: Option>, + /// A list of objects which are capable of recieving broadcasts. + /// + /// Certain types of events are "broadcast events" that are emitted on all + /// constructed objects in order of their creation, whether or not they are + /// currently present on the display list. This list keeps track of that. + /// + /// TODO: These should be weak object pointers, but our current garbage + /// collector does not support weak references. + broadcast_list: Vec>, + #[cfg(feature = "avm_debug")] pub debug_output: bool, } @@ -78,6 +88,7 @@ impl<'gc> Avm2<'gc> { stack: Vec::new(), globals, system_prototypes: None, + broadcast_list: Vec::new(), #[cfg(feature = "avm_debug")] debug_output: false, @@ -133,6 +144,47 @@ impl<'gc> Avm2<'gc> { dispatch_event(&mut activation, target, event_object) } + /// Add an object to the broadcast list. + /// + /// This function does not (currently) check set membership, so multiple + /// registrations will result in multiple broadcasts on that object. Take + /// care to ensure that registration happens as close to the top of the + /// object hierarchy as possible. Currently, registration happens in + /// `EventDispatcher`, as that's the highest class that makes sense to send + /// broadcasts to. + pub fn register_broadcast_listener( + context: &mut UpdateContext<'_, 'gc, '_>, + object: Object<'gc>, + ) { + context.avm2.broadcast_list.push(object); + } + + /// Dispatch an event on all objects in the current execution list. + /// + /// `on_class_proto` specifies a class or interface prototype whose + /// instances, implementers, and/or subclasses define the set of objects + /// that will recieve the event. You can broadcast to just display objects, + /// or specific interfaces, and so on. + pub fn broadcast_event( + context: &mut UpdateContext<'_, 'gc, '_>, + event: Event<'gc>, + on_class_proto: Object<'gc>, + ) -> Result<(), Error> { + let el_length = context.avm2.broadcast_list.len(); + + for i in 0..el_length { + let object = context.avm2.broadcast_list.get(i).copied(); + + if let Some(object) = object { + if object.has_prototype_in_chain(on_class_proto, true)? { + Avm2::dispatch_event(context, event.clone(), object)?; + } + } + } + + Ok(()) + } + pub fn run_stack_frame_for_callable( callable: Object<'gc>, reciever: Option>, diff --git a/core/src/avm2/globals/flash/events/eventdispatcher.rs b/core/src/avm2/globals/flash/events/eventdispatcher.rs index 2cf774889..6b306bb4c 100644 --- a/core/src/avm2/globals/flash/events/eventdispatcher.rs +++ b/core/src/avm2/globals/flash/events/eventdispatcher.rs @@ -11,7 +11,7 @@ use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{DispatchObject, Object, TObject}; use crate::avm2::traits::Trait; use crate::avm2::value::Value; -use crate::avm2::Error; +use crate::avm2::{Avm2, Error}; use gc_arena::{GcCell, MutationContext}; /// Implements `flash.events.EventDispatcher`'s instance constructor. @@ -38,6 +38,8 @@ pub fn instance_init<'gc>( dispatch_list.into(), activation, )?; + + Avm2::register_broadcast_listener(&mut activation.context, this); } Ok(Value::Undefined)