blob: ba2dff4aea345b33c1ce5b2316438d0589550f35 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/site_isolation/site_details.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/metrics/metrics_memory_details.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/metrics/metrics_service.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_utils.h"
#include "extensions/common/switches.h"
#include "extensions/test/test_extension_dir.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 "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
using base::Bucket;
using content::WebContents;
using extensions::Extension;
using extensions::TestExtensionDir;
using testing::ElementsAre;
using testing::PrintToString;
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();
content::RunMessageLoop();
}
// Returns a HistogramTester which observed the most recent call to
// StartFetchAndWait().
base::HistogramTester* uma() { return uma_.get(); }
int GetOutOfProcessIframeCount() {
std::vector<Bucket> buckets =
uma_->GetAllSamples("SiteIsolation.OutOfProcessIframes");
CHECK_EQ(1U, buckets.size());
return buckets[0].min;
}
int GetOutOfProcessInnerFrameTreesCount() {
std::vector<Bucket> buckets =
uma_->GetAllSamples("SiteIsolation.OutOfProcessInnerFrameTrees");
CHECK_EQ(1U, buckets.size());
return buckets[0].min;
}
size_t CountPageTitles() {
size_t count = 0;
for (const ProcessMemoryInformation& process : ChromeBrowser()->processes) {
if (process.process_type == content::PROCESS_TYPE_RENDERER) {
count += process.titles.size();
}
}
return count;
}
private:
~TestMemoryDetails() override {}
void OnDetailsAvailable() override {
MetricsMemoryDetails::OnDetailsAvailable();
// Exit the loop initiated by StartFetchAndWait().
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
std::unique_ptr<base::HistogramTester> uma_;
};
// This matcher takes three other matchers as arguments, and applies one of them
// depending on the current site isolation mode. The first applies if no site
// isolation mode is active; the second applies under --isolate-extensions mode;
// and the third applies under --site-per-process mode.
MATCHER_P3(DependingOnPolicy,
isolate_nothing,
isolate_extensions,
isolate_all_sites,
#if BUILDFLAG(IS_ANDROID)
std::string("(with oopifs disabled) ") +
PrintToString(isolate_nothing)
#else
content::AreAllSitesIsolatedForTesting()
? std::string("(under --site-per-process) ") +
PrintToString(isolate_all_sites)
: std::string("(under --isolate-extensions) ") +
PrintToString(isolate_extensions)
#endif
) {
#if BUILDFLAG(IS_ANDROID)
return ExplainMatchResult(isolate_nothing, arg, result_listener);
#else
return content::AreAllSitesIsolatedForTesting()
? ExplainMatchResult(isolate_all_sites, arg, result_listener)
: ExplainMatchResult(isolate_extensions, arg, result_listener);
#endif
}
// Matcher for base::Bucket objects that allows bucket_min to be a matcher.
MATCHER_P2(Sample,
bucket_min,
count,
std::string("is a Bucket whose count is ") + PrintToString(count) +
std::string(" and whose value is ") +
PrintToString(bucket_min)) {
return ExplainMatchResult(count, arg.count, result_listener) &&
ExplainMatchResult(bucket_min, arg.min, result_listener);
}
// Allow matchers to be pretty-printed when passed to PrintToString() for the
// cases we care about.
template <typename P1, typename P2, typename P3>
void PrintTo(const DependingOnPolicyMatcherP3<P1, P2, P3>& matcher,
std::ostream* os) {
testing::Matcher<int> matcherCast = matcher;
matcherCast.DescribeTo(os);
}
template <typename P1, typename P2>
void PrintTo(const SampleMatcherP2<P1, P2>& matcher, std::ostream* os) {
testing::Matcher<Bucket> matcherCast = matcher;
matcherCast.DescribeTo(os);
}
// 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
class SiteDetailsBrowserTest : public extensions::ExtensionBrowserTest {
public:
SiteDetailsBrowserTest() {}
SiteDetailsBrowserTest(const SiteDetailsBrowserTest&) = delete;
SiteDetailsBrowserTest& operator=(const SiteDetailsBrowserTest&) = delete;
~SiteDetailsBrowserTest() override {}
void SetUpOnMainThread() override {
extensions::ExtensionBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
// Add content/test/data so we can use cross_site_iframe_factory.html
base::FilePath test_data_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
embedded_test_server()->ServeFilesFromDirectory(
test_data_dir.AppendASCII("content/test/data/"));
ASSERT_TRUE(embedded_test_server()->Start());
}
// Create and install an extension that has a couple of web-accessible
// resources and, optionally, a background process.
const Extension* CreateExtension(const std::string& name,
bool has_background_process) {
TestExtensionDir dir;
auto manifest = base::Value::Dict()
.Set("name", name)
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("web_accessible_resources",
base::Value::List()
.Append("blank_iframe.html")
.Append("http_iframe.html")
.Append("two_http_iframes.html"));
if (has_background_process) {
manifest.Set("background",
base::Value::Dict().Set(
"scripts", base::Value::List().Append("script.js")));
dir.WriteFile(FILE_PATH_LITERAL("script.js"),
"console.log('" + name + " running');");
}
dir.WriteFile(FILE_PATH_LITERAL("blank_iframe.html"),
base::StringPrintf("<html><body>%s, blank iframe:"
" <iframe width=80 height=80></iframe>"
"</body></html>",
name.c_str()));
std::string iframe_url =
embedded_test_server()
->GetURL("w.com", "/cross_site_iframe_factory.html?w")
.spec();
std::string iframe_url2 =
embedded_test_server()
->GetURL("x.com", "/cross_site_iframe_factory.html?x")
.spec();
dir.WriteFile(
FILE_PATH_LITERAL("http_iframe.html"),
base::StringPrintf("<html><body>%s, http:// iframe:"
" <iframe width=80 height=80 src='%s'></iframe>"
"</body></html>",
name.c_str(), iframe_url.c_str()));
dir.WriteFile(FILE_PATH_LITERAL("two_http_iframes.html"),
base::StringPrintf(
"<html><body>%s, two http:// iframes:"
" <iframe width=80 height=80 src='%s'></iframe>"
" <iframe width=80 height=80 src='%s'></iframe>"
"</body></html>",
name.c_str(), iframe_url.c_str(), iframe_url2.c_str()));
dir.WriteManifest(manifest);
const Extension* extension = LoadExtension(dir.UnpackedPath());
EXPECT_TRUE(extension);
temp_dirs_.push_back(std::move(dir));
return extension;
}
const Extension* CreateHostedApp(const std::string& name,
const GURL& app_url) {
TestExtensionDir dir;
auto manifest =
base::Value::Dict()
.Set("name", name)
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("app",
base::Value::Dict()
.Set("urls", base::Value::List().Append(app_url.spec()))
.Set("launch",
base::Value::Dict().Set("web_url", app_url.spec())));
dir.WriteManifest(manifest);
const Extension* extension = LoadExtension(dir.UnpackedPath());
EXPECT_TRUE(extension);
temp_dirs_.push_back(std::move(dir));
return extension;
}
int GetRenderProcessCountFromUma(base::HistogramTester* uma) {
auto buckets = uma->GetAllSamples("Memory.RenderProcessHost.Count.All");
EXPECT_EQ(buckets.size(), 1u);
int rph_count = buckets[0].min;
// Memory.RenderProcessHost.Count.All includes the spare process. If a
// spare is present, subtract it from total count since the tests below
// assume no spare.
if (content::RenderProcessHost::GetSpareRenderProcessHostForTesting())
rph_count--;
return rph_count;
}
int GetRenderProcessCount() {
return content::RenderProcessHost::GetCurrentRenderProcessCountForTesting();
}
private:
std::vector<TestExtensionDir> temp_dirs_;
};
// Test the accuracy of SiteDetails process estimation, in the presence of
// multiple iframes, navigation, multiple BrowsingInstances, and multiple tabs
// in the same BrowsingInstance.
//
// Disabled since it's flaky: https://crbug.com/830318.
IN_PROC_BROWSER_TEST_F(SiteDetailsBrowserTest, DISABLED_ManyIframes) {
// Page with 14 nested oopifs across 9 sites (a.com through i.com).
// None of these are https.
GURL abcdefghi_url = embedded_test_server()->GetURL(
"a.com",
"/cross_site_iframe_factory.html?a(b(a(b,c,d,e,f,g,h)),c,d,e,i(f))");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), abcdefghi_url));
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(1U, details->CountPageTitles());
EXPECT_THAT(
details->uma()->GetAllSamples("SiteIsolation.BrowsingInstanceCount"),
HasOneSample(1));
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 1, 9));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 14));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 114)));
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(0, 0, 114)));
// Navigate to a different, disjoint set of 7 sites.
GURL pqrstuv_url = embedded_test_server()->GetURL(
"p.com",
"/cross_site_iframe_factory.html?p(q(r),r(s),s(t),t(q),u(u),v(p))");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), pqrstuv_url));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(1U, details->CountPageTitles());
EXPECT_THAT(
details->uma()->GetAllSamples("SiteIsolation.BrowsingInstanceCount"),
HasOneSample(1));
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 1, 7));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 11));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 68)));
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(0, 0, 68)));
// Open a second tab (different BrowsingInstance) with 4 sites (a through d).
GURL abcd_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c(d())))");
ASSERT_TRUE(AddTabAtIndex(1, abcd_url, ui::PAGE_TRANSITION_TYPED));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(2U, details->CountPageTitles());
EXPECT_THAT(
details->uma()->GetAllSamples("SiteIsolation.BrowsingInstanceCount"),
HasOneSample(2));
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(2, 2, 11));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 14));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 81)));
EXPECT_THAT(
details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
DependingOnPolicy(ElementsAre(Bucket(0, 2)), ElementsAre(Bucket(0, 2)),
ElementsAre(Bucket(12, 1), Bucket(68, 1))));
// Open a third tab (different BrowsingInstance) with the same 4 sites.
ASSERT_TRUE(AddTabAtIndex(2, abcd_url, ui::PAGE_TRANSITION_TYPED));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_THAT(
details->uma()->GetAllSamples("SiteIsolation.BrowsingInstanceCount"),
HasOneSample(3));
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
// For --site-per-process, the total process count will be 12 instead of 15,
// because the third tab's subframes (b, c, d) will reuse matching subframe
// processes from the second tab (across BrowsingInstances). This subframe
// process consolidation was added as part of https://crbug.com/512560. Note
// that the a.com main frame in tab 3 won't reuse tab 2's main frame process,
// so this is still one process higher than the lower bound.
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 3, 12));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 17));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 96)));
EXPECT_THAT(
details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
DependingOnPolicy(ElementsAre(Bucket(0, 3)), ElementsAre(Bucket(0, 3)),
ElementsAre(Bucket(12, 2), Bucket(68, 1))));
// From the third tab, window.open() a fourth tab in the same
// BrowsingInstance, to a page using the same four sites "a-d" as third tab,
// plus an additional site "e". The estimated process counts should increase
// by one (not five) from the previous scenario, as the new tab can reuse the
// four processes already in the BrowsingInstance.
GURL dcbae_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?d(c(b(a(e))))");
ui_test_utils::UrlLoadObserver load_complete(
dcbae_url, content::NotificationService::AllSources());
ASSERT_EQ(3, browser()->tab_strip_model()->count());
ASSERT_TRUE(
content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
"window.open('" + dcbae_url.spec() + "');"));
ASSERT_EQ(4, browser()->tab_strip_model()->count());
load_complete.Wait();
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_THAT(
details->uma()->GetAllSamples("SiteIsolation.BrowsingInstanceCount"),
HasOneSample(3));
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 3, 13));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 21));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 114)));
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
DependingOnPolicy(
ElementsAre(Bucket(0, 3)), ElementsAre(Bucket(0, 3)),
ElementsAre(Bucket(12, 1), Bucket(29, 1), Bucket(68, 1))));
}
// TODO(crbug.com/671891): This test is flaky.
IN_PROC_BROWSER_TEST_F(SiteDetailsBrowserTest, DISABLED_IsolateExtensions) {
// We start on "about:blank", which should be credited with a process in this
// case.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), 1);
EXPECT_EQ(0, details->GetOutOfProcessIframeCount());
// Install one script-injecting extension with background page, and an
// extension with web accessible resources.
const Extension* extension1 = CreateExtension("Extension One", true);
const Extension* extension2 = CreateExtension("Extension Two", false);
// 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.
GURL tab1_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c)");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab1_url));
WebContents* tab1 = browser()->tab_strip_model()->GetWebContentsAt(0);
GURL tab2_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(d,e)");
ASSERT_TRUE(AddTabAtIndex(1, tab2_url, ui::PAGE_TRANSITION_TYPED));
WebContents* tab2 = browser()->tab_strip_model()->GetWebContentsAt(1);
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 3, 7));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 4));
// Test that "one process per extension" applies even when web content has an
// extension iframe.
// Tab1 navigates its first iframe to a resource of extension1. This shouldn't
// result in a new extension process (it should share with extension1's
// background page).
content::NavigateIframeToURL(
tab1, "child-0", extension1->GetResourceURL("/blank_iframe.html"));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 3, 6));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 1, 4));
// Tab2 navigates its first iframe to a resource of extension1. This also
// shouldn't result in a new extension process (it should share with the
// background page and the other iframe).
content::NavigateIframeToURL(
tab2, "child-0", extension1->GetResourceURL("/blank_iframe.html"));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 3, 5));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 2, 4));
// Tab1 navigates its second iframe to a resource of extension2. This SHOULD
// result in a new process since extension2 had no existing process.
content::NavigateIframeToURL(
tab1, "child-1", extension2->GetResourceURL("/blank_iframe.html"));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 4, 5));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 3, 4));
// Tab2 navigates its second iframe to a resource of extension2. This should
// share the existing extension2 process.
content::NavigateIframeToURL(
tab2, "child-1", extension2->GetResourceURL("/blank_iframe.html"));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 4, 4));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 4, 4));
// Install extension3 (identical config to extension2)
const Extension* extension3 = CreateExtension("Extension Three", false);
// Navigate Tab2 to a top-level page from extension3. There are four processes
// now: one for tab1's main frame, and one for each of the extensions:
// extension1 has a process because it has a background page; extension2 is
// used as an iframe in tab1, and extension3 is the top-level frame in tab2.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extension3->GetResourceURL("blank_iframe.html")));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 4, 4));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 2, 2));
// Navigate tab2 to a different extension3 page containing a web iframe. The
// iframe should get its own process. The lower bound number indicates that,
// in theory, the iframe could share a process with tab1's main frame.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extension3->GetResourceURL("http_iframe.html")));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(3, 5, 5));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 3, 3));
// Navigate tab1 to an extension3 page with an extension3 iframe. There should
// be three processes estimated by IsolateExtensions: one for extension3, one
// for extension1's background page, and one for the web iframe in tab2.
browser()->tab_strip_model()->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extension3->GetResourceURL("blank_iframe.html")));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(2, 3, 3));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 1, 1));
// Now navigate tab1 to an extension3 page with a web iframe. This could share
// a process with tab2's iframe (the LowerBound number), or it could get its
// own process (the Estimate number).
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extension3->GetResourceURL("http_iframe.html")));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
// There should be four total renderer processes: one for each of the two web
// iframes, one for extension3, and one for extension 1's background page.
// Note that the optimization in https://crbug.com/512560, where subframes
// that require a dedicated process reuse existing processes where possible,
// does not apply to web iframes in extensions anymore -- see
// https://crbug.com/899418.
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(2, 4, 4));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 2, 2));
}
// Exercises accounting in the case where an extension has two different-site
// web iframes.
IN_PROC_BROWSER_TEST_F(SiteDetailsBrowserTest, ExtensionWithTwoWebIframes) {
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
// Install one script-injecting extension with background page, and an
// extension with web accessible resources.
const Extension* extension = CreateExtension("Test Extension", false);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extension->GetResourceURL("/two_http_iframes.html")));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
if (content::AreAllSitesIsolatedForTesting()) {
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 3, 3));
} else {
// When full isolation is not turned on, the 2 subframes share a process.
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 2, 2));
}
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 2, 2));
}
// Verifies that --isolate-extensions doesn't isolate hosted apps.
//
// Disabled since it's flaky: https://crbug.com/830318.
IN_PROC_BROWSER_TEST_F(SiteDetailsBrowserTest,
DISABLED_IsolateExtensionsHostedApps) {
GURL app_with_web_iframe_url = embedded_test_server()->GetURL(
"app.org", "/cross_site_iframe_factory.html?app.org(b.com)");
GURL app_in_web_iframe_url = embedded_test_server()->GetURL(
"b.com", "/cross_site_iframe_factory.html?b.com(app.org)");
// No hosted app is installed: app.org just behaves like a normal domain.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), app_with_web_iframe_url));
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 1, 2));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 1));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), app_in_web_iframe_url));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 1, 2));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 1));
// Now install app.org as a hosted app.
CreateHostedApp("App", GURL("http://app.org"));
// Reload the same two pages, and verify that the hosted app still is not
// isolated by --isolate-extensions, but is isolated by --site-per-process.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), app_with_web_iframe_url));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 1, 2));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 1));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), app_in_web_iframe_url));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_EQ(GetRenderProcessCountFromUma(details->uma()),
GetRenderProcessCount());
EXPECT_THAT(GetRenderProcessCount(), DependingOnPolicy(1, 1, 2));
EXPECT_THAT(details->GetOutOfProcessIframeCount(),
DependingOnPolicy(0, 0, 1));
}
// Verifies that the UMA counter for SiteInstances in a BrowsingInstance is
// correct when using tabs with web pages.
//
// Disabled since it's flaky. https://crbug.com/934900
IN_PROC_BROWSER_TEST_F(SiteDetailsBrowserTest,
DISABLED_VerifySiteInstanceCountInBrowsingInstance) {
// Page with 14 nested oopifs across 9 sites (a.com through i.com).
GURL abcdefghi_url = embedded_test_server()->GetURL(
"a.com",
"/cross_site_iframe_factory.html?a(b(a(b,c,d,e,f,g,h)),c,d,e,i(f))");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), abcdefghi_url));
// Get the metrics.
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
// Since there are no extensions involved, the results in the default case
// and extensions::IsIsolateExtensionsEnabled() are the same.
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.SiteInstancesPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(1, 1, 9)));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 114)));
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(0, 0, 114)));
// Open another tab through window.open(), which will be in the same
// BrowsingInstance.
GURL dcbae_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?d(c(b(j(k))))");
ui_test_utils::UrlLoadObserver load_complete(
dcbae_url, content::NotificationService::AllSources());
ASSERT_EQ(1, browser()->tab_strip_model()->count());
ASSERT_TRUE(
content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
"window.open('" + dcbae_url.spec() + "');"));
ASSERT_EQ(2, browser()->tab_strip_model()->count());
load_complete.Wait();
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.SiteInstancesPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(1, 1, 11)));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 160)));
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(0, 0, 160)));
// Open a tab, which will be in a different BrowsingInstance.
GURL abcd_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c(d())))");
ASSERT_TRUE(AddTabAtIndex(1, abcd_url, ui::PAGE_TRANSITION_TYPED));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_THAT(
details->uma()->GetAllSamples(
"SiteIsolation.SiteInstancesPerBrowsingInstance"),
DependingOnPolicy(ElementsAre(Sample(1, 2)), ElementsAre(Sample(1, 2)),
ElementsAre(Sample(4, 1), Sample(11, 1))));
EXPECT_THAT(details->uma()->GetAllSamples("SiteIsolation.ProxyCount"),
HasOneSample(DependingOnPolicy(0, 0, 160)));
EXPECT_THAT(
details->uma()->GetAllSamples(
"SiteIsolation.ProxyCountPerBrowsingInstance"),
DependingOnPolicy(ElementsAre(Sample(0, 2)), ElementsAre(Sample(0, 2)),
ElementsAre(Sample(12, 1), Sample(160, 1))));
}
// Verifies that the UMA counter for SiteInstances in a BrowsingInstance is
// correct when extensions and web pages are mixed together.
//
// Disabled since it's flaky: https://crbug.com/830318.
IN_PROC_BROWSER_TEST_F(
SiteDetailsBrowserTest,
DISABLED_VerifySiteInstanceCountInBrowsingInstanceWithExtensions) {
// 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.
GURL tab_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c,d(e))");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
WebContents* tab = browser()->tab_strip_model()->GetWebContentsAt(0);
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
// Since there are no extensions loaded yet, the results in the default case
// and extensions::IsIsolateExtensionsEnabled() are the same.
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.SiteInstancesPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(1, 1, 5)));
// Load an extension without a background page, which will avoid creating a
// BrowsingInstance for it.
const Extension* extension1 = CreateExtension("Extension One", false);
// Navigate the tab's first iframe to a resource of the extension. The
// extension iframe will be put in the same BrowsingInstance as it is part
// of the frame tree.
content::NavigateIframeToURL(
tab, "child-0", extension1->GetResourceURL("/blank_iframe.html"));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.SiteInstancesPerBrowsingInstance"),
HasOneSample(DependingOnPolicy(1, 2, 5)));
// Now load an extension with a background page. This will result in a
// BrowsingInstance for the background page.
const Extension* extension2 = CreateExtension("Extension Two", true);
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.SiteInstancesPerBrowsingInstance"),
DependingOnPolicy(ElementsAre(Bucket(1, 2)),
ElementsAre(Bucket(1, 1), Bucket(2, 1)),
ElementsAre(Bucket(1, 1), Bucket(5, 1))));
// Navigate the second iframe of the tab to the second extension. It should
// stay in the same BrowsingInstance as the page.
content::NavigateIframeToURL(
tab, "child-1", extension2->GetResourceURL("/blank_iframe.html"));
details = new TestMemoryDetails();
details->StartFetchAndWait();
EXPECT_THAT(details->uma()->GetAllSamples(
"SiteIsolation.SiteInstancesPerBrowsingInstance"),
DependingOnPolicy(ElementsAre(Bucket(1, 2)),
ElementsAre(Bucket(1, 1), Bucket(3, 1)),
ElementsAre(Bucket(1, 1), Bucket(5, 1))));
}
class PrerenderSiteDetailsBrowserTest : public InProcessBrowserTest {
public:
PrerenderSiteDetailsBrowserTest()
: prerender_helper_(
base::BindRepeating(&PrerenderSiteDetailsBrowserTest::web_contents,
base::Unretained(this))) {}
~PrerenderSiteDetailsBrowserTest() override = default;
PrerenderSiteDetailsBrowserTest(const PrerenderSiteDetailsBrowserTest&) =
delete;
PrerenderSiteDetailsBrowserTest& operator=(
const PrerenderSiteDetailsBrowserTest&) = delete;
void SetUp() override {
prerender_helper_.SetUp(embedded_test_server());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->Start());
}
protected:
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::test::PrerenderTestHelper prerender_helper_;
};
IN_PROC_BROWSER_TEST_F(PrerenderSiteDetailsBrowserTest,
MemoryDetailsForPrerender) {
// Navigate to an initial page.
auto initial_url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// Load a page in the prerender.
GURL prerender_url = embedded_test_server()->GetURL("/title2.html");
int host_id = prerender_helper_.AddPrerender(prerender_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
EXPECT_FALSE(host_observer.was_activated());
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
// Currently we don't collect the title of the prerendering page.
EXPECT_EQ(1U, details->CountPageTitles());
}
class FencedFrameSiteDetailsBrowserTest : public InProcessBrowserTest {
public:
FencedFrameSiteDetailsBrowserTest() = default;
~FencedFrameSiteDetailsBrowserTest() override = default;
FencedFrameSiteDetailsBrowserTest(const FencedFrameSiteDetailsBrowserTest&) =
delete;
FencedFrameSiteDetailsBrowserTest& operator=(
const FencedFrameSiteDetailsBrowserTest&) = delete;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
protected:
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_helper_;
}
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
private:
content::test::FencedFrameTestHelper fenced_frame_helper_;
};
IN_PROC_BROWSER_TEST_F(FencedFrameSiteDetailsBrowserTest,
MemoryDetailsForFencedFrame) {
content::IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
auto initial_url = embedded_test_server()->GetURL("a.com", "/empty.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// Load a fenced frame.
GURL fenced_frame_url =
embedded_test_server()->GetURL("b.com", "/fenced_frames/iframe.html");
content::RenderFrameHost* fenced_frame_host =
fenced_frame_test_helper().CreateFencedFrame(
web_contents()->GetPrimaryMainFrame(), fenced_frame_url);
ASSERT_TRUE(fenced_frame_host);
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
// Currently we don't collect the title of the fenced frame.
EXPECT_EQ(1U, details->CountPageTitles());
// Expect we encountered one fenced frame.
EXPECT_EQ(1, details->GetOutOfProcessInnerFrameTreesCount());
}
class BackForwardCacheSiteDetailsBrowserTest : public InProcessBrowserTest {
public:
BackForwardCacheSiteDetailsBrowserTest() {
// Enable BackForwardCache.
feature_list_.InitWithFeaturesAndParameters(
content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(
/*ignore_outstanding_network_request=*/false),
content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
}
~BackForwardCacheSiteDetailsBrowserTest() override = default;
BackForwardCacheSiteDetailsBrowserTest(
const BackForwardCacheSiteDetailsBrowserTest&) = delete;
BackForwardCacheSiteDetailsBrowserTest& operator=(
const BackForwardCacheSiteDetailsBrowserTest&) = delete;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
protected:
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(BackForwardCacheSiteDetailsBrowserTest,
MemoryDetailsForBackForwardCache) {
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_EQ(web_contents()->GetVisibility(), content::Visibility::VISIBLE);
// Navigate to A.
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
// Navigate to B.
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
scoped_refptr<TestMemoryDetails> details = new TestMemoryDetails();
details->StartFetchAndWait();
// Currently we don't collect the title of the back forward cache.
EXPECT_EQ(1U, details->CountPageTitles());
}