Skip to content

Commit 6f941fc

Browse files
authored
lib: implement passive listener behavior per spec
Implements the WHATWG DOM specification for passive event listeners, ensuring that calls to `preventDefault()` are correctly ignored within a passive listener context. An internal `kInPassiveListener` state is added to the Event object to track when a passive listener is executing. The `preventDefault()` method and the `returnValue` setter are modified to check this state, as well as the event's `cancelable` property. This state is reliably cleaned up within a `finally` block to prevent state pollution in case a listener throws an error. This resolves previously failing Web Platform Tests (WPT) in `AddEventListenerOptions-passive.any.js`. Refs: https://dom.spec.whatwg.org/#dom-event-preventdefault PR-URL: #59995 Reviewed-By: Daeyeon Jeong <[email protected]> Reviewed-By: Mattias Buelens <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Jason Zhang <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 60f1a5d commit 6f941fc

File tree

3 files changed

+24
-13
lines changed

3 files changed

+24
-13
lines changed

lib/internal/event_target.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const { now } = require('internal/perf/utils');
7676

7777
const kType = Symbol('type');
7878
const kDetail = Symbol('detail');
79+
const kInPassiveListener = Symbol('kInPassiveListener');
7980

8081
const isTrustedSet = new SafeWeakSet();
8182
const isTrusted = ObjectGetOwnPropertyDescriptor({
@@ -127,6 +128,7 @@ class Event {
127128

128129
this[kTarget] = null;
129130
this[kIsBeingDispatched] = false;
131+
this[kInPassiveListener] = false;
130132
}
131133

132134
/**
@@ -178,6 +180,7 @@ class Event {
178180
preventDefault() {
179181
if (!isEvent(this))
180182
throw new ERR_INVALID_THIS('Event');
183+
if (!this.#cancelable || this[kInPassiveListener]) return;
181184
this.#defaultPrevented = true;
182185
}
183186

@@ -266,6 +269,19 @@ class Event {
266269
return !this.#cancelable || !this.#defaultPrevented;
267270
}
268271

272+
/**
273+
* @type {boolean}
274+
*/
275+
set returnValue(value) {
276+
if (!isEvent(this))
277+
throw new ERR_INVALID_THIS('Event');
278+
279+
if (!value) {
280+
if (!this.#cancelable || this[kInPassiveListener]) return;
281+
this.#defaultPrevented = true;
282+
}
283+
}
284+
269285
/**
270286
* @type {boolean}
271287
*/
@@ -760,7 +776,6 @@ class EventTarget {
760776
throw new ERR_EVENT_RECURSION(event.type);
761777

762778
this[kHybridDispatch](event, event.type, event);
763-
764779
return event.defaultPrevented !== true;
765780
}
766781

@@ -813,8 +828,8 @@ class EventTarget {
813828
this[kRemoveListener](root.size, type, listener, capture);
814829
}
815830

831+
let arg;
816832
try {
817-
let arg;
818833
if (handler.isNodeStyleListener) {
819834
arg = nodeValue;
820835
} else {
@@ -824,6 +839,9 @@ class EventTarget {
824839
handler.callback.deref() : handler.callback;
825840
let result;
826841
if (callback) {
842+
if (handler.passive && !handler.isNodeStyleListener) {
843+
arg[kInPassiveListener] = true;
844+
}
827845
result = FunctionPrototypeCall(callback, this, arg);
828846
if (!handler.isNodeStyleListener) {
829847
arg[kIsBeingDispatched] = false;
@@ -833,6 +851,9 @@ class EventTarget {
833851
addCatch(result);
834852
} catch (err) {
835853
emitUncaughtException(err);
854+
} finally {
855+
if (arg?.[kInPassiveListener])
856+
arg[kInPassiveListener] = false;
836857
}
837858

838859
handler = next;

test/parallel/test-whatwg-events-add-event-listener-options-passive.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const common = require('../common');
3+
require('../common');
44

55
// Manually converted from https://github.com/web-platform-tests/wpt/blob/master/dom/events/AddEventListenerOptions-passive.html
66
// in order to define the `document` ourselves
@@ -58,7 +58,6 @@ const {
5858
testPassiveValue({}, true);
5959
testPassiveValue({ passive: false }, true);
6060

61-
common.skip('TODO: passive listeners is still broken');
6261
testPassiveValue({ passive: 1 }, false);
6362
testPassiveValue({ passive: true }, false);
6463
testPassiveValue({ passive: 0 }, true);

test/wpt/status/dom/events.json

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
11
{
2-
"AddEventListenerOptions-passive.any.js": {
3-
"fail": {
4-
"expected": [
5-
"preventDefault should be ignored if-and-only-if the passive option is true",
6-
"returnValue should be ignored if-and-only-if the passive option is true",
7-
"passive behavior of one listener should be unaffected by the presence of other listeners"
8-
]
9-
}
10-
},
112
"Event-dispatch-listener-order.window.js": {
123
"skip": "document is not defined"
134
},

0 commit comments

Comments
 (0)