blob: f56f49b3f00cfa1d2df7c007cb1b901795952909 [file]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/subresource_filter/content/renderer/subresource_filter_agent.h"
#include <memory>
#include <string_view>
#include <utility>
#include "base/files/file.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "components/subresource_filter/content/renderer/unverified_ruleset_dealer.h"
#include "components/subresource_filter/core/common/memory_mapped_ruleset.h"
#include "components/subresource_filter/core/common/scoped_timers.h"
#include "components/subresource_filter/core/common/test_ruleset_creator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_document_subresource_filter.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_error.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "url/gurl.h"
namespace subresource_filter {
namespace {
// The SubresourceFilterAgent with its dependencies on Blink mocked out.
//
// This approach is somewhat rudimentary, but appears to be the best compromise
// considering the alternatives:
// -- Passing in a TestRenderFrame would itself require bringing up a
// significant number of supporting classes.
// -- Using a RenderViewTest would not allow having any non-filtered resource
// loads due to not having a child thread and ResourceDispatcher.
// The real implementations of the mocked-out methods are exercised in:
// chrome/browser/subresource_filter/subresource_filter_browsertest.cc.
class SubresourceFilterAgentUnderTest : public SubresourceFilterAgent {
public:
explicit SubresourceFilterAgentUnderTest(
UnverifiedRulesetDealer* ruleset_dealer,
bool is_subresource_filter_root,
bool is_provisional,
bool is_parent_ad_frame,
bool is_frame_created_by_ad_script)
: SubresourceFilterAgent(nullptr /* RenderFrame */, ruleset_dealer),
is_subresource_filter_root_(is_subresource_filter_root),
is_provisional_(is_provisional),
is_parent_ad_frame_(is_parent_ad_frame),
is_frame_created_by_ad_script_(is_frame_created_by_ad_script) {}
SubresourceFilterAgentUnderTest(const SubresourceFilterAgentUnderTest&) =
delete;
SubresourceFilterAgentUnderTest& operator=(
const SubresourceFilterAgentUnderTest&) = delete;
~SubresourceFilterAgentUnderTest() override = default;
MOCK_METHOD0(OnSetSubresourceFilterForCurrentDocumentCalled, void());
MOCK_METHOD0(SignalFirstSubresourceDisallowedForCurrentDocument, void());
MOCK_METHOD1(SendDocumentLoadStatistics,
void(const mojom::DocumentLoadStatistics&));
MOCK_METHOD0(SendFrameIsAd, void());
MOCK_METHOD0(SendFrameWasCreatedByAdScript, void());
bool IsSubresourceFilterChild() override {
return !is_subresource_filter_root_;
}
bool IsProvisional() override { return is_provisional_; }
bool IsParentAdFrame() override { return is_parent_ad_frame_; }
bool IsFrameCreatedByAdScript() override {
return is_frame_created_by_ad_script_;
}
GURL GetDocumentURL() override { return document_url_; }
void SetSubresourceFilterForCurrentDocument(
std::unique_ptr<blink::WebDocumentSubresourceFilter> filter) override {
last_injected_filter_ = std::move(filter);
OnSetSubresourceFilterForCurrentDocumentCalled();
}
bool IsAdFrame() override {
return ad_evidence_ && ad_evidence_->IndicatesAdFrame();
}
const std::optional<blink::FrameAdEvidence>& AdEvidence() override {
return ad_evidence_;
}
void SetAdEvidence(const blink::FrameAdEvidence& ad_evidence) override {
ad_evidence_ = ad_evidence;
}
blink::WebDocumentSubresourceFilter* filter() {
return last_injected_filter_.get();
}
std::unique_ptr<blink::WebDocumentSubresourceFilter> TakeFilter() {
return std::move(last_injected_filter_);
}
void SetInheritedActivationStateForNewDocument(mojom::ActivationLevel level) {
mojom::ActivationState state;
state.activation_level = level;
inherited_activation_state_for_new_document_ = state;
}
using SubresourceFilterAgent::ActivateForNextCommittedLoad;
void SetDocumentUrl(GURL url) { document_url_ = std::move(url); }
private:
const mojom::ActivationState GetInheritedActivationStateForNewDocument()
override {
return inherited_activation_state_for_new_document_;
}
const bool is_subresource_filter_root_;
const bool is_provisional_;
const bool is_parent_ad_frame_;
const bool is_frame_created_by_ad_script_;
// Production can set this on the RenderFrame, which we intercept and store
// here.
std::optional<blink::FrameAdEvidence> ad_evidence_;
std::unique_ptr<blink::WebDocumentSubresourceFilter> last_injected_filter_;
mojom::ActivationState inherited_activation_state_for_new_document_;
GURL document_url_ = GURL("about:blank");
};
constexpr const char kTestFirstURL[] = "http://example.com/alpha";
constexpr const char kTestSecondURL[] = "http://example.com/beta";
constexpr const char kTestFirstURLPathSuffix[] = "alpha";
constexpr const char kTestSecondURLPathSuffix[] = "beta";
constexpr const char kTestBothURLsPathSuffix[] = "a";
// Histogram names.
constexpr const char kDocumentLoadRulesetIsAvailable[] =
"SubresourceFilter.DocumentLoad.RulesetIsAvailable";
constexpr const char kMainFrameLoadRulesetIsAvailableAnyActivationLevel[] =
"SubresourceFilter.MainFrameLoad.RulesetIsAvailableAnyActivationLevel";
} // namespace
class SubresourceFilterAgentTest : public ::testing::Test {
public:
SubresourceFilterAgentTest() = default;
SubresourceFilterAgentTest(const SubresourceFilterAgentTest&) = delete;
SubresourceFilterAgentTest& operator=(const SubresourceFilterAgentTest&) =
delete;
protected:
void SetUp() override {
ResetAgent(/*is_subresource_filter_root=*/true,
/*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/false);
}
void ResetAgent(bool is_subresource_filter_root,
bool is_provisional,
bool is_parent_ad_frame,
bool is_frame_created_by_ad_script) {
ResetAgentWithoutInitialize(is_subresource_filter_root, is_provisional,
is_parent_ad_frame,
is_frame_created_by_ad_script);
ExpectSendFrameWasCreatedByAdScript(!is_subresource_filter_root &&
!is_provisional &&
is_frame_created_by_ad_script
? 1
: 0);
ExpectSendFrameIsAd(
!is_subresource_filter_root && !is_provisional &&
(is_parent_ad_frame || is_frame_created_by_ad_script)
? 1
: 0);
agent_->Initialize();
agent_->SetDocumentUrl(GURL("https://example.test/"));
::testing::Mock::VerifyAndClearExpectations(&*agent_);
}
// This creates the `agent_` but does not initialize it, so that tests can
// inject gmock expectations against the `agent_` to verify or change the
// behaviour of the initialize step.
void ResetAgentWithoutInitialize(bool is_subresource_filter_root,
bool is_provisional,
bool is_parent_ad_frame,
bool is_frame_created_by_ad_script) {
agent_ = std::make_unique<
::testing::StrictMock<SubresourceFilterAgentUnderTest>>(
&ruleset_dealer_, is_subresource_filter_root, is_provisional,
is_parent_ad_frame, is_frame_created_by_ad_script);
}
void SetTestRulesetToDisallowURLsWithPathSuffix(std::string_view suffix) {
testing::TestRulesetPair test_ruleset_pair;
ASSERT_NO_FATAL_FAILURE(
test_ruleset_creator_.CreateRulesetToDisallowURLsWithPathSuffix(
suffix, &test_ruleset_pair));
ruleset_dealer_.SetRulesetFile(
testing::TestRuleset::Open(test_ruleset_pair.indexed));
}
void StartLoadWithoutSettingActivationState() {
agent_as_rfo()->DidStartNavigation(GURL(), std::nullopt);
agent_as_rfo()->ReadyToCommitNavigation(nullptr);
agent_as_rfo()->DidCreateNewDocument();
}
void PerformSameDocumentNavigationWithoutSettingActivationLevel() {
agent_as_rfo()->DidStartNavigation(GURL(), std::nullopt);
agent_as_rfo()->ReadyToCommitNavigation(nullptr);
// No DidCreateNewDocument, since same document navigations by definition
// don't create a new document.
// No DidFinishLoad is called in this case.
}
void StartLoadAndSetActivationState(mojom::ActivationLevel level,
bool is_ad_frame = false) {
mojom::ActivationState state;
state.activation_level = level;
StartLoadAndSetActivationState(state, is_ad_frame);
}
void StartLoadAndSetActivationState(mojom::ActivationState state,
bool is_ad_frame = false) {
agent_as_rfo()->DidStartNavigation(GURL(), std::nullopt);
agent_as_rfo()->ReadyToCommitNavigation(nullptr);
std::optional<blink::FrameAdEvidence> ad_evidence;
if (agent()->IsSubresourceFilterChild()) {
// Generate an evidence object matching the `ad_type`.
ad_evidence = blink::FrameAdEvidence(false /* parent_is_ad */);
if (is_ad_frame) {
ad_evidence->set_created_by_ad_script(
blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript);
}
ad_evidence->set_is_complete();
} else {
ASSERT_FALSE(is_ad_frame);
}
agent()->ActivateForNextCommittedLoad(state.Clone(), ad_evidence);
agent_as_rfo()->DidCreateNewDocument();
}
void FinishLoad() { agent_as_rfo()->DidFinishLoad(); }
void ExpectSubresourceFilterGetsInjected() {
EXPECT_CALL(*agent(), OnSetSubresourceFilterForCurrentDocumentCalled());
}
void ExpectNoSubresourceFilterGetsInjected() {
EXPECT_CALL(*agent(), OnSetSubresourceFilterForCurrentDocumentCalled())
.Times(0);
}
void ExpectSignalAboutFirstSubresourceDisallowed() {
EXPECT_CALL(*agent(), SignalFirstSubresourceDisallowedForCurrentDocument());
}
void ExpectNoSignalAboutFirstSubresourceDisallowed() {
EXPECT_CALL(*agent(), SignalFirstSubresourceDisallowedForCurrentDocument())
.Times(0);
}
void ExpectDocumentLoadStatisticsSent() {
EXPECT_CALL(*agent(), SendDocumentLoadStatistics(::testing::_));
}
void ExpectSendFrameIsAd(int times) {
EXPECT_CALL(*agent(), SendFrameIsAd()).Times(times);
}
void ExpectSendFrameWasCreatedByAdScript(int times) {
EXPECT_CALL(*agent(), SendFrameWasCreatedByAdScript()).Times(times);
}
void ExpectLoadPolicy(
std::string_view url_spec,
blink::WebDocumentSubresourceFilter::LoadPolicy expected_policy) {
blink::WebURL url = GURL(url_spec);
network::mojom::RequestDestination request_destination =
network::mojom::RequestDestination::kImage;
blink::WebDocumentSubresourceFilter::LoadPolicy actual_policy =
agent()->filter()->GetLoadPolicy(url, request_destination,
/*out_rule=*/nullptr);
EXPECT_EQ(expected_policy, actual_policy);
// If the load policy indicated the load was filtered, simulate a filtered
// load callback. In production, this will be called in FrameFetchContext,
// but we simulate the call here.
if (actual_policy == blink::WebDocumentSubresourceFilter::kDisallow) {
agent()->filter()->ReportDisallowedLoad();
}
}
SubresourceFilterAgentUnderTest* agent() { return agent_.get(); }
content::RenderFrameObserver* agent_as_rfo() {
return static_cast<content::RenderFrameObserver*>(agent_.get());
}
private:
testing::TestRulesetCreator test_ruleset_creator_;
UnverifiedRulesetDealer ruleset_dealer_;
std::unique_ptr<SubresourceFilterAgentUnderTest> agent_;
};
TEST_F(SubresourceFilterAgentTest, RulesetUnset_RulesetNotAvailable) {
base::HistogramTester histogram_tester;
// Do not set ruleset.
ExpectNoSubresourceFilterGetsInjected();
StartLoadWithoutSettingActivationState();
FinishLoad();
histogram_tester.ExpectTotalCount(kDocumentLoadRulesetIsAvailable, 0);
histogram_tester.ExpectUniqueSample(
kMainFrameLoadRulesetIsAvailableAnyActivationLevel, 0, 1);
}
TEST_F(SubresourceFilterAgentTest, DisabledByDefault_NoFilterIsInjected) {
base::HistogramTester histogram_tester;
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestBothURLsPathSuffix));
ExpectNoSubresourceFilterGetsInjected();
StartLoadWithoutSettingActivationState();
FinishLoad();
histogram_tester.ExpectTotalCount(kDocumentLoadRulesetIsAvailable, 0);
histogram_tester.ExpectUniqueSample(
kMainFrameLoadRulesetIsAvailableAnyActivationLevel, 1, 1);
}
TEST_F(SubresourceFilterAgentTest, MmapFailure_FailsToInjectSubresourceFilter) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
MemoryMappedRuleset::SetMemoryMapFailuresForTesting(true);
ExpectNoSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled,
false /* is_ad_frame */);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
MemoryMappedRuleset::SetMemoryMapFailuresForTesting(false);
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled,
false /* is_ad_frame */);
}
TEST_F(SubresourceFilterAgentTest, Disabled_NoFilterIsInjected) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestBothURLsPathSuffix));
ExpectNoSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kDisabled);
FinishLoad();
}
TEST_F(SubresourceFilterAgentTest,
EnabledButRulesetUnavailable_NoFilterIsInjected) {
base::HistogramTester histogram_tester;
ExpectNoSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
FinishLoad();
histogram_tester.ExpectUniqueSample(kDocumentLoadRulesetIsAvailable, 0, 1);
histogram_tester.ExpectUniqueSample(
kMainFrameLoadRulesetIsAvailableAnyActivationLevel, 0, 1);
}
// Never inject a filter for root frame about:blank loads, even though we do for
// child frame loads. Those are tested via browser tests.
// TODO(csharrison): Refactor these unit tests so it is easier to test with real
// backing RenderFrames.
TEST_F(SubresourceFilterAgentTest, EmptyDocumentLoad_NoFilterIsInjected) {
base::HistogramTester histogram_tester;
agent()->SetDocumentUrl(GURL("about:blank"));
ExpectNoSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
FinishLoad();
histogram_tester.ExpectTotalCount(kDocumentLoadRulesetIsAvailable, 0);
histogram_tester.ExpectTotalCount(
kMainFrameLoadRulesetIsAvailableAnyActivationLevel, 0);
}
TEST_F(SubresourceFilterAgentTest, Enabled_FilteringIsInEffectForOneLoad) {
base::HistogramTester histogram_tester;
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
ExpectSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectLoadPolicy(kTestSecondURL, blink::WebDocumentSubresourceFilter::kAllow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
// In-page navigation should not count as a new load.
ExpectNoSubresourceFilterGetsInjected();
ExpectNoSignalAboutFirstSubresourceDisallowed();
PerformSameDocumentNavigationWithoutSettingActivationLevel();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectLoadPolicy(kTestSecondURL, blink::WebDocumentSubresourceFilter::kAllow);
ExpectNoSubresourceFilterGetsInjected();
StartLoadWithoutSettingActivationState();
FinishLoad();
// Resource loads after the in-page navigation should not be counted toward
// the figures below, as they came after the original page load event. There
// should be no samples recorded into subresource count histograms during the
// final load where there is no activation.
histogram_tester.ExpectUniqueSample(kDocumentLoadRulesetIsAvailable, 1, 1);
histogram_tester.ExpectUniqueSample(
kMainFrameLoadRulesetIsAvailableAnyActivationLevel, 1, 2);
}
TEST_F(SubresourceFilterAgentTest, Enabled_HistogramSamplesOverTwoLoads) {
for (const bool measure_performance : {false, true}) {
base::HistogramTester histogram_tester;
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ExpectSubresourceFilterGetsInjected();
mojom::ActivationState state;
state.activation_level = mojom::ActivationLevel::kEnabled;
state.measure_performance = measure_performance;
StartLoadAndSetActivationState(state);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
ExpectSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectNoSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectNoSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestSecondURL,
blink::WebDocumentSubresourceFilter::kAllow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(state);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
ExpectNoSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestSecondURL,
blink::WebDocumentSubresourceFilter::kAllow);
ExpectSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
histogram_tester.ExpectUniqueSample(kDocumentLoadRulesetIsAvailable, 1, 2);
histogram_tester.ExpectUniqueSample(
kMainFrameLoadRulesetIsAvailableAnyActivationLevel, 1, 2);
}
}
TEST_F(SubresourceFilterAgentTest, Enabled_NewRulesetIsPickedUpAtNextLoad) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
// Set the new ruleset just after the deadline for being used for the current
// load, to exercises doing filtering based on obseleted rulesets.
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestSecondURLPathSuffix));
ExpectSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectLoadPolicy(kTestSecondURL, blink::WebDocumentSubresourceFilter::kAllow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
ExpectSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL, blink::WebDocumentSubresourceFilter::kAllow);
ExpectLoadPolicy(kTestSecondURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
}
// Make sure that the activation decision does not outlive a failed provisional
// load (and affect the second next load).
TEST_F(SubresourceFilterAgentTest,
Enabled_FilteringNoLongerEffectAfterProvisionalLoadIsCancelled) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestBothURLsPathSuffix));
ExpectNoSubresourceFilterGetsInjected();
agent_as_rfo()->DidStartNavigation(GURL(), std::nullopt);
agent_as_rfo()->ReadyToCommitNavigation(nullptr);
mojom::ActivationStatePtr state = mojom::ActivationState::New();
state->activation_level = mojom::ActivationLevel::kEnabled;
state->measure_performance = true;
agent()->ActivateForNextCommittedLoad(std::move(state), std::nullopt);
agent_as_rfo()->DidFailProvisionalLoad();
agent_as_rfo()->DidStartNavigation(GURL(), std::nullopt);
agent_as_rfo()->ReadyToCommitNavigation(nullptr);
agent_as_rfo()->DidCommitProvisionalLoad(ui::PAGE_TRANSITION_LINK);
FinishLoad();
}
TEST_F(SubresourceFilterAgentTest, DryRun_ResourcesAreEvaluatedButNotFiltered) {
base::HistogramTester histogram_tester;
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kDryRun);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
// In dry-run mode, loads to the first URL should be recorded as
// `MatchedRules`, but still be allowed to proceed and not recorded as
// `Disallowed`.
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kWouldDisallow);
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kWouldDisallow);
ExpectLoadPolicy(kTestSecondURL, blink::WebDocumentSubresourceFilter::kAllow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
histogram_tester.ExpectUniqueSample(kDocumentLoadRulesetIsAvailable, 1, 1);
histogram_tester.ExpectUniqueSample(
kMainFrameLoadRulesetIsAvailableAnyActivationLevel, 1, 1);
EXPECT_FALSE(agent()->IsAdFrame());
}
TEST_F(SubresourceFilterAgentTest,
SignalFirstSubresourceDisallowed_OncePerDocumentLoad) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
ExpectSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectNoSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectLoadPolicy(kTestSecondURL, blink::WebDocumentSubresourceFilter::kAllow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
ExpectLoadPolicy(kTestSecondURL, blink::WebDocumentSubresourceFilter::kAllow);
ExpectSignalAboutFirstSubresourceDisallowed();
ExpectLoadPolicy(kTestFirstURL,
blink::WebDocumentSubresourceFilter::kDisallow);
ExpectDocumentLoadStatisticsSent();
FinishLoad();
}
TEST_F(SubresourceFilterAgentTest,
SignalFirstSubresourceDisallowed_ComesAfterAgentDestroyed) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
auto filter = agent()->TakeFilter();
ResetAgent(/*is_subresource_filter_root=*/true, /*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/false);
// The filter has been disconnected from the agent, so a call to
// reportDisallowedLoad() should not signal a first resource disallowed call
// to the agent, nor should it cause a crash.
ExpectNoSignalAboutFirstSubresourceDisallowed();
filter->ReportDisallowedLoad();
}
TEST_F(SubresourceFilterAgentTest,
FailedInitialLoad_FilterInjectedOnInitialDocumentCreation) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix("somethingNotMatched"));
ResetAgent(/*is_subresource_filter_root=*/false, /*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/false);
agent()->SetInheritedActivationStateForNewDocument(
mojom::ActivationLevel::kEnabled);
ExpectNoSubresourceFilterGetsInjected();
EXPECT_CALL(*agent(), OnSetSubresourceFilterForCurrentDocumentCalled());
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ExpectNoSubresourceFilterGetsInjected();
agent_as_rfo()->DidFailProvisionalLoad();
}
TEST_F(SubresourceFilterAgentTest,
FailedInitialMainFrameLoad_FilterInjectedOnInitialDocumentCreation) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix("somethingNotMatched"));
agent()->SetInheritedActivationStateForNewDocument(
mojom::ActivationLevel::kEnabled);
ExpectNoSubresourceFilterGetsInjected();
EXPECT_CALL(*agent(), OnSetSubresourceFilterForCurrentDocumentCalled());
StartLoadAndSetActivationState(mojom::ActivationLevel::kEnabled);
ExpectNoSubresourceFilterGetsInjected();
agent_as_rfo()->DidFailProvisionalLoad();
}
TEST_F(SubresourceFilterAgentTest,
DryRun_IsAssociatedWithAdFrameforDocumentOrDedicatedWorker) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ResetAgent(/*is_subresource_filter_root=*/false, /*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/false);
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kDryRun,
true /* is_ad_frame */);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
// Test the ad frame value that is set at the filter. This also represents
// the flag passed to a dedicated worker filter created. For testing the flag
// passed to the dedicated worker filter, the unit test is not able to test
// the implementation of WillCreateWorkerFetchContext as that will require
// setup of a WebWorkerFetchContextImpl.
EXPECT_TRUE(agent()->IsAdFrame());
}
TEST_F(SubresourceFilterAgentTest, DryRun_AdFrameIsUntaggedByBrowser) {
ASSERT_NO_FATAL_FAILURE(
SetTestRulesetToDisallowURLsWithPathSuffix(kTestFirstURLPathSuffix));
ResetAgent(/*is_subresource_filter_root=*/false, /*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/false);
// Browser tags the frame as an ad.
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kDryRun,
true /* is_ad_frame */);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
EXPECT_TRUE(agent()->IsAdFrame());
ExpectDocumentLoadStatisticsSent();
FinishLoad();
// Browser then untags the frame as an ad frame.
ExpectSubresourceFilterGetsInjected();
StartLoadAndSetActivationState(mojom::ActivationLevel::kDryRun,
false /* is_ad_frame */);
ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(agent()));
EXPECT_FALSE(agent()->IsAdFrame());
ExpectDocumentLoadStatisticsSent();
FinishLoad();
}
TEST_F(SubresourceFilterAgentTest, DryRun_SendsFrameIsAd) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/false,
/*is_provisional=*/false,
/*is_parent_ad_frame=*/true,
/*is_frame_created_by_ad_script=*/false);
ExpectSendFrameIsAd(1);
agent()->Initialize();
// SendFrameIsAd() is sent from Initialize();
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument verify that SendFrameIsAd is not called again.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
}
TEST_F(SubresourceFilterAgentTest,
DryRun_SendFrameIsAdNotSentFromProvisionalFrame) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/false,
/*is_provisional=*/true,
/*is_parent_ad_frame=*/true,
/*is_frame_created_by_ad_script=*/false);
ExpectSendFrameIsAd(0);
agent()->Initialize();
// SendFrameIsAd() is not sent from Initialize() since the frame is
// provisional.
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument and verify that SendFrameIsAd is not called from
// there either.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
}
TEST_F(SubresourceFilterAgentTest, DryRun_SendFrameIsAdNotSentFromNonAdFrame) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/false,
/*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/false);
ExpectSendFrameIsAd(0);
agent()->Initialize();
// SendFrameIsAd() is not sent from Initialize() since the frame is not an ad
// frame.
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument and verify that SendFrameIsAd is not called from
// there either.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
}
TEST_F(SubresourceFilterAgentTest, DryRun_SendFrameIsAdNotSentFromRootFrame) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/true,
/*is_provisional=*/false,
/*is_parent_ad_frame=*/true,
/*is_frame_created_by_ad_script=*/false);
ExpectSendFrameIsAd(0);
agent()->Initialize();
// SendFrameIsAd() is not sent from Initialize() since the frame is the root
// frame, even though it's an ad frame.
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument and verify that SendFrameIsAd is not called from
// there either.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
}
TEST_F(SubresourceFilterAgentTest,
DryRun_SendFrameIsAdNotSentFromNonAdSubFrame) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/false,
/*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script*/ false);
ExpectSendFrameIsAd(0);
agent()->Initialize();
// SendFrameIsAd() is not sent from Initialize() since the frame is the root
// frame, even though it's an ad frame.
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument and verify that SendFrameIsAd is not called from
// there either.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
}
TEST_F(SubresourceFilterAgentTest, DryRun_SendsFrameWasCreatedByAdScript) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/false,
/*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/true);
ExpectSendFrameWasCreatedByAdScript(1);
ExpectSendFrameIsAd(1);
agent()->Initialize();
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument twice and verify that
// SendFrameWasCreatedByAdScript is only called once.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
agent_as_rfo()->DidCreateNewDocument();
}
TEST_F(SubresourceFilterAgentTest,
DryRun_SendFrameWasCreatedByAdScriptNotSentFromProvisionalFrame) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/false,
/*is_provisional=*/true,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/true);
ExpectSendFrameWasCreatedByAdScript(0);
agent()->Initialize();
// SendFrameWasCreatedByAdScript() is not sent from Initialize() since the
// frame is provisional, even though it's created by ad script.
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument and verify that SendFrameWasCreatedByAdScript is
// not called from there either.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
}
TEST_F(
SubresourceFilterAgentTest,
DryRun_SendFrameWasCreatedByAdScriptNotSentFromFrameNotCreatedByAdScript) {
ResetAgentWithoutInitialize(/*is_subresource_filter_root=*/false,
/*is_provisional=*/false,
/*is_parent_ad_frame=*/false,
/*is_frame_created_by_ad_script=*/false);
ExpectSendFrameWasCreatedByAdScript(0);
agent()->Initialize();
// SendFrameWasCreatedByAdScript() is not sent from Initialize() since the
// frame was not created by ad script.
::testing::Mock::VerifyAndClearExpectations(agent());
// Call DidCreateNewDocument and verify that SendFrameWasCreatedByAdScript is
// not called from there either.
ExpectSendFrameIsAd(0);
agent_as_rfo()->DidCreateNewDocument();
}
} // namespace subresource_filter