whatwg-url 16.0.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ whatwg-url is a full implementation of the WHATWG [URL Standard](https://url.spe
4
4
 
5
5
  ## Specification conformance
6
6
 
7
- whatwg-url is currently up to date with the URL spec up to commit [b6b3251](https://github.com/whatwg/url/commit/b6b3251fe911ab33d68fb051efe0e4d39ae4145e).
7
+ whatwg-url is currently up to date with the URL spec up to commit [3c83874](https://github.com/whatwg/url/commit/3c83874ae1eac84ceb71d8c4344673251c982bbe) and the web platform tests up to commit [2a91c2e](https://github.com/web-platform-tests/wpt/commit/2a91c2e71952bdceef8b2825ce3a0a0dc73aa588).
8
8
 
9
9
  For `file:` URLs, whose [origin is left unspecified](https://url.spec.whatwg.org/#concept-url-origin), whatwg-url chooses to use a new opaque origin (which serializes to `"null"`).
10
10
 
package/lib/URL-impl.js CHANGED
@@ -2,6 +2,7 @@
2
2
  const usm = require("./url-state-machine");
3
3
  const urlencoded = require("./urlencoded");
4
4
  const URLSearchParams = require("./URLSearchParams");
5
+ const URL = require("./URL");
5
6
 
6
7
  exports.implementation = class URLImpl {
7
8
  // Unlike the spec, we duplicate some code between the constructor and canParse, because we want to give useful error
@@ -32,7 +33,7 @@ exports.implementation = class URLImpl {
32
33
 
33
34
  static parse(globalObject, input, base) {
34
35
  try {
35
- return new URLImpl(globalObject, [input, base]);
36
+ return URL.createImpl(globalObject, [input, base]);
36
37
  } catch {
37
38
  return null;
38
39
  }
@@ -106,7 +106,10 @@ exports.install = (globalObject, globalNames) => {
106
106
  let curArg = arguments[0];
107
107
  if (curArg !== undefined) {
108
108
  if (utils.isObject(curArg)) {
109
- if (curArg[Symbol.iterator] !== undefined) {
109
+ if (
110
+ utils.getMethod(curArg, Symbol.iterator, "Failed to construct 'URLSearchParams': parameter 1") !==
111
+ undefined
112
+ ) {
110
113
  if (!utils.isObject(curArg)) {
111
114
  throw new globalObject.TypeError(
112
115
  "Failed to construct 'URLSearchParams': parameter 1" + " sequence" + " is not an iterable object."
@@ -63,6 +63,10 @@ function containsForbiddenDomainCodePoint(string) {
63
63
  return containsForbiddenHostCodePoint(string) || string.search(/[\u0000-\u001F]|%|\u007F/u) !== -1;
64
64
  }
65
65
 
66
+ function isASCIIString(string) {
67
+ return !/[^\u0000-\u007F]/u.test(string);
68
+ }
69
+
66
70
  function isSpecialScheme(scheme) {
67
71
  return specialSchemes[scheme] !== undefined;
68
72
  }
@@ -241,7 +245,7 @@ function parseIPv6(input) {
241
245
  }
242
246
 
243
247
  while (infra.isASCIIDigit(input[pointer])) {
244
- const number = parseInt(at(input, pointer));
248
+ const number = parseInt(at(input, pointer), 10);
245
249
  if (ipv4Piece === null) {
246
250
  ipv4Piece = number;
247
251
  } else if (ipv4Piece === 0) {
@@ -342,7 +346,7 @@ function parseHost(input, isOpaque = false) {
342
346
  }
343
347
 
344
348
  const domain = utf8DecodeWithoutBOM(percentDecodeString(input));
345
- const asciiDomain = domainToASCII(domain);
349
+ const asciiDomain = domainParser(domain);
346
350
  if (asciiDomain === failure) {
347
351
  return failure;
348
352
  }
@@ -426,28 +430,65 @@ function serializeHost(host) {
426
430
  return host;
427
431
  }
428
432
 
429
- function domainToASCII(domain, beStrict = false) {
430
- const result = tr46.toASCII(domain, {
431
- checkHyphens: beStrict,
432
- checkBidi: true,
433
- checkJoiners: true,
434
- useSTD3ASCIIRules: beStrict,
435
- transitionalProcessing: false,
436
- verifyDNSLength: beStrict,
437
- ignoreInvalidPunycode: false
438
- });
439
- if (result === null) {
440
- return failure;
441
- }
442
-
443
- if (!beStrict) {
444
- if (result === "") {
433
+ function domainParser(domain, beStrict = false) {
434
+ if (beStrict) {
435
+ const result = tr46.toASCII(domain, {
436
+ checkHyphens: true,
437
+ checkBidi: true,
438
+ checkJoiners: true,
439
+ useSTD3ASCIIRules: true,
440
+ transitionalProcessing: false,
441
+ verifyDNSLength: true,
442
+ ignoreInvalidPunycode: false
443
+ });
444
+ if (result === null) {
445
445
  return failure;
446
446
  }
447
- if (containsForbiddenDomainCodePoint(result)) {
447
+
448
+ return result;
449
+ }
450
+
451
+ let result;
452
+ if (isASCIIString(domain)) {
453
+ // The `domain parser` algorithm runs Unicode ToASCII here only to generate a `domain-to-ASCII` validation error,
454
+ // and we do not surface validation errors yet. If we ever support them
455
+ // (https://github.com/jsdom/whatwg-url/issues/156), we will need code like the following:
456
+ //
457
+ // if (tr46.toASCII(domain, {
458
+ // checkHyphens: false,
459
+ // checkBidi: true,
460
+ // checkJoiners: true,
461
+ // useSTD3ASCIIRules: false,
462
+ // transitionalProcessing: false,
463
+ // verifyDNSLength: false,
464
+ // ignoreInvalidPunycode: false
465
+ // }) === null) {
466
+ // validationError("domain-to-ASCII");
467
+ // }
468
+
469
+ result = domain.toLowerCase();
470
+ } else {
471
+ result = tr46.toASCII(domain, {
472
+ checkHyphens: false,
473
+ checkBidi: true,
474
+ checkJoiners: true,
475
+ useSTD3ASCIIRules: false,
476
+ transitionalProcessing: false,
477
+ verifyDNSLength: false,
478
+ ignoreInvalidPunycode: false
479
+ });
480
+ if (result === null) {
448
481
  return failure;
449
482
  }
450
483
  }
484
+
485
+ if (result === "") {
486
+ return failure;
487
+ }
488
+ if (containsForbiddenDomainCodePoint(result)) {
489
+ return failure;
490
+ }
491
+
451
492
  return result;
452
493
  }
453
494
 
@@ -857,7 +898,7 @@ URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
857
898
  (isSpecial(this.url) && c === p("\\")) ||
858
899
  this.stateOverride) {
859
900
  if (this.buffer !== "") {
860
- const port = parseInt(this.buffer);
901
+ const port = parseInt(this.buffer, 10);
861
902
  if (port > 2 ** 16 - 1) {
862
903
  this.parseError = true;
863
904
  return failure;
package/lib/utils.js CHANGED
@@ -111,7 +111,7 @@ function isArrayIndexPropName(P) {
111
111
  }
112
112
 
113
113
  const arrayBufferByteLengthGetter =
114
- Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
114
+ Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
115
115
  function isArrayBuffer(value) {
116
116
  try {
117
117
  arrayBufferByteLengthGetter.call(value);
@@ -198,6 +198,103 @@ function isAccessorDescriptor(desc) {
198
198
  return Object.hasOwn(desc, "get") || Object.hasOwn(desc, "set");
199
199
  }
200
200
 
201
+ function getMethod(value, property, errPrefix = "The provided value") {
202
+ const func = value[property];
203
+ if (func === undefined || func === null) {
204
+ return undefined;
205
+ }
206
+ if (typeof func !== "function") {
207
+ throw new TypeError(`${errPrefix}'s ${property} property is not a function.`);
208
+ }
209
+ return func;
210
+ }
211
+
212
+ function createAsyncFromSyncIterator(syncIterator) {
213
+ // Instead of re-implementing CreateAsyncFromSyncIterator and %AsyncFromSyncIteratorPrototype%,
214
+ // we use yield* inside an async generator function to achieve the same result.
215
+
216
+ // Wrap the sync iterator inside a sync iterable, so we can use it with yield*.
217
+ const syncIterable = {
218
+ [Symbol.iterator]: () => syncIterator
219
+ };
220
+ // Create an async generator function and immediately invoke it.
221
+ const asyncIterator = (async function* () {
222
+ return yield* syncIterable;
223
+ })();
224
+ // Return as an async iterator record.
225
+ return asyncIterator;
226
+ }
227
+
228
+ function convertAsyncSequence(object, itemConverter, errPrefix = "The provided value") {
229
+ if (!isObject(object)) {
230
+ throw new TypeError(`${errPrefix} is not an object.`);
231
+ }
232
+ let method = getMethod(object, Symbol.asyncIterator, errPrefix);
233
+ let type = "async";
234
+ if (method === undefined) {
235
+ method = getMethod(object, Symbol.iterator, errPrefix);
236
+ if (method === undefined) {
237
+ throw new TypeError(`${errPrefix} is not an async iterable object.`);
238
+ }
239
+ type = "sync";
240
+ }
241
+
242
+ return {
243
+ object,
244
+ method,
245
+ type,
246
+ // The wrapperSymbol ensures that if the async sequence is used as a return value,
247
+ // that it exposes the original JavaScript value.
248
+ // https://webidl.spec.whatwg.org/#js-async-iterable
249
+ [wrapperSymbol]: object,
250
+ // Implement the async iterator protocol, so users can iterate
251
+ // the async sequence directly (e.g. with for await...of)
252
+ // instead of needing to call a separate helper function to open the async sequence.
253
+ // https://webidl.spec.whatwg.org/#async-sequence-open
254
+ [Symbol.asyncIterator]() {
255
+ return openAsyncSequence(object, method, type, itemConverter, `${errPrefix}'s iterator`);
256
+ }
257
+ };
258
+ }
259
+
260
+ function openAsyncSequence(object, method, type, itemConverter, errPrefix = "The provided value") {
261
+ let iterator = call(method, object);
262
+ if (!isObject(iterator)) {
263
+ throw new TypeError(`${errPrefix}'s method must return an object`);
264
+ }
265
+ if (type === "sync") {
266
+ iterator = createAsyncFromSyncIterator(iterator);
267
+ }
268
+ const nextMethod = iterator.next;
269
+ return {
270
+ async next() {
271
+ const nextResult = await call(nextMethod, iterator);
272
+ if (!isObject(nextResult)) {
273
+ throw new TypeError(`${errPrefix}'s next method must return an object`);
274
+ }
275
+ const { done, value } = nextResult;
276
+ if (done) {
277
+ return { done: true, value: undefined };
278
+ }
279
+ return { done: false, value: itemConverter(value) };
280
+ },
281
+ async return(reason) {
282
+ const returnMethod = getMethod(iterator, "return", errPrefix);
283
+ if (returnMethod === undefined) {
284
+ return { done: true, value: undefined };
285
+ }
286
+ const returnResult = await call(returnMethod, iterator, reason);
287
+ if (!isObject(returnResult)) {
288
+ throw new TypeError(`${errPrefix}'s return method must return an object`);
289
+ }
290
+ return { done: true, value: undefined };
291
+ },
292
+ [Symbol.asyncIterator]() {
293
+ return this;
294
+ }
295
+ };
296
+ }
297
+
201
298
  const supportsPropertyIndex = Symbol("supports property index");
202
299
  const supportedPropertyIndices = Symbol("supported property indices");
203
300
  const supportsPropertyName = Symbol("supports property name");
@@ -232,6 +329,8 @@ module.exports = exports = {
232
329
  isArrayBuffer,
233
330
  isSharedArrayBuffer,
234
331
  isArrayIndexPropName,
332
+ getMethod,
333
+ convertAsyncSequence,
235
334
  supportsPropertyIndex,
236
335
  supportedPropertyIndices,
237
336
  supportsPropertyName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatwg-url",
3
- "version": "16.0.0",
3
+ "version": "17.0.0",
4
4
  "description": "An implementation of the WHATWG URL Standard's URL API and parsing machinery",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -15,21 +15,21 @@
15
15
  "url": "git+https://github.com/jsdom/whatwg-url.git"
16
16
  },
17
17
  "dependencies": {
18
- "@exodus/bytes": "^1.11.0",
18
+ "@exodus/bytes": "^1.15.1",
19
19
  "tr46": "^6.0.0",
20
20
  "webidl-conversions": "^8.0.1"
21
21
  },
22
22
  "devDependencies": {
23
- "@domenic/eslint-config": "^4.1.0",
24
- "tinybench": "^6.0.0",
25
- "c8": "^10.1.3",
26
- "esbuild": "^0.27.2",
27
- "eslint": "^9.39.2",
28
- "globals": "^17.3.0",
29
- "webidl2js": "^19.1.0"
23
+ "@domenic/eslint-config": "^5.1.0",
24
+ "tinybench": "^6.0.2",
25
+ "c8": "^11.0.0",
26
+ "esbuild": "^0.28.1",
27
+ "eslint": "^10.6.0",
28
+ "globals": "^17.7.0",
29
+ "webidl2js": "^20.1.0"
30
30
  },
31
31
  "engines": {
32
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
32
+ "node": "^22.14.0 || >=24.0.0"
33
33
  },
34
34
  "scripts": {
35
35
  "coverage": "c8 node --test --experimental-test-coverage test/*.js",