Skip to content

Commit aab20c3

Browse files
ikeyancapricorn86
andauthored
fix: [#1858] forEach should accept callback's this value and pass this to the third argument of callback (#1861)
* fix: [#1858] Fix this value of forEach * fix: [#1858] Adds tests * chore: [#1858] Improves performance and defaults to Window as this scope * chore: [#1858] Improves performance and defaults to Window as this scope * chore: [#1858] Improves performance and defaults to Window as this scope --------- Co-authored-by: David Ortner <[email protected]>
1 parent 0eb4e65 commit aab20c3

File tree

17 files changed

+159
-43
lines changed

17 files changed

+159
-43
lines changed

packages/happy-dom/src/dom/DOMTokenList.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default class DOMTokenList {
2020
attributeValue: ''
2121
};
2222
private [PropertySymbol.supports]: string[];
23+
private [PropertySymbol.proxy]?: this;
2324

2425
/**
2526
* Constructor.
@@ -45,7 +46,7 @@ export default class DOMTokenList {
4546

4647
const methodBinder = new ClassMethodBinder(this, [DOMTokenList]);
4748

48-
return new Proxy(this, {
49+
const proxy = new Proxy(this, {
4950
get: (target, property) => {
5051
if (property === 'length') {
5152
return target[PropertySymbol.getTokenList]().length;
@@ -125,6 +126,9 @@ export default class DOMTokenList {
125126
}
126127
}
127128
});
129+
this[PropertySymbol.proxy] = proxy;
130+
131+
return proxy;
128132
}
129133

130134
/**
@@ -224,10 +228,15 @@ export default class DOMTokenList {
224228
* @param thisArg
225229
*/
226230
public forEach(
227-
callback: (currentValue: string, currentIndex: number, listObj: string[]) => void,
228-
thisArg?: this
231+
callback: (currentValue: string, currentIndex: number, parent: this) => void,
232+
thisArg?: any
229233
): void {
230-
return this[PropertySymbol.getTokenList]().forEach(callback, thisArg);
234+
const thisArgValue = thisArg ?? this[PropertySymbol.ownerElement][PropertySymbol.window];
235+
const items = this[PropertySymbol.getTokenList]();
236+
const proxy = this[PropertySymbol.proxy] ?? this;
237+
for (let i = 0, max = items.length; i < max; i++) {
238+
callback.call(thisArgValue, items[i], i, proxy);
239+
}
231240
}
232241

233242
/**

packages/happy-dom/src/fetch/Fetch.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export default class Fetch {
273273
}
274274

275275
if (cachedResponse.state === CachedResponseStateEnum.stale) {
276-
const headers = new Headers(cachedResponse.request.headers);
276+
const headers = new this.#window.Headers(cachedResponse.request.headers);
277277

278278
if (cachedResponse.etag) {
279279
headers.set('If-None-Match', cachedResponse.etag);
@@ -475,7 +475,7 @@ export default class Fetch {
475475
requestHeaders.push(header.toLowerCase());
476476
}
477477

478-
const corsHeaders = new Headers({
478+
const corsHeaders = new this.#window.Headers({
479479
'Access-Control-Request-Method': this.request.method,
480480
Origin: this.#window.location.origin
481481
});
@@ -958,7 +958,7 @@ export default class Fetch {
958958
return true;
959959
}
960960

961-
const headers = new Headers(this.request.headers);
961+
const headers = new this.#window.Headers(this.request.headers);
962962
const requestInit: IRequestInit = {
963963
method: this.request.method,
964964
signal: this.request.signal,

packages/happy-dom/src/fetch/Headers.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import DOMException from '../exception/DOMException.js';
22
import * as PropertySymbol from '../PropertySymbol.js';
33
import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js';
44
import IHeadersInit from './types/IHeadersInit.js';
5+
import BrowserWindow from '../window/BrowserWindow.js';
56

67
/**
78
* Fetch headers.
89
*
910
* @see https://developer.mozilla.org/en-US/docs/Web/API/Headers
1011
*/
1112
export default class Headers {
13+
// Injected by WindowContextClassExtender
14+
protected declare [PropertySymbol.window]: BrowserWindow;
15+
1216
public [PropertySymbol.entries]: { [k: string]: { name: string; value: string[] } } = {};
1317

1418
/**
@@ -115,10 +119,15 @@ export default class Headers {
115119
* Executes a callback function once per each key/value pair in the Headers object.
116120
*
117121
* @param callback Callback.
122+
* @param thisArg thisArg.
118123
*/
119-
public forEach(callback: (name: string, value: string, thisArg: Headers) => void): void {
124+
public forEach(
125+
callback: (value: string, name: string, parent: this) => void,
126+
thisArg?: any
127+
): void {
128+
const thisArgValue = thisArg ?? this[PropertySymbol.window];
120129
for (const header of Object.values(this[PropertySymbol.entries])) {
121-
callback(header.value.join(', '), header.name, this);
130+
callback.call(thisArgValue, header.value.join(', '), header.name, this);
122131
}
123132
}
124133

packages/happy-dom/src/fetch/Request.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ export default class Request implements Request {
108108
this[PropertySymbol.body] = stream;
109109
this[PropertySymbol.credentials] =
110110
init?.credentials || (<Request>input).credentials || 'same-origin';
111-
this[PropertySymbol.headers] = new Headers(init?.headers || (<Request>input).headers || {});
111+
this[PropertySymbol.headers] = new this[PropertySymbol.window].Headers(
112+
init?.headers || (<Request>input).headers || {}
113+
);
112114

113115
FetchRequestHeaderUtility.removeForbiddenHeaders(this.headers);
114116

packages/happy-dom/src/fetch/Response.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default class Response implements Response {
6363
this.status = init?.status !== undefined ? init.status : 200;
6464
this.statusText = init?.statusText || '';
6565
this.ok = this.status >= 200 && this.status < 300;
66-
this.headers = new Headers(init?.headers);
66+
this.headers = new this[PropertySymbol.window].Headers(init?.headers);
6767

6868
// "Set-Cookie" and "Set-Cookie2" are not allowed in response headers according to spec.
6969
this.headers.delete('Set-Cookie');

packages/happy-dom/src/fetch/SyncFetch.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export default class SyncFetch {
149149
ok: true,
150150
url: this.request.url,
151151
redirected: false,
152-
headers: new Headers({ 'Content-Type': result.type }),
152+
headers: new this.#window.Headers({ 'Content-Type': result.type }),
153153
body: result.buffer,
154154
[PropertySymbol.virtualServerFile]: null
155155
};
@@ -220,7 +220,7 @@ export default class SyncFetch {
220220
}
221221

222222
if (cachedResponse.state === CachedResponseStateEnum.stale) {
223-
const headers = new Headers(cachedResponse.request.headers);
223+
const headers = new this.#window.Headers(cachedResponse.request.headers);
224224

225225
if (cachedResponse.etag) {
226226
headers.set('If-None-Match', cachedResponse.etag);
@@ -408,7 +408,7 @@ export default class SyncFetch {
408408
requestHeaders.push(header.toLowerCase());
409409
}
410410

411-
const corsHeaders = new Headers({
411+
const corsHeaders = new this.#window.Headers({
412412
'Access-Control-Request-Method': this.request.method,
413413
Origin: this.#window.location.origin
414414
});
@@ -665,7 +665,7 @@ export default class SyncFetch {
665665
);
666666
}
667667

668-
const headers = new Headers(this.request.headers);
668+
const headers = new this.#window.Headers(this.request.headers);
669669
const requestInit: IRequestInit = {
670670
method: this.request.method,
671671
signal: this.request.signal,

packages/happy-dom/src/fetch/utilities/FetchRequestHeaderUtility.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default class FetchRequestHeaderUtility {
7878
request: Request;
7979
baseHeaders?: Headers | null;
8080
}): { [key: string]: string } {
81-
const headers = new Headers(options.baseHeaders);
81+
const headers = new options.window.Headers(options.baseHeaders);
8282
options.request.headers.forEach((value, key) => {
8383
headers.set(key, value);
8484
});

packages/happy-dom/src/fetch/utilities/FetchResponseHeaderUtility.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default class FetchResponseHeaderUtility {
2121
requestURL: URL;
2222
rawHeaders: string[];
2323
}): Headers {
24-
const headers = new Headers();
24+
const headers = new options.browserFrame.window.Headers();
2525
let key = null;
2626

2727
for (const header of options.rawHeaders) {

packages/happy-dom/src/form-data/FormData.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,14 @@ export default class FormData implements Iterable<[string, string | File]> {
111111
* For each.
112112
*
113113
* @param callback Callback.
114+
* @param thisArg thisArg.
114115
*/
115-
public forEach(callback: (value: string | File, key: string, thisArg: FormData) => void): void {
116+
public forEach(
117+
callback: (value: string | File, key: string, parent: this) => void,
118+
thisArg?: any
119+
): void {
116120
for (const entry of this.#entries) {
117-
callback.call(this, entry.value, entry.name, this);
121+
callback.call(thisArg, entry.value, entry.name, this);
118122
}
119123
}
120124

packages/happy-dom/src/nodes/node/NodeList.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Node from './Node.js';
1010
class NodeList<T extends Node> {
1111
[index: number]: T;
1212
public [PropertySymbol.items]: T[];
13+
public [PropertySymbol.proxy]?: this;
1314

1415
/**
1516
* Constructor.
@@ -113,6 +114,7 @@ class NodeList<T extends Node> {
113114
}
114115
}
115116
});
117+
this[PropertySymbol.proxy] = proxy;
116118

117119
return proxy;
118120
}
@@ -198,10 +200,15 @@ class NodeList<T extends Node> {
198200
* @param thisArg thisArg.
199201
*/
200202
public forEach(
201-
callback: (currentValue: T, currentIndex: number, listObj: T[]) => void,
202-
thisArg?: this
203+
callback: (currentValue: T, currentIndex: number, parent: this) => void,
204+
thisArg?: any
203205
): void {
204-
return this[PropertySymbol.items].forEach(callback, thisArg);
206+
const items = this[PropertySymbol.items];
207+
const proxy = this[PropertySymbol.proxy] ?? this;
208+
for (let i = 0, max = items.length; i < max; i++) {
209+
const item = items[i];
210+
callback.call(thisArg ?? item[PropertySymbol.window], item, i, proxy);
211+
}
205212
}
206213

207214
/**

0 commit comments

Comments
 (0)