[flags] Make --js-flags settings have priority over V8 features

- Move --js-flags handling from the RenderProcessImpl constructor
  to a late stage in gin::V8Initializer::Initialize where they can
  override previously set flags
- Move content::switches::kJavaScriptFlags to blink::switches
- Allow blink_initializer.cc to access base::CommandLine to pass
  --js-flags to V8

V8 engineers are used to provide command-line flags manually.
For local testing it can be quite confusing if the flags conflict
with finch / field trial settings.
This CL changes the priority of --js-flags to be able to override
any flags previously set by features.

V8Flags are mostly initialized in two place:
1. content::RenderProcessImpl::RenderProcessImpl
2. gin::V8Initializer::Initialize

V8 flags are per-process globals, many of them should not be changed
after initialising V8. Thus the latest safe point to set V8 flags is
in gin::V8Initializer::Initialize.

Currently some flags are set in the RenderProcessImpl constructors,
especially --js-flags is processed there. With V8::SetFlagsFromString
the last flag takes precedence. As a result chrome feature flags that
are processed (late) in gin could not be overridden with --js-flags.

Bug: v8:12309
Change-Id: I832c274e760bf9bd61f9bf3fa8d7fc00251b4685
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3208413
Reviewed-by: Wez <wez@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Jochen Eisinger <jochen@chromium.org>
Reviewed-by: Elad Alon <eladalon@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Finnur Thorarinsson <finnur@chromium.org>
Owners-Override: Wez <wez@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#931522}
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 522aad1e..ef26941 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -121,6 +121,7 @@
 #include "services/network/public/cpp/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/public/common/switches.h"
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/compositor_observer.h"
 #include "ui/display/display_switches.h"
@@ -546,7 +547,8 @@
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--expose-gc");
 
     extensions::PlatformAppBrowserTest::SetUpCommandLine(command_line);
   }
diff --git a/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc b/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc
index 39d8c91..15672ba 100644
--- a/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc
+++ b/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc
@@ -29,6 +29,7 @@
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "net/dns/mock_host_resolver.h"
+#include "third_party/blink/public/common/switches.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chromeos/login/login_state/login_state.h"
@@ -81,7 +82,8 @@
  protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     ExtensionApiTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--expose-gc");
   }
 
   void SetUpOnMainThread() override {
diff --git a/chrome/browser/extensions/webstore_installer_test.cc b/chrome/browser/extensions/webstore_installer_test.cc
index c560db46..c0187441 100644
--- a/chrome/browser/extensions/webstore_installer_test.cc
+++ b/chrome/browser/extensions/webstore_installer_test.cc
@@ -30,6 +30,7 @@
 #include "net/base/host_port_pair.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "third_party/blink/public/common/switches.h"
 #include "url/gurl.h"
 
 using content::WebContents;
@@ -76,7 +77,8 @@
 
   // Allow tests to call window.gc(), so that we can check that callback
   // functions don't get collected prematurely.
-  command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+  command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                  "--expose-gc");
 }
 
 void WebstoreInstallerTest::SetUpOnMainThread() {
diff --git a/chrome/browser/media/webrtc/webrtc_browsertest.cc b/chrome/browser/media/webrtc/webrtc_browsertest.cc
index a81f3af..a11f9df 100644
--- a/chrome/browser/media/webrtc/webrtc_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_browsertest.cc
@@ -27,6 +27,7 @@
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/mojom/network_service_test.mojom.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/switches.h"
 
 #if defined(OS_MAC)
 #include "base/mac/mac_util.h"
@@ -57,7 +58,8 @@
     EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream));
 
     // Flag used by TestWebAudioMediaStream to force garbage collection.
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--expose-gc");
   }
 
   void RunsAudioVideoWebRTCCallInTwoTabs(
diff --git a/chrome/test/v8/wasm_trap_handler_browsertest.cc b/chrome/test/v8/wasm_trap_handler_browsertest.cc
index dd030f01..5514084 100644
--- a/chrome/test/v8/wasm_trap_handler_browsertest.cc
+++ b/chrome/test/v8/wasm_trap_handler_browsertest.cc
@@ -15,6 +15,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "third_party/blink/public/common/switches.h"
 
 namespace {
 // |kIsTrapHandlerSupported| indicates whether the trap handler is supported
@@ -86,7 +87,7 @@
 #if defined(OS_POSIX)
     command_line->AppendSwitch(switches::kEnableCrashReporterForTesting);
 #endif
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags,
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
                                     "--allow-natives-syntax");
   }
 };
diff --git a/content/browser/indexed_db/indexed_db_browsertest.cc b/content/browser/indexed_db/indexed_db_browsertest.cc
index 53d3c07f..e3e4afc 100644
--- a/content/browser/indexed_db/indexed_db_browsertest.cc
+++ b/content/browser/indexed_db/indexed_db_browsertest.cc
@@ -57,6 +57,7 @@
 #include "storage/browser/quota/quota_manager.h"
 #include "storage/browser/quota/quota_settings.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/common/switches.h"
 #include "third_party/leveldatabase/src/include/leveldb/status.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -449,7 +450,8 @@
   IndexedDBBrowserTestWithGCExposed() = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--expose-gc");
   }
 
  private:
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index 788ad4d..9800cf4 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -90,6 +90,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/mojom/browser_interface_broker.mojom-test-utils.h"
 #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h"
 #include "url/gurl.h"
@@ -229,7 +230,7 @@
     // TODO(https://crbug.com/794320): Remove this when the new Java Bridge code
     // is integrated into WebView.
     base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-        switches::kJavaScriptFlags, "--expose_gc");
+        blink::switches::kJavaScriptFlags, "--expose_gc");
 
     base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
         switches::kEnableBlinkFeatures, "WebOTP");
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 272a919..cc6c46a 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -433,9 +433,9 @@
       dynamic_code_can_be_disabled_ = true;
       return;
     }
-    if (cmd_line->HasSwitch(switches::kJavaScriptFlags)) {
+    if (cmd_line->HasSwitch(blink::switches::kJavaScriptFlags)) {
       std::string js_flags =
-          cmd_line->GetSwitchValueASCII(switches::kJavaScriptFlags);
+          cmd_line->GetSwitchValueASCII(blink::switches::kJavaScriptFlags);
       std::vector<base::StringPiece> js_flag_list = base::SplitStringPiece(
           js_flags, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
       for (const auto& js_flag : js_flag_list) {
@@ -3352,7 +3352,8 @@
   }
 
   if (IsJitDisabled())
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--jitless");
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--jitless");
 
 #if defined(OS_WIN)
   command_line->AppendSwitchASCII(
@@ -3461,7 +3462,6 @@
     switches::kFullMemoryCrashReport,
     switches::kGaiaUrl,
     switches::kIPCConnectionTimeout,
-    switches::kJavaScriptFlags,
     switches::kLogBestEffortTasks,
     switches::kLogFile,
     switches::kLoggingLevel,
@@ -3520,6 +3520,7 @@
     blink::switches::kShowLayoutShiftRegions,
     blink::switches::kShowPaintRects,
     blink::switches::kTouchTextSelectionStrategy,
+    blink::switches::kJavaScriptFlags,
     // Please keep these in alphabetical order. Compositor switches here
     // should also be added to
     // chrome/browser/ash/login/chrome_restart_request.cc.
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 22b586b..b7db74e 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -138,6 +138,7 @@
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
 #include "third_party/blink/public/common/permissions_policy/policy_value.h"
+#include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h"
 #include "third_party/blink/public/mojom/frame/frame.mojom.h"
@@ -9873,7 +9874,8 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     SitePerProcessBrowserTest::SetUpCommandLine(command_line);
     // Using the LeakDetector requires exposing GC.
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--expose-gc");
   }
 };
 
diff --git a/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc b/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
index a8da74e..33764f6af 100644
--- a/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
+++ b/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
@@ -54,6 +54,7 @@
 #include "services/network/public/cpp/corb/corb_impl.h"
 #include "services/network/public/cpp/features.h"
 #include "storage/browser/blob/blob_storage_context.h"
+#include "third_party/blink/public/common/switches.h"
 
 namespace content {
 
@@ -827,7 +828,8 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     PrefetchBrowserTestBase::SetUpCommandLine(command_line);
     // For window.gc().
-    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--expose-gc");
   }
 
   void SetUp() override {
diff --git a/content/browser/webrtc/webrtc_datachannel_browsertest.cc b/content/browser/webrtc/webrtc_datachannel_browsertest.cc
index c790902..69a5fc0 100644
--- a/content/browser/webrtc/webrtc_datachannel_browsertest.cc
+++ b/content/browser/webrtc/webrtc_datachannel_browsertest.cc
@@ -5,8 +5,8 @@
 #include "base/command_line.h"
 #include "base/macros.h"
 #include "content/browser/webrtc/webrtc_content_browsertest_base.h"
-#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
+#include "third_party/blink/public/common/switches.h"
 
 namespace {
 
@@ -30,7 +30,7 @@
     WebRtcContentBrowserTestBase::SetUpCommandLine(command_line);
     AppendUseFakeUIForMediaStreamFlag();
     base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-        switches::kJavaScriptFlags, kJavaScriptFeaturesNeeded);
+        blink::switches::kJavaScriptFlags, kJavaScriptFeaturesNeeded);
   }
 };
 
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index ddad753..1746ffa4 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -557,9 +557,6 @@
 // Enables unsafe fast JS calls between Blink and V8.
 const char kEnableUnsafeFastJSCalls[] = "enable-unsafe-fast-js-calls";
 
-// Specifies the flags passed to JS engine.
-const char kJavaScriptFlags[]               = "js-flags";
-
 // Flag to launch tests in the browser process.
 const char kLaunchAsBrowser[] = "as-browser";
 
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index d7e1b4c..1cc5f21 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -155,7 +155,6 @@
 CONTENT_EXPORT extern const char kIPCConnectionTimeout[];
 CONTENT_EXPORT extern const char kIsolateOrigins[];
 CONTENT_EXPORT extern const char kIsolationByDefault[];
-CONTENT_EXPORT extern const char kJavaScriptFlags[];
 CONTENT_EXPORT extern const char kJavaScriptHarmony[];
 CONTENT_EXPORT extern const char kEnableUnsafeFastJSCalls[];
 CONTENT_EXPORT extern const char kLaunchAsBrowser[];
diff --git a/content/renderer/gin_browsertest.cc b/content/renderer/gin_browsertest.cc
index 046b620..a1aa848 100644
--- a/content/renderer/gin_browsertest.cc
+++ b/content/renderer/gin_browsertest.cc
@@ -4,13 +4,13 @@
 
 #include "base/command_line.h"
 #include "base/macros.h"
-#include "content/public/common/content_switches.h"
 #include "content/public/test/render_view_test.h"
 #include "content/renderer/render_view_impl.h"
 #include "gin/handle.h"
 #include "gin/per_isolate_data.h"
 #include "gin/wrappable.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/web/blink.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 #include "third_party/blink/public/web/web_view.h"
@@ -50,7 +50,7 @@
 
   void SetUp() override {
     base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-        switches::kJavaScriptFlags, "--expose_gc");
+        blink::switches::kJavaScriptFlags, "--expose_gc");
 
     RenderViewTest::SetUp();
   }
diff --git a/content/renderer/render_process_impl.cc b/content/renderer/render_process_impl.cc
index e1bb26f..f7de1ad 100644
--- a/content/renderer/render_process_impl.cc
+++ b/content/renderer/render_process_impl.cc
@@ -25,8 +25,6 @@
 #include "base/debug/stack_trace.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_split.h"
 #include "base/system/sys_info.h"
 #include "base/task/thread_pool/initialization_util.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
@@ -271,16 +269,6 @@
     v8::V8::EnableWebAssemblyTrapHandler(use_v8_signal_handler);
   }
 #endif  // defined(OS_MAC) && defined(ARCH_CPU_X86_64)
-
-  if (command_line->HasSwitch(switches::kJavaScriptFlags)) {
-    std::string js_flags =
-        command_line->GetSwitchValueASCII(switches::kJavaScriptFlags);
-    std::vector<base::StringPiece> flag_list = base::SplitStringPiece(
-        js_flags, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-    for (const auto& flag : flag_list) {
-      v8::V8::SetFlagsFromString(std::string(flag).c_str(), flag.size());
-    }
-  }
 }
 
 RenderProcessImpl::~RenderProcessImpl() {
diff --git a/content/test/ppapi/ppapi_test.cc b/content/test/ppapi/ppapi_test.cc
index 00bb7f22..8b717e0 100644
--- a/content/test/ppapi/ppapi_test.cc
+++ b/content/test/ppapi/ppapi_test.cc
@@ -20,6 +20,7 @@
 #include "media/base/media_switches.h"
 #include "net/base/filename_util.h"
 #include "ppapi/shared_impl/ppapi_switches.h"
+#include "third_party/blink/public/common/switches.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/components/audio/cras_audio_handler.h"
@@ -57,7 +58,8 @@
   command_line->AppendSwitch(switches::kDisableSmoothScrolling);
 
   // Allow manual garbage collection.
-  command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose_gc");
+  command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                  "--expose_gc");
 
   command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
 
diff --git a/extensions/browser/guest_view/web_view/web_view_apitest.cc b/extensions/browser/guest_view/web_view/web_view_apitest.cc
index ccdab49..2cfe736 100644
--- a/extensions/browser/guest_view/web_view/web_view_apitest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_apitest.cc
@@ -51,6 +51,7 @@
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "services/network/public/cpp/features.h"
+#include "third_party/blink/public/common/switches.h"
 #include "ui/display/display_switches.h"
 
 #if defined(USE_AURA)
@@ -205,7 +206,8 @@
 
 void WebViewAPITest::SetUpCommandLine(base::CommandLine* command_line) {
   AppShellTest::SetUpCommandLine(command_line);
-  command_line->AppendSwitchASCII(::switches::kJavaScriptFlags, "--expose-gc");
+  command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                  "--expose-gc");
 }
 
 void WebViewAPITest::SetUpOnMainThread() {
diff --git a/gin/isolate_holder.cc b/gin/isolate_holder.cc
index 8786c14..d9b2b28f 100644
--- a/gin/isolate_holder.cc
+++ b/gin/isolate_holder.cc
@@ -110,9 +110,10 @@
 // static
 void IsolateHolder::Initialize(ScriptMode mode,
                                v8::ArrayBuffer::Allocator* allocator,
-                               const intptr_t* reference_table) {
+                               const intptr_t* reference_table,
+                               const std::string js_command_line_flags) {
   CHECK(allocator);
-  V8Initializer::Initialize(mode);
+  V8Initializer::Initialize(mode, js_command_line_flags);
   g_array_buffer_allocator = allocator;
   g_reference_table = reference_table;
 }
diff --git a/gin/public/isolate_holder.h b/gin/public/isolate_holder.h
index f0a7d0c05..8dbcc35 100644
--- a/gin/public/isolate_holder.h
+++ b/gin/public/isolate_holder.h
@@ -89,12 +89,15 @@
   // defined and the snapshot file is available, it should be loaded (by calling
   // V8Initializer::LoadV8SnapshotFromFD or
   // V8Initializer::LoadV8Snapshot) before calling this method.
+  // |js_command_line_flags| can contain a comma-separed command line flags
+  // that are passed to V8.
   // If the snapshot file contains customised contexts which have static
   // external references, |reference_table| needs to point an array of those
   // reference pointers. Otherwise, it can be nullptr.
   static void Initialize(ScriptMode mode,
                          v8::ArrayBuffer::Allocator* allocator,
-                         const intptr_t* reference_table = nullptr);
+                         const intptr_t* reference_table = nullptr,
+                         const std::string js_command_line_flags = {});
 
   // Returns whether `Initialize` has already been invoked in the process.
   // Initialization is a one-way operation (i.e., this method cannot return
diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc
index 497aeb6e..8beb760c 100644
--- a/gin/v8_initializer.cc
+++ b/gin/v8_initializer.cc
@@ -24,6 +24,8 @@
 #include "base/notreached.h"
 #include "base/path_service.h"
 #include "base/rand_util.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/system/sys_info.h"
@@ -228,48 +230,10 @@
   }
 }
 
-}  // namespace
-
-// static
-void V8Initializer::Initialize(IsolateHolder::ScriptMode mode) {
-  static bool v8_is_initialized = false;
-  if (v8_is_initialized)
-    return;
-
-  v8::V8::InitializePlatform(V8Platform::Get());
-
-  // Set this early on as some initialization steps, such as the initialization
-  // of the virtual memory cage, already use V8's random number generator.
-  v8::V8::SetEntropySource(&GenerateEntropy);
-
-#if defined(V8_VIRTUAL_MEMORY_CAGE)
-  static_assert(ARCH_CPU_64_BITS,
-                "V8 virtual memory cage can only work in 64-bit builds");
-  // For now, creating the virtual memory cage is optional, and we only do it
-  // if the correpsonding feature is enabled. In the future, it will be
-  // mandatory when compiling with V8_VIRTUAL_MEMORY_CAGE.
-  bool v8_cage_is_initialized = false;
-  if (base::FeatureList::IsEnabled(features::kV8VirtualMemoryCage)) {
-    v8_cage_is_initialized = v8::V8::InitializeVirtualMemoryCage();
-
-    // Record the size of the virtual memory cage, in GB. The size will always
-    // be a power of two, so we use a sparse histogram to capture it.
-    // If the initialization failed, this API will return zero.
-    // The main reason for capturing this histogram here instead of having V8
-    // do it is that there are no Isolates available yet, which are required
-    // for recording histograms in V8.
-    size_t size = v8::V8::GetVirtualMemoryCageSizeInBytes();
-    int sizeInGB = size >> 30;
-    DCHECK(base::bits::IsPowerOfTwo(size));
-    DCHECK(size == 0 || sizeInGB > 0);
-    base::UmaHistogramSparse("V8.VirtualMemoryCageSizeGB", sizeInGB);
-  }
-#endif
-
+void SetFlags(IsolateHolder::ScriptMode mode,
+              const std::string js_command_line_flags) {
   // We assume that all feature flag defaults correspond to the default
   // values of the coresponding V8 flags.
-  // TODO(cbruni): Fix flag priorities to let --js-flags override feature
-  // flags.
   SetV8FlagsIfOverridden(features::kV8OptimizeJavascript, "--opt", "--no-opt");
   SetV8FlagsIfOverridden(features::kV8FlushBytecode, "--flush-bytecode",
                          "--no-flush-bytecode");
@@ -348,6 +312,59 @@
     SetV8Flags("--use_strict");
   }
 
+  if (js_command_line_flags.empty())
+    return;
+
+  // Allow the --js-flags switch to override existing flags:
+  std::vector<base::StringPiece> flag_list =
+      base::SplitStringPiece(js_command_line_flags, ",", base::TRIM_WHITESPACE,
+                             base::SPLIT_WANT_NONEMPTY);
+  for (const auto& flag : flag_list) {
+    v8::V8::SetFlagsFromString(std::string(flag).c_str(), flag.size());
+  }
+}
+
+}  // namespace
+
+// static
+void V8Initializer::Initialize(IsolateHolder::ScriptMode mode,
+                               const std::string js_command_line_flags) {
+  static bool v8_is_initialized = false;
+  if (v8_is_initialized)
+    return;
+
+  v8::V8::InitializePlatform(V8Platform::Get());
+
+  // Set this early on as some initialization steps, such as the initialization
+  // of the virtual memory cage, already use V8's random number generator.
+  v8::V8::SetEntropySource(&GenerateEntropy);
+
+#if defined(V8_VIRTUAL_MEMORY_CAGE)
+  static_assert(ARCH_CPU_64_BITS,
+                "V8 virtual memory cage can only work in 64-bit builds");
+  // For now, creating the virtual memory cage is optional, and we only do it
+  // if the correpsonding feature is enabled. In the future, it will be
+  // mandatory when compiling with V8_VIRTUAL_MEMORY_CAGE.
+  bool v8_cage_is_initialized = false;
+  if (base::FeatureList::IsEnabled(features::kV8VirtualMemoryCage)) {
+    v8_cage_is_initialized = v8::V8::InitializeVirtualMemoryCage();
+
+    // Record the size of the virtual memory cage, in GB. The size will always
+    // be a power of two, so we use a sparse histogram to capture it.
+    // If the initialization failed, this API will return zero.
+    // The main reason for capturing this histogram here instead of having V8
+    // do it is that there are no Isolates available yet, which are required
+    // for recording histograms in V8.
+    size_t size = v8::V8::GetVirtualMemoryCageSizeInBytes();
+    int sizeInGB = size >> 30;
+    DCHECK(base::bits::IsPowerOfTwo(size));
+    DCHECK(size == 0 || sizeInGB > 0);
+    base::UmaHistogramSparse("V8.VirtualMemoryCageSizeGB", sizeInGB);
+  }
+#endif
+
+  SetFlags(mode, js_command_line_flags);
+
 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
   if (g_mapped_snapshot) {
     v8::StartupData snapshot;
diff --git a/gin/v8_initializer.h b/gin/v8_initializer.h
index 543bacd..de650aa 100644
--- a/gin/v8_initializer.h
+++ b/gin/v8_initializer.h
@@ -28,7 +28,8 @@
 class GIN_EXPORT V8Initializer {
  public:
   // This should be called by IsolateHolder::Initialize().
-  static void Initialize(IsolateHolder::ScriptMode mode);
+  static void Initialize(IsolateHolder::ScriptMode mode,
+                         const std::string js_command_line_flags = {});
 
   // Get address and size information for currently loaded snapshot.
   // If no snapshot is loaded, the return values are null for addresses
diff --git a/third_party/blink/common/switches.cc b/third_party/blink/common/switches.cc
index f8a99f8..0b40781 100644
--- a/third_party/blink/common/switches.cc
+++ b/third_party/blink/common/switches.cc
@@ -136,5 +136,9 @@
 
 const char kWebSQLInThirdPartyContextEnabled[] =
     "web-sql-in-third-party-context-enabled";
+
+// Specifies the flags passed to JS engine.
+const char kJavaScriptFlags[] = "js-flags";
+
 }  // namespace switches
 }  // namespace blink
diff --git a/third_party/blink/public/common/switches.h b/third_party/blink/public/common/switches.h
index c05317cb8..a995adf 100644
--- a/third_party/blink/public/common/switches.h
+++ b/third_party/blink/public/common/switches.h
@@ -16,9 +16,10 @@
 // alongside the definition of their values in the .cc file.
 BLINK_COMMON_EXPORT extern const char kAllowPreCommitInput[];
 BLINK_COMMON_EXPORT extern const char kBlinkSettings[];
+BLINK_COMMON_EXPORT extern const char kConditionalFocusWindowMs[];
 BLINK_COMMON_EXPORT extern const char kDarkModeSettings[];
-BLINK_COMMON_EXPORT extern const char kDefaultTileWidth[];
 BLINK_COMMON_EXPORT extern const char kDefaultTileHeight[];
+BLINK_COMMON_EXPORT extern const char kDefaultTileWidth[];
 BLINK_COMMON_EXPORT extern const char kDisableImageAnimationResync[];
 BLINK_COMMON_EXPORT extern const char kDisableLowResTiling[];
 BLINK_COMMON_EXPORT extern const char kDisablePartialRaster[];
@@ -30,24 +31,24 @@
     kEnableGpuMemoryBufferCompositorResources[];
 BLINK_COMMON_EXPORT extern const char kEnableLowResTiling[];
 BLINK_COMMON_EXPORT extern const char kEnablePreferCompositingToLCDText[];
-BLINK_COMMON_EXPORT extern const char kEnableRGBA4444Textures[];
 BLINK_COMMON_EXPORT extern const char kEnableRasterSideDarkModeForImages[];
+BLINK_COMMON_EXPORT extern const char kEnableRGBA4444Textures[];
 BLINK_COMMON_EXPORT extern const char kEnableZeroCopy[];
 BLINK_COMMON_EXPORT extern const char kGpuRasterizationMSAASampleCount[];
-BLINK_COMMON_EXPORT extern const char kIntensiveWakeUpThrottlingPolicy[];
 BLINK_COMMON_EXPORT extern const char
     kIntensiveWakeUpThrottlingPolicy_ForceDisable[];
 BLINK_COMMON_EXPORT extern const char
     kIntensiveWakeUpThrottlingPolicy_ForceEnable[];
+BLINK_COMMON_EXPORT extern const char kIntensiveWakeUpThrottlingPolicy[];
+BLINK_COMMON_EXPORT extern const char kJavaScriptFlags[];
 BLINK_COMMON_EXPORT extern const char kMaxUntiledLayerHeight[];
 BLINK_COMMON_EXPORT extern const char kMaxUntiledLayerWidth[];
 BLINK_COMMON_EXPORT extern const char kMinHeightForGpuRasterTile[];
 BLINK_COMMON_EXPORT extern const char kNetworkQuietTimeout[];
+BLINK_COMMON_EXPORT extern const char kSharedArrayBufferAllowedOrigins[];
 BLINK_COMMON_EXPORT extern const char kShowLayoutShiftRegions[];
 BLINK_COMMON_EXPORT extern const char kShowPaintRects[];
 BLINK_COMMON_EXPORT extern const char kTouchTextSelectionStrategy[];
-BLINK_COMMON_EXPORT extern const char kSharedArrayBufferAllowedOrigins[];
-BLINK_COMMON_EXPORT extern const char kConditionalFocusWindowMs[];
 BLINK_COMMON_EXPORT extern const char kWebSQLInThirdPartyContextEnabled[];
 
 }  // namespace switches
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 53e11a4f..3b1ac1f 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -33,6 +33,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/system/sys_info.h"
+#include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
 #include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
