From cbc4b4878086b906394997f9d8dbacda624adf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 16 Sep 2026 00:25:28 +0200 Subject: [PATCH 1/2] Expose Worker in hydrated iframe realms --- .../native/webscene_v8_runtime.cpp | 9 +-- .../webscene_v8_runtime_cache_and_frames.inc | 1 + .../native/webscene_v8_runtime_workers.inc | 31 ++++++++ .../tests/hybrid_v8_runtime_tests.cpp | 3 + .../iframe-worker-extension-host.html | 73 +++++++++++++++++++ .../iframe-worker-extension-host-module.js | 7 ++ .../webscene-aureon-runtime-profile.json | 11 +++ 7 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 tests/WebPlatformSubset/contracts/iframe-worker-extension-host.html create mode 100644 tests/WebPlatformSubset/contracts/resources/aureon/iframe-worker-extension-host-module.js diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp index 4e0885764..7196e9fc6 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp @@ -3735,14 +3735,7 @@ struct v8_dom_runtime::implementation final { v8::Function::New(local_context, structured_clone, {}, 1).ToLocalChecked()).Check(); install_indexeddb(local_context); install_performance_timeline(local_context); - auto worker_constructor=v8::Function::New(local_context, worker_construct, {}, 1).ToLocalChecked(); - v8::Local event_target,worker_prototype,event_prototype; - if(local_context->Global()->Get(local_context,js_string(isolate,"EventTarget")).ToLocal(&event_target) - &&event_target->IsFunction() - &&worker_constructor->Get(local_context,js_string(isolate,"prototype")).ToLocal(&worker_prototype) - &&event_target.As()->Get(local_context,js_string(isolate,"prototype")).ToLocal(&event_prototype)) - worker_prototype.As()->SetPrototype(local_context,event_prototype).FromMaybe(false); - local_context->Global()->Set(local_context, js_string(isolate, "Worker"),worker_constructor).Check(); + install_worker_constructor(local_context); install_clipboard_api(local_context); install_websocket_globals(local_context); install_editor_web_platform_globals(local_context); diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_cache_and_frames.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_cache_and_frames.inc index d8412d76b..965bfa763 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_cache_and_frames.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_cache_and_frames.inc @@ -1903,6 +1903,7 @@ install_websocket_globals(local_context); install_editor_web_platform_globals(local_context); install_message_channel(local_context); + install_worker_constructor(local_context); install_tree_walker_platform(local_context); install_custom_elements_platform(local_context); install_fetch_globals(local_context); diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_workers.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_workers.inc index 5b230a3f4..ef1f927cc 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_workers.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_workers.inc @@ -35,6 +35,35 @@ static constexpr size_t maximum_worker_errors = 16U; static constexpr size_t maximum_worker_error_length = 4096U; + void install_worker_constructor(v8::Local local_context) { + auto worker_constructor = v8::Function::New( + local_context, + worker_construct, + {}, + 1).ToLocalChecked(); + v8::Local event_target; + v8::Local worker_prototype; + v8::Local event_prototype; + if (local_context->Global()->Get( + local_context, + js_string(isolate, "EventTarget")).ToLocal(&event_target) + && event_target->IsFunction() + && worker_constructor->Get( + local_context, + js_string(isolate, "prototype")).ToLocal(&worker_prototype) + && event_target.As()->Get( + local_context, + js_string(isolate, "prototype")).ToLocal(&event_prototype)) { + worker_prototype.As()->SetPrototype( + local_context, + event_prototype).FromMaybe(false); + } + local_context->Global()->Set( + local_context, + js_string(isolate, "Worker"), + worker_constructor).Check(); + } + static v8::MaybeLocal worker_transfer( const v8::FunctionCallbackInfo& info) { if (info.Length() < 2) return v8::Undefined(info.GetIsolate()); @@ -337,6 +366,8 @@ auto realm=child->context.Get(child->isolate);v8::Context::Scope scope(realm); if(packet&&!child->dispatch_worker_message(realm,realm->Global(),&*packet))report(child->last_error); child->drain_tasks(); + child->perform_microtask_checkpoint(); + if (!child->promote_pending_promise_error()) report(child->last_error); } {std::lock_guard lock(raw->mutex);raw->running_isolate=nullptr;} child->isolate->CancelTerminateExecution(); diff --git a/experiments/WebScene.NativeEngine.Probe/tests/hybrid_v8_runtime_tests.cpp b/experiments/WebScene.NativeEngine.Probe/tests/hybrid_v8_runtime_tests.cpp index 6b5273dc8..abd7096ad 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/hybrid_v8_runtime_tests.cpp +++ b/experiments/WebScene.NativeEngine.Probe/tests/hybrid_v8_runtime_tests.cpp @@ -218,6 +218,9 @@ void test_iframe_worker_extension_host_port_bootstrap() { const child = frame.contentDocument; child.open(); child.write(` diff --git a/tests/WebPlatformSubset/contracts/resources/aureon/iframe-worker-extension-host-module.js b/tests/WebPlatformSubset/contracts/resources/aureon/iframe-worker-extension-host-module.js new file mode 100644 index 000000000..a61ce3148 --- /dev/null +++ b/tests/WebPlatformSubset/contracts/resources/aureon/iframe-worker-extension-host-module.js @@ -0,0 +1,7 @@ +postMessage('worker-started'); +self.onmessage = event => { + const port = event.data.port; + if (!(port instanceof MessagePort)) throw Error('worker port transfer failed'); + port.onmessage = message => port.postMessage(message.data + 1); + port.start(); +}; diff --git a/tests/WebPlatformSubset/webscene-aureon-runtime-profile.json b/tests/WebPlatformSubset/webscene-aureon-runtime-profile.json index 37a6744b2..c04ec0854 100644 --- a/tests/WebPlatformSubset/webscene-aureon-runtime-profile.json +++ b/tests/WebPlatformSubset/webscene-aureon-runtime-profile.json @@ -44,6 +44,17 @@ "reason": "Focused Code OSS iframe bootstrap candidate matching the extension-host MessagePort handoff.", "nativeNavigation": true }, + { + "path": "contracts/iframe-worker-extension-host.html", + "type": "contract", + "capabilities": [ + "iframe-worker-constructor", + "module-workers", + "messageport-transfer" + ], + "reason": "Code OSS-shaped regression proving its hydrated extension-host iframe can construct a module Worker and bridge the transferred bootstrap port.", + "nativeNavigation": true + }, { "path": "contracts/worker-termination-queue-messageerror.html", "type": "contract", From 274bb1dbbefe592c582cdb48b85b51cea5341ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 16 Sep 2026 00:52:58 +0200 Subject: [PATCH 2/2] Make detached iframe callback regression timing-safe --- .../tests/native_v8_runtime_inspector_tests.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc index 8082836f9..7fdf0097e 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc @@ -1126,14 +1126,14 @@ void test_v8_inspector_raw_cdp_session(webscene_engine* engine) session_id, std::string{"{\"id\":650,\"method\":\"Runtime.evaluate\",\"params\":{\"contextId\":"} + std::to_string(frame_context_id) - + R"(,"expression":"parent.__websceneDetachedFrameCallbacks = 0; const record = () => ++parent.__websceneDetachedFrameCallbacks; setTimeout(record, 100); setInterval(record, 100); requestAnimationFrame(record); const originalTimeout = globalThis.setTimeout; globalThis.setTimeout = callback => originalTimeout(callback, 100); const channel = new MessageChannel(); channel.port1.onmessage = record; channel.port2.postMessage('queued'); globalThis.setTimeout = originalTimeout;","returnByValue":true}})" ); + + R"(,"expression":"parent.__websceneDetachedFrameCallbacks = 0; parent.__websceneInspectorFrameDetached = false; const record = () => { if (parent.__websceneInspectorFrameDetached) ++parent.__websceneDetachedFrameCallbacks; }; setTimeout(record, 100); setInterval(record, 100); requestAnimationFrame(record); const originalTimeout = globalThis.setTimeout; globalThis.setTimeout = callback => originalTimeout(callback, 100); const channel = new MessageChannel(); channel.port1.onmessage = record; channel.port2.postMessage('queued'); globalThis.setTimeout = originalTimeout;","returnByValue":true}})" ); require( wait_for_inspector_message(messages, R"("id":650)"), "Inspector could not schedule detached-frame task fixtures"); dispatch_inspector_test_message( engine, session_id, - R"({"id":66,"method":"Runtime.evaluate","params":{"expression":"document.body.removeChild(globalThis.__inspectorLifecycleFrame); delete globalThis.__inspectorLifecycleFrame;","returnByValue":true}})"); + R"({"id":66,"method":"Runtime.evaluate","params":{"expression":"globalThis.__websceneInspectorFrameDetached = true; document.body.removeChild(globalThis.__inspectorLifecycleFrame); delete globalThis.__inspectorLifecycleFrame;","returnByValue":true}})"); require( wait_for_inspector_message(messages, R"("id":66)"), "Inspector iframe removal did not complete");