blob: 78c205083b5a283ce05b707586e0ed4eaa3a22fc [file] [log] [blame]
// Copyright 2020 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 "content/browser/conversions/conversion_host.h"
#include <memory>
#include "base/test/metrics/histogram_tester.h"
#include "content/browser/conversions/conversion_manager.h"
#include "content/browser/conversions/conversion_test_utils.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/fake_mojo_message_dispatch_context.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_web_contents.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/conversions/conversions.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
const char kConversionUrl[] = "https://b.com";
Impression CreateValidImpression() {
Impression result;
result.conversion_destination = url::Origin::Create(GURL(kConversionUrl));
result.reporting_origin = url::Origin::Create(GURL("https://c.com"));
result.impression_data = 1UL;
return result;
}
} // namespace
class ConversionHostTest : public RenderViewHostTestHarness {
public:
ConversionHostTest() = default;
void SetUp() override {
RenderViewHostTestHarness::SetUp();
static_cast<WebContentsImpl*>(web_contents())
->RemoveReceiverSetForTesting(blink::mojom::ConversionHost::Name_);
conversion_host_ = ConversionHost::CreateForTesting(
web_contents(), std::make_unique<TestManagerProvider>(&test_manager_));
contents()->GetMainFrame()->InitializeRenderFrameIfNeeded();
}
TestWebContents* contents() {
return static_cast<TestWebContents*>(web_contents());
}
ConversionHost* conversion_host() { return conversion_host_.get(); }
protected:
TestConversionManager test_manager_;
private:
std::unique_ptr<ConversionHost> conversion_host_;
};
TEST_F(ConversionHostTest, ConversionInSubframe_BadMessage) {
contents()->NavigateAndCommit(GURL("http://www.example.com"));
// Create a subframe and use it as a target for the conversion registration
// mojo.
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
content::RenderFrameHost* subframe = rfh_tester->AppendChild("subframe");
conversion_host()->SetCurrentTargetFrameForTesting(subframe);
// Create a fake dispatch context to trigger a bad message in.
FakeMojoMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
// Message should be ignored because it was registered from a subframe.
conversion_host()->RegisterConversion(std::move(conversion));
EXPECT_EQ("blink.mojom.ConversionHost can only be used by the main frame.",
bad_message_observer.WaitForBadMessage());
EXPECT_EQ(0u, test_manager_.num_conversions());
}
TEST_F(ConversionHostTest, ConversionOnInsecurePage_BadMessage) {
// Create a page with an insecure origin.
contents()->NavigateAndCommit(GURL("http://www.example.com"));
conversion_host()->SetCurrentTargetFrameForTesting(main_rfh());
FakeMojoMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
conversion->reporting_origin =
url::Origin::Create(GURL("https://secure.com"));
// Message should be ignored because it was registered from an insecure page.
conversion_host()->RegisterConversion(std::move(conversion));
EXPECT_EQ(
"blink.mojom.ConversionHost can only be used in secure contexts with a "
"secure conversion registration origin.",
bad_message_observer.WaitForBadMessage());
EXPECT_EQ(0u, test_manager_.num_conversions());
}
TEST_F(ConversionHostTest, ConversionWithInsecureReportingOrigin_BadMessage) {
contents()->NavigateAndCommit(GURL("https://www.example.com"));
conversion_host()->SetCurrentTargetFrameForTesting(main_rfh());
FakeMojoMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
conversion->reporting_origin = url::Origin::Create(GURL("http://secure.com"));
// Message should be ignored because it was registered with an insecure
// redirect.
conversion_host()->RegisterConversion(std::move(conversion));
EXPECT_EQ(
"blink.mojom.ConversionHost can only be used in secure contexts with a "
"secure conversion registration origin.",
bad_message_observer.WaitForBadMessage());
EXPECT_EQ(0u, test_manager_.num_conversions());
}
TEST_F(ConversionHostTest, ValidConversion_NoBadMessage) {
// Create a page with a secure origin.
contents()->NavigateAndCommit(GURL("https://www.example.com"));
conversion_host()->SetCurrentTargetFrameForTesting(main_rfh());
// Create a fake dispatch context to listen for bad messages.
FakeMojoMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
conversion->reporting_origin =
url::Origin::Create(GURL("https://secure.com"));
conversion_host()->RegisterConversion(std::move(conversion));
// Run loop to allow the bad message code to run if a bad message was
// triggered.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(bad_message_observer.got_bad_message());
EXPECT_EQ(1u, test_manager_.num_conversions());
}
TEST_F(ConversionHostTest, ValidConversionWithEmbedderDisable_NoConversion) {
ConversionDisallowingContentBrowserClient disallowed_browser_client;
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(&disallowed_browser_client);
// Create a page with a secure origin.
contents()->NavigateAndCommit(GURL("https://www.example.com"));
conversion_host()->SetCurrentTargetFrameForTesting(main_rfh());
blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
conversion->reporting_origin =
url::Origin::Create(GURL("https://secure.com"));
conversion_host()->RegisterConversion(std::move(conversion));
EXPECT_EQ(0u, test_manager_.num_conversions());
SetBrowserClientForTesting(old_browser_client);
}
TEST_F(ConversionHostTest, ValidImpressionWithEmbedderDisable_NoImpression) {
ConversionDisallowingContentBrowserClient disallowed_browser_client;
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(&disallowed_browser_client);
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->Commit();
EXPECT_EQ(0u, test_manager_.num_impressions());
SetBrowserClientForTesting(old_browser_client);
}
TEST_F(ConversionHostTest, PerPageConversionMetrics) {
base::HistogramTester histograms;
contents()->NavigateAndCommit(GURL("https://www.example.com"));
// Initial document should not log metrics.
histograms.ExpectTotalCount("Conversions.RegisteredConversionsPerPage", 0);
conversion_host()->SetCurrentTargetFrameForTesting(main_rfh());
blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
conversion->reporting_origin =
url::Origin::Create(GURL("https://secure.com"));
for (size_t i = 0u; i < 8u; i++) {
conversion_host()->RegisterConversion(conversion->Clone());
EXPECT_EQ(1u, test_manager_.num_conversions());
test_manager_.Reset();
}
// Same document navs should not reset the counter.
contents()->NavigateAndCommit(GURL("https://www.example.com#hash"));
histograms.ExpectTotalCount("Conversions.RegisteredConversionsPerPage", 0);
// Re-navigating should reset the counter.
contents()->NavigateAndCommit(GURL("https://www.example-next.com"));
histograms.ExpectUniqueSample("Conversions.RegisteredConversionsPerPage", 8,
1);
}
TEST_F(ConversionHostTest, NoManager_NoPerPageConversionMetrics) {
// Replace the ConversionHost on the WebContents with one that is backed by a
// null ConversionManager.
static_cast<WebContentsImpl*>(web_contents())
->RemoveReceiverSetForTesting(blink::mojom::ConversionHost::Name_);
auto conversion_host = ConversionHost::CreateForTesting(
web_contents(), std::make_unique<TestManagerProvider>(nullptr));
contents()->NavigateAndCommit(GURL("https://www.example.com"));
base::HistogramTester histograms;
conversion_host->SetCurrentTargetFrameForTesting(main_rfh());
blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
conversion->reporting_origin =
url::Origin::Create(GURL("https://secure.com"));
conversion_host->RegisterConversion(std::move(conversion));
// Navigate again to trigger histogram code.
contents()->NavigateAndCommit(GURL("https://www.example-next.com"));
histograms.ExpectBucketCount("Conversions.RegisteredConversionsPerPage", 1,
0);
}
TEST_F(ConversionHostTest, NavigationWithNoImpression_Ignored) {
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
NavigationSimulatorImpl::NavigateAndCommitFromDocument(GURL(kConversionUrl),
main_rfh());
EXPECT_EQ(0u, test_manager_.num_impressions());
}
TEST_F(ConversionHostTest, ValidImpression_ForwardedToManager) {
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->Commit();
EXPECT_EQ(1u, test_manager_.num_impressions());
}
TEST_F(ConversionHostTest, ImpressionWithNoManagerAvilable_NoCrash) {
// Replace the ConversionHost on the WebContents with one that is backed by a
// null ConversionManager.
static_cast<WebContentsImpl*>(web_contents())
->RemoveReceiverSetForTesting(blink::mojom::ConversionHost::Name_);
auto conversion_host = ConversionHost::CreateForTesting(
web_contents(), std::make_unique<TestManagerProvider>(nullptr));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->Commit();
}
TEST_F(ConversionHostTest, ImpressionInSubframe_Ignored) {
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
// Create a subframe and use it as a target for the conversion registration
// mojo.
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
content::RenderFrameHost* subframe = rfh_tester->AppendChild("subframe");
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), subframe);
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->Commit();
EXPECT_EQ(0u, test_manager_.num_impressions());
}
// Test that if we cannot access the initiator frame of the navigation, we
// ignore the associated impression.
TEST_F(ConversionHostTest, ImpressionNavigationWithDeadInitiator_Ignored) {
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->Commit();
EXPECT_EQ(0u, test_manager_.num_impressions());
}
TEST_F(ConversionHostTest, ImpressionNavigationCommitsToErrorPage_Ignored) {
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->Fail(net::ERR_FAILED);
navigation->CommitErrorPage();
EXPECT_EQ(0u, test_manager_.num_impressions());
}
TEST_F(ConversionHostTest, ImpressionNavigationAborts_Ignored) {
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->AbortCommit();
EXPECT_EQ(0u, test_manager_.num_impressions());
}
TEST_F(ConversionHostTest,
CommittedOriginDiffersFromConversionDesintation_Ignored) {
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL("https://different.com"), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(CreateValidImpression());
navigation->Commit();
EXPECT_EQ(0u, test_manager_.num_impressions());
}
// TODO(crbug.com/1165985): Fix Linux ChromiumOS MSan Tests
TEST_F(ConversionHostTest,
DISABLED_ImpressionNavigation_OriginTrustworthyChecksPerformed) {
const char kLocalHost[] = "http://localhost";
struct {
std::string impression_origin;
std::string conversion_origin;
std::string reporting_origin;
bool impression_expected;
} kTestCases[] = {
{kLocalHost /* impression_origin */, kLocalHost /* conversion_origin */,
kLocalHost /* reporting_origin */, true /* impression_expected */},
{"http://127.0.0.1" /* impression_origin */,
"http://127.0.0.1" /* conversion_origin */,
"http://127.0.0.1" /* reporting_origin */,
true /* impression_expected */},
{kLocalHost /* impression_origin */, kLocalHost /* conversion_origin */,
"http://insecure.com" /* reporting_origin */,
false /* impression_expected */},
{kLocalHost /* impression_origin */,
"http://insecure.com" /* conversion_origin */,
kLocalHost /* reporting_origin */, false /* impression_expected */},
{"http://insecure.com" /* impression_origin */,
kLocalHost /* conversion_origin */, kLocalHost /* reporting_origin */,
false /* impression_expected */},
{"https://secure.com" /* impression_origin */,
"https://secure.com" /* conversion_origin */,
"https://secure.com" /* reporting_origin */,
true /* impression_expected */},
};
for (const auto& test_case : kTestCases) {
contents()->NavigateAndCommit(GURL(test_case.impression_origin));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(test_case.conversion_origin), main_rfh());
Impression impression;
impression.conversion_destination =
url::Origin::Create(GURL(test_case.conversion_origin));
impression.reporting_origin =
url::Origin::Create(GURL(test_case.reporting_origin));
navigation->set_impression(impression);
navigation->SetInitiatorFrame(main_rfh());
navigation->Commit();
EXPECT_EQ(test_case.impression_expected, test_manager_.num_impressions())
<< "For test case: " << test_case.impression_origin << " | "
<< test_case.conversion_origin << " | " << test_case.reporting_origin;
test_manager_.Reset();
}
}
} // namespace content