blob: 1065a37b3f8ac74e4677c0345888e8ef71f0709e [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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/browser/subframe_navigation_filtering_throttle.h"
#include <memory>
#include <sstream>
#include <string>
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/subresource_filter/content/browser/async_document_subresource_filter.h"
#include "components/subresource_filter/content/browser/async_document_subresource_filter_test_utils.h"
#include "components/subresource_filter/content/browser/subframe_navigation_test_utils.h"
#include "components/subresource_filter/content/browser/subresource_filter_observer_test_utils.h"
#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
#include "components/subresource_filter/core/common/test_ruleset_creator.h"
#include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace subresource_filter {
class MockDelegate : public SubframeNavigationFilteringThrottle::Delegate {
public:
MockDelegate() = default;
~MockDelegate() override = default;
// SubframeNavigationFilteringThrottle::Delegate:
bool CalculateIsAdSubframe(content::RenderFrameHost* frame_host,
LoadPolicy load_policy) override {
return false;
}
DISALLOW_COPY_AND_ASSIGN(MockDelegate);
};
class SubframeNavigationFilteringThrottleTest
: public content::RenderViewHostTestHarness,
public content::WebContentsObserver {
public:
SubframeNavigationFilteringThrottleTest() {}
~SubframeNavigationFilteringThrottleTest() override {}
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
NavigateAndCommit(GURL("https://example.test"));
Observe(RenderViewHostTestHarness::web_contents());
}
void TearDown() override {
dealer_handle_.reset();
ruleset_handle_.reset();
parent_filter_.reset();
RunUntilIdle();
content::RenderViewHostTestHarness::TearDown();
}
content::NavigationSimulator* navigation_simulator() {
return navigation_simulator_.get();
}
// content::WebContentsObserver:
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override {
ASSERT_FALSE(navigation_handle->IsInMainFrame());
// The |parent_filter_| is the parent frame's filter. Do not register a
// throttle if the parent is not activated with a valid filter.
if (parent_filter_) {
auto throttle = std::make_unique<SubframeNavigationFilteringThrottle>(
navigation_handle, parent_filter_.get(), &mock_delegate_);
ASSERT_NE(nullptr, throttle->GetNameForLogging());
navigation_handle->RegisterThrottleForTesting(std::move(throttle));
}
}
void InitializeDocumentSubresourceFilter(
const GURL& document_url,
mojom::ActivationLevel parent_level = mojom::ActivationLevel::kEnabled) {
ASSERT_NO_FATAL_FAILURE(
test_ruleset_creator_.CreateRulesetToDisallowURLsWithPathSuffix(
"disallowed.html", &test_ruleset_pair_));
// Make the blocking task runner run on the current task runner for the
// tests, to ensure that the NavigationSimulator properly runs all necessary
// tasks while waiting for throttle checks to finish.
dealer_handle_ = std::make_unique<VerifiedRulesetDealer::Handle>(
base::ThreadTaskRunnerHandle::Get());
dealer_handle_->TryOpenAndSetRulesetFile(test_ruleset_pair_.indexed.path,
/*expected_checksum=*/0,
base::DoNothing());
ruleset_handle_ =
std::make_unique<VerifiedRuleset::Handle>(dealer_handle_.get());
testing::TestActivationStateCallbackReceiver activation_state;
mojom::ActivationState parent_activation_state;
parent_activation_state.activation_level = parent_level;
parent_activation_state.enable_logging = true;
parent_filter_ = std::make_unique<AsyncDocumentSubresourceFilter>(
ruleset_handle_.get(),
AsyncDocumentSubresourceFilter::InitializationParams(
document_url, url::Origin::Create(document_url),
parent_activation_state),
activation_state.GetCallback());
RunUntilIdle();
activation_state.ExpectReceivedOnce(parent_activation_state);
}
void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
void CreateTestSubframeAndInitNavigation(const GURL& first_url,
content::RenderFrameHost* parent) {
content::RenderFrameHost* render_frame =
content::RenderFrameHostTester::For(parent)->AppendChild(
base::StringPrintf("subframe-%s", first_url.spec().c_str()));
navigation_simulator_ =
content::NavigationSimulator::CreateRendererInitiated(first_url,
render_frame);
}
const std::vector<std::string>& GetConsoleMessages() {
return content::RenderFrameHostTester::For(main_rfh())
->GetConsoleMessages();
}
std::string GetFilterConsoleMessage(const GURL& filtered_url) {
return base::StringPrintf(kDisallowSubframeConsoleMessageFormat,
filtered_url.possibly_invalid_spec().c_str());
}
private:
testing::TestRulesetCreator test_ruleset_creator_;
testing::TestRulesetPair test_ruleset_pair_;
MockDelegate mock_delegate_;
std::unique_ptr<VerifiedRulesetDealer::Handle> dealer_handle_;
std::unique_ptr<VerifiedRuleset::Handle> ruleset_handle_;
std::unique_ptr<AsyncDocumentSubresourceFilter> parent_filter_;
std::unique_ptr<content::NavigationSimulator> navigation_simulator_;
DISALLOW_COPY_AND_ASSIGN(SubframeNavigationFilteringThrottleTest);
};
TEST_F(SubframeNavigationFilteringThrottleTest, FilterOnStart) {
InitializeDocumentSubresourceFilter(GURL("https://example.test"));
const GURL url("https://example.test/disallowed.html");
CreateTestSubframeAndInitNavigation(url, main_rfh());
EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_TRUE(
base::Contains(GetConsoleMessages(), GetFilterConsoleMessage(url)));
}
TEST_F(SubframeNavigationFilteringThrottleTest, FilterOnRedirect) {
InitializeDocumentSubresourceFilter(GURL("https://example.test"));
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
SimulateRedirectAndGetResult(
navigation_simulator(),
GURL("https://example.test/disallowed.html")));
}
TEST_F(SubframeNavigationFilteringThrottleTest, DryRunOnStart) {
InitializeDocumentSubresourceFilter(GURL("https://example.test"),
mojom::ActivationLevel::kDryRun);
const GURL url("https://example.test/disallowed.html");
CreateTestSubframeAndInitNavigation(url, main_rfh());
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_FALSE(
base::Contains(GetConsoleMessages(), GetFilterConsoleMessage(url)));
}
TEST_F(SubframeNavigationFilteringThrottleTest, DryRunOnRedirect) {
InitializeDocumentSubresourceFilter(GURL("https://example.test"),
mojom::ActivationLevel::kDryRun);
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateRedirectAndGetResult(
navigation_simulator(),
GURL("https://example.test/disallowed.html")));
}
TEST_F(SubframeNavigationFilteringThrottleTest, FilterOnSecondRedirect) {
InitializeDocumentSubresourceFilter(GURL("https://example.test"));
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(
content::NavigationThrottle::PROCEED,
SimulateRedirectAndGetResult(navigation_simulator(),
GURL("https://example.test/allowed2.html")));
EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
SimulateRedirectAndGetResult(
navigation_simulator(),
GURL("https://example.test/disallowed.html")));
}
TEST_F(SubframeNavigationFilteringThrottleTest, NeverFilterNonMatchingRule) {
InitializeDocumentSubresourceFilter(GURL("https://example.test"));
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(
content::NavigationThrottle::PROCEED,
SimulateRedirectAndGetResult(navigation_simulator(),
GURL("https://example.test/allowed2.html")));
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateCommitAndGetResult(navigation_simulator()));
}
TEST_F(SubframeNavigationFilteringThrottleTest, FilterSubsubframe) {
// Fake an activation of the subframe.
content::RenderFrameHost* parent_subframe =
content::RenderFrameHostTester::For(main_rfh())
->AppendChild("parent-sub");
GURL test_url = GURL("https://example.test");
auto navigation = content::NavigationSimulator::CreateRendererInitiated(
test_url, parent_subframe);
navigation->Start();
InitializeDocumentSubresourceFilter(GURL("https://example.test"));
navigation->Commit();
CreateTestSubframeAndInitNavigation(
GURL("https://example.test/disallowed.html"), parent_subframe);
EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
SimulateStartAndGetResult(navigation_simulator()));
}
TEST_F(SubframeNavigationFilteringThrottleTest, DelayMetrics) {
base::HistogramTester histogram_tester;
InitializeDocumentSubresourceFilter(GURL("https://example.test"));
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
navigation_simulator()->SetTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
SimulateRedirectAndGetResult(
navigation_simulator(),
GURL("https://example.test/disallowed.html")));
navigation_simulator()->CommitErrorPage();
const char kFilterDelayDisallowed[] =
"SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Disallowed2";
const char kFilterDelayWouldDisallow[] =
"SubresourceFilter.DocumentLoad.SubframeFilteringDelay.WouldDisallow";
const char kFilterDelayAllowed[] =
"SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Allowed";
histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 1);
histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 0);
histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 0);
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateCommitAndGetResult(navigation_simulator()));
histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 1);
histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 0);
histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 1);
}
TEST_F(SubframeNavigationFilteringThrottleTest, DelayMetricsDryRun) {
base::HistogramTester histogram_tester;
InitializeDocumentSubresourceFilter(GURL("https://example.test"),
mojom::ActivationLevel::kDryRun);
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
navigation_simulator()->SetTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateRedirectAndGetResult(
navigation_simulator(),
GURL("https://example.test/disallowed.html")));
navigation_simulator()->Commit();
const char kFilterDelayDisallowed[] =
"SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Disallowed2";
const char kFilterDelayWouldDisallow[] =
"SubresourceFilter.DocumentLoad.SubframeFilteringDelay.WouldDisallow";
const char kFilterDelayAllowed[] =
"SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Allowed";
histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 0);
histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 1);
histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 0);
CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
main_rfh());
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateStartAndGetResult(navigation_simulator()));
EXPECT_EQ(content::NavigationThrottle::PROCEED,
SimulateCommitAndGetResult(navigation_simulator()));
histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 0);
histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 1);
histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 1);
}
} // namespace subresource_filter