Skip to content

Commit

Permalink
Add new UKM event OpenerHeuristic_PostPopupCookieAccess.
Browse files Browse the repository at this point in the history
Reviewed and approved on https://docs.google.com/document/d/1GJBMYuhl77eTSjLMMEtq1Y2Kd3Vdv1ozfdFi1iCueZI/edit?usp=sharing.

Change-Id: I5da33123c65d7969afc6da936ecb59147643d443
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4742007
Commit-Queue: Anton Maliev <amaliev@chromium.org>
Reviewed-by: Sun Yueru <yrsun@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1185221}
  • Loading branch information
amaliev authored and Chromium LUCI CQ committed Aug 18, 2023
1 parent 06d0444 commit ab9c0c2
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 61 deletions.
139 changes: 139 additions & 0 deletions chrome/browser/3pcd/heuristics/opener_heuristic_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
Expand All @@ -12,6 +14,7 @@
#include "chrome/browser/dips/dips_service.h"
#include "chrome/browser/dips/dips_storage.h"
#include "chrome/browser/dips/dips_test_utils.h"
#include "chrome/browser/dips/dips_utils.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/navigation_handle.h"
Expand Down Expand Up @@ -377,6 +380,70 @@ IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest,
1u);
}

IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest,
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");

// Initialize interaction and popup.
RecordInteraction(popup_url, clock_.Now() - base::Hours(3));
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
ASSERT_TRUE(OpenPopup(popup_url).has_value());
GetDipsService()->storage()->FlushPostedTasksForTesting();

// Assert that the UKM events and DIPS entries were recorded.
ASSERT_EQ(
ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupPastInteraction")
.size(),
1u);
ASSERT_EQ(ukm_recorder.GetEntriesByName("OpenerHeuristic.TopLevel").size(),
1u);

int64_t access_id;
base::OnceCallback<void(absl::optional<PopupsStateValue>)> assert_popup =
base::BindLambdaForTesting([&](absl::optional<PopupsStateValue> state) {
ASSERT_TRUE(state.has_value());
access_id = state->access_id;
});
GetDipsService()
->storage()
->AsyncCall(&DIPSStorage::ReadPopup)
.WithArgs(GetSiteForDIPS(opener_url), GetSiteForDIPS(popup_url))
.Then(std::move(assert_popup));
GetDipsService()->storage()->FlushPostedTasksForTesting();

// We host the "image" 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(
base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
ASSERT_TRUE(https_server.Start());

// Add a cookie access by popup_url on opener_url.
ASSERT_TRUE(NavigateToSetCookie(GetActiveWebContents(), &https_server,
"sub.b.test",
/*is_secure_cookie_set=*/true));
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
CreateImageAndWaitForCookieAccess(
GetActiveWebContents(),
https_server.GetURL("sub.b.test", "/favicon/icon.png"));
GetDipsService()->storage()->FlushPostedTasksForTesting();

// Assert that the UKM event for the PostPopupCookieAccess was recorded.
auto access_entries = ukm_recorder.GetEntries(
"OpenerHeuristic.PostPopupCookieAccess",
{"AccessId", "AccessSucceeded", "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["HoursSincePopupOpened"], 0);
}

IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, PopupInteraction) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html");
Expand Down Expand Up @@ -472,6 +539,78 @@ IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest,
EXPECT_EQ(entries[0].metrics["UrlIndex"], 1);
}

IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest,
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");

// Initialize popup and interaction.
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
auto maybe_popup = OpenPopup(popup_url_1);
ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();

clock_.Advance(base::Minutes(1));
ASSERT_TRUE(content::NavigateToURL(*maybe_popup, popup_url_2, popup_url_3));

clock_.Advance(base::Minutes(1));
SimulateMouseClick(*maybe_popup);
GetDipsService()->storage()->FlushPostedTasksForTesting();

// Assert that the UKM events and DIPS entries were recorded.
ASSERT_EQ(
ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(),
1u);
ASSERT_EQ(ukm_recorder.GetEntriesByName("OpenerHeuristic.TopLevel").size(),
1u);

