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::globals::SystemPrototypes;
|
||||||
use crate::avm2::method::Method;
|
use crate::avm2::method::Method;
|
||||||
|
use crate::avm2::object::EventObject;
|
||||||
use crate::avm2::script::{Script, TranslationUnit};
|
use crate::avm2::script::{Script, TranslationUnit};
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
use crate::tag_utils::SwfSlice;
|
use crate::tag_utils::SwfSlice;
|
||||||
|
@ -40,6 +41,7 @@ mod value;
|
||||||
|
|
||||||
pub use crate::avm2::activation::Activation;
|
pub use crate::avm2::activation::Activation;
|
||||||
pub use crate::avm2::domain::Domain;
|
pub use crate::avm2::domain::Domain;
|
||||||
|
pub use crate::avm2::events::Event;
|
||||||
pub use crate::avm2::names::{Namespace, QName};
|
pub use crate::avm2::names::{Namespace, QName};
|
||||||
pub use crate::avm2::object::{Object, StageObject, TObject};
|
pub use crate::avm2::object::{Object, StageObject, TObject};
|
||||||
pub use crate::avm2::value::Value;
|
pub use crate::avm2::value::Value;
|
||||||
|
@ -115,6 +117,22 @@ impl<'gc> Avm2<'gc> {
|
||||||
Ok(())
|
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(
|
pub fn run_stack_frame_for_callable(
|
||||||
callable: Object<'gc>,
|
callable: Object<'gc>,
|
||||||
reciever: Option<Object<'gc>>,
|
reciever: Option<Object<'gc>>,
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
//! Core event structure
|
//! Core event structure
|
||||||
|
|
||||||
|
use crate::avm2::activation::Activation;
|
||||||
|
use crate::avm2::names::{Namespace, QName};
|
||||||
use crate::avm2::object::{Object, TObject};
|
use crate::avm2::object::{Object, TObject};
|
||||||
use crate::avm2::string::AvmString;
|
use crate::avm2::string::AvmString;
|
||||||
|
use crate::avm2::value::Value;
|
||||||
|
use crate::avm2::Error;
|
||||||
|
use crate::display_object::TDisplayObject;
|
||||||
use gc_arena::Collect;
|
use gc_arena::Collect;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
@ -338,3 +343,142 @@ impl<'gc> Hash for EventHandler<'gc> {
|
||||||
self.handler.as_ptr().hash(state);
|
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::activation::Activation;
|
||||||
use crate::avm2::class::{Class, ClassAttributes};
|
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::globals::NS_RUFFLE_INTERNAL;
|
||||||
use crate::avm2::method::Method;
|
use crate::avm2::method::Method;
|
||||||
use crate::avm2::names::{Namespace, QName};
|
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::traits::Trait;
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
use crate::avm2::Error;
|
use crate::avm2::Error;
|
||||||
use crate::display_object::TDisplayObject;
|
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
|
|
||||||
const NS_EVENT_DISPATCHER: &str = "https://ruffle.rs/AS3/impl/EventDispatcher/";
|
|
||||||
|
|
||||||
/// Implements `flash.events.EventDispatcher`'s instance constructor.
|
/// Implements `flash.events.EventDispatcher`'s instance constructor.
|
||||||
pub fn instance_init<'gc>(
|
pub fn instance_init<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
@ -158,24 +157,6 @@ pub fn has_event_listener<'gc>(
|
||||||
Ok(Value::Undefined)
|
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`.
|
/// Implements `EventDispatcher.willTrigger`.
|
||||||
pub fn will_trigger<'gc>(
|
pub fn will_trigger<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
@ -222,59 +203,6 @@ pub fn will_trigger<'gc>(
|
||||||
Ok(false.into())
|
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`.
|
/// Implements `EventDispatcher.dispatchEvent`.
|
||||||
pub fn dispatch_event<'gc>(
|
pub fn dispatch_event<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
@ -291,67 +219,11 @@ pub fn dispatch_event<'gc>(
|
||||||
return Err("Dispatched Events must be subclasses of Event.".into());
|
return Err("Dispatched Events must be subclasses of Event.".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut this) = this {
|
if let Some(this) = this {
|
||||||
let target = this
|
Ok(dispatch_event_internal(activation, this, event)?.into())
|
||||||
.get_property(
|
} else {
|
||||||
this,
|
Ok(false.into())
|
||||||
&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.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements `flash.events.EventDispatcher`'s class constructor.
|
/// Implements `flash.events.EventDispatcher`'s class constructor.
|
||||||
|
|
Loading…
Reference in New Issue