diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini index 6ba65c540b67..f3003f8680c1 100644 --- a/browser/base/content/test/tabs/browser.ini +++ b/browser/base/content/test/tabs/browser.ini @@ -32,6 +32,8 @@ 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] diff --git a/browser/base/content/test/tabs/browser_new_tab_in_privileged_process_pref.js b/browser/base/content/test/tabs/browser_new_tab_in_privileged_process_pref.js new file mode 100644 index 000000000000..043153560ff7 --- /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(); +}); diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 61f0ac058a19..de5540b028bc 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2746,6 +2746,8 @@ ContentChild::RecvRemoteType(const nsString& aRemoteType) 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")); } diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 86bedfc4033c..edfa1598d770 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -1857,8 +1857,8 @@ ContentParent::ShouldKeepProcessAlive() const 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; diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 05c23f448451..b88c9c85e512 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -43,6 +43,7 @@ #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" diff --git a/js/xpconnect/loader/ScriptPreloader.cpp b/js/xpconnect/loader/ScriptPreloader.cpp index f92805238436..3e72cd251b2e 100644 --- a/js/xpconnect/loader/ScriptPreloader.cpp +++ b/js/xpconnect/loader/ScriptPreloader.cpp @@ -192,6 +192,9 @@ 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; } diff --git a/js/xpconnect/loader/ScriptPreloader.h b/js/xpconnect/loader/ScriptPreloader.h index cd1d3cee80a6..4874ee6cec00 100644 --- a/js/xpconnect/loader/ScriptPreloader.h +++ b/js/xpconnect/loader/ScriptPreloader.h @@ -43,6 +43,7 @@ namespace loader { Parent, Web, Extension, + Privileged, }; template diff --git a/js/xpconnect/loader/script_cache.py b/js/xpconnect/loader/script_cache.py index e6cc73fab593..c8c57122fcaf 100755 --- a/js/xpconnect/loader/script_cache.py +++ b/js/xpconnect/loader/script_cache.py @@ -20,6 +20,7 @@ class ProcessTypes: Default = 0 Web = 1 Extension = 2 + Privileged = 3 def __init__(self, val): self.val = val @@ -32,6 +33,8 @@ class ProcessTypes: res.append('Web') if self.val & (1 << self.Extension): res.append('Extension') + if self.val & (1 << self.Privileged): + res.append('Privileged') return '|'.join(res) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 65b64d6b878a..74de0e5b4e21 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3218,6 +3218,14 @@ 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); @@ -3254,6 +3262,9 @@ pref("browser.tabs.remote.separateFileUriProcess", true); // 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); diff --git a/toolkit/modules/E10SUtils.jsm b/toolkit/modules/E10SUtils.jsm index 05749c79195a..5598367b6d8c 100644 --- a/toolkit/modules/E10SUtils.jsm +++ b/toolkit/modules/E10SUtils.jsm @@ -13,6 +13,8 @@ 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"); @@ -35,11 +37,14 @@ const NOT_REMOTE = null; 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. @@ -82,6 +87,7 @@ var E10SUtils = { WEB_REMOTE_TYPE, FILE_REMOTE_TYPE, EXTENSION_REMOTE_TYPE, + PRIVILEGED_REMOTE_TYPE, LARGE_ALLOCATION_REMOTE_TYPE, canLoadURIInProcess(aURL, aProcess) { @@ -153,6 +159,11 @@ var E10SUtils = { 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; }