1
0
Fork 0
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:
Colin Ihrig 2022-06-17 11:05:02 -04:00 committed by GitHub
parent 870d200716
commit 367e006e06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 7 deletions

View file

@ -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,
);
});

View file

@ -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 listeners 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;

View file

@ -142,6 +142,7 @@ declare type EventListenerOrEventListenerObject =
interface AddEventListenerOptions extends EventListenerOptions {
once?: boolean;
passive?: boolean;
signal?: AbortSignal;
}
interface EventListenerOptions {

View file

@ -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);
});