avm2: Add convenience method for natively-dispatched events.

This commit is contained in:
David Wendt 2021-01-12 20:02:07 -05:00 committed by Mike Welsh
parent 98abaf4ca2
commit 63af38be9a
3 changed files with 169 additions and 135 deletions

View File

@ -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<bool, Error> {
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<Object<'gc>>,

View File

@ -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<Object<'_>> {
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<Object<'gc>> = 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<bool, Error> {
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)
}

View File

@ -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<Object<'_>> {
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<Object<'gc>> = 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.