blob: 4c278ff8a8eaef5f7ae435a81636d83476432f37 [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 <map>
#include <string>
#include "base/files/file_util.h"
#include "base/strings/string_piece.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/dom_distiller/tab_utils.h"
#include "chrome/browser/dom_distiller/test_distillation_observers.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/install_verifier.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
#include "components/dom_distiller/content/browser/test_distillability_observer.h"
#include "components/dom_distiller/core/dom_distiller_features.h"
#include "components/dom_distiller/core/dom_distiller_switches.h"
#include "components/dom_distiller/core/url_constants.h"
#include "components/embedder_support/switches.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/private_network_access_util.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_builder.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h"
namespace {
using blink::mojom::WebFeature;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::Pair;
// We use a custom page that explicitly disables its own favicon (by providing
// an invalid data: URL for it) so as to prevent the browser from making an
// automatic request to /favicon.ico. This is because the automatic request
// messes with our tests, in which we want to trigger a single request from the
// web page to a resource of our choice and observe the side-effect in metrics.
constexpr char kNoFaviconPath[] = "/private_network_access/no-favicon.html";
// Same as kNoFaviconPath, except it carries a header that makes the browser
// consider it came from the `public` address space, irrespective of the fact
// that we loaded the web page from localhost.
constexpr char kTreatAsPublicAddressPath[] =
"/private_network_access/no-favicon-treat-as-public-address.html";
GURL SecureURL(const net::EmbeddedTestServer& server, const std::string& path) {
// Test HTTPS servers cannot lie about their hostname, so they yield URLs
// starting with https://localhost. http://localhost is already a secure
// context, so we do not bother instantiating an HTTPS server.
return server.GetURL(path);
}
GURL NonSecureURL(const net::EmbeddedTestServer& server,
const std::string& path) {
return server.GetURL("foo.test", path);
}
GURL LocalSecureURL(const net::EmbeddedTestServer& server) {
return SecureURL(server, kNoFaviconPath);
}
GURL LocalNonSecureURL(const net::EmbeddedTestServer& server) {
return NonSecureURL(server, kNoFaviconPath);
}
GURL PublicSecureURL(const net::EmbeddedTestServer& server) {
return SecureURL(server, kTreatAsPublicAddressPath);
}
GURL PublicNonSecureURL(const net::EmbeddedTestServer& server) {
return NonSecureURL(server, kTreatAsPublicAddressPath);
}
// Similar to LocalNonSecure() but can be fetched by any origin.
GURL LocalNonSecureWithCrossOriginCors(const net::EmbeddedTestServer& server) {
return SecureURL(server, "/cors-ok.txt");
}
// The returned script evaluates to a boolean indicating whether the fetch
// succeeded or not.
std::string FetchScript(const GURL& url) {
return content::JsReplace(
"fetch($1).then(response => true).catch(error => false)", url);
}
constexpr char kFeatureHistogramName[] = "Blink.UseCounter.Features";
constexpr WebFeature kAllAddressSpaceFeatures[] = {
WebFeature::kAddressSpacePrivateSecureContextEmbeddedLocal,
WebFeature::kAddressSpacePrivateNonSecureContextEmbeddedLocal,
WebFeature::kAddressSpacePublicSecureContextEmbeddedLocal,
WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocal,
WebFeature::kAddressSpaceUnknownSecureContextEmbeddedLocal,
WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedLocal,
WebFeature::kAddressSpacePublicSecureContextEmbeddedPrivate,
WebFeature::kAddressSpacePublicNonSecureContextEmbeddedPrivate,
WebFeature::kAddressSpaceUnknownSecureContextEmbeddedPrivate,
WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedPrivate,
WebFeature::kAddressSpacePrivateSecureContextNavigatedToLocal,
WebFeature::kAddressSpacePrivateNonSecureContextNavigatedToLocal,
WebFeature::kAddressSpacePublicSecureContextNavigatedToLocal,
WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal,
WebFeature::kAddressSpaceUnknownSecureContextNavigatedToLocal,
WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToLocal,
WebFeature::kAddressSpacePublicSecureContextNavigatedToPrivate,
WebFeature::kAddressSpacePublicNonSecureContextNavigatedToPrivate,
WebFeature::kAddressSpaceUnknownSecureContextNavigatedToPrivate,
WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToPrivate,
};
// Returns a map of WebFeature to bucket count. Skips buckets with zero counts.
std::map<WebFeature, int> GetAddressSpaceFeatureBucketCounts(
const base::HistogramTester& tester) {
std::map<WebFeature, int> counts;
for (WebFeature feature : kAllAddressSpaceFeatures) {
int count = tester.GetBucketCount(kFeatureHistogramName, feature);
if (count == 0) {
continue;
}
counts.emplace(feature, count);
}
return counts;
}
// Private Network Access is a web platform specification aimed at securing
// requests made from public websites to the private network and localhost.
//
// It is mostly implemented in content/, but some of its integrations (
// (with Blink UseCounters, with chrome/-specific special schemes) cannot be
// tested in content/, however, thus we define this standalone test here.
//
// See also:
//
// - specification: https://wicg.github.io/private-network-access.
// - feature browsertests:
// //content/browser/renderer_host/private_network_access_browsertest.cc
//
class PrivateNetworkAccessBrowserTestBase : public InProcessBrowserTest {
public:
PrivateNetworkAccessBrowserTestBase(
std::vector<base::Feature> enabled_features,
std::vector<base::Feature> disabled_features) {
features_.InitWithFeatures(enabled_features, disabled_features);
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
bool NavigateAndFlushHistograms() {
// Commit a new navigation in order to flush UseCounters incremented during
// the last navigation to the browser process, so they are reflected in
// histograms.
return content::NavigateToURL(web_contents(), GURL("about:blank"));
}
// Never returns nullptr. The returned server is already Start()ed.
//
// NOTE: This is defined as a method on the test fixture instead of a free
// function because GetChromeTestDataDir() is a test fixture method itself.
// We return a unique_ptr because EmbeddedTestServer is not movable and C++17
// support is not available at time of writing.
std::unique_ptr<net::EmbeddedTestServer> NewServer(
net::EmbeddedTestServer::Type server_type =
net::EmbeddedTestServer::TYPE_HTTP) {
std::unique_ptr<net::EmbeddedTestServer> server =
std::make_unique<net::EmbeddedTestServer>(server_type);
server->AddDefaultHandlers(GetChromeTestDataDir());
EXPECT_TRUE(server->Start());
return server;
}
protected:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// The public key used to verify test trial tokens.
// See: //docs/origin_trial_integration.md
constexpr char kOriginTrialTestPublicKey[] =
"dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey,
kOriginTrialTestPublicKey);
}
private:
base::test::ScopedFeatureList features_;
};
class PrivateNetworkAccessWithFeatureDisabledBrowserTest
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessWithFeatureDisabledBrowserTest()
: PrivateNetworkAccessBrowserTestBase(
{},
{
features::kBlockInsecurePrivateNetworkRequests,
}) {}
};
class PrivateNetworkAccessWithFeatureEnabledBrowserTest
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessWithFeatureEnabledBrowserTest()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kBlockInsecurePrivateNetworkRequestsDeprecationTrial,
dom_distiller::kReaderMode,
},
{}) {}
void SetUpCommandLine(base::CommandLine* command_line) override {
PrivateNetworkAccessBrowserTestBase::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kEnableDomDistiller);
}
private:
void SetUpOnMainThread() override {
PrivateNetworkAccessBrowserTestBase::SetUpOnMainThread();
// The distiller needs to run in an isolated environment. For tests we
// can simply use the last value available.
if (!dom_distiller::DistillerJavaScriptWorldIdIsSet()) {
dom_distiller::SetDistillerJavaScriptWorldId(
content::ISOLATED_WORLD_ID_CONTENT_END);
}
}
};
// ================
// USECOUNTER TESTS
// ================
//
// UseCounters are translated into UMA histograms at the chrome/ layer, by the
// page_load_metrics component. These tests verify that UseCounters are recorded
// correctly by Private Network Access code in the right circumstances.
// This test verifies that no feature is counted for the initial navigation from
// a new tab to a page served by localhost.
//
// Regression test for https://crbug.com/1134601.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
DoesNotRecordAddressSpaceFeatureForInitialNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(GetAddressSpaceFeatureBucketCounts(histogram_tester), IsEmpty());
}
// This test verifies that no feature is counted for top-level navigations from
// a public page to a local page.
//
// TODO(crbug.com/1129326): Revisit this once the story around top-level
// navigations is closer to being resolved. Counting these events will help
// decide what to do.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
DoesNotRecordAddressSpaceFeatureForRegularNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(GetAddressSpaceFeatureBucketCounts(histogram_tester), IsEmpty());
}
// This test verifies that when a secure context served from the public address
// space loads a resource from the local network, the correct WebFeature is
// use-counted.
// Disabled, as explained in https://crbug.com/1143206
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
DISABLED_RecordsAddressSpaceFeatureForFetch) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
fetch("defaultresponse").then(response => response.ok)
)"));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(
Pair(WebFeature::kAddressSpacePublicSecureContextEmbeddedLocal, 1)));
}
// This test verifies that when a non-secure context served from the public
// address space loads a resource from the local network, the correct WebFeature
// is use-counted.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessWithFeatureDisabledBrowserTest,
DISABLED_RecordsAddressSpaceFeatureForFetchInNonSecureContext) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
fetch("defaultresponse").then(response => response.ok)
)"));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(Pair(
WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocal, 1)));
}
// This test verifies that when the user navigates a `public` document to a
// document served by a non-public IP, no address space feature is recorded.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessWithFeatureEnabledBrowserTest,
DoesNotRecordAddressSpaceFeatureForBrowserInitiatedNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
EXPECT_TRUE(
content::NavigateToURL(web_contents(), LocalNonSecureURL(*server)));
EXPECT_THAT(GetAddressSpaceFeatureBucketCounts(histogram_tester), IsEmpty());
}
// This test verifies that when a `public` document navigates itself to a
// document served by a non-public IP, the correct address space feature is
// recorded.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
RecordsAddressSpaceFeatureForNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
EXPECT_TRUE(content::NavigateToURLFromRenderer(web_contents(),
LocalNonSecureURL(*server)));
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(Pair(
WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1)));
}
// This test verifies that when a `public` document navigates itself to a
// document served by a non-public IP, the correct address space feature is
// recorded, even if the target document carries a CSP `treat-as-public-address`
// directive.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessWithFeatureEnabledBrowserTest,
RecordsAddressSpaceFeatureForNavigationToTreatAsPublicAddress) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
EXPECT_THAT(GetAddressSpaceFeatureBucketCounts(histogram_tester), IsEmpty());
// Navigate to a different URL with the same CSP directive. If we just tried
// to navigate to `PublicNonSecureURL(*server)`, nothing would happen.
EXPECT_TRUE(content::NavigateToURLFromRenderer(
web_contents(),
NonSecureURL(
*server,
"/set-header?Content-Security-Policy: treat-as-public-address")));
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(Pair(
WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1)));
}
// This test verifies that when a page embeds an empty iframe pointing to
// about:blank, no address space feature is recorded. It serves as a basis for
// comparison with the following tests, which test behavior with iframes.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessWithFeatureEnabledBrowserTest,
DoesNotRecordAddressSpaceFeatureForChildAboutBlankNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
new Promise(resolve => {
const child = document.createElement("iframe");
child.src = "about:blank";
child.onload = () => { resolve(true); };
document.body.appendChild(child);
})
)"));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(GetAddressSpaceFeatureBucketCounts(histogram_tester), IsEmpty());
}
// This test verifies that when a non-secure context served from the public
// address space loads a child frame from the local network, the correct
// WebFeature is use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
RecordsAddressSpaceFeatureForChildNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
base::StringPiece script_template = R"(
new Promise(resolve => {
const child = document.createElement("iframe");
child.src = $1;
child.onload = () => { resolve(true); };
document.body.appendChild(child);
})
)";
EXPECT_EQ(true,
content::EvalJs(web_contents(),
content::JsReplace(script_template,
LocalNonSecureURL(*server))));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(Pair(
WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1)));
}
// This test verifies that when a non-secure context served from the public
// address space loads a grand-child frame from the local network, the correct
// WebFeature is use-counted. If inheritance did not work correctly, the
// intermediate about:blank frame might confuse the address space logic.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
RecordsAddressSpaceFeatureForGrandchildNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
base::StringPiece script_template = R"(
function addChildFrame(doc, src) {
return new Promise(resolve => {
const child = doc.createElement("iframe");
child.src = src;
child.onload = () => { resolve(child); };
doc.body.appendChild(child);
});
}
addChildFrame(document, "about:blank")
.then(child => addChildFrame(child.contentDocument, $1))
.then(grandchild => true);
)";
EXPECT_EQ(true,
content::EvalJs(web_contents(),
content::JsReplace(script_template,
LocalNonSecureURL(*server))));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(Pair(
WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1)));
}
// This test verifies that the right address space feature is recorded when a
// navigation results in a private network request. Specifically, in this test
// the document being navigated is not the one initiating the navigation (the
// latter being the "remote initiator" referenced by the test name).
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
RecordsAddressSpaceFeatureForRemoteInitiatorNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(content::NavigateToURL(
web_contents(),
NonSecureURL(
*server,
"/private_network_access/remote-initiator-navigation.html")));
EXPECT_EQ(true, content::EvalJs(web_contents(), content::JsReplace(R"(
runTest({
url: "/defaultresponse",
});
)")));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(Pair(
WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1)));
}
// This test verifies that when the initiator of a navigation is no longer
// around by the time the navigation finishes, then no address space feature is
// recorded, and importantly: the browser does not crash.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessWithFeatureEnabledBrowserTest,
DoesNotRecordAddressSpaceFeatureForClosedInitiatorNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(content::NavigateToURL(
web_contents(),
NonSecureURL(
*server,
"/private_network_access/remote-initiator-navigation.html")));
EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
runTest({
url: new URL("/slow?3", window.location).href,
initiatorBehavior: "close",
});
)"));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(GetAddressSpaceFeatureBucketCounts(histogram_tester), IsEmpty());
}
// This test verifies that when the initiator of a navigation has already
// navigated itself by the time the navigation finishes, then no address space
// feature is recorded.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessWithFeatureEnabledBrowserTest,
DoesNotRecordAddressSpaceFeatureForMissingInitiatorNavigation) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(content::NavigateToURL(
web_contents(),
NonSecureURL(
*server,
"/private_network_access/remote-initiator-navigation.html")));
EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
runTest({
url: new URL("/slow?3", window.location).href,
initiatorBehavior: "navigate",
});
)"));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(GetAddressSpaceFeatureBucketCounts(histogram_tester), IsEmpty());
}
// This test verifies that private network requests that are blocked result in
// a WebFeature being use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
RecordsAddressSpaceFeatureForBlockedRequests) {
base::HistogramTester histogram_tester;
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
EXPECT_TRUE(
content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
fetch("defaultresponse").catch(() => true)
)"));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_THAT(
GetAddressSpaceFeatureBucketCounts(histogram_tester),
ElementsAre(Pair(
WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocal, 1)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
RecordsAddressSpaceFeatureForDeprecationTrial) {
base::HistogramTester histogram_tester;
content::DeprecationTrialURLLoaderInterceptor interceptor;
EXPECT_TRUE(content::NavigateToURL(web_contents(), interceptor.EnabledUrl()));
EXPECT_TRUE(NavigateAndFlushHistograms());
EXPECT_EQ(
histogram_tester.GetBucketCount(
kFeatureHistogramName,
WebFeature::
kPrivateNetworkAccessNonSecureContextsAllowedDeprecationTrial),
1);
}
// ====================
// SPECIAL SCHEME TESTS
// ====================
//
// These tests verify the IP address space assigned to documents loaded from a
// variety of special URL schemes. Since these are not loaded over the network,
// an IP address space must be made up for them.
// This test verifies that the chrome-untrusted:// scheme is considered local
// for the purpose of Private Network Access computations.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
SpecialSchemeChromeUntrusted) {
// The only way to have a page with a loaded chrome-untrusted:// url without
// relying on platform specific or components features, is to use the
// new-tab-page host. chrome-untrusted://new-tab-page is restricted to iframes
// however so we load chrome://new-tab-page that embeds chrome-untrusted://
// frame(s) by default.
EXPECT_TRUE(
content::NavigateToURL(web_contents(), GURL("chrome://new-tab-page")));
std::vector<content::RenderFrameHost*> frames =
web_contents()->GetAllFrames();
ASSERT_GE(frames.size(), 2u);
content::RenderFrameHost* iframe = frames[1];
EXPECT_TRUE(iframe->GetLastCommittedURL().SchemeIs(
content::kChromeUIUntrustedScheme));
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
// TODO(crbug.com/591068): The chrome-untrusted:// page should be kLocal, and
// not require a Private Network Access CORS preflight. However we have not
// yet implemented the CORS preflight mechanism, and fixing the underlying
// issue will not change the test result. Once CORS preflight is implemented,
// review this test and delete this comment.
// Note: CSP is blocking javascript eval, unless we run it in an isolated
// world.
EXPECT_EQ(true, content::EvalJs(iframe, FetchScript(fetch_url),
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
content::ISOLATED_WORLD_ID_CONTENT_END));
}
// This test verifies that the devtools:// scheme is considered local for the
// purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
SpecialSchemeDevtools) {
EXPECT_TRUE(content::NavigateToURL(
web_contents(), GURL("devtools://devtools/bundled/devtools_app.html")));
EXPECT_TRUE(web_contents()->GetMainFrame()->GetLastCommittedURL().SchemeIs(
content::kChromeDevToolsScheme));
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
// TODO(crbug.com/591068): The devtools:// page should be kLocal, and not
// require a Private Network Access CORS preflight. However we have not yet
// implemented the CORS preflight mechanism, and fixing the underlying issue
// will not change the test result. Once CORS preflight is implemented, review
// this test and delete this comment.
EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url)));
}
// This test verifies that the chrome-search:// scheme is considered local for
// the purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
SpecialSchemeChromeSearch) {
EXPECT_TRUE(content::NavigateToURL(
web_contents(), GURL("chrome-search://most-visited/title.html")));
ASSERT_TRUE(web_contents()->GetMainFrame()->GetLastCommittedURL().SchemeIs(
chrome::kChromeSearchScheme));
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
// TODO(crbug.com/591068): The chrome-search:// page should be kLocal, and not
// require a Private Network Access CORS preflight. However we have not yet
// implemented the CORS preflight mechanism, and fixing the underlying issue
// will not change the test result. Once CORS preflight is implemented, review
// this test and delete this comment.
// Note: CSP is blocking javascript eval, unless we run it in an isolated
// world.
EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
content::ISOLATED_WORLD_ID_CONTENT_END));
}
// This test verifies that the chrome-extension:// scheme is considered local
// for the purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
SpecialSchemeChromeExtension) {
base::ScopedAllowBlockingForTesting allow_blocking;
extensions::ScopedInstallVerifierBypassForTest install_verifier_bypass;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
static constexpr char kPageFile[] = "page.html";
std::vector<base::Value> resources;
resources.emplace_back(std::string(kPageFile));
constexpr char kContents[] = R"(
<html>
<head>
<title>IPAddressSpace of chrome-extension:// schemes.</title>
</head>
<body>
</body>
</html>
)";
base::WriteFile(temp_dir.GetPath().AppendASCII(kPageFile), kContents,
sizeof(kContents) - 1);
extensions::ExtensionBuilder builder("test");
builder.SetPath(temp_dir.GetPath())
.SetVersion("1.0")
.SetLocation(extensions::mojom::ManifestLocation::kExternalPolicyDownload)
.SetManifestKey("web_accessible_resources", std::move(resources));
extensions::ExtensionService* service =
extensions::ExtensionSystem::Get(browser()->profile())
->extension_service();
scoped_refptr<const extensions::Extension> extension = builder.Build();
service->OnExtensionInstalled(extension.get(), syncer::StringOrdinal(), 0);
const GURL url = extension->GetResourceURL(kPageFile);
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
ASSERT_TRUE(web_contents()->GetMainFrame()->GetLastCommittedURL().SchemeIs(
extensions::kExtensionScheme));
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
// TODO(crbug.com/591068): The chrome-extension:// page should be kLocal, and
// not require a Private Network Access CORS preflight. However we have not
// yet implemented the CORS preflight mechanism, and fixing the underlying
// issue will not change the test result. Once CORS preflight is implemented,
// review this test and delete this comment.
// Note: CSP is blocking javascript eval, unless we run it in an isolated
// world.
EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
content::ISOLATED_WORLD_ID_CONTENT_END));
}
// This test verifies that the chrome-distiller:// scheme is considered public
// for the purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
SpecialSchemeChromeDistiller) {
// Load the base page to be distilled. Note that HTTPS has to be used
// otherwise the page won't be distillable.
std::unique_ptr<net::EmbeddedTestServer> https_server =
NewServer(net::EmbeddedTestServer::TYPE_HTTPS);
GURL article_url = https_server->GetURL("/dom_distiller/simple_article.html");
dom_distiller::TestDistillabilityObserver distillability_observer(
web_contents());
dom_distiller::DistillabilityResult expected_result;
expected_result.is_distillable = true;
expected_result.is_last = false;
expected_result.is_mobile_friendly = false;
EXPECT_TRUE(content::NavigateToURL(web_contents(), article_url));
// This blocks until the page is found to be distillable.
distillability_observer.WaitForResult(expected_result);
// Distill the page. It will be placed in a new WebContents replacing the old
// one.
DistillCurrentPageAndView(web_contents());
dom_distiller::DistilledPageObserver(web_contents())
.WaitUntilFinishedLoading();
EXPECT_TRUE(web_contents()->GetMainFrame()->GetLastCommittedURL().SchemeIs(
dom_distiller::kDomDistillerScheme));
std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
// Note: CSP is blocking javascript eval, unless we run it in an isolated
// world.
EXPECT_EQ(false, content::EvalJs(web_contents(), FetchScript(fetch_url),
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
content::ISOLATED_WORLD_ID_CONTENT_END));
}
} // namespace