diff --git a/BUILD.gn b/BUILD.gn index 9d7a235403..f8d9354cdf 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -477,6 +477,102 @@ action("electron_generate_node_defines") { args = [ rebase_path(target_gen_dir) ] + rebase_path(inputs) } +action("electron_generate_preload_realm_bindings") { + script = "build/generate_preload_realm_bindings.py" + + core_idl_files = [ + "//third_party/blink/renderer/core/dom/abort_controller.idl", + "//third_party/blink/renderer/core/dom/abort_signal.idl", + "//third_party/blink/renderer/core/dom/dom_exception.idl", + "//third_party/blink/renderer/core/dom/events/custom_event.idl", + "//third_party/blink/renderer/core/dom/events/event.idl", + "//third_party/blink/renderer/core/dom/events/event_target.idl", + "//third_party/blink/renderer/core/dom/observable.idl", + "//third_party/blink/renderer/core/dom/quota_exceeded_error.idl", + "//third_party/blink/renderer/core/dom/subscriber.idl", + "//third_party/blink/renderer/core/dom/void_function.idl", + "//third_party/blink/renderer/core/events/error_event.idl", + "//third_party/blink/renderer/core/events/promise_rejection_event.idl", + "//third_party/blink/renderer/core/frame/universal_global_scope.idl", + "//third_party/blink/renderer/core/messaging/structured_serialize_options.idl", + "//third_party/blink/renderer/core/streams/byte_length_queuing_strategy.idl", + "//third_party/blink/renderer/core/streams/count_queuing_strategy.idl", + "//third_party/blink/renderer/core/streams/readable_byte_stream_controller.idl", + "//third_party/blink/renderer/core/streams/readable_stream.idl", + "//third_party/blink/renderer/core/streams/readable_stream_byob_reader.idl", + "//third_party/blink/renderer/core/streams/readable_stream_byob_request.idl", + "//third_party/blink/renderer/core/streams/readable_stream_default_controller.idl", + "//third_party/blink/renderer/core/streams/readable_stream_default_reader.idl", + "//third_party/blink/renderer/core/streams/transform_stream.idl", + "//third_party/blink/renderer/core/streams/transform_stream_default_controller.idl", + "//third_party/blink/renderer/core/streams/writable_stream.idl", + "//third_party/blink/renderer/core/streams/writable_stream_default_controller.idl", + "//third_party/blink/renderer/core/streams/writable_stream_default_writer.idl", + "//third_party/blink/renderer/core/url/url.idl", + "//third_party/blink/renderer/core/url/url_search_params.idl", + "//third_party/blink/renderer/core/url_pattern/url_pattern.idl", + ] + modules_idl_files = [ + "//third_party/blink/renderer/modules/compression/compression_stream.idl", + "//third_party/blink/renderer/modules/compression/decompression_stream.idl", + "//third_party/blink/renderer/modules/encoding/text_decoder.idl", + "//third_party/blink/renderer/modules/encoding/text_decoder_stream.idl", + "//third_party/blink/renderer/modules/encoding/text_encoder.idl", + "//third_party/blink/renderer/modules/encoding/text_encoder_stream.idl", + ] + idl_file = "shell/renderer/preload_realm_global_scope.idl" + runtime_enabled_features = + "//third_party/blink/renderer/platform/runtime_enabled_features.json5" + wrapper_type_info_header = + "//third_party/blink/renderer/platform/bindings/wrapper_type_info.h" + + inputs = [ + idl_file, + runtime_enabled_features, + wrapper_type_info_header, + ] + core_idl_files + modules_idl_files + + # Read Blink's own .pydeps files directly instead of using action_with_pydeps + # (which expects a checked-in build/generate_preload_realm_bindings.pydeps) + # so we automatically pick up upstream Blink bindings script changes on rolls + # without manually maintaining a duplicate .pydeps file in //electron. + _blink_scripts_dir = "//third_party/blink/renderer/bindings/scripts" + _pydeps_files = [ + "$_blink_scripts_dir/collect_idl_files.pydeps", + "$_blink_scripts_dir/generate_bindings.pydeps", + ] + _pydeps_lines = [] + foreach(_pydeps_file, _pydeps_files) { + _pydeps_lines += read_file(_pydeps_file, "list lines") + } + _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ]) + inputs += + rebase_path(_pydeps_entries, ".", _blink_scripts_dir) + _pydeps_files + + outputs = [ + "$target_gen_dir/preload_realm/v8_preload_realm_global_scope.cc", + "$target_gen_dir/preload_realm/v8_preload_realm_global_scope.h", + ] + + args = + [ + "--root-src-dir", + rebase_path("//", root_build_dir), + "--root-gen-dir", + rebase_path(root_gen_dir, root_build_dir), + "--output-reldir", + rebase_path("$target_gen_dir/preload_realm", root_gen_dir), + "--idl-file", + rebase_path(idl_file, root_build_dir), + "--runtime-enabled-features", + rebase_path(runtime_enabled_features, root_build_dir), + "--wrapper-type-info-header", + rebase_path(wrapper_type_info_header, root_build_dir), + "--core-idl-files", + ] + rebase_path(core_idl_files, root_build_dir) + + [ "--modules-idl-files" ] + rebase_path(modules_idl_files, root_build_dir) +} + source_set("electron_lib") { configs += [ "//v8:external_startup_data", @@ -491,6 +587,7 @@ source_set("electron_lib") { deps = [ ":electron_fuses", ":electron_generate_node_defines", + ":electron_generate_preload_realm_bindings", ":electron_js2c", ":electron_version_header", ":resources", @@ -633,7 +730,8 @@ source_set("electron_lib") { deps += [ "//electron/build/config:generate_mas_config" ] - sources = filenames.lib_sources + sources = filenames.lib_sources + + get_target_outputs(":electron_generate_preload_realm_bindings") if (is_win) { sources += filenames.lib_sources_win } diff --git a/build/generate_preload_realm_bindings.py b/build/generate_preload_realm_bindings.py new file mode 100644 index 0000000000..7a036a81dc --- /dev/null +++ b/build/generate_preload_realm_bindings.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +"""Generates Blink V8 bindings for PreloadRealmGlobalScope.""" + +import argparse +import os +import sys +import tempfile + + +def setup_blink_paths(root_src_dir): + module_dirs = ( + os.path.join(root_src_dir, 'third_party', 'blink', 'renderer', 'bindings', + 'scripts'), + os.path.join(root_src_dir, 'third_party', 'blink', 'renderer', 'build', + 'scripts'), + os.path.join(root_src_dir, 'third_party', 'mako', 'mako'), + os.path.join(root_src_dir, 'tools'), + ) + for module_dir in reversed(module_dirs): + if module_dir not in sys.path: + sys.path.insert(1, module_dir) + + +# Interfaces that should be [Exposed=*] per spec (crbug.com/41480387 and +# crbug.com/40282760) and are exposed on PreloadRealmGlobalScope in addition +# to constructs already marked [Exposed=*]. +PRELOAD_REALM_EXPOSED_INTERFACES = frozenset([ + 'AbortController', + 'AbortSignal', + 'CompressionStream', + 'CustomEvent', + 'DOMException', + 'DecompressionStream', + 'ErrorEvent', + 'Event', + 'EventTarget', + 'Observable', + 'PromiseRejectionEvent', + 'Subscriber', + 'TextDecoder', + 'TextDecoderStream', + 'TextEncoder', + 'TextEncoderStream', + 'URL', + 'URLPattern', + 'URLSearchParams', +]) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--root-src-dir', required=True) + parser.add_argument('--root-gen-dir', required=True) + parser.add_argument('--output-reldir', required=True) + parser.add_argument('--idl-file', required=True) + parser.add_argument('--runtime-enabled-features', required=True) + parser.add_argument('--wrapper-type-info-header', required=True) + parser.add_argument('--core-idl-files', nargs='+', required=True) + parser.add_argument('--modules-idl-files', nargs='+', required=True) + options = parser.parse_args() + + import re + with open(options.wrapper_type_info_header, encoding='utf-8') as f: + wti_content = f.read() + start_match = re.search( + r'static_assert\(kScriptWrappableStartTag\s*==\s*(\d+)\)', wti_content) + last_match = re.search( + r'kLastGeneratedScriptWrappableTag\s*=\s*(\d+)', wti_content) + assert start_match and last_match, ( + 'Could not parse ScriptWrappable tag bounds from wrapper_type_info.h') + start_tag = int(start_match.group(1)) + last_generated_tag = int(last_match.group(1)) + assert last_generated_tag - start_tag >= 1600, ( + f'ScriptWrappable tag range [{start_tag}, {last_generated_tag}) is too ' + 'narrow; verify kLastGeneratedScriptWrappableTag - 1 does not collide.') + + setup_blink_paths(options.root_src_dir) + + import bind_gen + from bind_gen import codegen_expr + from bind_gen import codegen_utils + from bind_gen.interface import generate_class_like + from idl_parser import idl_lexer + from idl_parser import idl_parser + import web_idl + from web_idl.idl_compiler import IdlCompiler + from web_idl.idl_type import IdlTypeFactory + from web_idl.ir_builder import load_and_register_idl_definitions + from web_idl.ir_map import IRMap + from web_idl.reference import RefByIdFactory + + lexer = idl_lexer.IDLLexer() + idl_file_parser = idl_parser.IDLParser(lexer) + + def strip_non_global_defs(ast_node): + # Only Interface, InterfaceMixin (for UniversalGlobalScope), Includes, + # Dictionary (for StructuredSerializeOptions), and Callback (for + # VoidFunction) are needed to compile PreloadRealmGlobalScope. Stripping + # members and auxiliary definitions from the exposed interface files avoids + # pulling in their transitive argument/attribute types and mixins. + children = getattr(ast_node, '_children', None) + if not children: + return ast_node + kept = [] + for child in children: + cls = child.GetClass() + name = child.GetName() + if cls == 'Interface': + if not child.GetProperty('MIXIN') and name != 'PreloadRealmGlobalScope': + child._children = [ + c for c in (child._children or []) + if c.GetClass() == 'ExtAttributes' + ] + kept.append(child) + elif cls == 'Includes' and name == 'PreloadRealmGlobalScope': + kept.append(child) + elif cls == 'Dictionary' and name == 'StructuredSerializeOptions': + kept.append(child) + elif cls == 'Callback' and name == 'VoidFunction': + kept.append(child) + ast_node._children = kept + return ast_node + + core_ast = web_idl.AstGroup( + component=web_idl.Component('core'), + for_testing=False, + ) + for filepath in options.core_idl_files: + core_ast.add_ast_node( + strip_non_global_defs(idl_parser.ParseFile(idl_file_parser, filepath))) + + modules_ast = web_idl.AstGroup( + component=web_idl.Component('modules'), + for_testing=False, + ) + for filepath in list(options.modules_idl_files) + [options.idl_file]: + modules_ast.add_ast_node( + strip_non_global_defs(idl_parser.ParseFile(idl_file_parser, filepath))) + + with tempfile.NamedTemporaryFile(suffix='.pickle', delete=False) as tmp_core: + tmp_core_path = tmp_core.name + with tempfile.NamedTemporaryFile( + suffix='.pickle', delete=False) as tmp_modules: + tmp_modules_path = tmp_modules.name + with tempfile.NamedTemporaryFile(suffix='.pickle', delete=False) as tmp_db: + tmp_db_path = tmp_db.name + + try: + core_ast.write_to_file(tmp_core_path) + modules_ast.write_to_file(tmp_modules_path) + + web_idl.init( + runtime_enabled_features_paths=[options.runtime_enabled_features]) + + ir_map = IRMap() + ref_to_idl_def_factory = RefByIdFactory() + idl_type_factory = IdlTypeFactory() + + load_and_register_idl_definitions( + filepaths=[tmp_core_path, tmp_modules_path], + register_ir=ir_map.register, + create_ref_to_idl_def=ref_to_idl_def_factory.create, + idl_type_factory=idl_type_factory, + ) + + for ir in ir_map.irs_of_kind(IRMap.IR.Kind.INTERFACE): + if ir.identifier in PRELOAD_REALM_EXPOSED_INTERFACES: + ir.exposure.add_global_name_and_feature('PreloadRealm') + + errors = [] + compiler = IdlCompiler( + ir_map=ir_map, + ref_to_idl_def_factory=ref_to_idl_def_factory, + idl_type_factory=idl_type_factory, + report_error=errors.append, + ) + database = compiler.build_database() + if errors: + sys.exit('\n'.join(errors)) + + preload_iface = database.find(web_idl.Identifier('PreloadRealmGlobalScope')) + preload_iface._code_generator_info._blink_headers = ( + 'electron/shell/renderer/preload_realm_global_scope.h',) + # Use `blink::kLastGeneratedScriptWrappableTag - 1` so the tag automatically + # tracks Blink's generated ScriptWrappable tag upper bound in + # wrapper_type_info.h. + preload_tag_expr = 'blink::kLastGeneratedScriptWrappableTag - 1' + preload_iface._tag = preload_tag_expr + preload_iface._max_subclass_tag = preload_tag_expr + + database.write_to_file(tmp_db_path) + + # 3. Configure bind_gen to emit single-component bindings into output_reldir + # without MODULES_EXPORT DLL macro annotations. + orig_expr_from_exposure = codegen_expr.expr_from_exposure + + def patched_expr_from_exposure(exposure, **kwargs): + if any(e.global_name == 'PreloadRealm' + for e in exposure.global_names_and_features) and not kwargs.get( + 'global_names'): + return codegen_expr._Expr('true') + return orig_expr_from_exposure(exposure, **kwargs) + + codegen_expr.expr_from_exposure = patched_expr_from_exposure + bind_gen.interface.expr_from_exposure = patched_expr_from_exposure + codegen_utils.component_export = lambda component, for_testing: '' + bind_gen.interface.component_export = codegen_utils.component_export + codegen_utils.component_export_header = lambda component, for_testing: None + bind_gen.interface.component_export_header = ( + codegen_utils.component_export_header) + + from bind_gen.union_name_mapper import UnionNameMapper + UnionNameMapper.init = classmethod( + lambda cls, filename, database: cls.init_with_config_text('', database)) + + component_reldirs = { + web_idl.Component('core'): + 'third_party/blink/renderer/bindings/core/v8', + web_idl.Component('modules'): + 'third_party/blink/renderer/bindings/modules/v8', + } + bind_gen.init( + web_idl_database_path=tmp_db_path, + root_src_dir=options.root_src_dir, + root_gen_dir=options.root_gen_dir, + component_reldirs=component_reldirs, + enable_style_format=False, + enable_code_generation_tracing=False, + ) + + # Route PreloadRealmGlobalScope's own output files to Electron's gen dir + # while keeping core/modules paths intact for #include references to Blink + # interfaces. + from bind_gen.path_manager import PathManager + orig_path_manager_init = PathManager.__init__ + + def patched_path_manager_init(self, idl_definition): + orig_path_manager_init(self, idl_definition) + if getattr(idl_definition, 'identifier', + None) == 'PreloadRealmGlobalScope': + norm_out = os.path.normpath(options.output_reldir).replace('\\', '/') + self._api_dir = norm_out + self._impl_dir = norm_out + + PathManager.__init__ = patched_path_manager_init + + db = bind_gen.package_initializer.package_initializer().web_idl_database() + generate_class_like(db.find(web_idl.Identifier('PreloadRealmGlobalScope'))) + finally: + for path in (tmp_core_path, tmp_modules_path, tmp_db_path): + if os.path.exists(path): + os.remove(path) + + +if __name__ == '__main__': + main() diff --git a/filenames.gni b/filenames.gni index c56b7c841a..33d870a95d 100644 --- a/filenames.gni +++ b/filenames.gni @@ -829,6 +829,8 @@ filenames = { "shell/renderer/preload_environment.h", "shell/renderer/preload_realm_context.cc", "shell/renderer/preload_realm_context.h", + "shell/renderer/preload_realm_global_scope.cc", + "shell/renderer/preload_realm_global_scope.h", "shell/renderer/preload_utils.cc", "shell/renderer/preload_utils.h", "shell/renderer/renderer_client_base.cc", diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 70f38ec162..f23e958435 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -139,4 +139,5 @@ fix_plumb_the_requesting_frame_through_pointer_lock_requests.patch feat_let_the_host_of_a_remote_nswindow_set_more_of_its_behavior.patch device_make_devicemonitormessagewindow_a_nodestructor.patch fix_handle_a_null_pending_call_in_dbus_objectproxy.patch -feat_add_preloadrealmglobalscope.patch +feat_update_executioncontext_api.patch +use_forward_slashes_for_the_default_crdtp_include_dir_in_templates.patch diff --git a/patches/chromium/feat_add_preloadrealmglobalscope.patch b/patches/chromium/feat_add_preloadrealmglobalscope.patch deleted file mode 100644 index 3f8757ac46..0000000000 --- a/patches/chromium/feat_add_preloadrealmglobalscope.patch +++ /dev/null @@ -1,686 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Samuel Maddock -Date: Tue, 20 Oct 2026 18:54:12 +0000 -Subject: feat: add PreloadRealmGlobalScope - -Electron runs service worker preload scripts in a context of their own on -the worker's thread. That context used to be a Blink ShadowRealmGlobalScope, -which exists for the TC39 ShadowRealm proposal. The proposal has stalled, so -Chromium may remove its implementation. - -PreloadRealmGlobalScope is a global scope for that context which does not -depend on any ShadowRealm code. It is exposed the same interfaces as a -ShadowRealm, which are the ones the specs mark [Exposed=*], and defers to -the context that created it for everything else. Unlike a ShadowRealm, it -reports uncaught errors and console messages through that context instead -of treating them as unreachable. - -Electron creates the context itself, in -shell/renderer/preload_realm_context.cc. This patch is specific to Electron -and is not intended to be upstreamed. - -diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc -index 41a5db4e019084d47763a43bd548b29faa070660..3e62033228307ef2474c91e250db08cd72934ad6 100644 ---- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc -+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc -@@ -78,6 +78,7 @@ - #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" - #include "third_party/blink/renderer/core/inspector/main_thread_debugger.h" - #include "third_party/blink/renderer/core/page/page.h" -+#include "third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.h" - #include "third_party/blink/renderer/core/script/modulator.h" - #include "third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h" - #include "third_party/blink/renderer/core/timing/time_clamper.h" -@@ -448,6 +449,11 @@ static void PromiseRejectHandlerInWorker(v8::PromiseRejectMessage data) { - ExecutionContext* execution_context = ExecutionContext::From(script_state); - if (!execution_context) - return; -+ // An Electron preload realm has no script controller of its own. -+ if (auto* preload_realm = -+ DynamicTo(execution_context)) { -+ execution_context = preload_realm->GetInitiatorExecutionContext(); -+ } - - ExecutionContext* root_worker_context = - execution_context->IsShadowRealmGlobalScope() -diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni -index 788f261f96ef65df2bc7fef0fa2a793da22e44ca..7b8efe1bc88c18e3ccf6e9a663c6670f46e8bea6 100644 ---- a/third_party/blink/renderer/bindings/generated_in_core.gni -+++ b/third_party/blink/renderer/bindings/generated_in_core.gni -@@ -1530,6 +1530,8 @@ generated_interface_sources_in_core = [ - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_preference_manager.h", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_preference_object.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_preference_object.h", -+ "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_preload_realm_global_scope.cc", -+ "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_preload_realm_global_scope.h", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_progress_event.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_progress_event.h", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_promise_rejection_event.cc", -diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni -index b56e9a96ebd3d598e53848fa955d02ac45628900..4f4d8a5ec938f7b4ccd336b64221d62017fb83b4 100644 ---- a/third_party/blink/renderer/bindings/generated_in_modules.gni -+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni -@@ -2793,6 +2793,8 @@ generated_interface_sources_in_modules = [ - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_plugin.h", - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_plugin_array.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_plugin_array.h", -+ "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_preload_realm_global_scope.cc", -+ "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_preload_realm_global_scope.h", - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_presentation.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_presentation.h", - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_presentation_availability.cc", -diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni -index f8d8e7180904bdf3a330483b8e6e7c3bf5dde439..9f495478b3a5164db9f113041a8e4e38ffb1d0fd 100644 ---- a/third_party/blink/renderer/bindings/idl_in_core.gni -+++ b/third_party/blink/renderer/bindings/idl_in_core.gni -@@ -828,6 +828,7 @@ static_idl_files_in_core = [ - "//third_party/blink/renderer/core/preferences/preference_manager.idl", - "//third_party/blink/renderer/core/preferences/preference_object.idl", - "//third_party/blink/renderer/core/preferences/navigator_preferences.idl", -+ "//third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.idl", - "//third_party/blink/renderer/core/url/origin.idl", - "//third_party/blink/renderer/core/url/url.idl", - "//third_party/blink/renderer/core/url/url_search_params.idl", -diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py -index aafb09af75d6adb990bf7b71f3ea5d4abe5da2dd..e7a7c0ec1e80b69c8cda9c474861fc770294c8a2 100644 ---- a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py -+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py -@@ -287,6 +287,7 @@ def expr_from_exposure( - "DedicatedWorker": "IsDedicatedWorkerGlobalScope", - "LayoutWorklet": "IsLayoutWorkletGlobalScope", - "PaintWorklet": "IsPaintWorkletGlobalScope", -+ "PreloadRealm": "IsPreloadRealmGlobalScope", - "ServiceWorker": "IsServiceWorkerGlobalScope", - "ShadowRealm": "IsShadowRealmGlobalScope", - "SharedWorker": "IsSharedWorkerGlobalScope", -diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn -index 12942929d97d2bfff86fc43c506253934fd8e68e..a56872b923bd6944a9821ee455654b18daed26f1 100644 ---- a/third_party/blink/renderer/core/BUILD.gn -+++ b/third_party/blink/renderer/core/BUILD.gn -@@ -65,6 +65,7 @@ import("//third_party/blink/renderer/core/page/build.gni") - import("//third_party/blink/renderer/core/paint/build.gni") - import("//third_party/blink/renderer/core/permissions_policy/build.gni") - import("//third_party/blink/renderer/core/preferences/build.gni") -+import("//third_party/blink/renderer/core/preload_realm/build.gni") - import("//third_party/blink/renderer/core/resize_observer/build.gni") - import("//third_party/blink/renderer/core/route_matching/build.gni") - import("//third_party/blink/renderer/core/sanitizer/build.gni") -@@ -361,6 +362,7 @@ component("core") { - sources += - rebase_path(blink_core_sources_xmlhttprequest, "", "xmlhttprequest") - sources += rebase_path(blink_core_sources_preferences, "", "preferences") -+ sources += rebase_path(blink_core_sources_preload_realm, "", "preload_realm") - - configs -= core_config_remove - configs += core_config_add -diff --git a/third_party/blink/renderer/core/dom/abort_controller.idl b/third_party/blink/renderer/core/dom/abort_controller.idl -index 563b583bb9d64b350e3fdb0969901e8c674dc47c..427efba591af98680cfe59fa3f2089085d431599 100644 ---- a/third_party/blink/renderer/core/dom/abort_controller.idl -+++ b/third_party/blink/renderer/core/dom/abort_controller.idl -@@ -5,7 +5,7 @@ - // https://dom.spec.whatwg.org/#interface-abortcontroller - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface AbortController { - [CallWith=ScriptState, Measure] constructor(); - [SameObject] readonly attribute AbortSignal signal; -diff --git a/third_party/blink/renderer/core/dom/abort_signal.idl b/third_party/blink/renderer/core/dom/abort_signal.idl -index d4ed5c6639d848a71f40d866cf53516873e05292..99620265ccb9a98ea8513f75b55ecdff4b9c596f 100644 ---- a/third_party/blink/renderer/core/dom/abort_signal.idl -+++ b/third_party/blink/renderer/core/dom/abort_signal.idl -@@ -5,7 +5,7 @@ - // https://dom.spec.whatwg.org/#interface-AbortSignal - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface AbortSignal : EventTarget { - [ - CallWith=ScriptState, -diff --git a/third_party/blink/renderer/core/dom/dom_exception.idl b/third_party/blink/renderer/core/dom/dom_exception.idl -index 6e829626ffac53dd947b599ea374016ff73a8557..60a2a94c51167952aa0e7159f61c2ffd1e638770 100644 ---- a/third_party/blink/renderer/core/dom/dom_exception.idl -+++ b/third_party/blink/renderer/core/dom/dom_exception.idl -@@ -31,7 +31,7 @@ - // https://webidl.spec.whatwg.org/#es-DOMException - - [ -- Exposed=(Window,Worker,ShadowRealm), // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm), // TODO(crbug.com/41480387): This should be Exposed=* - Serializable - ] interface DOMException { - constructor(optional DOMString message = "", optional DOMString name = "Error"); -diff --git a/third_party/blink/renderer/core/dom/events/custom_event.idl b/third_party/blink/renderer/core/dom/events/custom_event.idl -index 200c6b8f30b122fcbbd5c76fecdd83350c81a9b7..6c002b630bda97a3571a5b16121bb620773e91b8 100644 ---- a/third_party/blink/renderer/core/dom/events/custom_event.idl -+++ b/third_party/blink/renderer/core/dom/events/custom_event.idl -@@ -26,7 +26,7 @@ - // https://dom.spec.whatwg.org/#interface-customevent - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface CustomEvent : Event { - [CallWith=ScriptState] constructor(DOMString type, optional CustomEventInit eventInitDict = {}); - [CallWith=ScriptState] readonly attribute any detail; -diff --git a/third_party/blink/renderer/core/dom/events/event.idl b/third_party/blink/renderer/core/dom/events/event.idl -index 11e459da0f5d2b05d32ca8d483ed784473beed69..6cc8da25f79ca2aad3c52b79e17fd7b9a2e6280a 100644 ---- a/third_party/blink/renderer/core/dom/events/event.idl -+++ b/third_party/blink/renderer/core/dom/events/event.idl -@@ -21,7 +21,7 @@ - // https://dom.spec.whatwg.org/#interface-event - - [ -- Exposed=(Window,Worker,AudioWorklet,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,AudioWorklet,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface Event { - constructor(DOMString type, optional EventInit eventInitDict = {}); - readonly attribute DOMString type; -diff --git a/third_party/blink/renderer/core/dom/events/event_target.idl b/third_party/blink/renderer/core/dom/events/event_target.idl -index 24e54a9c02041b53c79657e8143f4f063f9cd161..803d937fabf31619dea6caa58b5e16c51fda0920 100644 ---- a/third_party/blink/renderer/core/dom/events/event_target.idl -+++ b/third_party/blink/renderer/core/dom/events/event_target.idl -@@ -26,7 +26,7 @@ dictionary ObservableEventListenerOptions { - }; - - [ -- Exposed=(Window,Worker,AudioWorklet,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,AudioWorklet,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface EventTarget { - [CallWith=ScriptState] constructor(); - undefined addEventListener(DOMString type, EventListener? listener, optional (AddEventListenerOptions or boolean) options); -diff --git a/third_party/blink/renderer/core/dom/observable.idl b/third_party/blink/renderer/core/dom/observable.idl -index 79410b5072c589f34cf180b10d1861e1a5516627..69cd9ce2fac2fe0f6cc1570ccd7a48bf8f1875c3 100644 ---- a/third_party/blink/renderer/core/dom/observable.idl -+++ b/third_party/blink/renderer/core/dom/observable.idl -@@ -45,7 +45,7 @@ dictionary SubscribeOptions { - typedef (ObserverCallback or Observer) ObserverUnion; - typedef (ObserverCallback or ObservableInspector) ObservableInspectorUnion; - --[Exposed=(Window,Worker,ShadowRealm)] // TODO(crbug.com/40282760): This should be Exposed=* -+[Exposed=(Window,Worker,ShadowRealm,PreloadRealm)] // TODO(crbug.com/40282760): This should be Exposed=* - interface Observable { - [CallWith=ScriptState, MeasureAs=ObservableConstructor] constructor(SubscribeCallback callback); - [CallWith=ScriptState] void subscribe(optional ObserverUnion observer = {}, optional SubscribeOptions options = {}); -diff --git a/third_party/blink/renderer/core/dom/subscriber.idl b/third_party/blink/renderer/core/dom/subscriber.idl -index 9a09310ba30b872bc8b9d6f78d34d07717f61611..45dc4b90d9e818c36f4553730578ac5a00f497da 100644 ---- a/third_party/blink/renderer/core/dom/subscriber.idl -+++ b/third_party/blink/renderer/core/dom/subscriber.idl -@@ -4,7 +4,7 @@ - - // https://github.com/WICG/observable - --[Exposed=(Window,Worker,ShadowRealm)] // TODO(crbug.com/40282760): This should be Exposed=* -+[Exposed=(Window,Worker,ShadowRealm,PreloadRealm)] // TODO(crbug.com/40282760): This should be Exposed=* - interface Subscriber { - void next(any result); - [CallWith=ScriptState] void error(any error); -diff --git a/third_party/blink/renderer/core/events/error_event.idl b/third_party/blink/renderer/core/events/error_event.idl -index cacba4123e3b97e085c3ea6023eb2ef3efc5a5b5..f176f64f7d3d323a83a5fb1a17a7d150ba032ca2 100644 ---- a/third_party/blink/renderer/core/events/error_event.idl -+++ b/third_party/blink/renderer/core/events/error_event.idl -@@ -31,7 +31,7 @@ - // https://html.spec.whatwg.org/C/#the-errorevent-interface - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface ErrorEvent : Event { - [CallWith=ScriptState] constructor(DOMString type, optional ErrorEventInit eventInitDict = {}); - readonly attribute DOMString message; -diff --git a/third_party/blink/renderer/core/events/promise_rejection_event.idl b/third_party/blink/renderer/core/events/promise_rejection_event.idl -index 693b2e78ee0e9dfb68bd0b08565ea76ce6711620..504f48fb2e3816686b75bb14798e51e722b62ffe 100644 ---- a/third_party/blink/renderer/core/events/promise_rejection_event.idl -+++ b/third_party/blink/renderer/core/events/promise_rejection_event.idl -@@ -5,7 +5,7 @@ - // https://html.spec.whatwg.org/C/#promiserejectionevent - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface PromiseRejectionEvent : Event { - [CallWith=ScriptState] constructor(DOMString type, PromiseRejectionEventInit eventInitDict); - [CallWith=ScriptState] readonly attribute object promise; -diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h -index 07c9dd9c4ffa9b0b805187acce4f0eef4b641ceb..c73a5b68cc3d0b0a813779b1a11812e144a767c5 100644 ---- a/third_party/blink/renderer/core/execution_context/execution_context.h -+++ b/third_party/blink/renderer/core/execution_context/execution_context.h -@@ -160,6 +160,7 @@ class CORE_EXPORT ExecutionContext : public Supplementable, - virtual bool IsAudioWorkletGlobalScope() const { return false; } - virtual bool IsLayoutWorkletGlobalScope() const { return false; } - virtual bool IsPaintWorkletGlobalScope() const { return false; } -+ virtual bool IsPreloadRealmGlobalScope() const { return false; } - virtual bool IsThreadedWorkletGlobalScope() const { return false; } - virtual bool IsShadowRealmGlobalScope() const { return false; } - virtual bool IsJSExecutionForbidden() const { return false; } -diff --git a/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc b/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc -index 3a52d94a105bcc34c2319940eb10ff381fe975ba..b3e3b1088239342d31090258e2d4b81d58d76fd0 100644 ---- a/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc -+++ b/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc -@@ -38,6 +38,7 @@ - #include "third_party/blink/renderer/core/inspector/identifiers_factory.h" - #include "third_party/blink/renderer/core/inspector/v8_inspector_string.h" - #include "third_party/blink/renderer/core/inspector/worker_inspector_controller.h" -+#include "third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.h" - #include "third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h" - #include "third_party/blink/renderer/core/workers/dedicated_worker_global_scope.h" - #include "third_party/blink/renderer/core/workers/worker_global_scope.h" -@@ -81,6 +82,10 @@ void WorkerThreadDebugger::ReportConsoleMessage( - SourceLocation* location) { - if (!context) - return; -+ // An Electron preload realm has no worker thread of its own. -+ if (auto* preload_realm = DynamicTo(context)) { -+ context = preload_realm->GetInitiatorExecutionContext(); -+ } - - ExecutionContext* root_worker_context = - context->IsShadowRealmGlobalScope() -@@ -168,6 +173,10 @@ void WorkerThreadDebugger::ExceptionThrown(WorkerThread* worker_thread, - } - - int WorkerThreadDebugger::ContextGroupId(ExecutionContext* context) { -+ // An Electron preload realm belongs to its initiator's context group. -+ if (auto* preload_realm = DynamicTo(context)) { -+ context = preload_realm->GetInitiatorExecutionContext(); -+ } - return ContextGroupId(To(context)->GetThread()); - } - -diff --git a/third_party/blink/renderer/core/preload_realm/build.gni b/third_party/blink/renderer/core/preload_realm/build.gni -new file mode 100644 -index 0000000000000000000000000000000000000000..95ad9051f0e81374443f4a7bb9d8e74fd39d230b ---- /dev/null -+++ b/third_party/blink/renderer/core/preload_realm/build.gni -@@ -0,0 +1,8 @@ -+# Copyright (c) 2026 Anthropic, PBC. -+# Use of this source code is governed by the MIT license that can be -+# found in the LICENSE file. -+ -+blink_core_sources_preload_realm = [ -+ "preload_realm_global_scope.cc", -+ "preload_realm_global_scope.h", -+] -diff --git a/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.cc b/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.cc -new file mode 100644 -index 0000000000000000000000000000000000000000..51a32a4a8d16c7286cd6d1c288397d6b93fc1454 ---- /dev/null -+++ b/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.cc -@@ -0,0 +1,149 @@ -+// Copyright (c) 2026 Anthropic, PBC. -+// Use of this source code is governed by the MIT license that can be -+// found in the LICENSE file. -+ -+#include "third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.h" -+ -+#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" -+#include "third_party/blink/renderer/core/inspector/inspector_audits_issue.h" -+ -+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(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 -+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) {} -+ -+bool PreloadRealmGlobalScope::IsPreloadRealmGlobalScope() const { -+ return true; -+} -+ -+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 -diff --git a/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.h b/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.h -new file mode 100644 -index 0000000000000000000000000000000000000000..4d525b9ddf3009dd93280901a129a72c7a8a8990 ---- /dev/null -+++ b/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.h -@@ -0,0 +1,89 @@ -+// Copyright (c) 2026 Anthropic, PBC. -+// Use of this source code is governed by the MIT license that can be -+// found in the LICENSE file. -+ -+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PRELOAD_REALM_PRELOAD_REALM_GLOBAL_SCOPE_H_ -+#define THIRD_PARTY_BLINK_RENDERER_CORE_PRELOAD_REALM_PRELOAD_REALM_GLOBAL_SCOPE_H_ -+ -+#include "base/task/single_thread_task_runner.h" -+#include "third_party/blink/renderer/core/core_export.h" -+#include "third_party/blink/renderer/core/execution_context/execution_context.h" -+#include "third_party/blink/renderer/core/frame/universal_global_scope.h" -+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" -+#include "third_party/blink/renderer/platform/weborigin/kurl.h" -+#include "third_party/blink/renderer/platform/wtf/casting.h" -+ -+namespace blink { -+ -+// Global scope of the context Electron runs preload scripts in when the -+// context they are attached to (the "initiator") has no isolated worlds of its -+// own, as is the case for service workers. It exposes only the interfaces -+// marked [Exposed=PreloadRealm] or [Exposed=*] and otherwise defers to the -+// initiator, which owns it. -+class CORE_EXPORT PreloadRealmGlobalScope final : public ScriptWrappable, -+ public UniversalGlobalScope, -+ public ExecutionContext { -+ DEFINE_WRAPPERTYPEINFO(); -+ -+ public: -+ explicit PreloadRealmGlobalScope( -+ ExecutionContext* initiator_execution_context); -+ -+ ExecutionContext* GetInitiatorExecutionContext() const { -+ return initiator_execution_context_.Get(); -+ } -+ -+ void Trace(Visitor* visitor) const override; -+ -+ // UniversalGlobalScope: -+ ExecutionContext* GetExecutionContext() const override; -+ -+ // MojoBindingContext: -+ const BrowserInterfaceBrokerProxy& GetBrowserInterfaceBroker() const override; -+ scoped_refptr GetTaskRunner( -+ TaskType task_type) override; -+ -+ // UseCounter: -+ void CountUse(mojom::blink::WebFeature feature) override; -+ void CountDeprecation(mojom::blink::WebFeature feature) override; -+ void CountWebDXFeature(mojom::blink::WebDXFeature feature) override; -+ -+ // ExecutionContext: -+ bool IsPreloadRealmGlobalScope() const override; -+ const KURL& Url() const override; -+ const KURL& BaseURL() const override; -+ KURL CompleteURL(const String& url) const override; -+ void DisableEval(const String& error_message) override; -+ void SetWasmEvalErrorMessage(const String& error_message) override; -+ String UserAgent() const override; -+ HttpsState GetHttpsState() const override; -+ ResourceFetcher* Fetcher() override; -+ void ExceptionThrown(ErrorEvent* error_event, -+ SanitizeScriptErrors sanitize_script_errors) override; -+ void AddInspectorIssue(AuditsIssue issue) override; -+ EventTarget* ErrorEventTarget() override; -+ FrameOrWorkerScheduler* GetScheduler() override; -+ bool CrossOriginIsolatedCapability() const override; -+ bool IsIsolatedContext() const override; -+ ukm::UkmRecorder* UkmRecorder() override; -+ ukm::SourceId UkmSourceID() const override; -+ ExecutionContextToken GetExecutionContextToken() const override; -+ bool IsSecureContext() const override; -+ -+ private: -+ void AddConsoleMessageImpl(ConsoleMessage* message, -+ bool discard_duplicates) override; -+ -+ const Member initiator_execution_context_; -+}; -+ -+template <> -+struct DowncastTraits { -+ static bool AllowFrom(const ExecutionContext& context) { -+ return context.IsPreloadRealmGlobalScope(); -+ } -+}; -+ -+} // namespace blink -+ -+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_PRELOAD_REALM_PRELOAD_REALM_GLOBAL_SCOPE_H_ -diff --git a/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.idl b/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.idl -new file mode 100644 -index 0000000000000000000000000000000000000000..c74c96d418c86996ca4754f54751e15591478357 ---- /dev/null -+++ b/third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.idl -@@ -0,0 +1,12 @@ -+// Copyright (c) 2026 Anthropic, PBC. -+// Use of this source code is governed by the MIT license that can be -+// found in the LICENSE file. -+ -+// Global scope of the context Electron runs service worker preload scripts in. -+ -+[ -+ Exposed=PreloadRealm, -+ Global=PreloadRealm -+] interface PreloadRealmGlobalScope {}; -+ -+PreloadRealmGlobalScope includes UniversalGlobalScope; -diff --git a/third_party/blink/renderer/core/url/url.idl b/third_party/blink/renderer/core/url/url.idl -index 2f83c1225068ac03d3ebf75515cf0f4ea191db2d..74abf60de4f4922f168abd5b055fb1ef2a869cf2 100644 ---- a/third_party/blink/renderer/core/url/url.idl -+++ b/third_party/blink/renderer/core/url/url.idl -@@ -27,7 +27,7 @@ - // https://url.spec.whatwg.org/#url - - [ -- Exposed=(Window,Worker,ShadowRealm), // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm), // TODO(crbug.com/41480387): This should be Exposed=* - LegacyWindowAlias=webkitURL - ] interface URL { - [RaisesException] constructor(USVString url, optional USVString base); -diff --git a/third_party/blink/renderer/core/url/url_search_params.idl b/third_party/blink/renderer/core/url/url_search_params.idl -index 2ac09cbcf12b2bb0213f0a117d8981ad212f9719..ceb2903ae6a7d084db4e1b0f5e002a41f8b3c2cb 100644 ---- a/third_party/blink/renderer/core/url/url_search_params.idl -+++ b/third_party/blink/renderer/core/url/url_search_params.idl -@@ -5,7 +5,7 @@ - // https://url.spec.whatwg.org/#interface-urlsearchparams - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface URLSearchParams { - [RaisesException] constructor(optional (sequence> or record or USVString) init = ""); - readonly attribute unsigned long size; -diff --git a/third_party/blink/renderer/core/url_pattern/url_pattern.idl b/third_party/blink/renderer/core/url_pattern/url_pattern.idl -index e82a9bb83d39f69191077299144c2b5508274f03..9b44dcb473ee847951e8a05e91c87bc19d869aae 100644 ---- a/third_party/blink/renderer/core/url_pattern/url_pattern.idl -+++ b/third_party/blink/renderer/core/url_pattern/url_pattern.idl -@@ -10,7 +10,7 @@ enum URLPatternComponent { "protocol", "username", "password", "hostname", - - // https://wicg.github.io/urlpattern/ - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface URLPattern { - [RaisesException, CallWith=Isolate, Measure] - constructor(URLPatternInput input, USVString baseURL, optional URLPatternOptions options = {}); -diff --git a/third_party/blink/renderer/modules/compression/compression_stream.idl b/third_party/blink/renderer/modules/compression/compression_stream.idl -index 78652ab22ddfac35fa95f65dc5574e2b6ff20170..1243baa84f2fb88423070df12e26f057ef1c5d25 100644 ---- a/third_party/blink/renderer/modules/compression/compression_stream.idl -+++ b/third_party/blink/renderer/modules/compression/compression_stream.idl -@@ -1,5 +1,5 @@ - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface CompressionStream { - [CallWith=ScriptState, RaisesException, MeasureAs=CompressionStreamConstructor] constructor(DOMString format); - readonly attribute ReadableStream readable; -diff --git a/third_party/blink/renderer/modules/compression/decompression_stream.idl b/third_party/blink/renderer/modules/compression/decompression_stream.idl -index 7856196ec0ac3362b4745a22045f4ee71ef6370d..1c325435ccec3d29382fa394e784c131edccd83e 100644 ---- a/third_party/blink/renderer/modules/compression/decompression_stream.idl -+++ b/third_party/blink/renderer/modules/compression/decompression_stream.idl -@@ -1,5 +1,5 @@ - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface DecompressionStream { - [CallWith=ScriptState, RaisesException, MeasureAs=DecompressionStreamConstructor] constructor(DOMString format); - readonly attribute ReadableStream readable; -diff --git a/third_party/blink/renderer/modules/encoding/text_decoder.idl b/third_party/blink/renderer/modules/encoding/text_decoder.idl -index 4564e37db341e567fab74ddf93dd5f90423e3f4e..8f83f70b372d27e9176232dc7fe6c3af89fc44a1 100644 ---- a/third_party/blink/renderer/modules/encoding/text_decoder.idl -+++ b/third_party/blink/renderer/modules/encoding/text_decoder.idl -@@ -31,7 +31,7 @@ - // https://encoding.spec.whatwg.org/#textdecoder - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface TextDecoder { - [RaisesException, MeasureAs=TextDecoderConstructor] constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {}); - readonly attribute DOMString encoding; -diff --git a/third_party/blink/renderer/modules/encoding/text_decoder_stream.idl b/third_party/blink/renderer/modules/encoding/text_decoder_stream.idl -index b5f09ee82ff68cd7dab3370fbefb42a96fc371df..09b2bf58080085d8eea8f46022cc97fb47ef7397 100644 ---- a/third_party/blink/renderer/modules/encoding/text_decoder_stream.idl -+++ b/third_party/blink/renderer/modules/encoding/text_decoder_stream.idl -@@ -4,7 +4,7 @@ - - // https://encoding.spec.whatwg.org/#interface-textdecoderstream - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface TextDecoderStream { - [CallWith=ScriptState, RaisesException, MeasureAs=TextDecoderStreamConstructor] constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {}); - readonly attribute DOMString encoding; -diff --git a/third_party/blink/renderer/modules/encoding/text_encoder.idl b/third_party/blink/renderer/modules/encoding/text_encoder.idl -index 8085e6edc8349a9d3763f825313cc3445bf06312..245d53014a80e61b24120ff898c6398b3ef3e07c 100644 ---- a/third_party/blink/renderer/modules/encoding/text_encoder.idl -+++ b/third_party/blink/renderer/modules/encoding/text_encoder.idl -@@ -29,7 +29,7 @@ - */ - - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface TextEncoder { - [CallWith=ExecutionContext, RaisesException, MeasureAs=TextEncoderConstructor] constructor(); - readonly attribute DOMString encoding; -diff --git a/third_party/blink/renderer/modules/encoding/text_encoder_stream.idl b/third_party/blink/renderer/modules/encoding/text_encoder_stream.idl -index d2c71dee48bbe0a08e4ba6dee7a28cdbef6c52a2..4daa2b3eca5da7a80475a722fb3c96b8ec3c3fc7 100644 ---- a/third_party/blink/renderer/modules/encoding/text_encoder_stream.idl -+++ b/third_party/blink/renderer/modules/encoding/text_encoder_stream.idl -@@ -4,7 +4,7 @@ - - // https://encoding.spec.whatwg.org/#interface-textencoderstream - [ -- Exposed=(Window,Worker,ShadowRealm) // TODO(crbug.com/41480387): This should be Exposed=* -+ Exposed=(Window,Worker,ShadowRealm,PreloadRealm) // TODO(crbug.com/41480387): This should be Exposed=* - ] interface TextEncoderStream { - [CallWith=ScriptState, RaisesException, MeasureAs=TextEncoderStreamConstructor] constructor(); - readonly attribute DOMString encoding; diff --git a/patches/chromium/feat_update_executioncontext_api.patch b/patches/chromium/feat_update_executioncontext_api.patch new file mode 100644 index 0000000000..a8ea6849e3 --- /dev/null +++ b/patches/chromium/feat_update_executioncontext_api.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: deepak1556 +Date: Wed, 21 Oct 2026 22:35:04 +0900 +Subject: feat: add ExecutionContext::GetRootExecutionContext() + +Electron runs service worker preload scripts in a PreloadRealmGlobalScope +context on the worker thread. Because a preload realm is a secondary +ExecutionContext attached to an initiator WorkerOrWorkletGlobalScope +rather than a WorkerOrWorkletGlobalScope itself, Blink code on the +worker thread that downcasts ExecutionContext::From(script_state) via +To() will fail a DowncastTraits DCHECK. + +Specifically, three worker thread paths need the root worker +ExecutionContext rather than the child realm's ExecutionContext: +1. PromiseRejectHandlerInWorker (v8_initializer.cc), which looks up the + worker's WorkerOrWorkletScriptController::GetRejectedPromises() queue + when an unhandled promise rejection occurs in the preload realm. +2. WorkerThreadDebugger::ReportConsoleMessage (worker_thread_debugger.cc), + which forwards console messages (including unhandled promise rejection + errors) to the worker thread's WorkerReportingProxy. +3. WorkerThreadDebugger::ContextGroupId (worker_thread_debugger.cc), + which resolves the WorkerThread's DevTools context group ID when + DevTools Command Line APIs such as getEventListeners() are evaluated + in the preload realm (or in a ShadowRealm). + +Previously, (1) and (2) hardcoded an IsShadowRealmGlobalScope() check +and downcast to ShadowRealmGlobalScope::GetRootInitiatorExecutionContext(), +while (3) did not unwrap child realms at all. + +Add a virtual ExecutionContext::GetRootExecutionContext() method (and +overridden by ShadowRealmGlobalScope and Electron's PreloadRealmGlobalScope to +return the root initiator ExecutionContext). + +Pending upstream crbug. + +diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc +index 41a5db4e019084d47763a43bd548b29faa070660..49206eb36d5d4dff4e2c26691f39ecb6db6f0f09 100644 +--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc ++++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc +@@ -450,10 +450,7 @@ static void PromiseRejectHandlerInWorker(v8::PromiseRejectMessage data) { + return; + + ExecutionContext* root_worker_context = +- execution_context->IsShadowRealmGlobalScope() +- ? To(execution_context) +- ->GetRootInitiatorExecutionContext() +- : execution_context; ++ execution_context->GetRootExecutionContext(); + DCHECK(root_worker_context->IsWorkerOrWorkletGlobalScope()); + + auto* script_controller = +diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h +index 07c9dd9c4ffa9b0b805187acce4f0eef4b641ceb..158a1b496753c192d5fe4525234e94dc5f413571 100644 +--- a/third_party/blink/renderer/core/execution_context/execution_context.h ++++ b/third_party/blink/renderer/core/execution_context/execution_context.h +@@ -164,6 +164,8 @@ class CORE_EXPORT ExecutionContext : public Supplementable, + virtual bool IsShadowRealmGlobalScope() const { return false; } + virtual bool IsJSExecutionForbidden() const { return false; } + ++ virtual ExecutionContext* GetRootExecutionContext() { return this; } ++ + // Notifies the execution context that new web socket activity (such as + // sending or receiving a message) has happened. + virtual void NotifyWebSocketActivity() {} +diff --git a/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc b/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc +index 3a52d94a105bcc34c2319940eb10ff381fe975ba..5f0e35a3c07edd04750f3898fc73c79aff0224a9 100644 +--- a/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc ++++ b/third_party/blink/renderer/core/inspector/worker_thread_debugger.cc +@@ -82,11 +82,7 @@ void WorkerThreadDebugger::ReportConsoleMessage( + if (!context) + return; + +- ExecutionContext* root_worker_context = +- context->IsShadowRealmGlobalScope() +- ? To(context) +- ->GetRootInitiatorExecutionContext() +- : context; ++ ExecutionContext* root_worker_context = context->GetRootExecutionContext(); + + To(root_worker_context) + ->GetThread() +@@ -168,7 +164,9 @@ void WorkerThreadDebugger::ExceptionThrown(WorkerThread* worker_thread, + } + + int WorkerThreadDebugger::ContextGroupId(ExecutionContext* context) { +- return ContextGroupId(To(context)->GetThread()); ++ return ContextGroupId( ++ To(context->GetRootExecutionContext()) ++ ->GetThread()); + } + + void WorkerThreadDebugger::PauseWorkerOnStart(WorkerThread* worker_thread) { +diff --git a/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.cc b/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.cc +index 7b77d51ca3ca41a187021a13c213bbd723e997da..06620d26be43508a0c2e3e163ec0f049edf946b8 100644 +--- a/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.cc ++++ b/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.cc +@@ -20,10 +20,7 @@ ShadowRealmGlobalScope::ShadowRealmGlobalScope( + + ExecutionContext* ShadowRealmGlobalScope::GetRootInitiatorExecutionContext() + const { +- return initiator_execution_context_->IsShadowRealmGlobalScope() +- ? To(initiator_execution_context_.Get()) +- ->GetRootInitiatorExecutionContext() +- : initiator_execution_context_.Get(); ++ return initiator_execution_context_->GetRootExecutionContext(); + } + + void ShadowRealmGlobalScope::Trace(Visitor* visitor) const { +@@ -63,6 +60,10 @@ bool ShadowRealmGlobalScope::IsShadowRealmGlobalScope() const { + return true; + } + ++ExecutionContext* ShadowRealmGlobalScope::GetRootExecutionContext() { ++ return GetRootInitiatorExecutionContext(); ++} ++ + const KURL& ShadowRealmGlobalScope::Url() const { + return GetRootInitiatorExecutionContext()->Url(); + } +diff --git a/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h b/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h +index 956049a3e86cba9bfd1ee15a92a49f13d105ee30..fabf4706ad2eedd4a0d00562b842276443840903 100644 +--- a/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h ++++ b/third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h +@@ -45,6 +45,7 @@ class CORE_EXPORT ShadowRealmGlobalScope final : public EventTarget, + + // ExecutionContext: + bool IsShadowRealmGlobalScope() const override; ++ ExecutionContext* GetRootExecutionContext() override; + const KURL& Url() const override; + const KURL& BaseURL() const override; + KURL CompleteURL(const String& url) const override; diff --git a/patches/chromium/use_forward_slashes_for_the_default_crdtp_include_dir_in_templates.patch b/patches/chromium/use_forward_slashes_for_the_default_crdtp_include_dir_in_templates.patch new file mode 100644 index 0000000000..f03bcb245b --- /dev/null +++ b/patches/chromium/use_forward_slashes_for_the_default_crdtp_include_dir_in_templates.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Sanders +Date: Wed, 21 Oct 2026 11:32:00 -0700 +Subject: Use forward slashes for the default crdtp include dir in templates + +Without this includes in generated source files on Windows look like +`#include "third_party/inspector_protocol\crdtp/cbor.h"` which can +confuse static analysis tools such as clang-tidy. + +Refs https://chromium-review.googlesource.com/c/deps/inspector_protocol/+/8624663 + +diff --git a/third_party/inspector_protocol/code_generator.py b/third_party/inspector_protocol/code_generator.py +index 259313e258710cfb3ba4eee63aa3193123a4c230..71c88f0a3065d730476e0569dac28e0fc687cb1e 100755 +--- a/third_party/inspector_protocol/code_generator.py ++++ b/third_party/inspector_protocol/code_generator.py +@@ -5,6 +5,7 @@ + + import os + import os.path ++import posixpath + import sys + import argparse + import collections +@@ -121,7 +122,7 @@ def read_config(): + ".lib.export_macro": "", + ".lib.export_header": False, + ".crdtp": False, +- ".crdtp.dir": os.path.join(inspector_protocol_dir, "crdtp"), ++ ".crdtp.dir": posixpath.join(inspector_protocol_dir, "crdtp"), + ".crdtp.namespace": "crdtp", + } + for key_value in config_values: diff --git a/shell/renderer/api/electron_api_context_bridge.cc b/shell/renderer/api/electron_api_context_bridge.cc index e95fe67fdd..1f93152175 100644 --- a/shell/renderer/api/electron_api_context_bridge.cc +++ b/shell/renderer/api/electron_api_context_bridge.cc @@ -796,14 +796,14 @@ v8::MaybeLocal GetTargetContext(v8::Isolate* isolate, world_id == WorldIDs::MAIN_WORLD_ID ? frame->MainWorldScriptContext() : frame->GetScriptContextFromWorldId(isolate, world_id); - } else if (execution_context->IsPreloadRealmGlobalScope()) { + } else if (maybe_target_context = + electron::preload_realm::GetInitiatorContext(source_context); + !maybe_target_context.IsEmpty()) { if (world_id != WorldIDs::MAIN_WORLD_ID) { isolate->ThrowException(v8::Exception::Error(gin::StringToV8( isolate, "Isolated worlds are not supported in preload realms."))); - return maybe_target_context; + return v8::MaybeLocal(); } - maybe_target_context = - electron::preload_realm::GetInitiatorContext(source_context); } else { NOTREACHED(); } diff --git a/shell/renderer/preload_realm_context.cc b/shell/renderer/preload_realm_context.cc index 743e8cb4ab..3c4347d379 100644 --- a/shell/renderer/preload_realm_context.cc +++ b/shell/renderer/preload_realm_context.cc @@ -9,98 +9,94 @@ #include "base/process/process_metrics.h" #include "mojo/public/cpp/base/big_buffer.h" #include "shell/common/api/electron_bindings.h" -#include "shell/common/gc_plugin.h" #include "shell/common/gin_helper/dictionary.h" -#include "shell/common/gin_helper/gin_embedders.h" #include "shell/common/js2c_bundle_ids.h" #include "shell/common/node_util.h" #include "shell/renderer/preload_environment.h" +#include "shell/renderer/preload_realm_global_scope.h" #include "shell/renderer/preload_utils.h" #include "shell/renderer/service_worker_data.h" #include "third_party/blink/renderer/bindings/core/v8/script_state_impl.h" // nogncheck #include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck #include "third_party/blink/renderer/core/inspector/worker_thread_debugger.h" // nogncheck -#include "third_party/blink/renderer/core/preload_realm/preload_realm_global_scope.h" // nogncheck #include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h" // nogncheck #include "third_party/blink/renderer/platform/bindings/script_state.h" // nogncheck #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h" // nogncheck #include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h" // nogncheck #include "third_party/blink/renderer/platform/context_lifecycle_observer.h" // nogncheck +#include "third_party/blink/renderer/platform/supplementable.h" // nogncheck #include "v8/include/v8-context.h" namespace electron::preload_realm { namespace { -static constexpr int kElectronContextEmbedderDataIndex = - static_cast(gin::kPerContextDataStartIndex) + - static_cast(electron::kEmbedderElectron); - // This is a helper class to make the initiator ExecutionContext the owner // of a PreloadRealmGlobalScope and its ScriptState. When the initiator // ExecutionContext is destroyed, the PreloadRealmGlobalScope is destroyed, // too. class PreloadRealmLifetimeController : public blink::GarbageCollected, + public blink::Supplement, public blink::ContextLifecycleObserver { public: + static const char kSupplementName[]; + explicit PreloadRealmLifetimeController( blink::ExecutionContext* initiator_execution_context, blink::ScriptState* initiator_script_state, blink::PreloadRealmGlobalScope* preload_realm_global_scope, blink::ScriptState* preload_realm_script_state, electron::ServiceWorkerData* service_worker_data) - : initiator_script_state_(initiator_script_state), + : blink::Supplement(*preload_realm_global_scope), + initiator_script_state_(initiator_script_state), is_initiator_worker_or_worklet_( initiator_execution_context->IsWorkerOrWorkletGlobalScope()), preload_realm_global_scope_(preload_realm_global_scope), preload_realm_script_state_(preload_realm_script_state), service_worker_data_(service_worker_data) { - // Align lifetime of this controller to that of the initiator's context. - self_ = this; - SetContextLifecycleNotifier(initiator_execution_context); RegisterDebugger(initiator_execution_context); - initiator_context()->SetAlignedPointerInEmbedderData( - kElectronContextEmbedderDataIndex, static_cast(this), - v8::kEmbedderDataTypeTagDefault); - realm_context()->SetAlignedPointerInEmbedderData( - kElectronContextEmbedderDataIndex, static_cast(this), - v8::kEmbedderDataTypeTagDefault); + blink::Supplement::ProvideTo( + *initiator_execution_context, this); + blink::Supplement::ProvideTo( + *preload_realm_global_scope, this); metrics_ = base::ProcessMetrics::CreateCurrentProcessMetrics(); RunInitScript(); } static PreloadRealmLifetimeController* From(v8::Local context) { - if (context->GetNumberOfEmbedderDataFields() <= - kElectronContextEmbedderDataIndex) { + if (context.IsEmpty()) return nullptr; - } - auto* controller = static_cast( - context->GetAlignedPointerFromEmbedderData( - kElectronContextEmbedderDataIndex, - v8::kEmbedderDataTypeTagDefault)); - CHECK(controller); - return controller; + return blink::Supplement::From< + PreloadRealmLifetimeController>(blink::ExecutionContext::From(context)); } void Trace(blink::Visitor* visitor) const override { visitor->Trace(initiator_script_state_); visitor->Trace(preload_realm_global_scope_); visitor->Trace(preload_realm_script_state_); + blink::Supplement::Trace(visitor); ContextLifecycleObserver::Trace(visitor); } + bool IsPreloadRealmContext(v8::Local context) const { + return preload_realm_script_state_ && + preload_realm_script_state_->ContextIsValid() && + preload_realm_script_state_->GetContext() == context; + } + v8::MaybeLocal GetContext() { - return preload_realm_script_state_->ContextIsValid() + return preload_realm_script_state_ && + preload_realm_script_state_->ContextIsValid() ? preload_realm_script_state_->GetContext() : v8::MaybeLocal(); } v8::MaybeLocal GetInitiatorContext() { - return initiator_script_state_->ContextIsValid() + return initiator_script_state_ && initiator_script_state_->ContextIsValid() ? initiator_script_state_->GetContext() : v8::MaybeLocal(); } @@ -112,9 +108,9 @@ class PreloadRealmLifetimeController protected: void ContextDestroyed() override { v8::HandleScope handle_scope(realm_isolate()); - realm_context()->SetAlignedPointerInEmbedderData( - kElectronContextEmbedderDataIndex, nullptr, - v8::kEmbedderDataTypeTagDefault); + preload_realm_global_scope_ + ->blink::Supplementable::RemoveSupplement< + PreloadRealmLifetimeController>(); preload_realm_script_state_->DisposePerContextData(); // No V8 GC runs during worker thread termination, so the ScriptState's @@ -125,8 +121,6 @@ class PreloadRealmLifetimeController preload_realm_script_state_.Clear(); preload_realm_global_scope_->NotifyContextDestroyed(); preload_realm_global_scope_.Clear(); - - self_.Clear(); } private: @@ -136,9 +130,6 @@ class PreloadRealmLifetimeController v8::Local realm_context() { return preload_realm_script_state_->GetContext(); } - v8::Local initiator_context() { - return initiator_script_state_->GetContext(); - } void RegisterDebugger(blink::ExecutionContext* initiator_execution_context) { v8::Isolate* isolate = realm_isolate(); @@ -218,27 +209,20 @@ class PreloadRealmLifetimeController std::unique_ptr metrics_; raw_ptr service_worker_data_; - - GC_PLUGIN_IGNORE( - "Intentional GC root to keep this object alive until the context is " - "destroyed") - blink::Persistent self_; }; +const char PreloadRealmLifetimeController::kSupplementName[] = + "PreloadRealmLifetimeController"; + } // namespace v8::MaybeLocal GetInitiatorContext( v8::Local context) { DCHECK(!context.IsEmpty()); - blink::ExecutionContext* execution_context = - blink::ExecutionContext::From(context); - if (!execution_context->IsPreloadRealmGlobalScope()) - return v8::MaybeLocal(); auto* controller = PreloadRealmLifetimeController::From(context); - if (controller) { - return controller->GetInitiatorContext(); - } - return v8::MaybeLocal(); + if (!controller || !controller->IsPreloadRealmContext(context)) + return v8::MaybeLocal(); + return controller->GetInitiatorContext(); } v8::MaybeLocal GetPreloadRealmContext( diff --git a/shell/renderer/preload_realm_global_scope.cc b/shell/renderer/preload_realm_global_scope.cc new file mode 100644 index 0000000000..2dcd3b0976 --- /dev/null +++ b/shell/renderer/preload_realm_global_scope.cc @@ -0,0 +1,158 @@ +// 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 + +#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(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 +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 diff --git a/shell/renderer/preload_realm_global_scope.h b/shell/renderer/preload_realm_global_scope.h new file mode 100644 index 0000000000..cc36f71c85 --- /dev/null +++ b/shell/renderer/preload_realm_global_scope.h @@ -0,0 +1,81 @@ +// Copyright (c) 2026 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_RENDERER_PRELOAD_REALM_GLOBAL_SCOPE_H_ +#define ELECTRON_SHELL_RENDERER_PRELOAD_REALM_GLOBAL_SCOPE_H_ + +#include "base/task/single_thread_task_runner.h" +#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck +#include "third_party/blink/renderer/core/frame/universal_global_scope.h" // nogncheck +#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" // nogncheck +#include "third_party/blink/renderer/platform/weborigin/kurl.h" // nogncheck + +namespace blink { + +// Global scope of the context Electron runs preload scripts in when the +// context they are attached to (the "initiator") has no isolated worlds of its +// own, as is the case for service workers. It exposes only the interfaces +// marked [Exposed=PreloadRealm] or [Exposed=*] and otherwise defers to the +// initiator, which owns it. +class PreloadRealmGlobalScope final : public ScriptWrappable, + public UniversalGlobalScope, + public ExecutionContext { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit PreloadRealmGlobalScope( + ExecutionContext* initiator_execution_context); + + ExecutionContext* GetInitiatorExecutionContext() const { + return initiator_execution_context_.Get(); + } + + void Trace(Visitor* visitor) const override; + + // UniversalGlobalScope: + ExecutionContext* GetExecutionContext() const override; + + // MojoBindingContext: + const BrowserInterfaceBrokerProxy& GetBrowserInterfaceBroker() const override; + scoped_refptr GetTaskRunner( + TaskType task_type) override; + + // UseCounter: + void CountUse(mojom::blink::WebFeature feature) override; + void CountDeprecation(mojom::blink::WebFeature feature) override; + void CountWebDXFeature(mojom::blink::WebDXFeature feature) override; + + // ExecutionContext: + bool IsShadowRealmGlobalScope() const override; + ExecutionContext* GetRootExecutionContext() override; + const KURL& Url() const override; + const KURL& BaseURL() const override; + KURL CompleteURL(const String& url) const override; + void DisableEval(const String& error_message) override; + void SetWasmEvalErrorMessage(const String& error_message) override; + String UserAgent() const override; + HttpsState GetHttpsState() const override; + ResourceFetcher* Fetcher() override; + void ExceptionThrown(ErrorEvent* error_event, + SanitizeScriptErrors sanitize_script_errors) override; + void AddInspectorIssue(AuditsIssue issue) override; + EventTarget* ErrorEventTarget() override; + FrameOrWorkerScheduler* GetScheduler() override; + bool CrossOriginIsolatedCapability() const override; + bool IsIsolatedContext() const override; + ukm::UkmRecorder* UkmRecorder() override; + ukm::SourceId UkmSourceID() const override; + ExecutionContextToken GetExecutionContextToken() const override; + bool IsSecureContext() const override; + + private: + void AddConsoleMessageImpl(ConsoleMessage* message, + bool discard_duplicates) override; + + const Member initiator_execution_context_; +}; + +} // namespace blink + +#endif // ELECTRON_SHELL_RENDERER_PRELOAD_REALM_GLOBAL_SCOPE_H_ diff --git a/shell/renderer/preload_realm_global_scope.idl b/shell/renderer/preload_realm_global_scope.idl new file mode 100644 index 0000000000..7f1e40b291 --- /dev/null +++ b/shell/renderer/preload_realm_global_scope.idl @@ -0,0 +1,12 @@ +// Copyright (c) 2026 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +// Global scope of the context Electron runs service worker preload scripts in. + +[ + Exposed=PreloadRealm, + Global=PreloadRealm +] interface PreloadRealmGlobalScope {}; + +PreloadRealmGlobalScope includes UniversalGlobalScope;