Skip to content

Commit 8bf1f26

Browse files
authored
fix: [#1577] Event.target should be the target element after an event has been dispatched (#1676)
1 parent f79188a commit 8bf1f26

File tree

13 files changed

+43
-27
lines changed

13 files changed

+43
-27
lines changed

packages/happy-dom/src/PropertySymbol.ts

+1
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,4 @@ export const xmlProcessingInstruction = Symbol('xmlProcessingInstruction');
378378
export const root = Symbol('root');
379379
export const filterNode = Symbol('filterNode');
380380
export const customElementReactionStack = Symbol('customElementReactionStack');
381+
export const dispatching = Symbol('dispatching');

packages/happy-dom/src/event/Event.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default class Event {
2525
public [PropertySymbol.timeStamp] = performance.now();
2626
public [PropertySymbol.type]: string;
2727

28+
public [PropertySymbol.dispatching] = false;
2829
public [PropertySymbol.immediatePropagationStopped] = false;
2930
public [PropertySymbol.propagationStopped] = false;
3031
public [PropertySymbol.target]: EventTarget = null;

packages/happy-dom/src/event/EventTarget.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,18 @@ export default class EventTarget {
110110
* @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().
111111
*/
112112
public dispatchEvent(event: Event): boolean {
113-
if (!event[PropertySymbol.target]) {
113+
// The "load" event is a special case. It should not bubble up to the window from the document.
114+
if (
115+
!event[PropertySymbol.dispatching] &&
116+
(event[PropertySymbol.type] !== 'load' || !event[PropertySymbol.target])
117+
) {
118+
event[PropertySymbol.dispatching] = true;
114119
event[PropertySymbol.target] = this[PropertySymbol.proxy] || this;
115120

116121
this.#goThroughDispatchEventPhases(event);
117122

123+
event[PropertySymbol.dispatching] = false;
124+
118125
return !(event[PropertySymbol.cancelable] && event[PropertySymbol.defaultPrevented]);
119126
}
120127

@@ -172,7 +179,6 @@ export default class EventTarget {
172179
event[PropertySymbol.immediatePropagationStopped]
173180
) {
174181
event[PropertySymbol.eventPhase] = EventPhaseEnum.none;
175-
event[PropertySymbol.target] = null;
176182
event[PropertySymbol.currentTarget] = null;
177183
return;
178184
}
@@ -201,7 +207,6 @@ export default class EventTarget {
201207
event[PropertySymbol.immediatePropagationStopped]
202208
) {
203209
event[PropertySymbol.eventPhase] = EventPhaseEnum.none;
204-
event[PropertySymbol.target] = null;
205210
event[PropertySymbol.currentTarget] = null;
206211
return;
207212
}
@@ -210,7 +215,6 @@ export default class EventTarget {
210215

211216
// None phase (done)
212217
event[PropertySymbol.eventPhase] = EventPhaseEnum.none;
213-
event[PropertySymbol.target] = null;
214218
event[PropertySymbol.currentTarget] = null;
215219
}
216220

packages/happy-dom/src/window/BrowserWindow.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -845,15 +845,15 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
845845
// Not sure why target is set to document here, but this is how it works in the browser
846846
const loadEvent = new Event('load');
847847

848-
loadEvent[PropertySymbol.currentTarget] = this.document;
848+
loadEvent[PropertySymbol.currentTarget] = this;
849849
loadEvent[PropertySymbol.target] = this.document;
850850
loadEvent[PropertySymbol.eventPhase] = EventPhaseEnum.atTarget;
851851

852852
this.dispatchEvent(loadEvent);
853853

854-
loadEvent[PropertySymbol.target] = null;
855854
loadEvent[PropertySymbol.currentTarget] = null;
856855
loadEvent[PropertySymbol.eventPhase] = EventPhaseEnum.none;
856+
loadEvent[PropertySymbol.dispatching] = false;
857857
});
858858

859859
this[PropertySymbol.bindMethods]();

packages/happy-dom/test/event/Event.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('Event', () => {
3636
});
3737
span.dispatchEvent(event);
3838

39-
expect(event.target).toBe(null);
39+
expect(event.target).toBe(span);
4040
expect(target).toBe(span);
4141
});
4242
});

