chromium/chrome/browser/page_load_metrics/observers/ad_metrics/ad_density_intervention_android_browsertest.cc

// Copyright 2020 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/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/infobars/core/infobar.h"
#include "components/infobars/core/infobar_delegate.h"
#include "components/infobars/core/infobar_manager.h"
#include "components/messages/android/message_enums.h"
#include "components/messages/android/messages_feature.h"
#include "components/messages/android/test/messages_test_helper.h"
#include "components/page_load_metrics/browser/observers/ad_metrics/ad_intervention_browser_test_utils.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
#include "components/subresource_filter/core/common/common_features.h"
#include "components/subresource_filter/core/common/test_ruleset_utils.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_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/screen_info.h"
#include "ui/gfx/geometry/rect.h"
#include "url/gurl.h"

namespace {

const char kAdsInterventionRecordedHistogram[] =
    "SubresourceFilter.PageLoad.AdsInterventionTriggered";

}  // namespace

class AdDensityViolationBrowserTestInfobarUi
    : public subresource_filter::SubresourceFilterBrowserTest {
 public:
  AdDensityViolationBrowserTestInfobarUi() = default;

  void SetUp() override {
    std::vector<base::test::FeatureRef> enabled = {
        subresource_filter::kAdTagging,
        subresource_filter::kAdsInterventionsEnforced};
    std::vector<base::test::FeatureRef> disabled = {
        messages::kMessagesForAndroidAdsBlocked};

    feature_list_.InitWithFeatures(enabled, disabled);
    subresource_filter::SubresourceFilterBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    SubresourceFilterBrowserTest::SetUpOnMainThread();
    SetRulesetWithRules(
        {subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(
    AdDensityViolationBrowserTestInfobarUi,
    MobilePageAdDensityByHeightAbove30_AdInterventionTriggered_InfobarUi) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
      web_contents);
  const GURL url(embedded_test_server()->GetURL(
      "a.com", "/ads_observer/blank_with_adiframe_writer.html"));

  waiter->SetMainFrameIntersectionExpectation();
  EXPECT_TRUE(content::NavigateToURL(web_contents, url));
  waiter->Wait();

  int document_height = page_load_metrics::GetDocumentHeight(web_contents);

  int frame_width = 100;  // Ad density by height is independent of frame width.
  int frame_height = document_height * 0.45;

  // Create the frame with b.com as origin to not get caught by
  // restricted ad tagging.
  page_load_metrics::CreateAndWaitForIframeAtRect(
      web_contents, waiter.get(),
      embedded_test_server()->GetURL("b.com", "/ads_observer/pixel.png"),
      gfx::Rect(0, 0, frame_width, frame_height));

  // Delete the page load metrics test waiter instead of reinitializing it
  // for the next page load.
  waiter.reset();

  EXPECT_TRUE(content::NavigateToURL(web_contents, url));

  // blank_with_adiframe_writer loads a script tagged as an ad, verify it is not
  // loaded and the subresource filter UI for ad blocking is shown.
  EXPECT_FALSE(
      WasParsedScriptElementLoaded(web_contents->GetPrimaryMainFrame()));
  EXPECT_EQ(infobars::ContentInfoBarManager::FromWebContents(web_contents)
                ->infobars()
                .size(),
            1u);
  EXPECT_EQ(infobars::ContentInfoBarManager::FromWebContents(web_contents)
                ->infobars()[0]
                ->delegate()
                ->GetIdentifier(),
            infobars::InfoBarDelegate::ADS_BLOCKED_INFOBAR_DELEGATE_ANDROID);
  histogram_tester.ExpectBucketCount(
      kAdsInterventionRecordedHistogram,
      static_cast<int>(subresource_filter::mojom::AdsViolation::
                           kMobileAdDensityByHeightAbove30),
      1);
}

IN_PROC_BROWSER_TEST_F(
    AdDensityViolationBrowserTestInfobarUi,
    MobilePageAdDensityByHeightBelow30_AdInterventionNotTriggered_InfobarUi) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
      web_contents);
  const GURL url(embedded_test_server()->GetURL(
      "a.com", "/ads_observer/blank_with_adiframe_writer.html"));

  waiter->SetMainFrameIntersectionExpectation();
  EXPECT_TRUE(content::NavigateToURL(web_contents, url));
  waiter->Wait();

  int document_height = page_load_metrics::GetDocumentHeight(web_contents);

  int frame_width = 100;  // Ad density by height is independent of frame width.
  int frame_height = document_height * 0.25;

  // Create the frame with b.com as origin to not get caught by
  // restricted ad tagging.
  page_load_metrics::CreateAndWaitForIframeAtRect(
      web_contents, waiter.get(),
      embedded_test_server()->GetURL("b.com", "/ads_observer/pixel.png"),
      gfx::Rect(0, 0, frame_width, frame_height));

  // Delete the page load metrics test waiter instead of reinitializing it
  // for the next page load.
  waiter.reset();

  EXPECT_TRUE(content::NavigateToURL(web_contents, url));

  // blank_with_adiframe_writer loads a script tagged as an ad, verify it is
  // loaded as ads are not blocked and the subresource filter UI is not
  // shown.
  EXPECT_TRUE(
      WasParsedScriptElementLoaded(web_contents->GetPrimaryMainFrame()));

  // No ads blocked infobar should be shown as we have not triggered the
  // intervention.
  EXPECT_EQ(infobars::ContentInfoBarManager::FromWebContents(web_contents)
                ->infobars()
                .size(),
            0u);
  histogram_tester.ExpectTotalCount(kAdsInterventionRecordedHistogram, 0);
}

class AdDensityViolationBrowserTestMessagesUi
    : public subresource_filter::SubresourceFilterBrowserTest {
 public:
  AdDensityViolationBrowserTestMessagesUi() = default;

  void SetUp() override {
    std::vector<base::test::FeatureRef> enabled = {
        subresource_filter::kAdTagging,
        subresource_filter::kAdsInterventionsEnforced,
        messages::kMessagesForAndroidAdsBlocked};
    std::vector<base::test::FeatureRef> disabled = {};

    feature_list_.InitWithFeatures(enabled, disabled);
    subresource_filter::SubresourceFilterBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    SubresourceFilterBrowserTest::SetUpOnMainThread();
    SetRulesetWithRules(
        {subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
  }

  messages::MessagesTestHelper messages_test_helper;

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(
    AdDensityViolationBrowserTestMessagesUi,
    MobilePageAdDensityByHeightAbove30_AdInterventionTriggered_MessagesUi) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
      web_contents);
  const GURL url(embedded_test_server()->GetURL(
      "a.com", "/ads_observer/blank_with_adiframe_writer.html"));

  waiter->SetMainFrameIntersectionExpectation();
  EXPECT_TRUE(content::NavigateToURL(web_contents, url));
  waiter->Wait();

  int document_height = page_load_metrics::GetDocumentHeight(web_contents);

  int frame_width = 100;  // Ad density by height is independent of frame width.
  int frame_height = document_height * 0.45;

  // Create the frame with b.com as origin to not get caught by
  // restricted ad tagging.
  page_load_metrics::CreateAndWaitForIframeAtRect(
      web_contents, waiter.get(),
      embedded_test_server()->GetURL("b.com", "/ads_observer/pixel.png"),
      gfx::Rect(0, 0, frame_width, frame_height));

  // Delete the page load metrics test waiter instead of reinitializing it
  // for the next page load.
  waiter.reset();

  EXPECT_TRUE(content::NavigateToURL(web_contents, url));

  // blank_with_adiframe_writer loads a script tagged as an ad, verify it is not
  // loaded and the subresource filter UI for ad blocking is shown.
  EXPECT_FALSE(
      WasParsedScriptElementLoaded(web_contents->GetPrimaryMainFrame()));

  EXPECT_EQ(messages_test_helper.GetMessageCount(
                web_contents->GetTopLevelNativeWindow()),
            1);
  EXPECT_EQ(messages_test_helper.GetMessageIdentifier(
                web_contents->GetTopLevelNativeWindow(), 0),
            static_cast<int>(messages::MessageIdentifier::ADS_BLOCKED));

  histogram_tester.ExpectBucketCount(
      kAdsInterventionRecordedHistogram,
      static_cast<int>(subresource_filter::mojom::AdsViolation::
                           kMobileAdDensityByHeightAbove30),
      1);
}

IN_PROC_BROWSER_TEST_F(
    AdDensityViolationBrowserTestMessagesUi,
    MobilePageAdDensityByHeightBelow30_AdInterventionNotTriggered_MessagesUi) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
      web_contents);
  const GURL url(embedded_test_server()->GetURL(
      "a.com", "/ads_observer/blank_with_adiframe_writer.html"));

  waiter->SetMainFrameIntersectionExpectation();
  EXPECT_TRUE(content::NavigateToURL(web_contents, url));
  waiter->Wait();

  int document_height = page_load_metrics::GetDocumentHeight(web_contents);

  int frame_width = 100;  // Ad density by height is independent of frame width.
  int frame_height = document_height * 0.25;

  // Create the frame with b.com as origin to not get caught by
  // restricted ad tagging.
  page_load_metrics::CreateAndWaitForIframeAtRect(
      web_contents, waiter.get(),
      embedded_test_server()->GetURL("b.com", "/ads_observer/pixel.png"),
      gfx::Rect(0, 0, frame_width, frame_height));

  // Delete the page load metrics test waiter instead of reinitializing it
  // for the next page load.
  waiter.reset();

  EXPECT_TRUE(content::NavigateToURL(web_contents, url));

  // blank_with_adiframe_writer loads a script tagged as an ad, verify it is
  // loaded as ads are not blocked and the subresource filter UI is not
  // shown.
  EXPECT_TRUE(
      WasParsedScriptElementLoaded(web_contents->GetPrimaryMainFrame()));

  // No ads blocked message should be shown as we have not triggered the
  // intervention.
  EXPECT_EQ(messages_test_helper.GetMessageCount(
                web_contents->GetTopLevelNativeWindow()),
            0);
  histogram_tester.ExpectTotalCount(kAdsInterventionRecordedHistogram, 0);
}

class AdDensityViolationBrowserTestWithoutEnforcement
    : public subresource_filter::SubresourceFilterBrowserTest {
 public:
  AdDensityViolationBrowserTestWithoutEnforcement() = default;

  void SetUp() override {
    std::vector<base::test::FeatureRef> enabled = {
        subresource_filter::kAdTagging};
    std::vector<base::test::FeatureRef> disabled = {
        subresource_filter::kAdsInterventionsEnforced};

    feature_list_.InitWithFeatures(enabled, disabled);
    subresource_filter::SubresourceFilterBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    SubresourceFilterBrowserTest::SetUpOnMainThread();
    SetRulesetWithRules(
        {subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
  }

  messages::MessagesTestHelper messages_test_helper;

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(
    AdDensityViolationBrowserTestWithoutEnforcement,
    MobilePageAdDensityByHeightAbove30_NoAdInterventionTriggered) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
      web_contents);
  const GURL url(embedded_test_server()->GetURL(
      "a.com", "/ads_observer/blank_with_adiframe_writer.html"));

  waiter->SetMainFrameIntersectionExpectation();
  EXPECT_TRUE(content::NavigateToURL(web_contents, url));
  waiter->Wait();

  int document_height = page_load_metrics::GetDocumentHeight(web_contents);

  int frame_width = 100;  // Ad density by height is independent of frame width.
  int frame_height = document_height * 0.45;

  // Create the frame with b.com as origin to not get caught by
  // restricted ad tagging.
  page_load_metrics::CreateAndWaitForIframeAtRect(
      web_contents, waiter.get(),
      embedded_test_server()->GetURL("b.com", "/ads_observer/pixel.png"),
      gfx::Rect(0, 0, frame_width, frame_height));

  // Delete the page load metrics test waiter instead of reinitializing it
  // for the next page load.
  waiter.reset();

  EXPECT_TRUE(content::NavigateToURL(web_contents, url));

  // We are not enforcing ad blocking on ads violations, site should load
  // as expected without subresource filter UI.
  EXPECT_TRUE(
      WasParsedScriptElementLoaded(web_contents->GetPrimaryMainFrame()));

  // No ads blocked prompt should be shown as we have not triggered the
  // intervention.
  EXPECT_EQ(infobars::ContentInfoBarManager::FromWebContents(web_contents)
                ->infobars()
                .size(),
            0u);
  EXPECT_EQ(messages_test_helper.GetMessageCount(
                web_contents->GetTopLevelNativeWindow()),
            0);
  histogram_tester.ExpectBucketCount(
      kAdsInterventionRecordedHistogram,
      static_cast<int>(subresource_filter::mojom::AdsViolation::
                           kMobileAdDensityByHeightAbove30),
      1);
}