Ignore beforeunload dialogs

According to WebDriver standard the alerts arising on beforeunload event
must never be shown in Classic session. This commit implements
autoacceptance of any beforeunload alerts.

Bug: chromedriver:4757
Change-Id: I5276db0a872699248f89cca06c697fb5aab64649
Validate-Test-Flakiness: skip
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5517510
Reviewed-by: Maksim Sadym <sadym@chromium.org>
Commit-Queue: Vladimir Nechaev <nechaev@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1297374}
diff --git a/chrome/test/chromedriver/capabilities.cc b/chrome/test/chromedriver/capabilities.cc
index 27d8baf3..103bd88b 100644
--- a/chrome/test/chromedriver/capabilities.cc
+++ b/chrome/test/chromedriver/capabilities.cc
@@ -1118,7 +1118,7 @@
   parser_map["strictFileInteractability"] =
       base::BindRepeating(&ParseBoolean, &strict_file_interactability);
   parser_map["webSocketUrl"] =
-      base::BindRepeating(&ParseBoolean, &webSocketUrl);
+      base::BindRepeating(&ParseBoolean, &web_socket_url);
   if (!w3c_compliant) {
     // TODO(https://crbug.com/chromedriver/2596): "unexpectedAlertBehaviour" is
     // legacy name of "unhandledPromptBehavior", remove when we stop supporting
diff --git a/chrome/test/chromedriver/capabilities.h b/chrome/test/chromedriver/capabilities.h
index 12e4f92..6f52221 100644
--- a/chrome/test/chromedriver/capabilities.h
+++ b/chrome/test/chromedriver/capabilities.h
@@ -193,7 +193,7 @@
 
   std::set<WebViewInfo::Type> window_types;
 
-  bool webSocketUrl = false;
+  bool web_socket_url = false;
 };
 
 bool GetChromeOptionsDictionary(const base::Value::Dict& params,
diff --git a/chrome/test/chromedriver/chrome/chrome_android_impl.cc b/chrome/test/chromedriver/chrome/chrome_android_impl.cc
index 38b63bd..7426e6e 100644
--- a/chrome/test/chromedriver/chrome/chrome_android_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_android_impl.cc
@@ -22,13 +22,15 @@
         devtools_event_listeners,
     std::optional<MobileDevice> mobile_device,
     std::string page_load_strategy,
-    std::unique_ptr<Device> device)
+    std::unique_ptr<Device> device,
+    bool autoaccept_beforeunload)
     : ChromeImpl(std::move(browser_info),
                  std::move(window_types),
                  std::move(websocket_client),
                  std::move(devtools_event_listeners),
                  std::move(mobile_device),
-                 page_load_strategy),
+                 page_load_strategy,
+                 autoaccept_beforeunload),
       device_(std::move(device)) {}
 
 ChromeAndroidImpl::~ChromeAndroidImpl() = default;
diff --git a/chrome/test/chromedriver/chrome/chrome_android_impl.h b/chrome/test/chromedriver/chrome/chrome_android_impl.h
index 28e469ef..b4562a4 100644
--- a/chrome/test/chromedriver/chrome/chrome_android_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_android_impl.h
@@ -23,7 +23,8 @@
                         devtools_event_listeners,
                     std::optional<MobileDevice> mobile_device,
                     std::string page_load_strategy,
-                    std::unique_ptr<Device> device);
+                    std::unique_ptr<Device> device,
+                    bool autoaccept_beforeunload);
   ~ChromeAndroidImpl() override;
 
   // Overridden from Chrome:
diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
index 95136e2..09f6ca7d 100644
--- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
@@ -85,13 +85,15 @@
     const base::CommandLine& command,
     base::ScopedTempDir* user_data_dir,
     base::ScopedTempDir* extension_dir,
-    bool network_emulation_enabled)
+    bool network_emulation_enabled,
+    bool autoaccept_beforeunload)
     : ChromeImpl(std::move(browser_info),
                  std::move(window_types),
                  std::move(websocket_client),
                  std::move(devtools_event_listeners),
                  std::move(mobile_device),
-                 page_load_strategy),
+                 page_load_strategy,
+                 autoaccept_beforeunload),
       process_(std::move(process)),
       command_(command),
       network_connection_enabled_(network_emulation_enabled),
@@ -165,9 +167,9 @@
   if (status.IsError())
     return status;
   std::unique_ptr<WebViewImpl> web_view_tmp =
-      WebViewImpl::CreateTopLevelWebView(id, w3c_compliant, &browser_info_,
-                                         std::move(client), mobile_device,
-                                         page_load_strategy());
+      WebViewImpl::CreateTopLevelWebView(
+          id, w3c_compliant, &browser_info_, std::move(client), mobile_device,
+          page_load_strategy(), autoaccept_beforeunload_);
   status = web_view_tmp->AttachTo(devtools_websocket_client_.get());
   if (status.IsError()) {
     return status;
diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h
index 84061d6..d549b82e 100644
--- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h
@@ -35,7 +35,8 @@
                     const base::CommandLine& command,
                     base::ScopedTempDir* user_data_dir,
                     base::ScopedTempDir* extension_dir,
-                    bool network_emulation_enabled);
+                    bool network_emulation_enabled,
+                    bool autoaccept_beforeunload);
   ~ChromeDesktopImpl() override;
 
   // Waits for a page with the given URL to appear and finish loading.
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 8d4c303..edf2737e8 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -183,7 +183,7 @@
         } else {
           web_views_.push_back(WebViewImpl::CreateTopLevelWebView(
               view.id, w3c_compliant, &browser_info_, std::move(client),
-              mobile_device_, page_load_strategy_));
+              mobile_device_, page_load_strategy_, autoaccept_beforeunload_));
         }
         status = web_views_.back()->AttachTo(devtools_websocket_client_.get());
         if (status.IsError()) {
@@ -689,11 +689,13 @@
                        std::vector<std::unique_ptr<DevToolsEventListener>>
                            devtools_event_listeners,
                        std::optional<MobileDevice> mobile_device,
-                       std::string page_load_strategy)
+                       std::string page_load_strategy,
+                       bool autoaccept_beforeunload)
     : mobile_device_(std::move(mobile_device)),
       browser_info_(std::move(browser_info)),
       window_types_(std::move(window_types)),
       devtools_websocket_client_(std::move(websocket_client)),
+      autoaccept_beforeunload_(autoaccept_beforeunload),
       devtools_event_listeners_(std::move(devtools_event_listeners)),
       page_load_strategy_(page_load_strategy) {
   window_types_.insert(WebViewInfo::kPage);
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.h b/chrome/test/chromedriver/chrome/chrome_impl.h
index 37a7bd7f..b806a07 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_impl.h
@@ -96,7 +96,8 @@
              std::vector<std::unique_ptr<DevToolsEventListener>>
                  devtools_event_listeners,
              std::optional<MobileDevice> mobile_device,
-             std::string page_load_strategy);
+             std::string page_load_strategy,
+             bool autoaccept_beforeunload);
 
   virtual Status QuitImpl() = 0;
   Status CloseTarget(const std::string& id);
@@ -118,6 +119,7 @@
   BrowserInfo browser_info_;
   std::set<WebViewInfo::Type> window_types_;
   std::unique_ptr<DevToolsClient> devtools_websocket_client_;
+  bool autoaccept_beforeunload_ = false;
 
  private:
   static Status PermissionNameToChromePermissions(
diff --git a/chrome/test/chromedriver/chrome/chrome_remote_impl.cc b/chrome/test/chromedriver/chrome/chrome_remote_impl.cc
index ec6ce22..b3e4342 100644
--- a/chrome/test/chromedriver/chrome/chrome_remote_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_remote_impl.cc
@@ -18,13 +18,15 @@
     std::vector<std::unique_ptr<DevToolsEventListener>>
         devtools_event_listeners,
     std::optional<MobileDevice> mobile_device,
-    std::string page_load_strategy)
+    std::string page_load_strategy,
+    bool autoaccept_beforeunload)
     : ChromeImpl(std::move(browser_info),
                  std::move(window_types),
                  std::move(websocket_client),
                  std::move(devtools_event_listeners),
                  std::move(mobile_device),
-                 page_load_strategy) {}
+                 page_load_strategy,
+                 autoaccept_beforeunload) {}
 
 ChromeRemoteImpl::~ChromeRemoteImpl() = default;
 
diff --git a/chrome/test/chromedriver/chrome/chrome_remote_impl.h b/chrome/test/chromedriver/chrome/chrome_remote_impl.h
index 4c21c80..4a56a355 100644
--- a/chrome/test/chromedriver/chrome/chrome_remote_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_remote_impl.h
@@ -21,7 +21,8 @@
                    std::vector<std::unique_ptr<DevToolsEventListener>>
                        devtools_event_listeners,
                    std::optional<MobileDevice> mobile_device,
-                   std::string page_load_strategy);
+                   std::string page_load_strategy,
+                   bool autoaccept_beforeunload);
   ~ChromeRemoteImpl() override;
 
   // Overridden from Chrome.
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.cc b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
index 18c04c2..fcc8707 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
@@ -47,6 +47,9 @@
 const char kNoNodeForBackendNodeIdError[] =
     "No node found for given backend id";
 const char kNoNodeWithGivenIdFoundError[] = "No node with given id found";
+const char kExecutionContextWasDestroyed[] = "Execution context was destroyed.";
+const char kInspectedTargetNavigatedOrClosed[] =
+    "Inspected target navigated or closed";
 
 static constexpr int kSessionNotFoundInspectorCode = -32001;
 static constexpr int kCdpMethodNotFoundCode = -32601;
@@ -1061,7 +1064,8 @@
     crashed_ = true;
     return Status(kTabCrashed);
   }
-  if (event.method == "Page.javascriptDialogOpening") {
+  if ((owner_ && owner_->GetJavaScriptDialogManager()->IsDialogOpen()) ||
+      (!owner_ && event.method == "Page.javascriptDialogOpening")) {
     // A command may have opened the dialog, which will block the response.
     // To find out which one (if any), do a round trip with a simple command
     // to the renderer and afterwards see if any of the commands still haven't
@@ -1388,6 +1392,11 @@
       // The error message that arises during DOM.resolveNode code.
       // This means that the node with given BackendNodeId is not found.
       return Status{kNoSuchElement, error_message};
+    } else if (error_message == kExecutionContextWasDestroyed ||
+               error_message == kInspectedTargetNavigatedOrClosed) {
+      // The error messages that arise if navigation was started by the
+      // asynchronous script before the script execution was finished..
+      return Status{kNavigationDetectedByRemoteEnd, error_message};
     }
     std::optional<int> error_code = error_dict->FindInt("code");
     if (error_code == kInvalidParamsInspectorCode) {
diff --git a/chrome/test/chromedriver/chrome/javascript_dialog_manager.cc b/chrome/test/chromedriver/chrome/javascript_dialog_manager.cc
index 600cd61..8a77e13 100644
--- a/chrome/test/chromedriver/chrome/javascript_dialog_manager.cc
+++ b/chrome/test/chromedriver/chrome/javascript_dialog_manager.cc
@@ -7,8 +7,9 @@
 #include "chrome/test/chromedriver/chrome/devtools_client.h"
 #include "chrome/test/chromedriver/chrome/status.h"
 
-JavaScriptDialogManager::JavaScriptDialogManager(DevToolsClient* client)
-    : client_(client) {
+JavaScriptDialogManager::JavaScriptDialogManager(DevToolsClient* client,
+                                                 bool autoaccept_beforeunload)
+    : client_(client), autoaccept_beforeunload_(autoaccept_beforeunload) {
   client_->AddListener(this);
 }
 
@@ -95,6 +96,10 @@
                     "dialog event missing or invalid 'defaultPrompt'");
     }
     prompt_text_ = *prompt_text;
+
+    if (*type == "beforeunload" && autoaccept_beforeunload_) {
+      return HandleDialog(true, nullptr);
+    }
   } else if (method == "Page.javascriptDialogClosed") {
     // Inspector only sends this event when all dialogs have been closed.
     // Clear the unhandled queue in case the user closed a dialog manually.
diff --git a/chrome/test/chromedriver/chrome/javascript_dialog_manager.h b/chrome/test/chromedriver/chrome/javascript_dialog_manager.h
index 416d1cd..a0fcfba 100644
--- a/chrome/test/chromedriver/chrome/javascript_dialog_manager.h
+++ b/chrome/test/chromedriver/chrome/javascript_dialog_manager.h
@@ -18,7 +18,8 @@
 // Tracks the opening and closing of JavaScript dialogs (e.g., alerts).
 class JavaScriptDialogManager : public DevToolsEventListener {
  public:
-  explicit JavaScriptDialogManager(DevToolsClient* client);
+  explicit JavaScriptDialogManager(DevToolsClient* client,
+                                   bool autoaccept_beforeunload);
 
   JavaScriptDialogManager(const JavaScriptDialogManager&) = delete;
   JavaScriptDialogManager& operator=(const JavaScriptDialogManager&) = delete;
@@ -49,6 +50,8 @@
   std::list<std::string> dialog_type_queue_;
 
   std::string prompt_text_;
+
+  bool autoaccept_beforeunload_ = false;
 };
 
 #endif  // CHROME_TEST_CHROMEDRIVER_CHROME_JAVASCRIPT_DIALOG_MANAGER_H_
diff --git a/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc b/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc
index 29385f6..1c374de 100644
--- a/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc
+++ b/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc
@@ -17,7 +17,7 @@
 
 TEST(JavaScriptDialogManager, NoDialog) {
   StubDevToolsClient client;
-  JavaScriptDialogManager manager(&client);
+  JavaScriptDialogManager manager(&client, true);
   std::string message("HI");
   ASSERT_EQ(kNoSuchAlert, manager.GetDialogMessage(&message).code());
   ASSERT_FALSE(manager.IsDialogOpen());
@@ -27,7 +27,7 @@
 
 TEST(JavaScriptDialogManager, HandleDialogPassesParams) {
   RecorderDevToolsClient client;
-  JavaScriptDialogManager manager(&client);
+  JavaScriptDialogManager manager(&client, true);
   base::Value::Dict params;
   params.Set("message", "hi");
   params.Set("type", "prompt");
@@ -45,7 +45,7 @@
 
 TEST(JavaScriptDialogManager, HandleDialogNullPrompt) {
   RecorderDevToolsClient client;
-  JavaScriptDialogManager manager(&client);
+  JavaScriptDialogManager manager(&client, true);
   base::Value::Dict params;
   params.Set("message", "hi");
   params.Set("type", "prompt");
@@ -60,7 +60,7 @@
 
 TEST(JavaScriptDialogManager, ReconnectClearsStateAndSendsEnable) {
   RecorderDevToolsClient client;
-  JavaScriptDialogManager manager(&client);
+  JavaScriptDialogManager manager(&client, true);
   base::Value::Dict params;
   params.Set("message", "hi");
   params.Set("type", "alert");
@@ -122,7 +122,7 @@
 
 TEST(JavaScriptDialogManager, OneDialog) {
   FakeDevToolsClient client;
-  JavaScriptDialogManager manager(&client);
+  JavaScriptDialogManager manager(&client, true);
   base::Value::Dict params;
   params.Set("message", "hi");
   params.Set("type", "alert");
@@ -150,7 +150,7 @@
 
 TEST(JavaScriptDialogManager, TwoDialogs) {
   FakeDevToolsClient client;
-  JavaScriptDialogManager manager(&client);
+  JavaScriptDialogManager manager(&client, true);
   base::Value::Dict params;
   params.Set("message", "1");
   params.Set("type", "confirm");
@@ -189,7 +189,7 @@
 TEST(JavaScriptDialogManager, OneDialogManualClose) {
   StubDevToolsClient client;
   BrowserInfo browser_info;
-  JavaScriptDialogManager manager(&client);
+  JavaScriptDialogManager manager(&client, true);
   base::Value::Dict params;
   params.Set("message", "hi");
   params.Set("type", "alert");
@@ -215,3 +215,50 @@
   ASSERT_EQ(kNoSuchAlert, manager.GetDialogMessage(&message).code());
   ASSERT_EQ(kNoSuchAlert, manager.HandleDialog(false, nullptr).code());
 }
+
+TEST(JavaScriptDialogManager, BeforeunloadIsAutoAccepted) {
+  FakeDevToolsClient client;
+  JavaScriptDialogManager manager(&client, true);
+  base::Value::Dict params;
+  params.Set("message", "hi");
+  params.Set("type", "beforeunload");
+  params.Set("defaultPrompt", "");
+  ASSERT_FALSE(manager.IsDialogOpen());
+  std::string message;
+  ASSERT_EQ(kNoSuchAlert, manager.GetDialogMessage(&message).code());
+
+  client.set_closing_count(1);
+  ASSERT_EQ(
+      kOk,
+      manager.OnEvent(&client, "Page.javascriptDialogOpening", params).code());
+
+  ASSERT_FALSE(manager.IsDialogOpen());
+  ASSERT_EQ(kNoSuchAlert, manager.GetDialogMessage(&message).code());
+  ASSERT_EQ(kNoSuchAlert, manager.HandleDialog(false, nullptr).code());
+}
+
+TEST(JavaScriptDialogManager, AlertAndBeforeunloadAreAutoAccepted) {
+  FakeDevToolsClient client;
+  JavaScriptDialogManager manager(&client, true);
+  base::Value::Dict params;
+  params.Set("message", "1");
+  params.Set("type", "alert");
+  params.Set("defaultPrompt", "");
+  ASSERT_EQ(
+      kOk,
+      manager.OnEvent(&client, "Page.javascriptDialogOpening", params).code());
+  params.Set("message", "2");
+  params.Set("type", "beforeunload");
+  // If a beforeunload dialog is detected all the previous dialogs are
+  // auto-accepted because a navigation has started after after their
+  // appearance.
+  client.set_closing_count(1);
+  ASSERT_EQ(
+      kOk,
+      manager.OnEvent(&client, "Page.javascriptDialogOpening", params).code());
+
+  ASSERT_FALSE(manager.IsDialogOpen());
+  std::string message;
+  ASSERT_EQ(kNoSuchAlert, manager.GetDialogMessage(&message).code());
+  ASSERT_EQ(kNoSuchAlert, manager.HandleDialog(false, nullptr).code());
+}
diff --git a/chrome/test/chromedriver/chrome/navigation_tracker_unittest.cc b/chrome/test/chromedriver/chrome/navigation_tracker_unittest.cc
index 7b5377e..2f7965f9 100644
--- a/chrome/test/chromedriver/chrome/navigation_tracker_unittest.cc
+++ b/chrome/test/chromedriver/chrome/navigation_tracker_unittest.cc
@@ -131,8 +131,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
   base::Value::Dict params;
@@ -159,8 +159,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
   base::Value::Dict params;
@@ -188,8 +188,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
   base::Value::Dict params;
@@ -241,8 +241,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, NavigationTracker::kNotLoading,
                             &web_view, &dialog_manager);
 
@@ -263,7 +263,7 @@
       std::make_unique<DeterminingLoadStateDevToolsClient>(
           false, false, std::string(), &dict);
   DevToolsClient* client_ptr = client_uptr.get();
-  JavaScriptDialogManager dialog_manager(client_ptr);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   EvaluateScriptWebView web_view(kOk);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
@@ -314,7 +314,7 @@
       std::make_unique<DeterminingLoadStateDevToolsClient>(
           false, false, std::string(), &dict);
   DevToolsClient* client_ptr = client_uptr.get();
-  JavaScriptDialogManager dialog_manager(client_ptr);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   EvaluateScriptWebView web_view(kOk);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
@@ -353,7 +353,7 @@
       std::make_unique<DeterminingLoadStateDevToolsClient>(
           false, false, std::string(), &dict);
   DevToolsClient* client_ptr = client_uptr.get();
-  JavaScriptDialogManager dialog_manager(client_ptr);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   EvaluateScriptWebView web_view(kOk);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
@@ -423,8 +423,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
   bool is_pending;
@@ -441,8 +441,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
   ASSERT_NO_FATAL_FAILURE(AssertPendingState(&tracker, true));
@@ -454,7 +454,7 @@
       std::make_unique<DeterminingLoadStateDevToolsClient>(
           false, true, std::string(), &dict);
   DevToolsClient* client_ptr = client_uptr.get();
-  JavaScriptDialogManager dialog_manager(client_ptr);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   EvaluateScriptWebView web_view(kOk);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
@@ -470,8 +470,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
   base::Value::Dict params;
@@ -487,7 +487,7 @@
       std::make_unique<DeterminingLoadStateDevToolsClient>(
           false, true, std::string(), &dict);
   DevToolsClient* client_ptr = client_uptr.get();
-  JavaScriptDialogManager dialog_manager(client_ptr);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   EvaluateScriptWebView web_view(kOk);
   NavigationTracker tracker(client_ptr, NavigationTracker::kNotLoading,
                             &web_view, &dialog_manager);
@@ -509,7 +509,7 @@
       std::make_unique<DeterminingLoadStateDevToolsClient>(
           false, true, std::string(), &dict);
   DevToolsClient* client_ptr = client_uptr.get();
-  JavaScriptDialogManager dialog_manager(client_ptr);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   EvaluateScriptWebView web_view(kOk);
   NavigationTracker tracker(client_ptr, NavigationTracker::kNotLoading,
                             &web_view, &dialog_manager);
@@ -532,7 +532,7 @@
       std::make_unique<DeterminingLoadStateDevToolsClient>(
           false, true, std::string(), &dict);
   DevToolsClient* client_ptr = client_uptr.get();
-  JavaScriptDialogManager dialog_manager(client_ptr);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   EvaluateScriptWebView web_view(kOk);
   NavigationTracker tracker(client_ptr, NavigationTracker::kNotLoading,
                             &web_view, &dialog_manager);
@@ -573,8 +573,8 @@
   DevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl web_view(client_ptr->GetId(), true, nullptr, &browser_info,
                        std::move(client_uptr), std::nullopt,
-                       PageLoadStrategy::kNormal);
-  JavaScriptDialogManager dialog_manager(client_ptr);
+                       PageLoadStrategy::kNormal, true);
+  JavaScriptDialogManager dialog_manager(client_ptr, true);
   NavigationTracker tracker(client_ptr, &web_view, &dialog_manager);
 
   bool is_pending;
diff --git a/chrome/test/chromedriver/chrome/status.cc b/chrome/test/chromedriver/chrome/status.cc
index 3ef992b..67556a9 100644
--- a/chrome/test/chromedriver/chrome/status.cc
+++ b/chrome/test/chromedriver/chrome/status.cc
@@ -76,6 +76,8 @@
       return "no such shadow root";
     case kDetachedShadowRoot:
       return "detached shadow root";
+    case kNavigationDetectedByRemoteEnd:
+      return "navigation detected by remote end";
     default:
       return "<unknown>";
   }
diff --git a/chrome/test/chromedriver/chrome/status.h b/chrome/test/chromedriver/chrome/status.h
index f881b7a..65e4157 100644
--- a/chrome/test/chromedriver/chrome/status.h
+++ b/chrome/test/chromedriver/chrome/status.h
@@ -44,6 +44,7 @@
   kTabCrashed,
   kTargetDetached,
   kUnexpectedAlertOpen_Keep,
+  kNavigationDetectedByRemoteEnd,
 };
 
 // Represents a WebDriver status, which may be an error or ok.
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc
index 3343f4c..d46ea59 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.cc
+++ b/chrome/test/chromedriver/chrome/web_view_impl.cc
@@ -373,10 +373,11 @@
     const BrowserInfo* browser_info,
     std::unique_ptr<DevToolsClient> client,
     std::optional<MobileDevice> mobile_device,
-    std::string page_load_strategy) {
+    std::string page_load_strategy,
+    bool autoaccept_beforeunload) {
   return std::make_unique<WebViewImpl>(
       id, w3c_compliant, nullptr, browser_info, std::move(client),
-      std::move(mobile_device), page_load_strategy);
+      std::move(mobile_device), page_load_strategy, autoaccept_beforeunload);
 }
 
 WebViewImpl::WebViewImpl(const std::string& id,
@@ -407,7 +408,8 @@
                          const BrowserInfo* browser_info,
                          std::unique_ptr<DevToolsClient> client,
                          std::optional<MobileDevice> mobile_device,
-                         std::string page_load_strategy)
+                         std::string page_load_strategy,
+                         bool autoaccept_beforeunload)
     : id_(id),
       w3c_compliant_(w3c_compliant),
       browser_info_(browser_info),
@@ -416,7 +418,8 @@
       parent_(parent),
       client_(std::move(client)),
       frame_tracker_(new FrameTracker(client_.get(), this)),
-      dialog_manager_(new JavaScriptDialogManager(client_.get())),
+      dialog_manager_(
+          new JavaScriptDialogManager(client_.get(), autoaccept_beforeunload)),
       mobile_emulation_override_manager_(
           new MobileEmulationOverrideManager(client_.get(),
                                              std::move(mobile_device),
@@ -426,7 +429,8 @@
       network_conditions_override_manager_(
           new NetworkConditionsOverrideManager(client_.get())),
       heap_snapshot_taker_(new HeapSnapshotTaker(client_.get())),
-      is_service_worker_(false) {
+      is_service_worker_(false),
+      autoaccept_beforeunload_(autoaccept_beforeunload) {
   // Downloading in headless mode requires the setting of
   // Browser.setDownloadBehavior. This is handled by the
   // DownloadDirectoryOverrideManager, which is only instantiated
@@ -481,7 +485,7 @@
       std::make_unique<DevToolsClientImpl>(session_id, session_id);
   std::unique_ptr<WebViewImpl> child = std::make_unique<WebViewImpl>(
       target_id, w3c_compliant_, this, browser_info_, std::move(child_client),
-      std::nullopt, "");
+      std::nullopt, "", autoaccept_beforeunload_);
   const WebViewImpl* root_view = this;
   while (root_view->parent_ != nullptr) {
     root_view = root_view->parent_;
@@ -1378,7 +1382,8 @@
   Status status{kOk};
   while (keep_waiting) {
     status = client_->HandleEventsUntil(not_pending_navigation, timeout);
-    keep_waiting = status.code() == kNoSuchExecutionContext;
+    keep_waiting = status.code() == kNoSuchExecutionContext ||
+                   status.code() == kNavigationDetectedByRemoteEnd;
   }
   if (status.code() == kTimeout && stop_load_on_timeout) {
     VLOG(0) << "Timed out. Stopping navigation...";
@@ -1394,7 +1399,8 @@
       new_status = client_->HandleEventsUntil(
           not_pending_navigation,
           Timeout(base::Seconds(kWaitForNavigationStopSeconds)));
-      keep_waiting = new_status.code() == kNoSuchExecutionContext;
+      keep_waiting = new_status.code() == kNoSuchExecutionContext ||
+                     new_status.code() == kNavigationDetectedByRemoteEnd;
     }
     navigation_tracker_->set_timed_out(false);
     if (new_status.IsError())
@@ -1733,61 +1739,33 @@
   async_args.Append(true);
   std::unique_ptr<base::Value> tmp;
   Timeout local_timeout(timeout);
+  std::unique_ptr<base::Value> query_value;
   Status status = CallFunctionWithTimeout(frame, kExecuteAsyncScriptScript,
-                                          async_args, timeout, &tmp);
-  if (status.IsError())
+                                          async_args, timeout, &query_value);
+  if (status.IsError()) {
     return status;
-
-  const char kDocUnloadError[] = "document unloaded while waiting for result";
-  std::string kQueryResult = base::StringPrintf(
-      "function() {"
-      "  var info = document.$chrome_asyncScriptInfo;"
-      "  if (!info)"
-      "    return {status: %d, value: '%s'};"
-      "  var result = info.result;"
-      "  if (!result)"
-      "    return {status: 0};"
-      "  delete info.result;"
-      "  return result;"
-      "}",
-      kJavaScriptError,
-      kDocUnloadError);
-  const base::TimeDelta kOneHundredMs = base::Milliseconds(100);
-
-  while (true) {
-    base::Value::List no_args;
-    std::unique_ptr<base::Value> query_value;
-    status = CallFunction(frame, kQueryResult, no_args, &query_value);
-    if (status.IsError()) {
-      if (status.code() == kNoSuchFrame)
-        return Status(kJavaScriptError, kDocUnloadError);
-      return status;
-    }
-
-    base::Value::Dict* result_info = query_value->GetIfDict();
-    if (!result_info)
-      return Status(kUnknownError, "async result info is not a dictionary");
-    std::optional<int> status_code = result_info->FindInt("status");
-    if (!status_code)
-      return Status(kUnknownError, "async result info has no int 'status'");
-    if (*status_code != kOk) {
-      const std::string* message = result_info->FindString("value");
-      return Status(static_cast<StatusCode>(*status_code),
-                    message ? *message : "");
-    }
-
-    if (base::Value* value = result_info->Find("value")) {
-      *result = base::Value::ToUniquePtrValue(value->Clone());
-      return Status(kOk);
-    }
-
-    // Since async-scripts return immediately, need to time period here instead.
-    if (local_timeout.IsExpired())
-      return Status(kTimeout);
-
-    base::PlatformThread::Sleep(
-        std::min(kOneHundredMs, local_timeout.GetRemainingTime()));
   }
+
+  base::Value::Dict* result_info = query_value->GetIfDict();
+  if (!result_info) {
+    return Status(kUnknownError, "async result info is not a dictionary");
+  }
+  std::optional<int> status_code = result_info->FindInt("status");
+  if (!status_code) {
+    return Status(kUnknownError, "async result info has no int 'status'");
+  }
+  if (*status_code != kOk) {
+    const std::string* message = result_info->FindString("value");
+    return Status(static_cast<StatusCode>(*status_code),
+                  message ? *message : "");
+  }
+  base::Value* value = result_info->Find("value");
+  if (!value) {
+    return Status{kJavaScriptError,
+                  "no value field in Reuntime.callFunctionOn result"};
+  }
+  *result = base::Value::ToUniquePtrValue(value->Clone());
+  return Status(kOk);
 }
 
 void WebViewImpl::SetFrame(const std::string& new_frame_id) {
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.h b/chrome/test/chromedriver/chrome/web_view_impl.h
index c705bb80..9b53ae0 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.h
+++ b/chrome/test/chromedriver/chrome/web_view_impl.h
@@ -43,14 +43,16 @@
       const BrowserInfo* browser_info,
       std::unique_ptr<DevToolsClient> client,
       std::optional<MobileDevice> mobile_device,
-      std::string page_load_strategy);
+      std::string page_load_strategy,
+      bool autoaccept_beforeunload);
   WebViewImpl(const std::string& id,
               const bool w3c_compliant,
               const WebViewImpl* parent,
               const BrowserInfo* browser_info,
               std::unique_ptr<DevToolsClient> client,
               std::optional<MobileDevice> mobile_device,
-              std::string page_load_strategy);
+              std::string page_load_strategy,
+              bool autoaccept_beforeunload);
   ~WebViewImpl() override;
   std::unique_ptr<WebViewImpl> CreateChild(const std::string& session_id,
                                            const std::string& target_id) const;
@@ -270,6 +272,7 @@
   std::unique_ptr<CastTracker> cast_tracker_;
   std::unique_ptr<FedCmTracker> fedcm_tracker_;
   bool is_service_worker_;
+  bool autoaccept_beforeunload_ = false;
 };
 
 // Responsible for locking a WebViewImpl and its associated data structure to
diff --git a/chrome/test/chromedriver/chrome/web_view_impl_unittest.cc b/chrome/test/chromedriver/chrome/web_view_impl_unittest.cc
index 0d91820..5fcbfd2 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl_unittest.cc
+++ b/chrome/test/chromedriver/chrome/web_view_impl_unittest.cc
@@ -467,7 +467,7 @@
   BrowserInfo browser_info;
   WebViewImpl level1(client_ptr->GetId(), true, nullptr, &browser_info,
                      std::move(client_uptr), std::nullopt,
-                     PageLoadStrategy::kEager);
+                     PageLoadStrategy::kEager, true);
   EXPECT_TRUE(socket_holder.ConnectSocket());
   EXPECT_TRUE(StatusOk(client_ptr->SetSocket(socket_holder.Wrapper())));
   std::string sessionid = "2";
@@ -493,7 +493,7 @@
   BrowserInfo browser_info;
   WebViewImpl parent_view(client_ptr->GetId(), true, nullptr, &browser_info,
                           std::move(client_uptr), std::nullopt,
-                          PageLoadStrategy::kEager);
+                          PageLoadStrategy::kEager, true);
   EXPECT_TRUE(socket_holder.ConnectSocket());
   EXPECT_TRUE(StatusOk(client_ptr->SetSocket(socket_holder.Wrapper())));
   ASSERT_FALSE(parent_view.IsNonBlocking());
@@ -515,7 +515,7 @@
   BrowserInfo browser_info;
   WebViewImpl parent_view(client_ptr->GetId(), true, nullptr, &browser_info,
                           std::move(client_uptr), std::nullopt,
-                          PageLoadStrategy::kNone);
+                          PageLoadStrategy::kNone, true);
   EXPECT_TRUE(socket_holder.ConnectSocket());
   EXPECT_TRUE(StatusOk(client_ptr->SetSocket(socket_holder.Wrapper())));
   std::string sessionid = "2";
@@ -535,7 +535,7 @@
   BrowserInfo browser_info;
   WebViewImpl parent_view(client_ptr->GetId(), true, nullptr, &browser_info,
                           std::move(client_uptr), std::nullopt,
-                          PageLoadStrategy::kNone);
+                          PageLoadStrategy::kNone, true);
   EXPECT_TRUE(socket_holder.ConnectSocket());
   EXPECT_TRUE(StatusOk(client_ptr->SetSocket(socket_holder.Wrapper())));
   std::string sessionid = "2";
@@ -558,7 +558,7 @@
   BrowserInfo browser_info;
   WebViewImpl parent_view(client_ptr->GetId(), true, nullptr, &browser_info,
                           std::move(client_uptr), std::nullopt,
-                          PageLoadStrategy::kNormal);
+                          PageLoadStrategy::kNormal, true);
   EXPECT_TRUE(socket_holder.ConnectSocket());
   EXPECT_TRUE(StatusOk(client_ptr->SetSocket(socket_holder.Wrapper())));
   std::string sessionid = "2";
@@ -578,7 +578,7 @@
   BrowserInfo browser_info;
   WebViewImpl view(client_ptr->GetId(), true, nullptr, &browser_info,
                    std::move(client_uptr), std::nullopt,
-                   PageLoadStrategy::kEager);
+                   PageLoadStrategy::kEager, true);
   std::string samesite = "Strict";
   base::Value::Dict dict;
   dict.Set("success", true);
@@ -595,7 +595,7 @@
   BrowserInfo browser_info;
   WebViewImpl view(client_ptr->GetId(), true, nullptr, &browser_info,
                    std::move(client_uptr), std::nullopt,
-                   PageLoadStrategy::kEager);
+                   PageLoadStrategy::kEager, true);
   {
     // Good 1
     base::Value::Dict node_ref;
@@ -647,7 +647,7 @@
   BrowserInfo browser_info;
   WebViewImpl view(client_ptr->GetId(), true, nullptr, &browser_info,
                    std::move(client_uptr), std::nullopt,
-                   PageLoadStrategy::kEager);
+                   PageLoadStrategy::kEager, true);
   {
     // Good 1
     base::Value::Dict node_ref;
@@ -696,7 +696,7 @@
   BrowserInfo browser_info;
   WebViewImpl view(client_ptr->GetId(), false, nullptr, &browser_info,
                    std::move(client_uptr), std::nullopt,
-                   PageLoadStrategy::kEager);
+                   PageLoadStrategy::kEager, true);
   {
     base::Value::Dict node_ref;
     node_ref.Set(kElementKey, ElementReference("root", "root_loader", 25));
@@ -723,7 +723,7 @@
       std::make_unique<FakeDevToolsClient>("root");
   client_uptr->SetResult(GenerateResponse(4321));
   WebViewImpl view("root", true, nullptr, &browser_info, std::move(client_uptr),
-                   std::nullopt, PageLoadStrategy::kEager);
+                   std::nullopt, PageLoadStrategy::kEager, true);
   view.GetFrameTracker()->SetContextIdForFrame("root", "irrelevant");
   view.GetFrameTracker()->SetContextIdForFrame("good", "irrelevant");
   {
@@ -761,7 +761,7 @@
       std::make_unique<FakeDevToolsClient>("good");
   client_uptr->SetResult(GenerateResponse(4321));
   WebViewImpl view("good", true, nullptr, &browser_info, std::move(client_uptr),
-                   std::nullopt, PageLoadStrategy::kEager);
+                   std::nullopt, PageLoadStrategy::kEager, true);
   view.GetFrameTracker()->SetContextIdForFrame("root", "irrelevant");
   view.GetFrameTracker()->SetContextIdForFrame("good", "irrelevant");
   {
@@ -799,7 +799,7 @@
       std::make_unique<FakeDevToolsClient>("root");
   FakeDevToolsClient* client_ptr = client_uptr.get();
   WebViewImpl view("root", true, nullptr, &browser_info, std::move(client_uptr),
-                   std::nullopt, PageLoadStrategy::kEager);
+                   std::nullopt, PageLoadStrategy::kEager, true);
   view.GetFrameTracker()->SetContextIdForFrame("root", "irrelevant");
   view.GetFrameTracker()->SetContextIdForFrame("good", "irrelevant");
   view.GetFrameTracker()->SetContextIdForFrame("bad", "irrelevant");
@@ -891,7 +891,7 @@
   BrowserInfo browser_info;
   WebViewImpl view(client_ptr->GetId(), true, nullptr, &browser_info,
                    std::move(client_uptr), std::nullopt,
-                   PageLoadStrategy::kEager);
+                   PageLoadStrategy::kEager, true);
   FedCmTracker* tracker = nullptr;
   Status status = view.GetFedCmTracker(&tracker);
   EXPECT_TRUE(StatusOk(status));
@@ -907,7 +907,7 @@
     client_ptr = client_uptr.get();
     view = std::make_unique<WebViewImpl>(
         "root", IsW3C(), nullptr, &browser_info, std::move(client_uptr),
-        std::nullopt, PageLoadStrategy::kEager);
+        std::nullopt, PageLoadStrategy::kEager, true);
     view->GetFrameTracker()->SetContextIdForFrame("root", "irrelevant");
     view->GetFrameTracker()->SetContextIdForFrame("good", "irrelevant");
     view->GetFrameTracker()->SetContextIdForFrame("bad", "irrelevant");
@@ -1104,7 +1104,7 @@
 
   BrowserInfo browser_info;
   WebViewImpl view("root", true, nullptr, &browser_info, std::move(client_uptr),
-                   std::nullopt, PageLoadStrategy::kEager);
+                   std::nullopt, PageLoadStrategy::kEager, true);
   view.GetFrameTracker()->SetContextIdForFrame("root", "irrelevant");
 
   std::unique_ptr<base::Value> result;
@@ -1145,7 +1145,7 @@
 
   BrowserInfo browser_info;
   WebViewImpl view("root", true, nullptr, &browser_info, std::move(client_uptr),
-                   std::nullopt, PageLoadStrategy::kEager);
+                   std::nullopt, PageLoadStrategy::kEager, true);
   view.GetFrameTracker()->SetContextIdForFrame("root", "irrelevant");
 
   std::unique_ptr<base::Value> result;
@@ -1183,7 +1183,7 @@
 
   BrowserInfo browser_info;
   WebViewImpl view("root", true, nullptr, &browser_info, std::move(client_uptr),
-                   std::nullopt, PageLoadStrategy::kEager);
+                   std::nullopt, PageLoadStrategy::kEager, true);
   view.GetFrameTracker()->SetContextIdForFrame("root", "irrelevant");
 
   std::unique_ptr<base::Value> result;
diff --git a/chrome/test/chromedriver/chrome_launcher.cc b/chrome/test/chromedriver/chrome_launcher.cc
index a559362..7ee02f56 100644
--- a/chrome/test/chromedriver/chrome_launcher.cc
+++ b/chrome/test/chromedriver/chrome_launcher.cc
@@ -253,7 +253,7 @@
   // disable throttling all together.
   // TODO(crbug.com/chromedriver/4762): Remove after the Mapper is moved away
   // from the tab.
-  if (capabilities.webSocketUrl) {
+  if (capabilities.web_socket_url) {
     switches.SetSwitch("disable-background-timer-throttling");
   }
 
@@ -447,7 +447,8 @@
   chrome = std::make_unique<ChromeRemoteImpl>(
       browser_info, capabilities.window_types,
       std::move(devtools_websocket_client), std::move(devtools_event_listeners),
-      capabilities.mobile_device, capabilities.page_load_strategy);
+      capabilities.mobile_device, capabilities.page_load_strategy,
+      !capabilities.web_socket_url);
   return Status(kOk);
 }
 
@@ -759,20 +760,18 @@
           std::move(devtools_event_listeners), capabilities.mobile_device,
           capabilities.page_load_strategy, std::move(process), command,
           &user_data_dir_temp_dir, &extension_dir,
-          capabilities.network_emulation_enabled);
+          capabilities.network_emulation_enabled, !capabilities.web_socket_url);
   if (!capabilities.extension_load_timeout.is_zero()) {
-    for (size_t i = 0; i < extension_bg_pages.size(); ++i) {
-      VLOG(0) << "Waiting for extension bg page load: "
-              << extension_bg_pages[i];
+    for (const std::string& url : extension_bg_pages) {
+      VLOG(0) << "Waiting for extension bg page load: " << url;
       std::unique_ptr<WebView> web_view;
       status = chrome_desktop->WaitForPageToLoad(
-          extension_bg_pages[i], capabilities.extension_load_timeout, &web_view,
-          w3c_compliant);
+          url, capabilities.extension_load_timeout, &web_view, w3c_compliant);
       if (status.IsError()) {
-        return Status(kSessionNotCreated,
-                      "failed to wait for extension background page to load: " +
-                          extension_bg_pages[i],
-                      status);
+        return Status(
+            kSessionNotCreated,
+            "failed to wait for extension background page to load: " + url,
+            status);
       }
     }
   }
@@ -847,7 +846,7 @@
       browser_info, capabilities.window_types,
       std::move(devtools_websocket_client), std::move(devtools_event_listeners),
       capabilities.mobile_device, capabilities.page_load_strategy,
-      std::move(device));
+      std::move(device), !capabilities.web_socket_url);
   return Status(kOk);
 }
 
@@ -907,21 +906,19 @@
           std::move(devtools_event_listeners), capabilities.mobile_device,
           capabilities.page_load_strategy, std::move(dummy_process), command,
           &user_data_dir_temp_dir, &extension_dir,
-          capabilities.network_emulation_enabled);
+          capabilities.network_emulation_enabled, !capabilities.web_socket_url);
 
   if (!capabilities.extension_load_timeout.is_zero()) {
-    for (size_t i = 0; i < extension_bg_pages.size(); ++i) {
-      VLOG(0) << "Waiting for extension bg page load: "
-              << extension_bg_pages[i];
+    for (const std::string& url : extension_bg_pages) {
+      VLOG(0) << "Waiting for extension bg page load: " << url;
       std::unique_ptr<WebView> web_view;
       status = chrome_impl->WaitForPageToLoad(
-          extension_bg_pages[i], capabilities.extension_load_timeout, &web_view,
-          w3c_compliant);
+          url, capabilities.extension_load_timeout, &web_view, w3c_compliant);
       if (status.IsError()) {
-        return Status(kSessionNotCreated,
-                      "failed to wait for extension background page to load: " +
-                          extension_bg_pages[i],
-                      status);
+        return Status(
+            kSessionNotCreated,
+            "failed to wait for extension background page to load: " + url,
+            status);
       }
     }
   }
diff --git a/chrome/test/chromedriver/js/execute_async_script.js b/chrome/test/chromedriver/js/execute_async_script.js
index a0735e6..a210c48 100644
--- a/chrome/test/chromedriver/js/execute_async_script.js
+++ b/chrome/test/chromedriver/js/execute_async_script.js
@@ -14,23 +14,6 @@
 };
 
 /**
- * Dictionary key for asynchronous script info.
- * @const
- */
-var ASYNC_INFO_KEY = '$chrome_asyncScriptInfo';
-
-/**
-* Return the information of asynchronous script execution.
-*
-* @return {Object<?>} Information of asynchronous script execution.
-*/
-function getAsyncScriptInfo() {
-  if (!(ASYNC_INFO_KEY in document))
-    document[ASYNC_INFO_KEY] = {'id': 0};
-  return document[ASYNC_INFO_KEY];
-}
-
-/**
 * Execute the given script and save its asynchronous result.
 *
 * If script1 finishes after script2 is executed, then script1's result will be
@@ -46,61 +29,46 @@
 *     exception occurs during the script, and an additional error callback will
 *     be supplied to the script.
 */
-function executeAsyncScript(script, args, isUserSupplied) {
-  let resolveHandle;
-  let rejectHandle;
+async function executeAsyncScript(script, args, isUserSupplied) {
   const Promise = window.cdc_adoQpoasnfa76pfcZLmcfl_Promise || window.Promise;
-  var promise = new Promise((resolve, reject) => {
-    resolveHandle = resolve;
-    rejectHandle = reject;
-  });
-  const info = getAsyncScriptInfo();
-  info.id++;
-  delete info.result;
-  const id = info.id;
-
   function isThenable(value) {
     return typeof value === 'object' && typeof value.then === 'function';
   }
-  function report(status, value) {
-    if (id != info.id)
-      return;
-    info.id++;
-    // Undefined value is skipped when the object is converted to JSON.
-    // Replace it with null so we don't lose the value.
-    if (value === undefined)
-      value = null;
-    info.result = {status: status, value: value};
-  }
   function reportValue(value) {
-    report(StatusCode.OK, value);
+    return {status: StatusCode.OK, value: value};
   }
-  function reportScriptError(error) {
+  function reportError(error) {
     var code = isUserSupplied ? StatusCode.JAVASCRIPT_ERROR :
                                 (error.code || StatusCode.UNKNOWN_ERROR);
     var message = error.message;
     if (error.stack) {
       message += "\nJavaScript stack:\n" + error.stack;
     }
-    report(code, message);
+    return {status: code, value: message};
   }
-  promise.then(reportValue).catch(reportScriptError);
-  args.push(resolveHandle);
-  if (!isUserSupplied)
-    args.push(rejectHandle);
-  try {
-    const scriptResult = new Function(script).apply(null, args);
-    // The return value is only considered if it is a promise.
-    if (isThenable(scriptResult)) {
-      const resolvedPromise = Promise.resolve(scriptResult);
-      resolvedPromise.then((value) => {
-        // Must be thenable if user-supplied.
-        if (!isUserSupplied || isThenable(value))
-          resolveHandle(value);
-      })
-      .catch(rejectHandle);
+  var promise = new Promise((resolve, reject) => {
+    args.push(resolve);
+    if (!isUserSupplied) {
+      args.push(reject);
     }
-  } catch (error) {
-    rejectHandle(error);
-  }
+    try {
+      let scriptResult = new Function(script).apply(null, args);
+      if (isThenable(scriptResult)) {
+        const resolvedPromise = Promise.resolve(scriptResult);
+        resolvedPromise.then((value) => {
+          // Must be thenable if user-supplied.
+          if (!isUserSupplied || isThenable(value))
+            resolve(value);
+        })
+        .catch(reject);
+      }
+    } catch (error) {
+      reject(error);
+    }
+  });
+  return await promise.then((result) => {
+    return reportValue(result);
+  }).catch((error) => {
+    return reportError(error);
+  });
 }
diff --git a/chrome/test/chromedriver/js/execute_async_script_test.html b/chrome/test/chromedriver/js/execute_async_script_test.html
index 1c4ff5d..30aa597 100644
--- a/chrome/test/chromedriver/js/execute_async_script_test.html
+++ b/chrome/test/chromedriver/js/execute_async_script_test.html
@@ -4,117 +4,73 @@
 <script src='execute_async_script.js'></script>
 <script>
 
-function resetAsyncScriptInfo() {
-  delete document[ASYNC_INFO_KEY];
-}
-
-function waitForResultToPopulate(callback) {
-  const info = getAsyncScriptInfo();
-  setTimeout(() => {
-    if (info.result)
-      callback(info);
-    else
-      waitForResultToPopulate(callback);
-  }, 10);
-}
-
 function testUserScriptThrows(runner) {
-  resetAsyncScriptInfo();
-  executeAsyncScript('f(123);', [], true);
-  waitForResultToPopulate((info) => {
-    assertEquals(StatusCode.JAVASCRIPT_ERROR, info.result.status);
+  executeAsyncScript('f(123);', [], true).then((result)=>{
+    assertEquals(StatusCode.JAVASCRIPT_ERROR, result.status);
     runner.continueTesting();
-  });
-  runner.waitForAsync();
+    });
+  runner.waitForAsync("StatusCode.JAVASCRIPT_ERROR");
 }
 
 function testScriptThrows(runner) {
-  resetAsyncScriptInfo();
-  executeAsyncScript('f(123);', [], false);
-  waitForResultToPopulate((info) => {
-    assertEquals(StatusCode.UNKNOWN_ERROR, info.result.status);
+  executeAsyncScript('f(123);', [], false).then((result)=>{
+    assertEquals(StatusCode.UNKNOWN_ERROR, result.status);
     runner.continueTesting();
-  });
-  runner.waitForAsync();
+    });
+  runner.waitForAsync("StatusCode.UNKNOWN_ERROR");
 }
 
 function testUserScriptWithArgs(runner) {
-  resetAsyncScriptInfo();
-
-  var injectedArgs = null;
+  let injectedArgs = null;
   function captureArguments(args) {
     injectedArgs = args;
   }
   // Pass function captureArguments as the first argument. It is used to capture
   // the injected arguments to the following script.
-  var script =
+  const script =
       'var args = arguments; args[0](args); args[args.length - 1](args[1]);';
-  var script_args = [captureArguments, 1];
-  executeAsyncScript(script, script_args, true);
-  waitForResultToPopulate((info) => {
-    assertEquals(3, injectedArgs.length);
-    assertEquals(captureArguments, injectedArgs[0]);
-    assertEquals(1, injectedArgs[1]);
+  const script_args = [captureArguments, 1];
+  executeAsyncScript(script, script_args, true)
+    .then((result)=>{
+      assertEquals(3, injectedArgs.length);
+      assertEquals(captureArguments, injectedArgs[0]);
+      assertEquals(1, injectedArgs[1]);
 
-    assertEquals(0, info.result.status);
-    assertEquals(1, info.result.value);
-    assertEquals(2, info.id);
-    runner.continueTesting();
-  });
-  runner.waitForAsync();
+      assertEquals(0, result.status);
+      assertEquals(1, result.value);
+      runner.continueTesting();
+    });
+  runner.waitForAsync("arguments are captured");
 }
 
 function testNonUserScriptCallback(runner) {
-  resetAsyncScriptInfo();
-  executeAsyncScript('arguments[1](arguments[0])', [33], false);
-  waitForResultToPopulate((info) => {
-    assertEquals(0, info.result.status);
-    assertEquals(33, info.result.value);
-    runner.continueTesting();
-  });
-  runner.waitForAsync();
+  executeAsyncScript('arguments[1](arguments[0])', [33], false)
+    .then((result)=>{
+      assertEquals(0, result.status);
+      assertEquals(33, result.value);
+      runner.continueTesting();
+    });
+  runner.waitForAsync('user script callback');
 }
 
 function testNonUserScriptCustomError(runner) {
-  resetAsyncScriptInfo();
-  executeAsyncScript('arguments[2](new Error("ERR"))', [33], false);
-  waitForResultToPopulate((info) => {
-    assertEquals(StatusCode.UNKNOWN_ERROR, info.result.status);
-    assertEquals(0, info.result.value.indexOf('ERR'));
-    runner.continueTesting();
-  });
-  runner.waitForAsync();
+  executeAsyncScript('arguments[2](new Error("ERR"))', [33], false)
+    .then((result)=>{
+      assertEquals(StatusCode.UNKNOWN_ERROR, result.status);
+      assertEquals(0, result.value.indexOf('ERR'));
+      runner.continueTesting();
+    });
+  runner.waitForAsync("custom error");
 }
 
 function testNonUserScriptCustomErrorCode(runner) {
-  resetAsyncScriptInfo();
   executeAsyncScript('var e = new Error("ERR"); e.code = 111; arguments[1](e)',
-                     [], false);
-  waitForResultToPopulate((info) => {
-    assertEquals(111, info.result.status);
-    assertEquals(0, info.result.value.indexOf('ERR'));
-    runner.continueTesting();
+    [], false).then((result)=>{
+      assertEquals(111, result.status);
+      assertEquals(0, result.value.indexOf('ERR'));
+      runner.continueTesting();
   });
-  runner.waitForAsync();
-}
-
-function testFirstScriptFinishAfterSecondScriptExecute(runner) {
-  resetAsyncScriptInfo();
-
-  executeAsyncScript(
-      'var f = arguments[0]; setTimeout(function(){ f(1); }, 100000);', []);
-  var info = getAsyncScriptInfo();
-  assert(!info.hasOwnProperty('result'));
-  assertEquals(1, info.id);
-
-  executeAsyncScript('var fn = arguments[0]; fn(2);', []);
-  waitForResultToPopulate((info) => {
-    assertEquals(0, info.result.status);
-    assertEquals(2, info.result.value);
-    assertEquals(3, info.id);
-    runner.continueTesting();
-  });
-  runner.waitForAsync();
+  runner.waitForAsync("custom error with custom error code");
 }
 
 </script>
diff --git a/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc b/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc
index 722bf4c..ed2676cf 100644
--- a/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc
+++ b/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc
@@ -20,7 +20,8 @@
     const base::CommandLine& command,
     base::ScopedTempDir* user_data_dir,
     base::ScopedTempDir* extension_dir,
-    bool network_emulation_enabled)
+    bool network_emulation_enabled,
+    bool autoaccept_beforeunload)
     : ChromeDesktopImpl(std::move(browser_info),
                         std::move(window_types),
                         std::move(websocket_client),
@@ -31,7 +32,8 @@
                         command,
                         user_data_dir,
                         extension_dir,
-                        network_emulation_enabled) {}
+                        network_emulation_enabled,
+                        autoaccept_beforeunload) {}
 
 ChromeReplayImpl::~ChromeReplayImpl() = default;
 
diff --git a/chrome/test/chromedriver/log_replay/chrome_replay_impl.h b/chrome/test/chromedriver/log_replay/chrome_replay_impl.h
index 43d743b..2848163 100644
--- a/chrome/test/chromedriver/log_replay/chrome_replay_impl.h
+++ b/chrome/test/chromedriver/log_replay/chrome_replay_impl.h
@@ -28,7 +28,8 @@
                    const base::CommandLine& command,
                    base::ScopedTempDir* user_data_dir,
                    base::ScopedTempDir* extension_dir,
-                   bool network_emulation_enabled);
+                   bool network_emulation_enabled,
+                   bool autoaccept_beforeunload);
   ~ChromeReplayImpl() override;
 
   // A no-op: all this does in DesktopChromeImpl is kill the Chrome process.
diff --git a/chrome/test/chromedriver/navigation_tracker_integrationtest.cc b/chrome/test/chromedriver/navigation_tracker_integrationtest.cc
index ad6b94c4..ca50ea1 100644
--- a/chrome/test/chromedriver/navigation_tracker_integrationtest.cc
+++ b/chrome/test/chromedriver/navigation_tracker_integrationtest.cc
@@ -173,7 +173,7 @@
   ASSERT_TRUE(StatusOk(status));
   WebViewImpl web_view(view_info->id, true, nullptr, &browser_info_,
                        std::move(client), std::nullopt,
-                       PageLoadStrategy::kNormal);
+                       PageLoadStrategy::kNormal, true);
   web_view.AttachTo(browser_client_.get());
 
   http_server_.SetDataForPath("test.html", "<span>DONE!</span>");
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index 8fd52ea..d6f13e17 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -1528,7 +1528,9 @@
     default:
       DCHECK(false);
       // Examples of unexpected codes:
-      // * kChromeNotReachable - kSessionNotCreated must be returned instead
+      // * kChromeNotReachable: kSessionNotCreated must be returned instead;
+      // * kNavigationDetectedByRemoteEnd: kUnknownError must be returned
+      //   instead.
       response = std::make_unique<net::HttpServerResponseInfo>(
           net::HTTP_INTERNAL_SERVER_ERROR);
       break;
diff --git a/chrome/test/chromedriver/session.h b/chrome/test/chromedriver/session.h
index c38f01f..d337d457 100644
--- a/chrome/test/chromedriver/session.h
+++ b/chrome/test/chromedriver/session.h
@@ -114,7 +114,7 @@
 
   const std::string id;
   bool w3c_compliant;
-  bool webSocketUrl = false;
+  bool web_socket_url = false;
   bool quit;
   bool detach;
   bool awaiting_bidi_response = false;
diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc
index 4b36413f..7b38fa1b 100644
--- a/chrome/test/chromedriver/session_commands.cc
+++ b/chrome/test/chromedriver/session_commands.cc
@@ -259,7 +259,7 @@
     caps.Set("hasTouchScreen", session->chrome->HasTouchScreen());
   }
 
-  if (session->webSocketUrl) {
+  if (session->web_socket_url) {
     caps.Set("webSocketUrl",
              "ws://" + session->host + "/session/" + session->id);
   }
@@ -300,7 +300,7 @@
   // |session| will own the |CommandListener|s.
   session->command_listeners.swap(command_listeners);
 
-  if (session->webSocketUrl) {
+  if (session->web_socket_url) {
     // Suffixes used with the client channels.
     std::string client_suffixes[] = {Session::kChannelSuffix,
                                      Session::kNoChannelSuffix,
@@ -350,7 +350,7 @@
     *value = std::make_unique<base::Value>(session->capabilities->Clone());
   }
 
-  if (session->webSocketUrl) {
+  if (session->web_socket_url) {
     WebView* web_view = nullptr;
     status = session->GetTargetWindow(&web_view);
     if (status.IsError())
@@ -457,7 +457,7 @@
   session->script_timeout = capabilities->script_timeout;
   session->strict_file_interactability =
       capabilities->strict_file_interactability;
-  session->webSocketUrl = capabilities->webSocketUrl;
+  session->web_socket_url = capabilities->web_socket_url;
   Log::Level driver_level = Log::kWarning;
   if (capabilities->logging_prefs.count(WebDriverLog::kDriverType))
     driver_level = capabilities->logging_prefs[WebDriverLog::kDriverType];
@@ -776,7 +776,7 @@
   if (status.IsError())
     return status;
   bool is_last_web_view = web_view_ids.size() == 1u;
-  if (session->webSocketUrl) {
+  if (session->web_socket_url) {
     is_last_web_view = web_view_ids.size() <= 2u;
   }
   web_view_ids.clear();
@@ -850,7 +850,7 @@
   if (status.IsError())
     return status;
 
-  if (session->webSocketUrl) {
+  if (session->web_socket_url) {
     auto it =
         base::ranges::find(web_view_ids, session->bidi_mapper_web_view_id);
     if (it != web_view_ids.end()) {
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 6ab046d..7dea4d3 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -1169,6 +1169,24 @@
     with self.assertRaises(chromedriver.DetachedShadowRoot):
       self._driver.ExecuteScript("return true;", shadow)
 
+  def testExecuteAsyncScriptWithResolve(self):
+    self.assertEqual(
+        10,
+        self._driver.ExecuteAsyncScript(
+            'arguments[0](10)'))
+    self.assertEqual(
+        'one',
+        self._driver.ExecuteAsyncScript(
+            'arguments[0]("one")'))
+    self.assertEqual(
+        0.123,
+        self._driver.ExecuteAsyncScript(
+            'arguments[0](0.123)'))
+    self.assertEqual(
+        [1, 2.2, 'three'],
+        self._driver.ExecuteAsyncScript(
+            'arguments[0]([1, 2.2, "three"])'))
+
   def testExecuteAsyncScript(self):
     self._driver.SetTimeouts({'script': 3000})
     self.assertRaises(
@@ -2452,8 +2470,32 @@
     self._driver.ExecuteScript('window.onbeforeunload=function(){return true}')
     self._driver.FindElement('tag name', 'body').Click()
     self._driver.GoBack()
-    self.assertTrue(self._driver.IsAlertOpen())
-    self._driver.HandleAlert(True)
+    self.assertFalse(self._driver.IsAlertOpen())
+
+  def testAlertHandlingOnNavigation(self):
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/page_test.html'))
+    self._driver.ExecuteScript('window.onbeforeunload=function(){return true}')
+    self._driver.FindElement('tag name', 'body').Click()
+    self._driver.ExecuteAsyncScript('''
+        const [url, resolve] = arguments;
+        window.location.href = url;
+        resolve(url);
+    ''', self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    self.assertFalse(self._driver.IsAlertOpen())
+
+  def testAlertHandlingOnNavigationNoResolve(self):
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/page_test.html'))
+    self._driver.ExecuteScript('window.onbeforeunload=function(){return true}')
+    self._driver.FindElement('tag name', 'body').Click()
+    self._driver.SetTimeouts({'script': 100})
+
+    # The following script never calls resolve. Therefore it times out.
+    with self.assertRaises(chromedriver.ScriptTimeout):
+      self._driver.ExecuteAsyncScript('''
+          const [url, resolve] = arguments;
+          window.location.href = url;
+      ''', self.GetHttpUrlForFile('/chromedriver/empty.html'))
+
     self.assertFalse(self._driver.IsAlertOpen())
 
   def testRefresh(self):
@@ -2485,10 +2527,7 @@
     self._driver.ExecuteScript('window.onbeforeunload=function(){return true}')
     self._driver.FindElement('tag name', 'body').Click()
     self._driver.Refresh()
-    self.assertTrue(self._driver.IsAlertOpen())
-    self.assertRaises(chromedriver.UnsupportedOperation,
-                 self._driver.HandleAlert,
-                 True, 'textToOnBeforeUnload')
+    self.assertFalse(self._driver.IsAlertOpen())
 
   def testAlertOnNewWindow(self):
     self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
@@ -5641,6 +5680,16 @@
     self._CheckPageLoadTimeout(self._driver.GoForward)
     self.assertEqual(self._initial_url, self._driver.GetCurrentUrl())
 
+  def testHistoryNavigationAndLoadWithPageLoadTimeout(self):
+    # Allow the page to load for the first time.
+    self._handler.send_response_event.set()
+    self._LoadHangingUrl()
+    self.assertTrue(self._handler.request_received_event.wait(1))
+
+    self._driver.GoBack()
+    self._CheckPageLoadTimeout(self._LoadHangingUrl)
+    self.assertEqual(self._initial_url, self._driver.GetCurrentUrl())
+
   def testRefreshWithPageLoadTimeout(self):
     # Allow the page to load for the first time.
     self._handler.send_response_event.set()
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index ea5802062..3160862 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -816,9 +816,15 @@
   Status status =
       web_view->CallUserSyncScript(session->GetCurrentFrameId(), script, *args,
                                    session->script_timeout, value);
-  if (status.code() == kTimeout)
-    return Status(kScriptTimeout);
-  return status;
+  switch (status.code()) {
+    case kTimeout:
+    // Navigation has happened during script execution. Further wait would lead
+    // to timeout.
+    case kNavigationDetectedByRemoteEnd:
+      return Status(kScriptTimeout);
+    default:
+      return status;
+  }
 }
 
 Status ExecuteExecuteAsyncScript(Session* session,
@@ -842,9 +848,15 @@
   Status status = web_view->CallUserAsyncFunction(
       session->GetCurrentFrameId(), "async function(){" + script + "}", *args,
       session->script_timeout, value);
-  if (status.code() == kTimeout)
-    return Status(kScriptTimeout);
-  return status;
+  switch (status.code()) {
+    case kTimeout:
+    // Navigation has happened during script execution. Further wait would lead
+    // to timeout.
+    case kNavigationDetectedByRemoteEnd:
+      return Status(kScriptTimeout);
+    default:
+      return status;
+  }
 }
 
 Status ExecuteNewWindow(Session* session,