0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-10-31 09:14:20 -04:00
denoland-deno/op_crates/url/00_url.js
Luca Casonato 9e6cd91014
chore: align fetch to spec (#10203)
This commit aligns the `fetch` API and the `Request` / `Response`
classes belonging to it to the spec. This commit enables all the
relevant `fetch` WPT tests. Spec compliance is now at around 90%.

Performance is essentially identical now (within 1% of 1.9.0).
2021-04-20 14:47:22 +02:00

409 lines
9.9 KiB
JavaScript

// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => {
const core = window.Deno.core;
function requiredArguments(name, length, required) {
if (length < required) {
const errMsg = `${name} requires at least ${required} argument${
required === 1 ? "" : "s"
}, but only ${length} present`;
throw new TypeError(errMsg);
}
}
const paramLists = new WeakMap();
const urls = new WeakMap();
class URLSearchParams {
#params = [];
constructor(init = "") {
if (typeof init === "string") {
// Overload: USVString
// If init is a string and starts with U+003F (?),
// remove the first code point from init.
if (init[0] == "?") {
init = init.slice(1);
}
this.#params = core.opSync("op_url_parse_search_params", init);
} else if (
Array.isArray(init) ||
typeof init?.[Symbol.iterator] == "function"
) {
// Overload: sequence<sequence<USVString>>
for (const pair of init) {
// If pair does not contain exactly two items, then throw a TypeError.
if (pair.length !== 2) {
throw new TypeError(
"URLSearchParams.constructor sequence argument must only contain pair elements",
);
}
this.#params.push([String(pair[0]), String(pair[1])]);
}
} else if (Object(init) !== init) {
// pass
} else if (init instanceof URLSearchParams) {
this.#params = [...init.#params];
} else {
// Overload: record<USVString, USVString>
for (const key of Object.keys(init)) {
this.#params.push([key, String(init[key])]);
}
}
paramLists.set(this, this.#params);
urls.set(this, null);
}
#updateUrlSearch = () => {
const url = urls.get(this);
if (url == null) {
return;
}
const parseArgs = { href: url.href, setSearch: this.toString() };
parts.set(url, core.opSync("op_url_parse", parseArgs));
};
append(name, value) {
requiredArguments("URLSearchParams.append", arguments.length, 2);
this.#params.push([String(name), String(value)]);
this.#updateUrlSearch();
}
delete(name) {
requiredArguments("URLSearchParams.delete", arguments.length, 1);
name = String(name);
let i = 0;
while (i < this.#params.length) {
if (this.#params[i][0] === name) {
this.#params.splice(i, 1);
} else {
i++;
}
}
this.#updateUrlSearch();
}
getAll(name) {
requiredArguments("URLSearchParams.getAll", arguments.length, 1);
name = String(name);
const values = [];
for (const entry of this.#params) {
if (entry[0] === name) {
values.push(entry[1]);
}
}
return values;
}
get(name) {
requiredArguments("URLSearchParams.get", arguments.length, 1);
name = String(name);
for (const entry of this.#params) {
if (entry[0] === name) {
return entry[1];
}
}
return null;
}
has(name) {
requiredArguments("URLSearchParams.has", arguments.length, 1);
name = String(name);
return this.#params.some((entry) => entry[0] === name);
}
set(name, value) {
requiredArguments("URLSearchParams.set", arguments.length, 2);
// If there are any name-value pairs whose name is name, in list,
// set the value of the first such name-value pair to value
// and remove the others.
name = String(name);
value = String(value);
let found = false;
let i = 0;
while (i < this.#params.length) {
if (this.#params[i][0] === name) {
if (!found) {
this.#params[i][1] = value;
found = true;
i++;
} else {
this.#params.splice(i, 1);
}
} else {
i++;
}
}
// Otherwise, append a new name-value pair whose name is name
// and value is value, to list.
if (!found) {
this.#params.push([String(name), String(value)]);
}
this.#updateUrlSearch();
}
sort() {
this.#params.sort((a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1));
this.#updateUrlSearch();
}
forEach(callbackfn, thisArg) {
requiredArguments("URLSearchParams.forEach", arguments.length, 1);
if (typeof thisArg !== "undefined") {
callbackfn = callbackfn.bind(thisArg);
}
for (const [key, value] of this.#params) {
callbackfn(value, key, this);
}
}
*keys() {
for (const [key] of this.#params) {
yield key;
}
}
*values() {
for (const [, value] of this.#params) {
yield value;
}
}
*entries() {
yield* this.#params;
}
*[Symbol.iterator]() {
yield* this.#params;
}
toString() {
return core.opSync("op_url_stringify_search_params", this.#params);
}
}
const parts = new WeakMap();
class URL {
#searchParams = null;
constructor(url, base) {
new.target;
if (url instanceof URL && base === undefined) {
parts.set(this, parts.get(url));
} else {
base = base !== undefined ? String(base) : base;
const parseArgs = { href: String(url), baseHref: base };
parts.set(this, core.opSync("op_url_parse", parseArgs));
}
}
[Symbol.for("Deno.customInspect")](inspect) {
const object = {
href: this.href,
origin: this.origin,
protocol: this.protocol,
username: this.username,
password: this.password,
host: this.host,
hostname: this.hostname,
port: this.port,
pathname: this.pathname,
hash: this.hash,
search: this.search,
};
return `${this.constructor.name} ${inspect(object)}`;
}
#updateSearchParams = () => {
if (this.#searchParams != null) {
const params = paramLists.get(this.#searchParams);
const newParams = core.opSync(
"op_url_parse_search_params",
this.search.slice(1),
);
params.splice(0, params.length, ...newParams);
}
};
get hash() {
return parts.get(this).hash;
}
set hash(value) {
try {
const parseArgs = { href: this.href, setHash: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get host() {
return parts.get(this).host;
}
set host(value) {
try {
const parseArgs = { href: this.href, setHost: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get hostname() {
return parts.get(this).hostname;
}
set hostname(value) {
try {
const parseArgs = { href: this.href, setHostname: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get href() {
return parts.get(this).href;
}
set href(value) {
try {
const parseArgs = { href: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
throw new TypeError("Invalid URL");
}
this.#updateSearchParams();
}
get origin() {
return parts.get(this).origin;
}
get password() {
return parts.get(this).password;
}
set password(value) {
try {
const parseArgs = { href: this.href, setPassword: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get pathname() {
return parts.get(this).pathname;
}
set pathname(value) {
try {
const parseArgs = { href: this.href, setPathname: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get port() {
return parts.get(this).port;
}
set port(value) {
try {
const parseArgs = { href: this.href, setPort: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get protocol() {
return parts.get(this).protocol;
}
set protocol(value) {
try {
const parseArgs = { href: this.href, setProtocol: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get search() {
return parts.get(this).search;
}
set search(value) {
try {
const parseArgs = { href: this.href, setSearch: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
this.#updateSearchParams();
} catch {
/* pass */
}
}
get username() {
return parts.get(this).username;
}
set username(value) {
try {
const parseArgs = { href: this.href, setUsername: String(value) };
parts.set(this, core.opSync("op_url_parse", parseArgs));
} catch {
/* pass */
}
}
get searchParams() {
if (this.#searchParams == null) {
this.#searchParams = new URLSearchParams(this.search);
urls.set(this.#searchParams, this);
}
return this.#searchParams;
}
toString() {
return this.href;
}
toJSON() {
return this.href;
}
}
/**
* This function implements application/x-www-form-urlencoded parsing.
* https://url.spec.whatwg.org/#concept-urlencoded-parser
* @param {Uint8Array} bytes
* @returns {[string, string][]}
*/
function parseUrlEncoded(bytes) {
return core.opSync("op_url_parse_search_params", null, bytes);
}
window.__bootstrap.url = {
URL,
URLSearchParams,
parseUrlEncoded,
};
})(this);