Commit fe9fa994 authored by Justin Miron's avatar Justin Miron Committed by Commit Bot

Ads Intervention Framework: UKM for triggered ads interventions

This change records UKM, AdsIntervention_LastIntervention on page loads
to origins that have previously triggered ads interventions. This UKM
tracks the subresource filter activation status and the ads violation
that triggered the intervention.

This allows analysis of an ads interventions benefit to a user and
their frequency across different sets of sites.

BUG=1099741

Change-Id: I653b0468c387727902f95e00c14cba1da9ae6996
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2419118
Commit-Queue: Justin Miron <justinmiron@google.com>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Cr-Commit-Position: refs/heads/master@{#820343}
parent 8df36d64
...@@ -12,7 +12,10 @@ ...@@ -12,7 +12,10 @@
#include "components/subresource_filter/content/browser/ads_intervention_manager.h" #include "components/subresource_filter/content/browser/ads_intervention_manager.h"
#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h" #include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
#include "components/subresource_filter/core/mojom/subresource_filter.mojom.h" #include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test.h" #include "content/public/test/browser_test.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -39,6 +42,7 @@ class AdsInterventionManagerTestWithEnforcement ...@@ -39,6 +42,7 @@ class AdsInterventionManagerTestWithEnforcement
IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithEnforcement, IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithEnforcement,
AdsInterventionEnforced_PageActivated) { AdsInterventionEnforced_PageActivated) {
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
ChromeSubresourceFilterClient* client = ChromeSubresourceFilterClient* client =
ChromeSubresourceFilterClient::FromWebContents(web_contents()); ChromeSubresourceFilterClient::FromWebContents(web_contents());
auto test_clock = std::make_unique<base::SimpleTestClock>(); auto test_clock = std::make_unique<base::SimpleTestClock>();
...@@ -56,6 +60,9 @@ IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithEnforcement, ...@@ -56,6 +60,9 @@ IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithEnforcement,
histogram_tester.ExpectBucketCount( histogram_tester.ExpectBucketCount(
kSubresourceFilterActionsHistogram, kSubresourceFilterActionsHistogram,
subresource_filter::SubresourceFilterAction::kUIShown, 0); subresource_filter::SubresourceFilterAction::kUIShown, 0);
auto entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(0u, entries.size());
// Trigger an ads violation and renavigate the page. Should trigger // Trigger an ads violation and renavigate the page. Should trigger
// subresource filter activation. // subresource filter activation.
...@@ -68,18 +75,45 @@ IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithEnforcement, ...@@ -68,18 +75,45 @@ IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithEnforcement,
histogram_tester.ExpectBucketCount( histogram_tester.ExpectBucketCount(
kSubresourceFilterActionsHistogram, kSubresourceFilterActionsHistogram,
subresource_filter::SubresourceFilterAction::kUIShown, 1); subresource_filter::SubresourceFilterAction::kUIShown, 1);
entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::AdsIntervention_LastIntervention::kInterventionTypeName,
static_cast<int>(mojom::AdsViolation::kMobileAdDensityByHeightAbove30));
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::AdsIntervention_LastIntervention::kInterventionStatusName,
static_cast<int>(AdsInterventionStatus::kBlocking));
// Advance the clock to clear the intervention. // Advance the clock to clear the intervention.
test_clock->Advance(subresource_filter::kAdsInterventionDuration.Get()); test_clock->Advance(subresource_filter::kAdsInterventionDuration.Get());
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame())); EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame()));
entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(2u, entries.size());
// One of the entries is kBlocking, verify that the other is kExpired after
// the intervention is cleared.
EXPECT_TRUE(
(*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdsIntervention_LastIntervention::
kInterventionStatusName) ==
static_cast<int>(AdsInterventionStatus::kExpired)) ||
(*ukm_recorder.GetEntryMetric(
entries.back(), ukm::builders::AdsIntervention_LastIntervention::
kInterventionStatusName) ==
static_cast<int>(AdsInterventionStatus::kExpired)));
} }
IN_PROC_BROWSER_TEST_F( IN_PROC_BROWSER_TEST_F(
AdsInterventionManagerTestWithEnforcement, AdsInterventionManagerTestWithEnforcement,
MultipleAdsInterventions_PageActivationClearedAfterFirst) { MultipleAdsInterventions_PageActivationClearedAfterFirst) {
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
ChromeSubresourceFilterClient* client = ChromeSubresourceFilterClient* client =
ChromeSubresourceFilterClient::FromWebContents(web_contents()); ChromeSubresourceFilterClient::FromWebContents(web_contents());
auto test_clock = std::make_unique<base::SimpleTestClock>(); auto test_clock = std::make_unique<base::SimpleTestClock>();
...@@ -97,6 +131,9 @@ IN_PROC_BROWSER_TEST_F( ...@@ -97,6 +131,9 @@ IN_PROC_BROWSER_TEST_F(
histogram_tester.ExpectBucketCount( histogram_tester.ExpectBucketCount(
kSubresourceFilterActionsHistogram, kSubresourceFilterActionsHistogram,
subresource_filter::SubresourceFilterAction::kUIShown, 0); subresource_filter::SubresourceFilterAction::kUIShown, 0);
auto entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(0u, entries.size());
// Trigger an ads violation and renavigate the page. Should trigger // Trigger an ads violation and renavigate the page. Should trigger
// subresource filter activation. // subresource filter activation.
...@@ -109,6 +146,17 @@ IN_PROC_BROWSER_TEST_F( ...@@ -109,6 +146,17 @@ IN_PROC_BROWSER_TEST_F(
histogram_tester.ExpectBucketCount( histogram_tester.ExpectBucketCount(
kSubresourceFilterActionsHistogram, kSubresourceFilterActionsHistogram,
subresource_filter::SubresourceFilterAction::kUIShown, 1); subresource_filter::SubresourceFilterAction::kUIShown, 1);
entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::AdsIntervention_LastIntervention::kInterventionTypeName,
static_cast<int>(mojom::AdsViolation::kMobileAdDensityByHeightAbove30));
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::AdsIntervention_LastIntervention::kInterventionStatusName,
static_cast<int>(AdsInterventionStatus::kBlocking));
// Advance the clock by less than kAdsInterventionDuration and trigger another // Advance the clock by less than kAdsInterventionDuration and trigger another
// intervention. This intervention is a no-op. // intervention. This intervention is a no-op.
...@@ -124,6 +172,21 @@ IN_PROC_BROWSER_TEST_F( ...@@ -124,6 +172,21 @@ IN_PROC_BROWSER_TEST_F(
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame())); EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame()));
entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(2u, entries.size());
// One of the entries is kBlocking, verify that the other is kExpired after
// the intervention is cleared.
EXPECT_TRUE(
(*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdsIntervention_LastIntervention::
kInterventionStatusName) ==
static_cast<int>(AdsInterventionStatus::kExpired)) ||
(*ukm_recorder.GetEntryMetric(
entries.back(), ukm::builders::AdsIntervention_LastIntervention::
kInterventionStatusName) ==
static_cast<int>(AdsInterventionStatus::kExpired)));
} }
class AdsInterventionManagerTestWithoutEnforcement class AdsInterventionManagerTestWithoutEnforcement
...@@ -141,8 +204,11 @@ class AdsInterventionManagerTestWithoutEnforcement ...@@ -141,8 +204,11 @@ class AdsInterventionManagerTestWithoutEnforcement
IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithoutEnforcement, IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithoutEnforcement,
AdsInterventionNotEnforced_NoPageActivation) { AdsInterventionNotEnforced_NoPageActivation) {
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
ChromeSubresourceFilterClient* client = ChromeSubresourceFilterClient* client =
ChromeSubresourceFilterClient::FromWebContents(web_contents()); ChromeSubresourceFilterClient::FromWebContents(web_contents());
auto test_clock = std::make_unique<base::SimpleTestClock>();
ads_intervention_manager()->set_clock_for_testing(test_clock.get());
const GURL url( const GURL url(
GetTestUrl("subresource_filter/frame_with_included_script.html")); GetTestUrl("subresource_filter/frame_with_included_script.html"));
...@@ -156,18 +222,35 @@ IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithoutEnforcement, ...@@ -156,18 +222,35 @@ IN_PROC_BROWSER_TEST_F(AdsInterventionManagerTestWithoutEnforcement,
histogram_tester.ExpectBucketCount( histogram_tester.ExpectBucketCount(
kSubresourceFilterActionsHistogram, kSubresourceFilterActionsHistogram,
subresource_filter::SubresourceFilterAction::kUIShown, 0); subresource_filter::SubresourceFilterAction::kUIShown, 0);
auto entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(0u, entries.size());
// Trigger an ads violation and renavigate to the page. Interventions are not // Trigger an ads violation and renavigate to the page. Interventions are not
// enforced so no activation should occur. // enforced so no activation should occur.
client->OnAdsViolationTriggered( client->OnAdsViolationTriggered(
web_contents()->GetMainFrame(), web_contents()->GetMainFrame(),
mojom::AdsViolation::kMobileAdDensityByHeightAbove30); mojom::AdsViolation::kMobileAdDensityByHeightAbove30);
const base::TimeDelta kRenavigationDelay = base::TimeDelta::FromHours(2);
test_clock->Advance(kRenavigationDelay);
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame())); EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame()));
histogram_tester.ExpectBucketCount( histogram_tester.ExpectBucketCount(
kSubresourceFilterActionsHistogram, kSubresourceFilterActionsHistogram,
subresource_filter::SubresourceFilterAction::kUIShown, 0); subresource_filter::SubresourceFilterAction::kUIShown, 0);
entries = ukm_recorder.GetEntriesByName(
ukm::builders::AdsIntervention_LastIntervention::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::AdsIntervention_LastIntervention::kInterventionTypeName,
static_cast<int>(mojom::AdsViolation::kMobileAdDensityByHeightAbove30));
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::AdsIntervention_LastIntervention::kInterventionStatusName,
static_cast<int>(AdsInterventionStatus::kWouldBlock));
} }
} // namespace subresource_filter } // namespace subresource_filter
...@@ -125,26 +125,14 @@ ChromeSubresourceFilterClient::OnPageActivationComputed( ...@@ -125,26 +125,14 @@ ChromeSubresourceFilterClient::OnPageActivationComputed(
*decision = subresource_filter::ActivationDecision::FORCED_ACTIVATION; *decision = subresource_filter::ActivationDecision::FORCED_ACTIVATION;
} }
const GURL& url(navigation_handle->GetURL()); if (profile_context_->ads_intervention_manager()->ShouldActivate(
navigation_handle)) {
base::Optional<
subresource_filter::AdsInterventionManager::LastAdsIntervention>
last_intervention =
profile_context_->ads_intervention_manager()->GetLastAdsIntervention(
url);
// Only activate the subresource filter if we are intervening on
// ads
if (profile_context_->settings_manager()->GetSiteActivationFromMetadata(
url) &&
last_intervention &&
last_intervention->duration_since <
subresource_filter::kAdsInterventionDuration.Get()) {
effective_activation_level = effective_activation_level =
subresource_filter::mojom::ActivationLevel::kEnabled; subresource_filter::mojom::ActivationLevel::kEnabled;
*decision = subresource_filter::ActivationDecision::ACTIVATED; *decision = subresource_filter::ActivationDecision::ACTIVATED;
} }
const GURL& url(navigation_handle->GetURL());
if (url.SchemeIsHTTPOrHTTPS()) { if (url.SchemeIsHTTPOrHTTPS()) {
profile_context_->settings_manager()->SetSiteMetadataBasedOnActivation( profile_context_->settings_manager()->SetSiteMetadataBasedOnActivation(
url, url,
...@@ -172,6 +160,7 @@ void ChromeSubresourceFilterClient::OnAdsViolationTriggered( ...@@ -172,6 +160,7 @@ void ChromeSubresourceFilterClient::OnAdsViolationTriggered(
// If the feature is disabled, simulate ads interventions as if we were // If the feature is disabled, simulate ads interventions as if we were
// enforcing on ads: do not record new interventions if we would be enforcing // enforcing on ads: do not record new interventions if we would be enforcing
// an intervention on ads already. // an intervention on ads already.
//
// TODO(https://crbug/1107998): Verify this behavior when violation signals // TODO(https://crbug/1107998): Verify this behavior when violation signals
// and histograms are added. // and histograms are added.
const GURL& url = rfh->GetLastCommittedURL(); const GURL& url = rfh->GetLastCommittedURL();
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
#include "base/values.h" #include "base/values.h"
#include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h" #include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h"
#include "components/subresource_filter/core/browser/subresource_filter_features.h" #include "components/subresource_filter/core/browser/subresource_filter_features.h"
#include "content/public/browser/navigation_handle.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace subresource_filter { namespace subresource_filter {
...@@ -19,6 +22,15 @@ namespace { ...@@ -19,6 +22,15 @@ namespace {
const char kLastAdsViolationTimeKey[] = "LastAdsViolationTime"; const char kLastAdsViolationTimeKey[] = "LastAdsViolationTime";
const char kLastAdsViolationKey[] = "LastAdsViolation"; const char kLastAdsViolationKey[] = "LastAdsViolation";
AdsInterventionStatus GetAdsInterventionStatus(bool activation_status,
bool intervention_active) {
if (!intervention_active)
return AdsInterventionStatus::kExpired;
return activation_status ? AdsInterventionStatus::kBlocking
: AdsInterventionStatus::kWouldBlock;
}
} // namespace } // namespace
AdsInterventionManager::AdsInterventionManager( AdsInterventionManager::AdsInterventionManager(
...@@ -69,4 +81,36 @@ AdsInterventionManager::GetLastAdsIntervention(const GURL& url) const { ...@@ -69,4 +81,36 @@ AdsInterventionManager::GetLastAdsIntervention(const GURL& url) const {
return base::nullopt; return base::nullopt;
} }
bool AdsInterventionManager::ShouldActivate(
content::NavigationHandle* navigation_handle) const {
const GURL& url(navigation_handle->GetURL());
// TODO(https://crbug.com/1136987): Add new ads intervention
// manager function to return struct with all ads intervention
// metadata to reduce metadata accesses.
base::Optional<AdsInterventionManager::LastAdsIntervention>
last_intervention = GetLastAdsIntervention(url);
// Only activate the subresource filter if we are intervening on
// ads.
bool current_activation_status =
settings_manager_->GetSiteActivationFromMetadata(url);
bool has_active_ads_intervention =
last_intervention &&
last_intervention->duration_since <
subresource_filter::kAdsInterventionDuration.Get();
if (last_intervention) {
auto* ukm_recorder = ukm::UkmRecorder::Get();
ukm::builders::AdsIntervention_LastIntervention builder(
ukm::ConvertToSourceId(navigation_handle->GetNavigationId(),
ukm::SourceIdType::NAVIGATION_ID));
builder
.SetInterventionType(static_cast<int>(last_intervention->ads_violation))
.SetInterventionStatus(static_cast<int>(GetAdsInterventionStatus(
current_activation_status, has_active_ads_intervention)));
builder.Record(ukm_recorder->Get());
}
return current_activation_status && has_active_ads_intervention;
}
} // namespace subresource_filter } // namespace subresource_filter
...@@ -17,6 +17,23 @@ namespace base { ...@@ -17,6 +17,23 @@ namespace base {
class Clock; class Clock;
} }
namespace content {
class NavigationHandle;
}
// The subresource filter activation status associated with an ads
// intervention during page load.
enum class AdsInterventionStatus {
// The ads intervention occurred longer than
// subresource_filter::kAdsInterventionDuration ago.
kExpired,
// Ads interventions are in dry run mode and the subresource filter would
// have activated due to an active intervention.
kWouldBlock,
// Ads interventions are activate and there is an active intervention.
kBlocking,
};
namespace subresource_filter { namespace subresource_filter {
// This class tracks ads interventions that have occurred on origins and is // This class tracks ads interventions that have occurred on origins and is
...@@ -69,6 +86,12 @@ class AdsInterventionManager { ...@@ -69,6 +86,12 @@ class AdsInterventionManager {
base::Optional<LastAdsIntervention> GetLastAdsIntervention( base::Optional<LastAdsIntervention> GetLastAdsIntervention(
const GURL& url) const; const GURL& url) const;
// Returns whether the subresource filter should activate for
// |navigation_handle| based on feature status and current active
// intervention. This should only be called once per page to calculate
// activation as it records per page intervention information.
bool ShouldActivate(content::NavigationHandle* navigation_handle) const;
void set_clock_for_testing(base::Clock* clock) { clock_ = clock; } void set_clock_for_testing(base::Clock* clock) { clock_ = clock; }
private: private:
......
...@@ -1269,6 +1269,16 @@ Unknown properties are collapsed to zero. --> ...@@ -1269,6 +1269,16 @@ Unknown properties are collapsed to zero. -->
<int value="3" label="Insecure non-ad request"/> <int value="3" label="Insecure non-ad request"/>
</enum> </enum>
<enum name="AdsInterventionStatus">
<int value="0" label="Expired"/>
<int value="1" label="Would Block"/>
<int value="2" label="Blocking"/>
</enum>
<enum name="AdsViolations">
<int value="0" label="kMobileAdDensityByHeightAbove30"/>
</enum>
<enum name="AdUserActivationStatus"> <enum name="AdUserActivationStatus">
<int value="0" label="No activation"/> <int value="0" label="No activation"/>
<int value="1" label="Received activation"/> <int value="1" label="Received activation"/>
...@@ -372,6 +372,30 @@ be describing additional metrics about the same event. ...@@ -372,6 +372,30 @@ be describing additional metrics about the same event.
</metric> </metric>
</event> </event>
<event name="AdsIntervention.LastIntervention" singular="True">
<owner>yaoxia@google.com</owner>
<owner>chrome-ads-core@google.com</owner>
<summary>
Recorded after computing the subresource filter activation for a page at
process response time during navigation. This is only recorded when there
has previously been an ads intervention triggered on the page within the
site data's lifetime (currently 7 days). Clearing site data will reset
intervention information.
</summary>
<metric name="InterventionStatus" enum="AdsInterventionStatus">
<summary>
Records the status of the ads intervention, whether the intervention lead
to subresource filter activation, measured during page load.
</summary>
</metric>
<metric name="InterventionType" enum="AdsViolations">
<summary>
Records the last measured intervention recorded for the page, measured
during page load.
</summary>
</metric>
</event>
<event name="AmpPageLoad" singular="True"> <event name="AmpPageLoad" singular="True">
<owner>sullivan@chromium.org</owner> <owner>sullivan@chromium.org</owner>
<metric name="MainFrameAmpPageLoad"> <metric name="MainFrameAmpPageLoad">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment