1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-28 16:20:57 -05:00

fix: webidl utils and align Event to spec (#9470)

This commit is contained in:
Luca Casonato 2021-02-13 15:58:12 +01:00 committed by GitHub
parent d2d7dc8d67
commit af460fc464
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1117 additions and 192 deletions

View file

@ -1,3 +1,3 @@
[WILDCARD]error: Uncaught TypeError: Event requires at least 1 argument, but only 0 present[WILDCARD] [WILDCARD]error: Uncaught TypeError: Failed to construct 'Event': 1 argument, but only 0 present.[WILDCARD]
at new Event (deno:op_crates/web/[WILDCARD]) at new Event (deno:op_crates/web/[WILDCARD])
at [WILDCARD] at [WILDCARD]

View file

@ -99,27 +99,27 @@ unitTest(function eventInspectOutput(): void {
[ [
new Event("test"), new Event("test"),
(event: Event) => (event: Event) =>
`Event {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n target: null,\n timeStamp: ${event.timeStamp},\n type: "test"\n}`, `Event {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "test"\n}`,
], ],
[ [
new ErrorEvent("error"), new ErrorEvent("error"),
(event: Event) => (event: Event) =>
`ErrorEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n target: null,\n timeStamp: ${event.timeStamp},\n type: "error",\n message: "",\n filename: "",\n lineno: 0,\n colno: 0,\n error: null\n}`, `ErrorEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "error",\n message: "",\n filename: "",\n lineno: 0,\n colno: 0,\n error: null\n}`,
], ],
[ [
new CloseEvent("close"), new CloseEvent("close"),
(event: Event) => (event: Event) =>
`CloseEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n target: null,\n timeStamp: ${event.timeStamp},\n type: "close",\n wasClean: false,\n code: 0,\n reason: ""\n}`, `CloseEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "close",\n wasClean: false,\n code: 0,\n reason: ""\n}`,
], ],
[ [
new CustomEvent("custom"), new CustomEvent("custom"),
(event: Event) => (event: Event) =>
`CustomEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n target: null,\n timeStamp: ${event.timeStamp},\n type: "custom",\n detail: undefined\n}`, `CustomEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "custom",\n detail: undefined\n}`,
], ],
[ [
new ProgressEvent("progress"), new ProgressEvent("progress"),
(event: Event) => (event: Event) =>
`ProgressEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n target: null,\n timeStamp: ${event.timeStamp},\n type: "progress",\n lengthComputable: false,\n loaded: 0,\n total: 0\n}`, `ProgressEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "progress",\n lengthComputable: false,\n loaded: 0,\n total: 0\n}`,
], ],
]; ];

671
op_crates/web/00_webidl.js Normal file
View file

@ -0,0 +1,671 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
// Adapted from https://github.com/jsdom/webidl-conversions.
// Copyright Domenic Denicola. Licensed under BSD-2-Clause License.
// Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md.
"use strict";
((window) => {
function makeException(ErrorType, message, opts = {}) {
if (opts.globals) {
ErrorType = opts.globals[ErrorType.name];
}
return new ErrorType(
`${opts.prefix ? opts.prefix + ": " : ""}${
opts.context ? opts.context : "Value"
} ${message}.`,
);
}
function toNumber(value, opts = {}) {
if (!opts.globals) {
return +value;
}
if (typeof value === "bigint") {
throw opts.globals.TypeError("Cannot convert a BigInt value to a number");
}
return opts.globals.Number(value);
}
function type(V) {
if (V === null) {
return "Null";
}
switch (typeof V) {
case "undefined":
return "Undefined";
case "boolean":
return "Boolean";
case "number":
return "Number";
case "string":
return "String";
case "symbol":
return "Symbol";
case "bigint":
return "BigInt";
case "object":
// Falls through
case "function":
// Falls through
default:
// Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for
// uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for
// such cases. So treat the default case as an object.
return "Object";
}
}
// Round x to the nearest integer, choosing the even integer if it lies halfway between two.
function evenRound(x) {
// There are four cases for numbers with fractional part being .5:
//
// case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example
// 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0
// 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2
// 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0
// 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2
// (where n is a non-negative integer)
//
// Branch here for cases 1 and 4
if (
(x > 0 && x % 1 === +0.5 && (x & 1) === 0) ||
(x < 0 && x % 1 === -0.5 && (x & 1) === 1)
) {
return censorNegativeZero(Math.floor(x));
}
return censorNegativeZero(Math.round(x));
}
function integerPart(n) {
return censorNegativeZero(Math.trunc(n));
}
function sign(x) {
return x < 0 ? -1 : 1;
}
function modulo(x, y) {
// https://tc39.github.io/ecma262/#eqn-modulo
// Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos
const signMightNotMatch = x % y;
if (sign(y) !== sign(signMightNotMatch)) {
return signMightNotMatch + y;
}
return signMightNotMatch;
}
function censorNegativeZero(x) {
return x === 0 ? 0 : x;
}
function createIntegerConversion(bitLength, typeOpts) {
const isSigned = !typeOpts.unsigned;
let lowerBound;
let upperBound;
if (bitLength === 64) {
upperBound = Number.MAX_SAFE_INTEGER;
lowerBound = !isSigned ? 0 : Number.MIN_SAFE_INTEGER;
} else if (!isSigned) {
lowerBound = 0;
upperBound = Math.pow(2, bitLength) - 1;
} else {
lowerBound = -Math.pow(2, bitLength - 1);
upperBound = Math.pow(2, bitLength - 1) - 1;
}
const twoToTheBitLength = Math.pow(2, bitLength);
const twoToOneLessThanTheBitLength = Math.pow(2, bitLength - 1);
return (V, opts = {}) => {
let x = toNumber(V, opts);
x = censorNegativeZero(x);
if (opts.enforceRange) {
if (!Number.isFinite(x)) {
throw makeException(TypeError, "is not a finite number", opts);
}
x = integerPart(x);
if (x < lowerBound || x > upperBound) {
throw makeException(
TypeError,
`is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`,
opts,
);
}
return x;
}
if (!Number.isNaN(x) && opts.clamp) {
x = Math.min(Math.max(x, lowerBound), upperBound);
x = evenRound(x);
return x;
}
if (!Number.isFinite(x) || x === 0) {
return 0;
}
x = integerPart(x);
// Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if
// possible. Hopefully it's an optimization for the non-64-bitLength cases too.
if (x >= lowerBound && x <= upperBound) {
return x;
}
// These will not work great for bitLength of 64, but oh well. See the README for more details.
x = modulo(x, twoToTheBitLength);
if (isSigned && x >= twoToOneLessThanTheBitLength) {
return x - twoToTheBitLength;
}
return x;
};
}
function createLongLongConversion(bitLength, { unsigned }) {
const upperBound = Number.MAX_SAFE_INTEGER;
const lowerBound = unsigned ? 0 : Number.MIN_SAFE_INTEGER;
const asBigIntN = unsigned ? BigInt.asUintN : BigInt.asIntN;
return (V, opts = {}) => {
let x = toNumber(V, opts);
x = censorNegativeZero(x);
if (opts.enforceRange) {
if (!Number.isFinite(x)) {
throw makeException(TypeError, "is not a finite number", opts);
}
x = integerPart(x);
if (x < lowerBound || x > upperBound) {
throw makeException(
TypeError,
`is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`,
opts,
);
}
return x;
}
if (!Number.isNaN(x) && opts.clamp) {
x = Math.min(Math.max(x, lowerBound), upperBound);
x = evenRound(x);
return x;
}
if (!Number.isFinite(x) || x === 0) {
return 0;
}
let xBigInt = BigInt(integerPart(x));
xBigInt = asBigIntN(bitLength, xBigInt);
return Number(xBigInt);
};
}
const converters = [];
converters.any = (V) => {
return V;
};
converters.boolean = function (val) {
return !!val;
};
converters.byte = createIntegerConversion(8, { unsigned: false });
converters.octet = createIntegerConversion(8, { unsigned: true });
converters.short = createIntegerConversion(16, { unsigned: false });
converters["unsigned short"] = createIntegerConversion(16, {
unsigned: true,
});
converters.long = createIntegerConversion(32, { unsigned: false });
converters["unsigned long"] = createIntegerConversion(32, { unsigned: true });
converters["long long"] = createLongLongConversion(64, { unsigned: false });
converters["unsigned long long"] = createLongLongConversion(64, {
unsigned: true,
});
converters.float = (V, opts) => {
const x = toNumber(V, opts);
if (!Number.isFinite(x)) {
throw makeException(
TypeError,
"is not a finite floating-point value",
opts,
);
}
if (Object.is(x, -0)) {
return x;
}
const y = Math.fround(x);
if (!Number.isFinite(y)) {
throw makeException(
TypeError,
"is outside the range of a single-precision floating-point value",
opts,
);
}
return y;
};
converters["unrestricted float"] = (V, opts) => {
const x = toNumber(V, opts);
if (isNaN(x)) {
return x;
}
if (Object.is(x, -0)) {
return x;
}
return Math.fround(x);
};
converters.double = (V, opts) => {
const x = toNumber(V, opts);
if (!Number.isFinite(x)) {
throw makeException(
TypeError,
"is not a finite floating-point value",
opts,
);
}
return x;
};
converters["unrestricted double"] = (V, opts) => {
const x = toNumber(V, opts);
return x;
};
converters.DOMString = function (V, opts = {}) {
if (opts.treatNullAsEmptyString && V === null) {
return "";
}
if (typeof V === "symbol") {
throw makeException(
TypeError,
"is a symbol, which cannot be converted to a string",
opts,
);
}
const StringCtor = opts.globals ? opts.globals.String : String;
return StringCtor(V);
};
converters.ByteString = (V, opts) => {
const x = converters.DOMString(V, opts);
let c;
for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) {
if (c > 255) {
throw makeException(TypeError, "is not a valid ByteString", opts);
}
}
return x;
};
converters.USVString = (V, opts) => {
const S = converters.DOMString(V, opts);
const n = S.length;
const U = [];
for (let i = 0; i < n; ++i) {
const c = S.charCodeAt(i);
if (c < 0xd800 || c > 0xdfff) {
U.push(String.fromCodePoint(c));
} else if (0xdc00 <= c && c <= 0xdfff) {
U.push(String.fromCodePoint(0xfffd));
} else if (i === n - 1) {
U.push(String.fromCodePoint(0xfffd));
} else {
const d = S.charCodeAt(i + 1);
if (0xdc00 <= d && d <= 0xdfff) {
const a = c & 0x3ff;
const b = d & 0x3ff;
U.push(String.fromCodePoint((2 << 15) + (2 << 9) * a + b));
++i;
} else {
U.push(String.fromCodePoint(0xfffd));
}
}
}
return U.join("");
};
converters.object = (V, opts) => {
if (type(V) !== "Object") {
throw makeException(TypeError, "is not an object", opts);
}
return V;
};
// Not exported, but used in Function and VoidFunction.
// Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so
// handling for that is omitted.
function convertCallbackFunction(V, opts) {
if (typeof V !== "function") {
throw makeException(TypeError, "is not a function", opts);
}
return V;
}
const abByteLengthGetter = Object.getOwnPropertyDescriptor(
ArrayBuffer.prototype,
"byteLength",
).get;
function isNonSharedArrayBuffer(V) {
try {
// This will throw on SharedArrayBuffers, but not detached ArrayBuffers.
// (The spec says it should throw, but the spec conflicts with implementations: https://github.com/tc39/ecma262/issues/678)
abByteLengthGetter.call(V);
return true;
} catch {
return false;
}
}
let sabByteLengthGetter;
function isSharedArrayBuffer(V) {
// TODO(lucacasonato): vulnerable to prototype pollution. Needs to happen
// here because SharedArrayBuffer is not available during snapshotting.
if (!sabByteLengthGetter) {
sabByteLengthGetter = Object.getOwnPropertyDescriptor(
SharedArrayBuffer.prototype,
"byteLength",
).get;
}
try {
sabByteLengthGetter.call(V);
return true;
} catch {
return false;
}
}
function isArrayBufferDetached(V) {
try {
// eslint-disable-next-line no-new
new Uint8Array(V);
return false;
} catch {
return true;
}
}
converters.ArrayBuffer = (V, opts = {}) => {
if (!isNonSharedArrayBuffer(V)) {
if (opts.allowShared && !isSharedArrayBuffer(V)) {
throw makeException(
TypeError,
"is not an ArrayBuffer or SharedArrayBuffer",
opts,
);
}
throw makeException(TypeError, "is not an ArrayBuffer", opts);
}
if (isArrayBufferDetached(V)) {
throw makeException(TypeError, "is a detached ArrayBuffer", opts);
}
return V;
};
const dvByteLengthGetter = Object.getOwnPropertyDescriptor(
DataView.prototype,
"byteLength",
).get;
converters.DataView = (V, opts = {}) => {
try {
dvByteLengthGetter.call(V);
} catch (e) {
throw makeException(TypeError, "is not a DataView", opts);
}
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
throw makeException(
TypeError,
"is backed by a SharedArrayBuffer, which is not allowed",
opts,
);
}
if (isArrayBufferDetached(V.buffer)) {
throw makeException(
TypeError,
"is backed by a detached ArrayBuffer",
opts,
);
}
return V;
};
// Returns the unforgeable `TypedArray` constructor name or `undefined`,
// if the `this` value isn't a valid `TypedArray` object.
//
// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag
const typedArrayNameGetter = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(Uint8Array).prototype,
Symbol.toStringTag,
).get;
[
Int8Array,
Int16Array,
Int32Array,
Uint8Array,
Uint16Array,
Uint32Array,
Uint8ClampedArray,
Float32Array,
Float64Array,
].forEach((func) => {
const name = func.name;
const article = /^[AEIOU]/.test(name) ? "an" : "a";
converters[name] = (V, opts = {}) => {
if (!ArrayBuffer.isView(V) || typedArrayNameGetter.call(V) !== name) {
throw makeException(
TypeError,
`is not ${article} ${name} object`,
opts,
);
}
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
throw makeException(
TypeError,
"is a view on a SharedArrayBuffer, which is not allowed",
opts,
);
}
if (isArrayBufferDetached(V.buffer)) {
throw makeException(
TypeError,
"is a view on a detached ArrayBuffer",
opts,
);
}
return V;
};
});
// Common definitions
converters.ArrayBufferView = (V, opts = {}) => {
if (!ArrayBuffer.isView(V)) {
throw makeException(
TypeError,
"is not a view on an ArrayBuffer or SharedArrayBuffer",
opts,
);
}
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
throw makeException(
TypeError,
"is a view on a SharedArrayBuffer, which is not allowed",
opts,
);
}
if (isArrayBufferDetached(V.buffer)) {
throw makeException(
TypeError,
"is a view on a detached ArrayBuffer",
opts,
);
}
return V;
};
converters.BufferSource = (V, opts = {}) => {
if (ArrayBuffer.isView(V)) {
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
throw makeException(
TypeError,
"is a view on a SharedArrayBuffer, which is not allowed",
opts,
);
}
if (isArrayBufferDetached(V.buffer)) {
throw makeException(
TypeError,
"is a view on a detached ArrayBuffer",
opts,
);
}
return V;
}
if (!opts.allowShared && !isNonSharedArrayBuffer(V)) {
throw makeException(
TypeError,
"is not an ArrayBuffer or a view on one",
opts,
);
}
if (
opts.allowShared &&
!isSharedArrayBuffer(V) &&
!isNonSharedArrayBuffer(V)
) {
throw makeException(
TypeError,
"is not an ArrayBuffer, SharedArrayBuffer, or a view on one",
opts,
);
}
if (isArrayBufferDetached(V)) {
throw makeException(TypeError, "is a detached ArrayBuffer", opts);
}
return V;
};
converters.DOMTimeStamp = converters["unsigned long long"];
converters.Function = convertCallbackFunction;
converters.VoidFunction = convertCallbackFunction;
function requiredArguments(length, required, opts = {}) {
if (length < required) {
const errMsg = `${
opts.prefix ? opts.prefix + ": " : ""
}${required} argument${
required === 1 ? "" : "s"
}, but only ${length} present.`;
throw new TypeError(errMsg);
}
}
function createDictionaryConverter(name, ...dictionaries) {
return function (V, opts = {}) {
const typeV = type(V);
switch (typeV) {
case "Undefined":
case "Null":
case "Object":
break;
default:
throw makeException(
TypeError,
"can not be converted to a dictionary",
opts,
);
}
const esDict = V;
const idlDict = {};
for (const members of dictionaries) {
for (const member of members) {
const key = member.key;
let esMemberValue;
if (typeV === "Undefined" || typeV === "Null") {
esMemberValue = undefined;
} else {
esMemberValue = esDict[key];
}
if (esMemberValue !== undefined) {
const converter = member.converter;
const idlMemberValue = converter(esMemberValue, {
...opts,
context: `${key} of '${name}'${
opts.context ? `(${opts.context})` : ""
}`,
});
idlDict[key] = idlMemberValue;
} else if ("defaultValue" in member) {
const defaultValue = member.defaultValue;
const idlMemberValue = defaultValue;
idlDict[key] = idlMemberValue;
} else if (member.required) {
throw new TypeError(
`can not be converted to '${name}' because ${key} is required in '${name}'.`,
);
}
}
}
return idlDict;
};
}
window.__bootstrap = window.__bootstrap || {};
window.__bootstrap.webidl = {
converters,
requiredArguments,
createDictionaryConverter,
};
})(this);

