Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80684 views
1
/* DOM Level2 Events implemented as described here:
2
*
3
* http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
4
*
5
*/
6
var core = require("./core").dom.level2.core,
7
utils = require("../utils"),
8
defineGetter = utils.defineGetter,
9
defineSetter = utils.defineSetter,
10
inheritFrom = utils.inheritFrom;
11
12
// modify cloned instance for more info check: https://github.com/tmpvar/jsdom/issues/325
13
core = Object.create(core);
14
15
var events = {};
16
17
events.EventException = function() {
18
if (arguments.length > 0) {
19
this._code = arguments[0];
20
} else {
21
this._code = 0;
22
}
23
if (arguments.length > 1) {
24
this._message = arguments[1];
25
} else {
26
this._message = "Unspecified event type";
27
}
28
Error.call(this, this._message);
29
if (Error.captureStackTrace) {
30
Error.captureStackTrace(this, events.EventException);
31
}
32
};
33
inheritFrom(Error, events.EventException, {
34
UNSPECIFIED_EVENT_TYPE_ERR : 0,
35
get code() { return this._code;}
36
});
37
38
events.Event = function(eventType) {
39
this._eventType = eventType;
40
this._type = null;
41
this._bubbles = null;
42
this._cancelable = null;
43
this._target = null;
44
this._currentTarget = null;
45
this._eventPhase = null;
46
this._timeStamp = null;
47
this._preventDefault = false;
48
this._stopPropagation = false;
49
};
50
events.Event.prototype = {
51
initEvent: function(type, bubbles, cancelable) {
52
this._type = type;
53
this._bubbles = bubbles;
54
this._cancelable = cancelable;
55
},
56
preventDefault: function() {
57
if (this._cancelable) {
58
this._preventDefault = true;
59
}
60
},
61
stopPropagation: function() {
62
this._stopPropagation = true;
63
},
64
CAPTURING_PHASE : 1,
65
AT_TARGET : 2,
66
BUBBLING_PHASE : 3,
67
get eventType() { return this._eventType; },
68
get type() { return this._type; },
69
get bubbles() { return this._bubbles; },
70
get cancelable() { return this._cancelable; },
71
get target() { return this._target; },
72
get currentTarget() { return this._currentTarget; },
73
get eventPhase() { return this._eventPhase; },
74
get timeStamp() { return this._timeStamp; }
75
};
76
77
78
events.UIEvent = function(eventType) {
79
events.Event.call(this, eventType);
80
this.view = null;
81
this.detail = null;
82
};
83
inheritFrom(events.Event, events.UIEvent, {
84
initUIEvent: function(type, bubbles, cancelable, view, detail) {
85
this.initEvent(type, bubbles, cancelable);
86
this.view = view;
87
this.detail = detail;
88
},
89
});
90
91
92
events.MouseEvent = function(eventType) {
93
events.UIEvent.call(this, eventType);
94
this.screenX = null;
95
this.screenY = null;
96
this.clientX = null;
97
this.clientY = null;
98
this.ctrlKey = null;
99
this.shiftKey = null;
100
this.altKey = null;
101
this.metaKey = null;
102
this.button = null;
103
this.relatedTarget = null;
104
};
105
inheritFrom(events.UIEvent, events.MouseEvent, {
106
initMouseEvent: function(type,
107
bubbles,
108
cancelable,
109
view,
110
detail,
111
screenX,
112
screenY,
113
clientX,
114
clientY,
115
ctrlKey,
116
altKey,
117
shiftKey,
118
metaKey,
119
button,
120
relatedTarget) {
121
this.initUIEvent(type, bubbles, cancelable, view, detail);
122
this.screenX = screenX
123
this.screenY = screenY
124
this.clientX = clientX
125
this.clientY = clientY
126
this.ctrlKey = ctrlKey
127
this.shiftKey = shiftKey
128
this.altKey = altKey
129
this.metaKey = metaKey
130
this.button = button
131
this.relatedTarget = relatedTarget
132
}
133
});
134
135
136
events.MutationEvent = function(eventType) {
137
events.Event.call(this, eventType);
138
this.relatedNode = null;
139
this.prevValue = null;
140
this.newValue = null;
141
this.attrName = null;
142
this.attrChange = null;
143
};
144
inheritFrom(events.Event, events.MutationEvent, {
145
initMutationEvent: function(type,
146
bubbles,
147
cancelable,
148
relatedNode,
149
prevValue,
150
newValue,
151
attrName,
152
attrChange) {
153
this.initEvent(type, bubbles, cancelable);
154
this.relatedNode = relatedNode;
155
this.prevValue = prevValue;
156
this.newValue = newValue;
157
this.attrName = attrName;
158
this.attrChange = attrChange;
159
},
160
MODIFICATION : 1,
161
ADDITION : 2,
162
REMOVAL : 3
163
});
164
165
events.EventTarget = function() {};
166
167
events.EventTarget.getListeners = function getListeners(target, type, capturing) {
168
var listeners = target._listeners
169
&& target._listeners[type]
170
&& target._listeners[type][capturing] || [];
171
if (!capturing) {
172
var traditionalHandler = target['on' + type];
173
if (traditionalHandler) {
174
var implementation = (target._ownerDocument ? target._ownerDocument.implementation
175
: target.document.implementation);
176
177
if (implementation.hasFeature('ProcessExternalResources', 'script')) {
178
listeners.push(traditionalHandler);
179
}
180
}
181
}
182
return listeners;
183
};
184
185
events.EventTarget.dispatch = function dispatch(event, iterator, capturing) {
186
var listeners,
187
currentListener,
188
target = iterator();
189
190
while (target && !event._stopPropagation) {
191
listeners = events.EventTarget.getListeners(target, event._type, capturing);
192
currentListener = listeners.length;
193
while (currentListener--) {
194
event._currentTarget = target;
195
try {
196
listeners[currentListener].call(target, event);
197
} catch (e) {
198
target.raise(
199
'error', "Dispatching event '" + event._type + "' failed",
200
{error: e, event: event}
201
);
202
}
203
}
204
target = iterator();
205
}
206
return !event._stopPropagation;
207
};
208
209
events.EventTarget.forwardIterator = function forwardIterator(list) {
210
var i = 0, len = list.length;
211
return function iterator() { return i < len ? list[i++] : null };
212
};
213
214
events.EventTarget.backwardIterator = function backwardIterator(list) {
215
var i = list.length;
216
return function iterator() { return i >=0 ? list[--i] : null };
217
};
218
219
events.EventTarget.singleIterator = function singleIterator(obj) {
220
var i = 1;
221
return function iterator() { return i-- ? obj : null };
222
};
223
224
events.EventTarget.prototype = {
225
addEventListener: function(type, listener, capturing) {
226
this._listeners = this._listeners || {};
227
var listeners = this._listeners[type] || {};
228
capturing = (capturing === true);
229
var capturingListeners = listeners[capturing] || [];
230
for (var i=0; i < capturingListeners.length; i++) {
231
if (capturingListeners[i] === listener) {
232
return;
233
}
234
}
235
capturingListeners.push(listener);
236
listeners[capturing] = capturingListeners;
237
this._listeners[type] = listeners;
238
},
239
240
removeEventListener: function(type, listener, capturing) {
241
var listeners = this._listeners && this._listeners[type];
242
if (!listeners) return;
243
var capturingListeners = listeners[(capturing === true)];
244
if (!capturingListeners) return;
245
for (var i=0; i < capturingListeners.length; i++) {
246
if (capturingListeners[i] === listener) {
247
capturingListeners.splice(i, 1);
248
return;
249
}
250
}
251
},
252
253
dispatchEvent: function(event) {
254
if (event == null) {
255
throw new events.EventException(0, "Null event");
256
}
257
if (event._type == null || event._type == "") {
258
throw new events.EventException(0, "Uninitialized event");
259
}
260
261
var targetList = [];
262
263
event._target = this;
264
265
//per the spec we gather the list of targets first to ensure
266
//against dom modifications during actual event dispatch
267
var target = this,
268
targetParent = target._parentNode;
269
while (targetParent) {
270
targetList.push(targetParent);
271
target = targetParent;
272
targetParent = target._parentNode;
273
}
274
targetParent = target._parentWindow;
275
if (targetParent) {
276
targetList.push(targetParent);
277
}
278
279
var iterator = events.EventTarget.backwardIterator(targetList);
280
281
event._eventPhase = event.CAPTURING_PHASE;
282
if (!events.EventTarget.dispatch(event, iterator, true)) return event._preventDefault;
283
284
iterator = events.EventTarget.singleIterator(event._target);
285
event._eventPhase = event.AT_TARGET;
286
if (!events.EventTarget.dispatch(event, iterator, false)) return event._preventDefault;
287
288
if (event._bubbles && !event._stopPropagation) {
289
var i = 0;
290
iterator = events.EventTarget.forwardIterator(targetList);
291
event._eventPhase = event.BUBBLING_PHASE;
292
events.EventTarget.dispatch(event, iterator, false);
293
}
294
295
return event._preventDefault;
296
}
297
298
};
299
300
// Reinherit class heirarchy with EventTarget at its root
301
inheritFrom(events.EventTarget, core.Node, core.Node.prototype);
302
303
// Node
304
inheritFrom(core.Node, core.Attr, core.Attr.prototype);
305
inheritFrom(core.Node, core.CharacterData, core.CharacterData.prototype);
306
inheritFrom(core.Node, core.Document, core.Document.prototype);
307
inheritFrom(core.Node, core.DocumentFragment, core.DocumentFragment.prototype);
308
inheritFrom(core.Node, core.DocumentType, core.DocumentType.prototype);
309
inheritFrom(core.Node, core.Element, core.Element.prototype);
310
inheritFrom(core.Node, core.Entity, core.Entity.prototype);
311
inheritFrom(core.Node, core.EntityReference, core.EntityReference.prototype);
312
inheritFrom(core.Node, core.Notation, core.Notation.prototype);
313
inheritFrom(core.Node, core.ProcessingInstruction, core.ProcessingInstruction.prototype);
314
315
// CharacterData
316
inheritFrom(core.CharacterData, core.Text, core.Text.prototype);
317
318
// Text
319
inheritFrom(core.Text, core.CDATASection, core.CDATASection.prototype);
320
inheritFrom(core.Text, core.Comment, core.Comment.prototype);
321
322
function getDocument(el) {
323
return el.nodeType == core.Node.DOCUMENT_NODE ? el : el._ownerDocument;
324
}
325
326
function mutationEventsEnabled(el) {
327
return el.nodeType != core.Node.ATTRIBUTE_NODE &&
328
getDocument(el).implementation.hasFeature('MutationEvents');
329
}
330
331
utils.intercept(core.Node, 'insertBefore', function(_super, args, newChild, refChild) {
332
var ret = _super.apply(this, args);
333
if (mutationEventsEnabled(this)) {
334
var doc = getDocument(this),
335
ev = doc.createEvent("MutationEvents");
336
337
ev.initMutationEvent("DOMNodeInserted", true, false, this, null, null, null, null);
338
newChild.dispatchEvent(ev);
339
if (this.nodeType == core.Node.DOCUMENT_NODE || this._attachedToDocument) {
340
ev = doc.createEvent("MutationEvents");
341
ev.initMutationEvent("DOMNodeInsertedIntoDocument", false, false, null, null, null, null, null);
342
core.visitTree(newChild, function(el) {
343
if (el.nodeType == core.Node.ELEMENT_NODE) {
344
el.dispatchEvent(ev);
345
el._attachedToDocument = true;
346
}
347
});
348
}
349
}
350
return ret;
351
});
352
353
utils.intercept(core.Node, 'removeChild', function (_super, args, oldChild) {
354
if (mutationEventsEnabled(this)) {
355
var doc = getDocument(this),
356
ev = doc.createEvent("MutationEvents");
357
358
ev.initMutationEvent("DOMNodeRemoved", true, false, this, null, null, null, null);
359
oldChild.dispatchEvent(ev);
360
361
ev = doc.createEvent("MutationEvents");
362
ev.initMutationEvent("DOMNodeRemovedFromDocument", false, false, null, null, null, null, null);
363
core.visitTree(oldChild, function(el) {
364
if (el.nodeType == core.Node.ELEMENT_NODE) {
365
el.dispatchEvent(ev);
366
el._attachedToDocument = false;
367
}
368
});
369
}
370
return _super.apply(this, args);
371
});
372
373
function dispatchAttrEvent(doc, target, prevVal, newVal, attrName, attrChange) {
374
if (!newVal || newVal != prevVal) {
375
var ev = doc.createEvent("MutationEvents");
376
ev.initMutationEvent("DOMAttrModified", true, false, target, prevVal,
377
newVal, attrName, attrChange);
378
target.dispatchEvent(ev);
379
}
380
}
381
382
function attrNodeInterceptor(change) {
383
return function(_super, args, node) {
384
var target = this._parentNode,
385
prev = _super.apply(this, args);
386
387
if (mutationEventsEnabled(target)) {
388
dispatchAttrEvent(target._ownerDocument,
389
target,
390
prev && prev.value || null,
391
change == 'ADDITION' ? node.value : null,
392
prev && prev.name || node.name,
393
events.MutationEvent.prototype[change]);
394
}
395
396
return prev;
397
};
398
}
399
400
function attrInterceptor(ns) {
401
return function(_super, args, localName, value, _name, _prefix, namespace) {
402
var target = this._parentNode;
403
404
if (!mutationEventsEnabled(target)) {
405
_super.apply(this, args);
406
return;
407
}
408
409
if (namespace === undefined) {
410
namespace = null;
411
}
412
413
var prev =
414
ns ? this.$getNode(namespace, localName) : this.$getNoNS(localName);
415
var prevVal = prev && prev.value || null;
416
417
_super.apply(this, args);
418
419
var node = ns ? this.$getNode(namespace, localName):
420
this.$getNoNS(localName);
421
422
dispatchAttrEvent(target._ownerDocument,
423
target,
424
prevVal,
425
node.value,
426
node.name,
427
events.MutationEvent.prototype.ADDITION);
428
};
429
}
430
431
432
utils.intercept(core.AttributeList, '$removeNode',
433
attrNodeInterceptor('REMOVAL'));
434
utils.intercept(core.AttributeList, '$setNode',
435
attrNodeInterceptor('ADDITION'));
436
utils.intercept(core.AttributeList, '$set', attrInterceptor(true));
437
utils.intercept(core.AttributeList, '$setNoNS', attrInterceptor(false));
438
439
defineGetter(core.CharacterData.prototype, "_nodeValue", function() {
440
return this.__nodeValue;
441
});
442
defineSetter(core.CharacterData.prototype, "_nodeValue", function(value) {
443
var oldValue = this.__nodeValue;
444
this.__nodeValue = value;
445
if (this._ownerDocument && this._parentNode && mutationEventsEnabled(this)) {
446
var ev = this._ownerDocument.createEvent("MutationEvents")
447
ev.initMutationEvent("DOMCharacterDataModified", true, false, this, oldValue, value, null, null);
448
this.dispatchEvent(ev);
449
}
450
});
451
452
core.Document.prototype.createEvent = function(eventType) {
453
switch (eventType) {
454
case "MutationEvents": return new events.MutationEvent(eventType);
455
case "UIEvents": return new events.UIEvent(eventType);
456
case "MouseEvents": return new events.MouseEvent(eventType);
457
case "HTMLEvents": return new events.Event(eventType);
458
}
459
return new events.Event(eventType);
460
};
461
462
exports.dom =
463
{
464
level2 : {
465
core : core,
466
events : events
467
}
468
};
469
470