packages/happy-dom/test/event/EventTarget.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Window from '../../src/window/Window.js';
33
import EventTarget from '../../src/event/EventTarget.js';
44
import Event from '../../src/event/Event.js';
55
import CustomEvent from '../../src/event/events/CustomEvent.js';
6+
import * as PropertySymbol from '../../src/PropertySymbol.js';
67
import { beforeEach, describe, it, expect } from 'vitest';
78

89
const EVENT_TYPE = 'click';
@@ -160,6 +161,10 @@ describe('EventTarget', () => {
160161
expect(recievedEvent).toBe(dispatchedEvent);
161162
expect(recievedTarget).toBe(eventTarget);
162163
expect(recievedCurrentTarget).toBe(eventTarget);
164+
expect(dispatchedEvent.target).toBe(eventTarget);
165+
expect(dispatchedEvent.currentTarget).toBe(null);
166+
expect(dispatchedEvent.defaultPrevented).toBe(false);
167+
expect(dispatchedEvent[PropertySymbol.dispatching]).toBe(false);
163168
});
164169

165170
it('Triggers all listeners, even though listeners are removed while dispatching.', () => {
@@ -196,6 +201,11 @@ describe('EventTarget', () => {
196201
expect(recievedCurrentTarget1).toBe(eventTarget);
197202
expect(recievedTarget2).toBe(eventTarget);
198203
expect(recievedCurrentTarget2).toBe(eventTarget);
204+
205+
expect(dispatchedEvent.target).toBe(eventTarget);
206+
expect(dispatchedEvent.currentTarget).toBe(null);
207+
expect(dispatchedEvent.defaultPrevented).toBe(false);
208+
expect(dispatchedEvent[PropertySymbol.dispatching]).toBe(false);
199209
});
200210
});
201211

