From afe89e8850d9ca2adcf73c8d2d63f5054a594878 Mon Sep 17 00:00:00 2001 From: Leo K Date: Mon, 21 Jun 2021 12:15:08 +0200 Subject: [PATCH] fix(websocket): spec conformance & WPT (#11010) --- extensions/webidl/00_webidl.js | 4 + extensions/websocket/01_websocket.js | 320 +++++++++++++++++---------- tools/wpt/config.json | 7 +- tools/wpt/expectation.json | 185 ++++++++++++++++ 4 files changed, 400 insertions(+), 116 deletions(-) diff --git a/extensions/webidl/00_webidl.js b/extensions/webidl/00_webidl.js index e6cda92d4c..87e9eccb7f 100644 --- a/extensions/webidl/00_webidl.js +++ b/extensions/webidl/00_webidl.js @@ -579,6 +579,10 @@ converters.ByteString, ); + converters["sequence"] = createSequenceConverter( + converters.DOMString, + ); + function requiredArguments(length, required, opts = {}) { if (length < required) { const errMsg = `${ diff --git a/extensions/websocket/01_websocket.js b/extensions/websocket/01_websocket.js index 3c3e12f83e..225c6725ba 100644 --- a/extensions/websocket/01_websocket.js +++ b/extensions/websocket/01_websocket.js @@ -3,28 +3,41 @@ ((window) => { const core = window.Deno.core; - - // provided by "deno_web" const { URL } = window.__bootstrap.url; + const webidl = window.__bootstrap.webidl; + const { HTTP_TOKEN_CODE_POINT_RE } = window.__bootstrap.infra; + + webidl.converters["sequence or DOMString"] = (V, opts) => { + // Union for (sequence or DOMString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[Symbol.iterator] !== undefined) { + return webidl.converters["sequence"](V, opts); + } + } + return webidl.converters.DOMString(V, opts); + }; + + webidl.converters["WebSocketSend"] = (V, opts) => { + // Union for (Blob or ArrayBufferView or ArrayBuffer or USVString) + if (V instanceof Blob) { + return webidl.converters["Blob"](V, opts); + } + if (typeof V === "object") { + if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) { + return webidl.converters["ArrayBuffer"](V, opts); + } + if (ArrayBuffer.isView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); + } + } + return webidl.converters["USVString"](V, opts); + }; const CONNECTING = 0; const OPEN = 1; const CLOSING = 2; const CLOSED = 3; - 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); - } - } - /** * Tries to close the resource (and ignores BadResource errors). * @param {number} rid @@ -74,14 +87,104 @@ }); } + const _readyState = Symbol("[[readyState]]"); + const _url = Symbol("[[url]]"); + const _rid = Symbol("[[rid]]"); + const _extensions = Symbol("[[extensions]]"); + const _protocol = Symbol("[[protocol]]"); + const _binaryType = Symbol("[[binaryType]]"); + const _bufferedAmount = Symbol("[[bufferedAmount]]"); class WebSocket extends EventTarget { - #readyState = CONNECTING; + [_rid]; + + [_readyState] = CONNECTING; + get readyState() { + webidl.assertBranded(this, WebSocket); + return this[_readyState]; + } + + get CONNECTING() { + webidl.assertBranded(this, WebSocket); + return CONNECTING; + } + get OPEN() { + webidl.assertBranded(this, WebSocket); + return OPEN; + } + get CLOSING() { + webidl.assertBranded(this, WebSocket); + return CLOSING; + } + get CLOSED() { + webidl.assertBranded(this, WebSocket); + return CLOSED; + } + + [_extensions] = ""; + get extensions() { + webidl.assertBranded(this, WebSocket); + return this[_extensions]; + } + + [_protocol] = ""; + get protocol() { + webidl.assertBranded(this, WebSocket); + return this[_protocol]; + } + + [_url] = ""; + get url() { + webidl.assertBranded(this, WebSocket); + return this[_url]; + } + + [_binaryType] = "blob"; + get binaryType() { + webidl.assertBranded(this, WebSocket); + return this[_binaryType]; + } + set binaryType(value) { + webidl.assertBranded(this, WebSocket); + value = webidl.converters.DOMString(value, { + prefix: "Failed to set 'binaryType' on 'WebSocket'", + }); + if (value === "blob" || value === "arraybuffer") { + this[_binaryType] = value; + } + } + + [_bufferedAmount] = 0; + get bufferedAmount() { + webidl.assertBranded(this, WebSocket); + return this[_bufferedAmount]; + } constructor(url, protocols = []) { super(); - requiredArguments("WebSocket", arguments.length, 1); + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'WebSocket'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + url = webidl.converters.USVString(url, { + prefix, + context: "Argument 1", + }); + protocols = webidl.converters["sequence or DOMString"]( + protocols, + { + prefix, + context: "Argument 2", + }, + ); - const wsURL = new URL(url); + let wsURL; + + try { + wsURL = new URL(url); + } catch (e) { + throw new DOMException(e.message, "SyntaxError"); + } if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { throw new DOMException( @@ -97,16 +200,16 @@ ); } - this.#url = wsURL.href; + this[_url] = wsURL.href; - core.opSync("op_ws_check_permission", this.#url); + core.opSync("op_ws_check_permission", this[_url]); - if (protocols && typeof protocols === "string") { + if (typeof protocols === "string") { protocols = [protocols]; } if ( - protocols.some((x) => protocols.indexOf(x) !== protocols.lastIndexOf(x)) + protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size ) { throw new DOMException( "Can't supply multiple times the same protocol.", @@ -114,19 +217,28 @@ ); } + if ( + protocols.some((protocol) => !HTTP_TOKEN_CODE_POINT_RE.test(protocol)) + ) { + throw new DOMException( + "Invalid protocol value.", + "SyntaxError", + ); + } + core.opAsync("op_ws_create", { url: wsURL.href, protocols: protocols.join(", "), }).then((create) => { - this.#rid = create.rid; - this.#extensions = create.extensions; - this.#protocol = create.protocol; + this[_rid] = create.rid; + this[_extensions] = create.extensions; + this[_protocol] = create.protocol; - if (this.#readyState === CLOSING) { + if (this[_readyState] === CLOSING) { core.opAsync("op_ws_close", { - rid: this.#rid, + rid: this[_rid], }).then(() => { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const errEvent = new ErrorEvent("error"); errEvent.target = this; @@ -135,10 +247,10 @@ const event = new CloseEvent("close"); event.target = this; this.dispatchEvent(event); - tryClose(this.#rid); + tryClose(this[_rid]); }); } else { - this.#readyState = OPEN; + this[_readyState] = OPEN; const event = new Event("open"); event.target = this; this.dispatchEvent(event); @@ -146,7 +258,7 @@ this.#eventLoop(); } }).catch((err) => { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const errorEv = new ErrorEvent( "error", @@ -161,67 +273,29 @@ }); } - get CONNECTING() { - return CONNECTING; - } - get OPEN() { - return OPEN; - } - get CLOSING() { - return CLOSING; - } - get CLOSED() { - return CLOSED; - } - - get readyState() { - return this.#readyState; - } - - #extensions = ""; - #protocol = ""; - #url = ""; - #rid; - - get extensions() { - return this.#extensions; - } - get protocol() { - return this.#protocol; - } - - #binaryType = "blob"; - get binaryType() { - return this.#binaryType; - } - set binaryType(value) { - if (value === "blob" || value === "arraybuffer") { - this.#binaryType = value; - } - } - #bufferedAmount = 0; - get bufferedAmount() { - return this.#bufferedAmount; - } - - get url() { - return this.#url; - } - send(data) { - requiredArguments("WebSocket.send", arguments.length, 1); + webidl.assertBranded(this, WebSocket); + const prefix = "Failed to execute 'send' on 'WebSocket'"; - if (this.#readyState != OPEN) { - throw Error("readyState not OPEN"); + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + data = webidl.converters.WebSocketSend(data, { + prefix, + context: "Argument 1", + }); + + if (this[_readyState] !== OPEN) { + throw new DOMException("readyState not OPEN", "InvalidStateError"); } const sendTypedArray = (ta) => { - this.#bufferedAmount += ta.size; + this[_bufferedAmount] += ta.byteLength; core.opAsync("op_ws_send", { - rid: this.#rid, + rid: this[_rid], kind: "binary", }, ta).then(() => { - this.#bufferedAmount -= ta.size; + this[_bufferedAmount] -= ta.byteLength; }); }; @@ -229,80 +303,94 @@ data.slice().arrayBuffer().then((ab) => sendTypedArray(new DataView(ab)) ); - } else if ( - data instanceof Int8Array || data instanceof Int16Array || - data instanceof Int32Array || data instanceof Uint8Array || - data instanceof Uint16Array || data instanceof Uint32Array || - data instanceof Uint8ClampedArray || data instanceof Float32Array || - data instanceof Float64Array || data instanceof DataView - ) { + } else if (ArrayBuffer.isView(data)) { sendTypedArray(data); } else if (data instanceof ArrayBuffer) { sendTypedArray(new DataView(data)); } else { const string = String(data); const d = core.encode(string); - this.#bufferedAmount += d.size; + this[_bufferedAmount] += d.byteLength; core.opAsync("op_ws_send", { - rid: this.#rid, + rid: this[_rid], kind: "text", text: string, }).then(() => { - this.#bufferedAmount -= d.size; + this[_bufferedAmount] -= d.byteLength; }); } } - close(code, reason) { - if (code && !(code === 1000 || (3000 <= code && code < 5000))) { + close(code = undefined, reason = undefined) { + webidl.assertBranded(this, WebSocket); + const prefix = "Failed to execute 'close' on 'WebSocket'"; + + if (code !== undefined) { + code = webidl.converters["unsigned short"](code, { + prefix, + clamp: true, + context: "Argument 1", + }); + } + + if (reason !== undefined) { + reason = webidl.converters.USVString(reason, { + prefix, + context: "Argument 2", + }); + } + + if ( + code !== undefined && !(code === 1000 || (3000 <= code && code < 5000)) + ) { throw new DOMException( "The close code must be either 1000 or in the range of 3000 to 4999.", - "NotSupportedError", + "InvalidAccessError", ); } - if (reason && core.encode(reason).byteLength > 123) { + if (reason !== undefined && core.encode(reason).byteLength > 123) { throw new DOMException( "The close reason may not be longer than 123 bytes.", "SyntaxError", ); } - if (this.#readyState === CONNECTING) { - this.#readyState = CLOSING; - } else if (this.#readyState === OPEN) { - this.#readyState = CLOSING; + if (this[_readyState] === CONNECTING) { + this[_readyState] = CLOSING; + } else if (this[_readyState] === OPEN) { + this[_readyState] = CLOSING; core.opAsync("op_ws_close", { - rid: this.#rid, + rid: this[_rid], code, reason, }).then(() => { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const event = new CloseEvent("close", { wasClean: true, - code, + code: code ?? 1005, reason, }); event.target = this; this.dispatchEvent(event); - tryClose(this.#rid); + tryClose(this[_rid]); }); } } async #eventLoop() { - while (this.#readyState === OPEN) { + while (this[_readyState] === OPEN) { const { kind, value } = await core.opAsync( "op_ws_next_event", - this.#rid, + this[_rid], ); switch (kind) { case "string": { const event = new MessageEvent("message", { data: value, - origin: this.#url, + origin: this[_url], }); event.target = this; this.dispatchEvent(event); @@ -319,7 +407,7 @@ const event = new MessageEvent("message", { data, - origin: this.#url, + origin: this[_url], }); event.target = this; this.dispatchEvent(event); @@ -327,13 +415,13 @@ } case "ping": { core.opAsync("op_ws_send", { - rid: this.#rid, + rid: this[_rid], kind: "pong", }); break; } case "close": { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const event = new CloseEvent("close", { wasClean: true, @@ -342,11 +430,11 @@ }); event.target = this; this.dispatchEvent(event); - tryClose(this.#rid); + tryClose(this[_rid]); break; } case "error": { - this.#readyState = CLOSED; + this[_readyState] = CLOSED; const errorEv = new ErrorEvent("error", { message: value, @@ -357,7 +445,7 @@ const closeEv = new CloseEvent("close"); closeEv.target = this; this.dispatchEvent(closeEv); - tryClose(this.#rid); + tryClose(this[_rid]); break; } } @@ -385,5 +473,7 @@ defineEventHandler(WebSocket.prototype, "close"); defineEventHandler(WebSocket.prototype, "open"); + webidl.configurePrototype(WebSocket); + window.__bootstrap.webSocket = { WebSocket }; })(this); diff --git a/tools/wpt/config.json b/tools/wpt/config.json index f146607c16..3bcd6612c4 100644 --- a/tools/wpt/config.json +++ b/tools/wpt/config.json @@ -1,6 +1,11 @@ { "check_subdomains": false, "ssl": { - "type": "pregenerated" + "type": "pregenerated", + "pregenerated": { + "ca_cert_path": "../../tools/wpt/certs/cacert.pem", + "host_cert_path": "../../tools/wpt/certs/web-platform.test.pem", + "host_key_path": "../../tools/wpt/certs/web-platform.test.key" + } } } diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 0001f92d30..962c12de6d 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -1023,5 +1023,190 @@ "set-blob.any.html": true, "set.any.html": true } + }, + "websockets": { + "Close-1000-reason.any.html": true, + "Close-1000-reason.any.html?wpt_flags=h2": false, + "Close-1000-reason.any.html?wss": true, + "Close-1000-verify-code.any.html": true, + "Close-1000-verify-code.any.html?wpt_flags=h2": false, + "Close-1000-verify-code.any.html?wss": true, + "Close-1000.any.html": true, + "Close-1000.any.html?wpt_flags=h2": false, + "Close-1000.any.html?wss": true, + "Close-1005-verify-code.any.html": true, + "Close-1005-verify-code.any.html?wpt_flags=h2": false, + "Close-1005-verify-code.any.html?wss": true, + "Close-1005.any.html": true, + "Close-1005.any.html?wpt_flags=h2": false, + "Close-1005.any.html?wss": true, + "Close-2999-reason.any.html": true, + "Close-2999-reason.any.html?wpt_flags=h2": false, + "Close-2999-reason.any.html?wss": true, + "Close-3000-reason.any.html": true, + "Close-3000-reason.any.html?wpt_flags=h2": false, + "Close-3000-reason.any.html?wss": true, + "Close-3000-verify-code.any.html": true, + "Close-3000-verify-code.any.html?wpt_flags=h2": false, + "Close-3000-verify-code.any.html?wss": true, + "Close-4999-reason.any.html": true, + "Close-4999-reason.any.html?wpt_flags=h2": false, + "Close-4999-reason.any.html?wss": true, + "Close-Reason-124Bytes.any.html": true, + "Close-Reason-124Bytes.any.html?wpt_flags=h2": false, + "Close-Reason-124Bytes.any.html?wss": true, + "Close-onlyReason.any.html": true, + "Close-onlyReason.any.html?wpt_flags=h2": false, + "Close-onlyReason.any.html?wss": true, + "Close-readyState-Closed.any.html": true, + "Close-readyState-Closed.any.html?wpt_flags=h2": false, + "Close-readyState-Closed.any.html?wss": true, + "Close-readyState-Closing.any.html": true, + "Close-readyState-Closing.any.html?wpt_flags=h2": false, + "Close-readyState-Closing.any.html?wss": true, + "Close-reason-unpaired-surrogates.any.html": true, + "Close-reason-unpaired-surrogates.any.html?wpt_flags=h2": false, + "Close-reason-unpaired-surrogates.any.html?wss": true, + "Close-server-initiated-close.any.html": true, + "Close-server-initiated-close.any.html?wpt_flags=h2": false, + "Close-server-initiated-close.any.html?wss": true, + "Close-undefined.any.html": true, + "Close-undefined.any.html?wpt_flags=h2": false, + "Close-undefined.any.html?wss": true, + "Create-asciiSep-protocol-string.any.html": true, + "Create-asciiSep-protocol-string.any.html?wpt_flags=h2": true, + "Create-asciiSep-protocol-string.any.html?wss": true, + "Create-blocked-port.any.html": true, + "Create-blocked-port.any.html?wpt_flags=h2": [ + "Basic check" + ], + "Create-blocked-port.any.html?wss": true, + "Create-extensions-empty.any.html": true, + "Create-extensions-empty.any.html?wpt_flags=h2": false, + "Create-extensions-empty.any.html?wss": true, + "Create-invalid-urls.any.html": true, + "Create-invalid-urls.any.html?wpt_flags=h2": true, + "Create-invalid-urls.any.html?wss": true, + "Create-non-absolute-url.any.html": true, + "Create-non-absolute-url.any.html?wpt_flags=h2": true, + "Create-non-absolute-url.any.html?wss": true, + "Create-nonAscii-protocol-string.any.html": true, + "Create-nonAscii-protocol-string.any.html?wpt_flags=h2": true, + "Create-nonAscii-protocol-string.any.html?wss": true, + "Create-on-worker-shutdown.any.html": false, + "Create-protocol-with-space.any.html": true, + "Create-protocol-with-space.any.html?wpt_flags=h2": true, + "Create-protocol-with-space.any.html?wss": true, + "Create-protocols-repeated-case-insensitive.any.html": true, + "Create-protocols-repeated-case-insensitive.any.html?wpt_flags=h2": true, + "Create-protocols-repeated-case-insensitive.any.html?wss": true, + "Create-protocols-repeated.any.html": true, + "Create-protocols-repeated.any.html?wpt_flags=h2": true, + "Create-protocols-repeated.any.html?wss": true, + "Create-url-with-space.any.html": true, + "Create-url-with-space.any.html?wpt_flags=h2": true, + "Create-url-with-space.any.html?wss": true, + "Create-valid-url-array-protocols.any.html": true, + "Create-valid-url-array-protocols.any.html?wpt_flags=h2": false, + "Create-valid-url-array-protocols.any.html?wss": true, + "Create-valid-url-binaryType-blob.any.html": true, + "Create-valid-url-binaryType-blob.any.html?wpt_flags=h2": false, + "Create-valid-url-binaryType-blob.any.html?wss": true, + "Create-valid-url-protocol-empty.any.html": true, + "Create-valid-url-protocol-empty.any.html?wpt_flags=h2": true, + "Create-valid-url-protocol-empty.any.html?wss": true, + "Create-valid-url-protocol-setCorrectly.any.html": true, + "Create-valid-url-protocol-setCorrectly.any.html?wpt_flags=h2": false, + "Create-valid-url-protocol-setCorrectly.any.html?wss": true, + "Create-valid-url-protocol-string.any.html": true, + "Create-valid-url-protocol-string.any.html?wpt_flags=h2": false, + "Create-valid-url-protocol-string.any.html?wss": true, + "Create-valid-url-protocol.any.html": true, + "Create-valid-url-protocol.any.html?wpt_flags=h2": false, + "Create-valid-url-protocol.any.html?wss": true, + "Create-valid-url.any.html": true, + "Create-valid-url.any.html?wpt_flags=h2": false, + "Create-valid-url.any.html?wss": true, + "Create-wrong-scheme.any.html": true, + "Create-wrong-scheme.any.html?wpt_flags=h2": true, + "Create-wrong-scheme.any.html?wss": true, + "Send-0byte-data.any.html": true, + "Send-0byte-data.any.html?wpt_flags=h2": false, + "Send-0byte-data.any.html?wss": true, + "Send-65K-data.any.html": true, + "Send-65K-data.any.html?wpt_flags=h2": false, + "Send-65K-data.any.html?wss": true, + "Send-before-open.any.html": true, + "Send-before-open.any.html?wpt_flags=h2": true, + "Send-before-open.any.html?wss": true, + "Send-binary-65K-arraybuffer.any.html": true, + "Send-binary-65K-arraybuffer.any.html?wpt_flags=h2": false, + "Send-binary-65K-arraybuffer.any.html?wss": true, + "Send-binary-arraybuffer.any.html": true, + "Send-binary-arraybuffer.any.html?wpt_flags=h2": false, + "Send-binary-arraybuffer.any.html?wss": true, + "Send-binary-arraybufferview-float32.any.html": true, + "Send-binary-arraybufferview-float32.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-float32.any.html?wss": true, + "Send-binary-arraybufferview-float64.any.html": true, + "Send-binary-arraybufferview-float64.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-float64.any.html?wss": true, + "Send-binary-arraybufferview-int16-offset.any.html": true, + "Send-binary-arraybufferview-int16-offset.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-int16-offset.any.html?wss": true, + "Send-binary-arraybufferview-int32.any.html": true, + "Send-binary-arraybufferview-int32.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-int32.any.html?wss": true, + "Send-binary-arraybufferview-int8.any.html": true, + "Send-binary-arraybufferview-int8.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-int8.any.html?wss": true, + "Send-binary-arraybufferview-uint16-offset-length.any.html": true, + "Send-binary-arraybufferview-uint16-offset-length.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-uint16-offset-length.any.html?wss": true, + "Send-binary-arraybufferview-uint32-offset.any.html": true, + "Send-binary-arraybufferview-uint32-offset.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-uint32-offset.any.html?wss": true, + "Send-binary-arraybufferview-uint8-offset-length.any.html": true, + "Send-binary-arraybufferview-uint8-offset-length.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-uint8-offset-length.any.html?wss": true, + "Send-binary-arraybufferview-uint8-offset.any.html": true, + "Send-binary-arraybufferview-uint8-offset.any.html?wpt_flags=h2": false, + "Send-binary-arraybufferview-uint8-offset.any.html?wss": true, + "Send-binary-blob.any.html": true, + "Send-binary-blob.any.html?wpt_flags=h2": false, + "Send-binary-blob.any.html?wss": true, + "Send-data.any.html": true, + "Send-data.any.html?wpt_flags=h2": false, + "Send-data.any.html?wss": true, + "Send-null.any.html": true, + "Send-null.any.html?wpt_flags=h2": false, + "Send-null.any.html?wss": true, + "Send-paired-surrogates.any.html": true, + "Send-paired-surrogates.any.html?wpt_flags=h2": false, + "Send-paired-surrogates.any.html?wss": true, + "Send-unicode-data.any.html": true, + "Send-unicode-data.any.html?wpt_flags=h2": false, + "Send-unicode-data.any.html?wss": true, + "Send-unpaired-surrogates.any.html": true, + "Send-unpaired-surrogates.any.html?wpt_flags=h2": false, + "Send-unpaired-surrogates.any.html?wss": true, + "basic-auth.any.html?wpt_flags=h2": false, + "basic-auth.any.html?wss": false, + "binaryType-wrong-value.any.html": true, + "binaryType-wrong-value.any.html?wpt_flags=h2": false, + "binaryType-wrong-value.any.html?wss": true, + "bufferedAmount-unchanged-by-sync-xhr.any.html": false, + "bufferedAmount-unchanged-by-sync-xhr.any.html?wpt_flags=h2": false, + "bufferedAmount-unchanged-by-sync-xhr.any.html?wss": false, + "close-invalid.any.html": true, + "close-invalid.any.html?wpt_flags=h2": true, + "close-invalid.any.html?wss": true, + "constructor.any.html": true, + "constructor.any.html?wpt_flags=h2": true, + "constructor.any.html?wss": true, + "eventhandlers.any.html": false, + "eventhandlers.any.html?wpt_flags=h2": false, + "eventhandlers.any.html?wss": false, + "referrer.any.html": true } }