From 534683640d13aa5c7add05cadcaccdca34c7a3ff Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:33:04 +0900 Subject: [PATCH 01/13] perf: always use the same prototype Iterator --- lib/fetch/formdata.js | 27 +++++++------------------ lib/fetch/headers.js | 28 ++++++++------------------ lib/fetch/util.js | 46 ++++++++++++++++++++++++++++--------------- 3 files changed, 45 insertions(+), 56 deletions(-) diff --git a/lib/fetch/formdata.js b/lib/fetch/formdata.js index add64aa9226..5eb7c17c93a 100644 --- a/lib/fetch/formdata.js +++ b/lib/fetch/formdata.js @@ -1,6 +1,6 @@ 'use strict' -const { isBlobLike, makeIterator } = require('./util') +const { isBlobLike, createIterator } = require('./util') const { kState } = require('./symbols') const { kEnumerableProperty } = require('../core/util') const { File: UndiciFile, FileLike, isFileLike } = require('./file') @@ -10,6 +10,8 @@ const { File: NativeFile } = require('node:buffer') /** @type {globalThis['File']} */ const File = NativeFile ?? UndiciFile +const makeIterator = createIterator('FormData', kState, 'name', 'value') + // https://xhr.spec.whatwg.org/#formdata class FormData { constructor (form) { @@ -158,34 +160,19 @@ class FormData { entries () { webidl.brandCheck(this, FormData) - return makeIterator( - () => this[kState], - 'FormData', - 'key+value', - 'name', 'value' - ) + return makeIterator(this, 'key+value') } keys () { webidl.brandCheck(this, FormData) - return makeIterator( - () => this[kState], - 'FormData', - 'key', - 'name', 'value' - ) + return makeIterator(this, 'key') } values () { webidl.brandCheck(this, FormData) - return makeIterator( - () => this[kState], - 'FormData', - 'value', - 'name', 'value' - ) + return makeIterator(this, 'value') } /** @@ -203,7 +190,7 @@ class FormData { ) } - for (const [key, value] of this) { + for (const [key, value] of makeIterator(this, 'key+value')) { callbackFn.call(thisArg, value, key, this) } } diff --git a/lib/fetch/headers.js b/lib/fetch/headers.js index 504942edc6e..9a0a1e07ac8 100644 --- a/lib/fetch/headers.js +++ b/lib/fetch/headers.js @@ -1,4 +1,5 @@ // https://github.com/Ethan-Arrowood/undici-fetch +// https://github.com/Ethan-Arrowood/undici-fetch 'use strict' @@ -6,7 +7,7 @@ const { kHeadersList, kConstruct } = require('../core/symbols') const { kGuard } = require('./symbols') const { kEnumerableProperty } = require('../core/util') const { - makeIterator, + createIterator, isValidHeaderName, isValidHeaderValue } = require('./util') @@ -16,6 +17,8 @@ const assert = require('node:assert') const kHeadersMap = Symbol('headers map') const kHeadersSortedMap = Symbol('headers map sorted') +const makeIterator = createIterator('Headers', kHeadersSortedMap, 0, 1) + /** * @param {number} code */ @@ -507,34 +510,19 @@ class Headers { keys () { webidl.brandCheck(this, Headers) - return makeIterator( - () => this[kHeadersSortedMap], - 'Headers', - 'key', - 0, 1 - ) + return makeIterator(this, 'key') } values () { webidl.brandCheck(this, Headers) - return makeIterator( - () => this[kHeadersSortedMap], - 'Headers', - 'value', - 0, 1 - ) + return makeIterator(this, 'value') } entries () { webidl.brandCheck(this, Headers) - return makeIterator( - () => this[kHeadersSortedMap], - 'Headers', - 'key+value', - 0, 1 - ) + return makeIterator(this, 'key+value') } /** @@ -552,7 +540,7 @@ class Headers { ) } - for (const [key, value] of this) { + for (const [key, value] of makeIterator(this, 'key+value')) { callbackFn.call(thisArg, value, key, this) } } diff --git a/lib/fetch/util.js b/lib/fetch/util.js index c5a6b46b170..a3c98a3b809 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -739,18 +739,14 @@ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbo /** * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object - * @param {() => unknown} iterator * @param {string} name name of the instance - * @param {'key'|'value'|'key+value'} kind + * @param {symbol} kInternalIterator * @param {string | number} [keyIndex] * @param {string | number} [valueIndex] */ -function makeIterator (iterator, name, kind, keyIndex = 0, valueIndex = 1) { - const object = { - index: 0, - kind, - target: iterator - } +function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) { + const kInternalObject = Symbol('internal Object') + // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. const iteratorObject = Object.create(esIteratorPrototype) @@ -767,17 +763,18 @@ function makeIterator (iterator, name, kind, keyIndex = 0, valueIndex = 1) { // 5. If object is not a default iterator object for interface, // then throw a TypeError. - if (Object.getPrototypeOf(this) !== iteratorObject) { + if (typeof this !== 'object' || this === null || !(kInternalObject in this)) { throw new TypeError( `'next' called on an object that does not implement interface ${name} Iterator.` ) } + const state = this[kInternalObject] // 6. Let index be object’s index. // 7. Let kind be object’s kind. // 8. Let values be object’s target's value pairs to iterate over. - const { index, kind, target } = object - const values = target() + const { index, kind, target } = state + const values = target[kInternalIterator] // 9. Let len be the length of values. const len = values.length @@ -790,7 +787,7 @@ function makeIterator (iterator, name, kind, keyIndex = 0, valueIndex = 1) { // 11. Let pair be the entry in values at index index. const { [keyIndex]: key, [valueIndex]: value } = values[index] // 12. Set object’s index to index + 1. - object.index = index + 1 + state.index = index + 1 // 13. Return the iterator result for pair and kind. // https://webidl.spec.whatwg.org/#iterator-result // 1. Let result be a value determined by the value of kind: @@ -844,9 +841,26 @@ function makeIterator (iterator, name, kind, keyIndex = 0, valueIndex = 1) { configurable: true }) - // esIteratorPrototype needs to be the prototype of iteratorObject - // which is the prototype of an empty object. Yes, it's confusing. - return Object.create(iteratorObject) + /** + * @param {unknown} target + * @param {'key'|'value'|'key+value'} kind + */ + return function (target, kind) { + // esIteratorPrototype needs to be the prototype of iteratorObject + // which is the prototype of an empty object. Yes, it's confusing. + const iterator = Object.create(iteratorObject) + Object.defineProperty(iterator, kInternalObject, { + value: { + target, + kind, + index: 0 + }, + writable: false, + enumerable: false, + configurable: true + }) + return iterator + } } /** @@ -1365,7 +1379,7 @@ module.exports = { sameOrigin, normalizeMethod, serializeJavascriptValueToJSONString, - makeIterator, + createIterator, isValidHeaderName, isValidHeaderValue, isErrorLike, From 8a2300a1eba67fccda73e5e79c972d7f4943bab2 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:24:27 +0900 Subject: [PATCH 02/13] add iteratorMixin --- lib/fetch/formdata.js | 52 ++++-------------------------- lib/fetch/headers.js | 52 ++++-------------------------- lib/fetch/util.js | 74 +++++++++++++++++++++++++++++++++++++++++-- lib/fetch/webidl.js | 2 +- 4 files changed, 85 insertions(+), 95 deletions(-) diff --git a/lib/fetch/formdata.js b/lib/fetch/formdata.js index 5eb7c17c93a..e0a5cf6c199 100644 --- a/lib/fetch/formdata.js +++ b/lib/fetch/formdata.js @@ -1,6 +1,6 @@ 'use strict' -const { isBlobLike, createIterator } = require('./util') +const { isBlobLike, iteratorMixin } = require('./util') const { kState } = require('./symbols') const { kEnumerableProperty } = require('../core/util') const { File: UndiciFile, FileLike, isFileLike } = require('./file') @@ -10,8 +10,6 @@ const { File: NativeFile } = require('node:buffer') /** @type {globalThis['File']} */ const File = NativeFile ?? UndiciFile -const makeIterator = createIterator('FormData', kState, 'name', 'value') - // https://xhr.spec.whatwg.org/#formdata class FormData { constructor (form) { @@ -156,47 +154,9 @@ class FormData { this[kState].push(entry) } } - - entries () { - webidl.brandCheck(this, FormData) - - return makeIterator(this, 'key+value') - } - - keys () { - webidl.brandCheck(this, FormData) - - return makeIterator(this, 'key') - } - - values () { - webidl.brandCheck(this, FormData) - - return makeIterator(this, 'value') - } - - /** - * @param {(value: string, key: string, self: FormData) => void} callbackFn - * @param {unknown} thisArg - */ - forEach (callbackFn, thisArg = globalThis) { - webidl.brandCheck(this, FormData) - - webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.forEach' }) - - if (typeof callbackFn !== 'function') { - throw new TypeError( - "Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'." - ) - } - - for (const [key, value] of makeIterator(this, 'key+value')) { - callbackFn.call(thisArg, value, key, this) - } - } } -FormData.prototype[Symbol.iterator] = FormData.prototype.entries +iteratorMixin('FormData', FormData, kState, 'name', 'value') Object.defineProperties(FormData.prototype, { append: kEnumerableProperty, @@ -205,11 +165,11 @@ Object.defineProperties(FormData.prototype, { getAll: kEnumerableProperty, has: kEnumerableProperty, set: kEnumerableProperty, - entries: kEnumerableProperty, - keys: kEnumerableProperty, - values: kEnumerableProperty, + // entries: kEnumerableProperty, + // keys: kEnumerableProperty, + // values: kEnumerableProperty, forEach: kEnumerableProperty, - [Symbol.iterator]: { enumerable: false }, + // [Symbol.iterator]: { enumerable: false }, [Symbol.toStringTag]: { value: 'FormData', configurable: true diff --git a/lib/fetch/headers.js b/lib/fetch/headers.js index 9a0a1e07ac8..00a14269f05 100644 --- a/lib/fetch/headers.js +++ b/lib/fetch/headers.js @@ -7,7 +7,7 @@ const { kHeadersList, kConstruct } = require('../core/symbols') const { kGuard } = require('./symbols') const { kEnumerableProperty } = require('../core/util') const { - createIterator, + iteratorMixin, isValidHeaderName, isValidHeaderValue } = require('./util') @@ -17,8 +17,6 @@ const assert = require('node:assert') const kHeadersMap = Symbol('headers map') const kHeadersSortedMap = Symbol('headers map sorted') -const makeIterator = createIterator('Headers', kHeadersSortedMap, 0, 1) - /** * @param {number} code */ @@ -507,44 +505,6 @@ class Headers { return headers } - keys () { - webidl.brandCheck(this, Headers) - - return makeIterator(this, 'key') - } - - values () { - webidl.brandCheck(this, Headers) - - return makeIterator(this, 'value') - } - - entries () { - webidl.brandCheck(this, Headers) - - return makeIterator(this, 'key+value') - } - - /** - * @param {(value: string, key: string, self: Headers) => void} callbackFn - * @param {unknown} thisArg - */ - forEach (callbackFn, thisArg = globalThis) { - webidl.brandCheck(this, Headers) - - webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.forEach' }) - - if (typeof callbackFn !== 'function') { - throw new TypeError( - "Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'." - ) - } - - for (const [key, value] of makeIterator(this, 'key+value')) { - callbackFn.call(thisArg, value, key, this) - } - } - [Symbol.for('nodejs.util.inspect.custom')] () { webidl.brandCheck(this, Headers) @@ -552,7 +512,7 @@ class Headers { } } -Headers.prototype[Symbol.iterator] = Headers.prototype.entries +iteratorMixin('Headers', Headers, kHeadersSortedMap, 0, 1) Object.defineProperties(Headers.prototype, { append: kEnumerableProperty, @@ -561,11 +521,11 @@ Object.defineProperties(Headers.prototype, { has: kEnumerableProperty, set: kEnumerableProperty, getSetCookie: kEnumerableProperty, - keys: kEnumerableProperty, - values: kEnumerableProperty, - entries: kEnumerableProperty, + // keys: kEnumerableProperty, + // values: kEnumerableProperty, + // entries: kEnumerableProperty, forEach: kEnumerableProperty, - [Symbol.iterator]: { enumerable: false }, + // [Symbol.iterator]: { enumerable: false }, [Symbol.toStringTag]: { value: 'Headers', configurable: true diff --git a/lib/fetch/util.js b/lib/fetch/util.js index a3c98a3b809..0737d874c09 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -6,9 +6,10 @@ const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet const { getGlobalOrigin } = require('./global') const { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require('./dataURL') const { performance } = require('node:perf_hooks') -const { isBlobLike, toUSVString, ReadableStreamFrom, isValidHTTPToken } = require('../core/util') +const { isBlobLike, ReadableStreamFrom, isValidHTTPToken } = require('../core/util') const assert = require('node:assert') const { isUint8Array } = require('util/types') +const { webidl } = require('./webidl') // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable /** @type {import('crypto')|undefined} */ @@ -863,6 +864,75 @@ function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) } } +/** + * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object + * @param {string} name name of the instance + * @param {any} object class + * @param {symbol} kInternalIterator + * @param {string | number} [keyIndex] + * @param {string | number} [valueIndex] + */ +function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) { + const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex) + + const properties = { + keys: { + writable: true, + enumerable: true, + configurable: true, + value: function keys () { + webidl.brandCheck(this, object) + return makeIterator(this, 'key') + } + }, + values: { + writable: true, + enumerable: true, + configurable: true, + value: function values () { + webidl.brandCheck(this, object) + return makeIterator(this, 'value') + } + }, + entries: { + writable: true, + enumerable: true, + configurable: true, + value: function entries () { + webidl.brandCheck(this, object) + return makeIterator(this, 'key+value') + } + }, + forEach: { + writable: true, + enumerable: true, + configurable: true, + value: function forEach (callbackfn, thisArg = undefined) { + webidl.brandCheck(this, object) + webidl.argumentLengthCheck(arguments, 1, { header: `${name}.forEach` }) + if (typeof callbackfn !== 'function') { + throw new TypeError( + `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.` + ) + } + for (const { 0: key, 1: value } of createIterator(this, 'key+value')) { + callbackfn.call(thisArg, value, key, this) + } + } + } + } + + return Object.defineProperties(object.prototype, { + ...properties, + [Symbol.iterator]: { + writable: true, + enumerable: false, + configurable: true, + value: properties.entries.value + } + }) +} + /** * @see https://fetch.spec.whatwg.org/#body-fully-read */ @@ -1354,7 +1424,6 @@ module.exports = { isCancelled, createDeferredPromise, ReadableStreamFrom, - toUSVString, tryUpgradeRequestToAPotentiallyTrustworthyURL, clampAndCoarsenConnectionTimingInfo, coarsenedSharedCurrentTime, @@ -1380,6 +1449,7 @@ module.exports = { normalizeMethod, serializeJavascriptValueToJSONString, createIterator, + iteratorMixin, isValidHeaderName, isValidHeaderValue, isErrorLike, diff --git a/lib/fetch/webidl.js b/lib/fetch/webidl.js index d639b8b7668..a93a25505fc 100644 --- a/lib/fetch/webidl.js +++ b/lib/fetch/webidl.js @@ -1,7 +1,7 @@ 'use strict' const { types } = require('node:util') -const { toUSVString } = require('./util') +const { toUSVString } = require('../core/util') /** @type {import('../../types/webidl').Webidl} */ const webidl = {} From 337ff622875235368e3dc34754b58d038e3584e5 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:25:54 +0900 Subject: [PATCH 03/13] rename state to object --- lib/fetch/util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 0737d874c09..935aafe243b 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -769,12 +769,12 @@ function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) `'next' called on an object that does not implement interface ${name} Iterator.` ) } - const state = this[kInternalObject] + const object = this[kInternalObject] // 6. Let index be object’s index. // 7. Let kind be object’s kind. // 8. Let values be object’s target's value pairs to iterate over. - const { index, kind, target } = state + const { index, kind, target } = object const values = target[kInternalIterator] // 9. Let len be the length of values. @@ -788,7 +788,7 @@ function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) // 11. Let pair be the entry in values at index index. const { [keyIndex]: key, [valueIndex]: value } = values[index] // 12. Set object’s index to index + 1. - state.index = index + 1 + object.index = index + 1 // 13. Return the iterator result for pair and kind. // https://webidl.spec.whatwg.org/#iterator-result // 1. Let result be a value determined by the value of kind: From 0962d5a96e39b936c9628cafaca17e47dcc9bd4f Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:29:17 +0900 Subject: [PATCH 04/13] fixup --- lib/fetch/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 935aafe243b..d6a7ab527d6 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -915,7 +915,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.` ) } - for (const { 0: key, 1: value } of createIterator(this, 'key+value')) { + for (const { 0: key, 1: value } of makeIterator(this, 'key+value')) { callbackfn.call(thisArg, value, key, this) } } From 19a7d8d17ba38e7999112d9cf999dfdb6ded0dc7 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:41:27 +0900 Subject: [PATCH 05/13] perf: use class make faster --- lib/fetch/util.js | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/lib/fetch/util.js b/lib/fetch/util.js index d6a7ab527d6..141e653eb99 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -745,37 +745,46 @@ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbo * @param {string | number} [keyIndex] * @param {string | number} [valueIndex] */ -function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) { - const kInternalObject = Symbol('internal Object') - - // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. - const iteratorObject = Object.create(esIteratorPrototype) +function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) { + class FastIterableIterator { + /** @type {any} */ + #target + /** @type {'key' | 'value' | 'key+value'} */ + #kind + /** @type {number} */ + #index + + /** + * @see https://webidl.spec.whatwg.org/#dfn-default-iterator-object + * @param {unknown} target + * @param {'key' | 'value' | 'key+value'} kind + */ + constructor (target, kind) { + this.#target = target + this.#kind = kind + this.#index = 0 + } - Object.defineProperty(iteratorObject, 'next', { - value: function next () { + next () { // 1. Let interface be the interface for which the iterator prototype object exists. - // 2. Let thisValue be the this value. - // 3. Let object be ? ToObject(thisValue). - // 4. If object is a platform object, then perform a security // check, passing: - // 5. If object is not a default iterator object for interface, // then throw a TypeError. - if (typeof this !== 'object' || this === null || !(kInternalObject in this)) { + // Object.getPrototypeOf(this) !== FastIterator.prototype + if (typeof this !== 'object' || this === null || !(#target in this)) { throw new TypeError( `'next' called on an object that does not implement interface ${name} Iterator.` ) } - const object = this[kInternalObject] // 6. Let index be object’s index. // 7. Let kind be object’s kind. // 8. Let values be object’s target's value pairs to iterate over. - const { index, kind, target } = object - const values = target[kInternalIterator] + const index = this.#index + const values = this.#target[kInternalIterator] // 9. Let len be the length of values. const len = values.length @@ -783,17 +792,25 @@ function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) // 10. If index is greater than or equal to len, then return // CreateIterResultObject(undefined, true). if (index >= len) { - return { value: undefined, done: true } + return { + value: undefined, + done: true + } } + // 11. Let pair be the entry in values at index index. const { [keyIndex]: key, [valueIndex]: value } = values[index] + // 12. Set object’s index to index + 1. - object.index = index + 1 + this.#index = index + 1 + // 13. Return the iterator result for pair and kind. + // https://webidl.spec.whatwg.org/#iterator-result + // 1. Let result be a value determined by the value of kind: let result - switch (kind) { + switch (this.#kind) { case 'key': // 1. Let idlKey be pair’s key. // 2. Let key be the result of converting idlKey to an @@ -822,45 +839,37 @@ function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) result = [key, value] break } + // 2. Return CreateIterResultObject(result, false). return { value: result, done: false } - }, - writable: true, - enumerable: true, - configurable: true - }) + } + } + + // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object + // @ts-ignore + delete FastIterableIterator.prototype.constructor + + Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype) - // The class string of an iterator prototype object for a given interface is the - // result of concatenating the identifier of the interface and the string " Iterator". - Object.defineProperty(iteratorObject, Symbol.toStringTag, { - value: `${name} Iterator`, - writable: false, - enumerable: false, - configurable: true + Object.defineProperties(FastIterableIterator.prototype, { + [Symbol.toStringTag]: { + writable: false, + enumerable: false, + configurable: true, + value: `${name} Iterator` + }, + next: { writable: true, enumerable: true, configurable: true } }) /** * @param {unknown} target - * @param {'key'|'value'|'key+value'} kind + * @param {'key' | 'value' | 'key+value'} kind */ return function (target, kind) { - // esIteratorPrototype needs to be the prototype of iteratorObject - // which is the prototype of an empty object. Yes, it's confusing. - const iterator = Object.create(iteratorObject) - Object.defineProperty(iterator, kInternalObject, { - value: { - target, - kind, - index: 0 - }, - writable: false, - enumerable: false, - configurable: true - }) - return iterator + return new FastIterableIterator(target, kind) } } @@ -873,7 +882,7 @@ function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) * @param {string | number} [valueIndex] */ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) { - const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex) + const createIterator = createFastIterator(name, kInternalIterator, keyIndex, valueIndex) const properties = { keys: { @@ -882,7 +891,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde configurable: true, value: function keys () { webidl.brandCheck(this, object) - return makeIterator(this, 'key') + return createIterator(this, 'key') } }, values: { @@ -891,7 +900,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde configurable: true, value: function values () { webidl.brandCheck(this, object) - return makeIterator(this, 'value') + return createIterator(this, 'value') } }, entries: { @@ -900,7 +909,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde configurable: true, value: function entries () { webidl.brandCheck(this, object) - return makeIterator(this, 'key+value') + return createIterator(this, 'key+value') } }, forEach: { @@ -915,7 +924,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.` ) } - for (const { 0: key, 1: value } of makeIterator(this, 'key+value')) { + for (const { 0: key, 1: value } of createIterator(this, 'key+value')) { callbackfn.call(thisArg, value, key, this) } } @@ -1448,7 +1457,6 @@ module.exports = { sameOrigin, normalizeMethod, serializeJavascriptValueToJSONString, - createIterator, iteratorMixin, isValidHeaderName, isValidHeaderValue, From a68d6775ff5a672f7ec4520f6109342ef5891ead Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:42:47 +0900 Subject: [PATCH 06/13] fixup --- lib/fetch/headers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/fetch/headers.js b/lib/fetch/headers.js index 00a14269f05..87e5669115c 100644 --- a/lib/fetch/headers.js +++ b/lib/fetch/headers.js @@ -1,5 +1,4 @@ // https://github.com/Ethan-Arrowood/undici-fetch -// https://github.com/Ethan-Arrowood/undici-fetch 'use strict' From 9fdee41006c5acaf9a39aa635fb27eca24776b8a Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:43:27 +0900 Subject: [PATCH 07/13] fixup --- lib/fetch/formdata.js | 5 ----- lib/fetch/headers.js | 5 ----- 2 files changed, 10 deletions(-) diff --git a/lib/fetch/formdata.js b/lib/fetch/formdata.js index e0a5cf6c199..80df2b8f399 100644 --- a/lib/fetch/formdata.js +++ b/lib/fetch/formdata.js @@ -165,11 +165,6 @@ Object.defineProperties(FormData.prototype, { getAll: kEnumerableProperty, has: kEnumerableProperty, set: kEnumerableProperty, - // entries: kEnumerableProperty, - // keys: kEnumerableProperty, - // values: kEnumerableProperty, - forEach: kEnumerableProperty, - // [Symbol.iterator]: { enumerable: false }, [Symbol.toStringTag]: { value: 'FormData', configurable: true diff --git a/lib/fetch/headers.js b/lib/fetch/headers.js index 87e5669115c..43860c5d98a 100644 --- a/lib/fetch/headers.js +++ b/lib/fetch/headers.js @@ -520,11 +520,6 @@ Object.defineProperties(Headers.prototype, { has: kEnumerableProperty, set: kEnumerableProperty, getSetCookie: kEnumerableProperty, - // keys: kEnumerableProperty, - // values: kEnumerableProperty, - // entries: kEnumerableProperty, - forEach: kEnumerableProperty, - // [Symbol.iterator]: { enumerable: false }, [Symbol.toStringTag]: { value: 'Headers', configurable: true From e526a4a762f6a9c87d9a0b025edc68ab8c0421dd Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:46:16 +0900 Subject: [PATCH 08/13] fixup --- lib/fetch/util.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 141e653eb99..5156a33d3d7 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -746,7 +746,7 @@ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbo * @param {string | number} [valueIndex] */ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) { - class FastIterableIterator { + class FastIterator { /** @type {any} */ #target /** @type {'key' | 'value' | 'key+value'} */ @@ -773,7 +773,6 @@ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = // check, passing: // 5. If object is not a default iterator object for interface, // then throw a TypeError. - // Object.getPrototypeOf(this) !== FastIterator.prototype if (typeof this !== 'object' || this === null || !(#target in this)) { throw new TypeError( `'next' called on an object that does not implement interface ${name} Iterator.` @@ -850,11 +849,11 @@ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object // @ts-ignore - delete FastIterableIterator.prototype.constructor + delete FastIterator.prototype.constructor - Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype) + Object.setPrototypeOf(FastIterator.prototype, esIteratorPrototype) - Object.defineProperties(FastIterableIterator.prototype, { + Object.defineProperties(FastIterator.prototype, { [Symbol.toStringTag]: { writable: false, enumerable: false, @@ -869,7 +868,7 @@ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = * @param {'key' | 'value' | 'key+value'} kind */ return function (target, kind) { - return new FastIterableIterator(target, kind) + return new FastIterator(target, kind) } } From 6d217d60162a00a248e340a7255d8af025126e53 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:48:21 +0900 Subject: [PATCH 09/13] fixup --- lib/fetch/util.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 5156a33d3d7..5147dfc36f0 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -746,7 +746,7 @@ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbo * @param {string | number} [valueIndex] */ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) { - class FastIterator { + class FastIterableIterator { /** @type {any} */ #target /** @type {'key' | 'value' | 'key+value'} */ @@ -849,11 +849,11 @@ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object // @ts-ignore - delete FastIterator.prototype.constructor + delete FastIterableIterator.prototype.constructor - Object.setPrototypeOf(FastIterator.prototype, esIteratorPrototype) + Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype) - Object.defineProperties(FastIterator.prototype, { + Object.defineProperties(FastIterableIterator.prototype, { [Symbol.toStringTag]: { writable: false, enumerable: false, @@ -866,9 +866,10 @@ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = /** * @param {unknown} target * @param {'key' | 'value' | 'key+value'} kind + * @returns {IterableIterator} */ return function (target, kind) { - return new FastIterator(target, kind) + return new FastIterableIterator(target, kind) } } From a7cd726a2ee9bcb73c031ce8b7cdeac12aa42f2d Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:51:59 +0900 Subject: [PATCH 10/13] fixup --- lib/fetch/util.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 5147dfc36f0..82e96ec9acd 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -745,7 +745,7 @@ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbo * @param {string | number} [keyIndex] * @param {string | number} [valueIndex] */ -function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) { +function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) { class FastIterableIterator { /** @type {any} */ #target @@ -882,7 +882,7 @@ function createFastIterator (name, kInternalIterator, keyIndex = 0, valueIndex = * @param {string | number} [valueIndex] */ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) { - const createIterator = createFastIterator(name, kInternalIterator, keyIndex, valueIndex) + const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex) const properties = { keys: { @@ -891,7 +891,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde configurable: true, value: function keys () { webidl.brandCheck(this, object) - return createIterator(this, 'key') + return makeIterator(this, 'key') } }, values: { @@ -900,7 +900,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde configurable: true, value: function values () { webidl.brandCheck(this, object) - return createIterator(this, 'value') + return makeIterator(this, 'value') } }, entries: { @@ -909,14 +909,14 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde configurable: true, value: function entries () { webidl.brandCheck(this, object) - return createIterator(this, 'key+value') + return makeIterator(this, 'key+value') } }, forEach: { writable: true, enumerable: true, configurable: true, - value: function forEach (callbackfn, thisArg = undefined) { + value: function forEach (callbackfn, thisArg = globalThis) { webidl.brandCheck(this, object) webidl.argumentLengthCheck(arguments, 1, { header: `${name}.forEach` }) if (typeof callbackfn !== 'function') { @@ -924,7 +924,7 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.` ) } - for (const { 0: key, 1: value } of createIterator(this, 'key+value')) { + for (const { 0: key, 1: value } of makeIterator(this, 'key+value')) { callbackfn.call(thisArg, value, key, this) } } @@ -1458,6 +1458,7 @@ module.exports = { normalizeMethod, serializeJavascriptValueToJSONString, iteratorMixin, + createIterator, isValidHeaderName, isValidHeaderValue, isErrorLike, From 740f1aa6565a765df9eb375c4746577316713b48 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 06:18:16 +0900 Subject: [PATCH 11/13] add test --- test/fetch/headers.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/fetch/headers.js b/test/fetch/headers.js index b61d8b612d2..44b7d051421 100644 --- a/test/fetch/headers.js +++ b/test/fetch/headers.js @@ -472,6 +472,21 @@ test('Headers as Iterable', async (t) => { deepStrictEqual([...headers], expected) }) + + await t.test('Headers iterator', (t) => { + const HeadersIteratorNext = Function.call.bind(Object.getPrototypeOf(new Headers()[Symbol.iterator]()).next) + + const init = [ + ['a', '1'], + ['b', '2'] + ] + + const headers = new Headers(init) + const iterator = headers[Symbol.iterator]() + assert.deepStrictEqual(HeadersIteratorNext(iterator), { value: init[0], done: false }) + assert.deepStrictEqual(HeadersIteratorNext(iterator), { value: init[1], done: false }) + assert.deepStrictEqual(HeadersIteratorNext(iterator), { value: undefined, done: true }) + }) }) test('arg validation', () => { From f6f09ad55bec468a0d15e9628b4669332012d384 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 06:19:01 +0900 Subject: [PATCH 12/13] fix test name --- test/fetch/headers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fetch/headers.js b/test/fetch/headers.js index 44b7d051421..47987a007df 100644 --- a/test/fetch/headers.js +++ b/test/fetch/headers.js @@ -473,7 +473,7 @@ test('Headers as Iterable', async (t) => { deepStrictEqual([...headers], expected) }) - await t.test('Headers iterator', (t) => { + await t.test('always use the same prototype Iterator', (t) => { const HeadersIteratorNext = Function.call.bind(Object.getPrototypeOf(new Headers()[Symbol.iterator]()).next) const init = [ From d9c94abf374230cb681d0e0c5f7a79c75eae5d85 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 13 Feb 2024 06:20:47 +0900 Subject: [PATCH 13/13] simplify --- test/fetch/headers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fetch/headers.js b/test/fetch/headers.js index 47987a007df..fcdf4b7a820 100644 --- a/test/fetch/headers.js +++ b/test/fetch/headers.js @@ -474,7 +474,7 @@ test('Headers as Iterable', async (t) => { }) await t.test('always use the same prototype Iterator', (t) => { - const HeadersIteratorNext = Function.call.bind(Object.getPrototypeOf(new Headers()[Symbol.iterator]()).next) + const HeadersIteratorNext = Function.call.bind(new Headers()[Symbol.iterator]().next) const init = [ ['a', '1'],