• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Flags: --expose-internals --no-warnings
2'use strict';
3
4const common = require('../common');
5const {
6  Event,
7  EventTarget,
8  defineEventHandler
9} = require('internal/event_target');
10
11const {
12  ok,
13  deepStrictEqual,
14  strictEqual,
15  throws,
16} = require('assert');
17
18const { once } = require('events');
19
20const { promisify } = require('util');
21const delay = promisify(setTimeout);
22
23// The globals are defined.
24ok(Event);
25ok(EventTarget);
26
27// The warning event has special behavior regarding attaching listeners
28let lastWarning;
29process.on('warning', (e) => {
30  lastWarning = e;
31});
32
33// Utility promise for parts of the test that need to wait for eachother -
34// Namely tests for warning events
35/* eslint-disable no-unused-vars */
36let asyncTest = Promise.resolve();
37
38// First, test Event
39{
40  const ev = new Event('foo');
41  strictEqual(ev.type, 'foo');
42  strictEqual(ev.cancelable, false);
43  strictEqual(ev.defaultPrevented, false);
44  strictEqual(typeof ev.timeStamp, 'number');
45
46  // Compatibility properties with the DOM
47  deepStrictEqual(ev.composedPath(), []);
48  strictEqual(ev.returnValue, true);
49  strictEqual(ev.bubbles, false);
50  strictEqual(ev.composed, false);
51  strictEqual(ev.isTrusted, false);
52  strictEqual(ev.eventPhase, 0);
53  strictEqual(ev.cancelBubble, false);
54
55  // Not cancelable
56  ev.preventDefault();
57  strictEqual(ev.defaultPrevented, false);
58}
59{
60  [
61    'foo',
62    1,
63    false,
64  ].forEach((i) => (
65    throws(() => new Event('foo', i), {
66      code: 'ERR_INVALID_ARG_TYPE',
67      name: 'TypeError',
68      message: 'The "options" argument must be of type object.' +
69               common.invalidArgTypeHelper(i)
70    })
71  ));
72}
73{
74  const ev = new Event('foo');
75  strictEqual(ev.cancelBubble, false);
76  ev.cancelBubble = true;
77  strictEqual(ev.cancelBubble, true);
78}
79{
80  const ev = new Event('foo');
81  strictEqual(ev.cancelBubble, false);
82  ev.stopPropagation();
83  strictEqual(ev.cancelBubble, true);
84}
85{
86  const ev = new Event('foo');
87  strictEqual(ev.cancelBubble, false);
88  ev.cancelBubble = 'some-truthy-value';
89  strictEqual(ev.cancelBubble, true);
90}
91{
92  // No argument behavior - throw TypeError
93  throws(() => {
94    new Event();
95  }, TypeError);
96  // Too many arguments passed behavior - ignore additional arguments
97  const ev = new Event('foo', {}, {});
98  strictEqual(ev.type, 'foo');
99}
100{
101  const ev = new Event('foo');
102  strictEqual(ev.cancelBubble, false);
103  ev.cancelBubble = true;
104  strictEqual(ev.cancelBubble, true);
105}
106{
107  const ev = new Event('foo');
108  strictEqual(ev.cancelBubble, false);
109  ev.stopPropagation();
110  strictEqual(ev.cancelBubble, true);
111}
112{
113  const ev = new Event('foo');
114  strictEqual(ev.cancelBubble, false);
115  ev.cancelBubble = 'some-truthy-value';
116  strictEqual(ev.cancelBubble, true);
117}
118{
119  const ev = new Event('foo', { cancelable: true });
120  strictEqual(ev.type, 'foo');
121  strictEqual(ev.cancelable, true);
122  strictEqual(ev.defaultPrevented, false);
123
124  ev.preventDefault();
125  strictEqual(ev.defaultPrevented, true);
126  throws(() => new Event(Symbol()), TypeError);
127}
128{
129  const ev = new Event('foo');
130  deepStrictEqual(Object.keys(ev), ['isTrusted']);
131}
132{
133  const eventTarget = new EventTarget();
134
135  const ev1 = common.mustCall(function(event) {
136    strictEqual(event.type, 'foo');
137    strictEqual(this, eventTarget);
138    strictEqual(event.eventPhase, 2);
139  }, 2);
140
141  const ev2 = {
142    handleEvent: common.mustCall(function(event) {
143      strictEqual(event.type, 'foo');
144      strictEqual(this, ev2);
145    })
146  };
147
148  eventTarget.addEventListener('foo', ev1);
149  eventTarget.addEventListener('foo', ev2, { once: true });
150  ok(eventTarget.dispatchEvent(new Event('foo')));
151  eventTarget.dispatchEvent(new Event('foo'));
152
153  eventTarget.removeEventListener('foo', ev1);
154  eventTarget.dispatchEvent(new Event('foo'));
155}
156{
157  // event subclassing
158  const SubEvent = class extends Event {};
159  const ev = new SubEvent('foo');
160  const eventTarget = new EventTarget();
161  const fn = common.mustCall((event) => strictEqual(event, ev));
162  eventTarget.addEventListener('foo', fn, { once: true });
163  eventTarget.dispatchEvent(ev);
164}
165
166{
167  const eventTarget = new EventTarget();
168  const event = new Event('foo', { cancelable: true });
169  eventTarget.addEventListener('foo', (event) => event.preventDefault());
170  ok(!eventTarget.dispatchEvent(event));
171}
172{
173  // Adding event listeners with a boolean useCapture
174  const eventTarget = new EventTarget();
175  const event = new Event('foo');
176  const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
177  eventTarget.addEventListener('foo', fn, false);
178  eventTarget.dispatchEvent(event);
179}
180
181{
182  // The `options` argument can be `null`.
183  const eventTarget = new EventTarget();
184  const event = new Event('foo');
185  const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
186  eventTarget.addEventListener('foo', fn, null);
187  eventTarget.dispatchEvent(event);
188}
189
190{
191  const uncaughtException = common.mustCall((err, event) => {
192    strictEqual(err.message, 'boom');
193    strictEqual(event.type, 'foo');
194  }, 4);
195
196  // Whether or not the handler function is async or not, errors
197  // are routed to uncaughtException
198  process.on('error', uncaughtException);
199
200  const eventTarget = new EventTarget();
201
202  const ev1 = async () => { throw new Error('boom'); };
203  const ev2 = () => { throw new Error('boom'); };
204  const ev3 = { handleEvent() { throw new Error('boom'); } };
205  const ev4 = { async handleEvent() { throw new Error('boom'); } };
206
207  // Errors in a handler won't stop calling the others.
208  eventTarget.addEventListener('foo', ev1, { once: true });
209  eventTarget.addEventListener('foo', ev2, { once: true });
210  eventTarget.addEventListener('foo', ev3, { once: true });
211  eventTarget.addEventListener('foo', ev4, { once: true });
212
213  eventTarget.dispatchEvent(new Event('foo'));
214}
215
216{
217  const eventTarget = new EventTarget();
218
219  // Once handler only invoked once
220  const ev = common.mustCall((event) => {
221    // Can invoke the same event name recursively
222    eventTarget.dispatchEvent(new Event('foo'));
223  });
224
225  // Errors in a handler won't stop calling the others.
226  eventTarget.addEventListener('foo', ev, { once: true });
227
228  eventTarget.dispatchEvent(new Event('foo'));
229}
230
231{
232  // Coercion to string works
233  strictEqual((new Event(1)).type, '1');
234  strictEqual((new Event(false)).type, 'false');
235  strictEqual((new Event({})).type, String({}));
236
237  const target = new EventTarget();
238
239  [
240    'foo',
241    {},  // No type event
242    undefined,
243    1,
244    false,
245  ].forEach((i) => {
246    throws(() => target.dispatchEvent(i), {
247      code: 'ERR_INVALID_ARG_TYPE',
248      name: 'TypeError',
249      message: 'The "event" argument must be an instance of Event.' +
250               common.invalidArgTypeHelper(i)
251    });
252  });
253
254  const err = (arg) => ({
255    code: 'ERR_INVALID_ARG_TYPE',
256    name: 'TypeError',
257    message: 'The "listener" argument must be an instance of EventListener.' +
258             common.invalidArgTypeHelper(arg)
259  });
260
261  [
262    'foo',
263    1,
264    {},  // No handleEvent function
265    false,
266  ].forEach((i) => throws(() => target.addEventListener('foo', i), err(i)));
267}
268
269{
270  const target = new EventTarget();
271  once(target, 'foo').then(common.mustCall());
272  target.dispatchEvent(new Event('foo'));
273}
274
275{
276  const target = new EventTarget();
277  const event = new Event('foo');
278  event.stopImmediatePropagation();
279  target.addEventListener('foo', common.mustNotCall());
280  target.dispatchEvent(event);
281}
282
283{
284  const target = new EventTarget();
285  const event = new Event('foo');
286  target.addEventListener('foo', common.mustCall((event) => {
287    event.stopImmediatePropagation();
288  }));
289  target.addEventListener('foo', common.mustNotCall());
290  target.dispatchEvent(event);
291}
292
293{
294  const target = new EventTarget();
295  const event = new Event('foo');
296  target.addEventListener('foo', common.mustCall((event) => {
297    event.stopImmediatePropagation();
298  }));
299  target.addEventListener('foo', common.mustNotCall());
300  target.dispatchEvent(event);
301}
302
303{
304  const target = new EventTarget();
305  const event = new Event('foo');
306  strictEqual(event.target, null);
307  target.addEventListener('foo', common.mustCall((event) => {
308    strictEqual(event.target, target);
309    strictEqual(event.currentTarget, target);
310    strictEqual(event.srcElement, target);
311  }));
312  target.dispatchEvent(event);
313}
314
315{
316  const target1 = new EventTarget();
317  const target2 = new EventTarget();
318  const event = new Event('foo');
319  target1.addEventListener('foo', common.mustCall((event) => {
320    throws(() => target2.dispatchEvent(event), {
321      code: 'ERR_EVENT_RECURSION'
322    });
323  }));
324  target1.dispatchEvent(event);
325}
326
327{
328  const target = new EventTarget();
329  const a = common.mustCall(() => target.removeEventListener('foo', a));
330  const b = common.mustCall(2);
331
332  target.addEventListener('foo', a);
333  target.addEventListener('foo', b);
334
335  target.dispatchEvent(new Event('foo'));
336  target.dispatchEvent(new Event('foo'));
337}
338
339{
340  const target = new EventTarget();
341  const a = common.mustCall(3);
342
343  target.addEventListener('foo', a, { capture: true });
344  target.addEventListener('foo', a, { capture: false });
345
346  target.dispatchEvent(new Event('foo'));
347  target.removeEventListener('foo', a, { capture: true });
348  target.dispatchEvent(new Event('foo'));
349  target.removeEventListener('foo', a, { capture: false });
350  target.dispatchEvent(new Event('foo'));
351}
352{
353  const target = new EventTarget();
354  strictEqual(target.toString(), '[object EventTarget]');
355  const event = new Event('');
356  strictEqual(event.toString(), '[object Event]');
357}
358{
359  const target = new EventTarget();
360  defineEventHandler(target, 'foo');
361  target.onfoo = common.mustCall();
362  target.dispatchEvent(new Event('foo'));
363}
364{
365  const target = new EventTarget();
366  defineEventHandler(target, 'foo');
367  let count = 0;
368  target.onfoo = () => count++;
369  target.onfoo = common.mustCall(() => count++);
370  target.dispatchEvent(new Event('foo'));
371  strictEqual(count, 1);
372}
373{
374  const target = new EventTarget();
375  defineEventHandler(target, 'foo');
376  let count = 0;
377  target.addEventListener('foo', () => count++);
378  target.onfoo = common.mustCall(() => count++);
379  target.dispatchEvent(new Event('foo'));
380  strictEqual(count, 2);
381}
382{
383  const target = new EventTarget();
384  defineEventHandler(target, 'foo');
385  const fn = common.mustNotCall();
386  target.onfoo = fn;
387  strictEqual(target.onfoo, fn);
388  target.onfoo = null;
389  target.dispatchEvent(new Event('foo'));
390}
391
392{
393  // `this` value of dispatchEvent
394  const target = new EventTarget();
395  const target2 = new EventTarget();
396  const event = new Event('foo');
397
398  ok(target.dispatchEvent.call(target2, event));
399
400  [
401    'foo',
402    {},
403    [],
404    1,
405    null,
406    undefined,
407    false,
408    Symbol(),
409    /a/,
410  ].forEach((i) => {
411    throws(() => target.dispatchEvent.call(i, event), {
412      code: 'ERR_INVALID_THIS'
413    });
414  });
415}
416
417{
418  // Event Statics
419  strictEqual(Event.NONE, 0);
420  strictEqual(Event.CAPTURING_PHASE, 1);
421  strictEqual(Event.AT_TARGET, 2);
422  strictEqual(Event.BUBBLING_PHASE, 3);
423  strictEqual(new Event('foo').eventPhase, Event.NONE);
424  const target = new EventTarget();
425  target.addEventListener('foo', common.mustCall((e) => {
426    strictEqual(e.eventPhase, Event.AT_TARGET);
427  }), { once: true });
428  target.dispatchEvent(new Event('foo'));
429  // Event is a function
430  strictEqual(Event.length, 1);
431}
432
433{
434  const target = new EventTarget();
435  const ev = new Event('toString');
436  const fn = common.mustCall((event) => strictEqual(event.type, 'toString'));
437  target.addEventListener('toString', fn);
438  target.dispatchEvent(ev);
439}
440{
441  const target = new EventTarget();
442  const ev = new Event('__proto__');
443  const fn = common.mustCall((event) => strictEqual(event.type, '__proto__'));
444  target.addEventListener('__proto__', fn);
445  target.dispatchEvent(ev);
446}
447
448{
449  const eventTarget = new EventTarget();
450  // Single argument throws
451  throws(() => eventTarget.addEventListener('foo'), TypeError);
452  // Null events - does not throw
453  eventTarget.addEventListener('foo', null);
454  eventTarget.removeEventListener('foo', null);
455  eventTarget.addEventListener('foo', undefined);
456  eventTarget.removeEventListener('foo', undefined);
457  // Strings, booleans
458  throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
459  throws(() => eventTarget.addEventListener('foo', false), TypeError);
460  throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
461  asyncTest = asyncTest.then(async () => {
462    const eventTarget = new EventTarget();
463    // Single argument throws
464    throws(() => eventTarget.addEventListener('foo'), TypeError);
465    // Null events - does not throw
466
467    eventTarget.addEventListener('foo', null);
468    eventTarget.removeEventListener('foo', null);
469
470    // Warnings always happen after nextTick, so wait for a timer of 0
471    await delay(0);
472    strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
473    strictEqual(lastWarning.target, eventTarget);
474    lastWarning = null;
475    eventTarget.addEventListener('foo', undefined);
476    await delay(0);
477    strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
478    strictEqual(lastWarning.target, eventTarget);
479    eventTarget.removeEventListener('foo', undefined);
480    // Strings, booleans
481    throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
482    throws(() => eventTarget.addEventListener('foo', false), TypeError);
483    throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
484  });
485}
486{
487  const eventTarget = new EventTarget();
488  const event = new Event('foo');
489  eventTarget.dispatchEvent(event);
490  strictEqual(event.target, eventTarget);
491}
492{
493  // Event target exported keys
494  const eventTarget = new EventTarget();
495  deepStrictEqual(Object.keys(eventTarget), []);
496  deepStrictEqual(Object.getOwnPropertyNames(eventTarget), []);
497  const parentKeys = Object.keys(Object.getPrototypeOf(eventTarget)).sort();
498  const keys = ['addEventListener', 'dispatchEvent', 'removeEventListener'];
499  deepStrictEqual(parentKeys, keys);
500}
501{
502  // Subclassing
503  class SubTarget extends EventTarget {}
504  const target = new SubTarget();
505  target.addEventListener('foo', common.mustCall());
506  target.dispatchEvent(new Event('foo'));
507}
508{
509  // Test event order
510  const target = new EventTarget();
511  let state = 0;
512  target.addEventListener('foo', common.mustCall(() => {
513    strictEqual(state, 0);
514    state++;
515  }));
516  target.addEventListener('foo', common.mustCall(() => {
517    strictEqual(state, 1);
518  }));
519  target.dispatchEvent(new Event('foo'));
520}
521{
522  const target = new EventTarget();
523  defineEventHandler(target, 'foo');
524  const descriptor = Object.getOwnPropertyDescriptor(target, 'onfoo');
525  strictEqual(descriptor.configurable, true);
526  strictEqual(descriptor.enumerable, true);
527}
528{
529  const target = new EventTarget();
530  defineEventHandler(target, 'foo');
531  const output = [];
532  target.addEventListener('foo', () => output.push(1));
533  target.onfoo = common.mustNotCall();
534  target.addEventListener('foo', () => output.push(3));
535  target.onfoo = () => output.push(2);
536  target.addEventListener('foo', () => output.push(4));
537  target.dispatchEvent(new Event('foo'));
538  deepStrictEqual(output, [1, 2, 3, 4]);
539}
540