From 62dd58bdede9d77ebc6bd66044d91e8b3729d13b Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 18 Sep 2026 15:45:11 +0800 Subject: [PATCH] fix(workers): inherit EventTarget across worker global scopes Build dedicated, shared and service worker globals from the native EventTarget template before context creation. Preserve immutable global prototype chains and explicitly link the Web IDL interface constructors. Cover all three worker kinds with the same regression fixture, including inherited when(), event delivery, cancellation, global error reporting, receiver branding and replacement of the public EventTarget constructor. Promote the passing worker inspect and immutable-prototype WPT cases. --- .../wpt-cross-current/passed-cases.txt | 2 ++ .../src/worker/thread/tests/lifecycle.rs | 2 +- .../fixtures/worker-global-prototypes.js | 34 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index dd2807ebbe..1895f591ba 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -4556,6 +4556,7 @@ dom/observable/tentative/observable-first.any.js?moli-wpt-any=window dom/observable/tentative/observable-forEach.any.js?moli-wpt-any=dedicatedworker dom/observable/tentative/observable-forEach.any.js?moli-wpt-any=window dom/observable/tentative/observable-forEach.window.js?moli-wpt-script=window +dom/observable/tentative/observable-inspect.any.js?moli-wpt-any=dedicatedworker dom/observable/tentative/observable-inspect.any.js?moli-wpt-any=window dom/observable/tentative/observable-last.any.js?moli-wpt-any=dedicatedworker dom/observable/tentative/observable-last.any.js?moli-wpt-any=window @@ -10020,6 +10021,7 @@ webidl/ecmascript-binding/constructors.html webidl/ecmascript-binding/default-iterator-object.html webidl/ecmascript-binding/default-toJSON-cross-realm.html webidl/ecmascript-binding/es-exceptions/exceptions.html +webidl/ecmascript-binding/global-immutable-prototype.any.js?moli-wpt-any=dedicatedworker webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html webidl/ecmascript-binding/has-instance.html webidl/ecmascript-binding/interface-object-set-receiver.html diff --git a/moli-renderer-v8/src/worker/thread/tests/lifecycle.rs b/moli-renderer-v8/src/worker/thread/tests/lifecycle.rs index b283d4e263..a781816a5d 100644 --- a/moli-renderer-v8/src/worker/thread/tests/lifecycle.rs +++ b/moli-renderer-v8/src/worker/thread/tests/lifecycle.rs @@ -276,7 +276,7 @@ async fn worker_global_prototype_chains_inherit_event_target_and_are_immutable() format!( r#" const result = ({fixture})({interface:?}); - if (result.checks !== 46 || result.failures.length) {{ + if (result.checks !== 61 || result.failures.length) {{ throw new Error(JSON.stringify(result)); }} "# diff --git a/moli-renderer-v8/tests/fixtures/worker-global-prototypes.js b/moli-renderer-v8/tests/fixtures/worker-global-prototypes.js index 003b9ce845..768205377e 100644 --- a/moli-renderer-v8/tests/fixtures/worker-global-prototypes.js +++ b/moli-renderer-v8/tests/fixtures/worker-global-prototypes.js @@ -38,6 +38,11 @@ check(Object.prototype.toString.call(WorkerGlobalScope.prototype) === '[object WorkerGlobalScope]', 'worker tag'); check(throwsTypeError(() => new scopeConstructor()), 'specific illegal constructor'); check(throwsTypeError(() => new WorkerGlobalScope()), 'worker illegal constructor'); + check(typeof self.when === 'function', 'inherited when exposed'); + check(self.when === eventTarget.prototype.when, 'inherited when identity'); + for (const value of chain.slice(0, 3)) { + check(!Object.hasOwn(value, 'when'), 'when is inherited'); + } test('prototype extension', () => { const key = Symbol('EventTarget extension'); eventTarget.prototype[key] = 17; @@ -56,6 +61,33 @@ self.dispatchEvent(new Event('worker-prototype-probe')); check(values.length === 1, 'global listener removal still works'); }); + test('inherited observable events', () => { + const controller = new AbortController(); + const values = []; + const observable = self.when('worker-prototype-probe'); + check(observable instanceof Observable, 'when returns Observable'); + observable.subscribe(event => values.push(event), {signal: controller.signal}); + const first = new Event('worker-prototype-probe'); + self.dispatchEvent(first); + check(values.length === 1 && values[0] === first, 'when receives global dispatch'); + check(first.target === self, 'dispatched event target is global'); + controller.abort(); + self.dispatchEvent(new Event('worker-prototype-probe')); + check(values.length === 1, 'abort removes global listener'); + }); + test('global error reporting', () => { + const errors = []; + const marker = new Error('inspector abort'); + const controller = new AbortController(); + self.when('error').take(1).subscribe(event => { + errors.push(event.error); + event.preventDefault(); + }); + new Observable(subscriber => subscriber.next(1)) + .inspect({abort() { throw marker; }}) + .subscribe(() => controller.abort(), {signal: controller.signal}); + check(errors.length === 1 && errors[0] === marker, 'when receives reported callback error'); + }); test('receiver branding', () => { let conversions = 0; let traps = 0; @@ -67,6 +99,7 @@ getPrototypeOf() { traps++; throw new Error('author proxy trap'); } }), revoked.proxy]) { check(throwsTypeError(() => eventTarget.prototype.addEventListener.call(receiver, type, () => {})), 'addEventListener rejects forged/proxy receiver'); + check(throwsTypeError(() => eventTarget.prototype.when.call(receiver, type)), 'when rejects forged/proxy receiver'); } check(conversions === 0, 'brand checked before conversion'); check(traps === 0, 'brand checked without proxy traps'); @@ -76,6 +109,7 @@ try { check(Object.getPrototypeOf(WorkerGlobalScope.prototype) === eventTarget.prototype, 'prototype keeps intrinsic EventTarget'); check(Object.getPrototypeOf(WorkerGlobalScope) === eventTarget, 'constructor keeps intrinsic EventTarget'); + check(self.when === eventTarget.prototype.when, 'when keeps intrinsic method'); } finally { self.EventTarget = eventTarget; } }); return {checks, failures};