ServiceWorkerGlobalScope doesn't inherit from the same EventTarget as globalThis.EventTarget

I need to support EventTarget’s addEventListener being passed an options object with once: true (various reasons, including library code that expects support) and right now Cloudflare Workers doesn’t support that–it errors with “useCapture must be false.” So I went to polyfill it on the EventTarget.prototype but discovered that the global ServiceWorkerGlobalScope aka globalThis aka self doesn’t actually inherit from the same prototype of the EventTarget that is available globally. If you follow the prototype chain of ServiceWorkerGlobalScope it does claim to inherit from some EventTarget, but if you check equality it confirms there must be two different duplicate implementations; one available at globalThis.EventTarget and another which ServiceWorkerGlobalScope inherits from.

More of an FYI that, while ServiceWorkerGlobalScope isn’t a reflection of any spec, it’s less than ideal behavior. I imagine it’s because of how the underlying runtime hooks into the ‘fetch’ event on the global listener vs. an actual generic EventTarget implementation. That said, it’s not the end of the world since the workaround is fairly simple for my specific case: I need to polyfill both the global EventTarget and the addEventListener on the EventTarget prototype that ServiceWorkerGlobalScope uses so that the global one works too.

Edit: actually, I just realized indeed it sort of is trying to follow the Service Worker spec (hence the ServiceWorkerGlobalScope name) in which case this is technically isn’t spec compliant indeed. But I won’t berate you all about it haha.

One easy way to confirm and compare behavior is to run this in both a Worker, and then try it in a browser (e.g. Chrome):

console.log(EventTarget.prototype.constructor.name === globalThis.__proto__.__proto__.__proto__.constructor.name);
// true in both Workers/Browser, both named "EventTarget"

console.log(EventTarget.prototype.constructor === globalThis.__proto__.__proto__.__proto__.constructor);
// false in Workers
// true in Browsers

The problem seems to be that ServiceWorkerGlobalScope's prototype is not inheriting from WorkerGlobalScope but from a shadow version, which is itself inheriting from a shadow EventTarget. They all however inherit from the global Object.

ServiceWorkerGlobalScope.prototype.__proto__ !== WorkerGlobalScope.prototype
ServiceWorkerGlobalScope.prototype.__proto__.constructor.name === WorkerGlobalScope.name

ServiceWorkerGlobalScope.prototype.__proto__.__proto__ !== EventTarget.prototype
ServiceWorkerGlobalScope.prototype.__proto__.__proto__.constructor.name === EventTarget.name

WorkerGlobalScope.prototype.__proto__ === EventTarget.prototype
WorkerGlobalScope.prototype.__proto__.constructor.name === EventTarget.name

If this is a question, I would recommend joining the official Workers discord server