| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| |
| #include "chrome/browser/chrome_content_browser_client.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/path_service.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h" |
| #include "chrome/browser/enterprise/connectors/analysis/clipboard_request_handler.h" |
| #include "chrome/browser/enterprise/connectors/test/fake_clipboard_request_handler.h" |
| #include "chrome/browser/external_protocol/external_protocol_handler.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/search/instant_service.h" |
| #include "chrome/browser/search/instant_service_factory.h" |
| #include "chrome/browser/search/search.h" |
| #include "chrome/browser/themes/theme_service.h" |
| #include "chrome/browser/themes/theme_service_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/search/instant_test_base.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/common/features.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/custom_handlers/protocol_handler.h" |
| #include "components/custom_handlers/protocol_handler_registry.h" |
| #include "components/enterprise/buildflags/buildflags.h" |
| #include "components/enterprise/data_controls/core/browser/test_utils.h" |
| #include "components/guest_view/browser/guest_view_base.h" |
| #include "components/guest_view/browser/guest_view_manager.h" |
| #include "components/guest_view/browser/guest_view_manager_delegate.h" |
| #include "components/guest_view/browser/test_guest_view_manager.h" |
| #include "components/network_session_configurator/common/network_switches.h" |
| #include "components/policy/core/common/cloud/cloud_policy_constants.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h" |
| #include "components/privacy_sandbox/privacy_sandbox_attestations/scoped_privacy_sandbox_attestations.h" |
| #include "components/privacy_sandbox/privacy_sandbox_features.h" |
| #include "components/safe_browsing/buildflags.h" |
| #include "components/site_isolation/site_isolation_policy.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/url_utils.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/fenced_frame_test_util.h" |
| #include "content/public/test/frame_test_utils.h" |
| #include "content/public/test/test_devtools_protocol_client.h" |
| #include "content/public/test/test_frame_navigation_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/url_loader_interceptor.h" |
| #include "extensions/browser/api/extensions_api_client.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_status_code.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "services/network/public/cpp/url_loader_factory_builder.h" |
| #include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom.h" |
| #include "ui/base/clipboard/clipboard_monitor.h" |
| #include "ui/base/data_transfer_policy/data_transfer_endpoint.h" |
| #include "ui/color/color_provider.h" |
| #include "ui/color/color_provider_key.h" |
| #include "ui/color/color_provider_manager.h" |
| #include "ui/color/color_provider_source.h" |
| #include "ui/color/color_provider_utils.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/native_theme/test_native_theme.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension_urls.h" |
| #include "url/url_constants.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS_CORE) |
| #include "extensions/common/extension_features.h" |
| #endif |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "chrome/test/base/launchservices_utils_mac.h" |
| #endif |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| #include "third_party/blink/public/common/features.h" |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/test/bind.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "chrome/browser/enterprise/connectors/connectors_service.h" |
| #include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h" // nogncheck |
| #include "chrome/browser/enterprise/connectors/test/fake_content_analysis_delegate.h" // nogncheck |
| #include "ui/base/clipboard/clipboard_format_type.h" |
| |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| #include "chrome/browser/enterprise/connectors/test/fake_content_analysis_sdk_manager.h" // nogncheck |
| #endif // BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| |
| #endif // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| #include "chrome/browser/glic/test_support/glic_test_environment.h" |
| #include "chrome/browser/glic/test_support/glic_test_util.h" |
| #endif |
| |
| namespace { |
| |
| std::vector<uint8_t> StringToVector(const std::string& str) { |
| return std::vector<uint8_t>(str.begin(), str.end()); |
| } |
| |
| // Use a test class with SetUpCommandLine to ensure the flag is sent to the |
| // first renderer process. |
| class ChromeContentBrowserClientBrowserTest : public InProcessBrowserTest { |
| public: |
| ChromeContentBrowserClientBrowserTest() = default; |
| |
| ChromeContentBrowserClientBrowserTest( |
| const ChromeContentBrowserClientBrowserTest&) = delete; |
| ChromeContentBrowserClientBrowserTest& operator=( |
| const ChromeContentBrowserClientBrowserTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| content::IsolateAllSitesForTesting(command_line); |
| } |
| }; |
| |
| // Test that a basic navigation works in --site-per-process mode. This prevents |
| // regressions when that mode calls out into the ChromeContentBrowserClient, |
| // such as http://crbug.com/164223. |
| IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientBrowserTest, |
| SitePerProcessNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url(embedded_test_server()->GetURL("/title1.html")); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| content::NavigationEntry* entry = browser() |
| ->tab_strip_model() |
| ->GetWebContentsAt(0) |
| ->GetController() |
| .GetLastCommittedEntry(); |
| |
| ASSERT_TRUE(entry != nullptr); |
| EXPECT_EQ(url, entry->GetURL()); |
| EXPECT_EQ(url, entry->GetVirtualURL()); |
| } |
| |
| // Helper class to mark "https://ntp.com/" as an isolated origin. |
| class IsolatedOriginNTPBrowserTest : public InProcessBrowserTest, |
| public InstantTestBase { |
| public: |
| IsolatedOriginNTPBrowserTest() = default; |
| |
| IsolatedOriginNTPBrowserTest(const IsolatedOriginNTPBrowserTest&) = delete; |
| IsolatedOriginNTPBrowserTest& operator=(const IsolatedOriginNTPBrowserTest&) = |
| delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ASSERT_TRUE(https_test_server().InitializeAndListen()); |
| |
| // Mark ntp.com (with an appropriate port from the test server) as an |
| // isolated origin. |
| GURL isolated_url(https_test_server().GetURL("ntp.com", "/")); |
| command_line->AppendSwitchASCII(switches::kIsolateOrigins, |
| isolated_url.spec()); |
| command_line->AppendSwitch(switches::kIgnoreCertificateErrors); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| https_test_server().StartAcceptingConnections(); |
| } |
| }; |
| |
| // Verifies that when the remote NTP URL has an origin which is also marked as |
| // an isolated origin (i.e., requiring a dedicated process), the NTP URL |
| // still loads successfully, and the resulting process is marked as an Instant |
| // process. See https://crbug.com/755595. |
| IN_PROC_BROWSER_TEST_F(IsolatedOriginNTPBrowserTest, |
| IsolatedOriginDoesNotInterfereWithNTP) { |
| GURL base_url = |
| https_test_server().GetURL("ntp.com", "/instant_extended.html"); |
| GURL ntp_url = |
| https_test_server().GetURL("ntp.com", "/instant_extended_ntp.html"); |
| SetupInstant(browser()->profile(), base_url, ntp_url); |
| |
| // Sanity check that a SiteInstance for a generic ntp.com URL requires a |
| // dedicated process. |
| content::BrowserContext* context = browser()->profile(); |
| GURL isolated_url(https_test_server().GetURL("ntp.com", "/title1.html")); |
| scoped_refptr<content::SiteInstance> site_instance = |
| content::SiteInstance::CreateForURL(context, isolated_url); |
| EXPECT_TRUE(site_instance->RequiresDedicatedProcess()); |
| // Verify the isolated origin does not receive an NTP site URL scheme. |
| EXPECT_FALSE( |
| site_instance->GetSiteURL().SchemeIs(chrome::kChromeSearchScheme)); |
| |
| // The site URL for the NTP URL should resolve to a chrome-search:// URL via |
| // GetEffectiveURL(), even if the NTP URL matches an isolated origin. |
| scoped_refptr<content::SiteInstance> ntp_site_instance = |
| content::SiteInstance::CreateForURL(context, ntp_url); |
| EXPECT_TRUE( |
| ntp_site_instance->GetSiteURL().SchemeIs(chrome::kChromeSearchScheme)); |
| |
| // Navigate to the NTP URL and verify that the resulting process is marked as |
| // an Instant process. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), ntp_url)); |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| InstantService* instant_service = |
| InstantServiceFactory::GetForProfile(browser()->profile()); |
| EXPECT_TRUE(instant_service->IsInstantProcess( |
| contents->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID())); |
| EXPECT_EQ(contents->GetPrimaryMainFrame()->GetSiteInstance()->GetSiteURL(), |
| ntp_site_instance->GetSiteURL()); |
| |
| // Navigating to a non-NTP URL on ntp.com should not result in an Instant |
| // process. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), isolated_url)); |
| EXPECT_FALSE(instant_service->IsInstantProcess( |
| contents->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID())); |
| EXPECT_EQ(contents->GetPrimaryMainFrame()->GetSiteInstance()->GetSiteURL(), |
| site_instance->GetSiteURL()); |
| } |
| |
| // Helper class to test window creation from NTP. |
| class OpenWindowFromNTPBrowserTest : public InProcessBrowserTest, |
| public InstantTestBase { |
| public: |
| OpenWindowFromNTPBrowserTest() = default; |
| |
| OpenWindowFromNTPBrowserTest(const OpenWindowFromNTPBrowserTest&) = delete; |
| OpenWindowFromNTPBrowserTest& operator=(const OpenWindowFromNTPBrowserTest&) = |
| delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kIgnoreCertificateErrors); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(https_test_server().InitializeAndListen()); |
| https_test_server().StartAcceptingConnections(); |
| } |
| }; |
| |
| // Test checks that navigations from NTP tab to URLs with same host as NTP but |
| // different path do not reuse NTP SiteInstance. See https://crbug.com/859062 |
| // for details. |
| IN_PROC_BROWSER_TEST_F(OpenWindowFromNTPBrowserTest, |
| TransferFromNTPCreateNewTab) { |
| GURL search_url = |
| https_test_server().GetURL("ntp.com", "/instant_extended.html"); |
| GURL ntp_url = |
| https_test_server().GetURL("ntp.com", "/instant_extended_ntp.html"); |
| SetupInstant(browser()->profile(), search_url, ntp_url); |
| |
| // Navigate to the NTP URL and verify that the resulting process is marked as |
| // an Instant process. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), ntp_url)); |
| content::WebContents* ntp_tab = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| InstantService* instant_service = |
| InstantServiceFactory::GetForProfile(browser()->profile()); |
| EXPECT_TRUE(instant_service->IsInstantProcess( |
| ntp_tab->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID())); |
| |
| // Execute script that creates new window from ntp tab with |
| // ntp.com/title1.html as target url. Host is same as remote-ntp host, yet |
| // path is different. |
| GURL generic_url(https_test_server().GetURL("ntp.com", "/title1.html")); |
| content::TestNavigationObserver opened_tab_observer(nullptr); |
| opened_tab_observer.StartWatchingNewWebContents(); |
| EXPECT_TRUE(ExecJs(ntp_tab, "window.open('" + generic_url.spec() + "');")); |
| opened_tab_observer.Wait(); |
| ASSERT_EQ(2, browser()->tab_strip_model()->count()); |
| |
| content::WebContents* opened_tab = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| // Wait until newly opened tab is fully loaded. |
| EXPECT_TRUE(WaitForLoadStop(opened_tab)); |
| |
| EXPECT_NE(opened_tab, ntp_tab); |
| EXPECT_EQ(generic_url, opened_tab->GetLastCommittedURL()); |
| // New created tab should not reside in an Instant process. |
| EXPECT_FALSE(instant_service->IsInstantProcess( |
| opened_tab->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID())); |
| } |
| |
| // Test for the state of Forced Colors Mode for a given WebContents across |
| // various scenarios. |
| class ForcedColorsTest : public testing::WithParamInterface<bool>, |
| public InProcessBrowserTest { |
| protected: |
| ForcedColorsTest() : theme_client_(&test_theme_) {} |
| |
| ~ForcedColorsTest() override { |
| CHECK_EQ(&theme_client_, SetBrowserClientForTesting(original_client_)); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, |
| "ForcedColors"); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| original_client_ = SetBrowserClientForTesting(&theme_client_); |
| } |
| |
| protected: |
| ui::TestNativeTheme test_theme_; |
| |
| private: |
| raw_ptr<content::ContentBrowserClient> original_client_ = nullptr; |
| |
| class ChromeContentBrowserClientWithWebTheme |
| : public ChromeContentBrowserClient { |
| public: |
| explicit ChromeContentBrowserClientWithWebTheme( |
| const ui::NativeTheme* theme) |
| : theme_(theme) {} |
| |
| protected: |
| const ui::NativeTheme* GetWebTheme() const override { return theme_; } |
| |
| private: |
| const raw_ptr<const ui::NativeTheme> theme_; |
| }; |
| |
| ChromeContentBrowserClientWithWebTheme theme_client_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(ForcedColorsTest, ForcedColors) { |
| test_theme_.set_forced_colors(GetParam()); |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), ui_test_utils::GetTestUrl( |
| base::FilePath(base::FilePath::kCurrentDirectory), |
| base::FilePath(FILE_PATH_LITERAL("forced-colors.html"))))); |
| std::u16string tab_title; |
| const char* expected = test_theme_.InForcedColorsMode() ? "active" : "none"; |
| ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &tab_title)); |
| EXPECT_EQ(base::ASCIIToUTF16(expected), tab_title); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ForcedColorsTest, ForcedColorsWithBlockList) { |
| test_theme_.set_forced_colors(GetParam()); |
| |
| const char* url = "https://foo.com"; |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(url))); |
| |
| // Add url to the page colors block list. |
| base::Value::List list; |
| list.Append(url); |
| Profile* profile = browser()->profile(); |
| profile->GetPrefs()->SetList(prefs::kPageColorsBlockList, list.Clone()); |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| |
| // Forced colors should be `none` when a site is added to the block list. |
| EXPECT_EQ(true, EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| base::StringPrintf( |
| "window.matchMedia('(forced-colors: %s)').matches", |
| "none"))); |
| |
| // Remove url from the page colors block list. |
| list.EraseValue(base::Value(url)); |
| profile->GetPrefs()->SetList(prefs::kPageColorsBlockList, list.Clone()); |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| |
| // Forced colors should respect the NativeTheme when a site is removed from |
| // the block list. |
| const char* expected = test_theme_.InForcedColorsMode() ? "active" : "none"; |
| EXPECT_EQ(true, EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| base::StringPrintf( |
| "window.matchMedia('(forced-colors: %s)').matches", |
| expected))); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, ForcedColorsTest, testing::Bool()); |
| |
| // Helper class to test the Page colors feature. Page colors is a feature that |
| // simulates Forced colors mode via a browser setting. |
| class PageColorsBrowserClientTest : public InProcessBrowserTest { |
| public: |
| PageColorsBrowserClientTest() = default; |
| |
| PageColorsBrowserClientTest(const PageColorsBrowserClientTest&) = delete; |
| PageColorsBrowserClientTest& operator=(const PageColorsBrowserClientTest&) = |
| delete; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PageColorsBrowserClientTest, |
| PageColorsAffectsWebContents) { |
| browser()->profile()->GetPrefs()->SetBoolean( |
| prefs::kApplyPageColorsOnlyOnIncreasedContrast, false); |
| browser()->profile()->GetPrefs()->SetInteger( |
| prefs::kPageColors, ui::NativeTheme::PageColors::kDusk); |
| |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("http://foo.com"))); |
| |
| // Check that the page colors are applied when Forced Colors is enabled. For |
| // the Dusk theme, the color value for Window is 0x2D3236 which corresponds to |
| // rgb(45, 50, 54). |
| std::string expected_bg_color = "rgb(45, 50, 54)"; |
| EXPECT_EQ(expected_bg_color, |
| EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| "window.getComputedStyle(document.body).getPropertyValue('" |
| "background-color').toString()")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PageColorsBrowserClientTest, |
| PageColorsAffectsCssPseudoElements) { |
| browser()->profile()->GetPrefs()->SetBoolean( |
| prefs::kApplyPageColorsOnlyOnIncreasedContrast, false); |
| browser()->profile()->GetPrefs()->SetInteger( |
| prefs::kPageColors, ui::NativeTheme::PageColors::kDesert); |
| |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), ui_test_utils::GetTestUrl( |
| base::FilePath(base::FilePath::kCurrentDirectory), |
| base::FilePath(FILE_PATH_LITERAL("system-colors.html"))))); |
| |
| // Check that the right system color is applied for Pseudo elements when |
| // Forced Colors is enabled. For the Desert theme, the color value for |
| // WindowText is 0x3D3D3D which corresponds to rgb(61, 61, 61). |
| std::string expected_element_color = "rgb(61, 61, 61)"; |
| EXPECT_EQ(expected_element_color, |
| EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| "window.getComputedStyle(document.getElementById('icon'), " |
| "'::before')." |
| "getPropertyValue('color').toString()")); |
| } |
| |
| // Tests for the preferred color scheme for a given WebContents. The first param |
| // controls whether the web NativeTheme is light or dark the second controls |
| // whether the color mode on the associated color provider is light or dark. |
| class PrefersColorSchemeTest |
| : public testing::WithParamInterface<std::tuple<bool, bool>>, |
| public InProcessBrowserTest { |
| protected: |
| PrefersColorSchemeTest() |
| : theme_client_(&test_theme_), |
| color_provider_source_(GetIsDarkColorProviderColorMode()) { |
| test_theme_.SetDarkMode(GetIsDarkNativeTheme()); |
| } |
| ~PrefersColorSchemeTest() override { |
| CHECK_EQ(&theme_client_, SetBrowserClientForTesting(original_client_)); |
| } |
| |
| const char* ExpectedColorScheme() const { |
| const char* color_provider_color_mode = |
| GetIsDarkColorProviderColorMode() ? "dark" : "light"; |
| const char* native_theme_color_mode = |
| GetIsDarkNativeTheme() ? "dark" : "light"; |
| |
| // WebUI's preferred color scheme should reflect the color mode of their |
| // associated ColorProvider, and not the preferred color scheme of the web |
| // NativeTheme. |
| const GURL& last_committed_url = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetLastCommittedURL(); |
| if (content::HasWebUIScheme(last_committed_url)) { |
| return color_provider_color_mode; |
| } |
| |
| // Pages in incognito profiles should follow the device theme. |
| if (browser()->profile()->IsIncognitoProfile()) { |
| return native_theme_color_mode; |
| } |
| |
| // Pages in regular profiles should follow the browser theme, reflected by |
| // the color mode of the associated ColorProvider. |
| return color_provider_color_mode; |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| original_client_ = SetBrowserClientForTesting(&theme_client_); |
| test_theme_.SetDarkMode(GetIsDarkNativeTheme()); |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| embedded_test_server()->ServeFilesFromDirectory( |
| base::PathService::CheckedGet(base::DIR_ASSETS) |
| .AppendASCII("gen/chrome/test/data/webui/glic/")); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| auto* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII( |
| ::switches::kGlicGuestURL, |
| embedded_test_server()->GetURL("/glic/test_client/index.html").spec()); |
| #endif |
| |
| guest_view_manager_ = |
| guest_view_manager_factory_.GetOrCreateTestGuestViewManager( |
| browser()->profile(), extensions::ExtensionsAPIClient::Get() |
| ->CreateGuestViewManagerDelegate()); |
| |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->SetColorProviderSource(&color_provider_source_); |
| } |
| |
| void TearDownOnMainThread() override { |
| guest_view_manager_ = nullptr; |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| protected: |
| bool GetIsDarkNativeTheme() const { return std::get<0>(GetParam()); } |
| bool GetIsDarkColorProviderColorMode() const { |
| return std::get<1>(GetParam()); |
| } |
| |
| guest_view::TestGuestViewManager* guest_view_manager() const { |
| return guest_view_manager_; |
| } |
| |
| ui::TestNativeTheme test_theme_; |
| |
| private: |
| raw_ptr<content::ContentBrowserClient> original_client_ = nullptr; |
| |
| class ChromeContentBrowserClientWithWebTheme |
| : public ChromeContentBrowserClient { |
| public: |
| explicit ChromeContentBrowserClientWithWebTheme( |
| const ui::NativeTheme* theme) |
| : theme_(theme) {} |
| |
| protected: |
| const ui::NativeTheme* GetWebTheme() const override { return theme_; } |
| |
| private: |
| const raw_ptr<const ui::NativeTheme> theme_; |
| }; |
| |
| class MockColorProviderSource : public ui::ColorProviderSource { |
| public: |
| explicit MockColorProviderSource(bool is_dark) { |
| key_.color_mode = is_dark ? ui::ColorProviderKey::ColorMode::kDark |
| : ui::ColorProviderKey::ColorMode::kLight; |
| } |
| MockColorProviderSource(const MockColorProviderSource&) = delete; |
| MockColorProviderSource& operator=(const MockColorProviderSource&) = delete; |
| ~MockColorProviderSource() override = default; |
| |
| // ui::ColorProviderSource: |
| const ui::ColorProvider* GetColorProvider() const override { |
| return &provider_; |
| } |
| |
| ui::RendererColorMap GetRendererColorMap( |
| ui::ColorProviderKey::ColorMode color_mode, |
| ui::ColorProviderKey::ForcedColors forced_colors) const override { |
| auto key = GetColorProviderKey(); |
| key.color_mode = color_mode; |
| key.forced_colors = forced_colors; |
| ui::ColorProvider* color_provider = |
| ui::ColorProviderManager::Get().GetColorProviderFor(key); |
| CHECK(color_provider); |
| return ui::CreateRendererColorMap(*color_provider); |
| } |
| |
| ui::ColorProviderKey GetColorProviderKey() const override { return key_; } |
| |
| private: |
| ui::ColorProvider provider_; |
| ui::ColorProviderKey key_; |
| }; |
| |
| ChromeContentBrowserClientWithWebTheme theme_client_; |
| MockColorProviderSource color_provider_source_; |
| #if BUILDFLAG(ENABLE_GLIC) |
| glic::GlicTestEnvironment glic_test_environment_; |
| #endif |
| guest_view::TestGuestViewManagerFactory guest_view_manager_factory_; |
| raw_ptr<guest_view::TestGuestViewManager> guest_view_manager_ = nullptr; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, PrefersColorScheme) { |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| ui_test_utils::GetTestUrl( |
| base::FilePath(base::FilePath::kCurrentDirectory), |
| base::FilePath(FILE_PATH_LITERAL("prefers-color-scheme.html"))))); |
| std::u16string tab_title; |
| ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &tab_title)); |
| EXPECT_EQ(base::ASCIIToUTF16(ExpectedColorScheme()), tab_title); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, FeatureOverridesChromeSchemes) { |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GURL(chrome::kChromeUIDownloadsURL))); |
| |
| EXPECT_EQ( |
| true, |
| EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| base::StringPrintf( |
| "window.matchMedia('(prefers-color-scheme: %s)').matches", |
| ExpectedColorScheme()))); |
| } |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, PrefersColorSchemeGlic) { |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIGlicURL))); |
| |
| guest_view::GuestViewBase* guest_view = |
| guest_view_manager()->WaitForSingleGuestViewCreated(); |
| // Intentionally ignore the return value. It seems that on Windows and Linux |
| // the guest contents could have already been loaded by the time we get here. |
| std::ignore = guest_view_manager()->WaitUntilAttachedAndLoaded(guest_view); |
| |
| EXPECT_EQ( |
| true, |
| EvalJs(guest_view->GetGuestMainFrame(), |
| base::StringPrintf( |
| "window.matchMedia('(prefers-color-scheme: %s)').matches", |
| ExpectedColorScheme()))); |
| } |
| #endif |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, FeatureOverridesPdfUI) { |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| |
| std::string pdf_extension_url(extensions::kExtensionScheme); |
| pdf_extension_url.append(url::kStandardSchemeSeparator); |
| pdf_extension_url.append(extension_misc::kPdfExtensionId); |
| GURL pdf_index = GURL(pdf_extension_url).Resolve("/index.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), pdf_index)); |
| |
| EXPECT_EQ( |
| true, |
| EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| base::StringPrintf( |
| "window.matchMedia('(prefers-color-scheme: %s)').matches", |
| ExpectedColorScheme()))); |
| } |
| #endif |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| PrefersColorSchemeTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| class PreferredRootScrollbarColorSchemeChromeClientTest |
| : public testing::WithParamInterface<std::tuple<bool, bool>>, |
| public InProcessBrowserTest { |
| protected: |
| PreferredRootScrollbarColorSchemeChromeClientTest() |
| : dark_mode_(std::get<0>(GetParam())), |
| uses_custom_theme_(std::get<1>(GetParam())), |
| theme_client_(&test_theme_), |
| theme_color_(dark_mode_ ? SK_ColorDKGRAY : SK_ColorLTGRAY) { |
| test_theme_.SetDarkMode(dark_mode_); |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| feature_list_.InitAndEnableFeature( |
| blink::features::kRootScrollbarFollowsBrowserTheme); |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| original_client_ = SetBrowserClientForTesting(&theme_client_); |
| test_theme_.SetDarkMode(dark_mode_); |
| ui::NativeTheme::GetInstanceForNativeUi()->set_use_dark_colors(dark_mode_); |
| ThemeService* theme_service = |
| ThemeServiceFactory::GetForProfile(browser()->profile()); |
| if (uses_custom_theme_) { |
| // Browser themes adapt their hue to match the used color scheme (light |
| // or dark), however autogenerated themes don't take color schemes into |
| // consideration. So we select which color to use based on the color |
| // scheme to simulate this behavior. |
| theme_service->BuildAutogeneratedThemeFromColor(theme_color_); |
| } else { |
| theme_service->UseDefaultTheme(); |
| } |
| } |
| |
| ~PreferredRootScrollbarColorSchemeChromeClientTest() override { |
| CHECK_EQ(&theme_client_, SetBrowserClientForTesting(original_client_)); |
| } |
| |
| blink::mojom::PreferredColorScheme ExpectedColorScheme() const { |
| return dark_mode_ ? blink::mojom::PreferredColorScheme::kDark |
| : blink::mojom::PreferredColorScheme::kLight; |
| } |
| |
| bool ThemeColorMatches() const { |
| const std::optional<SkColor> root_scrollbar_pref = |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetOrCreateWebPreferences() |
| .root_scrollbar_theme_color; |
| // If not using a custom theme, `root_scrollbar_theme_color` shouldn't be |
| // set. |
| if (!uses_custom_theme_) { |
| return !root_scrollbar_pref.has_value(); |
| } |
| EXPECT_TRUE(root_scrollbar_pref.has_value()); |
| const SkColor root_scrollbar_color = root_scrollbar_pref.value(); |
| // `root_scrollbar_theme_color` is set based off the toolbar color, which is |
| // generated using the theme's color. Because of this, we can't directly |
| // compare equality between the two colors and we check that they are |
| // similar enough. |
| const double r_diff = |
| std::abs(SkColorGetR(root_scrollbar_color) - |
| static_cast<double>(SkColorGetR(theme_color_))); |
| const double g_diff = |
| std::abs(SkColorGetG(root_scrollbar_color) - |
| static_cast<double>(SkColorGetG(theme_color_))); |
| const double b_diff = |
| std::abs(SkColorGetB(root_scrollbar_color) - |
| static_cast<double>(SkColorGetB(theme_color_))); |
| |
| const int kMaxDiff = 20; |
| return r_diff < kMaxDiff && b_diff < kMaxDiff && g_diff < kMaxDiff; |
| } |
| |
| private: |
| class ChromeContentBrowserClientWithWebTheme |
| : public ChromeContentBrowserClient { |
| public: |
| explicit ChromeContentBrowserClientWithWebTheme( |
| const ui::NativeTheme* theme) |
| : theme_(theme) {} |
| |
| protected: |
| const ui::NativeTheme* GetWebTheme() const override { return theme_; } |
| |
| private: |
| const raw_ptr<const ui::NativeTheme> theme_; |
| }; |
| |
| const bool dark_mode_ = false; |
| const bool uses_custom_theme_ = false; |
| raw_ptr<content::ContentBrowserClient> original_client_ = nullptr; |
| ui::TestNativeTheme test_theme_; |
| ChromeContentBrowserClientWithWebTheme theme_client_; |
| const SkColor theme_color_; |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // This test verifies that the preferred color scheme for root scrollbars is set |
| // appropriately following the web content's color scheme and the presence of |
| // a custom theme. |
| IN_PROC_BROWSER_TEST_P(PreferredRootScrollbarColorSchemeChromeClientTest, |
| ScrollbarFollowsPreferredColorScheme) { |
| EXPECT_EQ(browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetOrCreateWebPreferences() |
| .preferred_root_scrollbar_color_scheme, |
| ExpectedColorScheme()); |
| } |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| // This test verifies that the root scrollbar color theme is set correctly only |
| // when using a custom theme. |
| IN_PROC_BROWSER_TEST_P(PreferredRootScrollbarColorSchemeChromeClientTest, |
| VerifyRootScrollbarColorTheme) { |
| EXPECT_TRUE(ThemeColorMatches()); |
| } |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| PreferredRootScrollbarColorSchemeChromeClientTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| class PrefersContrastTest |
| : public testing::WithParamInterface<ui::NativeTheme::PreferredContrast>, |
| public InProcessBrowserTest { |
| protected: |
| PrefersContrastTest() : theme_client_(&test_theme_) {} |
| |
| ~PrefersContrastTest() override { |
| CHECK_EQ(&theme_client_, SetBrowserClientForTesting(original_client_)); |
| } |
| |
| const char* ExpectedPrefersContrast() const { |
| switch (GetParam()) { |
| case ui::NativeTheme::PreferredContrast::kNoPreference: |
| return "no-preference"; |
| case ui::NativeTheme::PreferredContrast::kMore: |
| return "more"; |
| case ui::NativeTheme::PreferredContrast::kLess: |
| return "less"; |
| case ui::NativeTheme::PreferredContrast::kCustom: |
| return "custom"; |
| } |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, |
| "PrefersContrast"); |
| command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, |
| "ForcedColors"); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| original_client_ = SetBrowserClientForTesting(&theme_client_); |
| } |
| |
| protected: |
| ui::TestNativeTheme test_theme_; |
| |
| private: |
| raw_ptr<content::ContentBrowserClient> original_client_ = nullptr; |
| |
| class ChromeContentBrowserClientWithWebTheme |
| : public ChromeContentBrowserClient { |
| public: |
| explicit ChromeContentBrowserClientWithWebTheme( |
| const ui::NativeTheme* theme) |
| : theme_(theme) {} |
| |
| protected: |
| const ui::NativeTheme* GetWebTheme() const override { return theme_; } |
| |
| private: |
| const raw_ptr<const ui::NativeTheme> theme_; |
| }; |
| |
| ChromeContentBrowserClientWithWebTheme theme_client_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(PrefersContrastTest, PrefersContrast) { |
| test_theme_.SetPreferredContrast(GetParam()); |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->OnWebPreferencesChanged(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| ui_test_utils::GetTestUrl( |
| base::FilePath(base::FilePath::kCurrentDirectory), |
| base::FilePath(FILE_PATH_LITERAL("prefers-contrast.html"))))); |
| std::u16string tab_title; |
| ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &tab_title)); |
| EXPECT_EQ(base::ASCIIToUTF16(ExpectedPrefersContrast()), tab_title); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| PrefersContrastTest, |
| testing::Values(ui::NativeTheme::PreferredContrast::kNoPreference, |
| ui::NativeTheme::PreferredContrast::kMore, |
| ui::NativeTheme::PreferredContrast::kLess, |
| ui::NativeTheme::PreferredContrast::kCustom)); |
| |
| class ProtocolHandlerTest : public InProcessBrowserTest { |
| public: |
| ProtocolHandlerTest() = default; |
| |
| ProtocolHandlerTest(const ProtocolHandlerTest&) = delete; |
| ProtocolHandlerTest& operator=(const ProtocolHandlerTest&) = delete; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| protected: |
| void AddProtocolHandler(const std::string& scheme, |
| const std::string& redirect_template) { |
| protocol_handler_registry()->OnAcceptRegisterProtocolHandler( |
| custom_handlers::ProtocolHandler::CreateProtocolHandler( |
| scheme, GURL(redirect_template))); |
| } |
| |
| custom_handlers::ProtocolHandlerRegistry* protocol_handler_registry() { |
| return ProtocolHandlerRegistryFactory::GetInstance()->GetForBrowserContext( |
| browser()->profile()); |
| } |
| }; |
| |
| // TODO(crbug.com/40917055): Enable test when MacOS flake is fixed. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_CustomHandler DISABLED_CustomHandler |
| #else |
| #define MAYBE_CustomHandler CustomHandler |
| #endif |
| IN_PROC_BROWSER_TEST_F(ProtocolHandlerTest, MAYBE_CustomHandler) { |
| #if BUILDFLAG(IS_MAC) |
| ASSERT_TRUE(test::RegisterAppWithLaunchServices()); |
| #endif |
| AddProtocolHandler("news", "https://abc.xyz/?url=%s"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("news:something"))); |
| |
| std::u16string expected_title = u"abc.xyz"; |
| content::TitleWatcher title_watcher( |
| browser()->tab_strip_model()->GetActiveWebContents(), expected_title); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| } |
| |
| // This is a regression test for crbug.com/969177. |
| IN_PROC_BROWSER_TEST_F(ProtocolHandlerTest, HandlersIgnoredWhenDisabled) { |
| AddProtocolHandler("bitcoin", "https://abc.xyz/?url=%s"); |
| protocol_handler_registry()->Disable(); |
| |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL("bitcoin:something"))); |
| |
| std::u16string tab_title; |
| ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &tab_title)); |
| EXPECT_EQ(u"about:blank", tab_title); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Tests that if a protocol handler is registered for a scheme, an external |
| // program (another Chrome tab in this case) is not launched to handle the |
| // navigation. This is a regression test for crbug.com/963133. |
| IN_PROC_BROWSER_TEST_F(ProtocolHandlerTest, ExternalProgramNotLaunched) { |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL("mailto:bob@example.com"))); |
| |
| // If an external program (Chrome) was launched, it will result in a second |
| // tab being opened. |
| EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| // Make sure the protocol handler redirected the navigation. |
| std::u16string expected_title = u"mail.google.com"; |
| content::TitleWatcher title_watcher( |
| browser()->tab_strip_model()->GetActiveWebContents(), expected_title); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| } |
| |
| namespace { |
| class FakeExternalProtocolHandlerWorker |
| : public shell_integration::DefaultSchemeClientWorker { |
| public: |
| FakeExternalProtocolHandlerWorker( |
| const GURL& url, |
| shell_integration::DefaultWebClientState os_state, |
| const std::u16string& program_name) |
| : shell_integration::DefaultSchemeClientWorker(url), |
| os_state_(os_state), |
| program_name_(program_name) {} |
| |
| private: |
| ~FakeExternalProtocolHandlerWorker() override = default; |
| |
| shell_integration::DefaultWebClientState CheckIsDefaultImpl() override { |
| return os_state_; |
| } |
| |
| std::u16string GetDefaultClientNameImpl() override { return program_name_; } |
| |
| void SetAsDefaultImpl(base::OnceClosure on_finished_callback) override { |
| std::move(on_finished_callback).Run(); |
| } |
| |
| shell_integration::DefaultWebClientState os_state_; |
| std::u16string program_name_; |
| }; |
| |
| class ScopedFakeExternalProtocolHandlerDelegate |
| : public ExternalProtocolHandler::Delegate { |
| public: |
| ScopedFakeExternalProtocolHandlerDelegate() { |
| ExternalProtocolHandler::SetDelegateForTesting(this); |
| } |
| ~ScopedFakeExternalProtocolHandlerDelegate() override { |
| ExternalProtocolHandler::SetDelegateForTesting(nullptr); |
| } |
| scoped_refptr<shell_integration::DefaultSchemeClientWorker> CreateShellWorker( |
| const GURL& url) override { |
| return new FakeExternalProtocolHandlerWorker( |
| url, shell_integration::UNKNOWN_DEFAULT, program_name_); |
| } |
| |
| ExternalProtocolHandler::BlockState GetBlockState(const std::string& scheme, |
| Profile* profile) override { |
| return ExternalProtocolHandler::UNKNOWN; |
| } |
| |
| void BlockRequest() override { |
| FAIL() << "Unexpected BlockRequest call received"; |
| } |
| |
| void RunExternalProtocolDialog( |
| const GURL& url, |
| content::WebContents* web_contents, |
| ui::PageTransition page_transition, |
| bool has_user_gesture, |
| const std::optional<url::Origin>& initiating_origin, |
| const std::u16string& program_name) override { |
| EXPECT_EQ(program_name_, program_name); |
| external_protocol_dialog_called_ = true; |
| launched_url_with_security_check_ = url.spec(); |
| } |
| |
| void LaunchUrlWithoutSecurityCheck( |
| const GURL& url, |
| content::WebContents* web_contents) override { |
| launched_url_without_security_check_ = url.spec(); |
| launch_url_run_loop_.Quit(); |
| } |
| |
| void FinishedProcessingCheck() override { launch_url_run_loop_.Quit(); } |
| |
| void WaitExternalUrlLaunchCompleted() { launch_url_run_loop_.Run(); } |
| |
| bool external_protocol_dialog_called() { |
| return external_protocol_dialog_called_; |
| } |
| std::string launched_url_without_security_check() { |
| return launched_url_without_security_check_; |
| } |
| std::string launched_url_with_security_check() { |
| return launched_url_with_security_check_; |
| } |
| |
| private: |
| base::RunLoop launch_url_run_loop_; |
| const std::u16string program_name_ = u"custom"; |
| bool external_protocol_dialog_called_ = false; |
| std::string launched_url_without_security_check_; |
| std::string launched_url_with_security_check_; |
| }; |
| |
| } // namespace |
| |
| // URLs which are explicitly allowlisted by policy can bypass security checks. |
| IN_PROC_BROWSER_TEST_F(ProtocolHandlerTest, |
| SecurityCheckExceptionForAllowlistedUrls) { |
| ProtocolHandlerRegistryFactory::GetInstance() |
| ->GetForBrowserContext(browser()->profile()) |
| ->OnAcceptRegisterProtocolHandler( |
| custom_handlers::ProtocolHandler::CreateProtocolHandler( |
| "map", GURL("geo://%s"))); |
| |
| ScopedFakeExternalProtocolHandlerDelegate delegate; |
| |
| base::Value::List allowlist; |
| allowlist.Append("geo://*"); |
| browser()->profile()->GetPrefs()->SetList(policy::policy_prefs::kUrlAllowlist, |
| std::move(allowlist)); |
| // The call to update the internal allowlist value is async. |
| base::RunLoop().RunUntilIdle(); |
| |
| const char kGeoUrl[] = "geo:48.2082,16.3738"; |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kGeoUrl))); |
| delegate.WaitExternalUrlLaunchCompleted(); |
| |
| EXPECT_FALSE(delegate.external_protocol_dialog_called()); |
| EXPECT_EQ(delegate.launched_url_without_security_check(), kGeoUrl); |
| EXPECT_EQ(delegate.launched_url_with_security_check(), ""); |
| } |
| |
| // Regardless of the value of the UrlAllowlist policy, intent:// URLs should |
| // always be deferred to the external protocol dialog (which currently defers |
| // the call to ARC). |
| IN_PROC_BROWSER_TEST_F(ProtocolHandlerTest, |
| IntentSchemeBypassSecurityExceptions) { |
| ProtocolHandlerRegistryFactory::GetInstance() |
| ->GetForBrowserContext(browser()->profile()) |
| ->OnAcceptRegisterProtocolHandler( |
| custom_handlers::ProtocolHandler::CreateProtocolHandler( |
| "search", GURL("intent://%s"))); |
| |
| ScopedFakeExternalProtocolHandlerDelegate delegate; |
| |
| base::Value::List allowlist; |
| allowlist.Append("intent://*"); |
| browser()->profile()->GetPrefs()->SetList(policy::policy_prefs::kUrlAllowlist, |
| std::move(allowlist)); |
| // The call to update the internal allowlist value is async. |
| base::RunLoop().RunUntilIdle(); |
| |
| const char kIntentUrl[] = |
| "intent://www.google.com/" |
| "#Intent;scheme=http;package=com.android.chrome;end"; |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kIntentUrl))); |
| delegate.WaitExternalUrlLaunchCompleted(); |
| |
| EXPECT_TRUE(delegate.external_protocol_dialog_called()); |
| // intent:// URLs should not skip security checks. |
| EXPECT_EQ(delegate.launched_url_without_security_check(), ""); |
| EXPECT_EQ(delegate.launched_url_with_security_check(), kIntentUrl); |
| } |
| #endif |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| class KeepaliveDurationOnShutdownTest : public InProcessBrowserTest, |
| public InstantTestBase { |
| public: |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| client_ = static_cast<ChromeContentBrowserClient*>( |
| content::SetBrowserClientForTesting(nullptr)); |
| content::SetBrowserClientForTesting(client_); |
| } |
| void TearDownOnMainThread() override { |
| client_ = nullptr; |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| raw_ptr<ChromeContentBrowserClient> client_ = nullptr; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(KeepaliveDurationOnShutdownTest, DefaultValue) { |
| Profile* profile = browser()->profile(); |
| EXPECT_EQ(client_->GetKeepaliveTimerTimeout(profile), base::TimeDelta()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(KeepaliveDurationOnShutdownTest, PolicySettings) { |
| Profile* profile = browser()->profile(); |
| profile->GetPrefs()->SetInteger(prefs::kFetchKeepaliveDurationOnShutdown, 2); |
| |
| EXPECT_EQ(client_->GetKeepaliveTimerTimeout(profile), base::Seconds(2)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(KeepaliveDurationOnShutdownTest, DynamicUpdate) { |
| Profile* profile = browser()->profile(); |
| profile->GetPrefs()->SetInteger(prefs::kFetchKeepaliveDurationOnShutdown, 2); |
| |
| EXPECT_EQ(client_->GetKeepaliveTimerTimeout(profile), base::Seconds(2)); |
| |
| profile->GetPrefs()->SetInteger(prefs::kFetchKeepaliveDurationOnShutdown, 3); |
| |
| EXPECT_EQ(client_->GetKeepaliveTimerTimeout(profile), base::Seconds(3)); |
| } |
| |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| class ClipboardTestContentAnalysisDelegate |
| : public enterprise_connectors::test::FakeContentAnalysisDelegate { |
| public: |
| ClipboardTestContentAnalysisDelegate(base::RepeatingClosure delete_closure, |
| StatusCallback status_callback, |
| std::string dm_token, |
| content::WebContents* web_contents, |
| Data data, |
| CompletionCallback callback) |
| : enterprise_connectors::test::FakeContentAnalysisDelegate( |
| delete_closure, |
| std::move(status_callback), |
| std::move(dm_token), |
| web_contents, |
| std::move(data), |
| std::move(callback)) {} |
| |
| static std::unique_ptr<ContentAnalysisDelegate> Create( |
| base::RepeatingClosure delete_closure, |
| StatusCallback status_callback, |
| std::string dm_token, |
| content::WebContents* web_contents, |
| Data data, |
| CompletionCallback callback) { |
| auto ret = std::make_unique<ClipboardTestContentAnalysisDelegate>( |
| delete_closure, std::move(status_callback), std::move(dm_token), |
| web_contents, std::move(data), std::move(callback)); |
| enterprise_connectors::FilesRequestHandler::SetFactoryForTesting( |
| base::BindRepeating( |
| &enterprise_connectors::test::FakeFilesRequestHandler::Create, |
| base::BindRepeating(&ClipboardTestContentAnalysisDelegate:: |
| FakeUploadFileForDeepScanning, |
| base::Unretained(ret.get())))); |
| enterprise_connectors::ClipboardRequestHandler::SetFactoryForTesting( |
| base::BindRepeating( |
| &enterprise_connectors::test::FakeClipboardRequestHandler::Create, |
| base::Unretained(ret.get()))); |
| return ret; |
| } |
| |
| private: |
| void FakeUploadFileForDeepScanning( |
| safe_browsing::BinaryUploadService::Result result, |
| const base::FilePath& path, |
| std::unique_ptr<safe_browsing::BinaryUploadService::Request> request, |
| enterprise_connectors::test::FakeFilesRequestHandler:: |
| FakeFileRequestCallback callback) override { |
| ASSERT_EQ(request->reason(), |
| enterprise_connectors::ContentAnalysisRequest::CLIPBOARD_PASTE); |
| |
| enterprise_connectors::test::FakeContentAnalysisDelegate:: |
| FakeUploadFileForDeepScanning(result, path, std::move(request), |
| std::move(callback)); |
| } |
| }; |
| |
| class IsClipboardPasteAllowedTest : public InProcessBrowserTest { |
| public: |
| IsClipboardPasteAllowedTest() { |
| EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| // Make sure enterprise policies are set to turn on content analysis. |
| enterprise_connectors::test::SetAnalysisConnector( |
| browser()->profile()->GetPrefs(), |
| enterprise_connectors::BULK_DATA_ENTRY, kBulkDataEntryPolicyValue); |
| enterprise_connectors::test::SetAnalysisConnector( |
| browser()->profile()->GetPrefs(), enterprise_connectors::FILE_ATTACHED, |
| kFileAttachedPolicyValue); |
| |
| enterprise_connectors::ContentAnalysisDelegate::SetFactoryForTesting( |
| base::BindRepeating( |
| &ClipboardTestContentAnalysisDelegate::Create, base::DoNothing(), |
| base::BindRepeating([](const std::string& contents, |
| const base::FilePath& path) { |
| bool success = false; |
| if (!contents.empty()) { |
| success = contents.substr(0, 5) == "allow"; |
| } else { |
| success = |
| path.BaseName().AsUTF8Unsafe().substr(0, 5) == "allow"; |
| } |
| return success |
| ? enterprise_connectors::test:: |
| FakeContentAnalysisDelegate::SuccessfulResponse( |
| {"dlp"}) |
| : enterprise_connectors::test:: |
| FakeContentAnalysisDelegate::DlpResponse( |
| enterprise_connectors:: |
| ContentAnalysisResponse::Result::SUCCESS, |
| "rule-name", |
| enterprise_connectors:: |
| ContentAnalysisResponse::Result:: |
| TriggeredRule::BLOCK); |
| }), |
| /*dm_token=*/std::string())); |
| |
| client_ = static_cast<ChromeContentBrowserClient*>( |
| content::SetBrowserClientForTesting(nullptr)); |
| content::SetBrowserClientForTesting(client_); |
| } |
| |
| void TearDownOnMainThread() override { |
| client_ = nullptr; |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| protected: |
| ChromeContentBrowserClient* client() const { return client_; } |
| |
| base::FilePath CreateTestFile(const base::FilePath::StringType& filename, |
| const std::string& content) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::FilePath path = temp_dir_.GetPath().Append(filename); |
| base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_WRITE); |
| file.WriteAtCurrentPos(base::as_byte_span(content)); |
| return path; |
| } |
| |
| private: |
| static constexpr char kBulkDataEntryPolicyValue[] = R"( |
| { |
| "service_provider": "local_system_agent", |
| "enable": [ |
| { |
| "url_list": ["*"], |
| "tags": ["dlp"] |
| } |
| ], |
| "block_until_verdict": 1, |
| "minimum_data_size": 1 |
| })"; |
| |
| static constexpr char kFileAttachedPolicyValue[] = R"( |
| { |
| "service_provider": "local_system_agent", |
| "enable": [ |
| { |
| "url_list": ["*"], |
| "tags": ["dlp"] |
| } |
| ], |
| "block_until_verdict": 1 |
| })"; |
| |
| base::ScopedTempDir temp_dir_; |
| raw_ptr<ChromeContentBrowserClient> client_ = nullptr; |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| // This installs a fake SDK manager that creates fake SDK clients when |
| // its GetClient() method is called. This is needed so that calls to |
| // ContentAnalysisSdkManager::Get()->GetClient() do not fail. |
| enterprise_connectors::FakeContentAnalysisSdkManager sdk_manager_; |
| #endif |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, BitmapAllowed) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.png = StringToVector("allowed"); |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.png.size(), |
| .format_type = ui::ClipboardFormatType::BitmapType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->png, StringToVector("allowed")); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, BitmapBlocked) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.png = StringToVector("blocked"); |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.png.size(), |
| .format_type = ui::ClipboardFormatType::BitmapType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| EXPECT_FALSE(clipboard_paste_data.has_value()); |
| #else |
| // Platforms that don't support local content analysis shouldn't |
| // block anything, even when the policy is set to a local service |
| // provider value. |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->png, StringToVector("blocked")); |
| #endif |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, TextAllowed) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.text = u"allowed"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.text.size(), |
| .format_type = ui::ClipboardFormatType::PlainTextType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->text, u"allowed"); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, TextBlocked) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.text = u"blocked"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.text.size(), |
| .format_type = ui::ClipboardFormatType::PlainTextType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| EXPECT_FALSE(clipboard_paste_data.has_value()); |
| #else |
| // Platforms that don't support local content analysis shouldn't |
| // block anything, even when the policy is set to a local service |
| // provider value. |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->text, u"blocked"); |
| #endif |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, HtmlAllowed) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.html = u"allowed"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.html.size(), |
| .format_type = ui::ClipboardFormatType::HtmlType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->html, u"allowed"); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, HtmlBlocked) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.html = u"blocked"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.html.size(), |
| .format_type = ui::ClipboardFormatType::HtmlType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| EXPECT_FALSE(clipboard_paste_data.has_value()); |
| #else |
| // Platforms that don't support local content analysis shouldn't |
| // block anything, even when the policy is set to a local service |
| // provider value. |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->html, u"blocked"); |
| #endif |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, SvgAllowed) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.svg = u"allowed"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.svg.size(), |
| .format_type = ui::ClipboardFormatType::SvgType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->svg, u"allowed"); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, SvgBlocked) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.svg = u"blocked"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.svg.size(), |
| .format_type = ui::ClipboardFormatType::SvgType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| EXPECT_FALSE(clipboard_paste_data.has_value()); |
| #else |
| // Platforms that don't support local content analysis shouldn't |
| // block anything, even when the policy is set to a local service |
| // provider value. |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->svg, u"blocked"); |
| #endif |
| })); |
| } |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, RtfAllowed) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.rtf = "allowed"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.rtf.size(), |
| .format_type = ui::ClipboardFormatType::RtfType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->rtf, "allowed"); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, RtfBlocked) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.rtf = "blocked"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.rtf.size(), |
| .format_type = ui::ClipboardFormatType::RtfType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| EXPECT_FALSE(clipboard_paste_data.has_value()); |
| #else |
| // Platforms that don't support local content analysis shouldn't |
| // block anything, even when the policy is set to a local service |
| // provider value. |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->rtf, "blocked"); |
| #endif |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, CustomDataAllowed) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.custom_data[u"custom/data"] = u"allowed"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.custom_data[u"custom/data"].size(), |
| .format_type = ui::ClipboardFormatType::DataTransferCustomType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->custom_data.size(), 1u); |
| EXPECT_TRUE( |
| clipboard_paste_data->custom_data.count(u"custom/data")); |
| EXPECT_EQ(clipboard_paste_data->custom_data[u"custom/data"], |
| u"allowed"); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, CustomDataBlocked) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.custom_data[u"custom/data"] = u"blocked"; |
| |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .size = clipboard_paste_data.custom_data[u"custom/data"].size(), |
| .format_type = ui::ClipboardFormatType::DataTransferCustomType(), |
| }, |
| clipboard_paste_data, |
| base::BindOnce( |
| [](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| EXPECT_FALSE(clipboard_paste_data.has_value()); |
| #else |
| // Platforms that don't support local content analysis shouldn't |
| // block anything, even when the policy is set to a local service |
| // provider value. |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->custom_data.size(), 1u); |
| EXPECT_TRUE( |
| clipboard_paste_data->custom_data.count(u"custom/data")); |
| EXPECT_EQ(clipboard_paste_data->custom_data[u"custom/data"], |
| u"blocked"); |
| #endif |
| })); |
| } |
| |
| // TODO(crbug.com/391682998): Enable. |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, DISABLED_AllFilesAllowed) { |
| std::vector<base::FilePath> paths; |
| paths.push_back(CreateTestFile(FILE_PATH_LITERAL("allow0"), "data")); |
| paths.push_back(CreateTestFile(FILE_PATH_LITERAL("allow1"), "data")); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.file_paths = paths; |
| |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .format_type = ui::ClipboardFormatType::FilenamesType(), |
| }, |
| clipboard_paste_data, |
| base::BindLambdaForTesting( |
| [paths](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(paths[0], clipboard_paste_data->file_paths[0]); |
| EXPECT_EQ(paths[1], clipboard_paste_data->file_paths[1]); |
| })); |
| } |
| |
| // TODO(crbug.com/391682998): Enable. |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, DISABLED_AllFilesBlocked) { |
| std::vector<base::FilePath> paths; |
| paths.push_back(CreateTestFile(FILE_PATH_LITERAL("block0"), "data")); |
| paths.push_back(CreateTestFile(FILE_PATH_LITERAL("block1"), "data")); |
| |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.file_paths = paths; |
| |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .format_type = ui::ClipboardFormatType::FilenamesType(), |
| }, |
| clipboard_paste_data, |
| base::BindLambdaForTesting( |
| [paths](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| #if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| EXPECT_FALSE(clipboard_paste_data.has_value()); |
| #else |
| // Platforms that don't support local content analysis shouldn't |
| // block anything, even when the policy is set to a local service |
| // provider value. |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->file_paths[0], paths[0]); |
| EXPECT_EQ(clipboard_paste_data->file_paths[1], paths[1]); |
| #endif |
| })); |
| } |
| |
| // TODO(crbug.com/391682998): Re-enable this test |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, DISABLED_SomeFilesBlocked) { |
| std::vector<base::FilePath> paths; |
| paths.push_back(CreateTestFile(FILE_PATH_LITERAL("allow0"), "data")); |
| paths.push_back(CreateTestFile(FILE_PATH_LITERAL("block1"), "data")); |
| ChromeContentBrowserClient::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.file_paths = paths; |
| |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| client()->IsClipboardPasteAllowedByPolicy( |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com"))), |
| content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [contents] { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| { |
| .format_type = ui::ClipboardFormatType::FilenamesType(), |
| }, |
| clipboard_paste_data, |
| base::BindLambdaForTesting( |
| [paths](std::optional<ChromeContentBrowserClient::ClipboardPasteData> |
| clipboard_paste_data) { |
| EXPECT_TRUE(clipboard_paste_data.has_value()); |
| EXPECT_EQ(clipboard_paste_data->file_paths[0], paths[0]); |
| })); |
| } |
| |
| // Verifies that the available clipboard types are correctly reported when |
| // clipboard data is replaced by a Data Controls policy. |
| IN_PROC_BROWSER_TEST_F(IsClipboardPasteAllowedTest, |
| GetClipboardTypesIfPolicyApplied) { |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetWebContentsAt(0); |
| |
| // Set policy that blocks copying to the OS clipboard for a specific source. |
| data_controls::SetDataControls(browser()->profile()->GetPrefs(), {R"({ |
| "name": "test rule", |
| "sources": { |
| "urls": ["https://google.com"] |
| }, |
| "destinations": { |
| "os_clipboard": true |
| }, |
| "restrictions": [ |
| {"class": "CLIPBOARD", "level": "BLOCK"} |
| ] |
| })"}); |
| |
| content::ClipboardPasteData clipboard_paste_data; |
| clipboard_paste_data.text = u"foo"; |
| clipboard_paste_data.custom_data[u"custom/data"] = u"custom data"; |
| |
| base::test::TestFuture<const ui::ClipboardFormatType&, |
| const content::ClipboardPasteData&, |
| std::optional<std::u16string>> |
| future; |
| |
| // Initiates a copy request and verify that replacement is written as per the |
| // policy. |
| client()->IsClipboardCopyAllowedByPolicy( |
| /*source=*/content::ClipboardEndpoint( |
| ui::DataTransferEndpoint(GURL("https://google.com")), |
| base::BindLambdaForTesting( |
| [&contents]() { return contents->GetBrowserContext(); }), |
| *contents->GetPrimaryMainFrame()), |
| /*metadata=*/{.size = 1234}, clipboard_paste_data, |
| future.GetCallback()); |
| auto replacement = future.Get<std::optional<std::u16string>>(); |
| EXPECT_TRUE(replacement.has_value()); |
| |
| // Triggers the clipboard observer started by `IsClipboardCopyAllowedByPolicy` |
| // so that it's aware of the new seqno. |
| ui::ClipboardMonitor::GetInstance()->NotifyClipboardDataChanged(); |
| |
| // Verifies that the last replaced clipboard types are retrieved correctly. |
| auto types = client()->GetClipboardTypesIfPolicyApplied( |
| ui::Clipboard::GetForCurrentThread()->GetSequenceNumber( |
| ui::ClipboardBuffer::kCopyPaste)); |
| |
| ASSERT_TRUE(types.has_value()); |
| ASSERT_EQ(types->size(), 2u); |
| EXPECT_EQ((*types)[0], u"text/plain"); |
| EXPECT_EQ((*types)[1], u"custom/data"); |
| } |
| |
| #endif // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| class AutomaticBeaconCredentialsBrowserTest : public InProcessBrowserTest, |
| public InstantTestBase { |
| public: |
| AutomaticBeaconCredentialsBrowserTest() { |
| scoped_feature_list_.InitWithFeatures( |
| /*enabled_features=*/{privacy_sandbox:: |
| kOverridePrivacySandboxSettingsLocalTesting}, |
| /*disabled_features=*/{ |
| content_settings::features::kTrackingProtection3pcd}); |
| } |
| |
| AutomaticBeaconCredentialsBrowserTest( |
| const AutomaticBeaconCredentialsBrowserTest&) = delete; |
| AutomaticBeaconCredentialsBrowserTest& operator=( |
| const AutomaticBeaconCredentialsBrowserTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kIgnoreCertificateErrors); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| |
| content::RenderFrameHost* primary_main_frame_host() { |
| return browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame(); |
| } |
| |
| content::test::FencedFrameTestHelper& fenced_frame_test_helper() { |
| return fenced_frame_test_helper_; |
| } |
| |
| private: |
| content::test::FencedFrameTestHelper fenced_frame_test_helper_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AutomaticBeaconCredentialsBrowserTest, |
| 3PCEnabledAndDisabled) { |
| privacy_sandbox::ScopedPrivacySandboxAttestations scoped_attestations( |
| privacy_sandbox::PrivacySandboxAttestations::CreateForTesting()); |
| // Mark all Privacy Sandbox APIs as attested since the test case is testing |
| // behaviors not related to attestations. |
| privacy_sandbox::PrivacySandboxAttestations::GetInstance() |
| ->SetAllPrivacySandboxAttestedForTesting(true); |
| |
| constexpr char kReportingURL[] = "/_report_event_server.html"; |
| constexpr char kBeaconMessage[] = "this is the message"; |
| |
| net::test_server::ControllableHttpResponse first_response( |
| &https_test_server(), kReportingURL); |
| net::test_server::ControllableHttpResponse second_response( |
| &https_test_server(), kReportingURL); |
| |
| ASSERT_TRUE(https_test_server().Start()); |
| |
| // Set up the document.cookie for credentialed automatic beacons. Automatic |
| // beacons are set up in chrome/test/data/interest_group/bidding_logic.js to |
| // send to "d.test/_report_event_server.html". |
| auto cookie_url = https_test_server().GetURL("d.test", "/empty.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), cookie_url)); |
| EXPECT_TRUE( |
| ExecJs(primary_main_frame_host(), |
| "document.cookie = 'name=foobarbaz; SameSite=None; Secure';")); |
| |
| auto initial_url = https_test_server().GetURL("a.test", "/empty.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); |
| |
| // Load a fenced frame. |
| GURL fenced_frame_url( |
| https_test_server().GetURL("a.test", "/fenced_frames/title1.html")); |
| GURL new_tab_url(https_test_server().GetURL("a.test", "/title2.html")); |
| EXPECT_TRUE(ExecJs(primary_main_frame_host(), |
| "var fenced_frame = document.createElement('fencedframe');" |
| "fenced_frame.id = 'fenced_frame';" |
| "document.body.appendChild(fenced_frame);")); |
| auto* fenced_frame_host = |
| fenced_frame_test_helper().GetMostRecentlyAddedFencedFrame( |
| primary_main_frame_host()); |
| content::TestFrameNavigationObserver observer(fenced_frame_host); |
| fenced_frame_test_helper().NavigateFencedFrameUsingFledge( |
| primary_main_frame_host(), fenced_frame_url, "fenced_frame"); |
| observer.Wait(); |
| |
| // The navigation will change the fenced frame node. Get the handle to the new |
| // node. |
| fenced_frame_host = |
| fenced_frame_test_helper().GetMostRecentlyAddedFencedFrame( |
| primary_main_frame_host()); |
| |
| // Set the automatic beacon |
| EXPECT_TRUE( |
| ExecJs(fenced_frame_host, |
| content::JsReplace(R"( |
| window.fence.setReportEventDataForAutomaticBeacons({ |
| eventType: $1, |
| eventData: $2, |
| destination: ['seller', 'buyer'] |
| }); |
| )", |
| "reserved.top_navigation", kBeaconMessage))); |
| |
| // Trigger the first automatic beacon and verify it was sent with cookie data. |
| auto top_nav_url = https_test_server().GetURL("a.test", "/empty.html"); |
| EXPECT_TRUE( |
| ExecJs(fenced_frame_host, |
| content::JsReplace("window.open($1, '_blank');", top_nav_url))); |
| first_response.WaitForRequest(); |
| EXPECT_EQ(1U, first_response.http_request()->headers.count("Cookie")); |
| EXPECT_EQ("name=foobarbaz", |
| first_response.http_request()->headers.at("Cookie")); |
| |
| // Disable 3rd party cookies. |
| browser()->profile()->GetPrefs()->SetBoolean( |
| prefs::kTrackingProtection3pcdEnabled, true); |
| |
| // Verify automatic beacons no longer are sent with cookie data. |
| EXPECT_TRUE( |
| ExecJs(fenced_frame_host, |
| content::JsReplace("window.open($1, '_blank');", top_nav_url))); |
| second_response.WaitForRequest(); |
| EXPECT_EQ(0U, second_response.http_request()->headers.count("Cookie")); |
| } |
| |
| class TopChromeChromeContentBrowserClientTest |
| : public ChromeContentBrowserClientBrowserTest { |
| public: |
| // ChromeContentBrowserClientBrowserTest: |
| void SetUpOnMainThread() override { |
| ChromeContentBrowserClientBrowserTest::SetUpOnMainThread(); |
| client_ = static_cast<ChromeContentBrowserClient*>( |
| content::SetBrowserClientForTesting(nullptr)); |
| content::SetBrowserClientForTesting(client_); |
| } |
| |
| ChromeContentBrowserClient* client() { return client_; } |
| |
| private: |
| raw_ptr<ChromeContentBrowserClient> client_ = nullptr; |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(TopChromeChromeContentBrowserClientTest, |
| UnboundRequestDoesNothing) { |
| #if BUILDFLAG(IS_ANDROID) |
| network::URLLoaderFactoryBuilder factory_builder; |
| client()->MaybeProxyNetworkBoundRequest(browser()->profile(), |
| net::handles::kInvalidNetworkHandle, |
| factory_builder, nullptr); |
| EXPECT_EQ( |
| client() |
| ->get_target_network_for_network_bound_network_context_for_testing(), |
| net::handles::kInvalidNetworkHandle); |
| EXPECT_FALSE( |
| client()->get_network_bound_network_context_for_testing().is_bound()); |
| #else // !BUILDFLAG(IS_ANDROID) |
| GTEST_SKIP() << "proxying bound requests is supported only on Android"; |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TopChromeChromeContentBrowserClientTest, |
| BoundRequestCreatesNetworkContext) { |
| #if BUILDFLAG(IS_ANDROID) |
| constexpr net::handles::NetworkHandle network = 1; |
| network::URLLoaderFactoryBuilder factory_builder; |
| client()->MaybeProxyNetworkBoundRequest(browser()->profile(), network, |
| factory_builder, nullptr); |
| EXPECT_EQ( |
| client() |
| ->get_target_network_for_network_bound_network_context_for_testing(), |
| network); |
| EXPECT_TRUE( |
| client()->get_network_bound_network_context_for_testing().is_bound()); |
| EXPECT_TRUE( |
| client()->get_network_bound_network_context_for_testing().is_connected()); |
| { |
| base::RunLoop run_loop; |
| client() |
| ->get_network_bound_network_context_for_testing() |
| ->GetBoundNetworkForTesting(base::BindOnce( |
| [](base::OnceClosure callback, |
| net::handles::NetworkHandle bound_network) { |
| EXPECT_EQ(bound_network, network); |
| std::move(callback).Run(); |
| }, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| #else // !BUILDFLAG(IS_ANDROID) |
| GTEST_SKIP() << "proxying bound requests is supported only on Android"; |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TopChromeChromeContentBrowserClientTest, |
| BoundRequestWithOverrideCreatesNetworkContext) { |
| #if BUILDFLAG(IS_ANDROID) |
| constexpr net::handles::NetworkHandle network = 1; |
| network::URLLoaderFactoryBuilder factory_builder; |
| network::mojom::URLLoaderFactoryOverridePtr factory_override; |
| EXPECT_FALSE(factory_override); |
| client()->MaybeProxyNetworkBoundRequest(browser()->profile(), network, |
| factory_builder, &factory_override); |
| EXPECT_EQ( |
| client() |
| ->get_target_network_for_network_bound_network_context_for_testing(), |
| network); |
| EXPECT_TRUE( |
| client()->get_network_bound_network_context_for_testing().is_bound()); |
| EXPECT_TRUE( |
| client()->get_network_bound_network_context_for_testing().is_connected()); |
| EXPECT_TRUE(factory_override->overriding_factory); |
| mojo::Remote<network::mojom::URLLoaderFactory> overridden_factory; |
| overridden_factory.Bind(std::move(factory_override->overriding_factory)); |
| { |
| base::RunLoop run_loop; |
| client() |
| ->get_network_bound_network_context_for_testing() |
| ->GetBoundNetworkForTesting(base::BindOnce( |
| [](base::OnceClosure callback, |
| net::handles::NetworkHandle bound_network) { |
| EXPECT_EQ(bound_network, network); |
| std::move(callback).Run(); |
| }, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| #else // !BUILDFLAG(IS_ANDROID) |
| GTEST_SKIP() << "proxying bound requests is supported only on Android"; |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TopChromeChromeContentBrowserClientTest, |
| ShouldReuseRendererWhenTopChromePagesPresent) { |
| const GURL top_chrome_url(chrome::kChromeUITabSearchURL); |
| const GURL top_chrome_url2(chrome::kChromeUIReadLaterURL); |
| const GURL random_url(GURL("www.google.com")); |
| |
| const auto navigate_browser = [&](const GURL& url, int index) { |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| content::NavigationEntry* entry = browser() |
| ->tab_strip_model() |
| ->GetWebContentsAt(index) |
| ->GetController() |
| .GetLastCommittedEntry(); |
| ASSERT_NE(nullptr, entry); |
| EXPECT_EQ(url, entry->GetURL()); |
| EXPECT_EQ(url, entry->GetVirtualURL()); |
| }; |
| |
| // Navigate to a top chrome URL. |
| navigate_browser(top_chrome_url, 0); |
| |
| // The browser now hosts a top chrome page and the client should return true |
| // to reuse the current renderer Host for the other Top Chrome UI pages. |
| EXPECT_TRUE(client()->ShouldTryToUseExistingProcessHost(browser()->profile(), |
| top_chrome_url2)); |
| |
| // Should not reuse existing process host for pages that are not Top Chrome |
| // UI. |
| EXPECT_FALSE(client()->ShouldTryToUseExistingProcessHost(browser()->profile(), |
| random_url)); |
| } |
| |
| class BundledCodeCacheChromeContentBrowserClientTest |
| : public InProcessBrowserTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| BundledCodeCacheChromeContentBrowserClientTest() { |
| feature_list_.InitWithFeatureState(features::kWebUIBundledCodeCache, |
| IsBundledCodeCacheEnabled()); |
| } |
| |
| bool IsBundledCodeCacheEnabled() const { return GetParam(); } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Assert top-chrome webui renderers disallow v8 feature flag overrides only |
| // when the bundled webui code cache is enabled. |
| IN_PROC_BROWSER_TEST_P(BundledCodeCacheChromeContentBrowserClientTest, |
| ConfiguresRendererForDisallowV8FeatureOverrides) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL top_chrome_url1(chrome::kChromeUITabSearchURL); |
| const GURL top_chrome_url2(chrome::kChromeUIReadLaterURL); |
| const GURL non_top_chrome_url1(chrome::kChromeUINewTabPageURL); |
| const GURL non_top_chrome_url2( |
| embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(top_chrome_url1.DomainIs(chrome::kChromeUITopChromeDomain)); |
| EXPECT_TRUE(top_chrome_url2.DomainIs(chrome::kChromeUITopChromeDomain)); |
| EXPECT_FALSE(non_top_chrome_url1.DomainIs(chrome::kChromeUITopChromeDomain)); |
| EXPECT_FALSE(non_top_chrome_url1.DomainIs(chrome::kChromeUITopChromeDomain)); |
| |
| // Disallow V8 feature flag overrides should only apply to top-chrome URLs |
| // when bundled code caching is enabled. |
| auto navigate_and_expect_policy_result = [this](const GURL& url, |
| bool expectation) { |
| content::RenderFrameHost* rfh = |
| ui_test_utils::NavigateToURL(browser(), url); |
| EXPECT_EQ(expectation, rfh->GetProcess()->DisallowV8FeatureFlagOverrides()); |
| }; |
| navigate_and_expect_policy_result(top_chrome_url1, |
| IsBundledCodeCacheEnabled()); |
| navigate_and_expect_policy_result(top_chrome_url2, |
| IsBundledCodeCacheEnabled()); |
| navigate_and_expect_policy_result(non_top_chrome_url1, false); |
| navigate_and_expect_policy_result(non_top_chrome_url1, false); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| BundledCodeCacheChromeContentBrowserClientTest, |
| ::testing::Bool(), |
| [](const ::testing::TestParamInfo< |
| BundledCodeCacheChromeContentBrowserClientTest::ParamType>& info) { |
| return info.param ? "BundledCodeCache_Enabled" |
| : "BundledCodeCache_Disabled"; |
| }); |
| |
| class DevToolsOverridesThirdPartyCookiesBrowserTest |
| : public InProcessBrowserTest, |
| public content::TestDevToolsProtocolClient { |
| public: |
| DevToolsOverridesThirdPartyCookiesBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| std::vector<base::test::FeatureRefAndParams> enabled_features; |
| std::vector<base::test::FeatureRef> disabled_features; |
| // The 3PCD tracking protection feature must be disabled so that we can |
| // disable third-party cookies by changing the devtools overrides. |
| disabled_features.push_back( |
| content_settings::features::kTrackingProtection3pcd); |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS_CORE) |
| // This feature must be enabled to align the behavior in the test with the |
| // actual behavior in the branded-build. Related bug: crbug.com/385032014. |
| enabled_features.push_back( |
| {extensions_features::kForceWebRequestProxyForTest, {}}); |
| |
| #endif |
| |
| feature_list_.InitWithFeaturesAndParameters(enabled_features, |
| disabled_features); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| https_server_.AddDefaultHandlers(GetChromeTestDataDir()); |
| ASSERT_TRUE(https_server_.Start()); |
| |
| // Open DevTools and enable network agent. |
| AttachToWebContents(web_contents()); |
| SendCommandAsync("Network.enable"); |
| } |
| |
| void TearDownOnMainThread() override { |
| DetachProtocolClient(); |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| content::WebContents* web_contents() const { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| GURL GetURL(std::string_view host) { return https_server_.GetURL(host, "/"); } |
| |
| void NavigateToPageWithFrame(std::string_view host, |
| Browser* browser_ptr = nullptr) { |
| GURL main_url(https_server_.GetURL(host, "/iframe.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser_ptr ? browser_ptr : browser(), main_url)); |
| } |
| |
| void NavigateFrameTo(std::string_view host, std::string_view path) { |
| GURL page = https_server_.GetURL(host, path); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents(), "test", page)); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| net::EmbeddedTestServer https_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsOverridesThirdPartyCookiesBrowserTest, |
| DevToolsForceDisableTPC) { |
| const std::string_view kHostA = "a.test"; |
| const std::string_view kHostB = "b.test"; |
| // Apply devtools overrides to enable 3pc restriction. |
| base::Value::Dict command_params; |
| command_params.Set("enableThirdPartyCookieRestriction", true); |
| command_params.Set("disableThirdPartyCookieMetadata", false); |
| command_params.Set("disableThirdPartyCookieHeuristics", false); |
| SendCommandSync("Network.setCookieControls", std::move(command_params)); |
| |
| NavigateToPageWithFrame(kHostA); |
| // Navigate iframe to a cross-site, cookie-setting endpoint, and verify that |
| // setting 3pc should be blocked due to devtools overrides. |
| NavigateFrameTo(kHostB, "/set-cookie?thirdparty=1;SameSite=None;Secure"); |
| EXPECT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostB)), ""); |
| |
| SendCommandAsync("Network.disable"); |
| // The override should stop working and setting 3pc is re-allowed after |
| // devtools is disabled. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/set-cookie?thirdparty=1;SameSite=None;Secure"); |
| EXPECT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostB)), |
| "thirdparty=1"); |
| } |
| } // namespace |