| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cstdint> |
| |
| #include "base/functional/callback_forward.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/to_string.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_expected_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/test_future.h" |
| #include "base/test/values_test_util.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "components/content_settings/core/common/features.h" |
| #include "components/ukm/content/source_url_recorder.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/browser/btm/btm_browsertest_utils.h" |
| #include "content/browser/btm/btm_service_impl.h" |
| #include "content/browser/btm/btm_storage.h" |
| #include "content/browser/btm/btm_test_utils.h" |
| #include "content/browser/btm/btm_utils.h" |
| #include "content/browser/tpcd_heuristics/opener_heuristic_metrics.h" |
| #include "content/browser/tpcd_heuristics/opener_heuristic_tab_helper.h" |
| #include "content/browser/tpcd_heuristics/opener_heuristic_utils.h" |
| #include "content/common/features.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_content_browser_client.h" |
| #include "content/public/test/hit_test_region_observer.h" |
| #include "content/public/test/render_frame_host_test_support.h" |
| #include "content/public/test/test_devtools_protocol_client.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/shell/browser/shell.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "services/metrics/public/cpp/ukm_source.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| #include "services/network/public/cpp/features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/strings/str_format.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "third_party/blink/public/common/switches.h" |
| #include "ui/base/window_open_disposition.h" |
| |
| using base::test::HasValue; |
| using base::test::ValueIs; |
| using content_settings::features::EnableForIframeTypes; |
| using testing::ElementsAre; |
| using testing::Field; |
| using testing::Optional; |
| using testing::Pair; |
| |
| namespace content { |
| |
| namespace { |
| |
| struct AccessGrantTestCase { |
| bool write_grant_enabled = false; |
| bool disable_for_ad_tagged_popups = false; |
| }; |
| |
| [[maybe_unused]] const AccessGrantTestCase kAccessGrantTestCases[] = { |
| {.write_grant_enabled = false, .disable_for_ad_tagged_popups = false}, |
| {.write_grant_enabled = true, .disable_for_ad_tagged_popups = false}, |
| {.write_grant_enabled = true, .disable_for_ad_tagged_popups = true}}; |
| |
| struct InteractionTimesTestCase { |
| std::string hours_since_activation_time; |
| std::string hours_since_authentication_time; |
| std::string expected_hours_since_interaction; |
| }; |
| |
| // "-1" hours since interaction represents no past interaction |
| const InteractionTimesTestCase InteractionTimeTestCases[] = { |
| {"5", "2", "2"}, |
| {"2", "5", "2"}, |
| {"-1", "2", "2"}, |
| {"5", "-1", "5"}, |
| {"-1", "-1", "-1"}}; |
| |
| enum class InteractionTypeTestCase { |
| USER_ACTIVATION = 0, |
| WEB_AUTHENTICATION = 1 |
| }; |
| |
| const InteractionTypeTestCase InteractionTypeTestCases[] = { |
| InteractionTypeTestCase::USER_ACTIVATION, |
| InteractionTypeTestCase::WEB_AUTHENTICATION}; |
| |
| // Waits for a pop-up to open. |
| class PopupObserver : public WebContentsObserver { |
| public: |
| explicit PopupObserver( |
| WebContents* web_contents, |
| WindowOpenDisposition open_disposition = WindowOpenDisposition::NEW_POPUP) |
| : WebContentsObserver(web_contents), |
| open_disposition_(open_disposition) {} |
| |
| void Wait() { run_loop_.Run(); } |
| WebContents* popup() { return popup_; } |
| |
| private: |
| // WebContentsObserver overrides: |
| void DidOpenRequestedURL(WebContents* new_contents, |
| RenderFrameHost* source_render_frame_host, |
| const GURL& url, |
| const Referrer& referrer, |
| WindowOpenDisposition disposition, |
| ui::PageTransition transition, |
| bool started_from_context_menu, |
| bool renderer_initiated) override { |
| if (!popup_ && disposition == open_disposition_) { |
| popup_ = new_contents; |
| run_loop_.Quit(); |
| } |
| } |
| |
| const WindowOpenDisposition open_disposition_; |
| raw_ptr<WebContents> popup_ = nullptr; |
| base::RunLoop run_loop_; |
| }; |
| |
| } // namespace |
| |
| // SubresourceFilterBrowserTest is necessary to test ad-tagging related |
| // behaviors. |
| class OpenerHeuristicBrowserTest : public ContentBrowserTest, |
| public TestDevToolsProtocolClient { |
| public: |
| OpenerHeuristicBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| // We host the "images" on an HTTPS server, because for it to write a |
| // cookie, the cookie needs to be SameSite=None and Secure. |
| https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| https_server_.AddDefaultHandlers(GetTestDataFilePath()); |
| } |
| |
| void SetUp() override { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{content_settings::features::kTpcdHeuristicsGrants, |
| tpcd_heuristics_grants_params_}, |
| {network::features::kSkipTpcdMitigationsForAds, |
| {{"SkipTpcdMitigationsForAdsHeuristics", "true"}}}, |
| {blink::features::kPartitionedPopins, {}}}, |
| {}); |
| |
| OpenerHeuristicTabHelper::SetClockForTesting(&clock_); |
| ContentBrowserTest::SetUp(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Prevents flakiness by handling clicks even before content is drawn. |
| command_line->AppendSwitch(blink::switches::kAllowPreCommitInput); |
| } |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| ASSERT_TRUE(https_server_.Start()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| |
| // TODO: crbug.com/376625002 - disabled for the move to //content since |
| // SubresourceFilterBrowserTest is unavailable. Either find a way to |
| // implement this test in //content or move it back to //chrome. |
| // |
| // // These rules apply an ad-tagging param to scripts in ad_script.js, |
| // // and to cookies marked with the `isad=1` param value. |
| // SetRulesetWithRules( |
| // {subresource_filter::testing::CreateSuffixRule("ad_script.js"), |
| // subresource_filter::testing::CreateSuffixRule("isad=1")}); |
| |
| BtmServiceImpl::Get(GetActiveWebContents()->GetBrowserContext()) |
| ->SetStorageClockForTesting(&clock_); |
| |
| ukm::InitializeSourceUrlRecorderForWebContents(GetActiveWebContents()); |
| |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), GURL("about:blank"))); |
| // Open and reset DevTools. |
| AttachToWebContents(GetActiveWebContents()); |
| SendCommandSync("Audits.enable"); |
| ClearNotifications(); |
| } |
| |
| void TearDownOnMainThread() override { |
| ASSERT_TRUE(https_server_.ShutdownAndWaitUntilComplete()); |
| |
| DetachProtocolClient(); |
| } |
| |
| WebContents* GetActiveWebContents() { return shell()->web_contents(); } |
| |
| OpenerHeuristicTabHelper* GetTabHelper() { |
| return OpenerHeuristicTabHelper::FromWebContents(GetActiveWebContents()); |
| } |
| |
| BtmServiceImpl* GetBtmService() { |
| return BtmServiceImpl::Get(GetActiveWebContents()->GetBrowserContext()); |
| } |
| |
| void RecordUserActivationInteraction(const GURL& url, base::Time time) { |
| auto* btm = GetBtmService(); |
| btm->storage() |
| ->AsyncCall(&BtmStorage::RecordUserActivation) |
| .WithArgs(url, time); |
| btm->storage()->FlushPostedTasksForTesting(); |
| } |
| |
| void RecordAuthenticationInteraction(const GURL& url, base::Time time) { |
| auto* btm = GetBtmService(); |
| btm->storage() |
| ->AsyncCall(&BtmStorage::RecordWebAuthnAssertion) |
| .WithArgs(url, time); |
| btm->storage()->FlushPostedTasksForTesting(); |
| } |
| |
| // Open a popup window, navigate it to `url`, and return its WebContents. |
| base::expected<WebContents*, std::string> OpenPopup(const GURL& url) { |
| return OpenPopup(url, url); |
| } |
| |
| // Open a popup window, start a navigation to `initial_url`, confirm that it |
| // lands on `final_url`, and return its WebContents. |
| base::expected<WebContents*, std::string> OpenPopup(const GURL& initial_url, |
| const GURL& final_url) { |
| auto* web_contents = GetActiveWebContents(); |
| if (web_contents->GetLastCommittedURL().is_empty()) { |
| // We can't call window.open() if we're not on a page. Go to about:blank. |
| if (!NavigateToURL(web_contents, GURL("about:blank"))) { |
| return base::unexpected("failed to navigate to about:blank"); |
| } |
| } |
| PopupObserver observer(web_contents); |
| if (!ExecJs(web_contents, |
| JsReplace("window.open($1, '', 'popup');", initial_url))) { |
| return base::unexpected("window.open failed"); |
| } |
| observer.Wait(); |
| |
| // Wait for the popup to finish navigating to its initial URL. |
| if (!WaitForLoadStop(observer.popup())) { |
| return base::unexpected("popup navigation failed"); |
| } |
| |
| if (observer.popup()->GetLastCommittedURL() != final_url) { |
| return base::unexpected(absl::StrFormat( |
| "popup navigated to %s (expected %s)", |
| observer.popup()->GetLastCommittedURL().spec(), final_url.spec())); |
| } |
| |
| // Wait for the read of the past interaction from the DIPS DB to complete, |
| // so the PopupPastInteraction UKM event is reported. |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| return observer.popup(); |
| } |
| |
| // Open a popup window with the given URL, using an ad-tagged script, and |
| // return its WebContents. |
| base::expected<WebContents*, std::string> OpenAdTaggedPopup(const GURL& url) { |
| WebContentsAddedObserver wca_observer; |
| |
| ExecuteScriptAsync(GetActiveWebContents(), |
| JsReplace("windowOpenFromAdScript($1)", url)); |
| |
| WebContents* new_web_contents = wca_observer.GetWebContents(); |
| TestNavigationObserver navigation_observer(new_web_contents); |
| navigation_observer.Wait(); |
| if (!navigation_observer.last_navigation_succeeded()) { |
| return base::unexpected("windowOpenFromAdScript failed"); |
| } |
| |
| return new_web_contents; |
| } |
| |
| // Navigate a (possibly nested) iframe of `parent_frame` to `url`. Return true |
| // iff navigation is successful. |
| [[nodiscard]] bool NavigateIframeTo(RenderFrameHost* parent_frame, |
| const GURL& url) { |
| TestNavigationObserver load_observer(GetActiveWebContents()); |
| std::string script = base::StringPrintf( |
| "var iframe = document.getElementById('test_iframe');iframe.src='%s';", |
| url.spec().c_str()); |
| if (!ExecJs(parent_frame, script, EXECUTE_SCRIPT_NO_USER_GESTURE)) { |
| return false; |
| } |
| load_observer.Wait(); |
| return load_observer.last_navigation_succeeded(); |
| } |
| |
| void SimulateMouseClick(WebContents* web_contents) { |
| WaitForHitTestData(web_contents->GetPrimaryMainFrame()); |
| UserActivationObserver observer(web_contents, |
| web_contents->GetPrimaryMainFrame()); |
| ::content::SimulateMouseClick(web_contents, 0, |
| blink::WebMouseEvent::Button::kLeft); |
| observer.Wait(); |
| } |
| |
| void SimulateWebAuthenticationAssertion(WebContents* web_contents) { |
| WebAuthnAssertionRequestSucceeded(web_contents->GetPrimaryMainFrame()); |
| } |
| |
| void DestroyWebContents(WebContents* web_contents) { |
| WebContentsDestroyedWatcher destruction_watcher(web_contents); |
| web_contents->Close(); |
| destruction_watcher.Wait(); |
| } |
| |
| base::expected<OptionalBool, std::string> GetOpenerHasSameSiteIframe( |
| ukm::TestUkmRecorder& ukm_recorder, |
| const std::string& entry_name) { |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries(entry_name, {"OpenerHasSameSiteIframe"}); |
| if (entries.size() != 1) { |
| return base::unexpected( |
| base::StringPrintf("Expected 1 %s entry, found %zu", |
| entry_name.c_str(), entries.size())); |
| } |
| return static_cast<OptionalBool>( |
| entries[0].metrics["OpenerHasSameSiteIframe"]); |
| } |
| |
| std::optional<PopupsStateValue> GetPopupState(const GURL& opener_url, |
| const GURL& popup_url) { |
| std::optional<PopupsStateValue> state; |
| |
| GetBtmService() |
| ->storage() |
| ->AsyncCall(&BtmStorage::ReadPopup) |
| .WithArgs(GetSiteForBtm(opener_url), GetSiteForBtm(popup_url)) |
| .Then(base::BindLambdaForTesting( |
| [&state](std::optional<PopupsStateValue> db_state) { |
| state = db_state; |
| })); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| return state; |
| } |
| |
| net::EmbeddedTestServer https_server_; |
| base::FieldTrialParams tpcd_heuristics_grants_params_; |
| base::SimpleTestClock clock_; |
| base::test::ScopedFeatureList feature_list_; |
| |
| protected: |
| void WaitForCookieIssueAndCheck(std::string_view third_party_site, |
| std::string_view warning, |
| std::string_view exclusion) { |
| CHECK(warning.empty() || exclusion.empty()) |
| << "inclusion reason and exclusion reason should not co-exist"; |
| auto is_cookie_issue = [](const base::Value::Dict& params) { |
| const std::string* issue_code = |
| params.FindStringByDottedPath("issue.code"); |
| return issue_code && *issue_code == "CookieIssue"; |
| }; |
| |
| // Wait for notification of a Cookie Issue. |
| base::Value::Dict params = WaitForMatchingNotification( |
| "Audits.issueAdded", base::BindRepeating(is_cookie_issue)); |
| |
| std::string_view reason_name = |
| warning.empty() ? "cookieExclusionReasons" : "cookieWarningReasons"; |
| std::string_view reason_value = warning.empty() ? exclusion : warning; |
| std::string partial_expected = |
| JsReplace(R"({ |
| "cookie": { |
| "domain": $1, |
| "name": "name", |
| "path": "/" |
| }, |
| $2: [ $3 ], |
| "operation": "ReadCookie", |
| })", |
| third_party_site, reason_name, reason_value); |
| |
| // Find relevant fields from cookieIssueDetails |
| ASSERT_THAT(params.FindDictByDottedPath("issue.details.cookieIssueDetails"), |
| testing::Pointee(base::test::DictionaryHasValues( |
| base::test::ParseJsonDict(partial_expected)))); |
| |
| ClearNotifications(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| RootWindowDoesntHavePopupState) { |
| ASSERT_FALSE(GetTabHelper()->popup_observer_for_testing()); |
| } |
| |
| // TODO(crbug.com/40276065): Test is flaky on Android. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PopupsWithOpenerHavePopupState \ |
| DISABLED_PopupsWithOpenerHavePopupState |
| #else |
| #define MAYBE_PopupsWithOpenerHavePopupState PopupsWithOpenerHavePopupState |
| #endif |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| MAYBE_PopupsWithOpenerHavePopupState) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| PopupObserver observer(web_contents); |
| ASSERT_TRUE(ExecJs(web_contents, |
| JsReplace("window.open($1, '', 'popup');", popup_url))); |
| observer.Wait(); |
| |
| auto* popup_tab_helper = |
| OpenerHeuristicTabHelper::FromWebContents(observer.popup()); |
| ASSERT_TRUE(popup_tab_helper); |
| ASSERT_TRUE(popup_tab_helper->popup_observer_for_testing()); |
| } |
| |
| // TODO(crbug.com/40925352): Flaky on android. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PopupsWithoutOpenerDoNotHavePopupState \ |
| DISABLED_PopupsWithoutOpenerDoNotHavePopupState |
| #else |
| #define MAYBE_PopupsWithoutOpenerDoNotHavePopupState \ |
| PopupsWithoutOpenerDoNotHavePopupState |
| #endif |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| MAYBE_PopupsWithoutOpenerDoNotHavePopupState) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| PopupObserver observer(web_contents); |
| ASSERT_TRUE( |
| ExecJs(web_contents, |
| JsReplace("window.open($1, '', 'popup,noopener');", popup_url))); |
| observer.Wait(); |
| |
| auto* popup_tab_helper = |
| OpenerHeuristicTabHelper::FromWebContents(observer.popup()); |
| ASSERT_TRUE(popup_tab_helper); |
| ASSERT_FALSE(popup_tab_helper->popup_observer_for_testing()); |
| } |
| |
| // TODO(crbug.com/40925352): Flaky on android. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PopinsDoNotHavePopupState DISABLED_PopinsDoNotHavePopupState |
| #else |
| #define MAYBE_PopinsDoNotHavePopupState PopinsDoNotHavePopupState |
| #endif |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| MAYBE_PopinsDoNotHavePopupState) { |
| GURL https_url = https_server_.GetURL("a.test", "/title1.html"); |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| // Initialize popup and interaction. |
| ASSERT_TRUE(NavigateToURL(web_contents, https_url)); |
| |
| PopupObserver observer(web_contents); |
| ASSERT_TRUE(ExecJs(web_contents, |
| JsReplace("window.open($1, '', 'popin');", https_url))); |
| observer.Wait(); |
| |
| auto* popup_tab_helper = |
| OpenerHeuristicTabHelper::FromWebContents(observer.popup()); |
| ASSERT_TRUE(popup_tab_helper); |
| ASSERT_FALSE(popup_tab_helper->popup_observer_for_testing()); |
| } |
| |
| // TODO(crbug.com/40925352): Flaky on android. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_NewTabURLsHavePopupState DISABLED_NewTabURLsHavePopupState |
| #else |
| #define MAYBE_NewTabURLsHavePopupState NewTabURLsHavePopupState |
| #endif |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| MAYBE_NewTabURLsHavePopupState) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| PopupObserver observer(web_contents, |
| WindowOpenDisposition::NEW_FOREGROUND_TAB); |
| ASSERT_TRUE(ExecJs(web_contents, JsReplace("window.open($1);", popup_url))); |
| observer.Wait(); |
| |
| auto* popup_tab_helper = |
| OpenerHeuristicTabHelper::FromWebContents(observer.popup()); |
| ASSERT_TRUE(popup_tab_helper); |
| ASSERT_TRUE(popup_tab_helper->popup_observer_for_testing()); |
| } |
| |
| class OpenerHeuristicIframeInitiatorBrowserTest |
| : public OpenerHeuristicBrowserTest, |
| public testing::WithParamInterface< |
| ::testing::tuple<EnableForIframeTypes, bool>> { |
| public: |
| OpenerHeuristicIframeInitiatorBrowserTest() |
| : iframe_types_flag_(std::get<0>(GetParam())), |
| is_nested_iframe_(std::get<1>(GetParam())) { |
| for (const auto [val, str] : |
| content_settings::features::kEnableForIframeTypesOptions) { |
| if (val == iframe_types_flag_) { |
| tpcd_heuristics_grants_params_ |
| ["TpcdPopupHeuristicEnableForIframeInitiator"] = str; |
| } |
| } |
| } |
| |
| const EnableForIframeTypes iframe_types_flag_; |
| const bool is_nested_iframe_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P( |
| OpenerHeuristicIframeInitiatorBrowserTest, |
| URLsInitiatedByFirstPartyIframes_HavePopupStateWithFlag) { |
| WebContents* web_contents = GetActiveWebContents(); |
| const GURL opener_primary_frame_url = |
| embedded_test_server()->GetURL("a.test", "/page_with_blank_iframe.html"); |
| const GURL opener_iframe_url = |
| embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL popup_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, opener_primary_frame_url)); |
| RenderFrameHost* parent_frame = web_contents->GetPrimaryMainFrame(); |
| ASSERT_TRUE(NavigateIframeTo(parent_frame, opener_primary_frame_url)); |
| if (is_nested_iframe_) { |
| parent_frame = ChildFrameAt(parent_frame, 0); |
| ASSERT_TRUE(NavigateIframeTo(parent_frame, opener_iframe_url)); |
| } |
| |
| PopupObserver observer(web_contents, |
| WindowOpenDisposition::NEW_FOREGROUND_TAB); |
| ASSERT_TRUE(ExecJs(ChildFrameAt(parent_frame, 0), |
| JsReplace("window.open($1);", popup_url))); |
| observer.Wait(); |
| |
| auto* popup_tab_helper = |
| OpenerHeuristicTabHelper::FromWebContents(observer.popup()); |
| ASSERT_TRUE(popup_tab_helper); |
| |
| if (iframe_types_flag_ == EnableForIframeTypes::kNone) { |
| ASSERT_FALSE(popup_tab_helper->popup_observer_for_testing()); |
| } else { |
| ASSERT_TRUE(popup_tab_helper->popup_observer_for_testing()); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| OpenerHeuristicIframeInitiatorBrowserTest, |
| URLsInitiatedByThirdPartyIframes_HavePopupStateWithFlag) { |
| WebContents* web_contents = GetActiveWebContents(); |
| const GURL opener_1p_frame_url = |
| embedded_test_server()->GetURL("a.test", "/page_with_blank_iframe.html"); |
| const GURL opener_3p_frame_url = |
| embedded_test_server()->GetURL("b.test", "/page_with_blank_iframe.html"); |
| GURL popup_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, opener_1p_frame_url)); |
| RenderFrameHost* parent_frame = web_contents->GetPrimaryMainFrame(); |
| ASSERT_TRUE(NavigateIframeTo(parent_frame, opener_3p_frame_url)); |
| if (is_nested_iframe_) { |
| parent_frame = ChildFrameAt(parent_frame, 0); |
| ASSERT_TRUE(NavigateIframeTo(parent_frame, opener_1p_frame_url)); |
| } |
| |
| PopupObserver observer(web_contents, |
| WindowOpenDisposition::NEW_FOREGROUND_TAB); |
| ASSERT_TRUE(ExecJs(ChildFrameAt(parent_frame, 0), |
| JsReplace("window.open($1);", popup_url))); |
| observer.Wait(); |
| |
| auto* popup_tab_helper = |
| OpenerHeuristicTabHelper::FromWebContents(observer.popup()); |
| ASSERT_TRUE(popup_tab_helper); |
| |
| if (iframe_types_flag_ == EnableForIframeTypes::kAll) { |
| ASSERT_TRUE(popup_tab_helper->popup_observer_for_testing()); |
| } else { |
| ASSERT_FALSE(popup_tab_helper->popup_observer_for_testing()); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| OpenerHeuristicIframeInitiatorBrowserTest, |
| ::testing::Combine(::testing::Values(EnableForIframeTypes::kNone, |
| EnableForIframeTypes::kFirstParty, |
| EnableForIframeTypes::kAll), |
| ::testing::Bool())); |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| PopupPastInteractionIsReported_WithoutInteraction) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| // Note: no previous interaction on a.test. |
| |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction", |
| {"HoursSinceLastInteraction", "InteractionType"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| popup_url); |
| // Since there was no prior or current interaction, the |
| // HoursSinceLastInteraction field is set to -1. |
| EXPECT_EQ(entries[0].metrics["HoursSinceLastInteraction"], -1); |
| EXPECT_EQ(entries[0].metrics["InteractionType"], |
| static_cast<int32_t>(BtmInteractionType::NoInteraction)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| PopupPastInteractionIsReported_WithoutRedirect) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| RecordUserActivationInteraction(GURL("https://a.test"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction", |
| {"HoursSinceLastInteraction"}); |
| ASSERT_EQ(entries.size(), 1u); |
| // Since the user landed on the page the popup was opened to, the UKM event |
| // has source type NAVIGATION_ID. |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| popup_url); |
| EXPECT_THAT(entries[0].metrics, |
| ElementsAre(Pair("HoursSinceLastInteraction", 3))); |
| } |
| |
| class OpenerHeuristicMultiplePastInteractionTypesBrowserTest |
| : public OpenerHeuristicBrowserTest, |
| public testing::WithParamInterface<InteractionTimesTestCase> { |
| public: |
| OpenerHeuristicMultiplePastInteractionTypesBrowserTest() = default; |
| |
| void MaybeRecordUserActivation(GURL url) { |
| int hours_since_activation; |
| ASSERT_TRUE(base::StringToInt(GetParam().hours_since_activation_time, |
| &hours_since_activation)); |
| if (hours_since_activation >= 0) { |
| RecordUserActivationInteraction( |
| url, clock_.Now() - base::Hours(hours_since_activation)); |
| } |
| } |
| |
| void MaybeRecordUserAuthentication(GURL url) { |
| int hours_since_authentication; |
| ASSERT_TRUE(base::StringToInt(GetParam().hours_since_authentication_time, |
| &hours_since_authentication)); |
| if (hours_since_authentication >= 0) { |
| RecordAuthenticationInteraction( |
| url, clock_.Now() - base::Hours(hours_since_authentication)); |
| } |
| } |
| |
| void getExpectedHoursSinceLastInteraction( |
| int& expected_hours_since_last_interaction) const { |
| ASSERT_TRUE(base::StringToInt(GetParam().expected_hours_since_interaction, |
| &expected_hours_since_last_interaction)); |
| } |
| |
| void SetUpOnMainThread() override { |
| OpenerHeuristicBrowserTest::SetUpOnMainThread(); |
| |
| browser_client_.emplace(); |
| browser_client()->SetBlockThirdPartyCookiesByDefault(true); |
| } |
| |
| TpcBlockingBrowserClient* browser_client() { |
| return &browser_client_->impl(); |
| } |
| |
| private: |
| // browser_client_ is wrapped in optional<> to delay construction -- it won't |
| // be registered properly if it's created too early. |
| std::optional<ContentBrowserTestTpcBlockingBrowserClient> browser_client_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(OpenerHeuristicMultiplePastInteractionTypesBrowserTest, |
| PopupPastInteractionIsReported_MostRecentInteraction) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| MaybeRecordUserActivation(GURL("https://a.test")); |
| MaybeRecordUserAuthentication(GURL("https://a.test")); |
| |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction", |
| {"HoursSinceLastInteraction"}); |
| // Only one interaction is recorded |
| ASSERT_EQ(entries.size(), 1u); |
| |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| popup_url); |
| // Only most recent interaction was reported regardless of interaction type |
| int expected_hours_since_last_interaction = 0; |
| getExpectedHoursSinceLastInteraction(expected_hours_since_last_interaction); |
| EXPECT_THAT(entries[0].metrics, |
| ElementsAre(Pair("HoursSinceLastInteraction", |
| expected_hours_since_last_interaction))); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| OpenerHeuristicMultiplePastInteractionTypesBrowserTest, |
| ::testing::ValuesIn(InteractionTimeTestCases)); |
| |
| // chrome/browser/ui/browser.h (for changing profile prefs) is not available on |
| // Android. |
| #if !BUILDFLAG(IS_ANDROID) |
| class OpenerHeuristicPastInteractionGrantBrowserTest |
| : public OpenerHeuristicBrowserTest, |
| public testing::WithParamInterface<AccessGrantTestCase> { |
| public: |
| OpenerHeuristicPastInteractionGrantBrowserTest() { |
| tpcd_heuristics_grants_params_ |
| ["TpcdWritePopupPastInteractionHeuristicsGrants"] = |
| GetParam().write_grant_enabled ? "20m" : "0s"; |
| tpcd_heuristics_grants_params_ |
| ["TpcdPopupHeuristicDisableForAdTaggedPopups"] = |
| base::ToString(GetParam().disable_for_ad_tagged_popups); |
| } |
| |
| void SetUpOnMainThread() override { |
| OpenerHeuristicBrowserTest::SetUpOnMainThread(); |
| |
| browser_client_.emplace(); |
| browser_client()->SetBlockThirdPartyCookiesByDefault(true); |
| } |
| |
| TpcBlockingBrowserClient* browser_client() { |
| return &browser_client_->impl(); |
| } |
| |
| private: |
| std::optional<ContentBrowserTestTpcBlockingBrowserClient> browser_client_; |
| }; |
| |
| namespace { |
| bool IsFullCookieAccessAllowed(WebContents* web_contents, |
| const GURL& url, |
| const GURL& first_party_url) { |
| return GetContentClientForTesting()->browser()->IsFullCookieAccessAllowed( |
| web_contents->GetBrowserContext(), web_contents, url, |
| blink::StorageKey::CreateFirstParty(url::Origin::Create(first_party_url)), |
| /*overrides=*/{}); |
| } |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_P(OpenerHeuristicPastInteractionGrantBrowserTest, |
| PopupPastInteractionIsReported_WithStorageAccessGrant) { |
| GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL initial_url = embedded_test_server()->GetURL( |
| "b.test", "/cross-site/c.test/title1.html"); |
| GURL final_url = embedded_test_server()->GetURL("c.test", "/title1.html"); |
| RecordUserActivationInteraction(initial_url, clock_.Now() - base::Hours(3)); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_THAT(OpenPopup(initial_url, final_url), HasValue()); |
| |
| // Expect that cookie access was granted for the Popup With Past Interaction |
| // heuristic, if the feature is enabled. |
| EXPECT_EQ(IsFullCookieAccessAllowed(GetActiveWebContents(), initial_url, |
| opener_url), |
| GetParam().write_grant_enabled); |
| // Cookie access was NOT granted for the site that the popup redirected to. |
| EXPECT_FALSE( |
| IsFullCookieAccessAllowed(GetActiveWebContents(), final_url, opener_url)); |
| } |
| |
| // TODO: crbug.com/376625002 - disabled for the move to //content since |
| // SubresourceFilterBrowserTest is unavailable. Either find a way to implement |
| // this test in //content or move it back to //chrome. |
| // |
| // TODO(crbug.com/40947612) Flaky on mac. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_AdTaggedPopupPastInteractionIsReported_WithStorageAccessGrant \ |
| DISABLED_AdTaggedPopupPastInteractionIsReported_WithStorageAccessGrant |
| #else |
| #define MAYBE_AdTaggedPopupPastInteractionIsReported_WithStorageAccessGrant \ |
| AdTaggedPopupPastInteractionIsReported_WithStorageAccessGrant |
| #endif |
| IN_PROC_BROWSER_TEST_P( |
| OpenerHeuristicPastInteractionGrantBrowserTest, |
| DISABLED_AdTaggedPopupPastInteractionIsReported_WithStorageAccessGrant) { |
| GURL opener_url = |
| embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html"); |
| GURL popup_url = embedded_test_server()->GetURL("c.com", "/title1.html"); |
| RecordUserActivationInteraction(popup_url, clock_.Now() - base::Hours(3)); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_THAT(OpenAdTaggedPopup(popup_url), HasValue()); |
| |
| // Expect that cookie access was granted for the ad-tagged Popup With Past |
| // Interaction heuristic, only if the flag is *off*. |
| bool should_cookies_be_blocked = !GetParam().write_grant_enabled || |
| GetParam().disable_for_ad_tagged_popups; |
| EXPECT_EQ( |
| IsFullCookieAccessAllowed(GetActiveWebContents(), popup_url, opener_url), |
| !should_cookies_be_blocked); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| OpenerHeuristicPastInteractionGrantBrowserTest, |
| ::testing::ValuesIn(kAccessGrantTestCases)); |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // TODO(crbug.com/40918571): Test is flaky on Android. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PopupPastInteractionIsReported_ServerRedirect \ |
| DISABLED_PopupPastInteractionIsReported_ServerRedirect |
| #else |
| #define MAYBE_PopupPastInteractionIsReported_ServerRedirect \ |
| PopupPastInteractionIsReported_ServerRedirect |
| #endif |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| MAYBE_PopupPastInteractionIsReported_ServerRedirect) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = |
| embedded_test_server()->GetURL("a.test", "/server-redirect?title1.html"); |
| GURL final_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| RecordUserActivationInteraction(GURL("https://a.test"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_THAT(OpenPopup(popup_url, final_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction", |
| {"HoursSinceLastInteraction"}); |
| ASSERT_EQ(entries.size(), 1u); |
| // Server redirect causes the UKM event to have source type REDIRECT_ID. |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::REDIRECT_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| popup_url); |
| EXPECT_THAT(entries[0].metrics, |
| ElementsAre(Pair("HoursSinceLastInteraction", 3))); |
| } |
| |
| // TODO(crbug.com/40282438): Flaky on Android. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PopupPastInteractionIsReported_ClientRedirect \ |
| DISABLED_PopupPastInteractionIsReported_ClientRedirect |
| #else |
| #define MAYBE_PopupPastInteractionIsReported_ClientRedirect \ |
| PopupPastInteractionIsReported_ClientRedirect |
| #endif |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| MAYBE_PopupPastInteractionIsReported_ClientRedirect) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL final_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| |
| RecordUserActivationInteraction(GURL("https://a.test"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url)); |
| // Perform a client-side redirect. |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(popup, final_url)); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction", |
| {"HoursSinceLastInteraction"}); |
| ASSERT_EQ(entries.size(), 1u); |
| // With a client redirect, we still get a source of type NAVIGATION_ID (since |
| // the URL committed). |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| popup_url); |
| EXPECT_THAT(entries[0].metrics, |
| ElementsAre(Pair("HoursSinceLastInteraction", 3))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| PopupPastInteractionIsReportedOnlyOnce) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| |
| RecordUserActivationInteraction(GURL("https://a.test"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url)); |
| |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupPastInteraction") |
| .size(), |
| 1u); |
| |
| ASSERT_TRUE(NavigateToURL( |
| popup, embedded_test_server()->GetURL("b.test", "/title1.html"))); |
| |
| // After another navigation, PopupPastInteraction isn't reported again (i.e., |
| // still once total). |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupPastInteraction") |
| .size(), |
| 1u); |
| } |
| |
| class OpenerHeuristicInteractionTypesBrowserTest |
| : public OpenerHeuristicBrowserTest, |
| public testing::WithParamInterface<InteractionTypeTestCase> { |
| public: |
| OpenerHeuristicInteractionTypesBrowserTest() = default; |
| |
| void RecordPastInteraction(const GURL& url, base::Time time) { |
| if (GetParam() == InteractionTypeTestCase::USER_ACTIVATION) { |
| RecordUserActivationInteraction(url, time); |
| } else if (GetParam() == InteractionTypeTestCase::WEB_AUTHENTICATION) { |
| RecordAuthenticationInteraction(url, time); |
| } |
| } |
| |
| void SimulateInteraction(WebContents* web_contents) { |
| if (GetParam() == InteractionTypeTestCase::USER_ACTIVATION) { |
| SimulateMouseClick(web_contents); |
| } else if (GetParam() == InteractionTypeTestCase::WEB_AUTHENTICATION) { |
| SimulateWebAuthenticationAssertion(web_contents); |
| } |
| } |
| |
| bool isAuthenticationInteraction() { |
| return GetParam() == InteractionTypeTestCase::WEB_AUTHENTICATION; |
| } |
| |
| void SetUpOnMainThread() override { |
| OpenerHeuristicBrowserTest::SetUpOnMainThread(); |
| |
| browser_client_.emplace(); |
| browser_client()->SetBlockThirdPartyCookiesByDefault(true); |
| } |
| |
| TpcBlockingBrowserClient* browser_client() { |
| return &browser_client_->impl(); |
| } |
| |
| base::Time Hours; |
| base::Time UserAuthenticationTime; |
| |
| private: |
| std::optional<ContentBrowserTestTpcBlockingBrowserClient> browser_client_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| OpenerHeuristicInteractionTypesBrowserTest, |
| ::testing::ValuesIn(InteractionTypeTestCases)); |
| |
| IN_PROC_BROWSER_TEST_P(OpenerHeuristicInteractionTypesBrowserTest, |
| PopupPastInteractionIsFollowedByPostPopupCookieAccess) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL popup_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| browser_client()->AllowThirdPartyCookiesOnSite(opener_url); |
| |
| // Initialize interaction and popup. |
| RecordPastInteraction(popup_url, clock_.Now() - base::Hours(3)); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| // Assert that the UKM events and DIPS entries were recorded. |
| int64_t access_id; |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupPastInteraction") |
| .size(), |
| 1u); |
| auto top_level_entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", {"AccessId"}); |
| ASSERT_EQ(top_level_entries.size(), 1u); |
| EXPECT_EQ( |
| ukm_recorder.GetSourceForSourceId(top_level_entries[0].source_id)->url(), |
| opener_url); |
| access_id = top_level_entries[0].metrics["AccessId"]; |
| |
| base::OnceCallback<void(std::optional<PopupsStateValue>)> assert_popup = |
| base::BindLambdaForTesting([&](std::optional<PopupsStateValue> state) { |
| ASSERT_TRUE(state.has_value()); |
| EXPECT_EQ(access_id, static_cast<int64_t>(state->access_id)); |
| }); |
| GetBtmService() |
| ->storage() |
| ->AsyncCall(&BtmStorage::ReadPopup) |
| .WithArgs(GetSiteForBtm(opener_url), GetSiteForBtm(popup_url)) |
| .Then(std::move(assert_popup)); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| // Add a cookie access by popup_url on opener_url. |
| ASSERT_TRUE(NavigateToSetCookie(GetActiveWebContents(), &https_server_, |
| "sub.b.test", |
| /*is_secure_cookie_set=*/true, |
| /*is_ad_tagged=*/true)); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| CreateImageAndWaitForCookieAccess( |
| GetActiveWebContents(), |
| https_server_.GetURL("sub.b.test", "/favicon/icon.png?isad=1")); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| // Assert that the UKM event for the PostPopupCookieAccess was recorded. |
| auto access_entries = ukm_recorder.GetEntries( |
| "OpenerHeuristic.PostPopupCookieAccess", |
| {"AccessId", "AccessSucceeded", "IsAdTagged", "HoursSincePopupOpened"}); |
| ASSERT_EQ(access_entries.size(), 1u); |
| EXPECT_EQ( |
| ukm_recorder.GetSourceForSourceId(access_entries[0].source_id)->url(), |
| opener_url); |
| EXPECT_EQ(access_entries[0].metrics["AccessId"], access_id); |
| EXPECT_EQ(access_entries[0].metrics["AccessSucceeded"], true); |
| // TODO: crbug.com/376625002 - disabled for the move to //content since |
| // SubresourceFilterBrowserTest is unavailable. Either find a way to implement |
| // this test in //content or move it back to //chrome. |
| // EXPECT_EQ(access_entries[0].metrics["IsAdTagged"], |
| // static_cast<int32_t>(OptionalBool::kTrue)); |
| EXPECT_EQ(access_entries[0].metrics["HoursSincePopupOpened"], 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(OpenerHeuristicInteractionTypesBrowserTest, |
| PopupInteraction) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL redirect_url = |
| embedded_test_server()->GetURL("b.test", "/server-redirect?title1.html"); |
| GURL final_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url)); |
| |
| clock_.Advance(base::Minutes(1)); |
| ASSERT_TRUE(NavigateToURL(popup, redirect_url, final_url)); |
| |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(), |
| 0u); |
| |
| clock_.Advance(base::Minutes(1)); |
| SimulateInteraction(popup); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries( |
| "OpenerHeuristic.PopupInteraction", |
| {"SecondsSinceCommitted", "UrlIndex", "InteractionType"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| final_url); |
| // The time between *popup_url* committing and the click. |
| EXPECT_EQ(entries[0].metrics["SecondsSinceCommitted"], |
| Bucketize3PCDHeuristicSample(base::Minutes(2).InSeconds(), |
| base::Minutes(3).InSeconds())); |
| // The user clicked on *final_url*, which was the third URL. |
| EXPECT_EQ(entries[0].metrics["UrlIndex"], 3); |
| EXPECT_EQ(entries[0].metrics["InteractionType"], |
| isAuthenticationInteraction() |
| ? static_cast<int32_t>(BtmInteractionType::Authentication) |
| : static_cast<int32_t>(BtmInteractionType::UserActivation)); |
| } |
| |
| // chrome/browser/ui/browser.h (for changing profile prefs) is not available on |
| // Android. |
| #if !BUILDFLAG(IS_ANDROID) |
| class OpenerHeuristicCurrentInteractionGrantBrowserTest |
| : public OpenerHeuristicBrowserTest, |
| public testing::WithParamInterface<AccessGrantTestCase> { |
| public: |
| OpenerHeuristicCurrentInteractionGrantBrowserTest() { |
| tpcd_heuristics_grants_params_ |
| ["TpcdWritePopupCurrentInteractionHeuristicsGrants"] = |
| GetParam().write_grant_enabled ? "20m" : "0s"; |
| tpcd_heuristics_grants_params_ |
| ["TpcdPopupHeuristicDisableForAdTaggedPopups"] = |
| base::ToString(GetParam().disable_for_ad_tagged_popups); |
| } |
| |
| void SetUpOnMainThread() override { |
| OpenerHeuristicBrowserTest::SetUpOnMainThread(); |
| |
| browser_client_.emplace(); |
| browser_client()->SetBlockThirdPartyCookiesByDefault(true); |
| } |
| |
| TpcBlockingBrowserClient* browser_client() { |
| return &browser_client_->impl(); |
| } |
| |
| private: |
| std::optional<ContentBrowserTestTpcBlockingBrowserClient> browser_client_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(OpenerHeuristicCurrentInteractionGrantBrowserTest, |
| PopupInteractionWithStorageAccessGrant) { |
| GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL initial_url = embedded_test_server()->GetURL( |
| "b.test", "/cross-site/c.test/title1.html"); |
| GURL final_url = embedded_test_server()->GetURL("c.test", "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(initial_url, final_url)); |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| |
| // Cookie access was NOT granted for the initial URL (that the user didn't |
| // interact with). |
| EXPECT_FALSE(IsFullCookieAccessAllowed(GetActiveWebContents(), initial_url, |
| opener_url)); |
| // Cookie access WAS granted for the interacted-with URL (if the feature was |
| // enabled). |
| EXPECT_EQ( |
| IsFullCookieAccessAllowed(GetActiveWebContents(), final_url, opener_url), |
| GetParam().write_grant_enabled); |
| } |
| |
| // TODO: crbug.com/376625002 - disabled for the move to //content since |
| // SubresourceFilterBrowserTest is unavailable. Either find a way to implement |
| // this test in //content or move it back to //chrome. |
| IN_PROC_BROWSER_TEST_P( |
| OpenerHeuristicCurrentInteractionGrantBrowserTest, |
| DISABLED_AdTaggedPopupInteractionWithStorageAccessGrant) { |
| GURL opener_url = |
| embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html"); |
| GURL popup_url = embedded_test_server()->GetURL("c.com", "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenAdTaggedPopup(popup_url)); |
| |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| |
| // Expect that cookie access was granted for the ad-tagged Popup With Current |
| // Interaction heuristic, only if the flag is *off*. |
| bool should_cookies_be_blocked = !GetParam().write_grant_enabled || |
| GetParam().disable_for_ad_tagged_popups; |
| EXPECT_EQ( |
| IsFullCookieAccessAllowed(GetActiveWebContents(), popup_url, opener_url), |
| !should_cookies_be_blocked); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| OpenerHeuristicCurrentInteractionGrantBrowserTest, |
| ::testing::ValuesIn(kAccessGrantTestCases)); |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| PopupInteractionIsOnlyReportedOnce) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL interaction_url = |
| embedded_test_server()->GetURL("b.test", "/title1.html"); |
| GURL final_url = embedded_test_server()->GetURL("c.test", "/title1.html"); |
| |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url)); |
| |
| ASSERT_TRUE(NavigateToURL(popup, interaction_url)); |
| SimulateMouseClick(popup); |
| |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(), |
| 1u); |
| |
| SimulateWebAuthenticationAssertion(popup); |
| |
| // The second interaction on same URL is not reported. |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(), |
| 1u); |
| |
| ASSERT_TRUE(NavigateToURL(popup, final_url)); |
| SimulateMouseClick(popup); |
| |
| // An additional click on a different site is not reported (still only 1 |
| // total). |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(), |
| 1u); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| PopupInteraction_IgnoreUncommitted) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL uncommitted_url = embedded_test_server()->GetURL("c.test", "/nocontent"); |
| |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url)); |
| |
| clock_.Advance(base::Minutes(1)); |
| // Attempt a navigation which won't commit (because the HTTP response is No |
| // Content). |
| ASSERT_TRUE(NavigateToURL(popup, uncommitted_url, popup_url)); |
| |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.PopupInteraction", |
| {"SecondsSinceCommitted", "UrlIndex"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| popup_url); |
| // The uncommitted navigation was ignored. UrlIndex is still 1. |
| EXPECT_EQ(entries[0].metrics["SecondsSinceCommitted"], |
| Bucketize3PCDHeuristicSample(base::Minutes(2).InSeconds(), |
| base::Minutes(3).InSeconds())); |
| EXPECT_EQ(entries[0].metrics["UrlIndex"], 1); |
| } |
| |
| // Very flaky on macOS: https://crbug.com/40933721 |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_PopupInteraction_IsFollowedByPostPopupCookieAccess \ |
| DISABLED_PopupInteraction_IsFollowedByPostPopupCookieAccess |
| #else |
| #define MAYBE_PopupInteraction_IsFollowedByPostPopupCookieAccess \ |
| PopupInteraction_IsFollowedByPostPopupCookieAccess |
| #endif |
| IN_PROC_BROWSER_TEST_P( |
| OpenerHeuristicInteractionTypesBrowserTest, |
| MAYBE_PopupInteraction_IsFollowedByPostPopupCookieAccess) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL popup_url_1 = embedded_test_server()->GetURL("c.test", "/title1.html"); |
| GURL popup_url_2 = |
| embedded_test_server()->GetURL("b.test", "/server-redirect?title1.html"); |
| GURL popup_url_3 = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| browser_client()->AllowThirdPartyCookiesOnSite(opener_url); |
| |
| // Initialize popup and interaction. |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url_1)); |
| |
| clock_.Advance(base::Minutes(1)); |
| ASSERT_TRUE(NavigateToURL(popup, popup_url_2, popup_url_3)); |
| |
| clock_.Advance(base::Minutes(1)); |
| SimulateInteraction(popup); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| // Assert that the UKM events and DIPS entries were recorded. |
| ASSERT_EQ( |
| ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(), |
| 1u); |
| auto top_level_entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", {"AccessId"}); |
| ASSERT_EQ(top_level_entries.size(), 1u); |
| EXPECT_EQ( |
| ukm_recorder.GetSourceForSourceId(top_level_entries[0].source_id)->url(), |
| opener_url); |
| |
| int64_t access_id; |
| base::OnceCallback<void(std::optional<PopupsStateValue>)> assert_popup = |
| base::BindLambdaForTesting([&](std::optional<PopupsStateValue> state) { |
| ASSERT_TRUE(state.has_value()); |
| access_id = static_cast<int64_t>(state->access_id); |
| }); |
| GetBtmService() |
| ->storage() |
| ->AsyncCall(&BtmStorage::ReadPopup) |
| .WithArgs(GetSiteForBtm(opener_url), GetSiteForBtm(popup_url_3)) |
| .Then(std::move(assert_popup)); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| // Add a cookie access by popup_url on opener_url. |
| ASSERT_TRUE(NavigateToSetCookie(GetActiveWebContents(), &https_server_, |
| "sub.b.test", |
| /*is_secure_cookie_set=*/true, |
| /*is_ad_tagged=*/false)); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| CreateImageAndWaitForCookieAccess( |
| GetActiveWebContents(), |
| https_server_.GetURL("sub.b.test", "/favicon/icon.png")); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| // Assert that the UKM event for the PostPopupCookieAccess was recorded. |
| auto access_entries = ukm_recorder.GetEntries( |
| "OpenerHeuristic.PostPopupCookieAccess", |
| {"AccessId", "AccessSucceeded", "IsAdTagged", "HoursSincePopupOpened"}); |
| ASSERT_EQ(access_entries.size(), 1u); |
| EXPECT_EQ( |
| ukm_recorder.GetSourceForSourceId(access_entries[0].source_id)->url(), |
| opener_url); |
| EXPECT_EQ(access_entries[0].metrics["AccessId"], access_id); |
| EXPECT_EQ(access_entries[0].metrics["AccessSucceeded"], true); |
| EXPECT_EQ(access_entries[0].metrics["IsAdTagged"], |
| static_cast<int32_t>(OptionalBool::kFalse)); |
| EXPECT_EQ(access_entries[0].metrics["HoursSincePopupOpened"], 0); |
| } |
| |
| // TODO(https://crbug.com/40933721): flaky on Mac. |
| // |
| // TODO: crbug.com/376625002 - disabled for the move to //content since the |
| // DevTools integration is still only in //chrome. Either find a way to |
| // implement this test in //content or move it back to //chrome. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_PopupInteraction_CookieAccessEmitsDevtoolsWarning \ |
| DISABLED_PopupInteraction_CookieAccessEmitsDevtoolsWarning |
| #else |
| #define MAYBE_PopupInteraction_CookieAccessEmitsDevtoolsWarning \ |
| PopupInteraction_CookieAccessEmitsDevtoolsWarning |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| OpenerHeuristicBrowserTest, |
| DISABLED_PopupInteraction_CookieAccessEmitsDevtoolsWarning) { |
| GURL opener_url = https_server_.GetURL("a.test", "/title1.html"); |
| GURL popup_url_1 = https_server_.GetURL("c.test", "/title1.html"); |
| GURL popup_url_2 = |
| https_server_.GetURL("b.test", "/server-redirect?title1.html"); |
| GURL popup_url_3 = https_server_.GetURL("b.test", "/title1.html"); |
| |
| // Initialize popup and interaction. |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url_1)); |
| |
| clock_.Advance(base::Minutes(1)); |
| ASSERT_TRUE(NavigateToURL(popup, popup_url_2, popup_url_3)); |
| |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| |
| // Add a cookie access by popup_url on opener_url. |
| ASSERT_TRUE(NavigateToSetCookie(GetActiveWebContents(), &https_server_, |
| "sub.b.test", |
| /*is_secure_cookie_set=*/true, |
| /*is_ad_tagged=*/false)); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| |
| CreateImageAndWaitForCookieAccess( |
| GetActiveWebContents(), |
| https_server_.GetURL("sub.b.test", "/favicon/icon.png")); |
| |
| // CookieIssue was fired since it was exempt from blocking |
| WaitForCookieIssueAndCheck("sub.b.test", |
| /*warning=*/{"WarnThirdPartyCookieHeuristic"}, |
| /*exclusion=*/{}); |
| } |
| |
| // TODO: crbug.com/376625002 - disabled for the move to //content since the |
| // DevTools integration is still only in //chrome. Either find a way to |
| // implement this test in //content or move it back to //chrome. |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| DISABLED_PopupInteraction_DevtoolsDisableHeuristics) { |
| GURL opener_url = https_server_.GetURL("a.test", "/title1.html"); |
| GURL popup_url_1 = https_server_.GetURL("c.test", "/title1.html"); |
| GURL popup_url_2 = |
| https_server_.GetURL("b.test", "/server-redirect?title1.html"); |
| GURL popup_url_3 = https_server_.GetURL("b.test", "/title1.html"); |
| |
| // Initialize popup and interaction. |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url_1)); |
| |
| clock_.Advance(base::Minutes(1)); |
| ASSERT_TRUE(NavigateToURL(popup, popup_url_2, popup_url_3)); |
| |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| |
| // Add a cookie access by popup_url on opener_url. |
| ASSERT_TRUE(NavigateToSetCookie(GetActiveWebContents(), &https_server_, |
| "sub.b.test", |
| /*is_secure_cookie_set=*/true, |
| /*is_ad_tagged=*/false)); |
| |
| SendCommandAsync("Network.enable"); |
| base::Value::Dict command_params; |
| command_params.Set("enableThirdPartyCookieRestriction", true); |
| command_params.Set("disableThirdPartyCookieMetadata", false); |
| command_params.Set("disableThirdPartyCookieHeuristics", true); |
| SendCommandSync("Network.setCookieControls", std::move(command_params)); |
| |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| CreateImageAndWaitForCookieAccess( |
| GetActiveWebContents(), |
| https_server_.GetURL("sub.b.test", "/favicon/icon.png")); |
| |
| // Since the cookie is no longer exempted by heuristics, |
| // ExcludeThirdPartyPhaseout cookie issue should be present. |
| WaitForCookieIssueAndCheck("sub.b.test", /*warning=*/{}, |
| /*exclusion=*/{"ExcludeThirdPartyPhaseout"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| TopLevelIsReported_PastInteraction_NoSameSiteIframe) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL toplevel_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL popup_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| RecordUserActivationInteraction(GURL("https://b.test"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, toplevel_url)); |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", |
| {"HasSameSiteIframe", "IsAdTaggedPopupClick"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| toplevel_url); |
| EXPECT_EQ(entries[0].metrics["HasSameSiteIframe"], |
| static_cast<int32_t>(OptionalBool::kFalse)); |
| EXPECT_EQ(entries[0].metrics["IsAdTaggedPopupClick"], false); |
| |
| ASSERT_THAT(GetOpenerHasSameSiteIframe( |
| ukm_recorder, "OpenerHeuristic.PopupPastInteraction"), |
| ValueIs(OptionalBool::kFalse)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| TopLevelIsReported_HasSameSiteIframe) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL toplevel_url = |
| embedded_test_server()->GetURL("a.test", "/page_with_blank_iframe.html"); |
| GURL iframe_url = |
| embedded_test_server()->GetURL("sub.b.test", "/title1.html"); |
| GURL popup_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| const std::string iframe_id = "test_iframe"; |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| RecordUserActivationInteraction(GURL("https://b.test"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, toplevel_url)); |
| ASSERT_TRUE( |
| NavigateIframeToURL(GetActiveWebContents(), iframe_id, iframe_url)); |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", |
| {"HasSameSiteIframe"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| toplevel_url); |
| EXPECT_EQ(entries[0].metrics["HasSameSiteIframe"], |
| static_cast<int32_t>(OptionalBool::kTrue)); |
| |
| ASSERT_THAT(GetOpenerHasSameSiteIframe( |
| ukm_recorder, "OpenerHeuristic.PopupPastInteraction"), |
| ValueIs(OptionalBool::kTrue)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, TopLevel_PopupProvider) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL toplevel_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL popup_url = embedded_test_server()->GetURL("google.com", "/title1.html"); |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| RecordUserActivationInteraction(GURL("https://google.com"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, toplevel_url)); |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", {"PopupProvider"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| toplevel_url); |
| EXPECT_EQ(entries[0].metrics["PopupProvider"], |
| static_cast<int64_t>(PopupProvider::kGoogle)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, TopLevel_PopupId) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL toplevel_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL popup_url = embedded_test_server()->GetURL("google.com", "/title1.html"); |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| RecordUserActivationInteraction(GURL("https://google.com"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, toplevel_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url)); |
| |
| SimulateMouseClick(popup); |
| |
| // Verify all three events share the same popup id. |
| auto tl_entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", {"PopupId"}); |
| ASSERT_EQ(tl_entries.size(), 1u); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(tl_entries[0].source_id)->url(), |
| toplevel_url); |
| const int64_t popup_id = tl_entries[0].metrics["PopupId"]; |
| EXPECT_NE(popup_id, 0); |
| |
| auto pi_entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.PopupInteraction", {"PopupId"}); |
| ASSERT_EQ(pi_entries.size(), 1u); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(pi_entries[0].source_id)->url(), |
| popup_url); |
| EXPECT_EQ(pi_entries[0].metrics["PopupId"], popup_id); |
| |
| auto ppi_entries = ukm_recorder.GetEntries( |
| "OpenerHeuristic.PopupPastInteraction", {"PopupId"}); |
| ASSERT_EQ(ppi_entries.size(), 1u); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(ppi_entries[0].source_id)->url(), |
| popup_url); |
| EXPECT_EQ(ppi_entries[0].metrics["PopupId"], popup_id); |
| |
| // Open second popup, verify different popup id. |
| ASSERT_THAT(OpenPopup(popup_url), HasValue()); |
| tl_entries = ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", {"PopupId"}); |
| ASSERT_EQ(tl_entries.size(), 2u); |
| const int64_t popup_id2 = tl_entries[1].metrics["PopupId"]; |
| EXPECT_NE(popup_id2, 0); |
| EXPECT_NE(popup_id, popup_id2); |
| } |
| |
| // TODO: crbug.com/376625002 - disabled for the move to //content since |
| // SubresourceFilterBrowserTest is unavailable. Either find a way to implement |
| // this test in //content or move it back to //chrome. |
| // |
| // TODO(crbug.com/41484288): Flaky on mac. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_TopLevel_PastInteraction_AdTagged \ |
| DISABLED_TopLevel_PastInteraction_AdTagged |
| #else |
| #define MAYBE_TopLevel_PastInteraction_AdTagged \ |
| TopLevel_PastInteraction_AdTagged |
| #endif |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| DISABLED_TopLevel_PastInteraction_AdTagged) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL toplevel_url = |
| embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html"); |
| GURL popup_url = embedded_test_server()->GetURL("b.com", "/title1.html"); |
| |
| RecordUserActivationInteraction(GURL("https://b.com"), |
| clock_.Now() - base::Hours(3)); |
| |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), toplevel_url)); |
| ASSERT_THAT(OpenAdTaggedPopup(popup_url), HasValue()); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", |
| {"IsAdTaggedPopupClick"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| toplevel_url); |
| EXPECT_EQ(entries[0].metrics["IsAdTaggedPopupClick"], true); |
| } |
| |
| // TODO: crbug.com/376625002 - disabled for the move to //content since |
| // SubresourceFilterBrowserTest is unavailable. Either find a way to implement |
| // this test in //content or move it back to //chrome. |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| DISABLED_TopLevel_CurrentInteraction_AdTagged) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL toplevel_url = |
| embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html"); |
| GURL popup_url = embedded_test_server()->GetURL("b.com", "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), toplevel_url)); |
| |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenAdTaggedPopup(popup_url)) |
| SimulateMouseClick(popup); |
| |
| std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries = |
| ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", |
| {"IsAdTaggedPopupClick"}); |
| ASSERT_EQ(entries.size(), 1u); |
| EXPECT_EQ(ukm::GetSourceIdType(entries[0].source_id), |
| ukm::SourceIdType::NAVIGATION_ID); |
| EXPECT_EQ(ukm_recorder.GetSourceForSourceId(entries[0].source_id)->url(), |
| toplevel_url); |
| EXPECT_EQ(entries[0].metrics["IsAdTaggedPopupClick"], true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, |
| PastAndCurrentInteractionAreBothReportedToDipsDb) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL initial_site = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| GURL initial_url = embedded_test_server()->GetURL( |
| "b.test", "/cross-site/c.test/title1.html"); |
| GURL final_url = embedded_test_server()->GetURL("c.test", "/title1.html"); |
| |
| // Initialize popup and interaction. |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), initial_site)); |
| SimulateMouseClick(GetActiveWebContents()); |
| |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(initial_url, final_url)); |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| std::optional<PopupsStateValue> initial_state = |
| GetPopupState(opener_url, initial_url); |
| std::optional<PopupsStateValue> final_state = |
| GetPopupState(opener_url, final_url); |
| ASSERT_THAT( |
| initial_state, |
| Optional(Field(&PopupsStateValue::is_current_interaction, false))); |
| ASSERT_THAT(final_state, |
| Optional(Field(&PopupsStateValue::is_current_interaction, true))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(OpenerHeuristicInteractionTypesBrowserTest, |
| InteractionTypeIsReportedToDipsDb) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| // GURL initial_site = embedded_test_server()->GetURL("b.test", |
| // "/title1.html"); |
| GURL initial_url = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| // GURL final_url = embedded_test_server()->GetURL("c.test", "/title1.html"); |
| |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(initial_url)); |
| clock_.Advance(base::Minutes(1)); |
| SimulateInteraction(popup); |
| GetBtmService()->storage()->FlushPostedTasksForTesting(); |
| |
| std::optional<PopupsStateValue> initial_state = |
| GetPopupState(opener_url, initial_url); |
| |
| ASSERT_THAT(initial_state, |
| Optional(Field(&PopupsStateValue::is_authentication_interaction, |
| isAuthenticationInteraction()))); |
| } |
| |
| // chrome/browser/ui/browser.h (for changing profile prefs) is not available on |
| // Android. |
| #if !BUILDFLAG(IS_ANDROID) |
| class OpenerHeuristicBackfillGrantBrowserTest |
| : public OpenerHeuristicBrowserTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| OpenerHeuristicBackfillGrantBrowserTest() { |
| tpcd_heuristics_grants_params_["TpcdBackfillPopupHeuristicsGrants"] = |
| GetParam() ? "1us" : "0s"; |
| tpcd_heuristics_grants_params_ |
| ["TpcdWritePopupCurrentInteractionHeuristicsGrants"] = "0s"; |
| } |
| |
| void SetUpOnMainThread() override { |
| OpenerHeuristicBrowserTest::SetUpOnMainThread(); |
| |
| clock_.SetNow(base::Time::Now()); |
| |
| browser_client_.emplace(); |
| browser_client()->SetBlockThirdPartyCookiesByDefault(true); |
| } |
| |
| TpcBlockingBrowserClient* browser_client() { |
| return &browser_client_->impl(); |
| } |
| |
| private: |
| std::optional<ContentBrowserTestTpcBlockingBrowserClient> browser_client_; |
| }; |
| |
| // Test the backfill grants created by OpenerHeuristicService when tracking |
| // protection is onboarded. |
| IN_PROC_BROWSER_TEST_P(OpenerHeuristicBackfillGrantBrowserTest, |
| TrackingProtectionOnboardingCreatesBackfillGrants) { |
| GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html"); |
| GURL popup_url_1 = embedded_test_server()->GetURL("b.test", "/title1.html"); |
| GURL popup_url_2 = embedded_test_server()->GetURL("c.test", "/title1.html"); |
| GURL popup_url_3 = embedded_test_server()->GetURL("d.test", "/title1.html"); |
| |
| // popup_url_1 was opened further back than the backfill lookback period of 10 |
| // minutes. |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url_1)); |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| |
| clock_.Advance(base::Minutes(10)); |
| |
| // popup_url_2 was opened with a past interaction, not a current interaction. |
| RecordUserActivationInteraction(popup_url_2, clock_.Now() - base::Hours(3)); |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_THAT(OpenPopup(popup_url_2), HasValue()); |
| |
| // Only popup_url_3 is eligible for a backfill grant. |
| ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), opener_url)); |
| ASSERT_OK_AND_ASSIGN(popup, OpenPopup(popup_url_3)); |
| clock_.Advance(base::Minutes(1)); |
| SimulateMouseClick(popup); |
| |
| base::test::TestFuture<bool> backfill_done; |
| GetActiveWebContents()->GetBrowserContext()->BackfillPopupHeuristicGrants( |
| backfill_done.GetCallback()); |
| ASSERT_EQ(backfill_done.Get(), GetParam()); |
| |
| // Expect that a cookie access grant is not backfilled for popup_url_1 or |
| // popup_url_2. |
| EXPECT_FALSE(IsFullCookieAccessAllowed(GetActiveWebContents(), popup_url_1, |
| opener_url)); |
| EXPECT_FALSE(IsFullCookieAccessAllowed(GetActiveWebContents(), popup_url_2, |
| opener_url)); |
| |
| // Expect that a cookie access grant is backfilled for popup_url_3 when the |
| // experiment is enabled. |
| EXPECT_EQ(IsFullCookieAccessAllowed(GetActiveWebContents(), popup_url_3, |
| opener_url), |
| GetParam()); |
| |
| // Expect that the cookie access grant applies to other URLs with the same |
| // eTLD+1. |
| GURL popup_url_3a = |
| embedded_test_server()->GetURL("www.d.test", "/favicon.png"); |
| EXPECT_EQ(IsFullCookieAccessAllowed(GetActiveWebContents(), popup_url_3a, |
| opener_url), |
| GetParam()); |
| GURL popup_url_3b = |
| embedded_test_server()->GetURL("corp.d.test", "/title1.html"); |
| EXPECT_EQ(IsFullCookieAccessAllowed(GetActiveWebContents(), popup_url_3b, |
| opener_url), |
| GetParam()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| OpenerHeuristicBackfillGrantBrowserTest, |
| ::testing::Values(false, true)); |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| } // namespace content |