@@ -750,12 +751,15 @@
 
 }  // namespace
 
-void V8Initializer::InitializeMainThread(const intptr_t* reference_table) {
+void V8Initializer::InitializeMainThread(
+    const intptr_t* reference_table,
+    const std::string js_command_line_flags) {
   DCHECK(IsMainThread());
 
   DEFINE_STATIC_LOCAL(ArrayBufferAllocator, array_buffer_allocator, ());
   gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode,
-                                 &array_buffer_allocator, reference_table);
+                                 &array_buffer_allocator, reference_table,
+                                 js_command_line_flags);
 
   ThreadScheduler* scheduler = ThreadScheduler::Current();
 
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.h b/third_party/blink/renderer/bindings/core/v8/v8_initializer.h
index e7cbc5d..71154d89 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.h
@@ -59,7 +59,8 @@
   static void SetNearV8HeapLimitOnMainThreadCallback(
       NearV8HeapLimitCallback callback);
 
-  static void InitializeMainThread(const intptr_t* reference_table);
+  static void InitializeMainThread(const intptr_t* reference_table,
+                                   const std::string js_command_line_flag);
   static void InitializeWorker(v8::Isolate*);
 
   static void ReportRejectedPromisesOnMainThread();
diff --git a/third_party/blink/renderer/controller/blink_initializer.cc b/third_party/blink/renderer/controller/blink_initializer.cc
index 27db9b3..b10766a 100644
--- a/third_party/blink/renderer/controller/blink_initializer.cc
+++ b/third_party/blink/renderer/controller/blink_initializer.cc
@@ -34,9 +34,11 @@
 #include <utility>
 
 #include "base/allocator/partition_allocator/page_allocator.h"
+#include "base/command_line.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/bindings/binder_map.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/platform/interface_registry.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/web/blink.h"
@@ -131,7 +133,11 @@
   // BlinkInitializer::Initialize() must be called before InitializeMainThread
   GetBlinkInitializer().Initialize();
 
-  V8Initializer::InitializeMainThread(V8ContextSnapshot::GetReferenceTable());
+  std::string js_command_line_flag =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          blink::switches::kJavaScriptFlags);
+  V8Initializer::InitializeMainThread(V8ContextSnapshot::GetReferenceTable(),
+                                      js_command_line_flag);
 
   GetBlinkInitializer().RegisterInterfaces(*binders);
 
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 0e72104..af776a9 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -1534,6 +1534,7 @@
     {
         'paths': [
             'third_party/blink/renderer/modules/webdatabase/dom_window_web_database.cc',
+            'third_party/blink/renderer/controller/blink_initializer.cc',
         ],
         'allowed': [
             'base::CommandLine',