avm2: Add convenience method for natively-dispatched events.
This commit is contained in:
parent
98abaf4ca2
commit
63af38be9a
|
@ -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>>,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue