Skip to content

Commit 2c34b15

Browse files
KhafraDevanonrig
authored andcommitted
url: implement URL.canParse
PR-URL: nodejs#47179 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Debadree Chatterjee <[email protected]> # Conflicts: # lib/internal/url.js # test/fixtures/wpt/README.md # test/fixtures/wpt/versions.json
1 parent b2fd203 commit 2c34b15

File tree

7 files changed

+147
-19
lines changed

7 files changed

+147
-19
lines changed

benchmark/url/whatwgurl-canParse.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
4+
const bench = common.createBenchmark(main, {
5+
type: Object.keys(common.urls),
6+
n: [25e6],
7+
});
8+
9+
function main({ type, n }) {
10+
bench.start();
11+
for (let i = 0; i < n; i += 1)
12+
URL.canParse(common.urls[type]);
13+
bench.end(n);
14+
}

doc/api/url.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,27 @@ added: v16.7.0
662662
Removes the stored {Blob} identified by the given ID. Attempting to revoke a
663663
ID that isn't registered will silently fail.
664664

665+
#### `URL.canParse(input[, base])`
666+
667+
<!-- YAML
668+
added: REPLACEME
669+
-->
670+
671+
* `input` {string} The absolute or relative input URL to parse. If `input`
672+
is relative, then `base` is required. If `input` is absolute, the `base`
673+
is ignored. If `input` is not a string, it is [converted to a string][] first.
674+
* `base` {string} The base URL to resolve against if the `input` is not
675+
absolute. If `base` is not a string, it is [converted to a string][] first.
676+
* Returns: {boolean}
677+
678+
Checks if an `input` relative to the `base` can be parsed to a `URL`.
679+
680+
```js
681+
const isValid = URL.canParse('/foo', 'https://example.org/'); // true
682+
683+
const isNotValid = URL.canParse('/foo'); // false
684+
```
685+
665686
### Class: `URLSearchParams`
666687

667688
<!-- YAML

lib/internal/url.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const {
8989
domainToASCII: _domainToASCII,
9090
domainToUnicode: _domainToUnicode,
9191
parse,
92+
canParse: _canParse,
9293
updateUrl,
9394
} = internalBinding('url');
9495

@@ -720,6 +721,16 @@ class URL {
720721
return this.#context.href;
721722
}
722723

