[DIPS] Only report stateful redirectors in inspector issues
This CL filters the sites reported by DIPS after a bounce happens to
only include those who also accessed cookies during the bounce (i.e.,
statefully bounced).
Bug: 1416548
Change-Id: Ia3e9488e8aedd9252411f43db76c9604460a4bdd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4428292
Reviewed-by: Ryan Tarpine <rtarpine@chromium.org>
Commit-Queue: Joshua Hood <jdh@chromium.org>
Reviewed-by: Jonathan Njeunje <njeunje@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1140179}
diff --git a/chrome/browser/dips/dips_bounce_detector.cc b/chrome/browser/dips/dips_bounce_detector.cc
index 8b09c513..532c8c5 100644
--- a/chrome/browser/dips/dips_bounce_detector.cc
+++ b/chrome/browser/dips/dips_bounce_detector.cc
@@ -84,8 +84,8 @@
detector_(this,
base::DefaultTickClock::GetInstance(),
base::DefaultClock::GetInstance()) {
- issue_callback_ = base::BindRepeating(&DIPSWebContentsObserver::EmitDIPSIssue,
- weak_factory_.GetWeakPtr());
+ issue_reporting_callback_ = base::BindRepeating(
+ &DIPSWebContentsObserver::EmitDIPSIssue, weak_factory_.GetWeakPtr());
}
DIPSWebContentsObserver::~DIPSWebContentsObserver() = default;
@@ -105,6 +105,9 @@
redirect_context_(
base::BindRepeating(&DIPSBounceDetectorDelegate::HandleRedirectChain,
base::Unretained(delegate)),
+ base::BindRepeating(
+ &DIPSBounceDetectorDelegate::ReportRedirectorsWithoutInteraction,
+ base::Unretained(delegate)),
/*initial_url=*/GURL::EmptyGURL()),
client_bounce_detection_timer_(
FROM_HERE,
@@ -126,8 +129,11 @@
}
DIPSRedirectContext::DIPSRedirectContext(DIPSRedirectChainHandler handler,
+ DIPSIssueHandler issue_handler,
const GURL& initial_url)
- : handler_(handler), initial_url_(initial_url) {}
+ : handler_(handler),
+ issue_handler_(issue_handler),
+ initial_url_(initial_url) {}
DIPSRedirectContext::~DIPSRedirectContext() = default;
@@ -137,6 +143,9 @@
if (client_redirect->access_type > CookieAccessType::kNone) {
update_offset_ = redirects_.size();
}
+ if (client_redirect->access_type > CookieAccessType::kRead) {
+ redirectors_.insert(GetSiteForDIPS(client_redirect->url));
+ }
redirects_.push_back(std::move(client_redirect));
}
@@ -147,10 +156,29 @@
if (redirect->access_type > CookieAccessType::kNone) {
update_offset_ = redirects_.size();
}
+ if (redirect->access_type > CookieAccessType::kRead) {
+ redirectors_.insert(GetSiteForDIPS(redirect->url));
+ }
redirects_.push_back(std::move(redirect));
}
}
+void DIPSRedirectContext::ReportIssue(const GURL& final_url) {
+ // Since redirectors that are the same as the start or final page won't be
+ // acted on, we don't report on them.
+ //
+ // NOTE: This is not exactly right since the end of this navigation may not
+ // necessarily be the end of the chain, if a client redirect happens. However,
+ // this is better for developer experience than waiting until then, since
+ // notifications come faster.
+ redirectors_.erase(GetSiteForDIPS(initial_url_));
+ redirectors_.erase(GetSiteForDIPS(final_url));
+
+ issue_handler_.Run(std::move(redirectors_));
+
+ redirectors_.clear();
+}
+
void DIPSRedirectContext::HandleUncommitted(
DIPSNavigationStart navigation_start,
std::vector<DIPSRedirectInfoPtr> server_redirects,
@@ -163,9 +191,11 @@
// `DIPSRedirectContext`'s in-progress chain within the temp
// `DIPSRedirectContext`, whilst leaving *this*
// `DIPSRedirectContext`'s in-progress chain unchanged.
- DIPSRedirectContext temp_context(handler_, initial_url_);
+ DIPSRedirectContext temp_context(handler_, issue_handler_,
+ initial_url_);
temp_context.AppendClientRedirect(std::move(client_redirect));
temp_context.AppendServerRedirects(std::move(server_redirects));
+ temp_context.ReportIssue(final_url);
temp_context.EndChain(std::move(final_url));
},
[&](GURL previous_nav_last_committed_url) {
@@ -173,9 +203,10 @@
// a new redirect chain within a new `DIPSRedirectContext` and
// process it immediately (the in-progress chain in *this*
// `DIPSRedirectContext` is irrelevant).
- DIPSRedirectContext temp_context(handler_,
+ DIPSRedirectContext temp_context(handler_, issue_handler_,
previous_nav_last_committed_url);
temp_context.AppendServerRedirects(std::move(server_redirects));
+ temp_context.ReportIssue(final_url);
temp_context.EndChain(std::move(final_url));
},
},
@@ -184,7 +215,8 @@
void DIPSRedirectContext::AppendCommitted(
DIPSNavigationStart navigation_start,
- std::vector<DIPSRedirectInfoPtr> server_redirects) {
+ std::vector<DIPSRedirectInfoPtr> server_redirects,
+ const GURL& final_url) {
// If there was a client-side redirect before
// `DIPSBounceDetector::client_bounce_detection_timer_` timedout, grow the
// chain. Otherwise, end it.
@@ -205,6 +237,7 @@
// Server-side redirects always grow the chain.
AppendServerRedirects(std::move(server_redirects));
+ ReportIssue(final_url);
}
void DIPSRedirectContext::EndChain(GURL final_url) {
@@ -229,6 +262,12 @@
if (redirects_[update_offset_]->url == url) {
redirects_[update_offset_]->access_type =
redirects_[update_offset_]->access_type | ToCookieAccessType(op);
+
+ // This cookie access might indicate a stateful bounce and ideally we'd
+ // report an issue to notify the user, but the navigation already
+ // committed and any relevant notifications were already emitted, so it's
+ // too late.
+
return true;
}
@@ -271,7 +310,7 @@
dips_service_->storage()
->AsyncCall(&DIPSStorage::FilterSitesWithoutInteraction)
.WithArgs(sites)
- .Then(issue_callback_);
+ .Then(issue_reporting_callback_);
}
void DIPSWebContentsObserver::RecordEvent(DIPSRecordedEvent event,
@@ -519,12 +558,10 @@
/*time=*/clock_->Now()));
}
- delegate_->ReportRedirectorsWithoutInteraction(
- GetRedirectors(server_state->navigation_start, navigation_handle));
-
if (navigation_handle->HasCommitted()) {
redirect_context_.AppendCommitted(std::move(server_state->navigation_start),
- std::move(redirects));
+ std::move(redirects),
+ navigation_handle->GetURL());
} else {
redirect_context_.HandleUncommitted(
std::move(server_state->navigation_start), std::move(redirects),
@@ -580,47 +617,6 @@
(now - last_time.value()) >= kTimestampUpdateInterval);
}
-std::set<std::string> DIPSBounceDetector::GetRedirectors(
- const DIPSNavigationStart& navigation_start,
- DIPSNavigationHandle* navigation_handle) {
- std::set<std::string> redirectors;
- std::string initial_site;
-
- absl::visit( //
- base::Overloaded{
- [&](const DIPSRedirectInfoPtr& client_redirect) {
- initial_site = GetSiteForDIPS(redirect_context_.GetInitialURL());
- // If the navigation started with a client redirect,
- // `navigation_start` will be a DIPSRedirectInfoPtr and
- // we'll include that redirector as well.
- redirectors.insert(GetSiteForDIPS(client_redirect->url));
- },
- [&](const GURL& client_url) {
- initial_site = GetSiteForDIPS(client_url);
- },
- },
- navigation_start);
-
- const auto& redirect_chain = navigation_handle->GetRedirectChain();
- // The last site in the chain is the destination page, so it is ignored here.
- for (auto it = redirect_chain.begin(); it != (redirect_chain.end() - 1);
- it++) {
- redirectors.insert(GetSiteForDIPS(*it));
- }
-
- // Since redirectors that are the same as the start or final page won't be
- // acted on, we don't report on them.
- //
- // NOTE: This is not exactly right since the end of this navigation may not
- // necessarily be the end of the chain, if a client redirect happens. However,
- // this is better for developer experience than waiting until then, since
- // notifications come faster.
- redirectors.erase(initial_site);
- redirectors.erase(GetSiteForDIPS(redirect_chain.back()));
-
- return redirectors;
-}
-
void DIPSWebContentsObserver::WebContentsDestroyed() {
detector_.BeforeDestruction();
}
diff --git a/chrome/browser/dips/dips_bounce_detector.h b/chrome/browser/dips/dips_bounce_detector.h
index 163e542..ca62741 100644
--- a/chrome/browser/dips/dips_bounce_detector.h
+++ b/chrome/browser/dips/dips_bounce_detector.h
@@ -33,6 +33,11 @@
class TickClock;
} // namespace base
+using DIPSIssueHandler =
+ base::RepeatingCallback<void(const std::set<std::string>& sites)>;
+using DIPSIssueReportingCallback =
+ base::RepeatingCallback<void(const std::set<std::string>& sites)>;
+
// ClientBounceDetectionState is owned by the DIPSBounceDetector and stores
// data needed to detect stateful client-side redirects.
class ClientBounceDetectionState {
@@ -62,6 +67,7 @@
class DIPSRedirectContext {
public:
DIPSRedirectContext(DIPSRedirectChainHandler handler,
+ DIPSIssueHandler issue_handler,
const GURL& initial_url);
~DIPSRedirectContext();
@@ -76,7 +82,8 @@
// start of a new one, or extends it, according to the value of
// `navigation_start`.
void AppendCommitted(DIPSNavigationStart navigation_start,
- std::vector<DIPSRedirectInfoPtr> server_redirects);
+ std::vector<DIPSRedirectInfoPtr> server_redirects,
+ const GURL& final_url);
// Terminates the in-progress redirect chain, ending it with `final_url`, and
// parsing it to the `DIPSRedirectChainHandler` iff the chain is valid. It
@@ -85,6 +92,8 @@
// NOTE: A chain is valid if it has a non-empty `initial_url_`.
void EndChain(GURL final_url);
+ void ReportIssue(const GURL& final_url);
+
[[nodiscard]] bool AddLateCookieAccess(GURL url, CookieOperation op);
size_t size() const { return redirects_.size(); }
@@ -100,18 +109,17 @@
void AppendServerRedirects(std::vector<DIPSRedirectInfoPtr> server_redirects);
DIPSRedirectChainHandler handler_;
+ DIPSIssueHandler issue_handler_;
// Represents the start of a chain and also indicates the presence of a valid
// chain.
GURL initial_url_;
std::vector<DIPSRedirectInfoPtr> redirects_;
+ std::set<std::string> redirectors_;
// The index of the last redirect to have a known cookie access. When adding
// late cookie accesses, we only consider redirects from this offset onwards.
size_t update_offset_ = 0;
};
-using DIPSIssueCallback =
- base::RepeatingCallback<void(const std::set<std::string>& sites)>;
-
// A simplified interface to WebContents and DIPSService that can be faked in
// tests. Needed to allow unit testing DIPSBounceDetector.
class DIPSBounceDetectorDelegate {
@@ -221,13 +229,6 @@
bool ShouldUpdateTimestamp(base::optional_ref<const base::Time> last_time,
base::Time now);
- // Returns the set of sites in the current (server) redirect chain. If the
- // navigation started with a client redirect, that site is also included.
- // Redirectors matching the initial or end site are omitted.
- std::set<std::string> GetRedirectors(
- const DIPSNavigationStart& navigation_start,
- DIPSNavigationHandle* navigation_handle);
-
raw_ptr<const base::TickClock> tick_clock_;
raw_ptr<const base::Clock> clock_;
raw_ptr<DIPSBounceDetectorDelegate> delegate_;
@@ -251,8 +252,9 @@
}
// Use the passed handler instead of DIPSWebContentsObserver::EmitDIPSIssue().
- void SetIssueReportingCallbackForTesting(DIPSIssueCallback callback) {
- issue_callback_ = callback;
+ void SetIssueReportingCallbackForTesting(
+ DIPSIssueReportingCallback callback) {
+ issue_reporting_callback_ = callback;
}
void SetClockForTesting(base::Clock* clock) {
@@ -300,7 +302,7 @@
// DIPSWebContentsObserver is observing.
raw_ptr<DIPSService> dips_service_;
DIPSBounceDetector detector_;
- DIPSIssueCallback issue_callback_;
+ DIPSIssueReportingCallback issue_reporting_callback_;
base::WeakPtrFactory<DIPSWebContentsObserver> weak_factory_{this};
diff --git a/chrome/browser/dips/dips_bounce_detector_browsertest.cc b/chrome/browser/dips/dips_bounce_detector_browsertest.cc
index 5ce7fec0..0c5befb 100644
--- a/chrome/browser/dips/dips_bounce_detector_browsertest.cc
+++ b/chrome/browser/dips/dips_bounce_detector_browsertest.cc
@@ -40,6 +40,7 @@
#endif
using base::Bucket;
+using content::CookieAccessDetails;
using content::NavigationHandle;
using content::WebContents;
using testing::ElementsAre;
@@ -133,8 +134,7 @@
log_.push_back(base::StringPrintf(
"OnCookiesAccessed(RenderFrameHost, %s: %s)",
- details.type == content::CookieAccessDetails::Type::kChange ? "Change"
- : "Read",
+ details.type == CookieOperation::kChange ? "Change" : "Read",
FormatURL(details.url).c_str()));
}
@@ -143,8 +143,7 @@
const content::CookieAccessDetails& details) {
log_.push_back(base::StringPrintf(
"OnCookiesAccessed(NavigationHandle, %s: %s)",
- details.type == content::CookieAccessDetails::Type::kChange ? "Change"
- : "Read",
+ details.type == CookieOperation::kChange ? "Change" : "Read",
FormatURL(details.url).c_str()));
}
@@ -245,7 +244,7 @@
const auto url =
embedded_test_server()->GetURL(host, "/set-cookie?name=value");
URLCookieAccessObserver observer(web_contents, url,
- URLCookieAccessObserver::Type::kChange);
+ CookieOperation::kChange);
bool success = content::NavigateToURL(web_contents, url);
if (success) {
observer.Wait();
@@ -256,7 +255,7 @@
void CreateImageAndWaitForCookieAccess(const GURL& image_url) {
WebContents* web_contents = GetActiveWebContents();
URLCookieAccessObserver observer(web_contents, image_url,
- URLCookieAccessObserver::Type::kRead);
+ CookieOperation::kRead);
ASSERT_TRUE(content::ExecJs(web_contents,
content::JsReplace(
R"(
@@ -323,7 +322,7 @@
// Visit the redirect.
URLCookieAccessObserver observer(web_contents, final_url,
- URLCookieAccessObserver::Type::kChange);
+ CookieOperation::kChange);
ASSERT_TRUE(content::NavigateToURL(web_contents, redirect_url, final_url));
observer.Wait();
@@ -458,24 +457,42 @@
ASSERT_TRUE(content::NavigateToURL(
web_contents, embedded_test_server()->GetURL("a.test", "/title1.html")));
- // Navigate with a click (not a redirect) to b.test, which S-redirects to
- // c.test.
+ // Navigate with a click (not a redirect) to b.test, which statefully
+ // S-redirects to c.test.
ASSERT_TRUE(content::NavigateToURLFromRenderer(
web_contents,
- embedded_test_server()->GetURL("b.test",
- "/cross-site/c.test/title1.html"),
+ embedded_test_server()->GetURL(
+ "b.test", "/cross-site-with-cookie/c.test/title1.html"),
embedded_test_server()->GetURL("c.test", "/title1.html")));
+ // Write a cookie via JS on c.test.
+ content::RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
+ FrameCookieAccessObserver c_cookie_observer(web_contents, frame,
+ CookieOperation::kChange);
+ ASSERT_TRUE(content::ExecJs(frame, "document.cookie = 'foo=bar';",
+ content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+ c_cookie_observer.Wait();
+
// Navigate without a click (i.e. by C-redirecting) to d.test.
ASSERT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture(
web_contents, embedded_test_server()->GetURL("d.test", "/title1.html")));
+ // Write a cookie via JS on d.test.
+ frame = web_contents->GetPrimaryMainFrame();
+ FrameCookieAccessObserver d_cookie_observer(web_contents, frame,
+ CookieOperation::kChange);
+ ASSERT_TRUE(content::ExecJs(frame, "document.cookie = 'foo=bar';",
+ content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+ d_cookie_observer.Wait();
+
// Navigate without a click (i.e. by C-redirecting) to e.test, which
- // S-redirects to f.test, which S-redirects to g.test.
+ // statefully S-redirects to f.test, which statefully S-redirects to g.test.
ASSERT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture(
web_contents,
embedded_test_server()->GetURL(
- "e.test", "/cross-site/f.test/cross-site/g.test/title1.html"),
+ "e.test",
+ "/cross-site-with-cookie/f.test/cross-site-with-cookie/g.test/"
+ "title1.html"),
embedded_test_server()->GetURL("g.test", "/title1.html")));
EndRedirectChain();
BlockUntilHelperProcessesPendingRequests();
@@ -753,11 +770,19 @@
// c.test.
ASSERT_TRUE(content::NavigateToURLFromRenderer(
web_contents,
- embedded_test_server()->GetURL("b.test",
- "/cross-site/c.test/title1.html"),
+ embedded_test_server()->GetURL(
+ "b.test", "/cross-site-with-cookie/c.test/title1.html"),
embedded_test_server()->GetURL("c.test", "/title1.html")));
WaitForIssueAndCheckTrackingSites({"b.test"});
+ // Write a cookie via JS on c.test.
+ content::RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
+ FrameCookieAccessObserver cookie_observer(web_contents, frame,
+ CookieOperation::kChange);
+ ASSERT_TRUE(content::ExecJs(frame, "document.cookie = 'foo=bar';",
+ content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+ cookie_observer.Wait();
+
// Navigate without a click (i.e. by C-redirecting) to d.test.
ASSERT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture(
web_contents, embedded_test_server()->GetURL("d.test", "/title1.html")));
@@ -768,7 +793,11 @@
ASSERT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture(
web_contents,
embedded_test_server()->GetURL(
- "e.test", "/cross-site/f.test/cross-site/g.test/title1.html"),
+ "e.test",
+ "/cross-site-with-cookie/f.test/cross-site-with-cookie/g.test/"
+ "title1.html"),
embedded_test_server()->GetURL("g.test", "/title1.html")));
- WaitForIssueAndCheckTrackingSites({"d.test", "e.test", "f.test"});
+ // Note d.test is not listed as a potentially tracking site since it did not
+ // write cookies before bouncing the user.
+ WaitForIssueAndCheckTrackingSites({"e.test", "f.test"});
}
diff --git a/chrome/browser/dips/dips_bounce_detector_unittest.cc b/chrome/browser/dips/dips_bounce_detector_unittest.cc
index 08c372ca..63cbfc2 100644
--- a/chrome/browser/dips/dips_bounce_detector_unittest.cc
+++ b/chrome/browser/dips/dips_bounce_detector_unittest.cc
@@ -7,6 +7,7 @@
#include <tuple>
#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
@@ -587,23 +588,29 @@
TEST_F(DIPSBounceDetectorTest,
ReportRedirectorsInChain_OnEachFinishedNavigation) {
- // Visit initial page on a.test
+ // Visit initial page on a.test and access cookies via JS.
NavigateTo("http://a.test", kWithUserGesture);
+ AccessClientCookie(CookieOperation::kChange);
// Navigate with a click (not a redirect) to b.test, which S-redirects to
- // c.test
+ // c.test.
StartNavigation("http://b.test", kWithUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://c.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(true);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test"));
- // Navigate without a click (i.e. by C-redirecting) to d.test
+ // Navigate without a click (i.e. by C-redirecting) to d.test.
NavigateTo("http://d.test", kNoUserGesture);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test", "c.test"));
+ // Access cookies on d.test.
+ AccessClientCookie(CookieOperation::kChange);
// Navigate without a click (i.e. by C-redirecting) to e.test, which
- // S-redirects to f.test
+ // S-redirects to f.test.
StartNavigation("http://e.test", kNoUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://f.test")
.Finish(true);
EXPECT_THAT(GetReportedSites(),
@@ -612,50 +619,93 @@
TEST_F(DIPSBounceDetectorTest,
ReportRedirectorsInChain_IncludingUncommittedNavigations) {
- // Visit initial page on a.test
+ // Visit initial page on a.test and access cookies via JS.
NavigateTo("http://a.test", kWithUserGesture);
+ AccessClientCookie(CookieOperation::kChange);
// Start a redirect chain that doesn't commit.
StartNavigation("http://b.test", kWithUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://c.test")
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://d.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(false);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test, c.test"));
// Because the previous navigation didn't commit, the following chain still
// starts from http://a.test/.
StartNavigation("http://e.test", kWithUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://f.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(true);
EXPECT_THAT(GetReportedSites(),
testing::ElementsAre("b.test, c.test", "e.test"));
}
+TEST_F(DIPSBounceDetectorTest,
+ ReportRedirectorsInChain_OmitNonStatefulRedirects) {
+ // Visit initial page on a.test and access cookies via JS.
+ NavigateTo("http://a.test", kWithUserGesture);
+ AccessClientCookie(CookieOperation::kChange);
+
+ // Navigate with a click (not a redirect) to b.test, which S-redirects to
+ // c.test (which doesn't access cookies).
+ StartNavigation("http://b.test", kWithUserGesture)
+ .AccessCookie(CookieOperation::kChange)
+ .RedirectTo("http://c.test")
+ .Finish(true);
+ EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test"));
+
+ // Navigate without a click (i.e. by C-redirecting) to d.test (which doesn't
+ // access cookies).
+ NavigateTo("http://d.test", kNoUserGesture);
+ EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test"));
+
+ // Navigate without a click (i.e. by C-redirecting) to e.test, which
+ // S-redirects to f.test.
+ StartNavigation("http://e.test", kNoUserGesture)
+ .AccessCookie(CookieOperation::kChange)
+ .RedirectTo("http://f.test")
+ .Finish(true);
+ EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test", "e.test"));
+}
+
// This test verifies that sites in a redirect chain that are the same as the
// starting site (i.e., last site before the redirect chain started) are not
// reported.
TEST_F(DIPSBounceDetectorTest,
ReportRedirectorsInChain_OmitSitesMatchingStartSite) {
- // Visit initial page on a.test.
+ // Visit initial page on a.test and access cookies via JS.
NavigateTo("http://a.test", kWithUserGesture);
+ AccessClientCookie(CookieOperation::kChange);
// Navigate with a click (not a redirect) to b.test, which S-redirects to
// a.test, which S-redirects to c.test.
StartNavigation("http://b.test", kWithUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://a.test")
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://c.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(true);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test"));
// Navigate without a click (i.e. by C-redirecting) to a.test.
NavigateTo("http://a.test", kNoUserGesture);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test", "c.test"));
+ // Access cookies via JS on a.test.
+ AccessClientCookie(CookieOperation::kChange);
// Navigate without a click (i.e. by C-redirecting) to d.test, which
// S-redirects to e.test, which S-redirects to f.test.
StartNavigation("http://d.test", kNoUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://e.test")
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://f.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(true);
EXPECT_THAT(GetReportedSites(),
testing::ElementsAre("b.test", "c.test", "d.test, e.test"));
@@ -665,26 +715,35 @@
// as the ending site of a navigation are not reported.
TEST_F(DIPSBounceDetectorTest,
ReportRedirectorsInChain_OmitSitesMatchingEndSite) {
- // Visit initial page on a.test.
+ // Visit initial page on a.test and access cookies via JS.
NavigateTo("http://a.test", kWithUserGesture);
+ AccessClientCookie(CookieOperation::kChange);
// Navigate with a click (not a redirect) to b.test, which S-redirects to
// c.test, which S-redirects to c.test.
StartNavigation("http://b.test", kWithUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://c.test")
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://c.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(true);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test"));
// Navigate without a click (i.e. by C-redirecting) to d.test.
NavigateTo("http://d.test", kNoUserGesture);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test", "c.test"));
+ // Access cookies via JS on d.test.
+ AccessClientCookie(CookieOperation::kChange);
// Navigate without a click (i.e. by C-redirecting) to e.test, which
// S-redirects to f.test, which S-redirects to e.test.
StartNavigation("http://e.test", kNoUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://f.test")
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://e.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(true);
EXPECT_THAT(GetReportedSites(),
testing::ElementsAre("b.test", "c.test", "d.test, f.test"));
@@ -692,14 +751,18 @@
TEST_F(DIPSBounceDetectorTest,
ReportRedirectorsInChain_OmitSitesMatchingEndSite_Uncommitted) {
- // Visit initial page on a.test.
+ // Visit initial page on a.test and access cookies via JS.
NavigateTo("http://a.test", kWithUserGesture);
+ AccessClientCookie(CookieOperation::kChange);
// Navigate with a click (not a redirect) to b.test, which S-redirects to
// c.test, which S-redirects to c.test.
StartNavigation("http://b.test", kWithUserGesture)
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://c.test")
+ .AccessCookie(CookieOperation::kChange)
.RedirectTo("http://c.test")
+ .AccessCookie(CookieOperation::kChange)
.Finish(false);
EXPECT_THAT(GetReportedSites(), testing::ElementsAre("b.test"));
@@ -1000,11 +1063,13 @@
TEST(DIPSRedirectContextTest, OneAppend) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
ASSERT_EQ(chains.size(), 0u);
context.AppendCommitted(
GURL("http://a.test/"),
- MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}));
+ MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}),
+ GURL("http://d.test/"));
ASSERT_EQ(chains.size(), 0u);
context.EndChain(GURL("http://d.test/"));
@@ -1019,14 +1084,17 @@
TEST(DIPSRedirectContextTest, TwoAppends_NoClientRedirect) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
ASSERT_EQ(chains.size(), 0u);
context.AppendCommitted(
GURL("http://a.test/"),
- MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}));
+ MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}),
+ GURL("http://d.test/"));
ASSERT_EQ(chains.size(), 0u);
context.AppendCommitted(GURL("http://d.test/"),
- MakeServerRedirects(0, {"http://e.test/"}));
+ MakeServerRedirects(0, {"http://e.test/"}),
+ GURL("http://f.test/"));
ASSERT_EQ(chains.size(), 1u);
context.EndChain(GURL("http://f.test/"));
@@ -1046,15 +1114,18 @@
TEST(DIPSRedirectContextTest, TwoAppends_WithClientRedirect) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
ASSERT_EQ(chains.size(), 0u);
context.AppendCommitted(
GURL("http://a.test/"),
- MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}));
+ MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}),
+ GURL("http://d.test/"));
ASSERT_EQ(chains.size(), 0u);
context.AppendCommitted(
MakeClientRedirect(2, "http://d.test/"),
- MakeServerRedirects(3, {"http://e.test/", "http://f.test/"}));
+ MakeServerRedirects(3, {"http://e.test/", "http://f.test/"}),
+ GURL("http://g.test/"));
ASSERT_EQ(chains.size(), 0u);
context.EndChain(GURL("http://g.test/"));
@@ -1078,13 +1149,16 @@
TEST(DIPSRedirectContextTest, OnlyClientRedirects) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
ASSERT_EQ(chains.size(), 0u);
- context.AppendCommitted(GURL("http://a.test/"), {});
+ context.AppendCommitted(GURL("http://a.test/"), {}, GURL("http://b.test/"));
ASSERT_EQ(chains.size(), 0u);
- context.AppendCommitted(MakeClientRedirect(0, "http://b.test/"), {});
+ context.AppendCommitted(MakeClientRedirect(0, "http://b.test/"), {},
+ GURL("http://c.test/"));
ASSERT_EQ(chains.size(), 0u);
- context.AppendCommitted(MakeClientRedirect(1, "http://c.test/"), {});
+ context.AppendCommitted(MakeClientRedirect(1, "http://c.test/"), {},
+ GURL("http://d.test/"));
ASSERT_EQ(chains.size(), 0u);
context.EndChain(GURL("http://d.test"));
@@ -1099,11 +1173,13 @@
TEST(DIPSRedirectContextTest, Uncommitted_NoClientRedirects) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
ASSERT_EQ(chains.size(), 0u);
context.AppendCommitted(
GURL("http://a.test/"),
- MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}));
+ MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}),
+ GURL("http://d.test/"));
ASSERT_EQ(chains.size(), 0u);
context.HandleUncommitted(
GURL("http://d.test/"),
@@ -1111,7 +1187,8 @@
GURL("http://g.test/"));
ASSERT_EQ(chains.size(), 1u);
context.AppendCommitted(GURL("http://h.test/"),
- MakeServerRedirects(0, {"http://i.test/"}));
+ MakeServerRedirects(0, {"http://i.test/"}),
+ GURL("http://j.test/"));
ASSERT_EQ(chains.size(), 2u);
context.EndChain(GURL("http://j.test/"));
@@ -1138,11 +1215,13 @@
TEST(DIPSRedirectContextTest, Uncommitted_IncludingClientRedirects) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
ASSERT_EQ(chains.size(), 0u);
context.AppendCommitted(
GURL("http://a.test/"),
- MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}));
+ MakeServerRedirects(0, {"http://b.test/", "http://c.test/"}),
+ GURL("http://d.test/"));
ASSERT_EQ(chains.size(), 0u);
// Uncommitted navigation:
context.HandleUncommitted(
@@ -1151,7 +1230,8 @@
GURL("http://g.test/"));
ASSERT_EQ(chains.size(), 1u);
context.AppendCommitted(MakeClientRedirect(2, "http://h.test/"),
- MakeServerRedirects(3, {"http://i.test/"}));
+ MakeServerRedirects(3, {"http://i.test/"}),
+ GURL("http://j.test/"));
ASSERT_EQ(chains.size(), 1u);
context.EndChain(GURL("http://j.test/"));
@@ -1179,13 +1259,14 @@
TEST(DIPSRedirectContextTest, NoRedirects) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
ASSERT_EQ(chains.size(), 0u);
- context.AppendCommitted(GURL("http://a.test/"), {});
+ context.AppendCommitted(GURL("http://a.test/"), {}, GURL("http://b.test/"));
ASSERT_EQ(chains.size(), 0u);
- context.AppendCommitted(GURL("http://b.test/"), {});
+ context.AppendCommitted(GURL("http://b.test/"), {}, GURL("http://c.test/"));
ASSERT_EQ(chains.size(), 1u);
context.HandleUncommitted(GURL("http://c.test/"), {}, GURL("http://d.test/"));
@@ -1213,13 +1294,15 @@
TEST(DIPSRedirectContextTest, AddLateCookieAccess) {
std::vector<ChainPair> chains;
DIPSRedirectContext context(
- base::BindRepeating(AppendChainPair, std::ref(chains)), GURL());
+ base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+ GURL());
context.AppendCommitted(
GURL("http://a.test/"),
MakeServerRedirects(
0, {"http://b.test/", "http://c.test/", "http://d.test/"},
- CookieAccessType::kNone));
+ CookieAccessType::kNone),
+ GURL("http://e.test/"));
EXPECT_TRUE(context.AddLateCookieAccess(GURL("http://b.test/"),
CookieOperation::kChange));
@@ -1239,7 +1322,8 @@
context.AppendCommitted(
MakeClientRedirect(3, "http://e.test/", CookieAccessType::kNone),
MakeServerRedirects(4, {"http://f.test/", "http://g.test/"},
- CookieAccessType::kRead));
+ CookieAccessType::kRead),
+ GURL("http://h.test/"));
// This late "write" will be merged with the "read" already recorded.
EXPECT_TRUE(context.AddLateCookieAccess(GURL("http://g.test/"),
@@ -1247,7 +1331,8 @@
context.AppendCommitted(
MakeClientRedirect(6, "http://h.test/", CookieAccessType::kNone),
- MakeServerRedirects(7, {"http://i.test/"}, CookieAccessType::kRead));
+ MakeServerRedirects(7, {"http://i.test/"}, CookieAccessType::kRead),
+ GURL("http://j.test/"));
// Can't modify h.test since i.test already has a known cookie access.
EXPECT_FALSE(context.AddLateCookieAccess(GURL("http://h.test/"),
diff --git a/chrome/browser/dips/dips_helper_browsertest.cc b/chrome/browser/dips/dips_helper_browsertest.cc
index f73a2481..d2c3b3d 100644
--- a/chrome/browser/dips/dips_helper_browsertest.cc
+++ b/chrome/browser/dips/dips_helper_browsertest.cc
@@ -53,29 +53,6 @@
namespace {
-class FrameCookieAccessObserver : public content::WebContentsObserver {
- public:
- explicit FrameCookieAccessObserver(WebContents* web_contents,
- RenderFrameHost* render_frame_host)
- : WebContentsObserver(web_contents),
- render_frame_host_(render_frame_host) {}
-
- // Wait until the frame accesses cookies.
- void Wait() { run_loop_.Run(); }
-
- // WebContentsObserver override
- void OnCookiesAccessed(content::RenderFrameHost* render_frame_host,
- const content::CookieAccessDetails& details) override {
- if (render_frame_host_ == render_frame_host) {
- run_loop_.Quit();
- }
- }
-
- private:
- const raw_ptr<content::RenderFrameHost> render_frame_host_;
- base::RunLoop run_loop_;
-};
-
// Histogram names
constexpr char kTimeToInteraction[] =
"Privacy.DIPS.TimeFromStorageToInteraction.Standard";
@@ -339,7 +316,8 @@
// Write a cookie in the b.test iframe.
SetDIPSTime(time);
- FrameCookieAccessObserver observer(web_contents, iframe);
+ FrameCookieAccessObserver observer(web_contents, iframe,
+ CookieAccessDetails::Type::kChange);
ASSERT_TRUE(content::ExecJs(
iframe, "document.cookie = 'foo=bar; SameSite=None; Secure';",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
@@ -385,7 +363,8 @@
// b.test.
SetDIPSTime(time + base::Seconds(10));
FrameCookieAccessObserver observer(web_contents,
- web_contents->GetPrimaryMainFrame());
+ web_contents->GetPrimaryMainFrame(),
+ CookieAccessDetails::Type::kRead);
ASSERT_TRUE(content::ExecJs(web_contents,
content::JsReplace(
R"(
@@ -527,7 +506,8 @@
// Write a cookie now that the click has been handled.
SetDIPSTime(time + base::Seconds(10));
- FrameCookieAccessObserver cookie_observer(web_contents, frame);
+ FrameCookieAccessObserver cookie_observer(web_contents, frame,
+ CookieAccessDetails::Type::kChange);
ASSERT_TRUE(content::ExecJs(frame, "document.cookie = 'foo=bar';",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
cookie_observer.Wait();
@@ -629,7 +609,8 @@
// Write a cookie now that both clicks have been handled.
SetDIPSTime(time + DIPSBounceDetector::kTimestampUpdateInterval +
base::Seconds(10));
- FrameCookieAccessObserver cookie_observer(web_contents, frame);
+ FrameCookieAccessObserver cookie_observer(web_contents, frame,
+ CookieAccessDetails::Type::kChange);
ASSERT_TRUE(content::ExecJs(frame, "document.cookie = 'foo=bar';",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
cookie_observer.Wait();
diff --git a/chrome/browser/dips/dips_test_utils.cc b/chrome/browser/dips/dips_test_utils.cc
index 66a96ca..680ed0ef 100644
--- a/chrome/browser/dips/dips_test_utils.cc
+++ b/chrome/browser/dips/dips_test_utils.cc
@@ -17,7 +17,7 @@
URLCookieAccessObserver::URLCookieAccessObserver(WebContents* web_contents,
const GURL& url,
- Type access_type)
+ CookieOperation access_type)
: WebContentsObserver(web_contents), url_(url), access_type_(access_type) {}
void URLCookieAccessObserver::Wait() {
@@ -40,6 +40,26 @@
}
}
+FrameCookieAccessObserver::FrameCookieAccessObserver(
+ WebContents* web_contents,
+ RenderFrameHost* render_frame_host,
+ CookieOperation access_type)
+ : WebContentsObserver(web_contents),
+ render_frame_host_(render_frame_host),
+ access_type_(access_type) {}
+
+void FrameCookieAccessObserver::Wait() {
+ run_loop_.Run();
+}
+
+void FrameCookieAccessObserver::OnCookiesAccessed(
+ content::RenderFrameHost* render_frame_host,
+ const content::CookieAccessDetails& details) {
+ if (details.type == access_type_ && render_frame_host_ == render_frame_host) {
+ run_loop_.Quit();
+ }
+}
+
RedirectChainObserver::RedirectChainObserver(DIPSService* service,
GURL final_url)
: final_url_(std::move(final_url)) {
diff --git a/chrome/browser/dips/dips_test_utils.h b/chrome/browser/dips/dips_test_utils.h
index 120f7af..1d83b608 100644
--- a/chrome/browser/dips/dips_test_utils.h
+++ b/chrome/browser/dips/dips_test_utils.h
@@ -14,6 +14,7 @@
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/dips/dips_redirect_info.h"
#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_utils.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/cookie_access_details.h"
@@ -28,10 +29,9 @@
class URLCookieAccessObserver : public content::WebContentsObserver {
public:
- using Type = content::CookieAccessDetails::Type;
URLCookieAccessObserver(content::WebContents* web_contents,
const GURL& url,
- Type access_type);
+ CookieOperation access_type);
void Wait();
@@ -43,7 +43,27 @@
const content::CookieAccessDetails& details) override;
GURL url_;
- Type access_type_;
+ CookieOperation access_type_;
+ base::RunLoop run_loop_;
+};
+
+class FrameCookieAccessObserver : public content::WebContentsObserver {
+ public:
+ explicit FrameCookieAccessObserver(
+ content::WebContents* web_contents,
+ content::RenderFrameHost* render_frame_host,
+ CookieOperation access_type);
+
+ // Wait until the frame accesses cookies.
+ void Wait();
+
+ // WebContentsObserver override
+ void OnCookiesAccessed(content::RenderFrameHost* render_frame_host,
+ const content::CookieAccessDetails& details) override;
+
+ private:
+ const raw_ptr<content::RenderFrameHost> render_frame_host_;
+ CookieOperation access_type_;
base::RunLoop run_loop_;
};