mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
chore: align Headers
to spec (#10199)
This commit aligns `Headers` to spec. It also removes the now unused 03_dom_iterable.js file. We now pass all relevant `Headers` WPT. We do not implement any sort of header filtering, as we are a server side runtime. This is likely not the most efficient implementation of `Headers` yet. It is however spec compliant. Once all the APIs in the `HTTP` hot loop are correct we can start optimizing them. It is likely that this commit reduces bench throughput temporarily.
This commit is contained in:
parent
0c5ecec8f6
commit
0552eaf569
14 changed files with 552 additions and 514 deletions
|
@ -1,88 +0,0 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { assert, assertEquals, unitTest } from "./test_util.ts";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
function setup() {
|
||||
const dataSymbol = Symbol("data symbol");
|
||||
class Base {
|
||||
[dataSymbol] = new Map<string, number>();
|
||||
|
||||
constructor(
|
||||
data: Array<[string, number]> | IterableIterator<[string, number]>,
|
||||
) {
|
||||
for (const [key, value] of data) {
|
||||
this[dataSymbol].set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
Base,
|
||||
// This is using an internal API we don't want published as types, so having
|
||||
// to cast to any to "trick" TypeScript
|
||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
||||
DomIterable: Deno[Deno.internal].DomIterableMixin(Base, dataSymbol),
|
||||
};
|
||||
}
|
||||
|
||||
unitTest(function testDomIterable(): void {
|
||||
const { DomIterable, Base } = setup();
|
||||
|
||||
const fixture: Array<[string, number]> = [
|
||||
["foo", 1],
|
||||
["bar", 2],
|
||||
];
|
||||
|
||||
const domIterable = new DomIterable(fixture);
|
||||
|
||||
assertEquals(Array.from(domIterable.entries()), fixture);
|
||||
assertEquals(Array.from(domIterable.values()), [1, 2]);
|
||||
assertEquals(Array.from(domIterable.keys()), ["foo", "bar"]);
|
||||
|
||||
let result: Array<[string, number]> = [];
|
||||
for (const [key, value] of domIterable) {
|
||||
assert(key != null);
|
||||
assert(value != null);
|
||||
result.push([key, value]);
|
||||
}
|
||||
assertEquals(fixture, result);
|
||||
|
||||
result = [];
|
||||
const scope = {};
|
||||
function callback(
|
||||
this: typeof scope,
|
||||
value: number,
|
||||
key: string,
|
||||
parent: typeof domIterable,
|
||||
): void {
|
||||
assertEquals(parent, domIterable);
|
||||
assert(key != null);
|
||||
assert(value != null);
|
||||
assert(this === scope);
|
||||
result.push([key, value]);
|
||||
}
|
||||
domIterable.forEach(callback, scope);
|
||||
assertEquals(fixture, result);
|
||||
|
||||
assertEquals(DomIterable.name, Base.name);
|
||||
});
|
||||
|
||||
unitTest(function testDomIterableScope(): void {
|
||||
const { DomIterable } = setup();
|
||||
|
||||
const domIterable = new DomIterable([["foo", 1]]);
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
function checkScope(thisArg: any, expected: any): void {
|
||||
function callback(this: typeof thisArg): void {
|
||||
assertEquals(this, expected);
|
||||
}
|
||||
domIterable.forEach(callback, thisArg);
|
||||
}
|
||||
|
||||
checkScope(0, Object(0));
|
||||
checkScope("", Object(""));
|
||||
checkScope(null, window);
|
||||
checkScope(undefined, window);
|
||||
});
|
|
@ -661,8 +661,8 @@ unitTest(
|
|||
const actual = new TextDecoder().decode(buf.bytes());
|
||||
const expected = [
|
||||
"POST /blah HTTP/1.1\r\n",
|
||||
"hello: World\r\n",
|
||||
"foo: Bar\r\n",
|
||||
"hello: World\r\n",
|
||||
"accept: */*\r\n",
|
||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||
"accept-encoding: gzip, br\r\n",
|
||||
|
@ -695,9 +695,9 @@ unitTest(
|
|||
const actual = new TextDecoder().decode(buf.bytes());
|
||||
const expected = [
|
||||
"POST /blah HTTP/1.1\r\n",
|
||||
"hello: World\r\n",
|
||||
"foo: Bar\r\n",
|
||||
"content-type: text/plain;charset=UTF-8\r\n",
|
||||
"foo: Bar\r\n",
|
||||
"hello: World\r\n",
|
||||
"accept: */*\r\n",
|
||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||
"accept-encoding: gzip, br\r\n",
|
||||
|
@ -733,8 +733,8 @@ unitTest(
|
|||
const actual = new TextDecoder().decode(buf.bytes());
|
||||
const expected = [
|
||||
"POST /blah HTTP/1.1\r\n",
|
||||
"hello: World\r\n",
|
||||
"foo: Bar\r\n",
|
||||
"hello: World\r\n",
|
||||
"accept: */*\r\n",
|
||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||
"accept-encoding: gzip, br\r\n",
|
||||
|
@ -1115,8 +1115,8 @@ unitTest(
|
|||
const actual = new TextDecoder().decode(buf.bytes());
|
||||
const expected = [
|
||||
"POST /blah HTTP/1.1\r\n",
|
||||
"hello: World\r\n",
|
||||
"foo: Bar\r\n",
|
||||
"hello: World\r\n",
|
||||
"accept: */*\r\n",
|
||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||
"accept-encoding: gzip, br\r\n",
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
import {
|
||||
assert,
|
||||
assertEquals,
|
||||
assertStringIncludes,
|
||||
unitTest,
|
||||
} from "./test_util.ts";
|
||||
import { assert, assertEquals, unitTest } from "./test_util.ts";
|
||||
const {
|
||||
inspectArgs,
|
||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
||||
|
@ -25,10 +20,7 @@ unitTest(function newHeaderTest(): void {
|
|||
// deno-lint-ignore no-explicit-any
|
||||
new Headers(null as any);
|
||||
} catch (e) {
|
||||
assertEquals(
|
||||
e.message,
|
||||
"Failed to construct 'Headers'; The provided value was not valid",
|
||||
);
|
||||
assert(e instanceof TypeError);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -271,13 +263,11 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
|||
methodRequireOneParam.forEach((method): void => {
|
||||
const headers = new Headers();
|
||||
let hasThrown = 0;
|
||||
let errMsg = "";
|
||||
try {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
(headers as any)[method]();
|
||||
hasThrown = 1;
|
||||
} catch (err) {
|
||||
errMsg = err.message;
|
||||
if (err instanceof TypeError) {
|
||||
hasThrown = 2;
|
||||
} else {
|
||||
|
@ -285,23 +275,17 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
|||
}
|
||||
}
|
||||
assertEquals(hasThrown, 2);
|
||||
assertStringIncludes(
|
||||
errMsg,
|
||||
`${method} requires at least 1 argument, but only 0 present`,
|
||||
);
|
||||
});
|
||||
|
||||
methodRequireTwoParams.forEach((method): void => {
|
||||
const headers = new Headers();
|
||||
let hasThrown = 0;
|
||||
let errMsg = "";
|
||||
|
||||
try {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
(headers as any)[method]();
|
||||
hasThrown = 1;
|
||||
} catch (err) {
|
||||
errMsg = err.message;
|
||||
if (err instanceof TypeError) {
|
||||
hasThrown = 2;
|
||||
} else {
|
||||
|
@ -309,19 +293,13 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
|||
}
|
||||
}
|
||||
assertEquals(hasThrown, 2);
|
||||
assertStringIncludes(
|
||||
errMsg,
|
||||
`${method} requires at least 2 arguments, but only 0 present`,
|
||||
);
|
||||
|
||||
hasThrown = 0;
|
||||
errMsg = "";
|
||||
try {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
(headers as any)[method]("foo");
|
||||
hasThrown = 1;
|
||||
} catch (err) {
|
||||
errMsg = err.message;
|
||||
if (err instanceof TypeError) {
|
||||
hasThrown = 2;
|
||||
} else {
|
||||
|
@ -329,10 +307,6 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
|||
}
|
||||
}
|
||||
assertEquals(hasThrown, 2);
|
||||
assertStringIncludes(
|
||||
errMsg,
|
||||
`${method} requires at least 2 arguments, but only 1 present`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -361,8 +335,8 @@ unitTest(function headersAppendMultiple(): void {
|
|||
const actual = [...headers];
|
||||
assertEquals(actual, [
|
||||
["set-cookie", "foo=bar"],
|
||||
["x-deno", "foo, bar"],
|
||||
["set-cookie", "bar=baz"],
|
||||
["x-deno", "foo, bar"],
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -372,22 +346,12 @@ unitTest(function headersAppendDuplicateSetCookieKey(): void {
|
|||
headers.append("Set-cookie", "baz=bar");
|
||||
const actual = [...headers];
|
||||
assertEquals(actual, [
|
||||
["set-cookie", "foo=bar"],
|
||||
["set-cookie", "foo=baz"],
|
||||
["set-cookie", "baz=bar"],
|
||||
]);
|
||||
});
|
||||
|
||||
unitTest(function headersSetDuplicateCookieKey(): void {
|
||||
const headers = new Headers([["Set-Cookie", "foo=bar"]]);
|
||||
headers.set("set-Cookie", "foo=baz");
|
||||
headers.set("set-cookie", "bar=qat");
|
||||
const actual = [...headers];
|
||||
assertEquals(actual, [
|
||||
["set-cookie", "foo=baz"],
|
||||
["set-cookie", "bar=qat"],
|
||||
]);
|
||||
});
|
||||
|
||||
unitTest(function headersGetSetCookie(): void {
|
||||
const headers = new Headers([
|
||||
["Set-Cookie", "foo=bar"],
|
||||
|
@ -411,7 +375,7 @@ unitTest(function customInspectReturnsCorrectHeadersFormat(): void {
|
|||
const singleHeader = new Headers([["Content-Type", "application/json"]]);
|
||||
assertEquals(
|
||||
stringify(singleHeader),
|
||||
"Headers { content-type: application/json }",
|
||||
`Headers { "content-type": "application/json" }`,
|
||||
);
|
||||
const multiParamHeader = new Headers([
|
||||
["Content-Type", "application/json"],
|
||||
|
@ -419,6 +383,6 @@ unitTest(function customInspectReturnsCorrectHeadersFormat(): void {
|
|||
]);
|
||||
assertEquals(
|
||||
stringify(multiParamHeader),
|
||||
"Headers { content-type: application/json, content-length: 1337 }",
|
||||
`Headers { "content-length": "1337", "content-type": "application/json" }`,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -35,7 +35,6 @@ import "./io_test.ts";
|
|||
import "./link_test.ts";
|
||||
import "./make_temp_test.ts";
|
||||
import "./metrics_test.ts";
|
||||
import "./dom_iterable_test.ts";
|
||||
import "./mkdir_test.ts";
|
||||
import "./net_test.ts";
|
||||
import "./os_test.ts";
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const { requiredArguments } = window.__bootstrap.fetchUtil;
|
||||
|
||||
function DomIterableMixin(
|
||||
Base,
|
||||
dataSymbol,
|
||||
) {
|
||||
// we have to cast `this` as `any` because there is no way to describe the
|
||||
// Base class in a way where the Symbol `dataSymbol` is defined. So the
|
||||
// runtime code works, but we do lose a little bit of type safety.
|
||||
|
||||
// Additionally, we have to not use .keys() nor .values() since the internal
|
||||
// slot differs in type - some have a Map, which yields [K, V] in
|
||||
// Symbol.iterator, and some have an Array, which yields V, in this case
|
||||
// [K, V] too as they are arrays of tuples.
|
||||
|
||||
const DomIterable = class extends Base {
|
||||
*entries() {
|
||||
for (const entry of this[dataSymbol]) {
|
||||
yield entry;
|
||||
}
|
||||
}
|
||||
|
||||
*keys() {
|
||||
for (const [key] of this[dataSymbol]) {
|
||||
yield key;
|
||||
}
|
||||
}
|
||||
|
||||
*values() {
|
||||
for (const [, value] of this[dataSymbol]) {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
|
||||
forEach(
|
||||
callbackfn,
|
||||
thisArg,
|
||||
) {
|
||||
requiredArguments(
|
||||
`${this.constructor.name}.forEach`,
|
||||
arguments.length,
|
||||
1,
|
||||
);
|
||||
callbackfn = callbackfn.bind(
|
||||
thisArg == null ? globalThis : Object(thisArg),
|
||||
);
|
||||
for (const [key, value] of this[dataSymbol]) {
|
||||
callbackfn(value, key, this);
|
||||
}
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
for (const entry of this[dataSymbol]) {
|
||||
yield entry;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// we want the Base class name to be the name of the class.
|
||||
Object.defineProperty(DomIterable, "name", {
|
||||
value: Base.name,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
return DomIterable;
|
||||
}
|
||||
|
||||
window.__bootstrap.internals = {
|
||||
...window.__bootstrap.internals ?? {},
|
||||
DomIterableMixin,
|
||||
};
|
||||
|
||||
window.__bootstrap.domIterable = {
|
||||
DomIterableMixin,
|
||||
};
|
||||
})(this);
|
|
@ -1,247 +1,327 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// @ts-check
|
||||
/// <reference path="../webidl/internal.d.ts" />
|
||||
/// <reference path="../web/internal.d.ts" />
|
||||
/// <reference path="../file/internal.d.ts" />
|
||||
/// <reference path="../file/lib.deno_file.d.ts" />
|
||||
/// <reference path="./internal.d.ts" />
|
||||
/// <reference path="./11_streams_types.d.ts" />
|
||||
/// <reference path="./lib.deno_fetch.d.ts" />
|
||||
/// <reference lib="esnext" />
|
||||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const { DomIterableMixin } = window.__bootstrap.domIterable;
|
||||
const { requiredArguments } = window.__bootstrap.fetchUtil;
|
||||
const webidl = window.__bootstrap.webidl;
|
||||
const {
|
||||
HTTP_WHITESPACE_PREFIX_RE,
|
||||
HTTP_WHITESPACE_SUFFIX_RE,
|
||||
HTTP_TOKEN_CODE_POINT_RE,
|
||||
byteLowerCase,
|
||||
} = window.__bootstrap.infra;
|
||||
|
||||
// From node-fetch
|
||||
// Copyright (c) 2016 David Frank. MIT License.
|
||||
const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/;
|
||||
const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
|
||||
const _headerList = Symbol("header list");
|
||||
const _iterableHeaders = Symbol("iterable headers");
|
||||
const _guard = Symbol("guard");
|
||||
|
||||
function isHeaders(value) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
return value instanceof Headers;
|
||||
/**
|
||||
* @typedef Header
|
||||
* @type {[string, string]}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef HeaderList
|
||||
* @type {Header[]}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {string} potentialValue
|
||||
* @returns {string}
|
||||
*/
|
||||
function normalizeHeaderValue(potentialValue) {
|
||||
potentialValue = potentialValue.replaceAll(HTTP_WHITESPACE_PREFIX_RE, "");
|
||||
potentialValue = potentialValue.replaceAll(HTTP_WHITESPACE_SUFFIX_RE, "");
|
||||
return potentialValue;
|
||||
}
|
||||
|
||||
const headersData = Symbol("headers data");
|
||||
|
||||
// TODO(bartlomieju): headerGuard? Investigate if it is needed
|
||||
// node-fetch did not implement this but it is in the spec
|
||||
function normalizeParams(name, value) {
|
||||
name = String(name).toLowerCase();
|
||||
value = String(value).trim();
|
||||
return [name, value];
|
||||
}
|
||||
|
||||
// The following name/value validations are copied from
|
||||
// https://github.com/bitinn/node-fetch/blob/master/src/headers.js
|
||||
// Copyright (c) 2016 David Frank. MIT License.
|
||||
function validateName(name) {
|
||||
if (invalidTokenRegex.test(name) || name === "") {
|
||||
throw new TypeError(`${name} is not a legal HTTP header name`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateValue(value) {
|
||||
if (invalidHeaderCharRegex.test(value)) {
|
||||
throw new TypeError(`${value} is not a legal HTTP header value`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Appends a key and value to the header list.
|
||||
*
|
||||
* The spec indicates that when a key already exists, the append adds the new
|
||||
* value onto the end of the existing value. The behaviour of this though
|
||||
* varies when the key is `set-cookie`. In this case, if the key of the cookie
|
||||
* already exists, the value is replaced, but if the key of the cookie does not
|
||||
* exist, and additional `set-cookie` header is added.
|
||||
*
|
||||
* The browser specification of `Headers` is written for clients, and not
|
||||
* servers, and Deno is a server, meaning that it needs to follow the patterns
|
||||
* expected for servers, of which a `set-cookie` header is expected for each
|
||||
* unique cookie key, but duplicate cookie keys should not exist. */
|
||||
function dataAppend(
|
||||
data,
|
||||
key,
|
||||
value,
|
||||
) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const [dataKey] = data[i];
|
||||
if (key === "set-cookie" && dataKey === "set-cookie") {
|
||||
const [, dataValue] = data[i];
|
||||
const [dataCookieKey] = dataValue.split("=");
|
||||
const [cookieKey] = value.split("=");
|
||||
if (dataCookieKey === cookieKey) {
|
||||
data[i][1] = value;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (dataKey === key) {
|
||||
data[i][1] += `, ${value}`;
|
||||
return;
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @param {HeadersInit} object
|
||||
*/
|
||||
function fillHeaders(headers, object) {
|
||||
if (Array.isArray(object)) {
|
||||
for (const header of object) {
|
||||
if (header.length !== 2) {
|
||||
throw new TypeError(
|
||||
`Invalid header. Length must be 2, but is ${header.length}`,
|
||||
);
|
||||
}
|
||||
appendHeader(headers, header[0], header[1]);
|
||||
}
|
||||
} else {
|
||||
for (const key of Object.keys(object)) {
|
||||
appendHeader(headers, key, object[key]);
|
||||
}
|
||||
}
|
||||
data.push([key, value]);
|
||||
}
|
||||
|
||||
/** Gets a value of a key in the headers list.
|
||||
*
|
||||
* This varies slightly from spec behaviour in that when the key is `set-cookie`
|
||||
* the value returned will look like a concatenated value, when in fact, if the
|
||||
* headers were iterated over, each individual `set-cookie` value is a unique
|
||||
* entry in the headers list. */
|
||||
function dataGet(
|
||||
data,
|
||||
key,
|
||||
) {
|
||||
const setCookieValues = [];
|
||||
for (const [dataKey, value] of data) {
|
||||
if (dataKey === key) {
|
||||
if (key === "set-cookie") {
|
||||
setCookieValues.push(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* https://fetch.spec.whatwg.org/#concept-headers-append
|
||||
* @param {Headers} headers
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
*/
|
||||
function appendHeader(headers, name, value) {
|
||||
// 1.
|
||||
value = normalizeHeaderValue(value);
|
||||
|
||||
// 2.
|
||||
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||
throw new TypeError("Header name is not valid.");
|
||||
}
|
||||
if (
|
||||
value.includes("\x00") || value.includes("\x0A") || value.includes("\x0D")
|
||||
) {
|
||||
throw new TypeError("Header value is not valid.");
|
||||
}
|
||||
|
||||
// 3.
|
||||
if (headers[_guard] == "immutable") {
|
||||
throw new TypeError("Headers are immutable.");
|
||||
}
|
||||
|
||||
// 7.
|
||||
const list = headers[_headerList];
|
||||
const lowercaseName = byteLowerCase(name);
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||
name = list[i][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (setCookieValues.length) {
|
||||
return setCookieValues.join(", ");
|
||||
}
|
||||
return undefined;
|
||||
list.push([name, value]);
|
||||
}
|
||||
|
||||
/** Sets a value of a key in the headers list.
|
||||
*
|
||||
* The spec indicates that the value should be replaced if the key already
|
||||
* exists. The behaviour here varies, where if the key is `set-cookie` the key
|
||||
* of the cookie is inspected, and if the key of the cookie already exists,
|
||||
* then the value is replaced. If the key of the cookie is not found, then
|
||||
* the value of the `set-cookie` is added to the list of headers.
|
||||
*
|
||||
* The browser specification of `Headers` is written for clients, and not
|
||||
* servers, and Deno is a server, meaning that it needs to follow the patterns
|
||||
* expected for servers, of which a `set-cookie` header is expected for each
|
||||
* unique cookie key, but duplicate cookie keys should not exist. */
|
||||
function dataSet(
|
||||
data,
|
||||
key,
|
||||
value,
|
||||
) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const [dataKey] = data[i];
|
||||
if (dataKey === key) {
|
||||
// there could be multiple set-cookie headers, but all others are unique
|
||||
if (key === "set-cookie") {
|
||||
const [, dataValue] = data[i];
|
||||
const [dataCookieKey] = dataValue.split("=");
|
||||
const [cookieKey] = value.split("=");
|
||||
if (cookieKey === dataCookieKey) {
|
||||
data[i][1] = value;
|
||||
return;
|
||||
/**
|
||||
* @param {HeaderList} list
|
||||
* @param {string} name
|
||||
*/
|
||||
function getHeader(list, name) {
|
||||
const lowercaseName = byteLowerCase(name);
|
||||
const entries = list.filter((entry) =>
|
||||
byteLowerCase(entry[0]) === lowercaseName
|
||||
).map((entry) => entry[1]);
|
||||
if (entries.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return entries.join("\x2C\x20");
|
||||
}
|
||||
}
|
||||
|
||||
class Headers {
|
||||
/** @type {HeaderList} */
|
||||
[_headerList] = [];
|
||||
/** @type {"immutable"| "request"| "request-no-cors"| "response" | "none"} */
|
||||
[_guard];
|
||||
|
||||
get [_iterableHeaders]() {
|
||||
const list = this[_headerList];
|
||||
|
||||
const headers = [];
|
||||
const headerNamesSet = new Set();
|
||||
for (const entry of list) {
|
||||
headerNamesSet.add(byteLowerCase(entry[0]));
|
||||
}
|
||||
const names = [...headerNamesSet].sort();
|
||||
for (const name of names) {
|
||||
// The following if statement, and if block of the following statement
|
||||
// are not spec compliant. `set-cookie` is the only header that can not
|
||||
// be concatentated, so must be given to the user as multiple headers.
|
||||
// The else block of the if statement is spec compliant again.
|
||||
if (name == "set-cookie") {
|
||||
const setCookie = list.filter((entry) =>
|
||||
byteLowerCase(entry[0]) === "set-cookie"
|
||||
);
|
||||
if (setCookie.length === 0) throw new TypeError("Unreachable");
|
||||
for (const entry of setCookie) {
|
||||
headers.push([name, entry[1]]);
|
||||
}
|
||||
} else {
|
||||
data[i][1] = value;
|
||||
return;
|
||||
const value = getHeader(list, name);
|
||||
if (value === null) throw new TypeError("Unreachable");
|
||||
headers.push([name, value]);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
data.push([key, value]);
|
||||
}
|
||||
|
||||
function dataDelete(data, key) {
|
||||
let i = 0;
|
||||
while (i < data.length) {
|
||||
const [dataKey] = data[i];
|
||||
if (dataKey === key) {
|
||||
data.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
/** @param {HeadersInit} [init] */
|
||||
constructor(init = undefined) {
|
||||
const prefix = "Failed to construct 'Event'";
|
||||
if (init !== undefined) {
|
||||
init = webidl.converters["HeadersInit"](init, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dataHas(data, key) {
|
||||
for (const [dataKey] of data) {
|
||||
if (dataKey === key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ref: https://fetch.spec.whatwg.org/#dom-headers
|
||||
class HeadersBase {
|
||||
constructor(init) {
|
||||
if (init === null) {
|
||||
throw new TypeError(
|
||||
"Failed to construct 'Headers'; The provided value was not valid",
|
||||
);
|
||||
} else if (isHeaders(init)) {
|
||||
this[headersData] = [...init];
|
||||
} else {
|
||||
this[headersData] = [];
|
||||
if (Array.isArray(init)) {
|
||||
for (const tuple of init) {
|
||||
// If header does not contain exactly two items,
|
||||
// then throw a TypeError.
|
||||
// ref: https://fetch.spec.whatwg.org/#concept-headers-fill
|
||||
requiredArguments(
|
||||
"Headers.constructor tuple array argument",
|
||||
tuple.length,
|
||||
2,
|
||||
);
|
||||
|
||||
this.append(tuple[0], tuple[1]);
|
||||
}
|
||||
} else if (init) {
|
||||
for (const [rawName, rawValue] of Object.entries(init)) {
|
||||
this.append(rawName, rawValue);
|
||||
}
|
||||
}
|
||||
this[webidl.brand] = webidl.brand;
|
||||
this[_guard] = "none";
|
||||
if (init !== undefined) {
|
||||
fillHeaders(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.for("Deno.customInspect")]() {
|
||||
let length = this[headersData].length;
|
||||
let output = "";
|
||||
for (const [key, value] of this[headersData]) {
|
||||
const prefix = length === this[headersData].length ? " " : "";
|
||||
const postfix = length === 1 ? " " : ", ";
|
||||
output = output + `${prefix}${key}: ${value}${postfix}`;
|
||||
length--;
|
||||
}
|
||||
return `Headers {${output}}`;
|
||||
}
|
||||
|
||||
// ref: https://fetch.spec.whatwg.org/#concept-headers-append
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
*/
|
||||
append(name, value) {
|
||||
requiredArguments("Headers.append", arguments.length, 2);
|
||||
const [newname, newvalue] = normalizeParams(name, value);
|
||||
validateName(newname);
|
||||
validateValue(newvalue);
|
||||
dataAppend(this[headersData], newname, newvalue);
|
||||
webidl.assertBranded(this, Headers);
|
||||
const prefix = "Failed to execute 'append' on 'Headers'";
|
||||
webidl.requiredArguments(arguments.length, 2, { prefix });
|
||||
name = webidl.converters["ByteString"](name, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
value = webidl.converters["ByteString"](value, {
|
||||
prefix,
|
||||
context: "Argument 2",
|
||||
});
|
||||
appendHeader(this, name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
delete(name) {
|
||||
requiredArguments("Headers.delete", arguments.length, 1);
|
||||
const [newname] = normalizeParams(name);
|
||||
validateName(newname);
|
||||
dataDelete(this[headersData], newname);
|
||||
const prefix = "Failed to execute 'delete' on 'Headers'";
|
||||
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||
name = webidl.converters["ByteString"](name, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
|
||||
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||
throw new TypeError("Header name is not valid.");
|
||||
}
|
||||
if (this[_guard] == "immutable") {
|
||||
throw new TypeError("Headers are immutable.");
|
||||
}
|
||||
|
||||
const list = this[_headerList];
|
||||
const lowercaseName = byteLowerCase(name);
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||
list.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
get(name) {
|
||||
requiredArguments("Headers.get", arguments.length, 1);
|
||||
const [newname] = normalizeParams(name);
|
||||
validateName(newname);
|
||||
return dataGet(this[headersData], newname) ?? null;
|
||||
const prefix = "Failed to execute 'get' on 'Headers'";
|
||||
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||
name = webidl.converters["ByteString"](name, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
|
||||
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||
throw new TypeError("Header name is not valid.");
|
||||
}
|
||||
|
||||
const list = this[_headerList];
|
||||
return getHeader(list, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
has(name) {
|
||||
requiredArguments("Headers.has", arguments.length, 1);
|
||||
const [newname] = normalizeParams(name);
|
||||
validateName(newname);
|
||||
return dataHas(this[headersData], newname);
|
||||
const prefix = "Failed to execute 'has' on 'Headers'";
|
||||
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||
name = webidl.converters["ByteString"](name, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
|
||||
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||
throw new TypeError("Header name is not valid.");
|
||||
}
|
||||
|
||||
const list = this[_headerList];
|
||||
const lowercaseName = byteLowerCase(name);
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
*/
|
||||
set(name, value) {
|
||||
requiredArguments("Headers.set", arguments.length, 2);
|
||||
const [newName, newValue] = normalizeParams(name, value);
|
||||
validateName(newName);
|
||||
validateValue(newValue);
|
||||
dataSet(this[headersData], newName, newValue);
|
||||
webidl.assertBranded(this, Headers);
|
||||
const prefix = "Failed to execute 'set' on 'Headers'";
|
||||
webidl.requiredArguments(arguments.length, 2, { prefix });
|
||||
name = webidl.converters["ByteString"](name, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
value = webidl.converters["ByteString"](value, {
|
||||
prefix,
|
||||
context: "Argument 2",
|
||||
});
|
||||
|
||||
value = normalizeHeaderValue(value);
|
||||
|
||||
// 2.
|
||||
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||
throw new TypeError("Header name is not valid.");
|
||||
}
|
||||
if (
|
||||
value.includes("\x00") || value.includes("\x0A") ||
|
||||
value.includes("\x0D")
|
||||
) {
|
||||
throw new TypeError("Header value is not valid.");
|
||||
}
|
||||
|
||||
if (this[_guard] == "immutable") {
|
||||
throw new TypeError("Headers are immutable.");
|
||||
}
|
||||
|
||||
const list = this[_headerList];
|
||||
const lowercaseName = byteLowerCase(name);
|
||||
let added = false;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||
if (!added) {
|
||||
list[i][1] = value;
|
||||
added = true;
|
||||
} else {
|
||||
list.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
list.push([name, value]);
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.for("Deno.customInspect")](inspect) {
|
||||
const headers = {};
|
||||
for (const header of this) {
|
||||
headers[header[0]] = header[1];
|
||||
}
|
||||
return `Headers ${inspect(headers)}`;
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
|
@ -249,7 +329,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
class Headers extends DomIterableMixin(HeadersBase, headersData) {}
|
||||
webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1);
|
||||
|
||||
webidl.converters["sequence<ByteString>"] = webidl
|
||||
.createSequenceConverter(webidl.converters["ByteString"]);
|
||||
webidl.converters["sequence<sequence<ByteString>>"] = webidl
|
||||
.createSequenceConverter(webidl.converters["sequence<ByteString>"]);
|
||||
webidl.converters["record<ByteString, ByteString>"] = webidl
|
||||
.createRecordConverter(
|
||||
webidl.converters["ByteString"],
|
||||
webidl.converters["ByteString"],
|
||||
);
|
||||
webidl.converters["HeadersInit"] = (V, opts) => {
|
||||
// Union for (sequence<sequence<ByteString>> or record<ByteString, ByteString>)
|
||||
if (typeof V === "object" && V !== null) {
|
||||
if (V[Symbol.iterator] !== undefined) {
|
||||
return webidl.converters["sequence<sequence<ByteString>>"](V, opts);
|
||||
}
|
||||
return webidl.converters["record<ByteString, ByteString>"](V, opts);
|
||||
}
|
||||
throw webidl.makeException(
|
||||
TypeError,
|
||||
"The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'",
|
||||
opts,
|
||||
);
|
||||
};
|
||||
webidl.converters["Headers"] = webidl.createInterfaceConverter(
|
||||
"Headers",
|
||||
Headers,
|
||||
);
|
||||
|
||||
window.__bootstrap.headers = {
|
||||
Headers,
|
||||
|
|
|
@ -58,10 +58,6 @@ pub fn init(isolate: &mut JsRuntime) {
|
|||
"deno:op_crates/fetch/01_fetch_util.js",
|
||||
include_str!("01_fetch_util.js"),
|
||||
),
|
||||
(
|
||||
"deno:op_crates/fetch/03_dom_iterable.js",
|
||||
include_str!("03_dom_iterable.js"),
|
||||
),
|
||||
(
|
||||
"deno:op_crates/fetch/11_streams.js",
|
||||
include_str!("11_streams.js"),
|
||||
|
|
|
@ -8,6 +8,74 @@
|
|||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const ASCII_DIGIT = ["\u0030-\u0039"];
|
||||
const ASCII_UPPER_ALPHA = ["\u0041-\u005A"];
|
||||
const ASCII_LOWER_ALPHA = ["\u0061-\u007A"];
|
||||
const ASCII_ALPHA = [...ASCII_UPPER_ALPHA, ...ASCII_LOWER_ALPHA];
|
||||
const ASCII_ALPHANUMERIC = [...ASCII_DIGIT, ...ASCII_ALPHA];
|
||||
|
||||
const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"];
|
||||
const HTTP_WHITESPACE = ["\u000A", "\u000D", ...HTTP_TAB_OR_SPACE];
|
||||
|
||||
const HTTP_TOKEN_CODE_POINT = [
|
||||
"\u0021",
|
||||
"\u0023",
|
||||
"\u0024",
|
||||
"\u0025",
|
||||
"\u0026",
|
||||
"\u0027",
|
||||
"\u002A",
|
||||
"\u002B",
|
||||
"\u002D",
|
||||
"\u002E",
|
||||
"\u005E",
|
||||
"\u005F",
|
||||
"\u0060",
|
||||
"\u007C",
|
||||
"\u007E",
|
||||
...ASCII_ALPHANUMERIC,
|
||||
];
|
||||
const HTTP_TOKEN_CODE_POINT_RE = new RegExp(
|
||||
`^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
|
||||
);
|
||||
const HTTP_QUOTED_STRING_TOKEN_POINT = [
|
||||
"\u0009",
|
||||
"\u0020-\u007E",
|
||||
"\u0080-\u00FF",
|
||||
];
|
||||
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
|
||||
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
|
||||
);
|
||||
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
|
||||
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
|
||||
`^[${HTTP_WHITESPACE_MATCHER}]+`,
|
||||
"g",
|
||||
);
|
||||
const HTTP_WHITESPACE_SUFFIX_RE = new RegExp(
|
||||
`[${HTTP_WHITESPACE_MATCHER}]+$`,
|
||||
"g",
|
||||
);
|
||||
|
||||
/**
|
||||
* Turn a string of chars into a regex safe matcher.
|
||||
* @param {string[]} chars
|
||||
* @returns {string}
|
||||
*/
|
||||
function regexMatcher(chars) {
|
||||
const matchers = chars.map((char) => {
|
||||
if (char.length === 1) {
|
||||
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
||||
} else if (char.length === 3 && char[1] === "-") {
|
||||
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}-\\u${
|
||||
char.charCodeAt(2).toString(16).padStart(4, "0")
|
||||
}`;
|
||||
} else {
|
||||
throw TypeError("unreachable");
|
||||
}
|
||||
});
|
||||
return matchers.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
||||
* @param {string} input
|
||||
|
@ -25,7 +93,43 @@
|
|||
return { result: input.slice(start, position), position };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @returns {string}
|
||||
*/
|
||||
function byteUpperCase(s) {
|
||||
return String(s).replace(/[a-z]/g, function byteUpperCaseReplace(c) {
|
||||
return c.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @returns {string}
|
||||
*/
|
||||
function byteLowerCase(s) {
|
||||
return String(s).replace(/[A-Z]/g, function byteUpperCaseReplace(c) {
|
||||
return c.toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
window.__bootstrap.infra = {
|
||||
collectSequenceOfCodepoints,
|
||||
ASCII_DIGIT,
|
||||
ASCII_UPPER_ALPHA,
|
||||
ASCII_LOWER_ALPHA,
|
||||
ASCII_ALPHA,
|
||||
ASCII_ALPHANUMERIC,
|
||||
HTTP_TAB_OR_SPACE,
|
||||
HTTP_WHITESPACE,
|
||||
HTTP_TOKEN_CODE_POINT,
|
||||
HTTP_TOKEN_CODE_POINT_RE,
|
||||
HTTP_QUOTED_STRING_TOKEN_POINT,
|
||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||
HTTP_WHITESPACE_PREFIX_RE,
|
||||
HTTP_WHITESPACE_SUFFIX_RE,
|
||||
regexMatcher,
|
||||
byteUpperCase,
|
||||
byteLowerCase,
|
||||
};
|
||||
})(globalThis);
|
||||
|
|
|
@ -8,72 +8,14 @@
|
|||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const { collectSequenceOfCodepoints } = window.__bootstrap.infra;
|
||||
|
||||
/**
|
||||
* @param {string[]} chars
|
||||
* @returns {string}
|
||||
*/
|
||||
function regexMatcher(chars) {
|
||||
const matchers = chars.map((char) => {
|
||||
if (char.length === 1) {
|
||||
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
||||
} else if (char.length === 3 && char[1] === "-") {
|
||||
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}-\\u${
|
||||
char.charCodeAt(2).toString(16).padStart(4, "0")
|
||||
}`;
|
||||
} else {
|
||||
throw TypeError("unreachable");
|
||||
}
|
||||
});
|
||||
return matchers.join("");
|
||||
}
|
||||
|
||||
const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"];
|
||||
const HTTP_WHITESPACE = ["\u000A", "\u000D", ...HTTP_TAB_OR_SPACE];
|
||||
|
||||
const ASCII_DIGIT = ["\u0030-\u0039"];
|
||||
const ASCII_UPPER_ALPHA = ["\u0041-\u005A"];
|
||||
const ASCII_LOWER_ALPHA = ["\u0061-\u007A"];
|
||||
const ASCII_ALPHA = [...ASCII_UPPER_ALPHA, ...ASCII_LOWER_ALPHA];
|
||||
const ASCII_ALPHANUMERIC = [...ASCII_DIGIT, ...ASCII_ALPHA];
|
||||
const HTTP_TOKEN_CODE_POINT = [
|
||||
"\u0021",
|
||||
"\u0023",
|
||||
"\u0025",
|
||||
"\u0026",
|
||||
"\u0027",
|
||||
"\u002A",
|
||||
"\u002B",
|
||||
"\u002D",
|
||||
"\u002E",
|
||||
"\u005E",
|
||||
"\u005F",
|
||||
"\u0060",
|
||||
"\u007C",
|
||||
"\u007E",
|
||||
...ASCII_ALPHANUMERIC,
|
||||
];
|
||||
const HTTP_TOKEN_CODE_POINT_RE = new RegExp(
|
||||
`^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
|
||||
);
|
||||
const HTTP_QUOTED_STRING_TOKEN_POINT = [
|
||||
"\u0009",
|
||||
"\u0020-\u007E",
|
||||
"\u0080-\u00FF",
|
||||
];
|
||||
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
|
||||
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
|
||||
);
|
||||
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
|
||||
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
|
||||
`^[${HTTP_WHITESPACE_MATCHER}]+`,
|
||||
"g",
|
||||
);
|
||||
const HTTP_WHITESPACE_SUFFIX_RE = new RegExp(
|
||||
`[${HTTP_WHITESPACE_MATCHER}]+$`,
|
||||
"g",
|
||||
);
|
||||
const {
|
||||
collectSequenceOfCodepoints,
|
||||
HTTP_WHITESPACE,
|
||||
HTTP_WHITESPACE_PREFIX_RE,
|
||||
HTTP_WHITESPACE_SUFFIX_RE,
|
||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||
HTTP_TOKEN_CODE_POINT_RE,
|
||||
} = window.__bootstrap.infra;
|
||||
|
||||
/**
|
||||
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
|
||||
|
@ -131,8 +73,16 @@
|
|||
return { result: input.substring(positionStart, position + 1), position };
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef MimeType
|
||||
* @property {string} type
|
||||
* @property {string} subtype
|
||||
* @property {Map<string,string>} parameters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @returns {MimeType | null}
|
||||
*/
|
||||
function parseMimeType(input) {
|
||||
// 1.
|
||||
|
|
25
op_crates/web/internal.d.ts
vendored
25
op_crates/web/internal.d.ts
vendored
|
@ -17,15 +17,32 @@ declare namespace globalThis {
|
|||
result: string;
|
||||
position: number;
|
||||
};
|
||||
ASCII_DIGIT: string[];
|
||||
ASCII_UPPER_ALPHA: string[];
|
||||
ASCII_LOWER_ALPHA: string[];
|
||||
ASCII_ALPHA: string[];
|
||||
ASCII_ALPHANUMERIC: string[];
|
||||
HTTP_TAB_OR_SPACE: string[];
|
||||
HTTP_WHITESPACE: string[];
|
||||
HTTP_TOKEN_CODE_POINT: string[];
|
||||
HTTP_TOKEN_CODE_POINT_RE: RegExp;
|
||||
HTTP_QUOTED_STRING_TOKEN_POINT: string[];
|
||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp;
|
||||
HTTP_WHITESPACE_PREFIX_RE: RegExp;
|
||||
HTTP_WHITESPACE_SUFFIX_RE: RegExp;
|
||||
regexMatcher(chars: string[]): string;
|
||||
byteUpperCase(s: string): string;
|
||||
byteLowerCase(s: string): string;
|
||||
};
|
||||
|
||||
declare var mimesniff: {
|
||||
parseMimeType(input: string): {
|
||||
declare namespace mimesniff {
|
||||
declare interface MimeType {
|
||||
type: string;
|
||||
subtype: string;
|
||||
parameters: Map<string, string>;
|
||||
} | null;
|
||||
};
|
||||
}
|
||||
declare function parseMimeType(input: string): MimeType | null;
|
||||
}
|
||||
|
||||
declare var eventTarget: {
|
||||
EventTarget: typeof EventTarget;
|
||||
|
|
|
@ -764,12 +764,16 @@
|
|||
opts,
|
||||
);
|
||||
}
|
||||
const keys = Reflect.ownKeys(V);
|
||||
const result = {};
|
||||
for (const key of V) {
|
||||
const typedKey = keyConverter(key, opts);
|
||||
const value = V[key];
|
||||
const typedValue = valueConverter(value, opts);
|
||||
result[typedKey] = typedValue;
|
||||
for (const key of keys) {
|
||||
const desc = Object.getOwnPropertyDescriptor(V, key);
|
||||
if (desc !== undefined && desc.enumerable === true) {
|
||||
const typedKey = keyConverter(key, opts);
|
||||
const value = V[key];
|
||||
const typedValue = valueConverter(value, opts);
|
||||
result[typedKey] = typedValue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
@ -802,29 +806,81 @@
|
|||
throw new TypeError("Illegal constructor");
|
||||
}
|
||||
|
||||
function define(target, source) {
|
||||
for (const key of Reflect.ownKeys(source)) {
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(source, key);
|
||||
if (descriptor && !Reflect.defineProperty(target, key, descriptor)) {
|
||||
throw new TypeError(`Cannot redefine property: ${String(key)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _iteratorInternal = Symbol("iterator internal");
|
||||
|
||||
const globalIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(
|
||||
[][Symbol.iterator](),
|
||||
));
|
||||
|
||||
function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) {
|
||||
const methods = {
|
||||
*entries() {
|
||||
assertBranded(this, prototype);
|
||||
for (const entry of this[dataSymbol]) {
|
||||
yield [entry[keyKey], entry[valueKey]];
|
||||
const iteratorPrototype = Object.create(globalIteratorPrototype, {
|
||||
[Symbol.toStringTag]: { configurable: true, value: `${name} Iterator` },
|
||||
});
|
||||
define(iteratorPrototype, {
|
||||
next() {
|
||||
const internal = this && this[_iteratorInternal];
|
||||
if (!internal) {
|
||||
throw new TypeError(
|
||||
`next() called on a value that is not a ${name} iterator object`,
|
||||
);
|
||||
}
|
||||
const { target, kind, index } = internal;
|
||||
const values = target[dataSymbol];
|
||||
const len = values.length;
|
||||
if (index >= len) {
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
const pair = values[index];
|
||||
internal.index = index + 1;
|
||||
let result;
|
||||
switch (kind) {
|
||||
case "key":
|
||||
result = pair[keyKey];
|
||||
break;
|
||||
case "value":
|
||||
result = pair[valueKey];
|
||||
break;
|
||||
case "key+value":
|
||||
result = [pair[keyKey], pair[valueKey]];
|
||||
break;
|
||||
}
|
||||
return { value: result, done: false };
|
||||
},
|
||||
});
|
||||
function createDefaultIterator(target, kind) {
|
||||
const iterator = Object.create(iteratorPrototype);
|
||||
Object.defineProperty(iterator, _iteratorInternal, {
|
||||
value: { target, kind, index: 0 },
|
||||
configurable: true,
|
||||
});
|
||||
return iterator;
|
||||
}
|
||||
|
||||
const methods = {
|
||||
entries() {
|
||||
assertBranded(this, prototype);
|
||||
return createDefaultIterator(this, "key+value");
|
||||
},
|
||||
[Symbol.iterator]() {
|
||||
assertBranded(this, prototype);
|
||||
return this.entries();
|
||||
return createDefaultIterator(this, "key+value");
|
||||
},
|
||||
*keys() {
|
||||
keys() {
|
||||
assertBranded(this, prototype);
|
||||
for (const entry of this[dataSymbol]) {
|
||||
yield entry[keyKey];
|
||||
}
|
||||
return createDefaultIterator(this, "key");
|
||||
},
|
||||
*values() {
|
||||
values() {
|
||||
assertBranded(this, prototype);
|
||||
for (const entry of this[dataSymbol]) {
|
||||
yield entry[valueKey];
|
||||
}
|
||||
return createDefaultIterator(this, "value");
|
||||
},
|
||||
forEach(idlCallback, thisArg) {
|
||||
assertBranded(this, prototype);
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit e19bdbe96243f2ba548c1fd01c0812d645ba0c6f
|
||||
Subproject commit 579608584916d582d38d0159666aae9a6aaf07ad
|
|
@ -684,6 +684,15 @@
|
|||
"Check isReloadNavigation attribute",
|
||||
"Check isHistoryNavigation attribute"
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"headers-basic.any.js": true,
|
||||
"headers-casing.any.js": true,
|
||||
"headers-combine.any.js": true,
|
||||
"headers-errors.any.js": true,
|
||||
"headers-normalize.any.js": true,
|
||||
"headers-record.any.js": true,
|
||||
"headers-structure.any.js": true
|
||||
}
|
||||
},
|
||||
"data-urls": {
|
||||
|
|
|
@ -25,6 +25,9 @@ export async function runWithTestUtil<T>(
|
|||
}
|
||||
const passedTime = performance.now() - start;
|
||||
if (passedTime > 15000) {
|
||||
proc.kill(2);
|
||||
await proc.status();
|
||||
proc.close();
|
||||
throw new Error("Timed out while trying to start wpt test util.");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue