Skip to content

Commit aa26811

Browse files
authored
Update URL Standard API to remove cannot-be-a-base-URL
Follows whatwg/url#655. Also introduces some new APIs.
1 parent e2d7990 commit aa26811

File tree

5 files changed

+45
-40
lines changed

5 files changed

+45
-40
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ whatwg-url is a full implementation of the WHATWG [URL Standard](https://url.spe
44

55
## Specification conformance
66

7-
whatwg-url is currently up to date with the URL spec up to commit [ab0e820](https://github.com/whatwg/url/commit/ab0e820b0b559610b30c731b7f2c1a8094181680).
7+
whatwg-url is currently up to date with the URL spec up to commit [43c2713](https://github.com/whatwg/url/commit/43c27137a0bc82c4b800fe74be893255fbeb35f4).
88

99
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"`).
1010

@@ -24,10 +24,12 @@ The following methods are exported for use by places like jsdom that need to imp
2424
- [Basic URL parser](https://url.spec.whatwg.org/#concept-basic-url-parser): `basicURLParse(input, { baseURL, url, stateOverride })`
2525
- [URL serializer](https://url.spec.whatwg.org/#concept-url-serializer): `serializeURL(urlRecord, excludeFragment)`
2626
- [Host serializer](https://url.spec.whatwg.org/#concept-host-serializer): `serializeHost(hostFromURLRecord)`
27+
- [URL path serializer](https://url.spec.whatwg.org/#url-path-serializer): `serializePath(urlRecord)`
2728
- [Serialize an integer](https://url.spec.whatwg.org/#serialize-an-integer): `serializeInteger(number)`
2829
- [Origin](https://url.spec.whatwg.org/#concept-url-origin) [serializer](https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin): `serializeURLOrigin(urlRecord)`
2930
- [Set the username](https://url.spec.whatwg.org/#set-the-username): `setTheUsername(urlRecord, usernameString)`
3031
- [Set the password](https://url.spec.whatwg.org/#set-the-password): `setThePassword(urlRecord, passwordString)`
32+
- [Has an opaque path](https://url.spec.whatwg.org/#url-opaque-path): `hasAnOpaquePath(urlRecord)`
3133
- [Cannot have a username/password/port](https://url.spec.whatwg.org/#cannot-have-a-username-password-port): `cannotHaveAUsernamePasswordPort(urlRecord)`
3234
- [Percent decode bytes](https://url.spec.whatwg.org/#percent-decode): `percentDecodeBytes(uint8Array)`
3335
- [Percent decode a string](https://url.spec.whatwg.org/#percent-decode-string): `percentDecodeString(string)`
@@ -52,7 +54,7 @@ The `stateOverride` parameter is one of the following strings:
5254
- [`"file host"`](https://url.spec.whatwg.org/#file-host-state)
5355
- [`"path start"`](https://url.spec.whatwg.org/#path-start-state)
5456
- [`"path"`](https://url.spec.whatwg.org/#path-state)
55-
- [`"cannot-be-a-base-URL path"`](https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state)
57+
- [`"opaque path"`](https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state)
5658
- [`"query"`](https://url.spec.whatwg.org/#query-state)
5759
- [`"fragment"`](https://url.spec.whatwg.org/#fragment-state)
5860

@@ -63,10 +65,9 @@ The URL record type has the following API:
6365
- [`password`](https://url.spec.whatwg.org/#concept-url-password)
6466
- [`host`](https://url.spec.whatwg.org/#concept-url-host)
6567
- [`port`](https://url.spec.whatwg.org/#concept-url-port)
66-
- [`path`](https://url.spec.whatwg.org/#concept-url-path) (as an array)
68+
- [`path`](https://url.spec.whatwg.org/#concept-url-path) (as an array of strings, or a string)
6769
- [`query`](https://url.spec.whatwg.org/#concept-url-query)
6870
- [`fragment`](https://url.spec.whatwg.org/#concept-url-fragment)
69-
- [`cannotBeABaseURL`](https://url.spec.whatwg.org/#url-cannot-be-a-base-url-flag) (as a boolean)
7071

7172
These properties should be treated with care, as in general changing them will cause the URL record to be in an inconsistent state until the appropriate invocation of `basicURLParse` is used to fix it up. You can see examples of this in the URL Standard, where there are many step sequences like "4. Set context object’s url’s fragment to the empty string. 5. Basic URL parse _input_ with context object’s url as _url_ and fragment state as _state override_." In between those two steps, a URL record is in an unusable state.
7273

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ exports.URLSearchParams = sharedGlobalObject.URLSearchParams;
1414
exports.parseURL = urlStateMachine.parseURL;
1515
exports.basicURLParse = urlStateMachine.basicURLParse;
1616
exports.serializeURL = urlStateMachine.serializeURL;
17+
exports.serializePath = urlStateMachine.serializePath;
1718
exports.serializeHost = urlStateMachine.serializeHost;
1819
exports.serializeInteger = urlStateMachine.serializeInteger;
1920
exports.serializeURLOrigin = urlStateMachine.serializeURLOrigin;
2021
exports.setTheUsername = urlStateMachine.setTheUsername;
2122
exports.setThePassword = urlStateMachine.setThePassword;
2223
exports.cannotHaveAUsernamePasswordPort = urlStateMachine.cannotHaveAUsernamePasswordPort;
24+
exports.hasAnOpaquePath = urlStateMachine.hasAnOpaquePath;
2325

2426
exports.percentDecodeString = percentEncoding.percentDecodeString;
2527
exports.percentDecodeBytes = percentEncoding.percentDecodeBytes;

lib/URL-impl.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ exports.implementation = class URLImpl {
101101
}
102102

103103
set host(v) {
104-
if (this._url.cannotBeABaseURL) {
104+
if (usm.hasAnOpaquePath(this._url)) {
105105
return;
106106
}
107107

@@ -117,7 +117,7 @@ exports.implementation = class URLImpl {
117117
}
118118

119119
set hostname(v) {
120-
if (this._url.cannotBeABaseURL) {
120+
if (usm.hasAnOpaquePath(this._url)) {
121121
return;
122122
}
123123

@@ -145,19 +145,11 @@ exports.implementation = class URLImpl {
145145
}
146146

147147
get pathname() {
148-
if (this._url.cannotBeABaseURL) {
149-
return this._url.path[0];
150-
}
151-
152-
if (this._url.path.length === 0) {
153-
return "";
154-
}
155-
156-
return `/${this._url.path.join("/")}`;
148+
return usm.serializePath(this._url);
157149
}
158150

159151
set pathname(v) {
160-
if (this._url.cannotBeABaseURL) {
152+
if (usm.hasAnOpaquePath(this._url)) {
161153
return;
162154
}
163155

lib/url-state-machine.js

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,11 @@ function includesCredentials(url) {
467467
}
468468

469469
function cannotHaveAUsernamePasswordPort(url) {
470-
return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";
470+
return url.host === null || url.host === "" || hasAnOpaquePath(url) || url.scheme === "file";
471+
}
472+
473+
function hasAnOpaquePath(url) {
474+
return typeof url.path === "string";
471475
}
472476

473477
function isNormalizedWindowsDriveLetter(string) {
@@ -493,9 +497,7 @@ function URLStateMachine(input, base, encodingOverride, url, stateOverride) {
493497
port: null,
494498
path: [],
495499
query: null,
496-
fragment: null,
497-
498-
cannotBeABaseURL: false
500+
fragment: null
499501
};
500502

501503
const res = trimControlChars(this.input);
@@ -592,9 +594,8 @@ URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
592594
this.state = "path or authority";
593595
++this.pointer;
594596
} else {
595-
this.url.cannotBeABaseURL = true;
596-
this.url.path.push("");
597-
this.state = "cannot-be-a-base-URL path";
597+
this.url.path = "";
598+
this.state = "opaque path";
598599
}
599600
} else if (!this.stateOverride) {
600601
this.buffer = "";
@@ -609,14 +610,13 @@ URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
609610
};
610611

611612
URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {
612-
if (this.base === null || (this.base.cannotBeABaseURL && c !== p("#"))) {
613+
if (this.base === null || (hasAnOpaquePath(this.base) && c !== p("#"))) {
613614
return failure;
614-
} else if (this.base.cannotBeABaseURL && c === p("#")) {
615+
} else if (hasAnOpaquePath(this.base) && c === p("#")) {
615616
this.url.scheme = this.base.scheme;
616-
this.url.path = this.base.path.slice();
617+
this.url.path = this.base.path;
617618
this.url.query = this.base.query;
618619
this.url.fragment = "";
619-
this.url.cannotBeABaseURL = true;
620620
this.state = "fragment";
621621
} else if (this.base.scheme === "file") {
622622
this.state = "file";
@@ -1033,7 +1033,7 @@ URLStateMachine.prototype["parse path"] = function parsePath(c) {
10331033
return true;
10341034
};
10351035

1036-
URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) {
1036+
URLStateMachine.prototype["parse opaque path"] = function parseOpaquePath(c) {
10371037
if (c === p("?")) {
10381038
this.url.query = "";
10391039
this.state = "query";
@@ -1053,7 +1053,7 @@ URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCan
10531053
}
10541054

10551055
if (!isNaN(c)) {
1056-
this.url.path[0] += utf8PercentEncodeCodePoint(c, isC0ControlPercentEncode);
1056+
this.url.path += utf8PercentEncodeCodePoint(c, isC0ControlPercentEncode);
10571057
}
10581058
}
10591059

@@ -1125,16 +1125,10 @@ function serializeURL(url, excludeFragment) {
11251125
}
11261126
}
11271127

1128-
if (url.cannotBeABaseURL) {
1129-
output += url.path[0];
1130-
} else {
1131-
if (url.host === null && url.path.length > 1 && url.path[0] === "") {
1132-
output += "/.";
1133-
}
1134-
for (const segment of url.path) {
1135-
output += `/${segment}`;
1136-
}
1128+
if (url.host === null && !hasAnOpaquePath(url) && url.path.length > 1 && url.path[0] === "") {
1129+
output += "/.";
11371130
}
1131+
output += serializePath(url);
11381132

11391133
if (url.query !== null) {
11401134
output += `?${url.query}`;
@@ -1158,14 +1152,28 @@ function serializeOrigin(tuple) {
11581152
return result;
11591153
}
11601154

1155+
function serializePath(url) {
1156+
if (hasAnOpaquePath(url)) {
1157+
return url.path;
1158+
}
1159+
1160+
let output = "";
1161+
for (const segment of url.path) {
1162+
output += `/${segment}`;
1163+
}
1164+
return output;
1165+
}
1166+
11611167
module.exports.serializeURL = serializeURL;
11621168

1169+
module.exports.serializePath = serializePath;
1170+
11631171
module.exports.serializeURLOrigin = function (url) {
11641172
// https://url.spec.whatwg.org/#concept-url-origin
11651173
switch (url.scheme) {
11661174
case "blob":
11671175
try {
1168-
return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0]));
1176+
return module.exports.serializeURLOrigin(module.exports.parseURL(serializePath(url)));
11691177
} catch (e) {
11701178
// serializing an opaque origin returns "null"
11711179
return "null";
@@ -1220,6 +1228,8 @@ module.exports.serializeHost = serializeHost;
12201228

12211229
module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
12221230

1231+
module.exports.hasAnOpaquePath = hasAnOpaquePath;
1232+
12231233
module.exports.serializeInteger = function (integer) {
12241234
return String(integer);
12251235
};

scripts/get-latest-platform-tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ process.on("unhandledRejection", err => {
2323
// 1. Go to https://github.com/web-platform-tests/wpt/tree/master/url
2424
// 2. Press "y" on your keyboard to get a permalink
2525
// 3. Copy the commit hash
26-
const commitHash = "eb8bdce552a2a9b10010f7755ed9d8d0773d548e";
26+
const commitHash = "5acc42721ce5811462acc297bff75d33f999cd8f";
2727

2828
const urlPrefix = `https://raw.githubusercontent.com/web-platform-tests/wpt/${commitHash}/url/`;
2929
const targetDir = path.resolve(__dirname, "..", "test", "web-platform-tests");

0 commit comments

Comments
 (0)