Repository: electron/electron (language: cpp)

Issue:
[Feature Request]: Allow custom v8 snapshots to be used in the main process and the default snapshot in the browser process
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.

### Problem Description

We are using `electron-mksnapshot` to generate a custom v8 snapshot for our application that is desired to run **only in the main process**, not the renderer process. While debugging our app, we noticed that the snapshot is available to the renderer process **even with nodeIntegration disabled**. 

We noticed that electron is [patching](https://github.com/electron/electron/blob/main/patches/chromium/load_v8_snapshot_in_browser_process.patch?rgh-link-date=2022-08-02T00%3A24%3A21Z) chromium to always load the v8 snapshots, with a comment indicating that this is required. Is loading the v8 snapshot required even when nodeIntegration is disabled?

We would like for there to be a way to use our custom snapshot in the main process but use the default snapshot (or no snapshot with nodeIntegration disabled) in the browser process.

### Proposed Solution

This could be done a variety of ways, but one possibility would be to update the [patch](https://github.com/electron/electron/blob/main/patches/chromium/load_v8_snapshot_in_browser_process.patch?rgh-link-date=2022-08-02T00%3A24%3A21Z) to not load the custom snapshot when node integration is disabled. Or if it's desired sometimes to have node integration disabled **and** a v8 snapshot included, you could have an additional parameter that specifies as such.

### Alternatives Considered

One alternative for us would be to have separate main electron processes: one for the bulk of our main code that uses the snapshot, and one to purely launch the browser that doesn't launch the snapshot. This is undesirable as it significantly complicates the code and potentially bloats the size of our binary.

### Additional Information

_No response_


Discussion / hints:
Hopefully someone from the core team will see this. We recently updated the content above to clarify the problem we're seeing as well as the desired solution.
cc @MarshallOfSound can we chat about this? This is for Cypress

Fix patch:
diff --git a/build/fuses/fuses.json5 b/build/fuses/fuses.json5
index f4984aa2a17b4..e8df5ffd7ab98 100644
--- a/build/fuses/fuses.json5
+++ b/build/fuses/fuses.json5
@@ -7,5 +7,6 @@
   "node_options": "1",
   "node_cli_inspect": "1",
   "embedded_asar_integrity_validation": "0",
-  "only_load_app_from_asar": "0"
+  "only_load_app_from_asar": "0",
+  "load_browser_process_specific_v8_snapshot": "0"
 }
diff --git a/docs/tutorial/fuses.md b/docs/tutorial/fuses.md
index bb0c61b475136..d933a841c46c5 100644
--- a/docs/tutorial/fuses.md
+++ b/docs/tutorial/fuses.md
@@ -54,6 +54,13 @@ For more information on how to use asar integrity validation please read the [As
 
 The onlyLoadAppFromAsar fuse changes the search system that Electron uses to locate your app code.  By default Electron will search in the following order `app.asar` -> `app` -> `default_app.asar`.  When this fuse is enabled the search order becomes a single entry `app.asar` thus ensuring that when combined with the `embeddedAsarIntegrityValidation` fuse it is impossible to load non-validated code.
 
+### `loadBrowserProcessSpecificV8Snapshot`
+
+**Default:** Disabled
+**@electron/fuses:** `FuseV1Options.LoadBrowserProcessSpecificV8Snapshot`
+
+The loadBrowserProcessSpecificV8Snapshot fuse changes which V8 snapshot file is used for the browser process.  By default Electron's processes will all use the same V8 snapshot file.  When this fuse is enabled the browser process uses the file called `browser_v8_context_snapshot.bin` for its V8 snapshot. The other processes will use the V8 snapshot file that they normally do.
+
 ## How do I flip the fuses?
 
 ### The easy way
diff --git a/patches/chromium/.patches b/patches/chromium/.patches
index 517793caf938b..efa176d161397 100644
--- a/patches/chromium/.patches
+++ b/patches/chromium/.patches
@@ -121,3 +121,4 @@ fix_revert_emulationhandler_update_functions_to_early_return.patch
 fix_crash_loading_non-standard_schemes_in_iframes.patch
 disable_optimization_guide_for_preconnect_feature.patch
 fix_return_v8_value_from_localframe_requestexecutescript.patch
+create_browser_v8_snapshot_file_name_fuse.patch
diff --git a/patches/chromium/create_browser_v8_snapshot_file_name_fuse.patch b/patches/chromium/create_browser_v8_snapshot_file_name_fuse.patch
new file mode 100644
index 0000000000000..6b074078b5735
--- /dev/null
+++ b/patches/chromium/create_browser_v8_snapshot_file_name_fuse.patch
@@ -0,0 +1,156 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Ryan Manuel <ryanm@cypress.io>
+Date: Thu, 4 Aug 2022 22:37:01 -0500
+Subject: Create browser v8 snapshot file name fuse
+
+By default, chromium sets up one v8 snapshot to be used in all v8 contexts. This patch allows consumers
+to have a dedicated browser process v8 snapshot defined by the file `browser_v8_context_snapshot.bin`.
+
+diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
+index 6242f0a0070b2b862f9fa5ed70825a16d6e270ef..af355d4b0adb509f6b04d8bc2eddbb3a6b3c9a08 100644
+--- a/content/app/content_main_runner_impl.cc
++++ b/content/app/content_main_runner_impl.cc
+@@ -37,6 +37,7 @@
+ #include "base/process/memory.h"
+ #include "base/process/process.h"
+ #include "base/process/process_handle.h"
++#include "base/strings/string_piece.h"
+ #include "base/strings/string_number_conversions.h"
+ #include "base/strings/string_util.h"
+ #include "base/task/thread_pool/thread_pool_instance.h"
+@@ -232,8 +233,13 @@ std::string GetSnapshotDataDescriptor(const base::CommandLine& command_line) {
+ 
+ #endif
+ 
+-void LoadV8SnapshotFile(const base::CommandLine& command_line) {
++void LoadV8SnapshotFile(const raw_ptr<ContentMainDelegate> delegate, const base::CommandLine& command_line) {
+   const gin::V8SnapshotFileType snapshot_type = GetSnapshotType(command_line);
++  base::StringPiece browser_v8_snapshot_file_name = delegate->GetBrowserV8SnapshotFilename();
++  if (!browser_v8_snapshot_file_name.empty()) {
++    gin::V8Initializer::LoadV8SnapshotFromFileName(browser_v8_snapshot_file_name, snapshot_type);
++    return;
++  }
+ #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
+   base::FileDescriptorStore& file_descriptor_store =
+       base::FileDescriptorStore::GetInstance();
+@@ -262,11 +268,12 @@ bool ShouldLoadV8Snapshot(const base::CommandLine& command_line,
+ 
+ #endif  // V8_USE_EXTERNAL_STARTUP_DATA
+ 
+-void LoadV8SnapshotIfNeeded(const base::CommandLine& command_line,
++void LoadV8SnapshotIfNeeded(const raw_ptr<ContentMainDelegate> delegate,
++                            const base::CommandLine& command_line,
+                             const std::string& process_type) {
+ #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
+   if (ShouldLoadV8Snapshot(command_line, process_type))
+-    LoadV8SnapshotFile(command_line);
++    LoadV8SnapshotFile(delegate, command_line);
+ #endif  // V8_USE_EXTERNAL_STARTUP_DATA
+ }
+ 
+@@ -925,7 +932,7 @@ int ContentMainRunnerImpl::Initialize(ContentMainParams params) {
+     return TerminateForFatalInitializationError();
+ #endif  // BUILDFLAG(IS_ANDROID) && (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
+ 
+-  LoadV8SnapshotIfNeeded(command_line, process_type);
++  LoadV8SnapshotIfNeeded(delegate_, command_line, process_type);
+ 
+   blink::TrialTokenValidator::SetOriginTrialPolicyGetter(
+       base::BindRepeating([]() -> blink::OriginTrialPolicy* {
+diff --git a/content/public/app/content_main_delegate.cc b/content/public/app/content_main_delegate.cc
+index 5450eb6ba565164953b778f861d8fc75a06b6115..3f15d5a83d6e8011da09da178a0a9dfd2dd95d30 100644
+--- a/content/public/app/content_main_delegate.cc
++++ b/content/public/app/content_main_delegate.cc
+@@ -5,6 +5,7 @@
+ #include "content/public/app/content_main_delegate.h"
+ 
+ #include "base/check.h"
++#include "base/strings/string_piece.h"
+ #include "build/build_config.h"
+ #include "content/public/browser/content_browser_client.h"
+ #include "content/public/common/content_client.h"
+@@ -83,6 +84,10 @@ absl::optional<int> ContentMainDelegate::PostEarlyInitialization(
+   return absl::nullopt;
+ }
+ 
++base::StringPiece ContentMainDelegate::GetBrowserV8SnapshotFilename() {
++  return base::StringPiece();
++}
++
+ ContentClient* ContentMainDelegate::CreateContentClient() {
+   return new ContentClient();
+ }
+diff --git a/content/public/app/content_main_delegate.h b/content/public/app/content_main_delegate.h
+index f40146c4ed20e63dd09450e43c26736171f02ed4..5e3246a1346bd0210e7b83842a17dcc1986c8647 100644
+--- a/content/public/app/content_main_delegate.h
++++ b/content/public/app/content_main_delegate.h
+@@ -9,6 +9,7 @@
+ #include <string>
+ #include <vector>
+ 
++#include "base/strings/string_piece.h"
+ #include "build/build_config.h"
+ #include "content/common/content_export.h"
+ #include "content/public/common/main_function_params.h"
+@@ -154,6 +155,8 @@ class CONTENT_EXPORT ContentMainDelegate {
+   virtual bool ShouldHandleConsoleControlEvents();
+ #endif
+ 
++  virtual base::StringPiece GetBrowserV8SnapshotFilename();
++
+  protected:
+   friend class ContentClientCreator;
+   friend class ContentClientInitializer;
+diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc
+index 5530d975303cc96701e4b70ffbcaf6e7c02bb016..edd9959cc0fd23711e19de4aee104199a8a3599e 100644
+--- a/gin/v8_initializer.cc
++++ b/gin/v8_initializer.cc
+@@ -496,8 +496,7 @@ void V8Initializer::GetV8ExternalSnapshotData(const char** snapshot_data_out,
+ 
+ #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
+ 
+-// static
+-void V8Initializer::LoadV8Snapshot(V8SnapshotFileType snapshot_file_type) {
++void V8Initializer::LoadV8SnapshotFromFileName(base::StringPiece file_name, V8SnapshotFileType snapshot_file_type) {
+   if (g_mapped_snapshot) {
+     // TODO(crbug.com/802962): Confirm not loading different type of snapshot
+     // files in a process.
+@@ -506,10 +505,17 @@ void V8Initializer::LoadV8Snapshot(V8SnapshotFileType snapshot_file_type) {
+ 
+   base::MemoryMappedFile::Region file_region;
+   base::File file =
+-      OpenV8File(GetSnapshotFileName(snapshot_file_type), &file_region);
++      OpenV8File(file_name.data(), &file_region);
+   LoadV8SnapshotFromFile(std::move(file), &file_region, snapshot_file_type);
+ }
+ 
++// static
++void V8Initializer::LoadV8Snapshot(V8SnapshotFileType snapshot_file_type) {
++  const char* file_name = GetSnapshotFileName(snapshot_file_type);
++
++  LoadV8SnapshotFromFileName(file_name, snapshot_file_type);
++}
++
+ // static
+ void V8Initializer::LoadV8SnapshotFromFile(
+     base::File snapshot_file,
+diff --git a/gin/v8_initializer.h b/gin/v8_initializer.h
+index 13a120c7fe8e69a44793473f3124c33d572a07a3..acb294780873c1d84546eb2b9acc00f86838361d 100644
+--- a/gin/v8_initializer.h
++++ b/gin/v8_initializer.h
+@@ -9,6 +9,7 @@
+ 
+ #include "base/files/file.h"
+ #include "base/files/memory_mapped_file.h"
++#include "base/strings/string_piece.h"
+ #include "build/build_config.h"
+ #include "gin/array_buffer.h"
+ #include "gin/gin_export.h"
+@@ -42,6 +43,7 @@ class GIN_EXPORT V8Initializer {
+                                         int* snapshot_size_out);
+ 
+ #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
++  static void LoadV8SnapshotFromFileName(base::StringPiece file_name, V8SnapshotFileType snapshot_file_type);
+   // Load V8 snapshot from default resources, if they are available.
+   static void LoadV8Snapshot(
+       V8SnapshotFileType snapshot_file_type = V8SnapshotFileType::kDefault);
diff --git a/shell/app/electron_main_delegate.cc b/shell/app/electron_main_delegate.cc
index 537f74a14d6d9..73d063026ad3e 100644
--- a/shell/app/electron_main_delegate.cc
+++ b/shell/app/electron_main_delegate.cc
@@ -23,6 +23,7 @@
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "content/public/common/content_switches.h"
 #include "electron/buildflags/buildflags.h"
+#include "electron/fuses.h"
 #include "extensions/common/constants.h"
 #include "ipc/ipc_buildflags.h"
 #include "sandbox/policy/switches.h"
@@ -422,6 +423,20 @@ absl::optional<int> ElectronMainDelegate::PreBrowserMain() {
   return absl::nullopt;
 }
 
+base::StringPiece ElectronMainDelegate::GetBrowserV8SnapshotFilename() {
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  std::string process_type =
+      command_line->GetSwitchValueASCII(::switches::kProcessType);
+  bool load_browser_process_specific_v8_snapshot =
+      process_type.empty() &&
+      electron::fuses::IsLoadBrowserProcessSpecificV8SnapshotEnabled();
+  if (load_browser_process_specific_v8_snapshot) {
+    return "browser_v8_context_snapshot.bin";
+  }
+  return ContentMainDelegate::GetBrowserV8SnapshotFilename();
+}
+
 content::ContentBrowserClient*
 ElectronMainDelegate::CreateContentBrowserClient() {
   browser_client_ = std::make_unique<ElectronBrowserClient>();
diff --git a/shell/app/electron_main_delegate.h b/shell/app/electron_main_delegate.h
index e8e57fad85db6..0a8363fb7a90b 100644
--- a/shell/app/electron_main_delegate.h
+++ b/shell/app/electron_main_delegate.h
@@ -30,6 +30,8 @@ class ElectronMainDelegate : public content::ContentMainDelegate {
   ElectronMainDelegate(const ElectronMainDelegate&) = delete;
   ElectronMainDelegate& operator=(const ElectronMainDelegate&) = delete;
 
+  base::StringPiece GetBrowserV8SnapshotFilename() override;
+
  protected:
   // content::ContentMainDelegate:
   absl::optional<int> BasicStartupComplete() override;


Test patch: