Files
electron/shell/renderer/preload_realm_global_scope.cc
T
RoboandSamuel Maddock a4b0cc65a5 refactor: generate PreloadRealmGlobalScope IDL bindings in //electron (#55165)
* refactor: use PreloadRealmGlobalScope for service worker preloads

Service worker preload scripts ran in Blink's ShadowRealmGlobalScope.
The ShadowRealm proposal has stalled, so Chromium may remove that class.

A new Chromium patch adds PreloadRealmGlobalScope, a global scope that
exposes the same interfaces without depending on any ShadowRealm code,
and Electron now creates the preload context with it.

Uncaught errors and unhandled rejections in a service worker preload
script are now reported through the worker's console. Before, they hit
NOTREACHED() in ShadowRealmGlobalScope.

Calling getEventListeners() from the DevTools console in the preload
context no longer crashes the renderer.

* refactor: generate PreloadRealmGlobalScope IDL bindings in //electron

* ci: normalize include paths in Windows gen headers for clang-tidy

The Windows clang-tidy job runs in a Linux container using generated
headers downloaded from out_gen_artifacts_win_x64. On Windows, Python
code generators such as inspector_protocol/code_generator.py use
os.path.join() and emit backslashes in #include directives, causing
Linux Siso scandeps and clang-tidy on RBE workers to fail with:

gen/third_party/blink/renderer/core/inspector/protocol/forward.h:15:10: error: 'third_party/inspector_protocol\crdtp/error_support.h' file not found [clang-diagnostic-error]
   15 | #include "third_party/inspector_protocol\crdtp/error_support.h"
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* fixup! normalize include paths in Windows gen headers for clang-tidy

* fix: resolve root worker context for ShadowRealms nested in preload realms

When a ShadowRealm is created inside a service worker preload realm
(e.g. with --harmony-shadow-realm), its initiator ExecutionContext is
PreloadRealmGlobalScope rather than a WorkerOrWorkletGlobalScope or
ShadowRealmGlobalScope. Previously, GetRootInitiatorExecutionContext()
only walked initiators via To<ShadowRealmGlobalScope>(), which either
stopped at PreloadRealmGlobalScope (failing the subsequent
To<WorkerOrWorkletGlobalScope>() cast on unhandled rejections or console
messages) or performed an invalid downcast to ShadowRealmGlobalScope.

Delegate GetRootInitiatorExecutionContext() to
initiator_execution_context_->GetRootExecutionContext() so nested
ShadowRealms walk through PreloadRealmGlobalScope to the underlying
ServiceWorkerGlobalScope.

* fix: use Supplement<ExecutionContext> for PreloadRealmLifetimeController

Replace V8 embedder data slot storage in PreloadRealmLifetimeController with
blink::Supplement<ExecutionContext> so it cannot collide with NodeBindings
storing node::IsolateData* in kElectronContextEmbedderDataIndex
or require a persistent self root.

* build: track blink pydeps

* chore: update patches

---------

Co-authored-by: Samuel Maddock <samuel.maddock@gmail.com>
2026-10-26 08:26:03 -07:00

159 lines
5.1 KiB
C++

