diff --git a/cli/tests/unit/event_target_test.ts b/cli/tests/unit/event_target_test.ts index dfc28605b4..f732003ecc 100644 --- a/cli/tests/unit/event_target_test.ts +++ b/cli/tests/unit/event_target_test.ts @@ -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, + ); +}); diff --git a/ext/web/02_event.js b/ext/web/02_event.js index 9d5169b9e0..f5b4bd7d34 100644 --- a/ext/web/02_event.js +++ b/ext/web/02_event.js @@ -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; diff --git a/ext/web/lib.deno_web.d.ts b/ext/web/lib.deno_web.d.ts index 00b494684a..e3fc58be02 100644 --- a/ext/web/lib.deno_web.d.ts +++ b/ext/web/lib.deno_web.d.ts @@ -142,6 +142,7 @@ declare type EventListenerOrEventListenerObject = interface AddEventListenerOptions extends EventListenerOptions { once?: boolean; passive?: boolean; + signal?: AbortSignal; } interface EventListenerOptions { diff --git a/tools/wpt/testharnessreport.js b/tools/wpt/testharnessreport.js index 3368faa4eb..a07c916c71 100644 --- a/tools/wpt/testharnessreport.js +++ b/tools/wpt/testharnessreport.js @@ -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); });