From 0e40f6a29f1f7d1554feccc1255984d38b406234 Mon Sep 17 00:00:00 2001 From: gh-fixes Date: Fri, 30 Oct 2026 11:40:33 +0000 Subject: [PATCH] fix: report defaultView as event target for window-dispatched events --- cjs/interface/document.js | 3 ++- cjs/interface/event-target.js | 5 ++++- cjs/shared/symbols.js | 4 ++++ esm/interface/document.js | 5 +++-- esm/interface/event-target.js | 5 ++++- esm/shared/symbols.js | 3 +++ test/interface/event-target.js | 14 +++++++++++++- 7 files changed, 33 insertions(+), 6 deletions(-) diff --git a/cjs/interface/document.js b/cjs/interface/document.js index 1a92fb28..d990bbc7 100644 --- a/cjs/interface/document.js +++ b/cjs/interface/document.js @@ -2,7 +2,7 @@ const {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, DOCUMENT_TYPE_NODE, ELEMENT_NODE, SVG_NAMESPACE} = require('../shared/constants.js'); const { - CUSTOM_ELEMENTS, DOM_PARSER, GLOBALS, IMAGE, MUTATION_OBSERVER, DOCTYPE, END, NEXT, MIME, EVENT_TARGET, UPGRADE + CUSTOM_ELEMENTS, DISPATCH_TARGET, DOM_PARSER, GLOBALS, IMAGE, MUTATION_OBSERVER, DOCTYPE, END, NEXT, MIME, EVENT_TARGET, UPGRADE } = require('../shared/symbols.js'); const {Facades, illegalConstructor} = require('../shared/facades.js'); @@ -98,6 +98,7 @@ class Document extends NonElementParentNode { case 'dispatchEvent': if (!this[EVENT_TARGET]) { const et = this[EVENT_TARGET] = new EventTarget; + et[DISPATCH_TARGET] = window.get(this); et.dispatchEvent = et.dispatchEvent.bind(et); et.addEventListener = et.addEventListener.bind(et); et.removeEventListener = et.removeEventListener.bind(et); diff --git a/cjs/interface/event-target.js b/cjs/interface/event-target.js index 450b3d63..88a9edff 100644 --- a/cjs/interface/event-target.js +++ b/cjs/interface/event-target.js @@ -1,6 +1,8 @@ 'use strict'; // https://dom.spec.whatwg.org/#interface-eventtarget +const {DISPATCH_TARGET} = require('../shared/symbols.js'); + const wm = new WeakMap(); function dispatch(event, listener) { @@ -70,12 +72,13 @@ class DOMEventTarget { dispatchEvent(event) { let node = this; + const target = this[DISPATCH_TARGET] || this; event.eventPhase = event.CAPTURING_PHASE; // intentionally simplified, specs imply way more code: https://dom.spec.whatwg.org/#event-path while (node) { if (node.dispatchEvent) - event._path.push({currentTarget: node, target: this}); + event._path.push({currentTarget: node, target}); node = event.bubbles && node._getParent && node._getParent(); } event._path.some(invokeListeners, event); diff --git a/cjs/shared/symbols.js b/cjs/shared/symbols.js index a63cca56..118f48cf 100644 --- a/cjs/shared/symbols.js +++ b/cjs/shared/symbols.js @@ -35,6 +35,10 @@ exports.END = END; const EVENT_TARGET = Symbol('EventTarget'); exports.EVENT_TARGET = EVENT_TARGET; +// used in EventTarget to report a different target (e.g. defaultView) +const DISPATCH_TARGET = Symbol('dispatchTarget'); +exports.DISPATCH_TARGET = DISPATCH_TARGET; + // used to augment a created document defaultView const GLOBALS = Symbol('globals'); exports.GLOBALS = GLOBALS; diff --git a/esm/interface/document.js b/esm/interface/document.js index d68c9eb5..7c942256 100644 --- a/esm/interface/document.js +++ b/esm/interface/document.js @@ -1,8 +1,8 @@ import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, DOCUMENT_TYPE_NODE, ELEMENT_NODE, SVG_NAMESPACE} from '../shared/constants.js'; import { - CUSTOM_ELEMENTS, DOM_PARSER, GLOBALS, IMAGE, MUTATION_OBSERVER, - DOCTYPE, END, NEXT, MIME, EVENT_TARGET, UPGRADE + CUSTOM_ELEMENTS, DISPATCH_TARGET, DOM_PARSER, GLOBALS, IMAGE, + MUTATION_OBSERVER, DOCTYPE, END, NEXT, MIME, EVENT_TARGET, UPGRADE } from '../shared/symbols.js'; import {Facades, illegalConstructor} from '../shared/facades.js'; @@ -98,6 +98,7 @@ export class Document extends NonElementParentNode { case 'dispatchEvent': if (!this[EVENT_TARGET]) { const et = this[EVENT_TARGET] = new EventTarget; + et[DISPATCH_TARGET] = window.get(this); et.dispatchEvent = et.dispatchEvent.bind(et); et.addEventListener = et.addEventListener.bind(et); et.removeEventListener = et.removeEventListener.bind(et); diff --git a/esm/interface/event-target.js b/esm/interface/event-target.js index edffded1..73fa447a 100644 --- a/esm/interface/event-target.js +++ b/esm/interface/event-target.js @@ -1,5 +1,7 @@ // https://dom.spec.whatwg.org/#interface-eventtarget +import {DISPATCH_TARGET} from '../shared/symbols.js'; + const wm = new WeakMap(); function dispatch(event, listener) { @@ -69,12 +71,13 @@ class DOMEventTarget { dispatchEvent(event) { let node = this; + const target = this[DISPATCH_TARGET] || this; event.eventPhase = event.CAPTURING_PHASE; // intentionally simplified, specs imply way more code: https://dom.spec.whatwg.org/#event-path while (node) { if (node.dispatchEvent) - event._path.push({currentTarget: node, target: this}); + event._path.push({currentTarget: node, target}); node = event.bubbles && node._getParent && node._getParent(); } event._path.some(invokeListeners, event); diff --git a/esm/shared/symbols.js b/esm/shared/symbols.js index e60c4533..4e67767d 100644 --- a/esm/shared/symbols.js +++ b/esm/shared/symbols.js @@ -25,6 +25,9 @@ export const END = Symbol('end'); // used in Document to make the globalThis an event target export const EVENT_TARGET = Symbol('EventTarget'); +// used in EventTarget to report a different target (e.g. defaultView) +export const DISPATCH_TARGET = Symbol('dispatchTarget'); + // used to augment a created document defaultView export const GLOBALS = Symbol('globals'); diff --git a/test/interface/event-target.js b/test/interface/event-target.js index f5f02bb2..698fd8f3 100644 --- a/test/interface/event-target.js +++ b/test/interface/event-target.js @@ -209,4 +209,16 @@ containerTarget.addEventListener('click', basicHandler, { once: true }); callCount = 0; buttonTarget.click(); assert(callCount, 4, '4 (2 are set before previous tests) and 2 here listeners should be called'); -assert(evBtn, 0, 'default click event button should be 0 (left click)') \ No newline at end of file +assert(evBtn, 0, 'default click event button should be 0 (left click)') + +// defaultView events must report the actual view as their target (#298) +let viewCalls = 0; +for (const name of ['document', 'window']) { + const view = window[name]; + view.addEventListener('direct', event => { + assert(event.target === view, true, `${name} is the event target`); + viewCalls++; + }, { once: true }); + view.dispatchEvent(new Event('direct')); +} +assert(viewCalls, 2, 'defaultView direct dispatch should invoke its listeners') \ No newline at end of file