blob: 7f26fa6961552b32072617bf626d36f84dbd137c [file] [log] [blame]
// 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 "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/metrics/metrics_memory_details.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/variations/active_field_trials.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/network/public/cpp/features.h"
#include "url/gurl.h"
using base::Bucket;
namespace {
class TestMemoryDetails : public MetricsMemoryDetails {
public:
TestMemoryDetails() : MetricsMemoryDetails(base::DoNothing()) {}
TestMemoryDetails(const TestMemoryDetails&) = delete;
TestMemoryDetails& operator=(const TestMemoryDetails&) = delete;
void StartFetchAndWait() {
uma_ = std::make_unique<base::HistogramTester>();
StartFetch();
run_loop_.Run();
}
// Returns a HistogramTester which observed the most recent call to
// StartFetchAndWait().
base::HistogramTester* uma() { return uma_.get(); }
int GetTotalProcessCount() {
std::vector<Bucket> buckets = uma_->GetAllSamples(
"Memory.RenderProcessHost.Count2.InitializedAndNotDead");
DCHECK(buckets.size() == 1U);
return buckets[0].min;
}
int GetOacProcessCount() {
std::vector<Bucket> buckets = uma_->GetAllSamples(
"Memory.RenderProcessHost.Count2.OriginAgentClusterOverhead");
// The bucket size will be zero when testing with OriginAgentCluster
// disabled.
CHECK(buckets.size() == 1U || buckets.size() == 0U);
return buckets.size() == 1U ? buckets[0].min : 0;
}
int GetOacProcessCountPercent() {
std::vector<Bucket> buckets = uma_->GetAllSamples(
"Memory.RenderProcessHost.Percent.OriginAgentClusterOverhead");
// The bucket size will be zero when testing with OriginAgentCluster
// disabled.
CHECK(buckets.size() == 1U || buckets.size() == 0U);
return buckets.size() == 1U ? buckets[0].min : 0;
}
private:
~TestMemoryDetails() override = default;
void OnDetailsAvailable() override {
MetricsMemoryDetails::OnDetailsAvailable();
// Exit the loop initiated by StartFetchAndWait().
run_loop_.QuitWhenIdle();
}
std::unique_ptr<base::HistogramTester> uma_;
base::RunLoop run_loop_;
};
// Matches a container of histogram samples, for the common case where the
// histogram received just one sample.
#define HasOneSample(x) ElementsAre(Sample(x, 1))
} // namespace
// General browsertests for the Origin-Agent-Cluster header can be found in
// content/browser/isolated_origin_browsertest.cc. However testing metrics
// related behavior is best done from chrome/; thus, this file exists.
class OriginAgentClusterBrowserTest : public InProcessBrowserTest {
public:
OriginAgentClusterBrowserTest()
: OriginAgentClusterBrowserTest(true /* enable_origin_agent_cluster_*/) {}
OriginAgentClusterBrowserTest(const OriginAgentClusterBrowserTest&) = delete;
OriginAgentClusterBrowserTest& operator=(
const OriginAgentClusterBrowserTest&) = delete;
~OriginAgentClusterBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
// Start the HTTPS server here so we can properly get the URL for the
// command-line isolated origin.
https_server()->AddDefaultHandlers(GetChromeTestDataDir());
https_server()->RegisterRequestHandler(
base::BindRepeating(&OriginAgentClusterBrowserTest::HandleResponse,
base::Unretained(this)));
ASSERT_TRUE(https_server()->Start());
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
std::string origin_list =
https_server()->GetURL("isolated.foo.com", "/").spec();
command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->StartAcceptingConnections();
}
void TearDownOnMainThread() override {
ASSERT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
protected:
explicit OriginAgentClusterBrowserTest(bool enable_oac)
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
enable_origin_agent_cluster_(enable_oac) {
// To keep the tests easier to reason about, turn off both the spare
// renderer process and process reuse for subframes in different
// BrowsingInstances.
if (enable_origin_agent_cluster_) {
feature_list_.InitWithFeatures(
/* enable_features */ {features::kOriginIsolationHeader,
features::kDisableProcessReuse},
/* disable_features */ {features::kSpareRendererForSitePerProcess});
} else {
feature_list_.InitWithFeatures(
/* enable_features */ {features::kDisableProcessReuse},
/* disable_features */ {features::kOriginIsolationHeader,
features::kSpareRendererForSitePerProcess});
}
}
private:
std::unique_ptr<net::test_server::HttpResponse> HandleResponse(
const net::test_server::HttpRequest& request) {
if (request.relative_url == "/origin_key_me") {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content_type("text/html");
response->AddCustomHeader("Origin-Agent-Cluster", "?1");
response->set_content("I like origin keys!");
return std::move(response);
} else if (request.relative_url == "/origin_key_me_iframe") {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content_type("text/html");
response->AddCustomHeader("Origin-Agent-Cluster", "?1");
response->set_content("<body><iframe id='test'></iframe></body>");
return std::move(response);
}
return nullptr;
}
// TODO(https://crbug.com/423465927): Explore a better approach to make the
// existing tests run with the prewarm feature enabled.
test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_{
test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
net::EmbeddedTestServer https_server_;
base::test::ScopedFeatureList feature_list_;
bool enable_origin_agent_cluster_;
};
class OriginAgentClusterDisabledBrowserTest
: public OriginAgentClusterBrowserTest {
public:
OriginAgentClusterDisabledBrowserTest()
: OriginAgentClusterBrowserTest(false /* enable_origin_agent_cluster_*/) {
}
OriginAgentClusterDisabledBrowserTest(
const OriginAgentClusterDisabledBrowserTest&) = delete;
OriginAgentClusterDisabledBrowserTest& operator=(
const OriginAgentClusterDisabledBrowserTest&) = delete;
~OriginAgentClusterDisabledBrowserTest() override = default;
}; // class OriginAgentClusterDisabledBrowserTest
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest, Navigations) {
GURL start_url(https_server()->GetURL("foo.com", "/iframe.html"));
GURL origin_keyed_url(
https_server()->GetURL("origin-keyed.foo.com", "/origin_key_me"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto web_feature_waiter =
std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
web_contents);
web_feature_waiter->AddWebFeatureExpectation(
blink::mojom::WebFeature::kOriginAgentClusterHeader);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));
EXPECT_FALSE(web_feature_waiter->DidObserveWebFeature(
blink::mojom::WebFeature::kOriginAgentClusterHeader));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", origin_keyed_url));
web_feature_waiter->Wait();
}
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsSimple) {
GURL start_url(https_server()->GetURL("foo.com", "/iframe.html"));
GURL origin_keyed_url(
https_server()->GetURL("origin-keyed.foo.com", "/origin_key_me"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", origin_keyed_url));
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(2, details->GetTotalProcessCount());
EXPECT_EQ(1, details->GetOacProcessCount());
EXPECT_EQ(50, details->GetOacProcessCountPercent());
}
// Same as OriginAgentClusterBrowserTest.ProcessCountMetricsSimple, but with
// OriginAgentCluster disabled, so no metrics should be recorded.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterDisabledBrowserTest,
ProcessCountMetricsSimple) {
GURL start_url(https_server()->GetURL("foo.com", "/iframe.html"));
GURL origin_keyed_url(
https_server()->GetURL("origin-keyed.foo.com", "/origin_key_me"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", origin_keyed_url));
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(1, details->GetTotalProcessCount());
EXPECT_EQ(0, details->GetOacProcessCount());
EXPECT_EQ(0, details->GetOacProcessCountPercent());
}
// In the case where we load an OAC origin with no base-origin, we expect zero
// overhead since the isolated origin only creates a single process, and no
// process is created for the base-origin.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsNoBaseOrigin) {
GURL origin_keyed_url(
https_server()->GetURL("origin-keyed.foo.com", "/origin_key_me"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), origin_keyed_url));
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(1, details->GetTotalProcessCount());
EXPECT_EQ(0, details->GetOacProcessCount());
EXPECT_EQ(0, details->GetOacProcessCountPercent());
}
// We expect the OAC overhead to be zero when no OAC origins are present.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsNoOACs) {
bool origin_keyed_processes_by_default =
content::SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault();
GURL start_url(https_server()->GetURL("foo.com", "/iframe.html"));
GURL sub_origin_url(https_server()->GetURL("sub.foo.com", "/title1.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", sub_origin_url));
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
if (origin_keyed_processes_by_default) {
// Even though sub.foo.com doesn't have an OAC opt-in header, it will still
// be isolated in this case due to the Origin Isolation mode, and thus it
// should still count as overhead.
EXPECT_EQ(2, details->GetTotalProcessCount());
EXPECT_EQ(1, details->GetOacProcessCount());
EXPECT_EQ(50, details->GetOacProcessCountPercent());
} else {
EXPECT_EQ(1, details->GetTotalProcessCount());
EXPECT_EQ(0, details->GetOacProcessCount());
EXPECT_EQ(0, details->GetOacProcessCountPercent());
}
}
// Two distinct OAC sub-origins with a base-origin should have an overhead of
// two processes.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsTwoSubOrigins) {
GURL start_url(https_server()->GetURL("foo.com", "/two_iframes_blank.html"));
GURL origin_keyed_url1(
https_server()->GetURL("sub1.foo.com", "/origin_key_me"));
GURL origin_keyed_url2(
https_server()->GetURL("sub2.foo.com", "/origin_key_me"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "iframe1", origin_keyed_url1));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "iframe2", origin_keyed_url2));
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(3, details->GetTotalProcessCount());
EXPECT_EQ(2, details->GetOacProcessCount());
EXPECT_EQ(66, details->GetOacProcessCountPercent());
}
// This test loads the same base-origin with an isolated sub-origin in each of
// two tabs. Each tab represents a separate BrowsingInstance, so we expect the
// OAC overhead of 1 to be counted twice.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsTwoTabs) {
GURL start_url(https_server()->GetURL("foo.com", "/iframe.html"));
GURL origin_keyed_url(
https_server()->GetURL("sub.foo.com", "/origin_key_me"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));
content::WebContents* tab1 =
browser()->tab_strip_model()->GetWebContentsAt(0);
EXPECT_TRUE(NavigateIframeToURL(tab1, "test", origin_keyed_url));
// Open two a.com tabs (with cross site http iframes). IsolateExtensions mode
// should have no effect so far, since there are no frames straddling the
// extension/web boundary.
ASSERT_TRUE(AddTabAtIndex(1, start_url, ui::PAGE_TRANSITION_TYPED));
content::WebContents* tab2 =
browser()->tab_strip_model()->GetWebContentsAt(1);
EXPECT_TRUE(NavigateIframeToURL(tab2, "test", origin_keyed_url));
EXPECT_NE(tab1, tab2);
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(4, details->GetTotalProcessCount());
EXPECT_EQ(2, details->GetOacProcessCount());
EXPECT_EQ(50, details->GetOacProcessCountPercent());
}
// Make sure command-line isolated origins don't trigger the OAC metrics.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsNoCmdLineIsolation) {
GURL main_frame_url(https_server()->GetURL("foo.com", "/iframe.html"));
GURL cmd_line_isolated_url(
https_server()->GetURL("isolated.foo.com", "/title1.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", cmd_line_isolated_url));
// Make sure we got two SiteInstances.
auto* main_frame = web_contents->GetPrimaryMainFrame();
auto* child_frame = ChildFrameAt(main_frame, 0);
EXPECT_NE(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(0, details->GetOacProcessCount());
EXPECT_EQ(0, details->GetOacProcessCountPercent());
}
// Make sure command-line isolated origins don't trigger the OAC metrics.
// Same as ProcessCountMetricsNoCmdLineIsolation but isolated child has OAC.
// We don't consider this as overhead because the extra process would still
// exist for this user even without OAC.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsNoCmdLineIsolationWithOAC1) {
GURL main_frame_url(https_server()->GetURL("foo.com", "/iframe.html"));
GURL cmd_line_isolated_url(
https_server()->GetURL("isolated.foo.com", "/origin_key_me"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", cmd_line_isolated_url));
// Make sure we got two SiteInstances.
auto* main_frame = web_contents->GetPrimaryMainFrame();
auto* child_frame = ChildFrameAt(main_frame, 0);
EXPECT_NE(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(0, details->GetOacProcessCount());
EXPECT_EQ(0, details->GetOacProcessCountPercent());
}
// Make sure command-line isolated origins don't trigger the OAC metrics.
// Same as ProcessCountMetricsNoCmdLineIsolation but both mainframe and child
// have OAC.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsNoCmdLineIsolationWithOAC2) {
GURL main_frame_url(
https_server()->GetURL("foo.com", "/origin_key_me_iframe"));
GURL cmd_line_isolated_url(
https_server()->GetURL("isolated.foo.com", "/origin_key_me"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", cmd_line_isolated_url));
// Make sure we got two SiteInstances.
auto* main_frame = web_contents->GetPrimaryMainFrame();
auto* child_frame = ChildFrameAt(main_frame, 0);
EXPECT_NE(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(0, details->GetOacProcessCount());
EXPECT_EQ(0, details->GetOacProcessCountPercent());
}
// Make sure command-line isolated origins don't trigger the OAC metrics.
// Same as ProcessCountMetricsNoCmdLineIsolation but mainframe has OAC.
IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest,
ProcessCountMetricsNoCmdLineIsolationWithOAC3) {
GURL main_frame_url(
https_server()->GetURL("foo.com", "/origin_key_me_iframe"));
GURL cmd_line_isolated_url(
https_server()->GetURL("isolated.foo.com", "/title1.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", cmd_line_isolated_url));
// Make sure we got two SiteInstances.
auto* main_frame = web_contents->GetPrimaryMainFrame();
auto* child_frame = ChildFrameAt(main_frame, 0);
EXPECT_NE(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(0, details->GetOacProcessCount());
EXPECT_EQ(0, details->GetOacProcessCountPercent());
}