// Copyright (c) 2026 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/renderer/preload_realm_global_scope.h"
#include <utility>
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/core/inspector/console_message.h" // nogncheck
#include "third_party/blink/renderer/core/inspector/inspector_audits_issue.h" // nogncheck
namespace blink {
PreloadRealmGlobalScope::PreloadRealmGlobalScope(
ExecutionContext* initiator_execution_context)
: ExecutionContext(initiator_execution_context->GetIsolate(),
initiator_execution_context->GetAgent()),
initiator_execution_context_(initiator_execution_context) {}
void PreloadRealmGlobalScope::Trace(Visitor* visitor) const {
visitor->Trace(initiator_execution_context_);
ScriptWrappable::Trace(visitor);
UniversalGlobalScope::Trace(visitor);
ExecutionContext::Trace(visitor);
}
ExecutionContext* PreloadRealmGlobalScope::GetExecutionContext() const {
return const_cast<PreloadRealmGlobalScope*>(this);
}
// Preload scripts reach the browser through Electron's IPC, never through
// Blink's own interfaces.
const BrowserInterfaceBrokerProxy&
PreloadRealmGlobalScope::GetBrowserInterfaceBroker() const {
return GetEmptyBrowserInterfaceBroker();
}
scoped_refptr<base::SingleThreadTaskRunner>
PreloadRealmGlobalScope::GetTaskRunner(TaskType task_type) {
return initiator_execution_context_->GetTaskRunner(task_type);
}
// Preload scripts are app code, so their feature usage is not counted as the
// initiator's.
void PreloadRealmGlobalScope::CountUse(mojom::blink::WebFeature feature) {}
void PreloadRealmGlobalScope::CountDeprecation(
mojom::blink::WebFeature feature) {}
void PreloadRealmGlobalScope::CountWebDXFeature(
mojom::blink::WebDXFeature feature) {}
// Return true so V8ScriptValueDeserializer::ExecutionContextExposesInterface
// allows structuredClone() of DOMException in preload realms without patching
// dom_exception.idl.
bool PreloadRealmGlobalScope::IsShadowRealmGlobalScope() const {
return true;
}
ExecutionContext* PreloadRealmGlobalScope::GetRootExecutionContext() {
return initiator_execution_context_->GetRootExecutionContext();
}
const KURL& PreloadRealmGlobalScope::Url() const {
return initiator_execution_context_->Url();
}
const KURL& PreloadRealmGlobalScope::BaseURL() const {
return initiator_execution_context_->BaseURL();
}
KURL PreloadRealmGlobalScope::CompleteURL(const String& url) const {
return initiator_execution_context_->CompleteURL(url);
}
// No Content Security Policy is ever bound to a preload realm, so nothing
// can ask it to disable eval.
void PreloadRealmGlobalScope::DisableEval(const String& error_message) {
NOTREACHED();
}
void PreloadRealmGlobalScope::SetWasmEvalErrorMessage(
const String& error_message) {
NOTREACHED();
}
String PreloadRealmGlobalScope::UserAgent() const {
return initiator_execution_context_->UserAgent();
}
HttpsState PreloadRealmGlobalScope::GetHttpsState() const {
return initiator_execution_context_->GetHttpsState();
}
// No interface that loads resources is exposed to a preload realm.
ResourceFetcher* PreloadRealmGlobalScope::Fetcher() {
NOTREACHED();
}
void PreloadRealmGlobalScope::ExceptionThrown(
ErrorEvent* error_event,
SanitizeScriptErrors sanitize_script_errors) {
initiator_execution_context_->ExceptionThrown(error_event,
sanitize_script_errors);
}
void PreloadRealmGlobalScope::AddInspectorIssue(AuditsIssue issue) {
initiator_execution_context_->AddInspectorIssue(std::move(issue));
}
// A preload realm has no `onerror`: uncaught errors go straight to
// ExceptionThrown().
EventTarget* PreloadRealmGlobalScope::ErrorEventTarget() {
return nullptr;
}
FrameOrWorkerScheduler* PreloadRealmGlobalScope::GetScheduler() {
return initiator_execution_context_->GetScheduler();
}
bool PreloadRealmGlobalScope::CrossOriginIsolatedCapability() const {
return false;
}
bool PreloadRealmGlobalScope::IsIsolatedContext() const {
return false;
}
ukm::UkmRecorder* PreloadRealmGlobalScope::UkmRecorder() {
return nullptr;
}
ukm::SourceId PreloadRealmGlobalScope::UkmSourceID() const {
return ukm::kInvalidSourceId;
}
// A preload realm identifies as its initiator, like an isolated world
// identifies as its frame. A token type of its own would have to be taught to
// every consumer of ExecutionContextToken in the browser process.
ExecutionContextToken PreloadRealmGlobalScope::GetExecutionContextToken()
const {
return initiator_execution_context_->GetExecutionContextToken();
}
bool PreloadRealmGlobalScope::IsSecureContext() const {
return initiator_execution_context_->IsSecureContext();
}
void PreloadRealmGlobalScope::AddConsoleMessageImpl(ConsoleMessage* message,
bool discard_duplicates) {
initiator_execution_context_->AddConsoleMessage(message, discard_duplicates);
}
} // namespace blink