View file

@ -1,7 +1,15 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
// @ts-check
/// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../web/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" />
"use strict"; "use strict";
((window) => { ((window) => {
const webidl = window.__bootstrap.webidl;
const { defineProperty } = Object; const { defineProperty } = Object;
// Defined in WebIDL 4.3. // Defined in WebIDL 4.3.
// https://heycam.github.io/webidl/#idl-DOMException // https://heycam.github.io/webidl/#idl-DOMException
@ -33,6 +41,7 @@
// Defined in WebIDL 2.8.1. // Defined in WebIDL 2.8.1.
// https://heycam.github.io/webidl/#dfn-error-names-table // https://heycam.github.io/webidl/#dfn-error-names-table
/** @type {Record<string, number>} */
const nameToCodeMapping = { const nameToCodeMapping = {
IndexSizeError: INDEX_SIZE_ERR, IndexSizeError: INDEX_SIZE_ERR,
HierarchyRequestError: HIERARCHY_REQUEST_ERR, HierarchyRequestError: HIERARCHY_REQUEST_ERR,
@ -67,9 +76,15 @@
constructor(message = "", name = "Error") { constructor(message = "", name = "Error") {
super(); super();
this.#message = String(message); this.#message = webidl.converters.DOMString(message, {
this.#name = name; prefix: "Failed to construct 'DOMException'",
this.#code = nameToCodeMapping[name] ?? 0; context: "Argument 1",
});
this.#name = webidl.converters.DOMString(name, {
prefix: "Failed to construct 'DOMException'",
context: "Argument 2",
});
this.#code = nameToCodeMapping[this.#name] ?? 0;
} }
get message() { get message() {

View file

@ -7,95 +7,65 @@
"use strict"; "use strict";
((window) => { ((window) => {
const eventData = new WeakMap(); const webidl = window.__bootstrap.webidl;
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);
}
}
// accessors for non runtime visible data // accessors for non runtime visible data
function getDispatched(event) { function getDispatched(event) {
return Boolean(eventData.get(event)?.dispatched); return Boolean(event[_dispatched]);
} }
function getPath(event) { function getPath(event) {
return eventData.get(event)?.path ?? []; return event[_path] ?? [];
} }
function getStopImmediatePropagation(event) { function getStopImmediatePropagation(event) {
return Boolean(eventData.get(event)?.stopImmediatePropagation); return Boolean(event[_stopImmediatePropagationFlag]);
} }
function setCurrentTarget( function setCurrentTarget(
event, event,
value, value,
) { ) {
event.currentTarget = value; event[_attributes].currentTarget = value;
} }
function setIsTrusted(event, value) { function setIsTrusted(event, value) {
const data = eventData.get(event); event[_isTrusted] = value;
if (data) {
data.isTrusted = value;
}
} }
function setDispatched(event, value) { function setDispatched(event, value) {
const data = eventData.get(event); event[_dispatched] = value;
if (data) {
data.dispatched = value;
}
} }
function setEventPhase(event, value) { function setEventPhase(event, value) {
event.eventPhase = value; event[_attributes].eventPhase = value;
} }
function setInPassiveListener(event, value) { function setInPassiveListener(event, value) {
const data = eventData.get(event); event[_inPassiveListener] = value;
if (data) {
data.inPassiveListener = value;
}
} }
function setPath(event, value) { function setPath(event, value) {
const data = eventData.get(event); event[_path] = value;
if (data) {
data.path = value;
}
} }
function setRelatedTarget( function setRelatedTarget(
event, event,
value, value,
) { ) {
if ("relatedTarget" in event) { event[_attributes].relatedTarget = value;
event.relatedTarget = value;
}
} }
function setTarget(event, value) { function setTarget(event, value) {
event.target = value; event[_attributes].target = value;
} }
function setStopImmediatePropagation( function setStopImmediatePropagation(
event, event,
value, value,
) { ) {
const data = eventData.get(event); event[_stopImmediatePropagationFlag] = value;
if (data) {
data.stopImmediatePropagation = value;
}
} }
// Type guards that widen the event type // Type guards that widen the event type
@ -108,35 +78,65 @@
const isTrusted = Object.getOwnPropertyDescriptor({ const isTrusted = Object.getOwnPropertyDescriptor({
get isTrusted() { get isTrusted() {
return eventData.get(this).isTrusted; return this[_isTrusted];
}, },
}, "isTrusted").get; }, "isTrusted").get;
const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{
key: "bubbles",
defaultValue: false,
converter: webidl.converters.boolean,
}, {
key: "cancelable",
defaultValue: false,
converter: webidl.converters.boolean,
}, {
key: "composed",
defaultValue: false,
converter: webidl.converters.boolean,
}]);
const _attributes = Symbol("[[attributes]]");
const _canceledFlag = Symbol("[[canceledFlag]]");
const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]");
const _stopImmediatePropagationFlag = Symbol(
"[[stopImmediatePropagationFlag]]",
);
const _inPassiveListener = Symbol("[[inPassiveListener]]");
const _dispatched = Symbol("[[dispatched]]");
const _isTrusted = Symbol("[[isTrusted]]");
const _path = Symbol("[[path]]");
class Event { class Event {
#canceledFlag = false; [_attributes] = {};
#stopPropagationFlag = false; [_canceledFlag] = false;
#attributes = {}; [_stopPropagationFlag] = false;
[_stopImmediatePropagationFlag] = false;
[_inPassiveListener] = false;
[_dispatched] = false;
[_isTrusted] = false;
[_path] = [];
constructor(type, eventInitDict = {}) { constructor(type, eventInitDict = {}) {
requiredArguments("Event", arguments.length, 1); webidl.requiredArguments(arguments.length, 1, {
type = String(type); prefix: "Failed to construct 'Event'",
this.#attributes = { });
type = webidl.converters.DOMString(type, {
prefix: "Failed to construct 'Event'",
context: "Argument 1",
});
const eventInit = eventInitConverter(eventInitDict, {
prefix: "Failed to construct 'Event'",
context: "Argument 2",
});
this[_attributes] = {
type, type,
bubbles: eventInitDict.bubbles ?? false, ...eventInit,
cancelable: eventInitDict.cancelable ?? false,
composed: eventInitDict.composed ?? false,
currentTarget: null, currentTarget: null,
eventPhase: Event.NONE, eventPhase: Event.NONE,
target: null, target: null,
timeStamp: Date.now(), timeStamp: Date.now(),
}; };
eventData.set(this, {
dispatched: false,
inPassiveListener: false,
isTrusted: false,
path: [],
stopImmediatePropagation: false,
});
Reflect.defineProperty(this, "isTrusted", { Reflect.defineProperty(this, "isTrusted", {
enumerable: true, enumerable: true,
get: isTrusted, get: isTrusted,
@ -147,95 +147,32 @@
return buildCustomInspectOutput(this, EVENT_PROPS, inspect); return buildCustomInspectOutput(this, EVENT_PROPS, inspect);
} }
get bubbles() {
return this.#attributes.bubbles;
}
get cancelBubble() {
return this.#stopPropagationFlag;
}
set cancelBubble(value) {
this.#stopPropagationFlag = value;
}
get cancelable() {
return this.#attributes.cancelable;
}
get composed() {
return this.#attributes.composed;
}
get currentTarget() {
return this.#attributes.currentTarget;
}
set currentTarget(value) {
this.#attributes = {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: value,
eventPhase: this.eventPhase,
target: this.target,
timeStamp: this.timeStamp,
};
}
get defaultPrevented() {
return this.#canceledFlag;
}
get eventPhase() {
return this.#attributes.eventPhase;
}
set eventPhase(value) {
this.#attributes = {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: value,
target: this.target,
timeStamp: this.timeStamp,
};
}
get initialized() {
return true;
}
get target() {
return this.#attributes.target;
}
set target(value) {
this.#attributes = {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: this.eventPhase,
target: value,
timeStamp: this.timeStamp,
};
}
get timeStamp() {
return this.#attributes.timeStamp;
}
get type() { get type() {
return this.#attributes.type; return this[_attributes].type;
}
set type(_) {
// this is a no-op because this member is readonly
}
get target() {
return this[_attributes].target;
}
set target(_) {
// this is a no-op because this member is readonly
}
get srcElement() {
return null;
}
set srcElement(_) {
// this is a no-op because this member is readonly
}
get currentTarget() {
return this[_attributes].currentTarget;
}
set currentTarget(_) {
// this is a no-op because this member is readonly
} }
composedPath() { composedPath() {
const path = eventData.get(this).path; const path = this[_path];
if (path.length === 0) { if (path.length === 0) {
return []; return [];
} }
@ -339,52 +276,123 @@
return composedPath.map((p) => p.item); return composedPath.map((p) => p.item);
} }
preventDefault() {
if (this.cancelable && !eventData.get(this).inPassiveListener) {
this.#canceledFlag = true;
}
}
stopPropagation() {
this.#stopPropagationFlag = true;
}
stopImmediatePropagation() {
this.#stopPropagationFlag = true;
eventData.get(this).stopImmediatePropagation = true;
}
get NONE() { get NONE() {
return Event.NONE; return Event.NONE;
} }
set NONE(_) {
// this is a no-op because this member is readonly
}
get CAPTURING_PHASE() { get CAPTURING_PHASE() {
return Event.CAPTURING_PHASE; return Event.CAPTURING_PHASE;
} }
set CAPTURING_PHASE(_) {
// this is a no-op because this member is readonly
}
get AT_TARGET() { get AT_TARGET() {
return Event.AT_TARGET; return Event.AT_TARGET;
} }
set AT_TARGET(_) {
// this is a no-op because this member is readonly
}
get BUBBLING_PHASE() { get BUBBLING_PHASE() {
return Event.BUBBLING_PHASE; return Event.BUBBLING_PHASE;
} }
set BUBBLING_PHASE(_) {
// this is a no-op because this member is readonly
}
static get NONE() { static get NONE() {
return 0; return 0;
} }
static set NONE(_) {
// this is a no-op because this member is readonly
}
static get CAPTURING_PHASE() { static get CAPTURING_PHASE() {
return 1; return 1;
} }
static set CAPTURING_PHASE(_) {
// this is a no-op because this member is readonly
}
static get AT_TARGET() { static get AT_TARGET() {
return 2; return 2;
} }
static set AT_TARGET(_) {
// this is a no-op because this member is readonly
}
static get BUBBLING_PHASE() { static get BUBBLING_PHASE() {
return 3; return 3;
} }
static set BUBBLING_PHASE(_) {
// this is a no-op because this member is readonly
}
get eventPhase() {
return this[_attributes].eventPhase;
}
set eventPhase(_) {
// this is a no-op because this member is readonly
}
stopPropagation() {
this[_stopPropagationFlag] = true;
}
get cancelBubble() {
return this[_stopPropagationFlag];
}
set cancelBubble(value) {
this[_stopPropagationFlag] = webidl.converters.boolean(value);
}
stopImmediatePropagation() {
this[_stopPropagationFlag] = true;
this[_stopImmediatePropagationFlag] = true;
}
get bubbles() {
return this[_attributes].bubbles;
}
set bubbles(_) {
// this is a no-op because this member is readonly
}
get cancelable() {
return this[_attributes].cancelable;
}
set cancelable(value) {
// this is a no-op because this member is readonly
}
get returnValue() {
return !this[_canceledFlag];
}
set returnValue(value) {
if (!webidl.converters.boolean(value)) {
this[_canceledFlag] = true;
}
}
preventDefault() {
if (this[_attributes].cancelable && !this[_inPassiveListener]) {
this[_canceledFlag] = true;
}
}
get defaultPrevented() {
return this[_canceledFlag];
}
set defaultPrevented(_) {
// this is a no-op because this member is readonly
}
get composed() {
return this[_attributes].composed;
}
set composed(_) {
// this is a no-op because this member is readonly
}
get initialized() {
return true;
}
get timeStamp() {
return this[_attributes].timeStamp;
}
set timeStamp(_) {
// this is a no-op because this member is readonly
}
} }
function buildCustomInspectOutput(object, keys, inspect) { function buildCustomInspectOutput(object, keys, inspect) {
@ -408,7 +416,9 @@
"currentTarget", "currentTarget",
"defaultPrevented", "defaultPrevented",
"eventPhase", "eventPhase",
"srcElement",
"target", "target",
"returnValue",
"timeStamp", "timeStamp",
"type", "type",
]; ];
@ -875,7 +885,9 @@
callback, callback,
options, options,
) { ) {
requiredArguments("EventTarget.addEventListener", arguments.length, 2); webidl.requiredArguments(arguments.length, 2, {
prefix: "Failed to execute 'addEventListener' on 'EventTarget'",
});
if (callback === null) { if (callback === null) {
return; return;
} }
@ -919,7 +931,9 @@
callback, callback,
options, options,
) { ) {
requiredArguments("EventTarget.removeEventListener", arguments.length, 2); webidl.requiredArguments(arguments.length, 2, {
prefix: "Failed to execute 'removeEventListener' on 'EventTarget'",
});
const listeners = eventTargetData.get(this ?? globalThis).listeners; const listeners = eventTargetData.get(this ?? globalThis).listeners;
if (callback !== null && type in listeners) { if (callback !== null && type in listeners) {
@ -948,7 +962,9 @@
} }
dispatchEvent(event) { dispatchEvent(event) {
requiredArguments("EventTarget.dispatchEvent", arguments.length, 1); webidl.requiredArguments(arguments.length, 1, {
prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'",
});
const self = this ?? globalThis; const self = this ?? globalThis;
const listeners = eventTargetData.get(self).listeners; const listeners = eventTargetData.get(self).listeners;
@ -1128,7 +1144,9 @@
constructor(type, eventInitDict = {}) { constructor(type, eventInitDict = {}) {
super(type, eventInitDict); super(type, eventInitDict);
requiredArguments("CustomEvent", arguments.length, 1); webidl.requiredArguments(arguments.length, 1, {
prefix: "Failed to construct 'CustomEvent'",
});
const { detail } = eventInitDict; const { detail } = eventInitDict;
this.#detail = detail; this.#detail = detail;
} }

View file

@ -1,10 +1,223 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file no-explicit-any ban-types
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
declare namespace globalThis { declare namespace globalThis {
declare namespace __bootstrap { declare namespace __bootstrap {
declare namespace webidl {
declare interface ConverterOpts {
/**
* The prefix for error messages created by this converter.
* Examples:
* - `Failed to construct 'Event'`
* - `Failed to execute 'removeEventListener' on 'EventTarget'`
*/
prefix: string;
}
declare interface ValueConverterOpts extends ConverterOpts {
/**
* The context of this value error messages created by this converter.
* Examples:
* - `Argument 1`
* - `Argument 3`
*/
context: string;
}
declare interface IntConverterOpts extends ValueConverterOpts {
/**
* Wether to throw if the number is outside of the acceptable values for
* this type.
*/
enforceRange?: boolean;
/**
* Wether to clamp this number to the acceptable values for this type.
*/
clamp?: boolean;
}
declare interface StringConverterOpts extends ValueConverterOpts {
/**
* Wether to treat `null` value as an empty string.
*/
treatNullAsEmptyString?: boolean;
}
declare interface BufferConverterOpts extends ValueConverterOpts {
/**
* Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`).
*/
allowShared?: boolean;
}
declare const converters: {
any(v: any): any;
/**
* Convert a value into a `boolean` (bool).
*/
boolean(v: any, opts?: IntConverterOpts): boolean;
/**
* Convert a value into a `byte` (int8).
*/
byte(v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `octet` (uint8).
*/
octet(v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `short` (int16).
*/
short(v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `unsigned short` (uint16).
*/
["unsigned short"](v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `long` (int32).
*/
long(v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `unsigned long` (uint32).
*/
["unsigned long"](v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `long long` (int64).
* **Note this is truncated to a JS number (53 bit precision).**
*/
["long long"](v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `unsigned long long` (uint64).
* **Note this is truncated to a JS number (53 bit precision).**
*/
["unsigned long long"](v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `float` (f32).
*/
float(v: any, opts?: ValueConverterOpts): number;
/**
* Convert a value into a `unrestricted float` (f32, infinity, or NaN).
*/
["unrestricted float"](v: any, opts?: ValueConverterOpts): number;
/**
* Convert a value into a `double` (f64).
*/
double(v: any, opts?: ValueConverterOpts): number;
/**
* Convert a value into a `unrestricted double` (f64, infinity, or NaN).
*/
["unrestricted double"](v: any, opts?: ValueConverterOpts): number;
/**
* Convert a value into a `DOMString` (string).
*/
DOMString(v: any, opts?: StringConverterOpts): string;
/**
* Convert a value into a `ByteString` (string with only u8 codepoints).
*/
ByteString(v: any, opts?: StringConverterOpts): string;
/**
* Convert a value into a `USVString` (string with only valid non
* surrogate Unicode code points).
*/
USVString(v: any, opts?: StringConverterOpts): string;
/**
* Convert a value into an `object` (object).
*/
object(v: any, opts?: ValueConverterOpts): object;
/**
* Convert a value into an `ArrayBuffer` (ArrayBuffer).
*/
ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer;
/**
* Convert a value into a `DataView` (ArrayBuffer).
*/
DataView(v: any, opts?: BufferConverterOpts): DataView;
/**
* Convert a value into a `Int8Array` (Int8Array).
*/
Int8Array(v: any, opts?: BufferConverterOpts): Int8Array;
/**
* Convert a value into a `Int16Array` (Int16Array).
*/
Int16Array(v: any, opts?: BufferConverterOpts): Int16Array;
/**
* Convert a value into a `Int32Array` (Int32Array).
*/
Int32Array(v: any, opts?: BufferConverterOpts): Int32Array;
/**
* Convert a value into a `Uint8Array` (Uint8Array).
*/
Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array;
/**
* Convert a value into a `Uint16Array` (Uint16Array).
*/
Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array;
/**
* Convert a value into a `Uint32Array` (Uint32Array).
*/
Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array;
/**
* Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray).
*/
Uint8ClampedArray(
v: any,
opts?: BufferConverterOpts,
): Uint8ClampedArray;
/**
* Convert a value into a `Float32Array` (Float32Array).
*/
Float32Array(v: any, opts?: BufferConverterOpts): Float32Array;
/**
* Convert a value into a `Float64Array` (Float64Array).
*/
Float64Array(v: any, opts?: BufferConverterOpts): Float64Array;
/**
* Convert a value into an `ArrayBufferView` (ArrayBufferView).
*/
ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView;
/**
* Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView).
*/
BufferSource(
v: any,
opts?: BufferConverterOpts,
): ArrayBuffer | ArrayBufferView;
/**
* Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long
*/
DOMTimeStamp(v: any, opts?: IntConverterOpts): number;
/**
* Convert a value into a `Function` ((...args: any[]) => any).
*/
Function(v: any, opts?: ValueConverterOpts): (...args: any) => any;
/**
* Convert a value into a `VoidFunction` (() => void).
*/
VoidFunction(v: any, opts?: ValueConverterOpts): () => void;
};
/**
* Assert that the a function has at least a required amount of arguments.
*/
declare function requiredArguments(
length: number,
required: number,
opts: ConverterOpts,
): void;
declare type Dictionary = DictionaryMember[];
declare interface DictionaryMember {
key: string;
converter: (v: any, opts: ValueConverterOpts) => any;
defaultValue?: boolean;
required?: boolean;
}
/**ie
* Assert that the a function has at least a required amount of arguments.
*/
declare function createDictionaryConverter<T>(
name: string,
...dictionaries: Dictionary[]
): (v: any, opts: ValueConverterOpts) => T;
}
declare var url: { declare var url: {
URLSearchParams: typeof URLSearchParams; URLSearchParams: typeof URLSearchParams;
}; };

View file

@ -41,20 +41,24 @@ pub fn op_domain_to_ascii(
pub fn init(isolate: &mut JsRuntime) { pub fn init(isolate: &mut JsRuntime) {
let files = vec![ let files = vec![
( (
"deno:op_crates/web/00_dom_exception.js", "deno:op_crates/web/00_webidl.js",
include_str!("00_dom_exception.js"), include_str!("00_webidl.js"),
), ),
( (
"deno:op_crates/web/01_event.js", "deno:op_crates/web/01_dom_exception.js",
include_str!("01_event.js"), include_str!("01_dom_exception.js"),
), ),
( (
"deno:op_crates/web/02_abort_signal.js", "deno:op_crates/web/02_event.js",
include_str!("02_abort_signal.js"), include_str!("02_event.js"),
), ),
( (
"deno:op_crates/web/03_global_interfaces.js", "deno:op_crates/web/03_abort_signal.js",
include_str!("03_global_interfaces.js"), include_str!("03_abort_signal.js"),
),
(
"deno:op_crates/web/04_global_interfaces.js",
include_str!("04_global_interfaces.js"),
), ),
( (
"deno:op_crates/web/08_text_encoding.js", "deno:op_crates/web/08_text_encoding.js",
@ -136,7 +140,7 @@ mod tests {
if let Err(error) = result { if let Err(error) = result {
let error_string = error.to_string(); let error_string = error.to_string();
// Test that the script specifier is a URL: `deno:<repo-relative path>`. // Test that the script specifier is a URL: `deno:<repo-relative path>`.
assert!(error_string.contains("deno:op_crates/web/01_event.js")); assert!(error_string.contains("deno:op_crates/web/02_event.js"));
assert!(error_string.contains("TypeError")); assert!(error_string.contains("TypeError"));
} else { } else {
unreachable!(); unreachable!();

@ -1 +1 @@
Subproject commit 81837c9a0fb4a11a7bc3b062219c758b238f1b39 Subproject commit 182191819bbfdc409bf5f29a5ea3bedb4545b91e

View file

@ -22,7 +22,11 @@
"event-global-extra.window.js": true, "event-global-extra.window.js": true,
"event-global.worker.js": true, "event-global.worker.js": true,
"legacy-pre-activation-behavior.window.js": true, "legacy-pre-activation-behavior.window.js": true,
"relatedTarget.window.js": true "relatedTarget.window.js": true,
"Event-constructors.any.js": [
"Untitled 2",
"Untitled 3"
]
}, },
"idlharness.any.js": false, "idlharness.any.js": false,
"idlharness.window.js": false "idlharness.window.js": false