mirror of
https://github.com/servo/servo.git
synced 2025-06-21 07:38:59 +01:00
Add basic event dispatch with bubbling, capturing, and propagation interruption.
This commit is contained in:
parent
bb97fd13f3
commit
88f5c2b133
12 changed files with 380 additions and 70 deletions
|
@ -768,7 +768,8 @@ pub enum Error {
|
||||||
NotFound,
|
NotFound,
|
||||||
HierarchyRequest,
|
HierarchyRequest,
|
||||||
InvalidCharacter,
|
InvalidCharacter,
|
||||||
NotSupported
|
NotSupported,
|
||||||
|
InvalidState
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Fallible<T> = Result<T, Error>;
|
pub type Fallible<T> = Result<T, Error>;
|
||||||
|
|
|
@ -31,6 +31,13 @@ pub struct AbstractEvent {
|
||||||
event: *mut Box<Event>
|
event: *mut Box<Event>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum EventPhase {
|
||||||
|
Phase_None = 0,
|
||||||
|
Phase_Capturing,
|
||||||
|
Phase_At_Target,
|
||||||
|
Phase_Bubbling
|
||||||
|
}
|
||||||
|
|
||||||
impl AbstractEvent {
|
impl AbstractEvent {
|
||||||
pub fn from_box(box: *mut Box<Event>) -> AbstractEvent {
|
pub fn from_box(box: *mut Box<Event>) -> AbstractEvent {
|
||||||
AbstractEvent {
|
AbstractEvent {
|
||||||
|
@ -95,6 +102,14 @@ impl AbstractEvent {
|
||||||
assert!(self.is_mouseevent());
|
assert!(self.is_mouseevent());
|
||||||
self.transmute_mut()
|
self.transmute_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn propagation_stopped(&self) -> bool {
|
||||||
|
self.event().stop_propagation
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bubbles(&self) -> bool {
|
||||||
|
self.event().bubbles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerivedWrapper for AbstractEvent {
|
impl DerivedWrapper for AbstractEvent {
|
||||||
|
@ -138,11 +153,18 @@ pub enum EventTypeId {
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
type_id: EventTypeId,
|
type_id: EventTypeId,
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
|
current_target: Option<AbstractEventTarget>,
|
||||||
|
target: Option<AbstractEventTarget>,
|
||||||
type_: ~str,
|
type_: ~str,
|
||||||
|
phase: EventPhase,
|
||||||
default_prevented: bool,
|
default_prevented: bool,
|
||||||
|
stop_propagation: bool,
|
||||||
|
stop_immediate: bool,
|
||||||
cancelable: bool,
|
cancelable: bool,
|
||||||
bubbles: bool,
|
bubbles: bool,
|
||||||
trusted: bool,
|
trusted: bool,
|
||||||
|
dispatching: bool,
|
||||||
|
initialized: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
|
@ -150,11 +172,18 @@ impl Event {
|
||||||
Event {
|
Event {
|
||||||
type_id: type_id,
|
type_id: type_id,
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
|
current_target: None,
|
||||||
|
target: None,
|
||||||
|
phase: Phase_None,
|
||||||
type_: ~"",
|
type_: ~"",
|
||||||
default_prevented: false,
|
default_prevented: false,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
trusted: false
|
trusted: false,
|
||||||
|
dispatching: false,
|
||||||
|
stop_propagation: false,
|
||||||
|
stop_immediate: false,
|
||||||
|
initialized: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +202,7 @@ impl Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn EventPhase(&self) -> u16 {
|
pub fn EventPhase(&self) -> u16 {
|
||||||
0
|
self.phase as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Type(&self) -> DOMString {
|
pub fn Type(&self) -> DOMString {
|
||||||
|
@ -181,11 +210,11 @@ impl Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn GetTarget(&self) -> Option<AbstractEventTarget> {
|
pub fn GetTarget(&self) -> Option<AbstractEventTarget> {
|
||||||
None
|
self.target
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn GetCurrentTarget(&self) -> Option<AbstractEventTarget> {
|
pub fn GetCurrentTarget(&self) -> Option<AbstractEventTarget> {
|
||||||
None
|
self.current_target
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn DefaultPrevented(&self) -> bool {
|
pub fn DefaultPrevented(&self) -> bool {
|
||||||
|
@ -193,13 +222,18 @@ impl Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn PreventDefault(&mut self) {
|
pub fn PreventDefault(&mut self) {
|
||||||
self.default_prevented = true
|
if self.cancelable {
|
||||||
|
self.default_prevented = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn StopPropagation(&mut self) {
|
pub fn StopPropagation(&mut self) {
|
||||||
|
self.stop_propagation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn StopImmediatePropagation(&mut self) {
|
pub fn StopImmediatePropagation(&mut self) {
|
||||||
|
self.stop_immediate = true;
|
||||||
|
self.stop_propagation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Bubbles(&self) -> bool {
|
pub fn Bubbles(&self) -> bool {
|
||||||
|
@ -221,6 +255,7 @@ impl Event {
|
||||||
self.type_ = null_str_as_word_null(type_);
|
self.type_ = null_str_as_word_null(type_);
|
||||||
self.cancelable = cancelable;
|
self.cancelable = cancelable;
|
||||||
self.bubbles = bubbles;
|
self.bubbles = bubbles;
|
||||||
|
self.initialized = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
111
src/components/script/dom/eventdispatcher.rs
Normal file
111
src/components/script/dom/eventdispatcher.rs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use dom::bindings::callback::eReportExceptions;
|
||||||
|
use dom::eventtarget::{AbstractEventTarget, Capturing, Bubbling};
|
||||||
|
use dom::event::{AbstractEvent, Phase_At_Target, Phase_None, Phase_Bubbling, Phase_Capturing};
|
||||||
|
use dom::node::AbstractNode;
|
||||||
|
use servo_util::tree::{TreeNodeRef};
|
||||||
|
|
||||||
|
// See http://dom.spec.whatwg.org/#concept-event-dispatch for the full dispatch algorithm
|
||||||
|
pub fn dispatch_event(target: AbstractEventTarget, event: AbstractEvent) -> bool {
|
||||||
|
assert!(!event.event().dispatching);
|
||||||
|
|
||||||
|
{
|
||||||
|
let event = event.mut_event();
|
||||||
|
event.target = Some(target);
|
||||||
|
event.dispatching = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_ = event.event().type_.clone();
|
||||||
|
let mut chain = ~[];
|
||||||
|
|
||||||
|
//TODO: no chain if not participating in a tree
|
||||||
|
if target.is_node() {
|
||||||
|
for ancestor in AbstractNode::from_eventtarget(target).ancestors() {
|
||||||
|
chain.push(AbstractEventTarget::from_node(ancestor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.mut_event().phase = Phase_Capturing;
|
||||||
|
|
||||||
|
//FIXME: The "callback this value" should be currentTarget
|
||||||
|
|
||||||
|
/* capturing */
|
||||||
|
for &cur_target in chain.rev_iter() {
|
||||||
|
//XXX bad clone
|
||||||
|
let stopped = match cur_target.eventtarget().get_listeners_for(type_.clone(), Capturing) {
|
||||||
|
Some(listeners) => {
|
||||||
|
event.mut_event().current_target = Some(cur_target);
|
||||||
|
for listener in listeners.iter() {
|
||||||
|
listener.HandleEvent__(event, eReportExceptions);
|
||||||
|
|
||||||
|
if event.event().stop_immediate {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.propagation_stopped()
|
||||||
|
}
|
||||||
|
None => false
|
||||||
|
};
|
||||||
|
|
||||||
|
if stopped {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* at target */
|
||||||
|
if !event.propagation_stopped() {
|
||||||
|
{
|
||||||
|
let event = event.mut_event();
|
||||||
|
event.phase = Phase_At_Target;
|
||||||
|
event.current_target = Some(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt_listeners = target.eventtarget().get_listeners(type_.clone());
|
||||||
|
for listeners in opt_listeners.iter() {
|
||||||
|
for listener in listeners.iter() {
|
||||||
|
listener.HandleEvent__(event, eReportExceptions);
|
||||||
|
if event.event().stop_immediate {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bubbling */
|
||||||
|
if event.bubbles() && !event.propagation_stopped() {
|
||||||
|
event.mut_event().phase = Phase_Bubbling;
|
||||||
|
|
||||||
|
for &cur_target in chain.iter() {
|
||||||
|
//XXX bad clone
|
||||||
|
let stopped = match cur_target.eventtarget().get_listeners_for(type_.clone(), Bubbling) {
|
||||||
|
Some(listeners) => {
|
||||||
|
event.mut_event().current_target = Some(cur_target);
|
||||||
|
for listener in listeners.iter() {
|
||||||
|
listener.HandleEvent__(event, eReportExceptions);
|
||||||
|
|
||||||
|
if event.event().stop_immediate {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.propagation_stopped()
|
||||||
|
}
|
||||||
|
None => false
|
||||||
|
};
|
||||||
|
if stopped {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let event = event.mut_event();
|
||||||
|
event.dispatching = false;
|
||||||
|
event.phase = Phase_None;
|
||||||
|
event.current_target = None;
|
||||||
|
|
||||||
|
!event.DefaultPrevented()
|
||||||
|
}
|
|
@ -2,12 +2,12 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use dom::bindings::callback::eReportExceptions;
|
|
||||||
use dom::bindings::codegen::EventTargetBinding;
|
use dom::bindings::codegen::EventTargetBinding;
|
||||||
use dom::bindings::utils::{Reflectable, Reflector, DOMString, Fallible, DerivedWrapper};
|
use dom::bindings::utils::{Reflectable, Reflector, DOMString, Fallible, DerivedWrapper};
|
||||||
use dom::bindings::utils::null_str_as_word_null;
|
use dom::bindings::utils::{null_str_as_word_null, InvalidState};
|
||||||
use dom::bindings::codegen::EventListenerBinding::EventListener;
|
use dom::bindings::codegen::EventListenerBinding::EventListener;
|
||||||
use dom::event::AbstractEvent;
|
use dom::event::AbstractEvent;
|
||||||
|
use dom::eventdispatcher::dispatch_event;
|
||||||
use dom::node::{AbstractNode, ScriptView};
|
use dom::node::{AbstractNode, ScriptView};
|
||||||
use script_task::page_from_context;
|
use script_task::page_from_context;
|
||||||
|
|
||||||
|
@ -18,10 +18,28 @@ use std::cast;
|
||||||
use std::hashmap::HashMap;
|
use std::hashmap::HashMap;
|
||||||
use std::unstable::raw::Box;
|
use std::unstable::raw::Box;
|
||||||
|
|
||||||
|
#[deriving(Eq)]
|
||||||
|
pub enum ListenerPhase {
|
||||||
|
Capturing,
|
||||||
|
Bubbling,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deriving(Eq)]
|
||||||
|
pub enum EventTargetTypeId {
|
||||||
|
WindowTypeId,
|
||||||
|
NodeTypeId
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deriving(Eq)]
|
||||||
|
struct EventListenerEntry {
|
||||||
|
phase: ListenerPhase,
|
||||||
|
listener: EventListener
|
||||||
|
}
|
||||||
|
|
||||||
pub struct EventTarget {
|
pub struct EventTarget {
|
||||||
|
type_id: EventTargetTypeId,
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
capturing_handlers: HashMap<~str, ~[EventListener]>,
|
handlers: HashMap<~str, ~[EventListenerEntry]>,
|
||||||
bubbling_handlers: HashMap<~str, ~[EventListener]>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AbstractEventTarget {
|
pub struct AbstractEventTarget {
|
||||||
|
@ -29,9 +47,9 @@ pub struct AbstractEventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AbstractEventTarget {
|
impl AbstractEventTarget {
|
||||||
pub fn from_box(box: *mut Box<EventTarget>) -> AbstractEventTarget {
|
pub fn from_box<T>(box: *mut Box<T>) -> AbstractEventTarget {
|
||||||
AbstractEventTarget {
|
AbstractEventTarget {
|
||||||
eventtarget: box
|
eventtarget: box as *mut Box<EventTarget>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +59,18 @@ impl AbstractEventTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_id(&self) -> EventTargetTypeId {
|
||||||
|
self.eventtarget().type_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_window(&self) -> bool {
|
||||||
|
self.type_id() == WindowTypeId
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_node(&self) -> bool {
|
||||||
|
self.type_id() == NodeTypeId
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Downcasting borrows
|
// Downcasting borrows
|
||||||
//
|
//
|
||||||
|
@ -59,11 +89,11 @@ impl AbstractEventTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eventtarget<'a>(&'a self) -> &'a EventTarget {
|
pub fn eventtarget<'a>(&'a self) -> &'a EventTarget {
|
||||||
self.transmute()
|
self.transmute()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mut_eventtarget<'a>(&'a mut self) -> &'a mut EventTarget {
|
pub fn mut_eventtarget<'a>(&'a mut self) -> &'a mut EventTarget {
|
||||||
self.transmute_mut()
|
self.transmute_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,35 +129,41 @@ impl Reflectable for AbstractEventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventTarget {
|
impl EventTarget {
|
||||||
pub fn new() -> EventTarget {
|
pub fn new_inherited(type_id: EventTargetTypeId) -> EventTarget {
|
||||||
EventTarget {
|
EventTarget {
|
||||||
|
type_id: type_id,
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
capturing_handlers: HashMap::new(),
|
handlers: HashMap::new(),
|
||||||
bubbling_handlers: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_wrapper(@mut self, cx: *JSContext, scope: *JSObject) {
|
pub fn get_listeners(&self, type_: ~str) -> Option<~[EventListener]> {
|
||||||
self.wrap_object_shared(cx, scope);
|
do self.handlers.find_equiv(&type_).map |listeners| {
|
||||||
|
listeners.iter().map(|entry| entry.listener).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_listeners_for(&self, type_: ~str, desired_phase: ListenerPhase)
|
||||||
|
-> Option<~[EventListener]> {
|
||||||
|
do self.handlers.find_equiv(&type_).map |listeners| {
|
||||||
|
let filtered = listeners.iter().filter(|entry| entry.phase == desired_phase);
|
||||||
|
filtered.map(|entry| entry.listener).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn AddEventListener(&mut self,
|
pub fn AddEventListener(&mut self,
|
||||||
ty: &DOMString,
|
ty: &DOMString,
|
||||||
listener: Option<EventListener>,
|
listener: Option<EventListener>,
|
||||||
capture: bool) {
|
capture: bool) {
|
||||||
// TODO: Handle adding a listener during event dispatch: should not be invoked during
|
for &listener in listener.iter() {
|
||||||
// current phase.
|
let entry = self.handlers.find_or_insert_with(null_str_as_word_null(ty), |_| ~[]);
|
||||||
// (https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener#Adding_a_listener_during_event_dispatch)
|
let phase = if capture { Capturing } else { Bubbling };
|
||||||
|
let new_entry = EventListenerEntry {
|
||||||
for listener in listener.iter() {
|
phase: phase,
|
||||||
let handlers = if capture {
|
listener: listener
|
||||||
&mut self.capturing_handlers
|
|
||||||
} else {
|
|
||||||
&mut self.bubbling_handlers
|
|
||||||
};
|
};
|
||||||
let entry = handlers.find_or_insert_with(null_str_as_word_null(ty), |_| ~[]);
|
if entry.position_elem(&new_entry).is_none() {
|
||||||
if entry.position_elem(listener).is_none() {
|
entry.push(new_entry);
|
||||||
entry.push((*listener).clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,15 +172,15 @@ impl EventTarget {
|
||||||
ty: &DOMString,
|
ty: &DOMString,
|
||||||
listener: Option<EventListener>,
|
listener: Option<EventListener>,
|
||||||
capture: bool) {
|
capture: bool) {
|
||||||
for listener in listener.iter() {
|
for &listener in listener.iter() {
|
||||||
let handlers = if capture {
|
let mut entry = self.handlers.find_mut(&null_str_as_word_null(ty));
|
||||||
&mut self.capturing_handlers
|
|
||||||
} else {
|
|
||||||
&mut self.bubbling_handlers
|
|
||||||
};
|
|
||||||
let mut entry = handlers.find_mut(&null_str_as_word_null(ty));
|
|
||||||
for entry in entry.mut_iter() {
|
for entry in entry.mut_iter() {
|
||||||
let position = entry.position_elem(listener);
|
let phase = if capture { Capturing } else { Bubbling };
|
||||||
|
let old_entry = EventListenerEntry {
|
||||||
|
phase: phase,
|
||||||
|
listener: listener
|
||||||
|
};
|
||||||
|
let position = entry.position_elem(&old_entry);
|
||||||
for &position in position.iter() {
|
for &position in position.iter() {
|
||||||
entry.remove(position);
|
entry.remove(position);
|
||||||
}
|
}
|
||||||
|
@ -152,25 +188,11 @@ impl EventTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn DispatchEvent(&self, _abstract_self: AbstractEventTarget, event: AbstractEvent) -> Fallible<bool> {
|
pub fn DispatchEvent(&self, abstract_self: AbstractEventTarget, event: AbstractEvent) -> Fallible<bool> {
|
||||||
//FIXME: get proper |this| object
|
if event.event().dispatching || !event.event().initialized {
|
||||||
|
return Err(InvalidState);
|
||||||
let type_ = event.event().type_.clone();
|
|
||||||
let maybe_handlers = self.capturing_handlers.find(&type_);
|
|
||||||
for handlers in maybe_handlers.iter() {
|
|
||||||
for handler in handlers.iter() {
|
|
||||||
handler.HandleEvent__(event, eReportExceptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if event.event().bubbles {
|
Ok(dispatch_event(abstract_self, event))
|
||||||
let maybe_handlers = self.bubbling_handlers.find(&type_);
|
|
||||||
for handlers in maybe_handlers.iter() {
|
|
||||||
for handler in handlers.iter() {
|
|
||||||
handler.HandleEvent__(event, eReportExceptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(!event.event().DefaultPrevented())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use dom::document::{AbstractDocument, DocumentTypeId};
|
||||||
use dom::documenttype::DocumentType;
|
use dom::documenttype::DocumentType;
|
||||||
use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId};
|
use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId};
|
||||||
use dom::element::{HTMLStyleElementTypeId};
|
use dom::element::{HTMLStyleElementTypeId};
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::{AbstractEventTarget, EventTarget, NodeTypeId};
|
||||||
use dom::nodelist::{NodeList};
|
use dom::nodelist::{NodeList};
|
||||||
use dom::htmlimageelement::HTMLImageElement;
|
use dom::htmlimageelement::HTMLImageElement;
|
||||||
use dom::htmliframeelement::HTMLIFrameElement;
|
use dom::htmliframeelement::HTMLIFrameElement;
|
||||||
|
@ -211,6 +211,13 @@ impl<'self, View> AbstractNode<View> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_eventtarget(target: AbstractEventTarget) -> AbstractNode<View> {
|
||||||
|
assert!(target.is_node());
|
||||||
|
unsafe {
|
||||||
|
cast::transmute(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Convenience accessors
|
// Convenience accessors
|
||||||
|
|
||||||
/// Returns the type ID of this node. Fails if this node is borrowed mutably.
|
/// Returns the type ID of this node. Fails if this node is borrowed mutably.
|
||||||
|
@ -522,7 +529,7 @@ impl Node<ScriptView> {
|
||||||
|
|
||||||
fn new_(type_id: NodeTypeId, doc: Option<AbstractDocument>) -> Node<ScriptView> {
|
fn new_(type_id: NodeTypeId, doc: Option<AbstractDocument>) -> Node<ScriptView> {
|
||||||
Node {
|
Node {
|
||||||
eventtarget: EventTarget::new(),
|
eventtarget: EventTarget::new_inherited(NodeTypeId),
|
||||||
type_id: type_id,
|
type_id: type_id,
|
||||||
|
|
||||||
abstract: None,
|
abstract: None,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use dom::bindings::codegen::WindowBinding;
|
||||||
use dom::bindings::utils::{Reflectable, Reflector};
|
use dom::bindings::utils::{Reflectable, Reflector};
|
||||||
use dom::bindings::utils::{DOMString, null_str_as_empty, Traceable};
|
use dom::bindings::utils::{DOMString, null_str_as_empty, Traceable};
|
||||||
use dom::document::AbstractDocument;
|
use dom::document::AbstractDocument;
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::{EventTarget, WindowTypeId};
|
||||||
use dom::node::{AbstractNode, ScriptView};
|
use dom::node::{AbstractNode, ScriptView};
|
||||||
use dom::navigator::Navigator;
|
use dom::navigator::Navigator;
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ impl Window {
|
||||||
image_cache_task: ImageCacheTask)
|
image_cache_task: ImageCacheTask)
|
||||||
-> @mut Window {
|
-> @mut Window {
|
||||||
let win = @mut Window {
|
let win = @mut Window {
|
||||||
eventtarget: EventTarget::new(),
|
eventtarget: EventTarget::new_inherited(WindowTypeId),
|
||||||
page: page,
|
page: page,
|
||||||
script_chan: script_chan.clone(),
|
script_chan: script_chan.clone(),
|
||||||
compositor: compositor,
|
compositor: compositor,
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub mod dom {
|
||||||
pub mod domparser;
|
pub mod domparser;
|
||||||
pub mod element;
|
pub mod element;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod eventdispatcher;
|
||||||
pub mod eventtarget;
|
pub mod eventtarget;
|
||||||
pub mod formdata;
|
pub mod formdata;
|
||||||
pub mod htmlanchorelement;
|
pub mod htmlanchorelement;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<script>
|
<script>
|
||||||
is_function(Event, "Event");
|
is_function(Event, "Event");
|
||||||
|
|
||||||
let ev = new Event("foopy");
|
let ev = new Event("foopy", {cancelable: true});
|
||||||
is_a(ev, Event);
|
is_a(ev, Event);
|
||||||
|
|
||||||
is(ev.type, 'foopy');
|
is(ev.type, 'foopy');
|
||||||
|
|
51
src/test/html/content/test_event_dispatch.html
Normal file
51
src/test/html/content/test_event_dispatch.html
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="harness.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span>Paragraph containing <div>event listener</div>.</span>
|
||||||
|
<script>
|
||||||
|
var bodyTimes = 0;
|
||||||
|
function bodyListener(ev) {
|
||||||
|
bodyTimes++;
|
||||||
|
is(ev.currentTarget, document.getElementsByTagName('body')[0]);
|
||||||
|
is(ev.target, document.getElementsByTagName('div')[0]);
|
||||||
|
if (bodyTimes == 1) {
|
||||||
|
is(ev.eventPhase, ev.CAPTURING_PHASE);
|
||||||
|
} else if (bodyTimes == 2) {
|
||||||
|
is(ev.eventPhase, ev.BUBBLING_PHASE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var spanTimes = 0;
|
||||||
|
function spanListener(ev) {
|
||||||
|
is(ev.currentTarget, document.getElementsByTagName('span')[0]);
|
||||||
|
is(ev.target, document.getElementsByTagName('div')[0]);
|
||||||
|
is(ev.eventPhase, ev.BUBBLING_PHASE);
|
||||||
|
spanTimes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var divTimes = 0;
|
||||||
|
function divListener(ev) {
|
||||||
|
var self = document.getElementsByTagName('div')[0];
|
||||||
|
is(ev.currentTarget, self);
|
||||||
|
is(ev.target, self);
|
||||||
|
is(ev.eventPhase, ev.AT_TARGET);
|
||||||
|
divTimes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementsByTagName('body')[0].addEventListener("foopy", bodyListener, true);
|
||||||
|
document.getElementsByTagName('body')[0].addEventListener("foopy", bodyListener, false);
|
||||||
|
document.getElementsByTagName('span')[0].addEventListener("foopy", spanListener, false);
|
||||||
|
document.getElementsByTagName('div')[0].addEventListener("foopy", divListener, false);
|
||||||
|
var ev = new Event('foopy', {bubbles: true});
|
||||||
|
is(ev.bubbles, true);
|
||||||
|
document.getElementsByTagName('div')[0].dispatchEvent(ev);
|
||||||
|
is(bodyTimes, 2, 'body listener should be called multiple times');
|
||||||
|
is(divTimes, 1, 'target listener should be called once');
|
||||||
|
is(spanTimes, 1, 'span listener should be called while bubbling');
|
||||||
|
|
||||||
|
finish();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
src/test/html/content/test_event_dispatch_dynamic.html
Normal file
21
src/test/html/content/test_event_dispatch_dynamic.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="harness.js"></script>
|
||||||
|
<b><b><b></b></b></b>
|
||||||
|
<script>
|
||||||
|
var sawmiddle = -1;
|
||||||
|
var sawouter = -1;
|
||||||
|
var step = 0;
|
||||||
|
var outerb = document.getElementsByTagName('b')[0];
|
||||||
|
var middleb = outerb.firstChild;
|
||||||
|
var innerb = middleb.firstChild;
|
||||||
|
outerb.addEventListener("x", function() {
|
||||||
|
middleb.addEventListener("x", function() {
|
||||||
|
sawmiddle = step++;
|
||||||
|
}, true);
|
||||||
|
sawouter = step++;
|
||||||
|
}, true);
|
||||||
|
innerb.dispatchEvent(new Event("x"));
|
||||||
|
is(sawmiddle, 1);
|
||||||
|
is(sawouter, 0);
|
||||||
|
finish();
|
||||||
|
</script>
|
42
src/test/html/content/test_event_dispatch_order.html
Normal file
42
src/test/html/content/test_event_dispatch_order.html
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="harness.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="foo"></div>
|
||||||
|
<script>
|
||||||
|
var sawBubble = false;
|
||||||
|
var sawCapture = false;
|
||||||
|
var sawBubbleTwice = false;
|
||||||
|
function handler(ev) {
|
||||||
|
is(ev.eventPhase, ev.AT_TARGET);
|
||||||
|
is(sawBubble, false);
|
||||||
|
is(sawCapture, false);
|
||||||
|
sawBubble = true;
|
||||||
|
}
|
||||||
|
function handler2(ev) {
|
||||||
|
is(ev.eventPhase, ev.AT_TARGET);
|
||||||
|
is(sawBubble, true);
|
||||||
|
is(sawCapture, false);
|
||||||
|
sawCapture = true;
|
||||||
|
}
|
||||||
|
function handler3(ev) {
|
||||||
|
is(ev.eventPhase, ev.AT_TARGET);
|
||||||
|
is(sawBubble, true);
|
||||||
|
is(sawCapture, true);
|
||||||
|
sawBubbleTwice = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = document.getElementById('foo');
|
||||||
|
target.addEventListener('foopy', handler, false);
|
||||||
|
target.addEventListener('foopy', handler2, true);
|
||||||
|
target.addEventListener('foopy', handler3, false);
|
||||||
|
var ev = new Event('foopy', {bubbles: true});
|
||||||
|
target.dispatchEvent(ev);
|
||||||
|
is(sawBubble, true);
|
||||||
|
is(sawCapture, true);
|
||||||
|
is(sawBubbleTwice, true);
|
||||||
|
finish();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -4,16 +4,35 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
var saw_event = false;
|
function onFoopy(ev) {
|
||||||
function onFoopy() {
|
|
||||||
window.removeEventListener('foopy', onFoopy);
|
window.removeEventListener('foopy', onFoopy);
|
||||||
saw_event = true;
|
is(ev instanceof expected, true);
|
||||||
|
is(ev.type, 'foopy');
|
||||||
}
|
}
|
||||||
window.addEventListener('foopy', onFoopy);
|
|
||||||
var ev = document.createEvent('HTMLEvents');
|
var expected;
|
||||||
ev.initEvent('foopy', true, true);
|
var events = [['HTMLEvents', Event, function(ev) { ev.initEvent('foopy', true, true); }],
|
||||||
window.dispatchEvent(ev);
|
['UIEvents', UIEvent, function(ev) { ev.initUIEvent('foopy', true, true, null, 0); }],
|
||||||
is(saw_event, true);
|
['MouseEvents', MouseEvent,
|
||||||
|
function(ev) { ev.initMouseEvent('foopy', true, true, null, 0,
|
||||||
|
0, 0, 0, 0, false, false,
|
||||||
|
false, false, 0, null); }]];
|
||||||
|
for (var i = 0; i < events.length; i++) {
|
||||||
|
addEventListener('foopy', onFoopy);
|
||||||
|
expected = events[i][1];
|
||||||
|
var ev = document.createEvent(events[i][0]);
|
||||||
|
events[i][2](ev);
|
||||||
|
window.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
var constructors = [Event, UIEvent, MouseEvent];
|
||||||
|
for (var i = 0; i < constructors.length; i++) {
|
||||||
|
addEventListener('foopy', onFoopy);
|
||||||
|
expected = constructors[i];
|
||||||
|
var ev = new constructors[i]('foopy', {cancelable: true, bubbles: true});
|
||||||
|
window.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue