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..103bd88 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 a0735e6b..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 1c4ff5da..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..d6f13e1 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,