int64_t access_id;
base::OnceCallback<void(absl::optional<PopupsStateValue>)> assert_popup =
base::BindLambdaForTesting([&](absl::optional<PopupsStateValue> state) {
ASSERT_TRUE(state.has_value());
access_id = state->access_id;
});
GetDipsService()
->storage()
->AsyncCall(&DIPSStorage::ReadPopup)
.WithArgs(GetSiteForDIPS(opener_url), GetSiteForDIPS(popup_url_3))
.Then(std::move(assert_popup));
GetDipsService()->storage()->FlushPostedTasksForTesting();

// We host the "image" 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(
base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
ASSERT_TRUE(https_server.Start());

// Add a cookie access by popup_url on opener_url.
ASSERT_TRUE(NavigateToSetCookie(GetActiveWebContents(), &https_server,
"sub.b.test",
/*is_secure_cookie_set=*/true));
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
CreateImageAndWaitForCookieAccess(
GetActiveWebContents(),
https_server.GetURL("sub.b.test", "/favicon/icon.png"));
GetDipsService()->storage()->FlushPostedTasksForTesting();

// Assert that the UKM event for the PostPopupCookieAccess was recorded.
auto access_entries = ukm_recorder.GetEntries(
"OpenerHeuristic.PostPopupCookieAccess",
{"AccessId", "AccessSucceeded", "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["HoursSincePopupOpened"], 0);
}

IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest,
TopLevelIsReported_PastInteraction_NoSameSiteIframe) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
Expand Down
84 changes: 80 additions & 4 deletions chrome/browser/3pcd/heuristics/opener_heuristic_tab_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "content/public/browser/render_frame_host.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"

using content::NavigationHandle;
using content::RenderFrameHost;
Expand Down Expand Up @@ -136,8 +137,10 @@ OpenerHeuristicTabHelper::PopupObserver::PopupObserver(
opener_(opener),
opener_page_id_(opener->page_id()),
opener_source_id_(
opener->web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()) {
}
opener->web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()),
opener_url_(opener->web_contents()
->GetPrimaryMainFrame()
->GetLastCommittedURL()) {}

OpenerHeuristicTabHelper::PopupObserver::~PopupObserver() = default;

Expand Down Expand Up @@ -172,7 +175,7 @@ void OpenerHeuristicTabHelper::PopupObserver::EmitPastInteractionIfReady() {
.SetPopupId(popup_id_)
.Record(ukm::UkmRecorder::Get());

EmitTopLevel(has_iframe);
EmitTopLevel(initial_url_, has_iframe);
}

void OpenerHeuristicTabHelper::PopupObserver::DidFinishNavigation(
Expand Down Expand Up @@ -236,10 +239,69 @@ void OpenerHeuristicTabHelper::PopupObserver::FrameReceivedUserActivation(

interaction_reported_ = true;

EmitTopLevel(has_iframe);
EmitTopLevel(render_frame_host->GetLastCommittedURL(), has_iframe);
}

void OpenerHeuristicTabHelper::OnCookiesAccessed(
content::RenderFrameHost* render_frame_host,
const content::CookieAccessDetails& details) {
if (!render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kPrerendering)) {
OnCookiesAccessed(render_frame_host->GetPageUkmSourceId(), details);
}
}

void OpenerHeuristicTabHelper::OnCookiesAccessed(
content::NavigationHandle* navigation_handle,
const content::CookieAccessDetails& details) {
OnCookiesAccessed(navigation_handle->GetNextPageUkmSourceId(), details);
}

void OpenerHeuristicTabHelper::OnCookiesAccessed(
const ukm::SourceId& source_id,
const content::CookieAccessDetails& details) {
DIPSService* dips = DIPSService::Get(web_contents()->GetBrowserContext());
if (!dips) {
// If DIPS is disabled, we can't look up past popup events.
// TODO(rtarpine): consider falling back to SiteEngagementService.
return;
}

// Ignore same-domain cookie access.
if (details.first_party_url.is_empty() ||
GetSiteForDIPS(details.first_party_url) == GetSiteForDIPS(details.url)) {
return;
}

dips->storage()
->AsyncCall(&DIPSStorage::ReadPopup)
.WithArgs(GetSiteForDIPS(details.first_party_url),
GetSiteForDIPS(details.url))
.Then(base::BindOnce(&OpenerHeuristicTabHelper::EmitPostPopupCookieAccess,
weak_factory_.GetWeakPtr(), source_id, details));
}

void OpenerHeuristicTabHelper::EmitPostPopupCookieAccess(
const ukm::SourceId& source_id,
const content::CookieAccessDetails& details,
absl::optional<PopupsStateValue> value) {
if (!value.has_value()) {
return;
}
int32_t hours_since_opener = Bucketize3PCDHeuristicTimeDelta(
GetClock()->Now() - value->last_popup_time, base::Days(30),
base::BindRepeating(&base::TimeDelta::InHours)
.Then(base::BindRepeating([](int64_t t) { return t; })));

ukm::builders::OpenerHeuristic_PostPopupCookieAccess(source_id)
.SetAccessId(value->access_id)
.SetAccessSucceeded(!details.blocked_by_policy)
.SetHoursSincePopupOpened(hours_since_opener)
.Record(ukm::UkmRecorder::Get());
}

void OpenerHeuristicTabHelper::PopupObserver::EmitTopLevel(
const GURL& popup_url,
OptionalBool has_iframe) {
if (toplevel_reported_) {
return;
Expand All @@ -252,6 +314,20 @@ void OpenerHeuristicTabHelper::PopupObserver::EmitTopLevel(
.Record(ukm::UkmRecorder::Get());

toplevel_reported_ = true;

DIPSService* dips = DIPSService::Get(web_contents()->GetBrowserContext());
if (!dips) {
// If DIPS is disabled, we can't look up past popup events.
// TODO(rtarpine): consider falling back to SiteEngagementService.
return;
}

dips->storage()
->AsyncCall(&DIPSStorage::WritePopup)
.WithArgs(GetSiteForDIPS(opener_url_), GetSiteForDIPS(popup_url),
/*access_id=*/base::RandUint64(),
/*popup_time=*/GetClock()->Now())
.Then(base::BindOnce([](bool succeeded) { DCHECK(succeeded); }));
}

OptionalBool
Expand Down
16 changes: 15 additions & 1 deletion chrome/browser/3pcd/heuristics/opener_heuristic_tab_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/dips/dips_utils.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
Expand Down Expand Up @@ -52,7 +53,7 @@ class OpenerHeuristicTabHelper
// the necessary information.
void EmitPastInteractionIfReady();
// Emit the OpenerHeuristic.TopLevel UKM event.
void EmitTopLevel(OptionalBool has_iframe);
void EmitTopLevel(const GURL& tracker_url, OptionalBool has_iframe);
// See if the opener page has an iframe from the same site.
OptionalBool GetOpenerHasSameSiteIframe(const GURL& popup_url);

Expand All @@ -70,6 +71,8 @@ class OpenerHeuristicTabHelper
const size_t opener_page_id_;
// A UKM source id for the page that opened the pop-up.
const ukm::SourceId opener_source_id_;
// The URL of the page that opened the pop-up.
const GURL opener_url_;
// How long after the user last interacted with the site until the pop-up
// opened.
absl::optional<base::TimeDelta> time_since_interaction_;
Expand Down Expand Up @@ -101,6 +104,13 @@ class OpenerHeuristicTabHelper
// Asynchronous callback for reading past interaction timestamps from the
// DIPSService.
void GotPopupDipsState(const DIPSState& state);
// Record a OpenerHeuristic.PostPopupCookieAccess event, if there has been a
// corresponding popup event for the provided source and target sites.
void OnCookiesAccessed(const ukm::SourceId& source_id,
const content::CookieAccessDetails& details);
void EmitPostPopupCookieAccess(const ukm::SourceId& source_id,
const content::CookieAccessDetails& details,
absl::optional<PopupsStateValue> value);

// WebContentsObserver overrides:
void PrimaryPageChanged(content::Page& page) override;
Expand All @@ -112,6 +122,10 @@ class OpenerHeuristicTabHelper
ui::PageTransition transition,
bool started_from_context_menu,
bool renderer_initiated) override;
void OnCookiesAccessed(content::RenderFrameHost* render_frame_host,
const content::CookieAccessDetails& details) override;
void OnCookiesAccessed(content::NavigationHandle* navigation_handle,
const content::CookieAccessDetails& details) override;

// To detect whether the user navigated away from the opener page before
// interacting with a popup, we increment this ID on each committed
Expand Down

0 comments on commit ab9c0c2

Please sign in to comment.