mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 15:49:44 -05:00
fix(ext/web): add EventTarget brand checking (#14637)
This commit adds brand checking to EventTarget. It also fixes a bug where deno would crash if an abort signal was aborted on the global addEventListener().
This commit is contained in:
parent
870d200716
commit
367e006e06
4 changed files with 58 additions and 7 deletions
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||
// deno-lint-ignore-file no-window-prefix
|
||||
import { assertEquals } from "./test_util.ts";
|
||||
import { assertEquals, assertThrows } from "./test_util.ts";
|
||||
|
||||
Deno.test(function addEventListenerTest() {
|
||||
const document = new EventTarget();
|
||||
|
@ -244,3 +244,38 @@ Deno.test(function eventTargetDispatchShouldSetTargetInListener() {
|
|||
target.dispatchEvent(event);
|
||||
assertEquals(called, true);
|
||||
});
|
||||
|
||||
Deno.test(function eventTargetAddEventListenerGlobalAbort() {
|
||||
return new Promise((resolve) => {
|
||||
const c = new AbortController();
|
||||
|
||||
c.signal.addEventListener("abort", () => resolve());
|
||||
addEventListener("test", () => {}, { signal: c.signal });
|
||||
c.abort();
|
||||
});
|
||||
});
|
||||
|
||||
Deno.test(function eventTargetBrandChecking() {
|
||||
const self = {};
|
||||
|
||||
assertThrows(
|
||||
() => {
|
||||
EventTarget.prototype.addEventListener.call(self, "test", null);
|
||||
},
|
||||
TypeError,
|
||||
);
|
||||
|
||||
assertThrows(
|
||||
() => {
|
||||
EventTarget.prototype.removeEventListener.call(self, "test", null);
|
||||
},
|
||||
TypeError,
|
||||
);
|
||||
|
||||
assertThrows(
|
||||
() => {
|
||||
EventTarget.prototype.dispatchEvent.call(self, new Event("test"));
|
||||
},
|
||||
TypeError,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -917,6 +917,7 @@
|
|||
class EventTarget {
|
||||
constructor() {
|
||||
this[eventTargetData] = getDefaultTargetData();
|
||||
this[webidl.brand] = webidl.brand;
|
||||
}
|
||||
|
||||
addEventListener(
|
||||
|
@ -924,6 +925,8 @@
|
|||
callback,
|
||||
options,
|
||||
) {
|
||||
const self = this ?? globalThis;
|
||||
webidl.assertBranded(self, EventTargetPrototype);
|
||||
const prefix = "Failed to execute 'addEventListener' on 'EventTarget'";
|
||||
|
||||
webidl.requiredArguments(arguments.length, 2, {
|
||||
|
@ -939,7 +942,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const { listeners } = (this ?? globalThis)[eventTargetData];
|
||||
const { listeners } = self[eventTargetData];
|
||||
|
||||
if (!(ReflectHas(listeners, type))) {
|
||||
listeners[type] = [];
|
||||
|
@ -965,7 +968,7 @@
|
|||
// If listener’s signal is not null, then add the following abort
|
||||
// abort steps to it: Remove an event listener.
|
||||
signal.addEventListener("abort", () => {
|
||||
this.removeEventListener(type, callback, options);
|
||||
self.removeEventListener(type, callback, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -978,11 +981,13 @@
|
|||
callback,
|
||||
options,
|
||||
) {
|
||||
const self = this ?? globalThis;
|
||||
webidl.assertBranded(self, EventTargetPrototype);
|
||||
webidl.requiredArguments(arguments.length, 2, {
|
||||
prefix: "Failed to execute 'removeEventListener' on 'EventTarget'",
|
||||
});
|
||||
|
||||
const { listeners } = (this ?? globalThis)[eventTargetData];
|
||||
const { listeners } = self[eventTargetData];
|
||||
if (callback !== null && ReflectHas(listeners, type)) {
|
||||
listeners[type] = ArrayPrototypeFilter(
|
||||
listeners[type],
|
||||
|
@ -1010,14 +1015,15 @@
|
|||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
webidl.requiredArguments(arguments.length, 1, {
|
||||
prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'",
|
||||
});
|
||||
// If `this` is not present, then fallback to global scope. We don't use
|
||||
// `globalThis` directly here, because it could be deleted by user.
|
||||
// Instead use saved reference to global scope when the script was
|
||||
// executed.
|
||||
const self = this ?? window;
|
||||
webidl.assertBranded(self, EventTargetPrototype);
|
||||
webidl.requiredArguments(arguments.length, 1, {
|
||||
prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'",
|
||||
});
|
||||
|
||||
const { listeners } = self[eventTargetData];
|
||||
if (!ReflectHas(listeners, event.type)) {
|
||||
|
@ -1042,6 +1048,7 @@
|
|||
}
|
||||
|
||||
webidl.configurePrototype(EventTarget);
|
||||
const EventTargetPrototype = EventTarget.prototype;
|
||||
|
||||
defineEnumerableProps(EventTarget, [
|
||||
"addEventListener",
|
||||
|
@ -1411,6 +1418,7 @@
|
|||
reportException(error);
|
||||
}
|
||||
|
||||
window[webidl.brand] = webidl.brand;
|
||||
window.Event = Event;
|
||||
window.EventTarget = EventTarget;
|
||||
window.ErrorEvent = ErrorEvent;
|
||||
|
|
1
ext/web/lib.deno_web.d.ts
vendored
1
ext/web/lib.deno_web.d.ts
vendored
|
@ -142,6 +142,7 @@ declare type EventListenerOrEventListenerObject =
|
|||
interface AddEventListenerOptions extends EventListenerOptions {
|
||||
once?: boolean;
|
||||
passive?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
interface EventListenerOptions {
|
||||
|
|
|
@ -18,5 +18,12 @@ window.add_completion_callback((_tests, harnessStatus) => {
|
|||
while (bytesWritten < data.byteLength) {
|
||||
bytesWritten += Deno.stderr.writeSync(data.subarray(bytesWritten));
|
||||
}
|
||||
|
||||
// TODO(cjihrig): Restore the prototype of globalThis to be an EventTarget
|
||||
// again. There are WPTs that change the prototype, which causes brand
|
||||
// checking to fail. Once the globalThis prototype is frozen properly, this
|
||||
// line can be removed.
|
||||
Object.setPrototypeOf(globalThis, EventTarget.prototype);
|
||||
|
||||
Deno.exit(harnessStatus.status === 0 ? 0 : 1);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue