diff --git a/packages/happy-dom/src/PropertySymbol.ts b/packages/happy-dom/src/PropertySymbol.ts index 32fbe3e4a..be9bee7b5 100644 --- a/packages/happy-dom/src/PropertySymbol.ts +++ b/packages/happy-dom/src/PropertySymbol.ts @@ -378,3 +378,4 @@ export const xmlProcessingInstruction = Symbol('xmlProcessingInstruction'); export const root = Symbol('root'); export const filterNode = Symbol('filterNode'); export const customElementReactionStack = Symbol('customElementReactionStack'); +export const dispatching = Symbol('dispatching'); diff --git a/packages/happy-dom/src/event/Event.ts b/packages/happy-dom/src/event/Event.ts index ba18a159c..333cfabe4 100644 --- a/packages/happy-dom/src/event/Event.ts +++ b/packages/happy-dom/src/event/Event.ts @@ -25,6 +25,7 @@ export default class Event { public [PropertySymbol.timeStamp] = performance.now(); public [PropertySymbol.type]: string; + public [PropertySymbol.dispatching] = false; public [PropertySymbol.immediatePropagationStopped] = false; public [PropertySymbol.propagationStopped] = false; public [PropertySymbol.target]: EventTarget = null; diff --git a/packages/happy-dom/src/event/EventTarget.ts b/packages/happy-dom/src/event/EventTarget.ts index 769318036..f93341c20 100644 --- a/packages/happy-dom/src/event/EventTarget.ts +++ b/packages/happy-dom/src/event/EventTarget.ts @@ -110,11 +110,18 @@ export default class EventTarget { * @returns The return value is false if event is cancelable and at least one of the event handlers which handled this event called Event.preventDefault(). */ public dispatchEvent(event: Event): boolean { - if (!event[PropertySymbol.target]) { + // The "load" event is a special case. It should not bubble up to the window from the document. + if ( + !event[PropertySymbol.dispatching] && + (event[PropertySymbol.type] !== 'load' || !event[PropertySymbol.target]) + ) { + event[PropertySymbol.dispatching] = true; event[PropertySymbol.target] = this[PropertySymbol.proxy] || this; this.#goThroughDispatchEventPhases(event); + event[PropertySymbol.dispatching] = false; + return !(event[PropertySymbol.cancelable] && event[PropertySymbol.defaultPrevented]); } @@ -172,7 +179,6 @@ export default class EventTarget { event[PropertySymbol.immediatePropagationStopped] ) { event[PropertySymbol.eventPhase] = EventPhaseEnum.none; - event[PropertySymbol.target] = null; event[PropertySymbol.currentTarget] = null; return; } @@ -201,7 +207,6 @@ export default class EventTarget { event[PropertySymbol.immediatePropagationStopped] ) { event[PropertySymbol.eventPhase] = EventPhaseEnum.none; - event[PropertySymbol.target] = null; event[PropertySymbol.currentTarget] = null; return; } @@ -210,7 +215,6 @@ export default class EventTarget { // None phase (done) event[PropertySymbol.eventPhase] = EventPhaseEnum.none; - event[PropertySymbol.target] = null; event[PropertySymbol.currentTarget] = null; } diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index a345c7761..e0722fd22 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -845,15 +845,15 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal // Not sure why target is set to document here, but this is how it works in the browser const loadEvent = new Event('load'); - loadEvent[PropertySymbol.currentTarget] = this.document; + loadEvent[PropertySymbol.currentTarget] = this; loadEvent[PropertySymbol.target] = this.document; loadEvent[PropertySymbol.eventPhase] = EventPhaseEnum.atTarget; this.dispatchEvent(loadEvent); - loadEvent[PropertySymbol.target] = null; loadEvent[PropertySymbol.currentTarget] = null; loadEvent[PropertySymbol.eventPhase] = EventPhaseEnum.none; + loadEvent[PropertySymbol.dispatching] = false; }); this[PropertySymbol.bindMethods](); diff --git a/packages/happy-dom/test/event/Event.test.ts b/packages/happy-dom/test/event/Event.test.ts index 00d17a69d..db78d23b6 100644 --- a/packages/happy-dom/test/event/Event.test.ts +++ b/packages/happy-dom/test/event/Event.test.ts @@ -36,7 +36,7 @@ describe('Event', () => { }); span.dispatchEvent(event); - expect(event.target).toBe(null); + expect(event.target).toBe(span); expect(target).toBe(span); }); }); diff --git a/packages/happy-dom/test/event/EventTarget.test.ts b/packages/happy-dom/test/event/EventTarget.test.ts index 4bceb98ed..a00e93e97 100644 --- a/packages/happy-dom/test/event/EventTarget.test.ts +++ b/packages/happy-dom/test/event/EventTarget.test.ts @@ -3,6 +3,7 @@ import Window from '../../src/window/Window.js'; import EventTarget from '../../src/event/EventTarget.js'; import Event from '../../src/event/Event.js'; import CustomEvent from '../../src/event/events/CustomEvent.js'; +import * as PropertySymbol from '../../src/PropertySymbol.js'; import { beforeEach, describe, it, expect } from 'vitest'; const EVENT_TYPE = 'click'; @@ -160,6 +161,10 @@ describe('EventTarget', () => { expect(recievedEvent).toBe(dispatchedEvent); expect(recievedTarget).toBe(eventTarget); expect(recievedCurrentTarget).toBe(eventTarget); + expect(dispatchedEvent.target).toBe(eventTarget); + expect(dispatchedEvent.currentTarget).toBe(null); + expect(dispatchedEvent.defaultPrevented).toBe(false); + expect(dispatchedEvent[PropertySymbol.dispatching]).toBe(false); }); it('Triggers all listeners, even though listeners are removed while dispatching.', () => { @@ -196,6 +201,11 @@ describe('EventTarget', () => { expect(recievedCurrentTarget1).toBe(eventTarget); expect(recievedTarget2).toBe(eventTarget); expect(recievedCurrentTarget2).toBe(eventTarget); + + expect(dispatchedEvent.target).toBe(eventTarget); + expect(dispatchedEvent.currentTarget).toBe(null); + expect(dispatchedEvent.defaultPrevented).toBe(false); + expect(dispatchedEvent[PropertySymbol.dispatching]).toBe(false); }); }); diff --git a/packages/happy-dom/test/nodes/document/Document.test.ts b/packages/happy-dom/test/nodes/document/Document.test.ts index a5a5c3305..b59c27e35 100644 --- a/packages/happy-dom/test/nodes/document/Document.test.ts +++ b/packages/happy-dom/test/nodes/document/Document.test.ts @@ -1397,7 +1397,7 @@ describe('Document', () => { expect(document.readyState).toBe(DocumentReadyStateEnum.interactive); setTimeout(() => { - expect((event).target).toBe(null); + expect((event).target).toBe(document); expect(target).toBe(document); expect(currentTarget).toBe(document); expect(document.readyState).toBe(DocumentReadyStateEnum.complete); @@ -1456,7 +1456,7 @@ describe('Document', () => { expect(resourceFetchCSSURL).toBe(cssURL); expect(resourceFetchJSWindow).toBe(window); expect(resourceFetchJSURL).toBe(jsURL); - expect((event).target).toBe(null); + expect((event).target).toBe(document); expect(target).toBe(document); expect(currentTarget).toBe(document); expect(document.readyState).toBe(DocumentReadyStateEnum.complete); diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts index dccc01064..9c05718ef 100644 --- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts +++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts @@ -470,7 +470,7 @@ describe('HTMLElement', () => { expect(((event)).composed).toBe(true); expect(((event)).width).toBe(1); expect(((event)).height).toBe(1); - expect(((event)).target).toBe(null); + expect(((event)).target).toBe(element); expect(((event)).currentTarget).toBe(null); expect(target).toBe(element); expect(currentTarget).toBe(element); diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts index 7a6849837..99e197407 100644 --- a/packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts +++ b/packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts @@ -48,14 +48,14 @@ describe('HTMLElementUtility', () => { expect(((blurEvent)).type).toBe('blur'); expect(((blurEvent)).bubbles).toBe(false); expect(((blurEvent)).composed).toBe(true); - expect(((blurEvent)).target).toBe(null); + expect(((blurEvent)).target).toBe(element); expect(blurTarget).toBe(element); expect(blurCurrentTarget).toBe(element); expect(((focusOutEvent)).type).toBe('focusout'); expect(((focusOutEvent)).bubbles).toBe(true); expect(((focusOutEvent)).composed).toBe(true); - expect(((focusOutEvent)).target).toBe(null); + expect(((focusOutEvent)).target).toBe(element); expect(focusOutTarget).toBe(element); expect(focusOutCurrentTarget).toBe(element); @@ -133,14 +133,14 @@ describe('HTMLElementUtility', () => { expect(((focusEvent)).type).toBe('focus'); expect(((focusEvent)).bubbles).toBe(false); expect(((focusEvent)).composed).toBe(true); - expect(((focusEvent)).target).toBe(null); + expect(((focusEvent)).target).toBe(element); expect(focusTarget).toBe(element); expect(focusCurrentTarget).toBe(element); expect(((focusInEvent)).type).toBe('focusin'); expect(((focusInEvent)).bubbles).toBe(true); expect(((focusInEvent)).composed).toBe(true); - expect(((focusInEvent)).target).toBe(null); + expect(((focusInEvent)).target).toBe(element); expect(focusInTarget).toBe(element); expect(focusInCurrentTarget).toBe(element); @@ -212,7 +212,7 @@ describe('HTMLElementUtility', () => { expect(((event)).type).toBe('blur'); expect(((event)).bubbles).toBe(false); expect(((event)).composed).toBe(true); - expect(((event)).target).toBe(null); + expect(((event)).target).toBe(previousElement); expect(target).toBe(previousElement); expect(currentTarget).toBe(previousElement); } diff --git a/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts b/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts index b715dd7fe..95477be16 100644 --- a/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts +++ b/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts @@ -117,7 +117,7 @@ describe('HTMLLinkElement', () => { expect(loadedURL).toBe('https://localhost:8080/test/path/file.css'); expect(element.sheet.cssRules.length).toBe(1); expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }'); - expect(((loadEvent)).target).toBe(null); + expect(((loadEvent)).target).toBe(element); expect(loadEventTarget).toBe(element); expect(loadEventCurrentTarget).toBe(element); }); @@ -198,7 +198,7 @@ describe('HTMLLinkElement', () => { expect(loadedURL).toBe('https://localhost:8080/test/path/file.css'); expect(element.sheet.cssRules.length).toBe(1); expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }'); - expect(((loadEvent)).target).toBe(null); + expect(((loadEvent)).target).toBe(element); expect(loadEventTarget).toBe(element); expect(loadEventCurrentTarget).toBe(element); }); diff --git a/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts b/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts index 85c9c957f..08ae8b252 100644 --- a/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts +++ b/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts @@ -208,7 +208,7 @@ describe('HTMLScriptElement', () => { await window.happyDOM?.waitUntilComplete(); - expect(((loadEvent)).target).toBe(null); + expect(((loadEvent)).target).toBe(script); expect(loadEventTarget).toBe(script); expect(loadEventCurrentTarget).toBe(script); expect(fetchedURL).toBe('https://localhost:8080/path/to/script.js'); @@ -269,7 +269,7 @@ describe('HTMLScriptElement', () => { window.document.body.appendChild(script); - expect(((loadEvent)).target).toBe(null); + expect(((loadEvent)).target).toBe(script); expect(loadEventTarget).toBe(script); expect(loadEventCurrentTarget).toBe(script); expect(fetchedWindow).toBe(window); diff --git a/packages/happy-dom/test/nodes/node/Node.test.ts b/packages/happy-dom/test/nodes/node/Node.test.ts index 5a5e2b0a7..faf0fc519 100644 --- a/packages/happy-dom/test/nodes/node/Node.test.ts +++ b/packages/happy-dom/test/nodes/node/Node.test.ts @@ -887,7 +887,7 @@ describe('Node', () => { expect(child.dispatchEvent(event)).toBe(true); expect(childEvent).toBe(event); - expect(((childEvent)).target).toBe(null); + expect(((childEvent)).target).toBe(child); expect(((childEvent)).currentTarget).toBe(null); expect(childEventTarget).toBe(child); expect(childEventCurrentTarget).toBe(child); @@ -922,7 +922,7 @@ describe('Node', () => { expect(childEvent).toBe(event); expect(parentEvent).toBe(event); - expect(((parentEvent)).target).toBe(null); + expect(((parentEvent)).target).toBe(child); expect(((parentEvent)).currentTarget).toBe(null); expect(childEventTarget).toBe(child); expect(childEventCurrentTarget).toBe(child); diff --git a/packages/happy-dom/test/window/BrowserWindow.test.ts b/packages/happy-dom/test/window/BrowserWindow.test.ts index 1377d7fe5..a0cb3c94c 100644 --- a/packages/happy-dom/test/window/BrowserWindow.test.ts +++ b/packages/happy-dom/test/window/BrowserWindow.test.ts @@ -1556,11 +1556,11 @@ describe('BrowserWindow', () => { }); setTimeout(() => { - expect((event).target).toBe(null); + expect((event).target).toBe(document); expect((event).currentTarget).toBe(null); expect((event).eventPhase).toBe(EventPhaseEnum.none); expect(target).toBe(document); - expect(currentTarget).toBe(document); + expect(currentTarget).toBe(window); resolve(null); }, 20); }); @@ -1614,11 +1614,11 @@ describe('BrowserWindow', () => { expect(resourceFetchCSSURL).toBe(cssURL); expect(resourceFetchJSWindow === window).toBe(true); expect(resourceFetchJSURL).toBe(jsURL); - expect((loadEvent).target).toBe(null); + expect((loadEvent).target).toBe(document); expect((loadEvent).currentTarget).toBe(null); expect((loadEvent).eventPhase).toBe(EventPhaseEnum.none); expect(loadEventTarget).toBe(document); - expect(loadEventCurrentTarget).toBe(document); + expect(loadEventCurrentTarget).toBe(window); expect(document.styleSheets.length).toBe(1); expect(document.styleSheets[0].cssRules[0].cssText).toBe(cssResponse); @@ -1648,9 +1648,9 @@ describe('BrowserWindow', () => { setTimeout(() => { expect(errorEvents.length).toBe(2); - expect(errorEvents[0].target).toBe(null); + expect(errorEvents[0].target).toBe(window); expect((errorEvents[0].error).message).toBe('Script error'); - expect(errorEvents[1].target).toBe(null); + expect(errorEvents[1].target).toBe(window); expect((errorEvents[1].error).message).toBe('Timeout error'); resolve(null);