| // Copyright 2019 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <string> | |
| #include <string_view> | |
| #include "base/base64.h" | |
| #include "base/check_deref.h" | |
| #include "base/files/scoped_temp_dir.h" | |
| #include "base/functional/bind.h" | |
| #include "base/memory/memory_pressure_listener_registry.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/path_service.h" | |
| #include "base/strings/string_split.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/strings/to_string.h" | |
| #include "base/strings/utf_string_conversions.h" | |
| #include "base/test/bind.h" | |
| #include "base/test/scoped_feature_list.h" | |
| #include "base/test/test_future.h" | |
| #include "base/test/test_switches.h" | |
| #include "base/test/values_test_util.h" | |
| #include "base/threading/thread_restrictions.h" | |
| #include "base/values.h" | |
| #include "build/build_config.h" | |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" | |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" | |
| #include "chrome/browser/apps/app_service/browser_app_launcher.h" | |
| #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h" | |
| #include "chrome/browser/data_saver/data_saver.h" | |
| #include "chrome/browser/devtools/devtools_window.h" | |
| #include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h" | |
| #include "chrome/browser/preloading/preloading_prefs.h" | |
| #include "chrome/browser/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_mixin.h" | |
| #include "chrome/browser/private_verification_tokens/private_verification_tokens_service.h" | |
| #include "chrome/browser/private_verification_tokens/private_verification_tokens_service_factory.h" | |
| #include "chrome/browser/profiles/profile.h" | |
| #include "chrome/browser/ssl/https_upgrades_util.h" | |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" | |
| #include "chrome/common/chrome_paths.h" | |
| #include "chrome/common/chrome_switches.h" | |
| #include "chrome/common/pref_names.h" | |
| #include "chrome/common/webui_url_constants.h" | |
| #include "chrome/test/base/chrome_test_utils.h" | |
| #include "components/content_settings/core/browser/cookie_settings.h" | |
| #include "components/content_settings/core/common/pref_names.h" | |
| #include "components/custom_handlers/protocol_handler_registry.h" | |
| #include "components/infobars/content/content_infobar_manager.h" | |
| #include "components/infobars/core/infobar.h" | |
| #include "components/infobars/core/infobar_delegate.h" | |
| #include "components/optimization_guide/proto/features/common_quality_data.pb.h" | |
| #include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h" | |
| #include "components/private_verification_tokens/common/private_verification_tokens_token.h" | |
| #include "components/services/app_service/public/cpp/app_launch_params.h" | |
| #include "content/public/browser/btm_redirect.h" | |
| #include "content/public/browser/btm_service.h" | |
| #include "content/public/browser/cookie_access_details.h" | |
| #include "content/public/browser/devtools_agent_host.h" | |
| #include "content/public/browser/navigation_controller.h" | |
| #include "content/public/browser/navigation_entry.h" | |
| #include "content/public/browser/page_navigator.h" | |
| #include "content/public/browser/ssl_status.h" | |
| #include "content/public/browser/web_contents.h" | |
| #include "content/public/common/content_features.h" | |
| #include "content/public/common/referrer.h" | |
| #include "content/public/test/browser_test.h" | |
| #include "content/public/test/browser_test_utils.h" | |
| #include "content/public/test/btm_service_test_utils.h" | |
| #include "content/public/test/download_test_observer.h" | |
| #include "content/public/test/preloading_test_util.h" | |
| #include "content/public/test/prerender_test_util.h" | |
| #include "extensions/buildflags/buildflags.h" | |
| #include "net/base/features.h" | |
| #include "net/base/ip_address.h" | |
| #include "net/dns/mock_host_resolver.h" | |
| #include "net/ssl/ssl_cipher_suite_names.h" | |
| #include "net/ssl/ssl_config.h" | |
| #include "net/ssl/ssl_connection_status_flags.h" | |
| #include "net/ssl/ssl_server_config.h" | |
| #include "printing/buildflags/buildflags.h" | |
| #include "testing/gmock/include/gmock/gmock.h" | |
| #include "third_party/blink/public/common/features.h" | |
| #include "third_party/boringssl/src/include/openssl/ssl.h" | |
| #include "ui/base/page_transition_types.h" | |
| #include "ui/base/window_open_disposition.h" | |
| #include "ui/gfx/codec/png_codec.h" | |
| #include "url/origin.h" | |
| #if BUILDFLAG(IS_ANDROID) | |
| #include "base/android/android_info.h" | |
| #include "base/test/run_until.h" | |
| #include "chrome/browser/android/tab_android.h" | |
| #include "chrome/browser/devtools/protocol/browser_handler_android.h" | |
| #include "chrome/browser/ui/android/tab_model/tab_model.h" | |
| #include "chrome/browser/ui/android/tab_model/tab_model_list.h" | |
| #include "chrome/browser/ui/android/tab_model/tab_model_test_helper.h" | |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" | |
| #endif | |
| #if !BUILDFLAG(IS_ANDROID) | |
| #include "base/task/sequenced_task_runner.h" | |
| #include "chrome/browser/prefs/session_startup_pref.h" | |
| #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h" | |
| #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h" | |
| #include "chrome/browser/profiles/profile_window.h" | |
| #include "chrome/browser/resource_coordinator/tab_manager.h" | |
| #include "chrome/browser/sessions/session_restore_test_helper.h" | |
| #include "chrome/browser/sessions/session_service_test_helper.h" | |
| #include "chrome/browser/ui/browser_commands.h" | |
| #include "chrome/browser/ui/browser_window/public/global_browser_collection.h" | |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
| #include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h" | |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h" | |
| #include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h" | |
| #include "chrome/test/base/mixin_based_in_process_browser_test.h" | |
| #include "chrome/test/base/ui_test_utils.h" | |
| #include "components/guest_view/browser/guest_view_base.h" | |
| #include "components/guest_view/browser/guest_view_manager_delegate.h" | |
| #include "components/guest_view/browser/test_guest_view_manager.h" | |
| #include "components/keep_alive_registry/keep_alive_types.h" | |
| #include "components/keep_alive_registry/scoped_keep_alive.h" | |
| #include "components/permissions/permission_request_manager.h" | |
| #include "components/tab_groups/tab_group_id.h" | |
| #include "content/public/browser/devtools_agent_host_client.h" | |
| #include "content/public/test/browser_test_utils.h" | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| #if BUILDFLAG(ENABLE_EXTENSIONS_CORE) | |
| #include "chrome/browser/extensions/extension_util.h" | |
| #include "chrome/browser/extensions/scoped_test_mv2_enabler.h" | |
| #include "extensions/browser/api/extensions_api_client.h" | |
| #include "extensions/browser/app_window/app_window.h" | |
| #include "extensions/browser/app_window/app_window_registry.h" | |
| #include "extensions/browser/extension_host.h" | |
| #include "extensions/browser/extension_registrar.h" | |
| #include "extensions/browser/extension_system.h" | |
| #include "extensions/browser/process_manager.h" | |
| #include "extensions/browser/test_extension_registry_observer.h" | |
| #include "extensions/browser/unpacked_installer.h" | |
| #include "extensions/common/manifest_handlers/background_info.h" | |
| #include "extensions/test/extension_test_message_listener.h" | |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS_CORE) | |
| #if BUILDFLAG(IS_WIN) | |
| #include "base/base_paths_win.h" | |
| #include "base/test/scoped_path_override.h" | |
| #endif | |
| using DevToolsProtocolTest = DevToolsProtocolTestBase; | |
| using testing::AllOf; | |
| using testing::Contains; | |
| using testing::Eq; | |
| using testing::Not; | |
| namespace { | |
| #if !BUILDFLAG(IS_ANDROID) | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| VisibleSecurityStateChangedNeutralState) { | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), GURL("about:blank"))); | |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents())); | |
| Attach(); | |
| SendCommandAsync("Security.enable"); | |
| base::DictValue params = | |
| WaitForNotification("Security.visibleSecurityStateChanged", true); | |
| std::string* security_state = | |
| params.FindStringByDottedPath("visibleSecurityState.securityState"); | |
| ASSERT_TRUE(security_state); | |
| EXPECT_EQ(std::string("neutral"), *security_state); | |
| EXPECT_FALSE(params.FindStringByDottedPath( | |
| "visibleSecurityState.certificateSecurityState")); | |
| EXPECT_FALSE( | |
| params.FindStringByDottedPath("visibleSecurityState.safetyTipInfo")); | |
| const base::Value* security_state_issue_ids = | |
| params.FindByDottedPath("visibleSecurityState.securityStateIssueIds"); | |
| EXPECT_TRUE(security_state_issue_ids->GetList().contains( | |
| "scheme-is-not-cryptographic")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CreateDeleteContext) { | |
| AttachToBrowserTarget(); | |
| for (int i = 0; i < 2; i++) { | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createBrowserContext"); | |
| std::string context_id = *result->FindString("browserContextId"); | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("browserContextId", context_id); | |
| SendCommandSync("Target.createTarget", std::move(params)); | |
| params = base::DictValue(); | |
| params.Set("browserContextId", context_id); | |
| SendCommandSync("Target.disposeBrowserContext", std::move(params)); | |
| } | |
| } | |
| IN_PROC_BROWSER_TEST_F( | |
| DevToolsProtocolTest, | |
| DownloadBehaviorOverridesRemainIndependentAcrossBrowserContexts) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL download_url = | |
| embedded_test_server()->GetURL("/download-test1.lib"); | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createBrowserContext"); | |
| ASSERT_TRUE(result); | |
| const std::string* browser_context_id = | |
| result->FindString("browserContextId"); | |
| ASSERT_TRUE(browser_context_id); | |
| const std::string context_id = *browser_context_id; | |
| content::TestDevToolsProtocolClient older_client; | |
| older_client.AttachToBrowserTarget(); | |
| base::DictValue params; | |
| params.Set("behavior", "deny"); | |
| ASSERT_TRUE(older_client.SendCommandSync("Browser.setDownloadBehavior", | |
| params.Clone())); | |
| // Replacing an override for the same context must not let the old handle | |
| // reset the replacement. | |
| ASSERT_TRUE(older_client.SendCommandSync("Browser.setDownloadBehavior", | |
| params.Clone())); | |
| params.Set("browserContextId", context_id); | |
| ASSERT_TRUE(older_client.SendCommandSync("Browser.setDownloadBehavior", | |
| std::move(params))); | |
| { | |
| content::DownloadTestObserverTerminal observer( | |
| browser()->GetProfile()->GetDownloadManager(), 1, | |
| content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); | |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), download_url)); | |
| observer.WaitForFinished(); | |
| EXPECT_EQ(1u, observer.NumDownloadsSeenInState( | |
| download::DownloadItem::CANCELLED)); | |
| } | |
| content::TestDevToolsProtocolClient newer_client; | |
| newer_client.AttachToBrowserTarget(); | |
| params = base::DictValue(); | |
| params.Set("behavior", "deny"); | |
| ASSERT_TRUE(newer_client.SendCommandSync("Browser.setDownloadBehavior", | |
| std::move(params))); | |
| params = base::DictValue(); | |
| params.Set("browserContextId", context_id); | |
| ASSERT_TRUE( | |
| SendCommandSync("Target.disposeBrowserContext", std::move(params))); | |
| older_client.DetachProtocolClient(); | |
| { | |
| content::DownloadTestObserverTerminal observer( | |
| browser()->GetProfile()->GetDownloadManager(), 1, | |
| content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); | |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), download_url)); | |
| observer.WaitForFinished(); | |
| EXPECT_EQ(1u, observer.NumDownloadsSeenInState( | |
| download::DownloadItem::CANCELLED)); | |
| } | |
| newer_client.DetachProtocolClient(); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| NewTabPageInCreatedContextDoesNotCrash) { | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createBrowserContext"); | |
| std::string context_id = *result->FindString("browserContextId"); | |
| base::DictValue params; | |
| params.Set("url", chrome::kChromeUINewTabURL); | |
| params.Set("browserContextId", context_id); | |
| const base::DictValue* create_target_result = | |
| SendCommandSync("Target.createTarget", std::move(params)); | |
| std::string target_id = *create_target_result->FindString("targetId"); | |
| scoped_refptr<content::DevToolsAgentHost> agent_host = | |
| content::DevToolsAgentHost::GetForId(target_id); | |
| ASSERT_TRUE(agent_host); | |
| content::WebContents* wc = agent_host->GetWebContents(); | |
| ASSERT_TRUE(wc); | |
| ASSERT_TRUE(content::WaitForLoadStop(wc)); | |
| EXPECT_EQ(chrome::ChromeUINewTabURLAsGURL(), wc->GetLastCommittedURL()); | |
| // Should not crash by this point. | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| CreateBrowserContextAcceptsProxyServer) { | |
| ScopedAllowHttpForHostnamesForTesting allow_http( | |
| {"this-page-does-not-exist.com"}, | |
| chrome_test_utils::GetProfile(this)->GetPrefs()); | |
| AttachToBrowserTarget(); | |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( | |
| [&](const net::test_server::HttpRequest& request) | |
| -> std::unique_ptr<net::test_server::HttpResponse> { | |
| std::unique_ptr<net::test_server::BasicHttpResponse> http_response( | |
| std::make_unique<net::test_server::BasicHttpResponse>()); | |
| http_response->set_code(net::HTTP_OK); | |
| http_response->set_content_type("text/html"); | |
| http_response->set_content("<title>Hello from proxy server!</title>"); | |
| return std::move(http_response); | |
| })); | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| base::DictValue params; | |
| params.Set("proxyServer", | |
| embedded_test_server()->host_port_pair().ToString()); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createBrowserContext", std::move(params)); | |
| std::string context_id = *result->FindString("browserContextId"); | |
| params = base::DictValue(); | |
| params.Set("url", "http://this-page-does-not-exist.com/site.html"); | |
| params.Set("browserContextId", context_id); | |
| result = SendCommandSync("Target.createTarget", std::move(params)); | |
| std::string target_id = *result->FindString("targetId"); | |
| scoped_refptr<content::DevToolsAgentHost> agent_host = | |
| content::DevToolsAgentHost::GetForId(target_id); | |
| ASSERT_TRUE(agent_host); | |
| content::WebContents* wc = agent_host->GetWebContents(); | |
| ASSERT_TRUE(wc); | |
| ASSERT_TRUE(content::WaitForLoadStop(wc)); | |
| EXPECT_EQ(GURL("http://this-page-does-not-exist.com/site.html"), | |
| wc->GetURL()); | |
| EXPECT_EQ("Hello from proxy server!", base::UTF16ToUTF8(wc->GetTitle())); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CreateInDefaultContextById) { | |
| AttachToBrowserTarget(); | |
| base::DictValue params; | |
| params.Set("filter", | |
| base::ListValue().Append( | |
| base::DictValue().Set("type", "page").Set("exclude", false))); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.getTargets", std::move(params)); | |
| const base::ListValue* list = result->FindList("targetInfos"); | |
| ASSERT_TRUE(list->size() == 1); | |
| const std::string context_id = | |
| *list->front().GetDict().FindString("browserContextId"); | |
| params = base::DictValue(); | |
| params.Set("url", "about:blank"); | |
| params.Set("browserContextId", context_id); | |
| result = SendCommandSync("Target.createTarget", std::move(params)); | |
| ASSERT_TRUE(result); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| InputDispatchEventsToCorrectTarget) { | |
| Attach(); | |
| std::string setup_logging = R"( | |
| window.logs = []; | |
| ['dragenter', 'keydown', 'mousedown', 'mouseenter', 'mouseleave', | |
| 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'click', 'touchcancel', | |
| 'touchend', 'touchmove', 'touchstart', | |
| ].forEach((event) => | |
| window.addEventListener(event, (e) => logs.push(e.type)));)"; | |
| content::WebContents* target_web_contents = | |
| chrome_test_utils::GetActiveWebContents(this); | |
| ui_test_utils::NavigateToURLWithDisposition( | |
| browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB, | |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); | |
| content::WebContents* other_web_contents = | |
| chrome_test_utils::GetActiveWebContents(this); | |
| EXPECT_TRUE(content::EvalJs(target_web_contents, setup_logging).is_ok()); | |
| EXPECT_TRUE(content::EvalJs(other_web_contents, setup_logging).is_ok()); | |
| base::DictValue params; | |
| params.Set("button", "left"); | |
| params.Set("clickCount", 1); | |
| params.Set("x", 100); | |
| params.Set("y", 250); | |
| params.Set("clickCount", 1); | |
| params.Set("type", "mousePressed"); | |
| SendCommandSync("Input.dispatchMouseEvent", params.Clone()); | |
| params.Set("type", "mouseMoved"); | |
| params.Set("y", 270); | |
| SendCommandSync("Input.dispatchMouseEvent", params.Clone()); | |
| params.Set("type", "mouseReleased"); | |
| SendCommandSync("Input.dispatchMouseEvent", std::move(params)); | |
| params = base::DictValue(); | |
| params.Set("x", 100); | |
| params.Set("y", 250); | |
| params.Set("type", "dragEnter"); | |
| params.SetByDottedPath("data.dragOperationsMask", 1); | |
| params.SetByDottedPath("data.items", base::ListValue()); | |
| SendCommandSync("Input.dispatchDragEvent", std::move(params)); | |
| params = base::DictValue(); | |
| params.Set("x", 100); | |
| params.Set("y", 250); | |
| SendCommandSync("Input.synthesizeTapGesture", std::move(params)); | |
| params = base::DictValue(); | |
| params.Set("type", "keyDown"); | |
| params.Set("key", "a"); | |
| SendCommandSync("Input.dispatchKeyEvent", std::move(params)); | |
| content::EvalJsResult main_target_events = | |
| content::EvalJs(target_web_contents, "logs.join(' ')"); | |
| content::EvalJsResult other_target_events = | |
| content::EvalJs(other_web_contents, "logs.join(' ')"); | |
| // mouse events might happen in the other_target if the real mouse pointer | |
| // happens to be over the browser window | |
| EXPECT_THAT( | |
| base::SplitString(main_target_events.ExtractString(), " ", | |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL), | |
| AllOf(Contains("mouseover"), Contains("mousedown"), Contains("mousemove"), | |
| Contains("mouseup"), Contains("click"), Contains("dragenter"), | |
| Contains("keydown"))); | |
| EXPECT_THAT(base::SplitString(other_target_events.ExtractString(), " ", | |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL), | |
| AllOf(Not(Contains("click")), Not(Contains("dragenter")), | |
| Not(Contains("keydown")))); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CreateTargetWithFocus) { | |
| AttachToBrowserTarget(); | |
| content::WebContents* const initial_tab = | |
| chrome_test_utils::GetActiveWebContents(this); | |
| ASSERT_TRUE(initial_tab); | |
| EXPECT_EQ(1, browser()->tab_strip_model()->count()); | |
| // By default, creating a new target also focuses it. | |
| SendCommandSync("Target.createTarget", | |
| base::DictValue().Set("url", "about:blank#1")); | |
| ASSERT_FALSE(error()); | |
| content::WebContents* const focused_tab = | |
| chrome_test_utils::GetActiveWebContents(this); | |
| EXPECT_NE(initial_tab, focused_tab); | |
| EXPECT_EQ(2, browser()->tab_strip_model()->count()); | |
| EXPECT_EQ(GURL("about:blank#1"), focused_tab->GetVisibleURL()); | |
| // When focus=false, the new target is not focused. | |
| SendCommandSync( | |
| "Target.createTarget", | |
| base::DictValue().Set("url", "about:blank#2").Set("focus", false)); | |
| ASSERT_FALSE(error()); | |
| EXPECT_EQ(focused_tab, chrome_test_utils::GetActiveWebContents(this)); | |
| EXPECT_EQ(3, browser()->tab_strip_model()->count()); | |
| EXPECT_EQ(GURL("about:blank#2"), | |
| browser()->tab_strip_model()->GetWebContentsAt(2)->GetVisibleURL()); | |
| // When background=true, the new target is not focused by default. | |
| SendCommandSync( | |
| "Target.createTarget", | |
| base::DictValue().Set("url", "about:blank#3").Set("background", true)); | |
| ASSERT_FALSE(error()); | |
| EXPECT_EQ(focused_tab, chrome_test_utils::GetActiveWebContents(this)); | |
| EXPECT_EQ(4, browser()->tab_strip_model()->count()); | |
| EXPECT_EQ(GURL("about:blank#3"), | |
| browser()->tab_strip_model()->GetWebContentsAt(3)->GetVisibleURL()); | |
| // It's an error to use focus=true and background=true. | |
| SendCommandSync("Target.createTarget", base::DictValue() | |
| .Set("url", "about:blank#4") | |
| .Set("focus", true) | |
| .Set("background", true)); | |
| ASSERT_TRUE(error()); | |
| EXPECT_EQ( | |
| "Can't focus a target in the background. Use background=false instead.", | |
| *error()->FindString("message")); | |
| EXPECT_EQ(4, browser()->tab_strip_model()->count()); | |
| } | |
| #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) | |
| // On ChromeOS and MacOS, the tabs are not backgrounded and unloaded in the same way as | |
| // on other platforms. | |
| #define MAYBE_AutoAttachToUnloadedTab DISABLED_AutoAttachToUnloadedTab | |
| #else | |
| #define MAYBE_AutoAttachToUnloadedTab AutoAttachToUnloadedTab | |
| #endif | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, MAYBE_AutoAttachToUnloadedTab) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| GURL url1 = embedded_test_server()->GetURL("/title1.html"); | |
| GURL url2 = embedded_test_server()->GetURL("/title2.html"); | |
| // 1. Create two tabs. | |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1)); | |
| ui_test_utils::NavigateToURLWithDisposition( | |
| browser(), url2, WindowOpenDisposition::NEW_FOREGROUND_TAB, | |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); | |
| ASSERT_EQ(2, browser()->tab_strip_model()->count()); | |
| ASSERT_EQ(1, browser()->tab_strip_model()->active_index()); | |
| content::WebContents* background_tab = | |
| browser()->tab_strip_model()->GetWebContentsAt(0); | |
| content::WebContents* active_tab = | |
| browser()->tab_strip_model()->GetWebContentsAt(1); | |
| // 2. Restore session without loading background tabs. | |
| Profile* profile = browser()->GetProfile(); | |
| SessionStartupPref::SetStartupPref( | |
| profile, SessionStartupPref(SessionStartupPref::LAST)); | |
| SessionServiceTestHelper(profile).SetForceBrowserNotAliveWithNoWindows(true); | |
| auto keep_alive = std::make_unique<ScopedKeepAlive>( | |
| KeepAliveOrigin::SESSION_RESTORE, KeepAliveRestartOption::DISABLED); | |
| auto profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>( | |
| profile, ProfileKeepAliveOrigin::kBrowserWindow); | |
| CloseBrowserSynchronously(browser()); | |
| ui_test_utils::AllBrowserTabAddedWaiter tab_waiter; | |
| SessionRestoreTestHelper restore_observer; | |
| chrome::NewEmptyWindow(profile); | |
| BrowserWindowInterface* new_browser = | |
| GlobalBrowserCollection::GetInstance()->FindBrowserWithTab( | |
| tab_waiter.Wait()); | |
| base::MemoryPressureListenerRegistry::SimulatePressureNotification( | |
| base::MEMORY_PRESSURE_LEVEL_CRITICAL); | |
| keep_alive.reset(); | |
| profile_keep_alive.reset(); | |
| // The new browser should have 2 tabs. | |
| ASSERT_EQ(2, new_browser->GetTabStripModel()->count()); | |
| ASSERT_EQ(1, new_browser->GetTabStripModel()->active_index()); | |
| background_tab = new_browser->GetTabStripModel()->GetWebContentsAt(0); | |
| active_tab = new_browser->GetTabStripModel()->GetWebContentsAt(1); | |
| // Background tab should not have a renderer. | |
| EXPECT_FALSE(background_tab->GetPrimaryMainFrame()->IsRenderFrameLive()); | |
| ASSERT_TRUE(content::WaitForLoadStop(active_tab)); | |
| EXPECT_TRUE(active_tab->GetPrimaryMainFrame()->IsRenderFrameLive()); | |
| // Attach to the browser target. | |
| AttachToBrowserTarget(); | |
| // Send Target.setAutoAttach with autoAttach=true, flatten=true. | |
| base::DictValue set_auto_attach_params; | |
| set_auto_attach_params.Set("autoAttach", true); | |
| set_auto_attach_params.Set("waitForDebuggerOnStart", false); | |
| set_auto_attach_params.Set("flatten", true); | |
| base::ListValue filter; | |
| // Include tab target. | |
| base::DictValue page_filter; | |
| page_filter.Set("type", "page"); | |
| page_filter.Set("exclude", true); | |
| filter.Append(std::move(page_filter)); | |
| filter.Append(base::DictValue()); | |
| set_auto_attach_params.Set("filter", std::move(filter)); | |
| SendCommandSync("Target.setAutoAttach", std::move(set_auto_attach_params)); | |
| auto is_tab_target = [](const base::DictValue& params) { | |
| const std::string* type = params.FindStringByDottedPath("targetInfo.type"); | |
| return type && *type == "tab"; | |
| }; | |
| // Verify that two attachedToTarget tab target events are received. | |
| base::DictValue attached_tab1_notif = WaitForMatchingNotification( | |
| "Target.attachedToTarget", base::BindRepeating(is_tab_target)); | |
| const std::string& session_id1 = | |
| *attached_tab1_notif.FindStringByDottedPath("sessionId"); | |
| ASSERT_EQ("tab", | |
| *attached_tab1_notif.FindStringByDottedPath("targetInfo.type")); | |
| base::DictValue set_auto_attach_params_for_tab; | |
| set_auto_attach_params_for_tab.Set("autoAttach", true); | |
| set_auto_attach_params_for_tab.Set("waitForDebuggerOnStart", false); | |
| set_auto_attach_params_for_tab.Set("flatten", true); | |
| SendSessionCommand("Target.setAutoAttach", | |
| set_auto_attach_params_for_tab.Clone(), session_id1, | |
| false); | |
| base::DictValue attached_tab2_notif = WaitForMatchingNotification( | |
| "Target.attachedToTarget", base::BindRepeating(is_tab_target)); | |
| const std::string& session_id2 = | |
| *attached_tab2_notif.FindStringByDottedPath("sessionId"); | |
| ASSERT_EQ("tab", | |
| *attached_tab2_notif.FindStringByDottedPath("targetInfo.type")); | |
| SendSessionCommand("Target.setAutoAttach", | |
| std::move(set_auto_attach_params_for_tab), session_id2, | |
| false); | |
| auto is_page_target = [](const base::DictValue& params) { | |
| const std::string* type = params.FindStringByDottedPath("targetInfo.type"); | |
| return type && *type == "page"; | |
| }; | |
| // Verify two attachedToTarget events for page targets are received. | |
| base::DictValue attached_page1_notif = WaitForMatchingNotification( | |
| "Target.attachedToTarget", base::BindRepeating(is_page_target)); | |
| base::DictValue attached_page2_notif = WaitForMatchingNotification( | |
| "Target.attachedToTarget", base::BindRepeating(is_page_target)); | |
| // The notifications can arrive in any order. The active tab is eagerly | |
| // loaded, so it should have a URL. The background tab may not. | |
| if (const std::string* url = | |
| attached_page1_notif.FindStringByDottedPath("targetInfo.url"); | |
| url && *url == url2.spec()) { | |
| std::swap(attached_page1_notif, attached_page2_notif); | |
| } | |
| // The background tab's URL may not be available yet. | |
| ASSERT_THAT(attached_page2_notif.FindStringByDottedPath("targetInfo.url"), | |
| testing::Pointee(url2.spec())); | |
| const std::string& page_session_id1 = | |
| *attached_page1_notif.FindStringByDottedPath("sessionId"); | |
| ASSERT_EQ("page", | |
| *attached_page1_notif.FindStringByDottedPath("targetInfo.type")); | |
| const std::string& page_session_id2 = | |
| *attached_page2_notif.FindStringByDottedPath("sessionId"); | |
| ASSERT_EQ("page", | |
| *attached_page2_notif.FindStringByDottedPath("targetInfo.type")); | |
| // Send Runtime.evaluate to both page targets and verify script result is | |
| // received for both. | |
| EXPECT_TRUE(background_tab->GetPrimaryMainFrame()->IsRenderFrameLive()); | |
| EXPECT_TRUE(active_tab->GetPrimaryMainFrame()->IsRenderFrameLive()); | |
| base::DictValue evaluate_params; | |
| evaluate_params.Set("expression", "1 + 2"); | |
| const base::DictValue* evaluate_result1 = SendSessionCommand( | |
| "Runtime.evaluate", evaluate_params.Clone(), page_session_id1, true); | |
| ASSERT_EQ(3, evaluate_result1->FindIntByDottedPath("result.value")); | |
| const base::DictValue* evaluate_result2 = SendSessionCommand( | |
| "Runtime.evaluate", std::move(evaluate_params), page_session_id2, true); | |
| ASSERT_EQ(3, evaluate_result2->FindIntByDottedPath("result.value")); | |
| } | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| #if BUILDFLAG(IS_ANDROID) | |
| IN_PROC_BROWSER_TEST_F( | |
| DevToolsProtocolTest, | |
| CreateWindowTargetRemainsAvailableWithoutMultiInstanceSupport) { | |
| if (base::android::android_info::sdk_int() >= | |
| base::android::android_info::SDK_VERSION_S) { | |
| GTEST_SKIP() << "This test covers the pre-Android S fallback"; | |
| } | |
| AttachToBrowserTarget(); | |
| const base::DictValue* create_result = SendCommandSync( | |
| "Target.createTarget", | |
| base::DictValue().Set("url", "about:blank").Set("newWindow", true)); | |
| ASSERT_FALSE(error()); | |
| ASSERT_TRUE(create_result); | |
| EXPECT_TRUE(create_result->FindString("targetId")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| AutoAttachedWindowTargetCanQueryPendingBrowserWindow) { | |
| if (base::android::android_info::sdk_int() < | |
| base::android::android_info::SDK_VERSION_S) { | |
| GTEST_SKIP() << "Pending browser windows require Android S+"; | |
| } | |
| AttachToBrowserTarget(); | |
| // A normal Android browser window requires an existing Activity to launch | |
| // from. Wait for the test's initial window to finish registering before | |
| // exercising creation of a second, pending window. | |
| ASSERT_TRUE(base::test::RunUntil( | |
| [] { return !GetAllBrowserWindowInterfaces().empty(); })); | |
| SendCommandSync("Target.setAutoAttach", | |
| base::DictValue() | |
| .Set("autoAttach", true) | |
| .Set("waitForDebuggerOnStart", false) | |
| .Set("flatten", true)); | |
| ASSERT_TRUE(result()); | |
| ClearNotifications(); | |
| SendCommandAsync( | |
| "Target.createTarget", | |
| base::DictValue().Set("url", "about:blank").Set("newWindow", true)); | |
| // The target is exposed during command dispatch, before Android can finish | |
| // creating and globally registering its Activity-backed browser window. | |
| auto is_page_target = [](const base::DictValue& params) { | |
| const std::string* type = params.FindStringByDottedPath("targetInfo.type"); | |
| return type && *type == "page"; | |
| }; | |
| ASSERT_TRUE(HasExistingNotificationMatching( | |
| [&is_page_target](const base::DictValue& notification) { | |
| const std::string* method = notification.FindString("method"); | |
| const base::DictValue* params = notification.FindDict("params"); | |
| return method && *method == "Target.attachedToTarget" && params && | |
| is_page_target(*params); | |
| })); | |
| const base::DictValue attached = WaitForMatchingNotification( | |
| "Target.attachedToTarget", base::BindRepeating(is_page_target)); | |
| const std::string* target_id = | |
| attached.FindStringByDottedPath("targetInfo.targetId"); | |
| ASSERT_TRUE(target_id); | |
| const base::DictValue* window_result = | |
| SendCommandSync("Browser.getWindowForTarget", | |
| base::DictValue().Set("targetId", *target_id)); | |
| ASSERT_TRUE(window_result); | |
| const std::optional<int> window_id = window_result->FindInt("windowId"); | |
| ASSERT_TRUE(window_id.has_value()); | |
| const base::DictValue* bounds = window_result->FindDict("bounds"); | |
| ASSERT_TRUE(bounds); | |
| EXPECT_TRUE(bounds->FindInt("left").has_value()); | |
| EXPECT_TRUE(bounds->FindInt("top").has_value()); | |
| EXPECT_TRUE(bounds->FindInt("width").has_value()); | |
| EXPECT_TRUE(bounds->FindInt("height").has_value()); | |
| EXPECT_TRUE(bounds->FindString("windowState")); | |
| EXPECT_EQ(nullptr, | |
| BrowserHandlerAndroid::FindBrowserWindowById(window_id.value())); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| CreateTargetUsesDefaultBrowserContext) { | |
| AttachToBrowserTarget(); | |
| content::WebContents* initial_web_contents = | |
| chrome_test_utils::GetActiveWebContents(this); | |
| ASSERT_TRUE(initial_web_contents); | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("newWindow", false); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createTarget", std::move(params)); | |
| ASSERT_TRUE(result); | |
| const std::string* target_id = result->FindString("targetId"); | |
| ASSERT_TRUE(target_id); | |
| const std::string created_target_id = *target_id; | |
| scoped_refptr<content::DevToolsAgentHost> agent_host = | |
| content::DevToolsAgentHost::GetForId(created_target_id); | |
| ASSERT_TRUE(agent_host); | |
| content::WebContents* web_contents = agent_host->GetWebContents(); | |
| ASSERT_TRUE(web_contents); | |
| EXPECT_EQ(initial_web_contents->GetBrowserContext(), | |
| web_contents->GetBrowserContext()); | |
| TabAndroid* tab = TabAndroid::FromWebContents(web_contents); | |
| ASSERT_TRUE(tab); | |
| EXPECT_EQ(web_contents->GetBrowserContext(), tab->profile()); | |
| params = base::DictValue(); | |
| params.Set("targetId", created_target_id); | |
| ASSERT_TRUE(SendCommandSync("Target.closeTarget", std::move(params))); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| CreateTargetUsesRequestedBrowserContext) { | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createBrowserContext"); | |
| ASSERT_TRUE(result); | |
| const std::string* browser_context_id = | |
| result->FindString("browserContextId"); | |
| ASSERT_TRUE(browser_context_id); | |
| const std::string context_id = *browser_context_id; | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("newWindow", true); | |
| params.Set("browserContextId", context_id); | |
| result = SendCommandSync("Target.createTarget", std::move(params)); | |
| ASSERT_TRUE(result); | |
| const std::string* target_id_value = result->FindString("targetId"); | |
| ASSERT_TRUE(target_id_value); | |
| const std::string target_id = *target_id_value; | |
| scoped_refptr<content::DevToolsAgentHost> agent_host = | |
| content::DevToolsAgentHost::GetForId(target_id); | |
| ASSERT_TRUE(agent_host); | |
| content::WebContents* web_contents = agent_host->GetWebContents(); | |
| ASSERT_TRUE(web_contents); | |
| EXPECT_EQ(context_id, web_contents->GetBrowserContext()->UniqueId()); | |
| TabAndroid* tab = TabAndroid::FromWebContents(web_contents); | |
| ASSERT_TRUE(tab); | |
| EXPECT_EQ(web_contents->GetBrowserContext(), tab->profile()); | |
| result = SendCommandSync("Target.getTargets"); | |
| ASSERT_TRUE(result); | |
| const base::ListValue* target_infos = result->FindList("targetInfos"); | |
| ASSERT_TRUE(target_infos); | |
| const base::Value* created_target_info = nullptr; | |
| for (const auto& target : *target_infos) { | |
| const std::string* listed_target_id = | |
| target.GetDict().FindString("targetId"); | |
| if (listed_target_id && *listed_target_id == target_id) { | |
| created_target_info = ⌖ | |
| break; | |
| } | |
| } | |
| ASSERT_TRUE(created_target_info); | |
| const std::string* listed_browser_context_id = | |
| created_target_info->GetDict().FindString("browserContextId"); | |
| ASSERT_TRUE(listed_browser_context_id); | |
| EXPECT_EQ(context_id, *listed_browser_context_id); | |
| params = base::DictValue(); | |
| params.Set("browserContextId", context_id); | |
| ASSERT_TRUE( | |
| SendCommandSync("Target.disposeBrowserContext", std::move(params))); | |
| EXPECT_FALSE(agent_host->GetWebContents()); | |
| } | |
| IN_PROC_BROWSER_TEST_F( | |
| DevToolsProtocolTest, | |
| DisposeBrowserContextClosesFrozenTabsAcrossTabModelRemoval) { | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createBrowserContext"); | |
| ASSERT_TRUE(result); | |
| const std::string* browser_context_id = | |
| result->FindString("browserContextId"); | |
| ASSERT_TRUE(browser_context_id); | |
| const std::string context_id = *browser_context_id; | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("newWindow", true); | |
| params.Set("browserContextId", context_id); | |
| result = SendCommandSync("Target.createTarget", std::move(params)); | |
| ASSERT_TRUE(result); | |
| const std::string* target_id_value = result->FindString("targetId"); | |
| ASSERT_TRUE(target_id_value); | |
| const std::string target_id = *target_id_value; | |
| scoped_refptr<content::DevToolsAgentHost> agent_host = | |
| content::DevToolsAgentHost::GetForId(target_id); | |
| ASSERT_TRUE(agent_host); | |
| content::WebContents* web_contents = agent_host->GetWebContents(); | |
| ASSERT_TRUE(web_contents); | |
| TabAndroid* context_tab = TabAndroid::FromWebContents(web_contents); | |
| ASSERT_TRUE(context_tab); | |
| Profile* context_profile = context_tab->profile(); | |
| ASSERT_TRUE(context_profile); | |
| TabModel* context_model = TabModelList::GetTabModelForTabAndroid(context_tab); | |
| ASSERT_TRUE(context_model); | |
| ASSERT_EQ(1, context_model->GetTabCount()); | |
| ASSERT_EQ(context_model, TabModelList::models().back()); | |
| const size_t model_count = TabModelList::models().size(); | |
| OwningTestTabModel later_model_a(context_profile); | |
| OwningTestTabModel later_model_b(context_profile); | |
| ASSERT_TRUE(later_model_a.AddEmptyTab(0, /*select=*/true)); | |
| TabAndroid* frozen_tab_a = later_model_a.AddEmptyTab(1, /*select=*/false); | |
| ASSERT_TRUE(frozen_tab_a); | |
| ASSERT_TRUE(later_model_b.AddEmptyTab(0, /*select=*/true)); | |
| TabAndroid* frozen_tab_b = later_model_b.AddEmptyTab(1, /*select=*/false); | |
| ASSERT_TRUE(frozen_tab_b); | |
| ASSERT_EQ(context_profile, frozen_tab_a->profile()); | |
| ASSERT_EQ(context_profile, frozen_tab_b->profile()); | |
| ASSERT_TRUE(frozen_tab_a->web_contents()); | |
| ASSERT_TRUE(frozen_tab_b->web_contents()); | |
| frozen_tab_a->DestroyWebContents(); | |
| frozen_tab_b->DestroyWebContents(); | |
| ASSERT_FALSE(frozen_tab_a->web_contents()); | |
| ASSERT_FALSE(frozen_tab_b->web_contents()); | |
| ASSERT_EQ(context_profile, frozen_tab_a->profile()); | |
| ASSERT_EQ(context_profile, frozen_tab_b->profile()); | |
| ASSERT_EQ(2, later_model_a.GetTabCount()); | |
| ASSERT_EQ(2, later_model_b.GetTabCount()); | |
| params = base::DictValue(); | |
| params.Set("browserContextId", context_id); | |
| ASSERT_TRUE( | |
| SendCommandSync("Target.disposeBrowserContext", std::move(params))); | |
| EXPECT_EQ(0, later_model_a.GetTabCount()); | |
| EXPECT_EQ(0, later_model_b.GetTabCount()); | |
| EXPECT_FALSE(agent_host->GetWebContents()); | |
| EXPECT_EQ(model_count + 1, TabModelList::models().size()); | |
| result = SendCommandSync("Target.getBrowserContexts"); | |
| ASSERT_TRUE(result); | |
| const base::ListValue* browser_context_ids = | |
| result->FindList("browserContextIds"); | |
| ASSERT_TRUE(browser_context_ids); | |
| EXPECT_FALSE(browser_context_ids->contains(context_id)); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| CreateTargetRejectsUnknownBrowserContext) { | |
| AttachToBrowserTarget(); | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("browserContextId", "unknown"); | |
| SendCommandSync("Target.createTarget", std::move(params)); | |
| ASSERT_TRUE(error()); | |
| EXPECT_EQ("Failed to find browser context with id unknown", | |
| *error()->FindString("message")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CreateListDisposeBrowserContext) { | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.createBrowserContext"); | |
| ASSERT_TRUE(result); | |
| const std::string* browser_context_id = | |
| result->FindString("browserContextId"); | |
| ASSERT_TRUE(browser_context_id); | |
| const std::string first_context_id = *browser_context_id; | |
| result = SendCommandSync("Target.createBrowserContext"); | |
| ASSERT_TRUE(result); | |
| browser_context_id = result->FindString("browserContextId"); | |
| ASSERT_TRUE(browser_context_id); | |
| const std::string second_context_id = *browser_context_id; | |
| EXPECT_NE(first_context_id, second_context_id); | |
| result = SendCommandSync("Target.getBrowserContexts"); | |
| ASSERT_TRUE(result); | |
| const base::ListValue* browser_context_ids = | |
| result->FindList("browserContextIds"); | |
| ASSERT_TRUE(browser_context_ids); | |
| EXPECT_TRUE(browser_context_ids->contains(first_context_id)); | |
| EXPECT_TRUE(browser_context_ids->contains(second_context_id)); | |
| const std::string* default_context_id = | |
| result->FindString("defaultBrowserContextId"); | |
| ASSERT_TRUE(default_context_id); | |
| EXPECT_NE(first_context_id, *default_context_id); | |
| EXPECT_NE(second_context_id, *default_context_id); | |
| base::DictValue params; | |
| params.Set("browserContextId", first_context_id); | |
| ASSERT_TRUE( | |
| SendCommandSync("Target.disposeBrowserContext", std::move(params))); | |
| result = SendCommandSync("Target.getBrowserContexts"); | |
| ASSERT_TRUE(result); | |
| browser_context_ids = result->FindList("browserContextIds"); | |
| ASSERT_TRUE(browser_context_ids); | |
| EXPECT_FALSE(browser_context_ids->contains(first_context_id)); | |
| EXPECT_TRUE(browser_context_ids->contains(second_context_id)); | |
| params = base::DictValue(); | |
| params.Set("browserContextId", second_context_id); | |
| ASSERT_TRUE( | |
| SendCommandSync("Target.disposeBrowserContext", std::move(params))); | |
| result = SendCommandSync("Target.getBrowserContexts"); | |
| ASSERT_TRUE(result); | |
| browser_context_ids = result->FindList("browserContextIds"); | |
| ASSERT_TRUE(browser_context_ids); | |
| EXPECT_FALSE(browser_context_ids->contains(first_context_id)); | |
| EXPECT_FALSE(browser_context_ids->contains(second_context_id)); | |
| } | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| NoInputEventsSentToBrowserWhenDisallowed) { | |
| SetIsTrusted(false); | |
| Attach(); | |
| base::DictValue params; | |
| params.Set("type", "rawKeyDown"); | |
| params.Set("key", "F12"); | |
| params.Set("windowsVirtualKeyCode", 123); | |
| params.Set("nativeVirtualKeyCode", 123); | |
| SendCommandSync("Input.dispatchKeyEvent", std::move(params)); | |
| EXPECT_EQ(nullptr, DevToolsWindow::FindDevToolsWindow(agent_host_.get())); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| PreloadEnabledStateUpdatedDefault) { | |
| Attach(); | |
| SendCommandAsync("Preload.enable"); | |
| const base::DictValue result = | |
| WaitForNotification("Preload.preloadEnabledStateUpdated", true); | |
| EXPECT_THAT(result.FindBool("disabledByPreference"), false); | |
| EXPECT_THAT(result.FindBool("disabledByHoldbackPrefetchSpeculationRules"), | |
| false); | |
| EXPECT_THAT(result.FindBool("disabledByHoldbackPrerenderSpeculationRules"), | |
| false); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| PreloadEnabledStateUpdatedDisabledByPreference) { | |
| Attach(); | |
| prefetch::SetPreloadPagesState( | |
| chrome_test_utils::GetProfile(this)->GetPrefs(), | |
| prefetch::PreloadPagesState::kNoPreloading); | |
| SendCommandAsync("Preload.enable"); | |
| const base::DictValue result = | |
| WaitForNotification("Preload.preloadEnabledStateUpdated", true); | |
| EXPECT_THAT(result.FindBool("disabledByPreference"), true); | |
| } | |
| class DevToolsProtocolTest_PreloadEnabledStateUpdatedDisabledByHoldback | |
| : public DevToolsProtocolTest { | |
| protected: | |
| void SetUp() override { | |
| // This holds back speculation rules prefetch and prerender. Note that | |
| // directly using enums (instead of strings) in the call to SetHoldback is | |
| // usually preferred, but this is not possible here because | |
| // content::content_preloading_predictor::kSpeculationRules, which is not | |
| // exposed outside of content. | |
| preloading_config_override_.SetHoldback("Prefetch", "SpeculationRules", | |
| true); | |
| preloading_config_override_.SetHoldback("Prerender", "SpeculationRules", | |
| true); | |
| DevToolsProtocolTest::SetUp(); | |
| } | |
| private: | |
| content::test::PreloadingConfigOverride preloading_config_override_; | |
| }; | |
| IN_PROC_BROWSER_TEST_F( | |
| DevToolsProtocolTest_PreloadEnabledStateUpdatedDisabledByHoldback, | |
| PreloadEnabledStateUpdatedDisabledByHoldback) { | |
| Attach(); | |
| SendCommandAsync("Preload.enable"); | |
| const base::DictValue result = | |
| WaitForNotification("Preload.preloadEnabledStateUpdated", true); | |
| EXPECT_THAT(result.FindBool("disabledByHoldbackPrefetchSpeculationRules"), | |
| true); | |
| EXPECT_THAT(result.FindBool("disabledByHoldbackPrerenderSpeculationRules"), | |
| true); | |
| } | |
| class DevToolsProtocolTest_PrefetchHoldbackDisabledIfCDPClientConnected | |
| : public DevToolsProtocolTest { | |
| protected: | |
| void SetUp() override { | |
| preloading_config_override_.SetHoldback("Prefetch", "SpeculationRules", | |
| true); | |
| DevToolsProtocolTest::SetUp(); | |
| } | |
| private: | |
| content::test::PreloadingConfigOverride preloading_config_override_; | |
| }; | |
| // Check that prefetch is enabled if DevToolsAgentHost exists even if it is | |
| // disabled by PreloadingConfig. | |
| IN_PROC_BROWSER_TEST_F( | |
| DevToolsProtocolTest_PrefetchHoldbackDisabledIfCDPClientConnected, | |
| PrefetchHoldbackDisabledIfCDPClientConnected) { | |
| Attach(); | |
| { | |
| SendCommandAsync("Preload.enable"); | |
| const base::DictValue result = | |
| WaitForNotification("Preload.preloadEnabledStateUpdated", true); | |
| EXPECT_THAT(result.FindBool("disabledByHoldbackPrefetchSpeculationRules"), | |
| true); | |
| } | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL("/empty.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| const std::string add_specrules = R"( | |
| const specrules = document.createElement("script"); | |
| specrules.type = "speculationrules"; | |
| specrules.text = ` | |
| { | |
| "prefetch":[ | |
| { | |
| "source": "list", | |
| "urls": ["title1.html"] | |
| } | |
| ] | |
| }`; | |
| document.body.appendChild(specrules); | |
| )"; | |
| EXPECT_TRUE(content::EvalJs(web_contents(), add_specrules).is_ok()); | |
| { | |
| base::DictValue result; | |
| while (true) { | |
| result = WaitForNotification("Preload.prefetchStatusUpdated", true); | |
| if (*result.FindString("status") == "Ready") { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| #if !BUILDFLAG(IS_ANDROID) | |
| IN_PROC_BROWSER_TEST_F( | |
| DevToolsProtocolTest, | |
| NoPendingUrlShownWhenAttachedToBrowserInitiatedFailedNavigation) { | |
| GURL url("invalid.scheme:for-sure"); | |
| ui_test_utils::AllBrowserTabAddedWaiter tab_added_waiter; | |
| content::WebContents* web_contents = | |
| browser()->OpenURL(content::OpenURLParams::CreateBrowserInitiated( | |
| url, WindowOpenDisposition::NEW_FOREGROUND_TAB, | |
| ui::PAGE_TRANSITION_TYPED), | |
| /*navigation_handle_callback=*/{}); | |
| tab_added_waiter.Wait(); | |
| ASSERT_TRUE(WaitForLoadStop(web_contents)); | |
| content::NavigationController& navigation_controller = | |
| web_contents->GetController(); | |
| content::NavigationEntry* pending_entry = | |
| navigation_controller.GetPendingEntry(); | |
| ASSERT_NE(nullptr, pending_entry); | |
| EXPECT_EQ(url, pending_entry->GetURL()); | |
| EXPECT_EQ(pending_entry, navigation_controller.GetVisibleEntry()); | |
| agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(web_contents); | |
| agent_host_->AttachClient(this); | |
| SendCommandSync("Page.enable"); | |
| // Ensure that a failed pending entry is cleared when the DevTools protocol | |
| // attaches, so that any modified page content is not attributed to the failed | |
| // URL. (crbug.com/40055319) | |
| EXPECT_EQ(nullptr, navigation_controller.GetPendingEntry()); | |
| EXPECT_EQ(GURL(""), navigation_controller.GetVisibleEntry()->GetURL()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| NoPendingUrlShownForPageNavigateFromChromeExtension) { | |
| GURL url("https://example.com"); | |
| // DevTools protocol use cases that have an initiator origin (e.g., for | |
| // extensions) should use renderer-initiated navigations and be subject to URL | |
| // spoof defenses. | |
| SetNavigationInitiatorOrigin( | |
| url::Origin::Create(GURL("chrome-extension://abc123/"))); | |
| // Attach DevTools and start a navigation but don't wait for it to finish. | |
| Attach(); | |
| SendCommandSync("Page.enable"); | |
| base::DictValue params; | |
| params.Set("url", url.spec()); | |
| SendCommandAsync("Page.navigate", std::move(params)); | |
| content::NavigationController& navigation_controller = | |
| web_contents()->GetController(); | |
| content::NavigationEntry* pending_entry = | |
| navigation_controller.GetPendingEntry(); | |
| ASSERT_NE(nullptr, pending_entry); | |
| EXPECT_EQ(url, pending_entry->GetURL()); | |
| // Attaching the DevTools protocol to the initial empty document of a new tab | |
| // should prevent the pending URL from being visible, since the protocol | |
| // allows modifying the initial empty document in a way that could be useful | |
| // for URL spoofs. | |
| EXPECT_NE(pending_entry, navigation_controller.GetVisibleEntry()); | |
| EXPECT_NE(nullptr, navigation_controller.GetPendingEntry()); | |
| EXPECT_EQ(GURL("about:blank"), | |
| navigation_controller.GetVisibleEntry()->GetURL()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, SetRPHRegistrationMode) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL("/empty.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| // Initial value | |
| custom_handlers::ProtocolHandlerRegistry* registry = | |
| ProtocolHandlerRegistryFactory::GetForBrowserContext( | |
| chrome_test_utils::GetProfile(this)); | |
| EXPECT_EQ(custom_handlers::RphRegistrationMode::kNone, | |
| registry->registration_mode()); | |
| // Set a invalid value | |
| base::DictValue params_invalid; | |
| params_invalid.Set("mode", "accept"); | |
| SendCommand("Page.setRPHRegistrationMode", std::move(params_invalid)); | |
| EXPECT_EQ(custom_handlers::RphRegistrationMode::kNone, | |
| registry->registration_mode()); | |
| // Set a valid value | |
| base::DictValue params; | |
| params.Set("mode", "autoAccept"); | |
| SendCommand("Page.setRPHRegistrationMode", std::move(params)); | |
| EXPECT_EQ(custom_handlers::RphRegistrationMode::kAutoAccept, | |
| registry->registration_mode()); | |
| } | |
| class URLCookieAccessObserver : public content::WebContentsObserver { | |
| public: | |
| URLCookieAccessObserver(content::WebContents* web_contents, | |
| const GURL& url, | |
| content::CookieAccessDetails::Type access_type) | |
| : WebContentsObserver(web_contents), | |
| url_(url), | |
| access_type_(access_type) {} | |
| void Wait() { run_loop_.Run(); } | |
| private: | |
| // WebContentsObserver overrides | |
| void OnCookiesAccessed(content::RenderFrameHost* render_frame_host, | |
| const content::CookieAccessDetails& details) override { | |
| if (details.type == access_type_ && details.url == url_) { | |
| run_loop_.Quit(); | |
| } | |
| } | |
| void OnCookiesAccessed(content::NavigationHandle* navigation_handle, | |
| const content::CookieAccessDetails& details) override { | |
| if (details.type == access_type_ && details.url == url_) { | |
| run_loop_.Quit(); | |
| } | |
| } | |
| GURL url_; | |
| content::CookieAccessDetails::Type access_type_; | |
| base::RunLoop run_loop_; | |
| }; | |
| class DevToolsProtocolTest_BounceTrackingMitigations | |
| : public DevToolsProtocolTest { | |
| protected: | |
| void SetUp() override { | |
| scoped_feature_list_.InitWithFeaturesAndParameters( | |
| /*enabled_features=*/{{features::kBtm, | |
| {{"triggering_action", "stateful_bounce"}}}}, | |
| /*disabled_features=*/{}); | |
| DevToolsProtocolTest::SetUp(); | |
| } | |
| void SetUpOnMainThread() override { | |
| host_resolver()->AddRule("*", "127.0.0.1"); | |
| DevToolsProtocolTest::SetUpOnMainThread(); | |
| } | |
| void SetBlockThirdPartyCookies(bool value) { | |
| chrome_test_utils::GetProfile(this)->GetPrefs()->SetInteger( | |
| prefs::kCookieControlsMode, | |
| static_cast<int>( | |
| value ? content_settings::CookieControlsMode::kBlockThirdParty | |
| : content_settings::CookieControlsMode::kOff)); | |
| } | |
| private: | |
| base::test::ScopedFeatureList scoped_feature_list_; | |
| }; | |
| testing::AssertionResult SimulateBtmBounce(BrowserWindowInterface* browser, | |
| const GURL& initial_url, | |
| const GURL& bounce_url, | |
| const GURL& final_url) { | |
| if (!ui_test_utils::NavigateToURLWithDisposition( | |
| browser, initial_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, | |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB | | |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)) { | |
| return testing::AssertionFailure() | |
| << "Failed to navigate to " << initial_url; | |
| } | |
| content::WebContents* web_contents = | |
| browser->GetTabStripModel()->GetActiveWebContents(); | |
| content::BtmService* btm_service = | |
| content::BtmService::Get(web_contents->GetBrowserContext()); | |
| if (!content::NavigateToURLFromRenderer(web_contents, bounce_url)) { | |
| return testing::AssertionFailure() | |
| << "Failed to navigate to " << bounce_url; | |
| } | |
| URLCookieAccessObserver cookie_observer( | |
| web_contents, bounce_url, content::CookieAccessDetails::Type::kChange); | |
| testing::AssertionResult js_result = | |
| content::ExecJs(web_contents, "document.cookie = 'bounce=stateful';", | |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE); | |
| if (!js_result) { | |
| return js_result; | |
| } | |
| cookie_observer.Wait(); | |
| content::BtmRedirectChainObserver final_observer(btm_service, final_url); | |
| if (!content::NavigateToURLFromRendererWithoutUserGesture(web_contents, | |
| final_url)) { | |
| return testing::AssertionFailure() << "Failed to navigate to " << final_url; | |
| } | |
| // End redirect chain by closing the tab. | |
| web_contents->Close(); | |
| final_observer.Wait(); | |
| if (testing::Test::HasFailure()) { | |
| return testing::AssertionFailure() << "Failure generated while waiting for " | |
| "the redirect chain to be reported"; | |
| } | |
| if (final_observer.redirects()->size() != 1) { | |
| return testing::AssertionFailure() << "Expected 1 redirect; found " | |
| << final_observer.redirects()->size(); | |
| } | |
| const content::BtmRedirect& redirect = *final_observer.redirects()->at(0); | |
| if (redirect.redirector_url != bounce_url) { | |
| return testing::AssertionFailure() << "Expected redirect at " << bounce_url | |
| << "; found " << redirect.redirector_url; | |
| } | |
| if (redirect.access_type != content::BtmDataAccessType::kWrite && | |
| redirect.access_type != content::BtmDataAccessType::kReadWrite) { | |
| return testing::AssertionFailure() | |
| << "No write access recorded for redirect"; | |
| } | |
| return testing::AssertionSuccess(); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_BounceTrackingMitigations, | |
| RunBounceTrackingMitigations) { | |
| SetBlockThirdPartyCookies(true); | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL("/empty.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| const GURL bouncer( | |
| embedded_test_server()->GetURL("example.test", "/title1.html")); | |
| // Record a stateful bounce for `bouncer`. | |
| ASSERT_TRUE(SimulateBtmBounce( | |
| browser(), embedded_test_server()->GetURL("a.test", "/empty.html"), | |
| bouncer, embedded_test_server()->GetURL("b.test", "/empty.html"))); | |
| SendCommandSync("Storage.runBounceTrackingMitigations"); | |
| const base::ListValue* deleted_sites_list = | |
| result()->FindList("deletedSites"); | |
| ASSERT_TRUE(deleted_sites_list); | |
| std::vector<std::string> deleted_sites; | |
| for (const auto& site : *deleted_sites_list) { | |
| deleted_sites.push_back(site.GetString()); | |
| } | |
| EXPECT_THAT(deleted_sites, testing::ElementsAre("example.test")); | |
| } | |
| class BtmStatusDevToolsProtocolTest | |
| : public DevToolsProtocolTest, | |
| public testing::WithParamInterface<std::tuple<bool, std::string>> { | |
| // The fields of `GetParam()` indicate/control the following: | |
| // `std::get<0>(GetParam())` => `features::kBtm` | |
| // `std::get<1>(GetParam())` => `features::kBtmTriggeringAction` | |
| // | |
| // In order for Bounce Tracking Mitigations to take effect, `features::kBtm` | |
| // must be enabled, and `features::kTriggeringAction` must NOT be none. | |
| // | |
| // Note: Bounce Tracking Mitigations issues only report sites that would | |
| // be affected when `features::kTriggeringAction` is set to stateful_bounce. | |
| protected: | |
| void SetUp() override { | |
| if (std::get<0>(GetParam())) { | |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( | |
| features::kBtm, {{"triggering_action", std::get<1>(GetParam())}}); | |
| } else { | |
| scoped_feature_list_.InitAndDisableFeature(features::kBtm); | |
| } | |
| DevToolsProtocolTest::SetUp(); | |
| } | |
| bool ShouldBeEnabled() { | |
| return (std::get<0>(GetParam()) && (std::get<1>(GetParam()) != "none")); | |
| } | |
| private: | |
| base::test::ScopedFeatureList scoped_feature_list_; | |
| }; | |
| IN_PROC_BROWSER_TEST_P(BtmStatusDevToolsProtocolTest, | |
| TrueWhenEnabledAndDeleting) { | |
| AttachToBrowserTarget(); | |
| base::DictValue btm_params; | |
| btm_params.Set("featureState", "DIPS"); | |
| SendCommand("SystemInfo.getFeatureState", std::move(btm_params)); | |
| EXPECT_EQ(result()->FindBool("featureEnabled"), ShouldBeEnabled()); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| All, | |
| BtmStatusDevToolsProtocolTest, | |
| ::testing::Combine( | |
| ::testing::Bool(), | |
| ::testing::Values("none", "storage", "bounce", "stateful_bounce"))); | |
| using DevToolsProtocolTest_AppId = DevToolsProtocolTest; | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_AppId, ReturnsManifestAppId) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL( | |
| "/banners/manifest_test_page.html?manifest=manifest_with_id.json")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| const base::DictValue* result = SendCommandSync("Page.getAppId"); | |
| EXPECT_EQ(*result->FindString("appId"), | |
| embedded_test_server()->GetURL("/some_id")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_AppId, | |
| ReturnsStartUrlAsManifestAppIdIfNotSet) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url( | |
| embedded_test_server()->GetURL("/web_apps/no_service_worker.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| const base::DictValue* result = SendCommandSync("Page.getAppId"); | |
| EXPECT_EQ(*result->FindString("appId"), | |
| embedded_test_server()->GetURL("/web_apps/no_service_worker.html")); | |
| EXPECT_EQ(*result->FindString("recommendedId"), | |
| "/web_apps/no_service_worker.html"); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_AppId, ReturnsNoAppIdIfNoManifest) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL("/empty.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| const base::DictValue* result = SendCommandSync("Page.getAppId"); | |
| EXPECT_FALSE(result->Find("appId")); | |
| EXPECT_FALSE(result->Find("recommendedId")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, VisibleSecurityStateSecureState) { | |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); | |
| https_server.ServeFilesFromSourceDirectory(GetChromeTestDataDir()); | |
| ASSERT_TRUE(https_server.Start()); | |
| ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( | |
| browser(), https_server.GetURL("/title1.html"), 1); | |
| content::NavigationEntry* entry = | |
| web_contents()->GetController().GetLastCommittedEntry(); | |
| ASSERT_TRUE(entry); | |
| // Extract SSL status data from the navigation entry. | |
| scoped_refptr<net::X509Certificate> page_cert = entry->GetSSL().certificate; | |
| ASSERT_TRUE(page_cert); | |
| int ssl_version = | |
| net::SSLConnectionStatusToVersion(entry->GetSSL().connection_status); | |
| const char* page_protocol; | |
| net::SSLVersionToString(&page_protocol, ssl_version); | |
| const char* page_key_exchange_str; | |
| const char* page_cipher; | |
| const char* page_mac; | |
| bool is_aead; | |
| bool is_tls13; | |
| uint16_t page_cipher_suite = | |
| net::SSLConnectionStatusToCipherSuite(entry->GetSSL().connection_status); | |
| net::SSLCipherSuiteToStrings(&page_key_exchange_str, &page_cipher, &page_mac, | |
| &is_aead, &is_tls13, page_cipher_suite); | |
| std::string page_key_exchange; | |
| if (page_key_exchange_str) | |
| page_key_exchange = page_key_exchange_str; | |
| const char* page_key_exchange_group = | |
| SSL_get_curve_name(entry->GetSSL().key_exchange_group); | |
| std::string page_subject_name; | |
| std::string page_issuer_name; | |
| double page_valid_from = 0.0; | |
| double page_valid_to = 0.0; | |
| if (entry->GetSSL().certificate) { | |
| page_subject_name = entry->GetSSL().certificate->subject().common_name; | |
| page_issuer_name = entry->GetSSL().certificate->issuer().common_name; | |
| page_valid_from = | |
| entry->GetSSL().certificate->valid_start().InSecondsFSinceUnixEpoch(); | |
| page_valid_to = | |
| entry->GetSSL().certificate->valid_expiry().InSecondsFSinceUnixEpoch(); | |
| } | |
| std::string page_certificate_network_error; | |
| if (net::IsCertStatusError(entry->GetSSL().cert_status)) { | |
| page_certificate_network_error = net::ErrorToString( | |
| net::MapCertStatusToNetError(entry->GetSSL().cert_status)); | |
| } | |
| bool page_certificate_has_weak_signature = | |
| (entry->GetSSL().cert_status & net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); | |
| bool page_certificate_has_sha1_signature_present = | |
| (entry->GetSSL().cert_status & net::CERT_STATUS_SHA1_SIGNATURE_PRESENT); | |
| int status = net::ObsoleteSSLStatus(entry->GetSSL().connection_status, | |
| entry->GetSSL().peer_signature_algorithm); | |
| bool page_modern_ssl = status == net::OBSOLETE_SSL_NONE; | |
| bool page_obsolete_ssl_protocol = status & net::OBSOLETE_SSL_MASK_PROTOCOL; | |
| bool page_obsolete_ssl_key_exchange = | |
| status & net::OBSOLETE_SSL_MASK_KEY_EXCHANGE; | |
| bool page_obsolete_ssl_cipher = status & net::OBSOLETE_SSL_MASK_CIPHER; | |
| bool page_obsolete_ssl_signature = status & net::OBSOLETE_SSL_MASK_SIGNATURE; | |
| Attach(); | |
| SendCommandAsync("Security.enable"); | |
| auto has_certificate = [](const base::DictValue& params) { | |
| return params.FindListByDottedPath( | |
| "visibleSecurityState.certificateSecurityState.certificate") != | |
| nullptr; | |
| }; | |
| base::DictValue params = | |
| WaitForMatchingNotification("Security.visibleSecurityStateChanged", | |
| base::BindRepeating(has_certificate)); | |
| // Verify that the visibleSecurityState payload matches the SSL status data. | |
| std::string* security_state = | |
| params.FindStringByDottedPath("visibleSecurityState.securityState"); | |
| ASSERT_TRUE(security_state); | |
| ASSERT_EQ(std::string("secure"), *security_state); | |
| base::Value* certificate_security_state = | |
| params.FindByDottedPath("visibleSecurityState.certificateSecurityState"); | |
| ASSERT_TRUE(certificate_security_state); | |
| base::DictValue& dict = certificate_security_state->GetDict(); | |
| std::string* protocol = dict.FindString("protocol"); | |
| ASSERT_TRUE(protocol); | |
| ASSERT_EQ(*protocol, page_protocol); | |
| std::string* key_exchange = dict.FindString("keyExchange"); | |
| ASSERT_TRUE(key_exchange); | |
| ASSERT_EQ(*key_exchange, page_key_exchange); | |
| std::string* key_exchange_group = dict.FindString("keyExchangeGroup"); | |
| if (key_exchange_group) { | |
| ASSERT_EQ(*key_exchange_group, page_key_exchange_group); | |
| } | |
| std::string* mac = dict.FindString("mac"); | |
| if (mac) { | |
| ASSERT_EQ(*mac, page_mac); | |
| } | |
| std::string* cipher = dict.FindString("cipher"); | |
| ASSERT_TRUE(cipher); | |
| ASSERT_EQ(*cipher, page_cipher); | |
| std::string* subject_name = dict.FindString("subjectName"); | |
| ASSERT_TRUE(subject_name); | |
| ASSERT_EQ(*subject_name, page_subject_name); | |
| std::string* issuer = dict.FindString("issuer"); | |
| ASSERT_TRUE(issuer); | |
| ASSERT_EQ(*issuer, page_issuer_name); | |
| auto valid_from = dict.FindDouble("validFrom"); | |
| ASSERT_TRUE(valid_from); | |
| ASSERT_EQ(*valid_from, page_valid_from); | |
| auto valid_to = dict.FindDouble("validTo"); | |
| ASSERT_TRUE(valid_to); | |
| ASSERT_EQ(*valid_to, page_valid_to); | |
| std::string* certificate_network_error = | |
| dict.FindString("certificateNetworkError"); | |
| if (certificate_network_error) { | |
| ASSERT_EQ(*certificate_network_error, page_certificate_network_error); | |
| } | |
| auto certificate_has_weak_signature = | |
| dict.FindBool("certificateHasWeakSignature"); | |
| ASSERT_TRUE(certificate_has_weak_signature); | |
| ASSERT_EQ(*certificate_has_weak_signature, | |
| page_certificate_has_weak_signature); | |
| auto certificate_has_sha1_signature_present = | |
| dict.FindBool("certificateHasSha1Signature"); | |
| ASSERT_TRUE(certificate_has_sha1_signature_present); | |
| ASSERT_EQ(*certificate_has_sha1_signature_present, | |
| page_certificate_has_sha1_signature_present); | |
| auto modern_ssl = dict.FindBool("modernSSL"); | |
| ASSERT_TRUE(modern_ssl); | |
| ASSERT_EQ(*modern_ssl, page_modern_ssl); | |
| auto obsolete_ssl_protocol = dict.FindBool("obsoleteSslProtocol"); | |
| ASSERT_TRUE(obsolete_ssl_protocol); | |
| ASSERT_EQ(*obsolete_ssl_protocol, page_obsolete_ssl_protocol); | |
| auto obsolete_ssl_key_exchange = dict.FindBool("obsoleteSslKeyExchange"); | |
| ASSERT_TRUE(obsolete_ssl_key_exchange); | |
| ASSERT_EQ(*obsolete_ssl_key_exchange, page_obsolete_ssl_key_exchange); | |
| auto obsolete_ssl_cipher = dict.FindBool("obsoleteSslCipher"); | |
| ASSERT_TRUE(obsolete_ssl_cipher); | |
| ASSERT_EQ(*obsolete_ssl_cipher, page_obsolete_ssl_cipher); | |
| auto obsolete_ssl_signature = dict.FindBool("obsoleteSslSignature"); | |
| ASSERT_TRUE(obsolete_ssl_signature); | |
| ASSERT_EQ(*obsolete_ssl_signature, page_obsolete_ssl_signature); | |
| const base::ListValue* certificate_value = dict.FindList("certificate"); | |
| ASSERT_TRUE(certificate_value); | |
| std::vector<std::string> der_certs; | |
| for (const auto& cert : *certificate_value) { | |
| std::string decoded; | |
| ASSERT_TRUE(base::Base64Decode(cert.GetString(), &decoded)); | |
| der_certs.push_back(decoded); | |
| } | |
| std::vector<std::string_view> cert_string_piece; | |
| for (const auto& str : der_certs) { | |
| cert_string_piece.push_back(str); | |
| } | |
| // Check that the certificateSecurityState.certificate matches. | |
| net::SHA256HashValue page_cert_chain_fingerprint = | |
| page_cert->CalculateChainFingerprint256(); | |
| scoped_refptr<net::X509Certificate> certificate = | |
| net::X509Certificate::CreateFromDERCertChain(cert_string_piece); | |
| ASSERT_TRUE(certificate); | |
| EXPECT_EQ(page_cert_chain_fingerprint, | |
| certificate->CalculateChainFingerprint256()); | |
| const base::Value* security_state_issue_ids = | |
| params.FindByDottedPath("visibleSecurityState.securityStateIssueIds"); | |
| ASSERT_TRUE(security_state_issue_ids->is_list()); | |
| EXPECT_EQ(security_state_issue_ids->GetList().size(), 0u); | |
| EXPECT_FALSE(params.FindByDottedPath("visibleSecurityState.safetyTipInfo")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| AutomationOverrideShowsAndRemovesInfoBar) { | |
| Attach(); | |
| auto* manager = infobars::ContentInfoBarManager::FromWebContents( | |
| chrome_test_utils::GetActiveWebContents(this)); | |
| { | |
| base::DictValue params; | |
| params.Set("enabled", true); | |
| SendCommandSync("Emulation.setAutomationOverride", std::move(params)); | |
| } | |
| EXPECT_EQ(manager->infobars().size(), 1u); | |
| { | |
| base::DictValue params; | |
| params.Set("enabled", false); | |
| SendCommandSync("Emulation.setAutomationOverride", std::move(params)); | |
| } | |
| EXPECT_EQ(manager->infobars().size(), 0u); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| AutomationOverrideAddsOneInfoBarOnly) { | |
| Attach(); | |
| auto* manager = infobars::ContentInfoBarManager::FromWebContents( | |
| chrome_test_utils::GetActiveWebContents(this)); | |
| { | |
| base::DictValue params; | |
| params.Set("enabled", true); | |
| SendCommandSync("Emulation.setAutomationOverride", std::move(params)); | |
| } | |
| EXPECT_EQ(manager->infobars().size(), 1u); | |
| { | |
| base::DictValue params; | |
| params.Set("enabled", true); | |
| SendCommandSync("Emulation.setAutomationOverride", std::move(params)); | |
| } | |
| EXPECT_EQ(manager->infobars().size(), 1u); | |
| } | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, UntrustedClient) { | |
| SetIsTrusted(false); | |
| Attach(); | |
| EXPECT_FALSE(SendCommandSync("HeapProfiler.enable")); // Implemented in V8 | |
| EXPECT_FALSE(SendCommandSync("LayerTree.enable")); // Implemented in blink | |
| EXPECT_FALSE(SendCommandSync( | |
| "Memory.prepareForLeakDetection")); // Implemented in content | |
| EXPECT_FALSE(SendCommandSync("Cast.enable")); // Implemented in content | |
| EXPECT_TRUE(SendCommandSync("Accessibility.enable")); | |
| } | |
| #if !BUILDFLAG(IS_ANDROID) | |
| class DevToolsProtocolScreenshotTest : public DevToolsProtocolTest { | |
| protected: | |
| void SetUp() override { | |
| EnablePixelOutput(); | |
| DevToolsProtocolTest::SetUp(); | |
| } | |
| SkBitmap CaptureScreenshot() { | |
| SendCommandSync("Page.captureScreenshot"); | |
| CHECK(!error()); | |
| const std::string* base64_data = result()->FindString("data"); | |
| CHECK(base64_data); | |
| std::optional<std::vector<uint8_t>> png_data = | |
| base::Base64Decode(*base64_data); | |
| SkBitmap bitmap = gfx::PNGCodec::Decode(png_data.value()); | |
| CHECK(!bitmap.isNull()); | |
| return bitmap; | |
| } | |
| }; | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolScreenshotTest, ScreenshotInactiveTab) { | |
| static constexpr char kBluePageURL[] = | |
| R"(data:text/html,<body style="background-color: blue"></body>)"; | |
| static constexpr char kRedPageURL[] = | |
| R"(data:text/html,<body style="background-color: red"></body>)"; | |
| ASSERT_TRUE(content::NavigateToURL(web_contents(), GURL(kBluePageURL))); | |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); | |
| Attach(); | |
| constexpr int kIndex = 1; | |
| ASSERT_TRUE(AddTabAtIndex(kIndex, GURL(kRedPageURL), | |
| ui::PageTransition::PAGE_TRANSITION_TYPED)); | |
| SkBitmap bitmap = CaptureScreenshot(); | |
| SkColor pixel_color = bitmap.getColor(100, 100); | |
| EXPECT_EQ(SK_ColorBLUE, pixel_color); | |
| } | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| #if BUILDFLAG(ENABLE_EXTENSIONS_CORE) | |
| class ExtensionProtocolTest : public DevToolsProtocolTest { | |
| protected: | |
| void SetUpOnMainThread() override { | |
| DevToolsProtocolTest::SetUpOnMainThread(); | |
| Profile* profile = chrome_test_utils::GetProfile(this); | |
| extension_registrar_ = extensions::ExtensionRegistrar::Get(profile); | |
| extension_registry_ = extensions::ExtensionRegistry::Get(profile); | |
| } | |
| content::WebContents* web_contents() override { | |
| return background_web_contents_; | |
| } | |
| const extensions::Extension* LoadExtensionOrApp( | |
| const base::FilePath& extension_path) { | |
| extensions::TestExtensionRegistryObserver observer(extension_registry_); | |
| extensions::UnpackedInstaller::Create(chrome_test_utils::GetProfile(this)) | |
| ->Load(extension_path); | |
| observer.WaitForExtensionLoaded(); | |
| const extensions::Extension* extension = nullptr; | |
| for (const auto& enabled_extension : | |
| extension_registry_->enabled_extensions()) { | |
| if (enabled_extension->path() == extension_path) { | |
| extension = enabled_extension.get(); | |
| break; | |
| } | |
| } | |
| CHECK(extension) << "Failed to find loaded extension " << extension_path; | |
| return extension; | |
| } | |
| const extensions::Extension* LoadExtension( | |
| const base::FilePath& extension_path) { | |
| ExtensionTestMessageListener activated_listener("WORKER_ACTIVATED"); | |
| const extensions::Extension* extension = LoadExtensionOrApp(extension_path); | |
| auto* process_manager = | |
| extensions::ProcessManager::Get(chrome_test_utils::GetProfile(this)); | |
| if (extensions::BackgroundInfo::IsServiceWorkerBased(extension)) { | |
| EXPECT_TRUE(activated_listener.WaitUntilSatisfied()); | |
| auto worker_ids = | |
| process_manager->GetServiceWorkersForExtension(extension->id()); | |
| CHECK_EQ(1lu, worker_ids.size()); | |
| } else { | |
| extensions::ExtensionHost* host = | |
| process_manager->GetBackgroundHostForExtension(extension->id()); | |
| background_web_contents_ = host->host_contents(); | |
| } | |
| return extension; | |
| } | |
| #if !BUILDFLAG(IS_ANDROID) | |
| // Android does not support Chrome Apps. | |
| void LaunchApp(const std::string& app_id) { | |
| apps::AppLaunchParams params( | |
| app_id, apps::LaunchContainer::kLaunchContainerNone, | |
| WindowOpenDisposition::NEW_WINDOW, apps::LaunchSource::kFromTest); | |
| apps::AppServiceProxyFactory::GetForProfile( | |
| chrome_test_utils::GetProfile(this)) | |
| ->BrowserAppLauncher() | |
| ->LaunchAppWithParamsForTesting(std::move(params)); | |
| } | |
| #endif | |
| void ReloadExtension(const std::string& extension_id) { | |
| extensions::TestExtensionRegistryObserver observer(extension_registry_); | |
| extension_registrar_->ReloadExtension(extension_id); | |
| observer.WaitForExtensionLoaded(); | |
| } | |
| private: | |
| raw_ptr<extensions::ExtensionRegistrar, DanglingUntriaged> | |
| extension_registrar_; | |
| raw_ptr<extensions::ExtensionRegistry, DanglingUntriaged> extension_registry_; | |
| raw_ptr<content::WebContents, DanglingUntriaged> background_web_contents_; | |
| #if BUILDFLAG(IS_WIN) | |
| // This is needed to stop ExtensionProtocolTestsfrom creating a | |
| // shortcut in the Windows start menu. The override needs to last until the | |
| // test is destroyed, because Windows shortcut tasks which create the shortcut | |
| // can run after the test body returns. | |
| base::ScopedPathOverride override_start_dir{base::DIR_START_MENU}; | |
| #endif // BUILDFLAG(IS_WIN | |
| // TODO(https://crbug.com/40804030): Remove this when updated to use MV3. | |
| extensions::ScopedTestMV2Enabler mv2_enabler_; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(ExtensionProtocolTest, ReloadTracedExtension) { | |
| base::FilePath extension_path = | |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA) | |
| .AppendASCII("devtools") | |
| .AppendASCII("extensions") | |
| .AppendASCII("simple_background_page"); | |
| auto* extension = LoadExtension(extension_path); | |
| ASSERT_TRUE(extension); | |
| Attach(); | |
| ReloadExtension(extension->id()); | |
| base::DictValue params; | |
| params.Set("categories", "-*"); | |
| SendCommandSync("Tracing.start", std::move(params)); | |
| SendCommandAsync("Tracing.end"); | |
| WaitForNotification("Tracing.tracingComplete", true); | |
| } | |
| IN_PROC_BROWSER_TEST_F(ExtensionProtocolTest, | |
| DISABLED_ReloadServiceWorkerExtension) { | |
| base::FilePath extension_path = | |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA) | |
| .AppendASCII("devtools") | |
| .AppendASCII("extensions") | |
| .AppendASCII("service_worker"); | |
| std::string extension_id; | |
| { | |
| // `extension` is stale after reload. | |
| auto* extension = LoadExtension(extension_path); | |
| ASSERT_THAT(extension, testing::NotNull()); | |
| extension_id = extension->id(); | |
| } | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = SendCommandSync("Target.getTargets"); | |
| base::DictValue ext_target; | |
| for (const auto& target : *result->FindList("targetInfos")) { | |
| if (*target.GetDict().FindString("type") == "service_worker") { | |
| ext_target = target.Clone().TakeDict(); | |
| break; | |
| } | |
| } | |
| { | |
| base::DictValue params; | |
| params.Set("targetId", CHECK_DEREF(ext_target.FindString("targetId"))); | |
| params.Set("waitForDebuggerOnStart", false); | |
| SendCommandSync("Target.autoAttachRelated", std::move(params)); | |
| } | |
| ReloadExtension(extension_id); | |
| base::DictValue attached = | |
| WaitForNotification("Target.attachedToTarget", true); | |
| base::Value* targetInfo = attached.Find("targetInfo"); | |
| ASSERT_THAT(targetInfo, testing::NotNull()); | |
| EXPECT_THAT(*targetInfo, base::test::DictionaryHasValue( | |
| "type", base::Value("service_worker"))); | |
| EXPECT_THAT(*targetInfo, base::test::DictionaryHasValue( | |
| "url", CHECK_DEREF(ext_target.Find("url")))); | |
| EXPECT_THAT(attached.FindBool("waitingForDebugger"), | |
| testing::Optional(false)); | |
| { | |
| base::DictValue params; | |
| params.Set("targetId", *targetInfo->GetDict().FindString("targetId")); | |
| params.Set("waitForDebuggerOnStart", false); | |
| SendCommandSync("Target.autoAttachRelated", std::move(params)); | |
| } | |
| auto detached = WaitForNotification("Target.detachedFromTarget", true); | |
| EXPECT_THAT(*detached.FindString("sessionId"), Eq("sessionId")); | |
| } | |
| // TODO(https://crbug.com/501442926): The following tests use guest view in a | |
| // Chrome App, which is only supported on ChromeOS. | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| // Accepts a list of URL predicates and allows awaiting for all matching | |
| // WebContents to load. | |
| class WebContentsBarrier { | |
| public: | |
| using Predicate = base::FunctionRef<bool(const GURL& url)>; | |
| WebContentsBarrier(std::initializer_list<Predicate> predicates) | |
| : predicates_(predicates) {} | |
| std::vector<raw_ptr<content::WebContents, VectorExperimental>> Await() { | |
| if (!IsReady()) { | |
| base::RunLoop run_loop; | |
| ready_callback_ = run_loop.QuitClosure(); | |
| run_loop.Run(); | |
| CHECK(IsReady()); | |
| } | |
| return std::move(ready_web_contents_); | |
| } | |
| private: | |
| class LoadObserver : public content::WebContentsObserver { | |
| public: | |
| LoadObserver(content::WebContents& wc, WebContentsBarrier& owner) | |
| : WebContentsObserver(&wc), owner_(owner) {} | |
| private: | |
| void DidFinishLoad(content::RenderFrameHost* host, | |
| const GURL& url) override { | |
| if (host != web_contents()->GetPrimaryMainFrame()) { | |
| return; | |
| } | |
| owner_->OnWebContentsLoaded(web_contents(), url); | |
| } | |
| const raw_ref<WebContentsBarrier> owner_; | |
| }; | |
| bool IsReady() const { return !pending_contents_count_; } | |
| void OnWebContentsCreated(content::WebContents* wc) { | |
| observers_.push_back(std::make_unique<LoadObserver>(*wc, *this)); | |
| } | |
| void OnWebContentsLoaded(content::WebContents* wc, const GURL& url) { | |
| CHECK(!IsReady()); | |
| for (size_t i = 0; i < predicates_.size(); ++i) { | |
| if (!predicates_[i](url)) { | |
| continue; | |
| } | |
| CHECK(!ready_web_contents_[i]) | |
| << " predicate #" << i << " matches " | |
| << ready_web_contents_[i]->GetLastCommittedURL() << " and " << url; | |
| ready_web_contents_[i] = wc; | |
| --pending_contents_count_; | |
| } | |
| if (IsReady() && ready_callback_) { | |
| std::move(ready_callback_).Run(); | |
| } | |
| } | |
| const std::vector<Predicate> predicates_; | |
| std::vector<raw_ptr<content::WebContents, VectorExperimental>> | |
| ready_web_contents_{predicates_.size(), nullptr}; | |
| size_t pending_contents_count_{predicates_.size()}; | |
| base::CallbackListSubscription creation_subscription_{ | |
| content::RegisterWebContentsCreationCallback( | |
| base::BindRepeating(&WebContentsBarrier::OnWebContentsCreated, | |
| base::Unretained(this)))}; | |
| std::vector<std::unique_ptr<LoadObserver>> observers_; | |
| base::OnceClosure ready_callback_; | |
| }; | |
| // TODO(crbug.com/40202416): Remove this when we remove the inner WebContents | |
| // implementation for guests. | |
| class ExtensionProtocolTestWithGuestViewInnerWebContents | |
| : public ExtensionProtocolTest { | |
| public: | |
| ExtensionProtocolTestWithGuestViewInnerWebContents() { | |
| scoped_feature_list_.InitAndDisableFeature(features::kGuestViewMPArch); | |
| } | |
| ~ExtensionProtocolTestWithGuestViewInnerWebContents() override = default; | |
| private: | |
| base::test::ScopedFeatureList scoped_feature_list_; | |
| guest_view::TestGuestViewManagerFactory guest_view_manager_factory_; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(ExtensionProtocolTestWithGuestViewInnerWebContents, | |
| TabTargetWithGuestView) { | |
| ASSERT_FALSE(base::FeatureList::IsEnabled(features::kGuestViewMPArch)); | |
| base::FilePath extension_path = | |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA) | |
| .AppendASCII("devtools") | |
| .AppendASCII("extensions") | |
| .AppendASCII("app_with_webview"); | |
| auto* extension = LoadExtensionOrApp(extension_path); | |
| ASSERT_THAT(extension, testing::NotNull()); | |
| WebContentsBarrier barrier( | |
| {[](const GURL& url) -> bool { | |
| return base::EndsWith(url.GetPath(), "host.html"); | |
| }, | |
| [](const GURL& url) -> bool { return url.SchemeIs(url::kDataScheme); }}); | |
| LaunchApp(extension->id()); | |
| std::vector<raw_ptr<content::WebContents, VectorExperimental>> wcs = | |
| barrier.Await(); | |
| ASSERT_THAT(wcs, testing::SizeIs(2)); | |
| EXPECT_NE(wcs[0], wcs[1]); | |
| // Assure host and view have different DevTools hosts. | |
| EXPECT_NE(content::DevToolsAgentHost::GetOrCreateForTab(wcs[0]), | |
| content::DevToolsAgentHost::GetOrCreateForTab(wcs[1])); | |
| // Assure host does not auto-attach view. | |
| AttachToTabTarget(wcs[0]); | |
| base::DictValue command_params; | |
| command_params = base::DictValue(); | |
| command_params.Set("autoAttach", true); | |
| command_params.Set("waitForDebuggerOnStart", false); | |
| command_params.Set("flatten", true); | |
| SendCommandSync("Target.setAutoAttach", std::move(command_params)); | |
| EXPECT_FALSE( | |
| HasExistingNotificationMatching([](const base::DictValue& notification) { | |
| if (*notification.FindString("method") != "Target.attachedToTarget") { | |
| return false; | |
| } | |
| const std::string* url = | |
| notification.FindStringByDottedPath("params.targetInfo.url"); | |
| return url && base::StartsWith(*url, "data:"); | |
| })); | |
| } | |
| class ExtensionProtocolTestWithGuestViewMPArch : public ExtensionProtocolTest { | |
| public: | |
| ExtensionProtocolTestWithGuestViewMPArch() { | |
| scoped_feature_list_.InitAndEnableFeature(features::kGuestViewMPArch); | |
| } | |
| ~ExtensionProtocolTestWithGuestViewMPArch() override = default; | |
| guest_view::TestGuestViewManager* GetGuestViewManager() { | |
| return guest_view_manager_factory_.GetOrCreateTestGuestViewManager( | |
| chrome_test_utils::GetProfile(this), | |
| extensions::ExtensionsAPIClient::Get() | |
| ->CreateGuestViewManagerDelegate()); | |
| } | |
| private: | |
| base::test::ScopedFeatureList scoped_feature_list_; | |
| guest_view::TestGuestViewManagerFactory guest_view_manager_factory_; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(ExtensionProtocolTestWithGuestViewMPArch, | |
| TabTargetDoesNotAutoAttachGuestView) { | |
| ASSERT_TRUE(base::FeatureList::IsEnabled(features::kGuestViewMPArch)); | |
| base::FilePath extension_path = | |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA) | |
| .AppendASCII("devtools") | |
| .AppendASCII("extensions") | |
| .AppendASCII("app_with_webview"); | |
| auto* extension = LoadExtensionOrApp(extension_path); | |
| ASSERT_THAT(extension, testing::NotNull()); | |
| WebContentsBarrier barrier({[](const GURL& url) -> bool { | |
| return base::EndsWith(url.GetPath(), "host.html"); | |
| }}); | |
| LaunchApp(extension->id()); | |
| std::vector<raw_ptr<content::WebContents, VectorExperimental>> wcs = | |
| barrier.Await(); | |
| ASSERT_THAT(wcs, testing::SizeIs(1)); | |
| auto* guest_view = GetGuestViewManager()->WaitForSingleGuestViewCreated(); | |
| ASSERT_TRUE(guest_view); | |
| GetGuestViewManager()->WaitUntilAttached(guest_view); | |
| // Assure tab-target does not auto-attach view. | |
| AttachToTabTarget(wcs[0]); | |
| auto command_params = base::DictValue() | |
| .Set("autoAttach", true) | |
| .Set("waitForDebuggerOnStart", false) | |
| .Set("flatten", true); | |
| SendCommandSync("Target.setAutoAttach", std::move(command_params)); | |
| EXPECT_FALSE( | |
| HasExistingNotificationMatching([](const base::DictValue& notification) { | |
| if (*notification.FindString("method") != "Target.attachedToTarget") { | |
| return false; | |
| } | |
| const std::string* url = | |
| notification.FindStringByDottedPath("params.targetInfo.url"); | |
| return url && base::StartsWith(*url, "data:"); | |
| })); | |
| } | |
| IN_PROC_BROWSER_TEST_F(ExtensionProtocolTestWithGuestViewMPArch, | |
| PrimaryMainFrameTargetAutoAttachesGuestView) { | |
| ASSERT_TRUE(base::FeatureList::IsEnabled(features::kGuestViewMPArch)); | |
| base::FilePath extension_path = | |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA) | |
| .AppendASCII("devtools") | |
| .AppendASCII("extensions") | |
| .AppendASCII("app_with_webview"); | |
| auto* extension = LoadExtensionOrApp(extension_path); | |
| ASSERT_THAT(extension, testing::NotNull()); | |
| WebContentsBarrier barrier({[](const GURL& url) -> bool { | |
| return base::EndsWith(url.GetPath(), "host.html"); | |
| }}); | |
| LaunchApp(extension->id()); | |
| std::vector<raw_ptr<content::WebContents, VectorExperimental>> wcs = | |
| barrier.Await(); | |
| ASSERT_EQ(wcs.size(), 1u); | |
| auto* guest_view = GetGuestViewManager()->WaitForSingleGuestViewCreated(); | |
| const std::string devtools_frame_token = | |
| guest_view->GetGuestMainFrame()->GetDevToolsFrameToken().ToString(); | |
| ASSERT_TRUE(guest_view); | |
| GetGuestViewManager()->WaitUntilAttached(guest_view); | |
| AttachToWebContents(wcs[0]); | |
| auto command_params = base::DictValue() | |
| .Set("autoAttach", true) | |
| .Set("waitForDebuggerOnStart", false) | |
| .Set("flatten", true); | |
| SendCommand("Target.setAutoAttach", std::move(command_params)); | |
| base::DictValue params = | |
| WaitForNotification("Target.attachedToTarget", /*allow_existing=*/true); | |
| EXPECT_EQ("webview", *params.FindStringByDottedPath("targetInfo.type")); | |
| EXPECT_EQ(devtools_frame_token, | |
| *params.FindStringByDottedPath("targetInfo.targetId")); | |
| EXPECT_EQ(wcs[0]->GetPrimaryMainFrame()->GetDevToolsFrameToken().ToString(), | |
| content::DevToolsAgentHost::GetForId(devtools_frame_token) | |
| ->GetParentId()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(ExtensionProtocolTestWithGuestViewMPArch, | |
| GuestViewIframeContentFrameUpdatedAfterAttach) { | |
| ASSERT_TRUE(base::FeatureList::IsEnabled(features::kGuestViewMPArch)); | |
| base::FilePath extension_path = | |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA) | |
| .AppendASCII("devtools") | |
| .AppendASCII("extensions") | |
| .AppendASCII("app_with_webview"); | |
| auto* extension = LoadExtensionOrApp(extension_path); | |
| ASSERT_THAT(extension, testing::NotNull()); | |
| WebContentsBarrier barrier({[](const GURL& url) -> bool { | |
| return base::EndsWith(url.GetPath(), "host.html"); | |
| }}); | |
| LaunchApp(extension->id()); | |
| std::vector<raw_ptr<content::WebContents, VectorExperimental>> wcs = | |
| barrier.Await(); | |
| ASSERT_EQ(wcs.size(), 1u); | |
| auto* guest_view = GetGuestViewManager()->WaitForSingleGuestViewCreated(); | |
| ASSERT_TRUE(guest_view); | |
| GetGuestViewManager()->WaitUntilAttached(guest_view); | |
| AttachToWebContents(wcs[0]); | |
| // Get the document's NodeId. | |
| const base::DictValue* result = SendCommandSync("DOM.getDocument"); | |
| ASSERT_TRUE(result); | |
| int document_node_id = result->FindIntByDottedPath("root.nodeId").value(); | |
| // Get the <webview>'s nodeId (by searching for it using querySelector). | |
| auto params = base::DictValue() | |
| .Set("nodeId", document_node_id) | |
| .Set("selector", "webview"); | |
| result = SendCommandSync("DOM.querySelector", std::move(params)); | |
| ASSERT_TRUE(result); | |
| int web_view_node_id = result->FindInt("nodeId").value(); | |
| // Get the <webview> shadow tree (using its nodeId), and retrieve its | |
| // placeholder <iframe>'s frameId. The result from "DOM.describeNode" will | |
| // look something like: | |
| // {"node": { | |
| // ..., | |
| // "shadowRoots": [{ | |
| // ..., | |
| // "children": [{ | |
| // ..., | |
| // "frameId": "...." | |
| // }] | |
| // }] | |
| // } | |
| params = base::DictValue() | |
| .Set("nodeId", web_view_node_id) | |
| .Set("depth", 2) | |
| .Set("pierce", true); | |
| result = SendCommandSync("DOM.describeNode", std::move(params)); | |
| ASSERT_TRUE(result); | |
| auto* frame_id = result->FindListByDottedPath("node.shadowRoots") | |
| ->front() | |
| .GetDict() | |
| .FindList("children") | |
| ->front() | |
| .GetDict() | |
| .FindString("frameId"); | |
| ASSERT_TRUE(frame_id); | |
| // The frameId (i.e. the placeholder RemoteFrame's devtools_frame_token) | |
| // should match the devtools_frame_token of the guest's main frame. | |
| EXPECT_EQ( | |
| *frame_id, | |
| guest_view->GetGuestMainFrame()->GetDevToolsFrameToken().ToString()); | |
| } | |
| #endif // BUILDFLAG(IS_CHROMEOS) | |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS_CORE) | |
| class PrerenderDataSaverProtocolTest : public DevToolsProtocolTest { | |
| public: | |
| PrerenderDataSaverProtocolTest() | |
| : prerender_helper_(base::BindRepeating( | |
| &PrerenderDataSaverProtocolTest::GetActiveWebContents, | |
| base::Unretained(this))) {} | |
| protected: | |
| void SetUp() override { | |
| prerender_helper_.RegisterServerRequestMonitor(embedded_test_server()); | |
| data_saver::OverrideIsDataSaverEnabledForTesting(true); | |
| DevToolsProtocolTest::SetUp(); | |
| } | |
| content::test::PrerenderTestHelper* prerender_helper() { | |
| return &prerender_helper_; | |
| } | |
| void TearDown() override { | |
| data_saver::ResetIsDataSaverEnabledForTesting(); | |
| DevToolsProtocolTest::TearDown(); | |
| } | |
| content::WebContents* GetActiveWebContents() { | |
| return chrome_test_utils::GetActiveWebContents(this); | |
| } | |
| private: | |
| content::test::PrerenderTestHelper prerender_helper_; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(PrerenderDataSaverProtocolTest, | |
| CheckReportedDisabledByDataSaverPreloadingState) { | |
| base::HistogramTester histogram_tester; | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| Attach(); | |
| SendCommandSync("Runtime.enable"); | |
| const GURL initial_url = embedded_test_server()->GetURL("/empty.html"); | |
| const GURL prerendering_url = | |
| embedded_test_server()->GetURL("/empty.html?prerender"); | |
| content::test::PrerenderHostRegistryObserver observer( | |
| *GetActiveWebContents()); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), initial_url)); | |
| prerender_helper()->AddPrerenderAsync(prerendering_url); | |
| observer.WaitForTrigger(prerendering_url); | |
| content::PrerenderHostId host_id = | |
| prerender_helper()->GetHostForUrl(prerendering_url); | |
| EXPECT_TRUE(host_id.is_null()); | |
| SendCommandAsync("Preload.enable"); | |
| const base::DictValue result = | |
| WaitForNotification("Preload.preloadEnabledStateUpdated", true); | |
| EXPECT_THAT(result.FindBool("disabledByDataSaver"), true); | |
| histogram_tester.ExpectUniqueSample( | |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", | |
| /*PrerenderFinalStatus::kDataSaverEnabled=*/38, 1); | |
| } | |
| #if !BUILDFLAG(IS_ANDROID) | |
| class PrivacySandboxAttestationsOverrideTest | |
| : public InProcessBrowserTestMixinHostSupport<DevToolsProtocolTest> { | |
| public: | |
| PrivacySandboxAttestationsOverrideTest() = default; | |
| private: | |
| privacy_sandbox::PrivacySandboxAttestationsMixin | |
| privacy_sandbox_attestations_mixin_{&mixin_host_}; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(PrivacySandboxAttestationsOverrideTest, | |
| PrivacySandboxEnrollmentOverride) { | |
| Attach(); | |
| base::DictValue btm_params; | |
| const std::string attestation_url = "https://google.com"; | |
| btm_params.Set("url", attestation_url); | |
| SendCommand("Browser.addPrivacySandboxEnrollmentOverride", | |
| std::move(btm_params)); | |
| EXPECT_TRUE( | |
| privacy_sandbox::PrivacySandboxAttestations::GetInstance()->IsOverridden( | |
| net::SchemefulSite(GURL(attestation_url)))); | |
| } | |
| IN_PROC_BROWSER_TEST_F(PrivacySandboxAttestationsOverrideTest, | |
| PrivacySandboxEnrollmentOverrideInvalidUrl) { | |
| Attach(); | |
| base::DictValue btm_params; | |
| const std::string attestation_url = "this is a bad url"; | |
| btm_params.Set("url", attestation_url); | |
| SendCommand("Browser.addPrivacySandboxEnrollmentOverride", | |
| std::move(btm_params)); | |
| EXPECT_TRUE(error()); | |
| EXPECT_FALSE( | |
| privacy_sandbox::PrivacySandboxAttestations::GetInstance()->IsOverridden( | |
| net::SchemefulSite(GURL(attestation_url)))); | |
| } | |
| class DevToolsProtocolTest_PrivateVerificationTokens | |
| : public DevToolsProtocolTest { | |
| public: | |
| DevToolsProtocolTest_PrivateVerificationTokens() { | |
| scoped_feature_list_.InitAndEnableFeature( | |
| net::features::kEnablePrivateVerificationTokens); | |
| } | |
| void SetUpOnMainThread() override { | |
| DevToolsProtocolTest::SetUpOnMainThread(); | |
| pvt_service_ = PrivateVerificationTokensServiceFactory::GetForProfile( | |
| browser()->GetProfile()); | |
| ASSERT_NE(pvt_service_, nullptr); | |
| if (!pvt_service_->is_initialized()) { | |
| base::test::TestFuture<void> init_future; | |
| class Waiter : public PrivateVerificationTokensService::Observer { | |
| public: | |
| explicit Waiter(base::OnceClosure callback) | |
| : callback_(std::move(callback)) {} | |
| void OnInitializationComplete() override { std::move(callback_).Run(); } | |
| private: | |
| base::OnceClosure callback_; | |
| }; | |
| Waiter waiter(init_future.GetCallback()); | |
| base::ScopedObservation<PrivateVerificationTokensService, | |
| PrivateVerificationTokensService::Observer> | |
| observation(&waiter); | |
| observation.Observe(pvt_service_); | |
| ASSERT_TRUE(init_future.Wait()); | |
| } | |
| } | |
| void TearDownOnMainThread() override { | |
| pvt_service_ = nullptr; | |
| DevToolsProtocolTest::TearDownOnMainThread(); | |
| } | |
| void StoreTestTokens(const url::Origin& issuer, size_t count) { | |
| std::vector<private_verification_tokens::PrivateVerificationTokensToken> | |
| tokens; | |
| for (size_t i = 0; i < count; ++i) { | |
| tokens.emplace_back(issuer, std::vector<uint8_t>{1, 2, 3}, /*key_id=*/10, | |
| base::Time::Now() + base::Days(1), /*version=*/1); | |
| } | |
| base::test::TestFuture<void> store_future; | |
| pvt_service_->StoreTokens(std::move(tokens), store_future.GetCallback()); | |
| ASSERT_TRUE(store_future.Wait()); | |
| } | |
| protected: | |
| raw_ptr<PrivateVerificationTokensService> pvt_service_; | |
| private: | |
| base::test::ScopedFeatureList scoped_feature_list_; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_PrivateVerificationTokens, | |
| ManagePrivateVerificationTokens) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL("/empty.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| const url::Origin issuer_origin = | |
| url::Origin::Create(GURL("https://issuer.example.com")); | |
| // 1. Initial state: no tokens. | |
| { | |
| SendCommandSync("Storage.getPrivateVerificationTokens"); | |
| ASSERT_TRUE(result()); | |
| EXPECT_THAT(result()->FindList("tokens"), | |
| testing::Pointee(testing::IsEmpty())); | |
| } | |
| // 2. Store tokens and verify getPrivateVerificationTokens. | |
| StoreTestTokens(issuer_origin, 3); | |
| std::string first_token_id; | |
| { | |
| SendCommandSync("Storage.getPrivateVerificationTokens"); | |
| ASSERT_TRUE(result()); | |
| const base::ListValue* tokens_list = result()->FindList("tokens"); | |
| ASSERT_THAT(tokens_list, testing::Pointee(testing::SizeIs(3))); | |
| const base::DictValue* first_token = tokens_list->front().GetIfDict(); | |
| ASSERT_TRUE(first_token); | |
| const std::string* id = first_token->FindString("id"); | |
| ASSERT_TRUE(id); | |
| EXPECT_FALSE(id->empty()); | |
| first_token_id = *id; | |
| EXPECT_THAT(first_token->FindString("issuerOrigin"), | |
| testing::Pointee(testing::Eq(issuer_origin.Serialize()))); | |
| EXPECT_THAT(first_token->FindInt("keyId"), testing::Optional(10)); | |
| EXPECT_THAT(first_token->FindInt("version"), testing::Optional(1)); | |
| const std::string* token_b64 = first_token->FindString("token"); | |
| ASSERT_TRUE(token_b64); | |
| EXPECT_THAT(base::Base64Decode(*token_b64), | |
| testing::Optional(testing::ElementsAre(1, 2, 3))); | |
| EXPECT_TRUE(first_token->FindDouble("expiration").has_value()); | |
| EXPECT_TRUE(first_token->FindDouble("creationTime").has_value()); | |
| } | |
| // 3. Delete one token by ID. | |
| { | |
| base::DictValue params; | |
| params.Set("tokenId", first_token_id); | |
| SendCommandSync("Storage.deletePrivateVerificationToken", | |
| std::move(params)); | |
| ASSERT_TRUE(result()); | |
| // Verify count is now 2. | |
| SendCommandSync("Storage.getPrivateVerificationTokens"); | |
| ASSERT_TRUE(result()); | |
| EXPECT_THAT(result()->FindList("tokens"), | |
| testing::Pointee(testing::SizeIs(2))); | |
| } | |
| // 4. Clear all tokens for the issuer. | |
| { | |
| base::DictValue params; | |
| params.Set("issuerOrigin", issuer_origin.Serialize()); | |
| SendCommandSync("Storage.clearPrivateVerificationTokens", | |
| std::move(params)); | |
| ASSERT_TRUE(result()); | |
| // Verify tokens are cleared. | |
| SendCommandSync("Storage.getPrivateVerificationTokens"); | |
| ASSERT_TRUE(result()); | |
| EXPECT_THAT(result()->FindList("tokens"), | |
| testing::Pointee(testing::IsEmpty())); | |
| } | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_PrivateVerificationTokens, | |
| TrackPrivateVerificationTokens) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL("/empty.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| const url::Origin issuer_origin = | |
| url::Origin::Create(GURL("https://issuer.example.com")); | |
| // Enable tracking. | |
| { | |
| base::DictValue params; | |
| params.Set("enable", true); | |
| SendCommandSync("Storage.setPrivateVerificationTokensTracking", | |
| std::move(params)); | |
| ASSERT_TRUE(result()); | |
| } | |
| // 1. Storing tokens emits Storage.privateVerificationTokensUpdated. | |
| StoreTestTokens(issuer_origin, 2); | |
| WaitForNotification("Storage.privateVerificationTokensUpdated", | |
| /*allow_existing=*/true); | |
| SendCommandSync("Storage.getPrivateVerificationTokens"); | |
| ASSERT_TRUE(result()); | |
| const base::ListValue* tokens_list = result()->FindList("tokens"); | |
| ASSERT_THAT(tokens_list, testing::Pointee(testing::SizeIs(2))); | |
| const std::string* first_token_id = | |
| tokens_list->front().GetIfDict()->FindString("id"); | |
| ASSERT_TRUE(first_token_id); | |
| // 2. Deleting a single token emits Storage.privateVerificationTokensUpdated. | |
| { | |
| base::DictValue params; | |
| params.Set("tokenId", *first_token_id); | |
| SendCommandSync("Storage.deletePrivateVerificationToken", | |
| std::move(params)); | |
| ASSERT_TRUE(result()); | |
| WaitForNotification("Storage.privateVerificationTokensUpdated", | |
| /*allow_existing=*/true); | |
| } | |
| // 3. Clearing tokens emits Storage.privateVerificationTokensUpdated. | |
| { | |
| base::DictValue params; | |
| params.Set("issuerOrigin", issuer_origin.Serialize()); | |
| SendCommandSync("Storage.clearPrivateVerificationTokens", | |
| std::move(params)); | |
| ASSERT_TRUE(result()); | |
| WaitForNotification("Storage.privateVerificationTokensUpdated", | |
| /*allow_existing=*/true); | |
| } | |
| // 4. Disabling tracking stops emitting events. | |
| { | |
| base::DictValue params; | |
| params.Set("enable", false); | |
| SendCommandSync("Storage.setPrivateVerificationTokensTracking", | |
| std::move(params)); | |
| ASSERT_TRUE(result()); | |
| } | |
| StoreTestTokens(issuer_origin, 1); | |
| EXPECT_FALSE( | |
| HasExistingNotification("Storage.privateVerificationTokensUpdated")); | |
| } | |
| class DevToolsProtocolTest_PrivateVerificationTokensDisabled | |
| : public DevToolsProtocolTest { | |
| public: | |
| DevToolsProtocolTest_PrivateVerificationTokensDisabled() { | |
| scoped_feature_list_.InitAndDisableFeature( | |
| net::features::kEnablePrivateVerificationTokens); | |
| } | |
| private: | |
| base::test::ScopedFeatureList scoped_feature_list_; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_PrivateVerificationTokensDisabled, | |
| ServiceNotCreatedWhenFeatureDisabled) { | |
| ASSERT_TRUE(embedded_test_server()->Start()); | |
| const GURL url(embedded_test_server()->GetURL("/empty.html")); | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), url)); | |
| Attach(); | |
| EXPECT_EQ(PrivateVerificationTokensServiceFactory::GetForProfile( | |
| browser()->GetProfile()), | |
| nullptr); | |
| // 1. Storage.getPrivateVerificationTokens | |
| { | |
| SendCommandSync("Storage.getPrivateVerificationTokens"); | |
| EXPECT_FALSE(result()); | |
| ASSERT_TRUE(error()); | |
| EXPECT_THAT(error()->FindString("message"), | |
| testing::Pointee(testing::Eq( | |
| "Private Verification Tokens service is not available"))); | |
| } | |
| // 2. Storage.clearPrivateVerificationTokens | |
| { | |
| base::DictValue params; | |
| params.Set("issuerOrigin", "https://issuer.example.com"); | |
| SendCommandSync("Storage.clearPrivateVerificationTokens", | |
| std::move(params)); | |
| EXPECT_FALSE(result()); | |
| ASSERT_TRUE(error()); | |
| EXPECT_THAT(error()->FindString("message"), | |
| testing::Pointee(testing::Eq( | |
| "Private Verification Tokens service is not available"))); | |
| } | |
| // 3. Storage.deletePrivateVerificationToken | |
| { | |
| base::DictValue params; | |
| params.Set("tokenId", "1"); | |
| SendCommandSync("Storage.deletePrivateVerificationToken", | |
| std::move(params)); | |
| EXPECT_FALSE(result()); | |
| ASSERT_TRUE(error()); | |
| EXPECT_THAT(error()->FindString("message"), | |
| testing::Pointee(testing::Eq( | |
| "Private Verification Tokens service is not available"))); | |
| } | |
| // 4. Storage.setPrivateVerificationTokensTracking | |
| { | |
| base::DictValue params; | |
| params.Set("enable", true); | |
| SendCommandSync("Storage.setPrivateVerificationTokensTracking", | |
| std::move(params)); | |
| EXPECT_FALSE(result()); | |
| ASSERT_TRUE(error()); | |
| EXPECT_THAT(error()->FindString("message"), | |
| testing::Pointee(testing::Eq( | |
| "Private Verification Tokens service is not available"))); | |
| } | |
| EXPECT_EQ(PrivateVerificationTokensServiceFactory::GetForProfileIfExists( | |
| browser()->GetProfile()), | |
| nullptr); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| TargetCreateToFileUrlRequiresFileAccess) { | |
| AttachToBrowserTarget(); | |
| base::DictValue params; | |
| params.Set("url", "file:///some/path"); | |
| SendCommandSync("Target.createTarget", params.Clone()); | |
| EXPECT_TRUE(result()); | |
| DetachProtocolClient(); | |
| SetMayReadLocalFiles(false); | |
| AttachToBrowserTarget(); | |
| SendCommandSync("Target.createTarget", params.Clone()); | |
| ASSERT_TRUE(error()); | |
| EXPECT_THAT(error()->FindInt("code"), | |
| testing::Optional(-32000)); // SERVER_ERROR | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| TargetCreateToViewSourceFileUrlRequiresFileAccess) { | |
| AttachToBrowserTarget(); | |
| base::DictValue params; | |
| params.Set("url", "view-source:file:///some/path"); | |
| SendCommandSync("Target.createTarget", params.Clone()); | |
| EXPECT_TRUE(result()); | |
| DetachProtocolClient(); | |
| SetMayReadLocalFiles(false); | |
| AttachToBrowserTarget(); | |
| SendCommandSync("Target.createTarget", params.Clone()); | |
| ASSERT_TRUE(error()); | |
| EXPECT_THAT(error()->FindInt("code"), | |
| testing::Optional(-32000)); // SERVER_ERROR | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| HiddenTargetIsNotVisibleInTabStrip) { | |
| AttachToBrowserTarget(); | |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); | |
| SendCommandSync("Target.getTargets"); | |
| const size_t initial_count = result()->FindList("targetInfos")->size(); | |
| ASSERT_GT(initial_count, 0u); | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("hidden", true); | |
| SendCommandSync("Target.createTarget", std::move(params)); | |
| // The tab strip should not contain the new tab. | |
| EXPECT_EQ(browser()->tab_strip_model()->count(), 1); | |
| // CDP `Target.getTargets` result should contain the new target. | |
| SendCommandSync("Target.getTargets"); | |
| EXPECT_EQ(initial_count + 1, result()->FindList("targetInfos")->size()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| HiddenTargetClosesWhenSessionClosed) { | |
| AttachToBrowserTarget(); | |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); | |
| SendCommandSync("Target.getTargets"); | |
| const size_t initial_count = result()->FindList("targetInfos")->size(); | |
| ASSERT_GT(initial_count, 0u); | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("hidden", true); | |
| SendCommandSync("Target.createTarget", std::move(params)); | |
| // The tab strip should not contain the new tab. | |
| EXPECT_EQ(browser()->tab_strip_model()->count(), 1); | |
| // CDP `Target.getTargets` result should contain the new target. | |
| SendCommandSync("Target.getTargets"); | |
| EXPECT_EQ(initial_count + 1, result()->FindList("targetInfos")->size()); | |
| // Disconnect and connect to session. | |
| agent_host_->DetachClient(this); | |
| AttachToBrowserTarget(); | |
| // The hidden target should be closed. | |
| SendCommandSync("Target.getTargets"); | |
| EXPECT_EQ(initial_count, result()->FindList("targetInfos")->size()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, HiddenTargetCanBeClosed) { | |
| AttachToBrowserTarget(); | |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); | |
| SendCommandSync("Target.getTargets"); | |
| const size_t initial_count = result()->FindList("targetInfos")->size(); | |
| ASSERT_GT(initial_count, 0u); | |
| SendCommand("Target.setAutoAttach", base::DictValue() | |
| .Set("autoAttach", true) | |
| .Set("waitForDebuggerOnStart", false) | |
| .Set("flatten", true)); | |
| SendCommandSync( | |
| "Target.createTarget", | |
| base::DictValue().Set("url", "about:blank").Set("hidden", true)); | |
| const std::string targetId(*result()->FindStringByDottedPath("targetId")); | |
| // CDP `Target.getTargets` result should contain the new target. | |
| SendCommandSync("Target.getTargets"); | |
| EXPECT_EQ(initial_count + 1, result()->FindList("targetInfos")->size()); | |
| SendCommandSync("Target.closeTarget", | |
| base::DictValue().Set("targetId", targetId)); | |
| WaitForNotification("Target.detachedFromTarget", true); | |
| SendCommandSync("Target.getTargets"); | |
| EXPECT_EQ(initial_count, result()->FindList("targetInfos")->size()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, HiddenTargetIsTheLastOne) { | |
| AttachToBrowserTarget(); | |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); | |
| base::DictValue filter_params; | |
| filter_params.Set( | |
| "filter", | |
| base::ListValue().Append( | |
| base::DictValue().Set("type", "page").Set("exclude", false))); | |
| SendCommandSync("Target.getTargets", filter_params.Clone()); | |
| const base::ListValue* list = result()->FindList("targetInfos"); | |
| ASSERT_TRUE(list && list->size() == 1); | |
| const std::string targetId(*list->front().GetDict().FindString("targetId")); | |
| SendCommandSync( | |
| "Target.createTarget", | |
| base::DictValue().Set("url", "about:blank").Set("hidden", true)); | |
| SendCommandSync("Target.getTargets", std::move(filter_params)); | |
| EXPECT_EQ(1u, result()->FindList("targetInfos")->size()); | |
| ui_test_utils::BrowserDestroyedObserver observer; | |
| SendCommandSync("Target.closeTarget", | |
| base::DictValue().Set("targetId", targetId)); | |
| observer.Wait(); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| NotHiddenTargetIsVisibleInTabStrip) { | |
| AttachToBrowserTarget(); | |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); | |
| SendCommandSync("Target.getTargets"); | |
| const size_t initial_count = result()->FindList("targetInfos")->size(); | |
| ASSERT_GT(initial_count, 0u); | |
| base::DictValue params; | |
| params.Set("url", "about:blank"); | |
| params.Set("hidden", false); | |
| SendCommandSync("Target.createTarget", std::move(params)); | |
| // The tab strip should contain the new tab. | |
| EXPECT_EQ(browser()->tab_strip_model()->count(), 2); | |
| // CDP `Target.getTargets` result should contain the new target. | |
| SendCommandSync("Target.getTargets"); | |
| EXPECT_EQ(initial_count + 1, result()->FindList("targetInfos")->size()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| TargetGetTargetsIncludesTabEmbedderData) { | |
| AttachToBrowserTarget(); | |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); | |
| browser()->tab_strip_model()->SetTabPinned(0, true); | |
| // Open a background tab to cover active and inactive tab metadata. | |
| SendCommandSync("Target.createTarget", base::DictValue() | |
| .Set("url", "about:blank") | |
| .Set("forTab", true) | |
| .Set("background", true)); | |
| ASSERT_FALSE(error()); | |
| EXPECT_EQ(2, browser()->tab_strip_model()->count()); | |
| const tab_groups::TabGroupId group_id = | |
| browser()->tab_strip_model()->AddToNewGroup({1}); | |
| base::DictValue tab_filter; | |
| tab_filter.Set("type", "tab"); | |
| tab_filter.Set("exclude", false); | |
| base::ListValue target_filter = | |
| base::ListValue().Append(std::move(tab_filter)); | |
| base::DictValue get_targets_params; | |
| get_targets_params.Set("filter", std::move(target_filter)); | |
| SendCommandSync("Target.getTargets", std::move(get_targets_params)); | |
| ASSERT_TRUE(result()); | |
| const base::ListValue* target_infos = result()->FindList("targetInfos"); | |
| ASSERT_TRUE(target_infos); | |
| ASSERT_EQ(2u, target_infos->size()); | |
| const base::DictValue* first_tab_data = nullptr; | |
| const base::DictValue* second_tab_data = nullptr; | |
| std::string first_tab_target_id; | |
| std::string second_tab_target_id; | |
| for (const auto& target_info : *target_infos) { | |
| const base::DictValue& target_info_dict = target_info.GetDict(); | |
| EXPECT_EQ("tab", *target_info_dict.FindString("type")); | |
| const std::string* browser_context_id = | |
| target_info_dict.FindString("browserContextId"); | |
| ASSERT_TRUE(browser_context_id); | |
| EXPECT_EQ(browser()->GetProfile()->UniqueId(), *browser_context_id); | |
| const std::string* target_id = target_info_dict.FindString("targetId"); | |
| ASSERT_TRUE(target_id); | |
| const base::DictValue* embedder_data = | |
| target_info_dict.FindDict("embedderData"); | |
| ASSERT_TRUE(embedder_data); | |
| std::optional<int> tab_strip_index = | |
| embedder_data->FindInt("tabStripIndex"); | |
| ASSERT_TRUE(tab_strip_index); | |
| if (*tab_strip_index == 0) { | |
| first_tab_data = embedder_data; | |
| first_tab_target_id = *target_id; | |
| } else if (*tab_strip_index == 1) { | |
| second_tab_data = embedder_data; | |
| second_tab_target_id = *target_id; | |
| } | |
| } | |
| ASSERT_TRUE(first_tab_data); | |
| EXPECT_EQ(true, *first_tab_data->FindBool("tabActive")); | |
| EXPECT_EQ(true, *first_tab_data->FindBool("tabPinned")); | |
| ASSERT_TRUE(second_tab_data); | |
| EXPECT_EQ(false, *second_tab_data->FindBool("tabActive")); | |
| EXPECT_EQ(false, *second_tab_data->FindBool("tabPinned")); | |
| EXPECT_EQ(group_id.ToString(), *second_tab_data->FindString("tabGroupId")); | |
| ASSERT_FALSE(first_tab_target_id.empty()); | |
| SendCommandSync("Target.getTargetInfo", | |
| base::DictValue().Set("targetId", first_tab_target_id)); | |
| ASSERT_TRUE(result()); | |
| const base::Value* first_target_info_embedder_data = | |
| result()->FindByDottedPath("targetInfo.embedderData"); | |
| ASSERT_TRUE(first_target_info_embedder_data); | |
| const base::DictValue& first_get_target_info_data = | |
| first_target_info_embedder_data->GetDict(); | |
| EXPECT_EQ(0, *first_get_target_info_data.FindInt("tabStripIndex")); | |
| EXPECT_EQ(true, *first_get_target_info_data.FindBool("tabActive")); | |
| EXPECT_EQ(true, *first_get_target_info_data.FindBool("tabPinned")); | |
| ASSERT_FALSE(second_tab_target_id.empty()); | |
| SendCommandSync("Target.getTargetInfo", | |
| base::DictValue().Set("targetId", second_tab_target_id)); | |
| ASSERT_TRUE(result()); | |
| const base::Value* second_target_info_embedder_data = | |
| result()->FindByDottedPath("targetInfo.embedderData"); | |
| ASSERT_TRUE(second_target_info_embedder_data); | |
| const base::DictValue& second_get_target_info_data = | |
| second_target_info_embedder_data->GetDict(); | |
| EXPECT_EQ(1, *second_get_target_info_data.FindInt("tabStripIndex")); | |
| EXPECT_EQ(false, *second_get_target_info_data.FindBool("tabActive")); | |
| EXPECT_EQ(false, *second_get_target_info_data.FindBool("tabPinned")); | |
| EXPECT_EQ(group_id.ToString(), | |
| *second_get_target_info_data.FindString("tabGroupId")); | |
| } | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| #if !BUILDFLAG(IS_ANDROID) | |
| class DevToolsProtocolTest_OpensDevTools : public DevToolsProtocolTest { | |
| public: | |
| std::string GetCurrentPageTargetId() { | |
| base::DictValue params; | |
| params.Set( | |
| "filter", | |
| base::ListValue().Append( | |
| base::DictValue().Set("type", "page").Set("exclude", false))); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.getTargets", std::move(params)); | |
| const base::ListValue* list = result->FindList("targetInfos"); | |
| EXPECT_EQ(1u, list->size()); | |
| return *list->front().GetDict().FindString("targetId"); | |
| } | |
| const base::DictValue* OpenDevToolsForCurrentPageTarget( | |
| std::optional<std::string> panel_id = std::nullopt) { | |
| const std::string target_id = GetCurrentPageTargetId(); | |
| base::DictValue params; | |
| params.Set("targetId", target_id); | |
| if (panel_id.has_value()) { | |
| params.Set("panelId", *panel_id); | |
| } | |
| return SendCommandSync("Target.openDevTools", std::move(params)); | |
| } | |
| base::DictValue FindDevToolsTarget() { | |
| // CDP `Target.getTargets` result should contain the new DevTools target. | |
| const base::DictValue* result = SendCommandSync("Target.getTargets"); | |
| base::DictValue devtools_target; | |
| for (const auto& target : *result->FindList("targetInfos")) { | |
| if (*target.GetDict().FindString("type") == "other") { | |
| devtools_target = target.Clone().TakeDict(); | |
| break; | |
| } | |
| } | |
| EXPECT_FALSE(devtools_target.empty()); | |
| return devtools_target; | |
| } | |
| }; | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_OpensDevTools, | |
| FindsDevToolsTarget) { | |
| AttachToBrowserTarget(); | |
| const std::string target_id = GetCurrentPageTargetId(); | |
| // 1. Check there is no devtools target initially. | |
| base::DictValue params; | |
| params.Set("targetId", target_id); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.getDevToolsTarget", params.Clone()); | |
| EXPECT_FALSE(error()); | |
| EXPECT_FALSE(result->Find("targetId")); | |
| // 2. Open DevTools. | |
| OpenDevToolsForCurrentPageTarget(); | |
| EXPECT_FALSE(error()); | |
| // 3. Check that devtools target is found. | |
| const base::DictValue* devtools_target_result = | |
| SendCommandSync("Target.getDevToolsTarget", std::move(params)); | |
| EXPECT_FALSE(error()); | |
| const std::string* devtools_target_id = | |
| devtools_target_result->FindString("targetId"); | |
| EXPECT_TRUE(devtools_target_id); | |
| EXPECT_FALSE(devtools_target_id->empty()); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_OpensDevTools, | |
| OpensDevTools_FailsForNonBrowserTargetSession) { | |
| AttachToTabTarget(web_contents()); | |
| OpenDevToolsForCurrentPageTarget(); | |
| EXPECT_EQ(*error()->FindString("message"), "Not allowed"); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, | |
| OpensDevTools_FailsForNonExistentTarget) { | |
| AttachToBrowserTarget(); | |
| base::DictValue params; | |
| params.Set("targetId", "<NonExistentTargetId>"); | |
| SendCommandSync("Target.openDevTools", std::move(params)); | |
| EXPECT_EQ(*error()->FindString("message"), "No target with given id found"); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_OpensDevTools, | |
| OpensDevTools_OpensForPageTarget) { | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = OpenDevToolsForCurrentPageTarget(); | |
| const std::string devtools_target_id(*result->FindString("targetId")); | |
| const base::DictValue devtools_target = FindDevToolsTarget(); | |
| EXPECT_EQ(devtools_target_id, *devtools_target.FindString("targetId")); | |
| const std::string url = *devtools_target.FindString("url"); | |
| EXPECT_TRUE(url.find("panel") == std::string::npos); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, OpensDevTools_OpensForTabTarget) { | |
| AttachToBrowserTarget(); | |
| base::DictValue tabFilter = base::DictValue(); | |
| tabFilter.Set("type", "tab"); | |
| tabFilter.Set("exclude", false); | |
| base::ListValue targetFilter = base::ListValue().Append(std::move(tabFilter)); | |
| base::DictValue getTargetParams = base::DictValue(); | |
| getTargetParams.Set("filter", std::move(targetFilter)); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.getTargets", std::move(getTargetParams)); | |
| base::DictValue tab_target; | |
| for (const auto& target : *result->FindList("targetInfos")) { | |
| if (*target.GetDict().FindString("type") == "tab") { | |
| tab_target = target.Clone().TakeDict(); | |
| break; | |
| } | |
| } | |
| base::DictValue params; | |
| params.Set("targetId", *tab_target.FindString("targetId")); | |
| result = SendCommandSync("Target.openDevTools", std::move(params)); | |
| const std::string devtools_target_id(*result->FindString("targetId")); | |
| // CDP `Target.getTargets` result should contain the new DevTools target. | |
| result = SendCommandSync("Target.getTargets"); | |
| base::DictValue devtools_target; | |
| for (const auto& target : *result->FindList("targetInfos")) { | |
| if (*target.GetDict().FindString("type") == "other") { | |
| devtools_target = target.Clone().TakeDict(); | |
| break; | |
| } | |
| } | |
| EXPECT_FALSE(devtools_target.empty()); | |
| EXPECT_EQ(devtools_target_id, *devtools_target.FindString("targetId")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, OpensDevTools_OpensUndocked) { | |
| set_agent_host_can_close(); | |
| base::DictValue prefs; | |
| prefs.Set("isUnderTest", "true"); | |
| prefs.Set("currentDockState", "undocked"); | |
| browser()->GetProfile()->GetPrefs()->SetDict(prefs::kDevToolsPreferences, | |
| std::move(prefs)); | |
| AttachToBrowserTarget(); | |
| base::DictValue tabFilter = base::DictValue(); | |
| tabFilter.Set("type", "tab"); | |
| tabFilter.Set("exclude", false); | |
| base::ListValue targetFilter = base::ListValue().Append(std::move(tabFilter)); | |
| base::DictValue getTargetParams = base::DictValue(); | |
| getTargetParams.Set("filter", std::move(targetFilter)); | |
| const base::DictValue* result = | |
| SendCommandSync("Target.getTargets", std::move(getTargetParams)); | |
| base::DictValue tab_target; | |
| for (const auto& target : *result->FindList("targetInfos")) { | |
| if (*target.GetDict().FindString("type") == "tab") { | |
| tab_target = target.Clone().TakeDict(); | |
| break; | |
| } | |
| } | |
| base::DictValue params; | |
| params.Set("targetId", *tab_target.FindString("targetId")); | |
| result = SendCommandSync("Target.openDevTools", std::move(params)); | |
| const std::string devtools_target_id(*result->FindString("targetId")); | |
| // CDP `Target.getTargets` result should contain the new DevTools target. | |
| result = SendCommandSync("Target.getTargets"); | |
| base::DictValue devtools_target; | |
| for (const auto& target : *result->FindList("targetInfos")) { | |
| if (*target.GetDict().FindString("type") == "other") { | |
| devtools_target = target.Clone().TakeDict(); | |
| break; | |
| } | |
| } | |
| EXPECT_FALSE(devtools_target.empty()); | |
| EXPECT_EQ(devtools_target_id, *devtools_target.FindString("targetId")); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_OpensDevTools, | |
| OpensDevTools_OpensInResourcesPanel) { | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = OpenDevToolsForCurrentPageTarget("resources"); | |
| const std::string devtools_target_id(*result->FindString("targetId")); | |
| const base::DictValue devtools_target = FindDevToolsTarget(); | |
| EXPECT_EQ(devtools_target_id, *devtools_target.FindString("targetId")); | |
| const std::string url = *devtools_target.FindString("url"); | |
| EXPECT_TRUE(url.find("panel=resources") != std::string::npos); | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest_OpensDevTools, | |
| OpensDevTools_OpensInUnsuportedPanel) { | |
| AttachToBrowserTarget(); | |
| const base::DictValue* result = | |
| OpenDevToolsForCurrentPageTarget("unsupported"); | |
| const std::string devtools_target_id(*result->FindString("targetId")); | |
| const base::DictValue devtools_target = FindDevToolsTarget(); | |
| EXPECT_EQ(devtools_target_id, *devtools_target.FindString("targetId")); | |
| const std::string url = *devtools_target.FindString("url"); | |
| EXPECT_TRUE(url.find("panel") == std::string::npos); | |
| } | |
| class IsolatedWebMulticastSocketsTest | |
| : public web_app::IsolatedWebAppBrowserTestHarness, | |
| public content::DevToolsAgentHostClient { | |
| public: | |
| content::RenderFrameHost* InstallAndOpenIsolatedWebApp() { | |
| using PermissionsPolicyFeature = network::mojom::PermissionsPolicyFeature; | |
| auto manifest_builder = | |
| web_app::ManifestBuilder() | |
| .AddPermissionsPolicyWildcard( | |
| PermissionsPolicyFeature::kDirectSockets) | |
| .AddPermissionsPolicyWildcard( | |
| PermissionsPolicyFeature::kLocalNetwork) | |
| .AddPermissionsPolicyWildcard( | |
| PermissionsPolicyFeature::kLoopbackNetwork) | |
| .AddPermissionsPolicyWildcard( | |
| PermissionsPolicyFeature::kMulticastInDirectSockets); | |
| auto app = web_app::IsolatedWebAppBuilder(std::move(manifest_builder)) | |
| .BuildBundle(); | |
| web_app::IsolatedWebAppUrlInfo url_info = app->Install(profile()).value(); | |
| return OpenApp(url_info.app_id()); | |
| } | |
| void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, | |
| base::span<const uint8_t> message) override { | |
| std::string_view message_sv(reinterpret_cast<const char*>(message.data()), | |
| message.size()); | |
| std::optional<base::Value> parsed = | |
| base::JSONReader::Read(message_sv, base::JSON_ALLOW_TRAILING_COMMAS); | |
| if (!parsed || !parsed->is_dict()) { | |
| return; | |
| } | |
| base::DictValue command = std::move(parsed->GetDict()); | |
| const std::string* method = command.FindString("method"); | |
| if (!method) { | |
| return; | |
| } | |
| notifications_[*method].push_back(std::move(command)); | |
| if (wait_for_notification_run_loop_ && method && | |
| *method == wait_for_method_) { | |
| wait_for_notification_run_loop_->Quit(); | |
| } | |
| } | |
| void AgentHostClosed(content::DevToolsAgentHost* agent_host) override { | |
| agent_host_ = nullptr; | |
| } | |
| protected: | |
| void Detach() { | |
| if (agent_host_) { | |
| agent_host_->DetachClient(this); | |
| agent_host_ = nullptr; | |
| } | |
| } | |
| void AttachToFrame(content::RenderFrameHost* frame_host) { | |
| Detach(); | |
| agent_host_ = content::DevToolsAgentHost::GetOrCreateFor( | |
| content::WebContents::FromRenderFrameHost(frame_host)); | |
| agent_host_->AttachClient(this); | |
| } | |
| void sendCommand(const std::string& method, base::DictValue params) { | |
| base::DictValue command; | |
| command.Set("id", ++last_id_); | |
| command.Set("method", method); | |
| if (!params.empty()) { | |
| command.Set("params", std::move(params)); | |
| } | |
| std::string json_command; | |
| base::JSONWriter::Write(command, &json_command); | |
| agent_host_->DispatchProtocolMessage( | |
| this, std::vector<uint8_t>(json_command.begin(), json_command.end())); | |
| } | |
| base::DictValue WaitForNotification(const std::string& method, | |
| bool allow_existing = false) { | |
| if (allow_existing) { | |
| if (auto it = notifications_.find(method); | |
| it != notifications_.end() && !it->second.empty()) { | |
| base::DictValue notification = std::move(it->second.front()); | |
| it->second.erase(it->second.begin()); | |
| return notification; | |
| } | |
| } | |
| wait_for_notification_run_loop_ = std::make_unique<base::RunLoop>(); | |
| wait_for_method_ = method; | |
| wait_for_notification_run_loop_->Run(); | |
| wait_for_notification_run_loop_.reset(); | |
| wait_for_method_ = ""; | |
| auto it = notifications_.find(method); | |
| EXPECT_TRUE(it != notifications_.end()); | |
| EXPECT_TRUE(!it->second.empty()); | |
| base::DictValue notification = std::move(it->second.front()); | |
| it->second.erase(it->second.begin()); | |
| return notification; | |
| } | |
| scoped_refptr<content::DevToolsAgentHost> agent_host_; | |
| private: | |
| std::map<std::string, std::vector<base::DictValue>> notifications_; | |
| std::unique_ptr<base::RunLoop> wait_for_notification_run_loop_; | |
| std::string wait_for_method_; | |
| int last_id_ = 0; | |
| }; | |
| IN_PROC_BROWSER_TEST_F(IsolatedWebMulticastSocketsTest, | |
| DirectSocketsUDPJoinLeaveMulticastEvents) { | |
| content::RenderFrameHost* iwa_frame = InstallAndOpenIsolatedWebApp(); | |
| auto* web_contents = content::WebContents::FromRenderFrameHost(iwa_frame); | |
| auto* manager = | |
| permissions::PermissionRequestManager::FromWebContents(web_contents); | |
| manager->set_auto_response_for_test( | |
| permissions::PermissionRequestManager::ACCEPT_ALL); | |
| ASSERT_TRUE(iwa_frame); | |
| AttachToFrame(iwa_frame); | |
| ASSERT_TRUE(agent_host_); | |
| sendCommand("Network.enable", base::DictValue()); | |
| const std::string multicast_script = | |
| R"JS( | |
| (async () => { | |
| const socket = new UDPSocket({ localAddress: "0.0.0.0" }); | |
| const { multicastController } = await socket.opened; | |
| if (!multicastController) { | |
| throw new Error("No multicastController"); | |
| } | |
| await multicastController.joinGroup("237.132.100.17"); | |
| await multicastController.leaveGroup("237.132.100.17"); | |
| await socket.close(); | |
| })() | |
| )JS"; | |
| content::EvalJsResult result = content::EvalJs(iwa_frame, multicast_script); | |
| ASSERT_TRUE(result.is_ok()) | |
| << "Connect script failed: " << result.ExtractError(); | |
| // Check for Join event | |
| base::DictValue joined_params = | |
| WaitForNotification("Network.directUDPSocketJoinedMulticastGroup", true); | |
| EXPECT_EQ(*joined_params.FindStringByDottedPath("params.IPAddress"), | |
| "237.132.100.17"); | |
| // Check for Leave event | |
| base::DictValue left_params = | |
| WaitForNotification("Network.directUDPSocketLeftMulticastGroup", true); | |
| EXPECT_EQ(*left_params.FindStringByDottedPath("params.IPAddress"), | |
| "237.132.100.17"); | |
| Detach(); | |
| agent_host_ = nullptr; | |
| } | |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, GetAnnotatedPageContent) { | |
| constexpr char kPageUrl[] = | |
| "data:text/html,<body><h1>Hello APC</h1>" | |
| "<p>Test paragraph</p></body>"; | |
| ASSERT_TRUE(content::NavigateToURL( | |
| chrome_test_utils::GetActiveWebContents(this), GURL(kPageUrl))); | |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents())); | |
| Attach(); | |
| base::DictValue params; | |
| params.Set("includeActionableInformation", true); | |
| const base::DictValue* result = | |
| SendCommandSync("Page.getAnnotatedPageContent", std::move(params)); | |
| ASSERT_TRUE(result); | |
| // Page.pdl defines "content" as a binary parameter. It contains the | |
| // base64-encoded serialized AnnotatedPageContent protobuf. | |
| const std::string* content_base64 = result->FindString("content"); | |
| ASSERT_TRUE(content_base64); | |
| EXPECT_FALSE(content_base64->empty()); | |
| std::string decoded_proto; | |
| ASSERT_TRUE(base::Base64Decode(*content_base64, &decoded_proto)); | |
| optimization_guide::proto::AnnotatedPageContent apc; | |
| ASSERT_TRUE(apc.ParseFromString(decoded_proto)); | |
| EXPECT_TRUE(apc.has_root_node()); | |
| } | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| } // namespace |