Bug 1525125 block newtab and homepage overrides in private windows r=rpl,Gijs
authorShane Caraveo <scaraveo@mozilla.com>
Tue, 19 Feb 2019 19:19:44 +0000
changeset 459937 ae77c4cd9a28e829a80244d53b7bd28aeced1358
parent 459936 74922ea29d44e5a45eb54da31f0329c326e4588c
child 459938 f4094fd9e1df1d7d1a43e42ebf4f6cb2c8b3e5c1
push id35580
push userdvarga@mozilla.com
push dateWed, 20 Feb 2019 04:03:23 +0000
treeherdermozilla-central@62a8571d3b92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrpl, Gijs
bugs1525125
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1525125 block newtab and homepage overrides in private windows r=rpl,Gijs Differential Revision: https://phabricator.services.mozilla.com/D18730
browser/base/content/browser.js
browser/base/content/utilityOverlay.js
browser/components/BrowserContentHandler.jsm
browser/components/extensions/ExtensionControlledPopup.jsm
browser/components/extensions/parent/ext-windows.js
browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
browser/components/sessionstore/SessionStore.jsm
browser/modules/HomePage.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2171,17 +2171,17 @@ function BrowserReloadSkipCache() {
   BrowserReloadWithFlags(reloadFlags);
 }
 
 function BrowserHome(aEvent) {
   if (aEvent && "button" in aEvent &&
       aEvent.button == 2) // right-click: do nothing
     return;
 
-  var homePage = HomePage.get();
+  var homePage = HomePage.get(window);
   var where = whereToOpenLink(aEvent, false, true);
   var urls;
   var notifyObservers;
 
   // Home page should open in a new tab when current tab is an app tab
   if (where == "current" &&
       gBrowser &&
       gBrowser.selectedTab.pinned)
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -19,20 +19,29 @@ XPCOMUtils.defineLazyModuleGetters(this,
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 Object.defineProperty(this, "BROWSER_NEW_TAB_URL", {
   enumerable: true,
   get() {
-    if (PrivateBrowsingUtils.isWindowPrivate(window) &&
-        !PrivateBrowsingUtils.permanentPrivateBrowsing &&
-        !aboutNewTabService.overridden) {
-      return "about:privatebrowsing";
+    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+      if (!PrivateBrowsingUtils.permanentPrivateBrowsing &&
+          !aboutNewTabService.overridden) {
+        return "about:privatebrowsing";
+      }
+      // If the extension does not have private browsing permission,
+      // use about:privatebrowsing.
+      if (aboutNewTabService.newTabURL.startsWith("moz-extension")) {
+        let url = new URL(aboutNewTabService.newTabURL);
+        if (!WebExtensionPolicy.getByHostname(url.hostname).privateBrowsingAllowed) {
+          return "about:privatebrowsing";
+        }
+      }
     }
     return aboutNewTabService.newTabURL;
   },
 });
 
 var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
 
 var gBidiUI = false;
--- a/browser/components/BrowserContentHandler.jsm
+++ b/browser/components/BrowserContentHandler.jsm
@@ -204,17 +204,17 @@ function getPostUpdateOverridePage(defau
  */
 function openBrowserWindow(cmdLine, triggeringPrincipal, urlOrUrlList, postData = null,
                            forcePrivate = false) {
   let chromeURL = AppConstants.BROWSER_CHROME_URL;
 
   let args;
   if (!urlOrUrlList) {
     // Just pass in the defaultArgs directly. We'll use system principal on the other end.
-    args = [gBrowserContentHandler.defaultArgs];
+    args = [gBrowserContentHandler.getDefaultArgs(forcePrivate)];
   } else {
     let pService = Cc["@mozilla.org/toolkit/profile-service;1"].
                   getService(Ci.nsIToolkitProfileService);
     if (cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
         pService.createdAlternateProfile) {
       let url = getNewInstallPage();
       if (Array.isArray(urlOrUrlList)) {
         urlOrUrlList.unshift(url);
@@ -513,17 +513,17 @@ nsBrowserContentHandler.prototype = {
     info += "  --window-size width[,height] Width and optionally height of screenshot.\n";
     info += "  --search <term>    Search <term> with your default search engine.\n";
     info += "  --setDefaultBrowser Set this app as the default browser.\n";
     return info;
   },
 
   /* nsIBrowserHandler */
 
-  get defaultArgs() {
+  getDefaultArgs(forcePrivate = false) {
     var prefb = Services.prefs;
 
     if (!gFirstWindow) {
       gFirstWindow = true;
       if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
         return "about:privatebrowsing";
       }
     }
@@ -598,17 +598,17 @@ nsBrowserContentHandler.prototype = {
         overridePage = additionalPage;
       }
     }
 
     var startPage = "";
     try {
       var choice = prefb.getIntPref("browser.startup.page");
       if (choice == 1 || choice == 3)
-        startPage = HomePage.get();
+        startPage = forcePrivate ? HomePage.getPrivate() : HomePage.get();
     } catch (e) {
       Cu.reportError(e);
     }
 
     if (startPage == "about:blank")
       startPage = "";
 
     let skipStartPage = ((override == OVERRIDE_NEW_PROFILE) || (override == OVERRIDE_ALTERNATE_PROFILE)) &&
@@ -616,16 +616,20 @@ nsBrowserContentHandler.prototype = {
     // Only show the startPage if we're not restoring an update session and are
     // not set to skip the start page on this profile
     if (overridePage && startPage && !willRestoreSession && !skipStartPage)
       return overridePage + "|" + startPage;
 
     return overridePage || startPage || "about:blank";
   },
 
+  get defaultArgs() {
+    return this.getDefaultArgs(PrivateBrowsingUtils.permanentPrivateBrowsing);
+  },
+
   mFeatures: null,
 
   getFeatures: function bch_features(cmdLine) {
     if (this.mFeatures === null) {
       this.mFeatures = "";
 
       if (cmdLine) {
         try {
--- a/browser/components/extensions/ExtensionControlledPopup.jsm
+++ b/browser/components/extensions/ExtensionControlledPopup.jsm
@@ -26,16 +26,18 @@ const {XPCOMUtils} = ChromeUtils.import(
 ChromeUtils.defineModuleGetter(this, "AddonManager",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "BrowserUtils",
                                "resource://gre/modules/BrowserUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "CustomizableUI",
                                "resource:///modules/CustomizableUI.jsm");
 ChromeUtils.defineModuleGetter(this, "ExtensionSettingsStore",
                                "resource://gre/modules/ExtensionSettingsStore.jsm");
+ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
+                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 let {
   makeWidgetId,
 } = ExtensionCommon;
 
 XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
   return Services.strings.createBundle("chrome://global/locale/extensions.properties");
 });
@@ -190,25 +192,30 @@ class ExtensionControlledPopup {
     this.removeObserver();
 
     if (!extensionId) {
       let item = ExtensionSettingsStore.getSetting(
         this.settingType, this.settingKey);
       extensionId = item && item.id;
     }
 
+    let win = targetWindow || this.topWindow;
+    let isPrivate = PrivateBrowsingUtils.isWindowPrivate(win);
+    if (isPrivate && extensionId && !WebExtensionPolicy.getByID(extensionId).privateBrowsingAllowed) {
+      return;
+    }
+
     // The item should have an extension and the user shouldn't have confirmed
     // the change here, but just to be sure check that it is still controlled
     // and the user hasn't already confirmed the change.
     // If there is no id, then the extension is no longer in control.
     if (!extensionId || this.userHasConfirmed(extensionId)) {
       return;
     }
 
-    let win = targetWindow || this.topWindow;
     // If the window closes while waiting for focus, this might reject/throw,
     // and we should stop trying to show the popup.
     try {
       await this._ensureWindowReady(win);
     } catch (ex) {
       return;
     }
 
--- a/browser/components/extensions/parent/ext-windows.js
+++ b/browser/components/extensions/parent/ext-windows.js
@@ -186,18 +186,23 @@ this.windows = class extends ExtensionAP
               for (let url of createData.url) {
                 array.appendElement(mkstr(url));
               }
               args.appendElement(array);
             } else {
               args.appendElement(mkstr(createData.url));
             }
           } else {
-            let url = createData.incognito && !PrivateBrowsingUtils.permanentPrivateBrowsing ?
-              "about:privatebrowsing" : HomePage.get().split("|", 1)[0];
+            let url;
+            if (createData.incognito) {
+              url = PrivateBrowsingUtils.permanentPrivateBrowsing ?
+                HomePage.getPrivate().split("|", 1)[0] : "about:privatebrowsing";
+            } else {
+              url = HomePage.get().split("|", 1)[0];
+            }
             args.appendElement(mkstr(url));
 
             if (url.startsWith("about:") &&
                 !context.checkLoadURL(url, {dontReportErrors: true})) {
               // The extension principal cannot directly load about:-URLs,
               // except for about:blank. So use the system principal instead.
               principal = Services.scriptSecurityManager.getSystemPrincipal();
             }
--- a/browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
+++ b/browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
@@ -417,8 +417,132 @@ add_task(async function test_doorhanger_
   ok(getHomePageURL().endsWith("ext1.html"), "The homepage is still the set");
 
   await BrowserTestUtils.closeWindow(win);
   await ext1.unload();
   await ext2.unload();
 
   ok(!isConfirmed(ext1Id), "The confirmation is cleaned up on uninstall");
 });
+
+add_task(async function test_overriding_home_page_incognito_not_allowed() {
+  await SpecialPowers.pushPrefEnv({set: [["extensions.allowPrivateBrowsingByDefault", false]]});
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      chrome_settings_overrides: {"homepage": "home.html"},
+      name: "extension",
+    },
+    background() {
+      browser.test.sendMessage("url", browser.runtime.getURL("home.html"));
+    },
+    files: {"home.html": "<h1>1</h1>"},
+    useAddonManager: "temporary",
+  });
+
+  await extension.startup();
+  let url = await extension.awaitMessage("url");
+
+  let windowOpenedPromise = BrowserTestUtils.waitForNewWindow({url});
+  let win = OpenBrowserWindow();
+  await windowOpenedPromise;
+  let doc = win.document;
+  let description = doc.getElementById("extension-homepage-notification-description");
+  let panel = doc.getElementById("extension-notification-panel");
+  await promisePopupShown(panel);
+
+  let popupnotification = description.closest("popupnotification");
+  is(description.textContent,
+     "An extension,  extension, changed what you see when you open your homepage and new windows.Learn more",
+     "The extension name is in the popup");
+  is(popupnotification.hidden, false, "The expected popup notification is visible");
+
+  ok(win.gURLBar.value.endsWith("home.html"), "extension is in control");
+  await BrowserTestUtils.closeWindow(win);
+
+  // Verify a private window does not open the extension page.
+  windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  win = OpenBrowserWindow({private: true});
+  await windowOpenedPromise;
+  win.BrowserHome();
+  await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+
+  is(win.gURLBar.value, "", "home page not used in private window");
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_overriding_home_page_incognito_not_allowed_bypass() {
+  await SpecialPowers.pushPrefEnv({set: [["extensions.allowPrivateBrowsingByDefault", false]]});
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      name: "extension",
+    },
+    background() {
+      browser.test.sendMessage("url", browser.runtime.getURL("home.html"));
+    },
+    files: {"home.html": "<h1>1</h1>"},
+    useAddonManager: "temporary",
+  });
+
+  await extension.startup();
+  let url = await extension.awaitMessage("url");
+
+  // Verify manually setting the pref to the extension page does not work.
+  let changed = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
+  Services.prefs.setStringPref(HOMEPAGE_URL_PREF, url);
+  await changed;
+  let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  let win = OpenBrowserWindow({private: true});
+  await windowOpenedPromise;
+  win.BrowserHome();
+  await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+
+  is(win.gURLBar.value, "", "home page not used in private window");
+  changed = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
+  Services.prefs.clearUserPref(HOMEPAGE_URL_PREF);
+  await changed;
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_overriding_home_page_incognito_spanning() {
+  await SpecialPowers.pushPrefEnv({set: [["extensions.allowPrivateBrowsingByDefault", false]]});
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      chrome_settings_overrides: {"homepage": "home.html"},
+      name: "private extension",
+      applications: {
+        gecko: {id: "@spanning-home"},
+      },
+    },
+    files: {"home.html": "<h1>1</h1>"},
+    useAddonManager: "permanent",
+    incognitoOverride: "spanning",
+  });
+
+  await extension.startup();
+
+  let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  let win = OpenBrowserWindow({private: true});
+  await windowOpenedPromise;
+  let doc = win.document;
+  let panel = doc.getElementById("extension-notification-panel");
+
+  let popupShown = promisePopupShown(panel);
+  win.BrowserHome();
+  await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+  await popupShown;
+
+  ok(getHomePageURL().endsWith("home.html"), "The homepage is set");
+  ok(win.gURLBar.value.endsWith("home.html"), "extension is in control in private window");
+
+  let popupHidden = promisePopupHidden(panel);
+  panel.hidePopup();
+  await popupHidden;
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
@@ -1,15 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "ExtensionSettingsStore",
                                "resource://gre/modules/ExtensionSettingsStore.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+                                   "@mozilla.org/browser/aboutnewtab-service;1",
+                                   "nsIAboutNewTabService");
 
 const NEWTAB_URI_1 = "webext-newtab-1.html";
 
 function getNotificationSetting(extensionId) {
   return ExtensionSettingsStore.getSetting("newTabNotification", extensionId);
 }
 
 function getNewTabDoorhanger() {
@@ -517,8 +520,194 @@ add_task(async function dontTemporarilyS
   is(gURLBar.value, "", "URL bar value should be empty.");
   ContentTask.spawn(tab.linkedBrowser, null, function() {
     is(content.document.body.textContent, "New tab!", "New tab page is loaded.");
   });
 
   BrowserTestUtils.removeTab(tab);
   await extension.unload();
 });
+
+add_task(async function test_overriding_newtab_incognito_not_allowed() {
+  await SpecialPowers.pushPrefEnv({set: [["extensions.allowPrivateBrowsingByDefault", false]]});
+
+  let panel = getNewTabDoorhanger().closest("panel");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      chrome_url_overrides: {"newtab": "newtab.html"},
+      name: "extension",
+      applications: {
+        gecko: {id: "@not-allowed-newtab"},
+      },
+    },
+    files: {
+      "newtab.html": `
+        <!DOCTYPE html>
+        <head>
+          <meta charset="utf-8"/></head>
+        <html>
+          <body>
+            <script src="newtab.js"></script>
+          </body>
+        </html>
+      `,
+
+      "newtab.js": function() {
+        window.onload = () => {
+          browser.test.sendMessage("from-newtab-page", window.location.href);
+        };
+      },
+    },
+    useAddonManager: "permanent",
+  });
+
+  await extension.startup();
+
+  let popupShown = promisePopupShown(panel);
+  BrowserOpenTab();
+  await popupShown;
+
+  let url = await extension.awaitMessage("from-newtab-page");
+  ok(url.endsWith("newtab.html"),
+     "Newtab url is overridden by the extension.");
+
+  // This will show a confirmation doorhanger, make sure we don't leave it open.
+  let popupHidden = promisePopupHidden(panel);
+  panel.hidePopup();
+  await popupHidden;
+
+  BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  // Verify a private window does not open the extension page.  We would
+  // get an extra notification that we don't listen for if it gets loaded.
+  let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  let win = OpenBrowserWindow({private: true});
+  await windowOpenedPromise;
+
+  let newTabOpened = waitForNewTab();
+  win.BrowserOpenTab();
+  await newTabOpened;
+
+  is(win.gURLBar.value, "", "newtab not used in private window");
+
+  // Verify setting the pref directly doesn't bypass permissions.
+  let origUrl = aboutNewTabService.newTabURL;
+  aboutNewTabService.newTabURL = url;
+  newTabOpened = waitForNewTab();
+  win.BrowserOpenTab();
+  await newTabOpened;
+
+  is(win.gURLBar.value, "", "directly set newtab not used in private window");
+
+  aboutNewTabService.newTabURL = origUrl;
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_overriding_newtab_incognito_not_allowed_bypass() {
+  await SpecialPowers.pushPrefEnv({set: [["extensions.allowPrivateBrowsingByDefault", false]]});
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      name: "extension",
+      applications: {
+        gecko: {id: "@not-allowed-newtab"},
+      },
+    },
+    background() {
+      browser.test.sendMessage("url", browser.runtime.getURL("newtab.html"));
+    },
+    files: {
+      "newtab.html": `
+        <!DOCTYPE html>
+        <head>
+          <meta charset="utf-8"/></head>
+        <html>
+          <body>
+          </body>
+        </html>
+      `,
+    },
+    useAddonManager: "permanent",
+  });
+
+  await extension.startup();
+  let url = await extension.awaitMessage("url");
+
+  // Verify setting the pref directly doesn't bypass permissions.
+  let origUrl = aboutNewTabService.newTabURL;
+  aboutNewTabService.newTabURL = url;
+
+  // Verify a private window does not open the extension page.  We would
+  // get an extra notification that we don't listen for if it gets loaded.
+  let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  let win = OpenBrowserWindow({private: true});
+  await windowOpenedPromise;
+
+  let newTabOpened = waitForNewTab();
+  win.BrowserOpenTab();
+  await newTabOpened;
+
+  is(win.gURLBar.value, "", "directly set newtab not used in private window");
+
+  aboutNewTabService.newTabURL = origUrl;
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_overriding_newtab_incognito_spanning() {
+  await SpecialPowers.pushPrefEnv({set: [["extensions.allowPrivateBrowsingByDefault", false]]});
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      chrome_url_overrides: {"newtab": "newtab.html"},
+      name: "extension",
+      applications: {
+        gecko: {id: "@spanning-newtab"},
+      },
+    },
+    files: {
+      "newtab.html": `
+        <!DOCTYPE html>
+        <head>
+          <meta charset="utf-8"/></head>
+        <html>
+          <body>
+            <script src="newtab.js"></script>
+          </body>
+        </html>
+      `,
+
+      "newtab.js": function() {
+        window.onload = () => {
+          browser.test.sendMessage("from-newtab-page", window.location.href);
+        };
+      },
+    },
+    useAddonManager: "permanent",
+    incognitoOverride: "spanning",
+  });
+
+  await extension.startup();
+
+  let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  let win = OpenBrowserWindow({private: true});
+  await windowOpenedPromise;
+  let panel = win.document.getElementById("extension-new-tab-notification").closest("panel");
+  let popupShown = promisePopupShown(panel);
+  win.BrowserOpenTab();
+  await popupShown;
+
+  let url = await extension.awaitMessage("from-newtab-page");
+  ok(url.endsWith("newtab.html"),
+     "Newtab url is overridden by the extension.");
+
+  // This will show a confirmation doorhanger, make sure we don't leave it open.
+  let popupHidden = promisePopupHidden(panel);
+  panel.hidePopup();
+  await popupHidden;
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3340,17 +3340,17 @@ var SessionStoreInternal = {
     // home pages then we'll end up overwriting all of them. Otherwise we'll
     // just close the tabs that match home pages. Tabs with the about:blank
     // URI will always be overwritten.
     let homePages = ["about:blank"];
     let removableTabs = [];
     let tabbrowser = aWindow.gBrowser;
     let startupPref = this._prefBranch.getIntPref("startup.page");
     if (startupPref == 1)
-      homePages = homePages.concat(HomePage.get().split("|"));
+      homePages = homePages.concat(HomePage.get(aWindow).split("|"));
 
     for (let i = tabbrowser._numPinnedTabs; i < tabbrowser.tabs.length; i++) {
       let tab = tabbrowser.tabs[i];
       if (homePages.includes(tab.linkedBrowser.currentURI.spec)) {
         removableTabs.push(tab);
       }
     }
 
--- a/browser/modules/HomePage.jsm
+++ b/browser/modules/HomePage.jsm
@@ -5,16 +5,18 @@
 /* globals ChromeUtils, Services */
 /* exported HomePage */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["HomePage"];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
+                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 const kPrefName = "browser.startup.homepage";
 
 function getHomepagePref(useDefault) {
   let homePage;
   let prefs = Services.prefs;
   if (useDefault) {
     prefs = prefs.getDefaultBranch(null);
@@ -38,24 +40,46 @@ function getHomepagePref(useDefault) {
     Services.prefs.clearUserPref(kPrefName);
     homePage = getHomepagePref(true);
   }
 
   return homePage;
 }
 
 let HomePage = {
-  get() {
+  get(aWindow) {
+    if (PrivateBrowsingUtils.permanentPrivateBrowsing ||
+        (aWindow && PrivateBrowsingUtils.isWindowPrivate(aWindow))) {
+      return this.getPrivate();
+    }
     return getHomepagePref();
   },
 
   getDefault() {
     return getHomepagePref(true);
   },
 
+  getPrivate() {
+    let homePages = getHomepagePref();
+    if (!homePages.includes("moz-extension")) {
+      return homePages;
+    }
+    // Verify private access and build a new list.
+    let privateHomePages = homePages.split("|").filter(page => {
+      let url = new URL(page);
+      if (url.protocol !== "moz-extension:") {
+        return true;
+      }
+      let policy = WebExtensionPolicy.getByHostname(url.hostname);
+      return policy && policy.privateBrowsingAllowed;
+    });
+    // Extensions may not be ready on startup, fallback to defaults.
+    return privateHomePages.join("|") || this.getDefault();
+  },
+
   get overridden() {
     return Services.prefs.prefHasUserValue(kPrefName);
   },
 
   set(value) {
     Services.prefs.setStringPref(kPrefName, value);
   },