File size: 10,495 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
"use strict";
var Event = require('./Event');
var MouseEvent = require('./MouseEvent');
var utils = require('./utils');
module.exports = EventTarget;
function EventTarget() {}
EventTarget.prototype = {
// XXX
// See WebIDL §4.8 for details on object event handlers
// and how they should behave. We actually have to accept
// any object to addEventListener... Can't type check it.
// on registration.
// XXX:
// Capturing event listeners are sort of rare. I think I can optimize
// them so that dispatchEvent can skip the capturing phase (or much of
// it). Each time a capturing listener is added, increment a flag on
// the target node and each of its ancestors. Decrement when removed.
// And update the counter when nodes are added and removed from the
// tree as well. Then, in dispatch event, the capturing phase can
// abort if it sees any node with a zero count.
addEventListener: function addEventListener(type, listener, capture) {
if (!listener) return;
if (capture === undefined) capture = false;
if (!this._listeners) this._listeners = Object.create(null);
if (!this._listeners[type]) this._listeners[type] = [];
var list = this._listeners[type];
// If this listener has already been registered, just return
for(var i = 0, n = list.length; i < n; i++) {
var l = list[i];
if (l.listener === listener && l.capture === capture)
return;
}
// Add an object to the list of listeners
var obj = { listener: listener, capture: capture };
if (typeof listener === 'function') obj.f = listener;
list.push(obj);
},
removeEventListener: function removeEventListener(type,
listener,
capture) {
if (capture === undefined) capture = false;
if (this._listeners) {
var list = this._listeners[type];
if (list) {
// Find the listener in the list and remove it
for(var i = 0, n = list.length; i < n; i++) {
var l = list[i];
if (l.listener === listener && l.capture === capture) {
if (list.length === 1) {
this._listeners[type] = undefined;
}
else {
list.splice(i, 1);
}
return;
}
}
}
}
},
// This is the public API for dispatching untrusted public events.
// See _dispatchEvent for the implementation
dispatchEvent: function dispatchEvent(event) {
// Dispatch an untrusted event
return this._dispatchEvent(event, false);
},
//
// See DOMCore §4.4
// XXX: I'll probably need another version of this method for
// internal use, one that does not set isTrusted to false.
// XXX: see Document._dispatchEvent: perhaps that and this could
// call a common internal function with different settings of
// a trusted boolean argument
//
// XXX:
// The spec has changed in how to deal with handlers registered
// on idl or content attributes rather than with addEventListener.
// Used to say that they always ran first. That's how webkit does it
// Spec now says that they run in a position determined by
// when they were first set. FF does it that way. See:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#event-handlers
//
_dispatchEvent: function _dispatchEvent(event, trusted) {
if (typeof trusted !== 'boolean') trusted = false;
function invoke(target, event) {
var type = event.type, phase = event.eventPhase;
event.currentTarget = target;
// If there was an individual handler defined, invoke it first
// XXX: see comment above: this shouldn't always be first.
if (phase !== Event.CAPTURING_PHASE &&
target._handlers && target._handlers[type])
{
var handler = target._handlers[type];
var rv;
if (typeof handler === 'function') {
rv=handler.call(event.currentTarget, event);
}
else {
var f = handler.handleEvent;
if (typeof f !== 'function')
throw new TypeError('handleEvent property of ' +
'event handler object is' +
'not a function.');
rv=f.call(handler, event);
}
switch(event.type) {
case 'mouseover':
if (rv === true) // Historical baggage
event.preventDefault();
break;
case 'beforeunload':
// XXX: eventually we need a special case here
/* falls through */
default:
if (rv === false)
event.preventDefault();
break;
}
}
// Now invoke list list of listeners for this target and type
var list = target._listeners && target._listeners[type];
if (!list) return;
list = list.slice();
for(var i = 0, n = list.length; i < n; i++) {
if (event._immediatePropagationStopped) return;
var l = list[i];
if ((phase === Event.CAPTURING_PHASE && !l.capture) ||
(phase === Event.BUBBLING_PHASE && l.capture))
continue;
if (l.f) {
l.f.call(event.currentTarget, event);
}
else {
var fn = l.listener.handleEvent;
if (typeof fn !== 'function')
throw new TypeError('handleEvent property of event listener object is not a function.');
fn.call(l.listener, event);
}
}
}
if (!event._initialized || event._dispatching) utils.InvalidStateError();
event.isTrusted = trusted;
// Begin dispatching the event now
event._dispatching = true;
event.target = this;
// Build the list of targets for the capturing and bubbling phases
// XXX: we'll eventually have to add Window to this list.
var ancestors = [];
for(var n = this.parentNode; n; n = n.parentNode)
ancestors.push(n);
// Capturing phase
event.eventPhase = Event.CAPTURING_PHASE;
for(var i = ancestors.length-1; i >= 0; i--) {
invoke(ancestors[i], event);
if (event._propagationStopped) break;
}
// At target phase
if (!event._propagationStopped) {
event.eventPhase = Event.AT_TARGET;
invoke(this, event);
}
// Bubbling phase
if (event.bubbles && !event._propagationStopped) {
event.eventPhase = Event.BUBBLING_PHASE;
for(var ii = 0, nn = ancestors.length; ii < nn; ii++) {
invoke(ancestors[ii], event);
if (event._propagationStopped) break;
}
}
event._dispatching = false;
event.eventPhase = Event.AT_TARGET;
event.currentTarget = null;
// Deal with mouse events and figure out when
// a click has happened
if (trusted && !event.defaultPrevented && event instanceof MouseEvent) {
switch(event.type) {
case 'mousedown':
this._armed = {
x: event.clientX,
y: event.clientY,
t: event.timeStamp
};
break;
case 'mouseout':
case 'mouseover':
this._armed = null;
break;
case 'mouseup':
if (this._isClick(event)) this._doClick(event);
this._armed = null;
break;
}
}
return !event.defaultPrevented;
},
// Determine whether a click occurred
// XXX We don't support double clicks for now
_isClick: function(event) {
return (this._armed !== null &&
event.type === 'mouseup' &&
event.isTrusted &&
event.button === 0 &&
event.timeStamp - this._armed.t < 1000 &&
Math.abs(event.clientX - this._armed.x) < 10 &&
Math.abs(event.clientY - this._armed.Y) < 10);
},
// Clicks are handled like this:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#interactive-content-0
//
// Note that this method is similar to the HTMLElement.click() method
// The event argument must be the trusted mouseup event
_doClick: function(event) {
if (this._click_in_progress) return;
this._click_in_progress = true;
// Find the nearest enclosing element that is activatable
// An element is activatable if it has a
// _post_click_activation_steps hook
var activated = this;
while(activated && !activated._post_click_activation_steps)
activated = activated.parentNode;
if (activated && activated._pre_click_activation_steps) {
activated._pre_click_activation_steps();
}
var click = this.ownerDocument.createEvent('MouseEvent');
click.initMouseEvent('click', true, true,
this.ownerDocument.defaultView, 1,
event.screenX, event.screenY,
event.clientX, event.clientY,
event.ctrlKey, event.altKey,
event.shiftKey, event.metaKey,
event.button, null);
var result = this._dispatchEvent(click, true);
if (activated) {
if (result) {
// This is where hyperlinks get followed, for example.
if (activated._post_click_activation_steps)
activated._post_click_activation_steps(click);
}
else {
if (activated._cancelled_activation_steps)
activated._cancelled_activation_steps();
}
}
},
//
// An event handler is like an event listener, but it registered
// by setting an IDL or content attribute like onload or onclick.
// There can only be one of these at a time for any event type.
// This is an internal method for the attribute accessors and
// content attribute handlers that need to register events handlers.
// The type argument is the same as in addEventListener().
// The handler argument is the same as listeners in addEventListener:
// it can be a function or an object. Pass null to remove any existing
// handler. Handlers are always invoked before any listeners of
// the same type. They are not invoked during the capturing phase
// of event dispatch.
//
_setEventHandler: function _setEventHandler(type, handler) {
if (!this._handlers) this._handlers = Object.create(null);
this._handlers[type] = handler;
},
_getEventHandler: function _getEventHandler(type) {
return (this._handlers && this._handlers[type]) || null;
}
};
|