blob: b9a96b478c4e6aecd033eaa5b6517a3b581a6640 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_file_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/btm/btm_bounce_detector.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/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/btm_service.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/btm_service_test_utils.h"
#include "content/public/test/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/test_browser_context.h"
#include "content/public/test/test_launcher.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/switches.h"
#include "ui/gfx/geometry/size.h"
#include "url/url_constants.h"
using testing::Optional;
using testing::Pair;
namespace content {
class BtmTabHelperBrowserTest : public ContentBrowserTest {
protected:
void SetUp() override {
std::vector<base::test::FeatureRefAndParams> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
enabled_features.push_back(
{features::kBtm, {{"triggering_action", "bounce"}}});
scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features,
disabled_features);
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 {
ASSERT_TRUE(embedded_test_server()->Start());
host_resolver()->AddRule("a.test", "127.0.0.1");
host_resolver()->AddRule("b.test", "127.0.0.1");
host_resolver()->AddRule("c.test", "127.0.0.1");
host_resolver()->AddRule("d.test", "127.0.0.1");
BtmWebContentsObserver::FromWebContents(GetActiveWebContents())
->SetClockForTesting(&test_clock_);
browser_client_.emplace();
browser_client_->impl().SetBlockThirdPartyCookiesByDefault(true);
WebContents* web_contents = GetActiveWebContents();
ASSERT_FALSE(btm::Are3PcsGenerallyEnabled(web_contents->GetBrowserContext(),
web_contents));
// We can only create extra browser contexts while single-threaded.
extra_browser_context_ = CreateTestBrowserContext();
}
void TearDown() override {
GetUIThreadTaskRunner({})->DeleteSoon(FROM_HERE,
std::move(extra_browser_context_));
ContentBrowserTest::TearDown();
}
WebContents* GetActiveWebContents() {
if (!web_contents_) {
web_contents_ = shell()->web_contents();
}
return web_contents_;
}
void SetBtmTime(base::Time time) { test_clock_.SetNow(time); }
[[nodiscard]] bool NavigateToURLAndWaitForCookieWrite(const GURL& url) {
URLCookieAccessObserver observer(GetActiveWebContents(), url,
CookieAccessDetails::Type::kChange);
bool success = NavigateToURL(GetActiveWebContents(), url);
if (!success) {
return false;
}
observer.Wait();
return true;
}
base::Clock* test_clock() { return &test_clock_; }
// Make GetActiveWebContents() return the given value instead of the default.
// Helpful for tests that use other WebContents (e.g. in incognito windows).
void OverrideActiveWebContents(WebContents* web_contents) {
web_contents_ = web_contents;
}
void EndRedirectChain() {
WebContents* web_contents = GetActiveWebContents();
BtmServiceImpl* btm_service =
BtmServiceImpl::Get(web_contents->GetBrowserContext());
GURL expected_url = web_contents->GetLastCommittedURL();
BtmRedirectChainObserver chain_observer(btm_service, expected_url);
// Performing a browser-based navigation terminates the current redirect
// chain.
ASSERT_TRUE(NavigateToURL(web_contents, embedded_test_server()->GetURL(
"a.test", "/title1.html")));
chain_observer.Wait();
}
BrowserContext* extra_browser_context() {
return extra_browser_context_.get();
}
private:
raw_ptr<WebContents, AcrossTasksDanglingUntriaged> web_contents_ = nullptr;
// 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_;
base::SimpleTestClock test_clock_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<TestBrowserContext> extra_browser_context_;
};
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest,
InteractionsRecordedInAncestorFrames) {
GURL url_a =
embedded_test_server()->GetURL("a.test", "/page_with_blank_iframe.html");
GURL url_b = embedded_test_server()->GetURL("b.test", "/title1.html");
const std::string kIframeId =
"test_iframe"; // defined in page_with_blank_iframe.html
base::Time time = base::Time::FromSecondsSinceUnixEpoch(1);
WebContents* web_contents = GetActiveWebContents();
// The top-level page is on a.test.
ASSERT_TRUE(NavigateToURL(web_contents, url_a));
SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents);
// Before clicking, no BTM state for either site.
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), url_a).has_value());
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), url_b).has_value());
// Click on the a.test top-level site.
SetBtmTime(time);
UserActivationObserver observer_a(web_contents,
web_contents->GetPrimaryMainFrame());
SimulateMouseClick(web_contents, 0, blink::WebMouseEvent::Button::kLeft);
observer_a.Wait();
// User activation is recorded for a.test (the top-level frame).
std::optional<StateValue> state_a =
GetBtmState(GetBtmService(web_contents), url_a);
ASSERT_TRUE(state_a.has_value());
EXPECT_EQ(std::make_optional(time), state_a->user_activation_times->first);
// Update the top-level page to have an iframe pointing to b.test.
ASSERT_TRUE(NavigateIframeToURL(web_contents, kIframeId, url_b));
RenderFrameHost* iframe =
FrameMatchingPredicate(web_contents->GetPrimaryPage(),
base::BindRepeating(&FrameIsChildOfMainFrame));
// Wait until we can click on the iframe.
WaitForHitTestData(iframe);
// Click on the b.test iframe.
base::Time frame_interaction_time = time + kBtmTimestampUpdateInterval;
SetBtmTime(frame_interaction_time);
UserActivationObserver observer_b(web_contents, iframe);
// TODO(crbug.com/40247129): Remove the ExecJs workaround once
// SimulateMouseClickOrTapElementWithId is able to activate iframes on Android
#if !BUILDFLAG(IS_ANDROID)
SimulateMouseClickOrTapElementWithId(web_contents, kIframeId);
#else
ASSERT_TRUE(ExecJs(iframe, "// empty script to activate iframe"));
#endif
observer_b.Wait();
// User activation on the top-level is updated by interacting with b.test
// (the iframe).
state_a = GetBtmState(GetBtmService(web_contents), url_a);
ASSERT_TRUE(state_a.has_value());
EXPECT_EQ(std::make_optional(frame_interaction_time),
state_a->user_activation_times->second);
// The iframe site doesn't have any state.
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), url_b).has_value());
}
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest,
MultipleUserInteractionsRecorded) {
GURL url = embedded_test_server()->GetURL("a.test", "/title1.html");
base::Time time = base::Time::FromSecondsSinceUnixEpoch(1);
WebContents* web_contents = GetActiveWebContents();
SetBtmTime(time);
// Navigate to a.test.
ASSERT_TRUE(NavigateToURL(web_contents, url));
SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents);
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
WaitForHitTestData(frame); // Wait until we can click.
// Before clicking, there's no BTM state for the site.
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), url).has_value());
UserActivationObserver observer1(web_contents, frame);
SimulateMouseClick(web_contents, 0, blink::WebMouseEvent::Button::kLeft);
observer1.Wait();
// One instance of user activation is recorded.
std::optional<StateValue> state_1 =
GetBtmState(GetBtmService(web_contents), url);
ASSERT_TRUE(state_1.has_value());
EXPECT_EQ(std::make_optional(time), state_1->user_activation_times->first);
EXPECT_EQ(state_1->user_activation_times->first,
state_1->user_activation_times->second);
SetBtmTime(time + kBtmTimestampUpdateInterval + base::Seconds(10));
UserActivationObserver observer_2(web_contents, frame);
SimulateMouseClick(web_contents, 0, blink::WebMouseEvent::Button::kLeft);
observer_2.Wait();
// A second, different, instance of user activation is recorded for the same
// site.
std::optional<StateValue> state_2 =
GetBtmState(GetBtmService(web_contents), url);
ASSERT_TRUE(state_2.has_value());
EXPECT_NE(state_2->user_activation_times->second,
state_2->user_activation_times->first);
EXPECT_EQ(std::make_optional(time), state_2->user_activation_times->first);
EXPECT_EQ(std::make_optional(time + kBtmTimestampUpdateInterval +
base::Seconds(10)),
state_2->user_activation_times->second);
}
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest, StorageRecordedInSingleFrame) {
// We host the iframe content on an HTTPS server, because for it to write a
// cookie, the cookie needs to be SameSite=None and Secure.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server.AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(https_server.Start());
GURL url_a =
embedded_test_server()->GetURL("a.test", "/page_with_blank_iframe.html");
GURL url_b = https_server.GetURL("b.test", "/title1.html");
const std::string kIframeId =
"test_iframe"; // defined in page_with_blank_iframe.html
base::Time time = base::Time::FromSecondsSinceUnixEpoch(1);
WebContents* web_contents = GetActiveWebContents();
// The top-level page is on a.test, containing an iframe pointing at b.test.
ASSERT_TRUE(NavigateToURL(web_contents, url_a));
ASSERT_TRUE(NavigateIframeToURL(web_contents, kIframeId, url_b));
RenderFrameHost* iframe =
FrameMatchingPredicate(web_contents->GetPrimaryPage(),
base::BindRepeating(&FrameIsChildOfMainFrame));
// Initially, no BTM state for either site.
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), url_a).has_value());
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), url_b).has_value());
// Write a cookie in the b.test iframe.
SetBtmTime(time);
FrameCookieAccessObserver observer(web_contents, iframe,
CookieAccessDetails::Type::kChange);
ASSERT_TRUE(ExecJs(iframe,
"document.cookie = 'foo=bar; SameSite=None; Secure';",
EXECUTE_SCRIPT_NO_USER_GESTURE));
observer.Wait();
// Nothing recorded for a.test (the top-level frame).
std::optional<StateValue> state_a =
GetBtmState(GetBtmService(web_contents), url_a);
EXPECT_FALSE(state_a.has_value());
// Nothing recorded for b.test (the iframe), since we don't record non main
// frame URLs to BTM state.
std::optional<StateValue> state_b =
GetBtmState(GetBtmService(web_contents), url_b);
EXPECT_FALSE(state_b.has_value());
}
namespace {
class BrowsingDataRemovalObserver : public BrowsingDataRemover::Observer {
public:
explicit BrowsingDataRemovalObserver(base::OnceClosure callback)
: callback_(std::move(callback)) {}
void OnBrowsingDataRemoverDone(uint64_t failed_data_types) override {
std::move(callback_).Run();
}
private:
base::OnceClosure callback_;
};
bool ClearBrowsingData(BrowsingDataRemover* remover,
base::TimeDelta time_period) {
base::RunLoop run_loop;
BrowsingDataRemovalObserver observer(run_loop.QuitClosure());
remover->AddObserver(&observer);
const base::Time now = base::Time::Now();
remover->RemoveAndReply(now - time_period, now,
ContentBrowserClient::kDefaultBtmRemoveMask |
TpcBlockingBrowserClient::DATA_TYPE_HISTORY,
BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
&observer);
run_loop.Run();
remover->RemoveObserver(&observer);
return !testing::Test::HasFailure();
}
} // namespace
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest,
ChromeBrowsingDataRemover_Basic) {
WebContents* web_contents = GetActiveWebContents();
base::Time interaction_time = base::Time::Now() - base::Seconds(10);
SetBtmTime(interaction_time);
// Perform a click to get a.test added to the BTM DB.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
UserActivationObserver observer(web_contents,
web_contents->GetPrimaryMainFrame());
SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents);
SimulateMouseClick(web_contents, 0, blink::WebMouseEvent::Button::kLeft);
observer.Wait();
// Verify it was added.
std::optional<StateValue> state_initial =
GetBtmState(GetBtmService(web_contents), GURL("http://a.test"));
ASSERT_TRUE(state_initial.has_value());
ASSERT_TRUE(state_initial->user_activation_times.has_value());
EXPECT_EQ(state_initial->user_activation_times->first, interaction_time);
// Remove browsing data for the past day.
ASSERT_TRUE(ClearBrowsingData(
GetActiveWebContents()->GetBrowserContext()->GetBrowsingDataRemover(),
base::Days(1)));
// Verify that the user activation has been cleared from the BTM DB.
std::optional<StateValue> state_final =
GetBtmState(GetBtmService(web_contents), GURL("http://a.test"));
EXPECT_FALSE(state_final.has_value());
}
// Makes a long URL involving several stateful stateful bounces on b.test,
// ultimately landing on c.test. Returns both the full redirect URL and the URL
// for the landing page. The landing page URL has a param appended to it to
// ensure it's unique to URLs from previous calls (to prevent caching).
std::pair<GURL, GURL> MakeRedirectAndFinalUrl(net::EmbeddedTestServer* server) {
uint64_t unique_value = base::RandUint64();
std::string final_dest =
base::StrCat({"/title1.html?i=", base::NumberToString(unique_value)});
std::string redirect_path =
"/cross-site-with-cookie/b.test/cross-site-with-cookie/"
"b.test/cross-site-with-cookie/b.test/cross-site-with-cookie/"
"b.test/cross-site-with-cookie/b.test/cross-site-with-cookie/"
"b.test/cross-site-with-cookie/b.test/cross-site-with-cookie/"
"b.test/cross-site-with-cookie/b.test/cross-site-with-cookie/d.test";
redirect_path += final_dest;
return std::make_pair(server->GetURL("b.test", redirect_path),
server->GetURL("d.test", final_dest));
}
// Attempt to detect flakiness in waiting for BTM storage by repeatedly
// visiting long redirect chains, deleting the relevant rows, and verifying the
// rows don't come back.
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest,
DetectRedirectHandlingFlakiness) {
WebContents* web_contents = GetActiveWebContents();
auto* btm_storage =
BtmServiceImpl::Get(web_contents->GetBrowserContext())->storage();
for (int i = 0; i < 10; i++) {
const base::Time bounce_time = base::Time::FromSecondsSinceUnixEpoch(i + 1);
SetBtmTime(bounce_time);
LOG(INFO) << "*** i=" << i << " ***";
// Make b.test statefully bounce.
ASSERT_TRUE(NavigateToURL(web_contents, embedded_test_server()->GetURL(
"a.test", "/title1.html")));
auto [redirect_url, final_url] =
MakeRedirectAndFinalUrl(embedded_test_server());
ASSERT_TRUE(
NavigateToURLFromRenderer(web_contents, redirect_url, final_url));
// End the chain so the bounce is recorded.
EndRedirectChain();
// Verify the bounces were recorded.
std::optional<StateValue> b_state =
GetBtmState(GetBtmService(web_contents), GURL("http://b.test"));
ASSERT_TRUE(b_state.has_value());
ASSERT_THAT(b_state->bounce_times,
Optional(Pair(bounce_time, bounce_time)));
btm_storage->AsyncCall(&BtmStorage::RemoveRows)
.WithArgs(std::vector<std::string>{"b.test"});
// Verify the row was removed before repeating the test. If we did not
// correctly wait for the whole chain to be handled before removing the row
// for b.test, it will likely be written again and this check will fail.
// (And if a write happens after this check, it will include a stale
// timestamp and will cause one the of the checks above to fail on the next
// loop iteration.)
ASSERT_FALSE(GetBtmState(GetBtmService(web_contents), GURL("http://b.test"))
.has_value());
}
}
// Flaky on Android: https://crbug.com/369717773
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_UserClearedSitesAreNotReportedToUKM \
DISABLED_UserClearedSitesAreNotReportedToUKM
#else
#define MAYBE_UserClearedSitesAreNotReportedToUKM \
UserClearedSitesAreNotReportedToUKM
#endif
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest,
MAYBE_UserClearedSitesAreNotReportedToUKM) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
WebContents* web_contents = GetActiveWebContents();
BtmServiceImpl* btm_service =
BtmServiceImpl::Get(web_contents->GetBrowserContext());
// A time more than an hour ago.
base::Time old_bounce_time = base::Time::Now() - base::Hours(2);
// A time within the past hour.
base::Time recent_bounce_time = base::Time::Now() - base::Minutes(10);
SetBtmTime(old_bounce_time);
// Make b.test statefully bounce to d.test.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
const GURL bounce_url1 = embedded_test_server()->GetURL(
"b.test", "/cross-site-with-cookie/d.test/title1.html");
URLCookieAccessObserver observer1(web_contents, bounce_url1,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURLFromRenderer(
web_contents, bounce_url1,
embedded_test_server()->GetURL("d.test", "/title1.html")));
observer1.Wait();
// End the chain so the bounce is recorded.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
SetBtmTime(recent_bounce_time);
// Make c.test statefully bounce to d.test.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
const GURL bounce_url2 = embedded_test_server()->GetURL(
"c.test", "/cross-site-with-cookie/d.test/title1.html");
URLCookieAccessObserver observer2(web_contents, bounce_url2,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURLFromRenderer(
web_contents, bounce_url2,
embedded_test_server()->GetURL("d.test", "/title1.html")));
observer2.Wait();
EndRedirectChain();
// Verify the bounces were recorded. b.test:
std::optional<StateValue> state =
GetBtmState(GetBtmService(web_contents), GURL("http://b.test"));
ASSERT_TRUE(state.has_value());
ASSERT_EQ(state->user_activation_times, std::nullopt);
// c.test:
state = GetBtmState(GetBtmService(web_contents), GURL("http://c.test"));
ASSERT_TRUE(state.has_value());
ASSERT_EQ(state->user_activation_times, std::nullopt);
// Remove browsing data for the past hour. This should include c.test but not
// b.test.
ASSERT_TRUE(ClearBrowsingData(
web_contents->GetBrowserContext()->GetBrowsingDataRemover(),
base::Hours(1)));
// Verify only the BTM record for c.test was deleted.
ASSERT_TRUE(GetBtmState(GetBtmService(web_contents), GURL("http://b.test"))
.has_value());
ASSERT_FALSE(GetBtmState(GetBtmService(web_contents), GURL("http://c.test"))
.has_value());
// Trigger the BTM timer which will delete tracker data.
SetBtmTime(recent_bounce_time + features::kBtmGracePeriod.Get() +
base::Milliseconds(1));
btm_service->OnTimerFiredForTesting();
btm_service->storage()->FlushPostedTasksForTesting();
base::RunLoop().RunUntilIdle();
// Verify that both BTM records are now gone.
ASSERT_FALSE(GetBtmState(GetBtmService(web_contents), GURL("http://b.test"))
.has_value());
ASSERT_FALSE(GetBtmState(GetBtmService(web_contents), GURL("http://c.test"))
.has_value());
// Only b.test was reported to UKM.
EXPECT_THAT(ukm_recorder, EntryUrlsAre("DIPS.Deletion", {"http://b.test/"}));
}
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest, SitesInOpenTabsAreExempt) {
WebContents* web_contents = GetActiveWebContents();
BtmServiceImpl* btm_service =
BtmServiceImpl::Get(web_contents->GetBrowserContext());
// A time within the past hour.
base::Time bounce_time = base::Time::Now() - base::Minutes(10);
SetBtmTime(bounce_time);
// Make b.test statefully bounce to d.test.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
ASSERT_TRUE(NavigateToURLFromRenderer(
web_contents,
embedded_test_server()->GetURL(
"b.test", "/cross-site-with-cookie/d.test/title1.html"),
embedded_test_server()->GetURL("d.test", "/title1.html")));
EndRedirectChain();
// Make c.test statefully bounce to d.test.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
ASSERT_TRUE(NavigateToURLFromRenderer(
web_contents,
embedded_test_server()->GetURL(
"c.test", "/cross-site-with-cookie/d.test/title1.html"),
embedded_test_server()->GetURL("d.test", "/title1.html")));
EndRedirectChain();
// Verify the bounces through b.test and c.test were recorded.
std::optional<StateValue> b_state =
GetBtmState(GetBtmService(web_contents), GURL("http://b.test"));
ASSERT_TRUE(b_state.has_value());
ASSERT_EQ(b_state->user_activation_times, std::nullopt);
std::optional<StateValue> c_state =
GetBtmState(GetBtmService(web_contents), GURL("http://c.test"));
ASSERT_TRUE(c_state.has_value());
ASSERT_EQ(c_state->user_activation_times, std::nullopt);
// Open b.test in a new tab.
auto new_tab = OpenInNewTab(
web_contents, embedded_test_server()->GetURL("c.test", "/title1.html"));
ASSERT_TRUE(new_tab.has_value()) << new_tab.error();
// Navigate to c.test in the new tab.
ASSERT_TRUE(NavigateToURL(
*new_tab, embedded_test_server()->GetURL("c.test", "/title1.html")));
// Trigger the BTM timer which would delete tracker data.
SetBtmTime(bounce_time + features::kBtmGracePeriod.Get() +
base::Milliseconds(1));
btm_service->OnTimerFiredForTesting();
btm_service->storage()->FlushPostedTasksForTesting();
base::RunLoop().RunUntilIdle();
// Verify that the BTM record for b.test is now gone, because there is no
// open tab on b.test.
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), GURL("http://b.test"))
.has_value());
// Verify that the BTM record for c.test is still present, because there is
// an open tab on c.test.
EXPECT_TRUE(GetBtmState(GetBtmService(web_contents), GURL("http://c.test"))
.has_value());
}
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest,
SitesInDestroyedTabsAreNotExempt) {
WebContents* web_contents = GetActiveWebContents();
BtmServiceImpl* btm_service =
BtmServiceImpl::Get(web_contents->GetBrowserContext());
// A time within the past hour.
base::Time bounce_time = base::Time::Now() - base::Minutes(10);
SetBtmTime(bounce_time);
// Make b.test statefully bounce to d.test.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
ASSERT_TRUE(NavigateToURLFromRenderer(
web_contents,
embedded_test_server()->GetURL(
"b.test", "/cross-site-with-cookie/d.test/title1.html"),
embedded_test_server()->GetURL("d.test", "/title1.html")));
EndRedirectChain();
// Verify the bounce through b.test was recorded.
std::optional<StateValue> b_state =
GetBtmState(GetBtmService(web_contents), GURL("http://b.test"));
ASSERT_TRUE(b_state.has_value());
ASSERT_EQ(b_state->user_activation_times, std::nullopt);
// Open b.test in a new tab.
auto new_tab = OpenInNewTab(
web_contents, embedded_test_server()->GetURL("c.test", "/title1.html"));
ASSERT_TRUE(new_tab.has_value()) << new_tab.error();
// Close the new tab with b.test.
CloseTab(*new_tab);
// Trigger the BTM timer which would delete tracker data.
SetBtmTime(bounce_time + features::kBtmGracePeriod.Get() +
base::Milliseconds(1));
btm_service->OnTimerFiredForTesting();
btm_service->storage()->FlushPostedTasksForTesting();
base::RunLoop().RunUntilIdle();
// Verify that the BTM record for b.test is now gone, because there is no
// open tab on b.test.
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), GURL("http://b.test"))
.has_value());
}
// Multiple running profiles is not supported on Android or ChromeOS.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(BtmTabHelperBrowserTest,
SitesInOpenTabsForDifferentProfilesAreNotExempt) {
WebContents* web_contents = GetActiveWebContents();
BtmServiceImpl* btm_service =
BtmServiceImpl::Get(web_contents->GetBrowserContext());
// A time within the past hour.
base::Time bounce_time = base::Time::Now() - base::Minutes(10);
SetBtmTime(bounce_time);
// Make c.test statefully bounce to d.test.
ASSERT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
ASSERT_TRUE(NavigateToURLFromRenderer(
web_contents,
embedded_test_server()->GetURL(
"c.test", "/cross-site-with-cookie/d.test/title1.html"),
embedded_test_server()->GetURL("d.test", "/title1.html")));
EndRedirectChain();
// Verify the bounce through c.test was recorded.
std::optional<StateValue> c_state =
GetBtmState(GetBtmService(web_contents), GURL("http://c.test"));
ASSERT_TRUE(c_state.has_value());
ASSERT_EQ(c_state->user_activation_times, std::nullopt);
// Open c.test on a new tab in a new window/profile.
Shell* another_window = Shell::CreateNewWindow(
extra_browser_context(), GURL(url::kAboutBlankURL), nullptr, gfx::Size());
ASSERT_TRUE(
NavigateToURL(another_window->web_contents(),
embedded_test_server()->GetURL("c.test", "/title1.html")));
// Trigger the BTM timer which would delete tracker data.
SetBtmTime(bounce_time + features::kBtmGracePeriod.Get() +
base::Milliseconds(1));
btm_service->OnTimerFiredForTesting();
btm_service->storage()->FlushPostedTasksForTesting();
base::RunLoop().RunUntilIdle();
// The BTM record for c.test was removed, because open tabs in a different
// profile are not exempt.
EXPECT_FALSE(GetBtmState(GetBtmService(web_contents), GURL("http://c.test"))
.has_value());
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
} // namespace content