packages/happy-dom/test/nodes/document/Document.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,7 @@ describe('Document', () => {
13971397
expect(document.readyState).toBe(DocumentReadyStateEnum.interactive);
13981398

13991399
setTimeout(() => {
1400-
expect((<Event>event).target).toBe(null);
1400+
expect((<Event>event).target).toBe(document);
14011401
expect(target).toBe(document);
14021402
expect(currentTarget).toBe(document);
14031403
expect(document.readyState).toBe(DocumentReadyStateEnum.complete);
@@ -1456,7 +1456,7 @@ describe('Document', () => {
14561456
expect(resourceFetchCSSURL).toBe(cssURL);
14571457
expect(resourceFetchJSWindow).toBe(window);
14581458
expect(resourceFetchJSURL).toBe(jsURL);
1459-
expect((<Event>event).target).toBe(null);
1459+
expect((<Event>event).target).toBe(document);
14601460
expect(target).toBe(document);
14611461
expect(currentTarget).toBe(document);
14621462
expect(document.readyState).toBe(DocumentReadyStateEnum.complete);

packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ describe('HTMLElement', () => {
470470
expect((<PointerEvent>(<unknown>event)).composed).toBe(true);
471471
expect((<PointerEvent>(<unknown>event)).width).toBe(1);
472472
expect((<PointerEvent>(<unknown>event)).height).toBe(1);
473-
expect((<PointerEvent>(<unknown>event)).target).toBe(null);
473+
expect((<PointerEvent>(<unknown>event)).target).toBe(element);
474474
expect((<PointerEvent>(<unknown>event)).currentTarget).toBe(null);
475475
expect(target).toBe(element);
476476
expect(currentTarget).toBe(element);

packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ describe('HTMLElementUtility', () => {
4848
expect((<FocusEvent>(<unknown>blurEvent)).type).toBe('blur');
4949
expect((<FocusEvent>(<unknown>blurEvent)).bubbles).toBe(false);
5050
expect((<FocusEvent>(<unknown>blurEvent)).composed).toBe(true);
51-
expect((<FocusEvent>(<unknown>blurEvent)).target).toBe(null);
51+
expect((<FocusEvent>(<unknown>blurEvent)).target).toBe(element);
5252
expect(blurTarget).toBe(element);
5353
expect(blurCurrentTarget).toBe(element);
5454

5555
expect((<FocusEvent>(<unknown>focusOutEvent)).type).toBe('focusout');
5656
expect((<FocusEvent>(<unknown>focusOutEvent)).bubbles).toBe(true);
5757
expect((<FocusEvent>(<unknown>focusOutEvent)).composed).toBe(true);
58-
expect((<FocusEvent>(<unknown>focusOutEvent)).target).toBe(null);
58+
expect((<FocusEvent>(<unknown>focusOutEvent)).target).toBe(element);
5959
expect(focusOutTarget).toBe(element);
6060
expect(focusOutCurrentTarget).toBe(element);
6161

@@ -133,14 +133,14 @@ describe('HTMLElementUtility', () => {
133133
expect((<FocusEvent>(<unknown>focusEvent)).type).toBe('focus');
134134
expect((<FocusEvent>(<unknown>focusEvent)).bubbles).toBe(false);
135135
expect((<FocusEvent>(<unknown>focusEvent)).composed).toBe(true);
136-
expect((<FocusEvent>(<unknown>focusEvent)).target).toBe(null);
136+
expect((<FocusEvent>(<unknown>focusEvent)).target).toBe(element);
137137
expect(focusTarget).toBe(element);
138138
expect(focusCurrentTarget).toBe(element);
139139

140140
expect((<FocusEvent>(<unknown>focusInEvent)).type).toBe('focusin');
141141
expect((<FocusEvent>(<unknown>focusInEvent)).bubbles).toBe(true);
142142
expect((<FocusEvent>(<unknown>focusInEvent)).composed).toBe(true);
143-
expect((<FocusEvent>(<unknown>focusInEvent)).target).toBe(null);
143+
expect((<FocusEvent>(<unknown>focusInEvent)).target).toBe(element);
144144
expect(focusInTarget).toBe(element);
145145
expect(focusInCurrentTarget).toBe(element);
146146

@@ -212,7 +212,7 @@ describe('HTMLElementUtility', () => {
212212
expect((<FocusEvent>(<unknown>event)).type).toBe('blur');
213213
expect((<FocusEvent>(<unknown>event)).bubbles).toBe(false);
214214
expect((<FocusEvent>(<unknown>event)).composed).toBe(true);
215-
expect((<FocusEvent>(<unknown>event)).target).toBe(null);
215+
expect((<FocusEvent>(<unknown>event)).target).toBe(previousElement);
216216
expect(target).toBe(previousElement);
217217
expect(currentTarget).toBe(previousElement);
218218
}

packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ describe('HTMLLinkElement', () => {
117117
expect(loadedURL).toBe('https://localhost:8080/test/path/file.css');
118118
expect(element.sheet.cssRules.length).toBe(1);
119119
expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }');
120-
expect((<Event>(<unknown>loadEvent)).target).toBe(null);
120+
expect((<Event>(<unknown>loadEvent)).target).toBe(element);
121121
expect(loadEventTarget).toBe(element);
122122
expect(loadEventCurrentTarget).toBe(element);
123123
});
@@ -198,7 +198,7 @@ describe('HTMLLinkElement', () => {
198198
expect(loadedURL).toBe('https://localhost:8080/test/path/file.css');
199199
expect(element.sheet.cssRules.length).toBe(1);
200200
expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }');
201-
expect((<Event>(<unknown>loadEvent)).target).toBe(null);
201+
expect((<Event>(<unknown>loadEvent)).target).toBe(element);
202202
expect(loadEventTarget).toBe(element);
203203
expect(loadEventCurrentTarget).toBe(element);
204204
});

packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ describe('HTMLScriptElement', () => {
208208

209209
await window.happyDOM?.waitUntilComplete();
210210

211-
expect((<Event>(<unknown>loadEvent)).target).toBe(null);
211+
expect((<Event>(<unknown>loadEvent)).target).toBe(script);
212212
expect(loadEventTarget).toBe(script);
213213
expect(loadEventCurrentTarget).toBe(script);
214214
expect(fetchedURL).toBe('https://localhost:8080/path/to/script.js');
@@ -269,7 +269,7 @@ describe('HTMLScriptElement', () => {
269269

270270
window.document.body.appendChild(script);
271271

272-
expect((<Event>(<unknown>loadEvent)).target).toBe(null);
272+
expect((<Event>(<unknown>loadEvent)).target).toBe(script);
273273
expect(loadEventTarget).toBe(script);
274274
expect(loadEventCurrentTarget).toBe(script);
275275
expect(fetchedWindow).toBe(window);

packages/happy-dom/test/nodes/node/Node.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ describe('Node', () => {
887887
expect(child.dispatchEvent(event)).toBe(true);
888888

889889
expect(childEvent).toBe(event);
890-
expect((<Event>(<unknown>childEvent)).target).toBe(null);
890+
expect((<Event>(<unknown>childEvent)).target).toBe(child);
891891
expect((<Event>(<unknown>childEvent)).currentTarget).toBe(null);
892892
expect(childEventTarget).toBe(child);
893893
expect(childEventCurrentTarget).toBe(child);
@@ -922,7 +922,7 @@ describe('Node', () => {
922922

923923
expect(childEvent).toBe(event);
924924
expect(parentEvent).toBe(event);
925-
expect((<Event>(<unknown>parentEvent)).target).toBe(null);
925+
expect((<Event>(<unknown>parentEvent)).target).toBe(child);
926926
expect((<Event>(<unknown>parentEvent)).currentTarget).toBe(null);
927927
expect(childEventTarget).toBe(child);
928928
expect(childEventCurrentTarget).toBe(child);

packages/happy-dom/test/window/BrowserWindow.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1556,11 +1556,11 @@ describe('BrowserWindow', () => {
15561556
});
15571557

15581558
setTimeout(() => {
1559-
expect((<Event>event).target).toBe(null);
1559+
expect((<Event>event).target).toBe(document);
15601560
expect((<Event>event).currentTarget).toBe(null);
15611561
expect((<Event>event).eventPhase).toBe(EventPhaseEnum.none);
15621562
expect(target).toBe(document);
1563-
expect(currentTarget).toBe(document);
1563+
expect(currentTarget).toBe(window);
15641564
resolve(null);
15651565
}, 20);
15661566
});
@@ -1614,11 +1614,11 @@ describe('BrowserWindow', () => {
16141614
expect(resourceFetchCSSURL).toBe(cssURL);
16151615
expect(resourceFetchJSWindow === window).toBe(true);
16161616
expect(resourceFetchJSURL).toBe(jsURL);
1617-
expect((<Event>loadEvent).target).toBe(null);
1617+
expect((<Event>loadEvent).target).toBe(document);
16181618
expect((<Event>loadEvent).currentTarget).toBe(null);
16191619
expect((<Event>loadEvent).eventPhase).toBe(EventPhaseEnum.none);
16201620
expect(loadEventTarget).toBe(document);
1621-
expect(loadEventCurrentTarget).toBe(document);
1621+
expect(loadEventCurrentTarget).toBe(window);
16221622
expect(document.styleSheets.length).toBe(1);
16231623
expect(document.styleSheets[0].cssRules[0].cssText).toBe(cssResponse);
16241624

@@ -1648,9 +1648,9 @@ describe('BrowserWindow', () => {
16481648

16491649
setTimeout(() => {
16501650
expect(errorEvents.length).toBe(2);
1651-
expect(errorEvents[0].target).toBe(null);
1651+
expect(errorEvents[0].target).toBe(window);
16521652
expect((<Error>errorEvents[0].error).message).toBe('Script error');
1653-
expect(errorEvents[1].target).toBe(null);
1653+
expect(errorEvents[1].target).toBe(window);
16541654
expect((<Error>errorEvents[1].error).message).toBe('Timeout error');
16551655

16561656
resolve(null);

0 commit comments

Comments
 (0)