From 63af38be9ac026721171aaf64c468acb9efb9c9b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 12 Jan 2021 20:02:07 -0500 Subject: [PATCH] avm2: Add convenience method for natively-dispatched events. --- core/src/avm2.rs | 18 +++ core/src/avm2/events.rs | 144 ++++++++++++++++++ .../globals/flash/events/eventdispatcher.rs | 142 +---------------- 3 files changed, 169 insertions(+), 135 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 7b0f50394..0230b5604 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -2,6 +2,7 @@ use crate::avm2::globals::SystemPrototypes; use crate::avm2::method::Method; +use crate::avm2::object::EventObject; use crate::avm2::script::{Script, TranslationUnit}; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; @@ -40,6 +41,7 @@ mod value; pub use crate::avm2::activation::Activation; pub use crate::avm2::domain::Domain; +pub use crate::avm2::events::Event; pub use crate::avm2::names::{Namespace, QName}; pub use crate::avm2::object::{Object, StageObject, TObject}; pub use crate::avm2::value::Value; @@ -115,6 +117,22 @@ impl<'gc> Avm2<'gc> { Ok(()) } + /// Dispatch an event on an object. + /// + /// The `bool` parameter reads true if the event was cancelled. + pub fn dispatch_event( + context: &mut UpdateContext<'_, 'gc, '_>, + event: Event<'gc>, + target: Object<'gc>, + ) -> Result { + use crate::avm2::events::dispatch_event; + let event_proto = context.avm2.system_prototypes.as_ref().unwrap().event; + let event_object = EventObject::from_event(context.gc_context, Some(event_proto), event); + let mut activation = Activation::from_nothing(context.reborrow()); + + dispatch_event(&mut activation, target, event_object) + } + pub fn run_stack_frame_for_callable( callable: Object<'gc>, reciever: Option>, diff --git a/core/src/avm2/events.rs b/core/src/avm2/events.rs index 31b63a4cf..276625a76 100644 --- a/core/src/avm2/events.rs +++ b/core/src/avm2/events.rs @@ -1,7 +1,12 @@ //! Core event structure +use crate::avm2::activation::Activation; +use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::string::AvmString; +use crate::avm2::value::Value; +use crate::avm2::Error; +use crate::display_object::TDisplayObject; use gc_arena::Collect; use std::collections::{BTreeMap, HashMap}; use std::hash::{Hash, Hasher}; @@ -338,3 +343,142 @@ impl<'gc> Hash for EventHandler<'gc> { self.handler.as_ptr().hash(state); } } + +pub const NS_EVENT_DISPATCHER: &str = "https://ruffle.rs/AS3/impl/EventDispatcher/"; + +/// Retrieve the parent of a given `EventDispatcher`. +/// +/// `EventDispatcher` does not provide a generic way for it's subclasses to +/// indicate ancestry. Instead, only specific event targets provide a hierarchy +/// to traverse. If no hierarchy is available, this returns `None`, as if the +/// target had no parent. +pub fn parent_of(target: Object<'_>) -> Option> { + if let Some(dobj) = target.as_display_object() { + if let Some(dparent) = dobj.parent() { + if let Value::Object(parent) = dparent.object2() { + return Some(parent); + } + } + } + + None +} + +/// Call all of the event handlers on a given target. +/// +/// The `target` is the current target of the `event`. `event` must be a valid +/// `EventObject`, or this function will panic. You must have already set the +/// event's phase to match what targets you are dispatching to, or you will +/// call the wrong handlers. +pub fn dispatch_event_to_target<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + mut target: Object<'gc>, + event: Object<'gc>, +) -> Result<(), Error> { + let dispatch_list = target + .get_property( + target, + &QName::new(Namespace::private(NS_EVENT_DISPATCHER), "dispatch_list"), + activation, + )? + .coerce_to_object(activation)?; + + let mut evtmut = event.as_event_mut(activation.context.gc_context).unwrap(); + let name = evtmut.event_type(); + let use_capture = evtmut.phase() == EventPhase::Capturing; + + evtmut.set_current_target(target); + + drop(evtmut); + + let handlers: Vec> = dispatch_list + .as_dispatch_mut(activation.context.gc_context) + .ok_or_else(|| Error::from("Internal dispatch list is missing during dispatch!"))? + .iter_event_handlers(name, use_capture) + .collect(); + + for handler in handlers.iter() { + if event + .as_event() + .unwrap() + .is_propagation_stopped_immediately() + { + break; + } + + handler.call( + activation.global_scope().coerce_to_object(activation).ok(), + &[event.into()], + activation, + None, + )?; + } + + Ok(()) +} + +pub fn dispatch_event<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + mut this: Object<'gc>, + event: Object<'gc>, +) -> Result { + let target = this + .get_property( + this, + &QName::new(Namespace::private(NS_EVENT_DISPATCHER), "target"), + activation, + )? + .coerce_to_object(activation) + .ok() + .unwrap_or(this); + + let mut ancestor_list = Vec::new(); + let mut parent = parent_of(target); + while let Some(par) = parent { + ancestor_list.push(par); + parent = parent_of(par); + } + + let mut evtmut = event.as_event_mut(activation.context.gc_context).unwrap(); + + evtmut.set_phase(EventPhase::Capturing); + evtmut.set_target(target); + + drop(evtmut); + + for ancestor in ancestor_list.iter().rev() { + if event.as_event().unwrap().is_propagation_stopped() { + break; + } + + dispatch_event_to_target(activation, *ancestor, event)?; + } + + event + .as_event_mut(activation.context.gc_context) + .unwrap() + .set_phase(EventPhase::AtTarget); + + if !event.as_event().unwrap().is_propagation_stopped() { + dispatch_event_to_target(activation, target, event)?; + } + + event + .as_event_mut(activation.context.gc_context) + .unwrap() + .set_phase(EventPhase::Bubbling); + + if event.as_event().unwrap().is_bubbling() { + for ancestor in ancestor_list.iter() { + if event.as_event().unwrap().is_propagation_stopped() { + break; + } + + dispatch_event_to_target(activation, *ancestor, event)?; + } + } + + let was_not_cancelled = !event.as_event().unwrap().is_cancelled(); + + Ok(was_not_cancelled) +} diff --git a/core/src/avm2/globals/flash/events/eventdispatcher.rs b/core/src/avm2/globals/flash/events/eventdispatcher.rs index 663559eb8..2cf774889 100644 --- a/core/src/avm2/globals/flash/events/eventdispatcher.rs +++ b/core/src/avm2/globals/flash/events/eventdispatcher.rs @@ -2,7 +2,9 @@ use crate::avm2::activation::Activation; use crate::avm2::class::{Class, ClassAttributes}; -use crate::avm2::events::EventPhase; +use crate::avm2::events::{ + dispatch_event as dispatch_event_internal, parent_of, NS_EVENT_DISPATCHER, +}; use crate::avm2::globals::NS_RUFFLE_INTERNAL; use crate::avm2::method::Method; use crate::avm2::names::{Namespace, QName}; @@ -10,11 +12,8 @@ use crate::avm2::object::{DispatchObject, Object, TObject}; use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::display_object::TDisplayObject; use gc_arena::{GcCell, MutationContext}; -const NS_EVENT_DISPATCHER: &str = "https://ruffle.rs/AS3/impl/EventDispatcher/"; - /// Implements `flash.events.EventDispatcher`'s instance constructor. pub fn instance_init<'gc>( activation: &mut Activation<'_, 'gc, '_>, @@ -158,24 +157,6 @@ pub fn has_event_listener<'gc>( Ok(Value::Undefined) } -/// Retrieve the parent of a given `EventDispatcher`. -/// -/// `EventDispatcher` does not provide a generic way for it's subclasses to -/// indicate ancestry. Instead, only specific event targets provide a hierarchy -/// to traverse. If no hierarchy is available, this returns `None`, as if the -/// target had no parent. -fn parent_of(target: Object<'_>) -> Option> { - if let Some(dobj) = target.as_display_object() { - if let Some(dparent) = dobj.parent() { - if let Value::Object(parent) = dparent.object2() { - return Some(parent); - } - } - } - - None -} - /// Implements `EventDispatcher.willTrigger`. pub fn will_trigger<'gc>( activation: &mut Activation<'_, 'gc, '_>, @@ -222,59 +203,6 @@ pub fn will_trigger<'gc>( Ok(false.into()) } -/// Call all of the event handlers on a given target. -/// -/// The `target` is the current target of the `event`. `event` must be a valid -/// `EventObject`, or this function will panic. You must have already set the -/// event's phase to match what targets you are dispatching to, or you will -/// call the wrong handlers. -pub fn dispatch_event_to_target<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - mut target: Object<'gc>, - event: Object<'gc>, -) -> Result<(), Error> { - let dispatch_list = target - .get_property( - target, - &QName::new(Namespace::private(NS_EVENT_DISPATCHER), "dispatch_list"), - activation, - )? - .coerce_to_object(activation)?; - - let mut evtmut = event.as_event_mut(activation.context.gc_context).unwrap(); - let name = evtmut.event_type(); - let use_capture = evtmut.phase() == EventPhase::Capturing; - - evtmut.set_current_target(target); - - drop(evtmut); - - let handlers: Vec> = dispatch_list - .as_dispatch_mut(activation.context.gc_context) - .ok_or_else(|| Error::from("Internal dispatch list is missing during dispatch!"))? - .iter_event_handlers(name, use_capture) - .collect(); - - for handler in handlers.iter() { - if event - .as_event() - .unwrap() - .is_propagation_stopped_immediately() - { - break; - } - - handler.call( - activation.global_scope().coerce_to_object(activation).ok(), - &[event.into()], - activation, - None, - )?; - } - - Ok(()) -} - /// Implements `EventDispatcher.dispatchEvent`. pub fn dispatch_event<'gc>( activation: &mut Activation<'_, 'gc, '_>, @@ -291,67 +219,11 @@ pub fn dispatch_event<'gc>( return Err("Dispatched Events must be subclasses of Event.".into()); } - if let Some(mut this) = this { - let target = this - .get_property( - this, - &QName::new(Namespace::private(NS_EVENT_DISPATCHER), "target"), - activation, - )? - .coerce_to_object(activation) - .ok() - .unwrap_or(this); - - let mut ancestor_list = Vec::new(); - let mut parent = parent_of(target); - while let Some(par) = parent { - ancestor_list.push(par); - parent = parent_of(par); - } - - let mut evtmut = event.as_event_mut(activation.context.gc_context).unwrap(); - - evtmut.set_phase(EventPhase::Capturing); - evtmut.set_target(target); - - drop(evtmut); - - for ancestor in ancestor_list.iter().rev() { - if event.as_event().unwrap().is_propagation_stopped() { - break; - } - - dispatch_event_to_target(activation, *ancestor, event)?; - } - - event - .as_event_mut(activation.context.gc_context) - .unwrap() - .set_phase(EventPhase::AtTarget); - - if !event.as_event().unwrap().is_propagation_stopped() { - dispatch_event_to_target(activation, target, event)?; - } - - event - .as_event_mut(activation.context.gc_context) - .unwrap() - .set_phase(EventPhase::Bubbling); - - if event.as_event().unwrap().is_bubbling() { - for ancestor in ancestor_list.iter() { - if event.as_event().unwrap().is_propagation_stopped() { - break; - } - - dispatch_event_to_target(activation, *ancestor, event)?; - } - } + if let Some(this) = this { + Ok(dispatch_event_internal(activation, this, event)?.into()) + } else { + Ok(false.into()) } - - let was_not_cancelled = !event.as_event().unwrap().is_cancelled(); - - Ok(was_not_cancelled.into()) } /// Implements `flash.events.EventDispatcher`'s class constructor.