Bug 1469072 - Add infrastructure to move Activity Stream into its own content process.
Summary:
This patch adds the infrastructure to move Activity Stream (about:newtab, about:home,
and about:welcome) into its own special content process - the privileged content
process. This feature of running Activity Stream in the privileged content process
is disabled by default. (See "browser.tabs.remote.separatePrivilegedContentProcess"
preference.) We can deal with other about: pages in a follow-up.
Reviewers: mconley
Tags: #secure-revision
Bug #: 1469072
Differential Revision:
https://phabricator.services.mozilla.com/D1731
MozReview-Commit-ID: 5gIrP4LxcIt
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -26,16 +26,18 @@ support-files =
[browser_multiselect_tabs_using_Ctrl.js]
[browser_multiselect_tabs_using_Shift.js]
[browser_navigatePinnedTab.js]
[browser_new_file_whitelisted_http_tab.js]
skip-if = !e10s # Test only relevant for e10s.
[browser_new_tab_insert_position.js]
skip-if = (debug && os == 'linux' && bits == 32) #Bug 1455882, disabled on Linux32 for almost permafailing
support-files = file_new_tab_page.html
+[browser_new_tab_in_privileged_process_pref.js]
+skip-if = !e10s # Pref and test only relevant for e10s.
[browser_new_web_tab_in_file_process_pref.js]
skip-if = !e10s # Pref and test only relevant for e10s.
[browser_newwindow_tabstrip_overflow.js]
[browser_open_newtab_start_observer_notification.js]
[browser_opened_file_tab_navigated_to_web.js]
[browser_overflowScroll.js]
[browser_pinnedTabs_clickOpen.js]
[browser_pinnedTabs_closeByKeyboard.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_new_tab_in_privileged_process_pref.js
@@ -0,0 +1,212 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Tests to ensure that Activity Stream loads in the privileged content process.
+ * Normal http web pages should load in the web content process.
+ * Ref: Bug 1469072.
+ */
+
+const ABOUT_BLANK = "about:blank";
+const ABOUT_HOME = "about:home";
+const ABOUT_NEWTAB = "about:newtab";
+const ABOUT_WELCOME = "about:welcome";
+const TEST_HTTP = "http://example.org/";
+
+/**
+ * Takes a xul:browser and makes sure that the remoteTypes for the browser in
+ * both the parent and the child processes are the same.
+ *
+ * @param {xul:browser} browser
+ * A xul:browser.
+ * @param {string} expectedRemoteType
+ * The expected remoteType value for the browser in both the parent
+ * and child processes.
+ * @param {optional string} message
+ * If provided, shows this string as the message when remoteType values
+ * do not match. If not present, it uses the default message defined
+ * in the function parameters.
+ */
+async function checkBrowserRemoteType(
+ browser,
+ expectedRemoteType,
+ message = `Ensures that tab runs in the ${expectedRemoteType} content process.`
+) {
+ // Check both parent and child to ensure that they have the correct remoteType.
+ is(browser.remoteType, expectedRemoteType, message);
+ is(browser.messageManager.remoteType, expectedRemoteType,
+ "Parent and child process should agree on the remote type.");
+}
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.newtab.preload", false],
+ ["browser.tabs.remote.separatePrivilegedContentProcess", true],
+ ["dom.ipc.processCount.privileged", 1],
+ ["dom.ipc.keepProcessesAlive.privileged", 1],
+ ]
+ });
+});
+
+/*
+ * Test to ensure that the Activity Stream tabs open in privileged content
+ * process. We will first open an about:newtab page that acts as a reference to
+ * the privileged content process. With the reference, we can then open Activity
+ * Stream links in a new tab and ensure that the new tab opens in the same
+ * privileged content process as our reference.
+ */
+add_task(async function activity_stream_in_privileged_content_process() {
+ Services.ppmm.releaseCachedProcesses();
+
+ await BrowserTestUtils.withNewTab(ABOUT_NEWTAB, async function(browser1) {
+ await checkBrowserRemoteType(browser1, E10SUtils.PRIVILEGED_REMOTE_TYPE);
+
+ // Note the processID for about:newtab for comparison later.
+ let privilegedPid = browser1.frameLoader.tabParent.osPid;
+
+ for (let url of [
+ ABOUT_NEWTAB,
+ ABOUT_WELCOME,
+ ABOUT_HOME,
+ `${ABOUT_NEWTAB}#foo`,
+ `${ABOUT_WELCOME}#bar`,
+ `${ABOUT_HOME}#baz`,
+ `${ABOUT_NEWTAB}?q=foo`,
+ `${ABOUT_WELCOME}?q=bar`,
+ `${ABOUT_HOME}?q=baz`
+ ]) {
+ await BrowserTestUtils.withNewTab(url, async function(browser2) {
+ is(browser2.frameLoader.tabParent.osPid, privilegedPid,
+ "Check that about:newtab tabs are in the same privileged content process.");
+ });
+ }
+ });
+
+ Services.ppmm.releaseCachedProcesses();
+});
+
+/*
+ * Test to ensure that a process switch occurs when navigating between normal
+ * web pages and Activity Stream pages in the same tab.
+ */
+add_task(async function process_switching_through_loading_in_the_same_tab() {
+ Services.ppmm.releaseCachedProcesses();
+
+ await BrowserTestUtils.withNewTab(TEST_HTTP, async function(browser) {
+ await checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE);
+
+ for (let [url, remoteType] of [
+ [ABOUT_NEWTAB, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [ABOUT_BLANK, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [ABOUT_HOME, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [ABOUT_WELCOME, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [ABOUT_BLANK, E10SUtils.WEB_REMOTE_TYPE],
+ [`${ABOUT_NEWTAB}#foo`, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [`${ABOUT_WELCOME}#bar`, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [`${ABOUT_HOME}#baz`, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [`${ABOUT_NEWTAB}?q=foo`, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [`${ABOUT_WELCOME}?q=bar`, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE],
+ [`${ABOUT_HOME}?q=baz`, E10SUtils.PRIVILEGED_REMOTE_TYPE],
+ [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE]
+ ]) {
+ BrowserTestUtils.loadURI(browser, url);
+ await BrowserTestUtils.browserLoaded(browser, false, url);
+ await checkBrowserRemoteType(browser, remoteType);
+ }
+ });
+
+ Services.ppmm.releaseCachedProcesses();
+});
+
+/*
+ * Test to ensure that a process switch occurs when navigating between normal
+ * web pages and Activity Stream pages using the browser's navigation features
+ * such as history and location change.
+ */
+add_task(async function process_switching_through_navigation_features() {
+ Services.ppmm.releaseCachedProcesses();
+
+ await BrowserTestUtils.withNewTab(ABOUT_NEWTAB, async function(browser) {
+ await checkBrowserRemoteType(browser, E10SUtils.PRIVILEGED_REMOTE_TYPE);
+
+ // Note the processID for about:newtab for comparison later.
+ let privilegedPid = browser.frameLoader.tabParent.osPid;
+
+ // Check that about:newtab opened from JS in about:newtab page is in the same process.
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, ABOUT_NEWTAB, true);
+ await ContentTask.spawn(browser, ABOUT_NEWTAB, uri => {
+ content.open(uri, "_blank");
+ });
+ let newTab = await promiseTabOpened;
+ registerCleanupFunction(async function() {
+ BrowserTestUtils.removeTab(newTab);
+ });
+ browser = newTab.linkedBrowser;
+ is(browser.frameLoader.tabParent.osPid, privilegedPid,
+ "Check that new tab opened from about:newtab is loaded in privileged content process.");
+
+ // Check that reload does not break the privileged content process affinity.
+ BrowserReload();
+ await BrowserTestUtils.browserLoaded(browser, false, ABOUT_NEWTAB);
+ is(browser.frameLoader.tabParent.osPid, privilegedPid,
+ "Check that about:newtab is still in privileged content process after reload.");
+
+ // Load http webpage
+ BrowserTestUtils.loadURI(browser, TEST_HTTP);
+ await BrowserTestUtils.browserLoaded(browser, false, TEST_HTTP);
+ await checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE);
+
+ // Check that using the history back feature switches back to privileged content process.
+ let promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, ABOUT_NEWTAB);
+ browser.goBack();
+ await promiseLocation;
+ // We will need to ensure that the process flip has fully completed so that
+ // the navigation history data will be available when we do browser.goForward();
+ await BrowserTestUtils.waitForEvent(newTab, "SSTabRestored");
+ is(browser.frameLoader.tabParent.osPid, privilegedPid,
+ "Check that about:newtab is still in privileged content process after history goBack.");
+
+ // Check that using the history forward feature switches back to the web content process.
+ promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, TEST_HTTP);
+ browser.goForward();
+ await promiseLocation;
+ // We will need to ensure that the process flip has fully completed so that
+ // the navigation history data will be available when we do browser.gotoIndex(0);
+ await BrowserTestUtils.waitForEvent(newTab, "SSTabRestored");
+ await checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE,
+ "Check that tab runs in the web content process after using history goForward.");
+
+ // Check that goto history index does not break the affinity.
+ promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, ABOUT_NEWTAB);
+ browser.gotoIndex(0);
+ await promiseLocation;
+ is(browser.frameLoader.tabParent.osPid, privilegedPid,
+ "Check that about:newtab is in privileged content process after history gotoIndex.");
+
+ BrowserTestUtils.loadURI(browser, TEST_HTTP);
+ await BrowserTestUtils.browserLoaded(browser, false, TEST_HTTP);
+ await checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE);
+
+ // Check that location change causes a change in process type as well.
+ await ContentTask.spawn(browser, ABOUT_NEWTAB, uri => {
+ content.location = uri;
+ });
+ await BrowserTestUtils.browserLoaded(browser, false, ABOUT_NEWTAB);
+ is(browser.frameLoader.tabParent.osPid, privilegedPid,
+ "Check that about:newtab is in privileged content process after location change.");
+ });
+
+ Services.ppmm.releaseCachedProcesses();
+});
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2741,16 +2741,18 @@ ContentChild::RecvRemoteType(const nsStr
mRemoteType.Assign(aRemoteType);
// For non-default ("web") types, update the process name so about:memory's
// process names are more obvious.
if (aRemoteType.EqualsLiteral(FILE_REMOTE_TYPE)) {
SetProcessName(NS_LITERAL_STRING("file:// Content"));
} else if (aRemoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) {
SetProcessName(NS_LITERAL_STRING("WebExtensions"));
+ } else if (aRemoteType.EqualsLiteral(PRIVILEGED_REMOTE_TYPE)) {
+ SetProcessName(NS_LITERAL_STRING("Privileged Content"));
} else if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) {
SetProcessName(NS_LITERAL_STRING("Large Allocation Web Content"));
}
return IPC_OK();
}
const nsAString&
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1852,18 +1852,18 @@ ContentParent::ShouldKeepProcessAlive()
return false;
}
auto contentParents = sBrowserContentParents->Get(mRemoteType);
if (!contentParents) {
return false;
}
- // We might want to keep alive some content processes alive during test runs,
- // for performance reasons. This should never be used in production.
+ // We might want to keep some content processes alive for performance reasons.
+ // e.g. test runs and privileged content process for some about: pages.
// We don't want to alter behavior if the pref is not set, so default to 0.
int32_t processesToKeepAlive = 0;
nsAutoCString keepAlivePref("dom.ipc.keepProcessesAlive.");
keepAlivePref.Append(NS_ConvertUTF16toUTF8(mRemoteType));
if (NS_FAILED(Preferences::GetInt(keepAlivePref.get(), &processesToKeepAlive))) {
return false;
}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -38,16 +38,17 @@
// These must match the similar ones in E10SUtils.jsm.
// Process names as reported by about:memory are defined in
// ContentChild:RecvRemoteType. Add your value there too or it will be called
// "Web Content".
#define DEFAULT_REMOTE_TYPE "web"
#define FILE_REMOTE_TYPE "file"
#define EXTENSION_REMOTE_TYPE "extension"
+#define PRIVILEGED_REMOTE_TYPE "privileged"
// This must start with the DEFAULT_REMOTE_TYPE above.
#define LARGE_ALLOCATION_REMOTE_TYPE "webLargeAllocation"
class nsConsoleService;
class nsIContentProcessInfo;
class nsICycleCollectorLogSink;
class nsIDumpGCAndCCLogsCallback;
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -187,16 +187,19 @@ ScriptPreloader::InitContentChild(Conten
}
ProcessType
ScriptPreloader::GetChildProcessType(const nsAString& remoteType)
{
if (remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) {
return ProcessType::Extension;
}
+ if (remoteType.EqualsLiteral(PRIVILEGED_REMOTE_TYPE)) {
+ return ProcessType::Privileged;
+ }
return ProcessType::Web;
}
namespace {
static void
TraceOp(JSTracer* trc, void* data)
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -38,16 +38,17 @@ namespace ipc {
namespace loader {
class InputBuffer;
class ScriptCacheChild;
enum class ProcessType : uint8_t {
Parent,
Web,
Extension,
+ Privileged,
};
template <typename T>
struct Matcher
{
virtual bool Matches(T) = 0;
};
}
--- a/js/xpconnect/loader/script_cache.py
+++ b/js/xpconnect/loader/script_cache.py
@@ -15,28 +15,31 @@ def usage():
sys.exit(1)
class ProcessTypes:
Default = 0
Web = 1
Extension = 2
+ Privileged = 3
def __init__(self, val):
self.val = val
def __str__(self):
res = []
if self.val & (1 << self.Default):
res.append('Parent')
if self.val & (1 << self.Web):
res.append('Web')
if self.val & (1 << self.Extension):
res.append('Extension')
+ if self.val & (1 << self.Privileged):
+ res.append('Privileged')
return '|'.join(res)
class InputBuffer(object):
def __init__(self, data):
self.data = data
self.offset = 0
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3213,16 +3213,24 @@ pref("dom.ipc.plugins.forcedirect.enable
pref("dom.ipc.processCount", 4);
// Default to allow only one file:// URL content process.
pref("dom.ipc.processCount.file", 1);
// WebExtensions only support a single extension process.
pref("dom.ipc.processCount.extension", 1);
+// Privileged content only supports a single content process.
+pref("dom.ipc.processCount.privileged", 1);
+
+// Keep a single privileged content process alive for performance reasons.
+// e.g. we do not want to throw content processes out every time we navigate
+// away from about:newtab.
+pref("dom.ipc.keepProcessesAlive.privileged", 1);
+
// Whether a native event loop should be used in the content process.
#if defined(XP_WIN)
pref("dom.ipc.useNativeEventProcessing.content", false);
#else
pref("dom.ipc.useNativeEventProcessing.content", true);
#endif
// Quantum DOM scheduling:
@@ -3249,16 +3257,19 @@ pref("browser.tabs.remote.separateFileUr
// Pref that enables top level web content pages that are opened from file://
// URI pages to run in the file content process.
// This has been added in case breaking any window references between these
// sorts of pages, which we have to do when we run them in the normal web
// content process, causes compatibility issues.
pref("browser.tabs.remote.allowLinkedWebInFileUriProcess", true);
+// Pref to control whether we use separate privileged content processes.
+pref("browser.tabs.remote.separatePrivilegedContentProcess", false);
+
// Enable the use of display-lists for SVG hit-testing and painting.
pref("svg.display-lists.hit-testing.enabled", true);
pref("svg.display-lists.painting.enabled", true);
// Is support for the <marker orient="auto-start-reverse"> feature enabled?
pref("svg.marker-improvements.enabled", true);
// Is support for the new getBBox method from SVG 2 enabled?
--- a/toolkit/modules/E10SUtils.jsm
+++ b/toolkit/modules/E10SUtils.jsm
@@ -8,16 +8,18 @@ var EXPORTED_SYMBOLS = ["E10SUtils"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "useSeparateFileUriProcess",
"browser.tabs.remote.separateFileUriProcess", false);
XPCOMUtils.defineLazyPreferenceGetter(this, "allowLinkedWebInFileUriProcess",
"browser.tabs.remote.allowLinkedWebInFileUriProcess", false);
+XPCOMUtils.defineLazyPreferenceGetter(this, "useSeparatePrivilegedContentProcess",
+ "browser.tabs.remote.separatePrivilegedContentProcess", false);
ChromeUtils.defineModuleGetter(this, "Utils",
"resource://gre/modules/sessionstore/Utils.jsm");
function getAboutModule(aURL) {
// Needs to match NS_GetAboutModuleName
let moduleName = aURL.pathQueryRef.replace(/[#?].*/, "").toLowerCase();
let contract = "@mozilla.org/network/protocol/about;1?what=" + moduleName;
try {
@@ -30,21 +32,24 @@ function getAboutModule(aURL) {
}
const NOT_REMOTE = null;
// These must match any similar ones in ContentParent.h.
const WEB_REMOTE_TYPE = "web";
const FILE_REMOTE_TYPE = "file";
const EXTENSION_REMOTE_TYPE = "extension";
+const PRIVILEGED_REMOTE_TYPE = "privileged";
// This must start with the WEB_REMOTE_TYPE above.
const LARGE_ALLOCATION_REMOTE_TYPE = "webLargeAllocation";
const DEFAULT_REMOTE_TYPE = WEB_REMOTE_TYPE;
+const ACTIVITY_STREAM_PAGES = new Set(["home", "newtab", "welcome"]);
+
function validatedWebRemoteType(aPreferredRemoteType, aTargetUri, aCurrentUri) {
// If the domain is whitelisted to allow it to use file:// URIs, then we have
// to run it in a file content process, in case it uses file:// sub-resources.
const sm = Services.scriptSecurityManager;
if (sm.inFileURIWhitelist(aTargetUri)) {
return FILE_REMOTE_TYPE;
}
@@ -77,16 +82,17 @@ function validatedWebRemoteType(aPreferr
}
var E10SUtils = {
DEFAULT_REMOTE_TYPE,
NOT_REMOTE,
WEB_REMOTE_TYPE,
FILE_REMOTE_TYPE,
EXTENSION_REMOTE_TYPE,
+ PRIVILEGED_REMOTE_TYPE,
LARGE_ALLOCATION_REMOTE_TYPE,
canLoadURIInProcess(aURL, aProcess) {
let remoteType = aProcess == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
? DEFAULT_REMOTE_TYPE : NOT_REMOTE;
return remoteType == this.getRemoteTypeForURI(aURL, true, remoteType);
},
@@ -148,16 +154,21 @@ var E10SUtils = {
// If the module doesn't exist then an error page will be loading, that
// should be ok to load in any process
if (!module) {
return aPreferredRemoteType;
}
let flags = module.getURIFlags(aURI);
if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD) {
+ // Load Activity Stream in a separate process.
+ if (useSeparatePrivilegedContentProcess &&
+ ACTIVITY_STREAM_PAGES.has(aURI.filePath)) {
+ return PRIVILEGED_REMOTE_TYPE;
+ }
return DEFAULT_REMOTE_TYPE;
}
// If the about page can load in parent or child, it should be safe to
// load in any remote type.
if (flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD) {
return aPreferredRemoteType;
}