724+
static canParse(url, base = undefined) {
725+
url = `${url}`;
726+
727+
if (base !== undefined) {
728+
base = `${base}`;
729+
}
730+
731+
return _canParse(url, base);
732+
}
733+
723734
static createObjectURL(obj) {
724735
const cryptoRandom = lazyCryptoRandom();
725736
if (cryptoRandom === undefined)
@@ -768,6 +779,15 @@ ObjectDefineProperties(URL.prototype, {
768779
toJSON: kEnumerableProperty,
769780
});
770781

782+
ObjectDefineProperties(URL, {
783+
canParse: {
784+
__proto__: null,
785+
configurable: true,
786+
writable: true,
787+
enumerable: true,
788+
},
789+
});
790+
771791
ObjectDefineProperties(URL, {
772792
createObjectURL: kEnumerableProperty,
773793
revokeObjectURL: kEnumerableProperty,

src/node_url.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,30 @@ void Parse(const FunctionCallbackInfo<Value>& args) {
9393
args.GetReturnValue().Set(true);
9494
}
9595

96+
void CanParse(const FunctionCallbackInfo<Value>& args) {
97+
CHECK_GE(args.Length(), 2);
98+
CHECK(args[0]->IsString()); // input
99+
// args[1] // base url
100+
101+
Environment* env = Environment::GetCurrent(args);
102+
HandleScope handle_scope(env->isolate());
103+
Context::Scope context_scope(env->context());
104+
105+
Utf8Value input(env->isolate(), args[0]);
106+
ada::result base;
107+
ada::url* base_pointer = nullptr;
108+
if (args[1]->IsString()) {
109+
base = ada::parse(Utf8Value(env->isolate(), args[1]).ToString());
110+
if (!base) {
111+
return args.GetReturnValue().Set(false);
112+
}
113+
base_pointer = &base.value();
114+
}
115+
ada::result out = ada::parse(input.ToStringView(), base_pointer);
116+
117+
args.GetReturnValue().Set(out.has_value());
118+
}
119+
96120
void DomainToASCII(const FunctionCallbackInfo<Value>& args) {
97121
Environment* env = Environment::GetCurrent(args);
98122
CHECK_GE(args.Length(), 1);
@@ -285,6 +309,7 @@ void Initialize(Local<Object> target,
285309
void* priv) {
286310
SetMethod(context, target, "parse", Parse);
287311
SetMethod(context, target, "updateUrl", UpdateUrl);
312+
SetMethodNoSideEffect(context, target, "canParse", CanParse);
288313
SetMethodNoSideEffect(context, target, "formatUrl", FormatUrl);
289314

290315
SetMethodNoSideEffect(context, target, "domainToASCII", DomainToASCII);
@@ -294,6 +319,7 @@ void Initialize(Local<Object> target,
294319

295320
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
296321
registry->Register(Parse);
322+
registry->Register(CanParse);
297323
registry->Register(UpdateUrl);
298324
registry->Register(FormatUrl);
299325

test/fixtures/wpt/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,30 @@ See [test/wpt](../../wpt/README.md) for information on how these tests are run.
1010

1111
Last update:
1212

13-
- common: https://github.com/web-platform-tests/wpt/tree/03c5072aff/common
13+
- common: https://github.com/web-platform-tests/wpt/tree/dbd648158d/common
1414
- console: https://github.com/web-platform-tests/wpt/tree/767ae35464/console
1515
- dom/abort: https://github.com/web-platform-tests/wpt/tree/8fadb38120/dom/abort
1616
- dom/events: https://github.com/web-platform-tests/wpt/tree/ab8999891c/dom/events
1717
- encoding: https://github.com/web-platform-tests/wpt/tree/0c1b9d1622/encoding
1818
- fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources
19-
- FileAPI: https://github.com/web-platform-tests/wpt/tree/3b279420d4/FileAPI
20-
- FileAPI/file: https://github.com/web-platform-tests/wpt/tree/c01f637cca/FileAPI/file
19+
- FileAPI: https://github.com/web-platform-tests/wpt/tree/1e432c4550/FileAPI
2120
- hr-time: https://github.com/web-platform-tests/wpt/tree/34cafd797e/hr-time
2221
- html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob
2322
- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing
2423
- html/webappapis/structured-clone: https://github.com/web-platform-tests/wpt/tree/47d3fb280c/html/webappapis/structured-clone
2524
- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers
26-
- interfaces: https://github.com/web-platform-tests/wpt/tree/fc086c82d5/interfaces
25+
- interfaces: https://github.com/web-platform-tests/wpt/tree/df731dab88/interfaces
2726
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline
28-
- resources: https://github.com/web-platform-tests/wpt/tree/fbf1e7d247/resources
29-
- streams: https://github.com/web-platform-tests/wpt/tree/9e5ef42bd3/streams
30-
- url: https://github.com/web-platform-tests/wpt/tree/f1ade799d0/url
27+
- resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing
28+
- resources: https://github.com/web-platform-tests/wpt/tree/919874f84f/resources
29+
- streams: https://github.com/web-platform-tests/wpt/tree/51750bc8d7/streams
30+
- url: https://github.com/web-platform-tests/wpt/tree/7c5c3cc125/url
3131
- user-timing: https://github.com/web-platform-tests/wpt/tree/df24fb604e/user-timing
3232
- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/d8dbe6990b/wasm/jsapi
3333
- wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi
34-
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/ee30029d47/WebCryptoAPI
34+
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/1406b5c0d0/WebCryptoAPI
3535
- webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions
36+
- webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/e97fac4791/webmessaging/broadcastchannel
3637

3738
[Web Platform Tests]: https://github.com/web-platform-tests/wpt
3839
[`git node wpt`]: https://github.com/nodejs/node-core-utils/blob/main/docs/git-node.md#git-node-wpt
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// This intentionally does not use resources/urltestdata.json to preserve resources.
2+
[
3+
{
4+
"url": undefined,
5+
"base": undefined,
6+
"expected": false
7+
},
8+
{
9+
"url": "a:b",
10+
"base": undefined,
11+
"expected": true
12+
},
13+
{
14+
"url": undefined,
15+
"base": "a:b",
16+
"expected": false
17+
},
18+
{
19+
"url": "a:/b",
20+
"base": undefined,
21+
"expected": true
22+
},
23+
{
24+
"url": undefined,
25+
"base": "a:/b",
26+
"expected": true
27+
},
28+
{
29+
"url": "https://test:test",
30+
"base": undefined,
31+
"expected": false
32+
},
33+
{
34+
"url": "a",
35+
"base": "https://b/",
36+
"expected": true
37+
}
38+
].forEach(({ url, base, expected }) => {
39+
test(() => {
40+
assert_equals(URL.canParse(url, base), expected);
41+
}, `URL.canParse(${url}, ${base})`);
42+
});

test/fixtures/wpt/versions.json

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"common": {
3-
"commit": "03c5072affa496c5fd2a506e5c40d23e36b5e3aa",
3+
"commit": "dbd648158d337580885e70a54f929daf215211a0",
44
"path": "common"
55
},
66
"console": {
@@ -24,13 +24,9 @@
2424
"path": "fetch/data-urls/resources"
2525
},
2626
"FileAPI": {
27-
"commit": "3b279420d40afea32506e823f9ac005448f4f3d8",
27+
"commit": "1e432c4550a1595888d7c9eb26d90895e9e7e022",
2828
"path": "FileAPI"
2929
},
30-
"FileAPI/file": {
31-
"commit": "c01f637cca43f0e08ce8e4269121dcd89ccbdd82",
32-
"path": "FileAPI/file"
33-
},
3430
"hr-time": {
3531
"commit": "34cafd797e58dad280d20040eee012d49ccfa91f",
3632
"path": "hr-time"
@@ -52,23 +48,27 @@
5248
"path": "html/webappapis/timers"
5349
},
5450
"interfaces": {
55-
"commit": "fc086c82d5a7e9b01a684a23336d6d1f9e79e303",
51+
"commit": "df731dab88a1a25c04eb7e6238c11dc28fda0801",
5652
"path": "interfaces"
5753
},
5854
"performance-timeline": {
5955
"commit": "17ebc3aea0d6321e69554067c39ab5855e6fb67e",
6056
"path": "performance-timeline"
6157
},
58+
"resource-timing": {
59+
"commit": "22d38586d04c1d22b64db36f439c6bb84f03db7d",
60+
"path": "resource-timing"
61+
},
6262
"resources": {
63-
"commit": "fbf1e7d24776b6da144dbca45c22d39dc0512d2d",
63+
"commit": "919874f84ff3703365063e749161a34af38c3d2a",
6464
"path": "resources"
6565
},
6666
"streams": {
67-
"commit": "9e5ef42bd34b5b19b76d0d4cb19012e52c222664",
67+
"commit": "51750bc8d749bd80930506f603940a6bafda459f",
6868
"path": "streams"
6969
},
7070
"url": {
71-
"commit": "f1ade799d04b72b0ff75c13e988744c2cd873741",
71+
"commit": "7c5c3cc125979b4768d414471e6ab655b473aae8",
7272
"path": "url"
7373
},
7474
"user-timing": {
@@ -84,11 +84,15 @@
8484
"path": "wasm/webapi"
8585
},
8686
"WebCryptoAPI": {
87-
"commit": "ee30029d47cf9f7cf8f71fe851b4c29903edf851",
87+
"commit": "1406b5c0d07b5e8dd08e328c451e42c23f3b96c8",
8888
"path": "WebCryptoAPI"
8989
},
9090
"webidl/ecmascript-binding/es-exceptions": {
9191
"commit": "a370aad338d6ed743abb4d2c6ae84a7f1058558c",
9292
"path": "webidl/ecmascript-binding/es-exceptions"
93+
},
94+
"webmessaging/broadcastchannel": {
95+
"commit": "e97fac4791931fb7455ba3fad759d362c7108b09",
96+
"path": "webmessaging/broadcastchannel"
9397
}
9498
}

0 commit comments

Comments
 (0)