Bug 721211: consolidate home page retrieval code, make it support non-localized prefs, r?jaws draft
authorAxel Hecht <axel@pike.org>
Mon, 28 May 2018 12:38:01 +0200
changeset 816065 931bfe61c2e9bebc2aca903a982c571cc7db5c9a
parent 815985 a675c5d7eb76887a3e4b24548d621c9cc05a1545
push id115740
push useraxel@mozilla.com
push dateTue, 10 Jul 2018 15:09:11 +0000
reviewersjaws
bugs721211
milestone63.0a1
Bug 721211: consolidate home page retrieval code, make it support non-localized prefs, r?jaws Also changes the tooltip on the home button to be independent of the URLs it opens, per dolske. Some tests explicitly set browser.startup.homepage, but only through SpecialPowers.putPrefEnv. That's a good compromise, given the extra functionality there. MozReview-Commit-ID: FPLxzi3jQAP
browser/app/profile/firefox.js
browser/base/content/browser-customization.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/test/general/browser_homeDrop.js
browser/branding/aurora/locales/browserconfig.properties
browser/branding/aurora/locales/jar.mn
browser/branding/nightly/locales/browserconfig.properties
browser/branding/nightly/locales/jar.mn
browser/branding/official/locales/browserconfig.properties
browser/branding/official/locales/jar.mn
browser/branding/unofficial/locales/browserconfig.properties
browser/branding/unofficial/locales/jar.mn
browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js
browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
browser/components/nsBrowserContentHandler.js
browser/components/nsBrowserGlue.js
browser/components/preferences/in-content/tests/browser_extension_controlled.js
browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
browser/components/sessionstore/SessionStore.jsm
browser/modules/HomePage.jsm
browser/modules/moz.build
browser/modules/test/unit/test_HomePage.js
browser/modules/test/unit/xpcshell.ini
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
toolkit/components/extensions/parent/ext-browserSettings.js
toolkit/components/extensions/test/xpcshell/test_ext_browserSettings_homepage.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -240,17 +240,17 @@ pref("browser.shell.mostRecentDateSetAsD
 pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true);
 pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", false);
 pref("browser.shell.defaultBrowserCheckCount", 0);
 pref("browser.defaultbrowser.notificationbar", false);
 
 // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
 // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
 pref("browser.startup.page",                1);
-pref("browser.startup.homepage",            "chrome://branding/locale/browserconfig.properties");
+pref("browser.startup.homepage",            "about:home");
 // Whether we should skip the homepage when opening the first-run page
 pref("browser.startup.firstrunSkipsHomepage", true);
 
 // Show an about:blank window as early as possible for quick startup feedback.
 // Held to nightly on Linux due to bug 1450626.
 // Disabled on Mac because the bouncing dock icon already provides feedback.
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) && defined(NIGHTLY_BUILD)
 pref("browser.startup.blankWindow", true);
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -41,18 +41,16 @@ var CustomizationHandler = {
     PlacesToolbarHelper.customizeStart();
   },
 
   _customizationEnding(aDetails) {
     // Update global UI elements that may have been added or removed
     if (aDetails.changed) {
       gURLBar = document.getElementById("urlbar");
 
-      gHomeButton.updateTooltip();
-
       if (AppConstants.platform != "macosx")
         updateEditUIVisibility();
 
       // Hacky: update the PopupNotifications' object's reference to the iconBox,
       // if it already exists, since it may have changed if the URL bar was
       // added/removed.
       if (!window.__lookupGetter__("PopupNotifications")) {
         PopupNotifications.iconBox =
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -25,16 +25,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
   CustomizableUI: "resource:///modules/CustomizableUI.jsm",
   Deprecated: "resource://gre/modules/Deprecated.jsm",
   DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
   E10SUtils: "resource://gre/modules/E10SUtils.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
   LanguagePrompt: "resource://gre/modules/LanguagePrompt.jsm",
+  HomePage: "resource:///modules/HomePage.jsm",
   LightweightThemeConsumer: "resource://gre/modules/LightweightThemeConsumer.jsm",
   LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   Log: "resource://gre/modules/Log.jsm",
   LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
@@ -1467,21 +1468,16 @@ var gBrowserInit = {
     PanelUI.init();
 
     UpdateUrlbarSearchSplitterState();
 
     BookmarkingUI.init();
     BrowserSearch.delayedStartupInit();
     AutoShowBookmarksToolbar.init();
 
-    Services.prefs.addObserver(gHomeButton.prefDomain, gHomeButton);
-
-    var homeButton = document.getElementById("home-button");
-    gHomeButton.updateTooltip(homeButton);
-
     let safeMode = document.getElementById("helpSafeMode");
     if (Services.appinfo.inSafeMode) {
       safeMode.label = safeMode.getAttribute("stoplabel");
       safeMode.accesskey = safeMode.getAttribute("stopaccesskey");
     }
 
     // BiDi UI
     gBidiUI = isBidiEnabled();
@@ -1918,22 +1914,16 @@ var gBrowserInit = {
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-origin-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-confirmation");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
       window.messageManager.removeMessageListener("Browser:URIFixup", gKeywordURIFixup);
       window.messageManager.removeMessageListener("Browser:LoadURI", RedirectLoad);
 
-      try {
-        Services.prefs.removeObserver(gHomeButton.prefDomain, gHomeButton);
-      } catch (ex) {
-        Cu.reportError(ex);
-      }
-
       if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
         MenuTouchModeObserver.uninit();
       }
       BrowserOffline.uninit();
       IndexedDBPromptHelper.uninit();
       CanvasPermissionPromptHelper.uninit();
       WebAuthnPromptHelper.uninit();
       PanelUI.uninit();
@@ -2231,17 +2221,17 @@ function BrowserReloadSkipCache() {
 }
 
 var BrowserHome = BrowserGoHome;
 function BrowserGoHome(aEvent) {
   if (aEvent && "button" in aEvent &&
       aEvent.button == 2) // right-click: do nothing
     return;
 
-  var homePage = gHomeButton.getHomePage();
+  var homePage = HomePage.get();
   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)
@@ -3308,29 +3298,23 @@ function goBackFromErrorPage() {
   }
 }
 
 /**
  * Return the default start page for the cases when the user's own homepage is
  * infected, so we can get them somewhere safe.
  */
 function getDefaultHomePage() {
-  // Get the start page from the *default* pref branch, not the user's
-  var prefs = Services.prefs.getDefaultBranch(null);
-  var url = BROWSER_NEW_TAB_URL;
+  let url = BROWSER_NEW_TAB_URL;
   if (PrivateBrowsingUtils.isWindowPrivate(window))
     return url;
-  try {
-    url = prefs.getComplexValue("browser.startup.homepage",
-                                Ci.nsIPrefLocalizedString).data;
-    // If url is a pipe-delimited set of pages, just take the first one.
-    if (url.includes("|"))
-      url = url.split("|")[0];
-  } catch (e) {
-    Cu.reportError("Couldn't get homepage pref: " + e);
+  url = HomePage.getDefault();
+  // If url is a pipe-delimited set of pages, just take the first one.
+  if (url.includes("|")) {
+    url = url.split("|")[0];
   }
   return url;
 }
 
 function BrowserFullScreen() {
   window.fullScreen = !window.fullScreen;
 }
 
@@ -3663,17 +3647,17 @@ function openHomeDialog(aURL) {
   }
 
   var pressedVal  = Services.prompt.confirmEx(window, promptTitle, promptMsg,
                           Services.prompt.STD_YES_NO_BUTTONS,
                           null, null, null, null, {value: 0});
 
   if (pressedVal == 0) {
     try {
-      Services.prefs.setStringPref("browser.startup.homepage", aURL);
+      HomePage.set(aURL);
     } catch (ex) {
       dump("Failed to set the home page.\n" + ex + "\n");
     }
   }
 }
 
 var newTabButtonObserver = {
   onDragOver(aEvent) {
@@ -4432,17 +4416,17 @@ function OpenBrowserWindow(options) {
   } else {
     // forget about the charset information.
     win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs);
   }
 
   win.addEventListener("MozAfterPaint", () => {
     TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
     if (Services.prefs.getIntPref("browser.startup.page") == 1
-        && defaultArgs == handler.startPage) {
+        && defaultArgs == HomePage.get()) {
       // A notification for when a user has triggered their homepage. This is used
       // to display a doorhanger explaining that an extension has modified the
       // homepage, if necessary.
       Services.obs.notifyObservers(win, "browser-open-homepage-start");
     }
   }, {once: true});
 
   return win;
@@ -5932,57 +5916,16 @@ var gUIDensity = {
       }
     }
 
     TabsInTitlebar.update();
     gBrowser.tabContainer.uiDensityChanged();
   },
 };
 
-var gHomeButton = {
-  prefDomain: "browser.startup.homepage",
-  observe(aSubject, aTopic, aPrefName) {
-    if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
-      return;
-
-    this.updateTooltip();
-  },
-
-  updateTooltip(homeButton) {
-    if (!homeButton)
-      homeButton = document.getElementById("home-button");
-    if (homeButton) {
-      var homePage = this.getHomePage();
-      homePage = homePage.replace(/\|/g, ", ");
-      if (["about:home", "about:newtab"].includes(homePage.toLowerCase()))
-        homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip"));
-      else
-        homeButton.setAttribute("tooltiptext", homePage);
-    }
-  },
-
-  getHomePage() {
-    var url;
-    try {
-      url = Services.prefs.getComplexValue(this.prefDomain,
-                                  Ci.nsIPrefLocalizedString).data;
-    } catch (e) {
-    }
-
-    // use this if we can't find the pref
-    if (!url) {
-      var configBundle = Services.strings
-                                 .createBundle("chrome://branding/locale/browserconfig.properties");
-      url = configBundle.GetStringFromName(this.prefDomain);
-    }
-
-    return url;
-  },
-};
-
 const nodeToTooltipMap = {
   "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
   "context-reload": "reloadButton.tooltip",
   "context-stop": "stopButton.tooltip",
   "downloads-button": "downloads.tooltip",
   "fullscreen-button": "fullscreenButton.tooltip",
   "appMenu-fullscreen-button": "fullscreenButton.tooltip",
   "new-window-button": "newWindowButton.tooltip",
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -761,17 +761,17 @@
                        label="&homeButton.label;"
                        ondragover="homeButtonObserver.onDragOver(event)"
                        ondragenter="homeButtonObserver.onDragOver(event)"
                        ondrop="homeButtonObserver.onDrop(event)"
                        ondragexit="homeButtonObserver.onDragExit(event)"
                        key="goHome"
                        onclick="BrowserGoHome(event);"
                        cui-areatype="toolbar"
-                       aboutHomeOverrideTooltip="&homeButton.defaultPage.tooltip;"/>
+                       tooltiptext="&homeButton.defaultPage.tooltip;"/>
         <toolbarspring cui-areatype="toolbar" class="chromeclass-toolbar-additional"/>
         <toolbaritem id="urlbar-container" flex="400" persist="width"
                      removable="false"
                      class="chromeclass-location" overflows="false">
             <textbox id="urlbar" flex="1"
                      placeholder="&urlbar.placeholder2;"
                      defaultPlaceholder="&urlbar.placeholder2;"
                      focused="true"
--- a/browser/base/content/test/general/browser_homeDrop.js
+++ b/browser/base/content/test/general/browser_homeDrop.js
@@ -1,18 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 add_task(async function() {
   let HOMEPAGE_PREF = "browser.startup.homepage";
 
-  let homepageStr = Cc["@mozilla.org/supports-string;1"]
-                    .createInstance(Ci.nsISupportsString);
-  homepageStr.data = "about:mozilla";
-  await pushPrefs([HOMEPAGE_PREF, homepageStr, Ci.nsISupportsString]);
+  await pushPrefs([HOMEPAGE_PREF, "about:mozilla"]);
 
   let EventUtils = {};
   Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
   // Since synthesizeDrop triggers the srcElement, need to use another button.
   let dragSrcElement = document.getElementById("downloads-button");
   ok(dragSrcElement, "Downloads button exists");
   let homeButton = document.getElementById("home-button");
deleted file mode 100644
--- a/browser/branding/aurora/locales/browserconfig.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# Do NOT localize or otherwise change these values
-browser.startup.homepage=about:home
--- a/browser/branding/aurora/locales/jar.mn
+++ b/browser/branding/aurora/locales/jar.mn
@@ -6,9 +6,8 @@
 [localization] @AB_CD@.jar:
   branding                                          (en-US/**/*.ftl)
 
 @AB_CD@.jar:
 % locale branding @AB_CD@ %locale/branding/
 # Aurora branding only exists in en-US
   locale/branding/brand.dtd        (en-US/brand.dtd)
   locale/branding/brand.properties (en-US/brand.properties)
-  locale/branding/browserconfig.properties
deleted file mode 100644
--- a/browser/branding/nightly/locales/browserconfig.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# Do NOT localize or otherwise change these values
-browser.startup.homepage=about:home
--- a/browser/branding/nightly/locales/jar.mn
+++ b/browser/branding/nightly/locales/jar.mn
@@ -6,9 +6,8 @@
 [localization] @AB_CD@.jar:
   branding                                          (en-US/**/*.ftl)
 
 @AB_CD@.jar:
 % locale branding @AB_CD@ %locale/branding/
 # Nightly branding only exists in en-US
   locale/branding/brand.dtd        (en-US/brand.dtd)
   locale/branding/brand.properties (en-US/brand.properties)
-  locale/branding/browserconfig.properties
deleted file mode 100644
--- a/browser/branding/official/locales/browserconfig.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# Do NOT localize or otherwise change these values
-browser.startup.homepage=about:home
--- a/browser/branding/official/locales/jar.mn
+++ b/browser/branding/official/locales/jar.mn
@@ -5,9 +5,8 @@
 
 [localization] @AB_CD@.jar:
   branding                                          (%*.ftl)
 
 @AB_CD@.jar:
 % locale branding @AB_CD@ %locale/branding/
   locale/branding/brand.dtd        (%brand.dtd)
   locale/branding/brand.properties (%brand.properties)
-  locale/branding/browserconfig.properties
deleted file mode 100644
--- a/browser/branding/unofficial/locales/browserconfig.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# Do NOT localize or otherwise change these values
-browser.startup.homepage=about:home
--- a/browser/branding/unofficial/locales/jar.mn
+++ b/browser/branding/unofficial/locales/jar.mn
@@ -6,9 +6,8 @@
 [localization] @AB_CD@.jar:
   branding                                          (en-US/**/*.ftl)
 
 @AB_CD@.jar:
 % locale branding @AB_CD@ %locale/branding/
 # Unofficial branding only exists in en-US
   locale/branding/brand.dtd        (en-US/brand.dtd)
   locale/branding/brand.properties (en-US/brand.properties)
-  locale/branding/browserconfig.properties
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js
@@ -1,22 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+ChromeUtils.defineModuleGetter(this, "HomePage",
+                               "resource:///modules/HomePage.jsm");
+
 registerCleanupFunction(function restore_pref_values() {
   // These two prefs are set as user prefs in case the "Locked"
   // option from this policy was not used. In this case, it won't
   // be tracked nor restored by the PoliciesPrefTracker.
   Services.prefs.clearUserPref("browser.startup.homepage");
   Services.prefs.clearUserPref("browser.startup.page");
 });
 
 async function check_homepage({expectedURL, expectedPageVal = 1, locked = false}) {
-  is(gHomeButton.getHomePage(),
+  is(HomePage.get(),
      expectedURL, "Homepage URL should match expected");
   is(Services.prefs.getIntPref("browser.startup.page", -1), expectedPageVal,
      "Pref page value should match expected");
   is(Services.prefs.prefIsLocked("browser.startup.homepage"), locked,
      "Lock status of browser.startup.homepage should match expected");
   is(Services.prefs.prefIsLocked("browser.startup.page"), locked,
      "Lock status of browser.startup.page should match expected");
 
--- 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
@@ -17,18 +17,17 @@ const HOME_URI_4 = "http://example.net/"
 
 const CONTROLLED_BY_THIS = "controlled_by_this_extension";
 const CONTROLLED_BY_OTHER = "controlled_by_other_extensions";
 const NOT_CONTROLLABLE = "not_controllable";
 
 const HOMEPAGE_URL_PREF = "browser.startup.homepage";
 
 const getHomePageURL = () => {
-  return Services.prefs.getComplexValue(
-    HOMEPAGE_URL_PREF, Ci.nsIPrefLocalizedString).data;
+  return Services.prefs.getStringPref(HOMEPAGE_URL_PREF);
 };
 
 function isConfirmed(id) {
   let item = ExtensionSettingsStore.getSetting("homepageNotification", id);
   return !!(item && item.value);
 }
 
 add_task(async function test_multiple_extensions_overriding_home_page() {
--- a/browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
@@ -1,13 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
+ChromeUtils.import("resource:///modules/HomePage.jsm");
 
 const {
   createAppInfo,
   promiseShutdownManager,
   promiseStartupManager,
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
@@ -20,25 +21,20 @@ add_task(async function test_overrides_u
    * search_provider are removed between updates and therefore the
    * settings are expected to revert. */
 
   const EXTENSION_ID = "test_overrides_update@tests.mozilla.org";
   const HOMEPAGE_URI = "webext-homepage-1.html";
 
   const HOMEPAGE_URL_PREF = "browser.startup.homepage";
 
-  const getHomePageURL = () => {
-    return Services.prefs.getComplexValue(
-      HOMEPAGE_URL_PREF, Ci.nsIPrefLocalizedString).data;
-  };
-
   function promisePrefChanged(value) {
     return new Promise((resolve, reject) => {
       Services.prefs.addObserver(HOMEPAGE_URL_PREF, function observer() {
-        if (getHomePageURL().endsWith(value)) {
+        if (HomePage.get().endsWith(value)) {
           Services.prefs.removeObserver(HOMEPAGE_URL_PREF, observer);
           resolve();
         }
       });
     });
   }
 
   await promiseStartupManager();
@@ -59,25 +55,25 @@ add_task(async function test_overrides_u
           "search_url": "https://example.com/?q={searchTerms}",
           "is_default": true,
         },
       },
     },
   };
   let extension = ExtensionTestUtils.loadExtension(extensionInfo);
 
-  let defaultHomepageURL = getHomePageURL();
+  let defaultHomepageURL = HomePage.get();
   let defaultEngineName = Services.search.currentEngine.name;
 
   let prefPromise = promisePrefChanged(HOMEPAGE_URI);
   await extension.startup();
   await prefPromise;
 
   equal(extension.version, "1.0", "The installed addon has the expected version.");
-  ok(getHomePageURL().endsWith(HOMEPAGE_URI),
+  ok(HomePage.get().endsWith(HOMEPAGE_URI),
      "Home page url is overridden by the extension.");
   equal(Services.search.currentEngine.name,
         "DuckDuckGo",
         "Default engine is overridden by the extension");
 
   extensionInfo.manifest = {
     "version": "2.0",
     "applications": {
@@ -87,17 +83,17 @@ add_task(async function test_overrides_u
     },
   };
 
   prefPromise = promisePrefChanged(defaultHomepageURL);
   await extension.upgrade(extensionInfo);
   await prefPromise;
 
   equal(extension.version, "2.0", "The updated addon has the expected version.");
-  equal(getHomePageURL(),
+  equal(HomePage.get(),
         defaultHomepageURL,
         "Home page url reverted to the default after update.");
   equal(Services.search.currentEngine.name,
         defaultEngineName,
         "Default engine reverted to the default after update.");
 
   await extension.unload();
 
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -4,16 +4,17 @@
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   HeadlessShell: "resource:///modules/HeadlessShell.jsm",
+  HomePage: "resource:///modules/HomePage.jsm",
   LaterRun: "resource:///modules/LaterRun.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ShellService: "resource:///modules/ShellService.jsm",
   UpdatePing: "resource://gre/modules/UpdatePing.jsm"
 });
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
   "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 
@@ -549,17 +550,17 @@ nsBrowserContentHandler.prototype = {
         overridePage = additionalPage;
       }
     }
 
     var startPage = "";
     try {
       var choice = prefb.getIntPref("browser.startup.page");
       if (choice == 1 || choice == 3)
-        startPage = this.startPage;
+        startPage = HomePage.get();
     } catch (e) {
       Cu.reportError(e);
     }
 
     if (startPage == "about:blank")
       startPage = "";
 
     let skipStartPage = override == OVERRIDE_NEW_PROFILE &&
@@ -567,27 +568,16 @@ 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 startPage() {
-    var uri = Services.prefs.getComplexValue("browser.startup.homepage",
-                                             Ci.nsIPrefLocalizedString).data;
-    if (!uri) {
-      Services.prefs.clearUserPref("browser.startup.homepage");
-      uri = Services.prefs.getComplexValue("browser.startup.homepage",
-                                           Ci.nsIPrefLocalizedString).data;
-    }
-    return uri;
-  },
-
   mFeatures: null,
 
   getFeatures: function bch_features(cmdLine) {
     if (this.mFeatures === null) {
       this.mFeatures = "";
 
       try {
         var width = cmdLine.handleFlagWithParam("width", false);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -101,16 +101,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
   CustomizableUI: "resource:///modules/CustomizableUI.jsm",
   DateTimePickerParent: "resource://gre/modules/DateTimePickerParent.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   Feeds: "resource:///modules/Feeds.jsm",
   FileSource: "resource://gre/modules/L10nRegistry.jsm",
   FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
   FxAccounts: "resource://gre/modules/FxAccounts.jsm",
+  HomePage: "resource:///modules/HomePage.jsm",
   HybridContentTelemetry: "resource://gre/modules/HybridContentTelemetry.jsm",
   Integration: "resource://gre/modules/Integration.jsm",
   L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
   LanguagePrompt: "resource://gre/modules/LanguagePrompt.jsm",
   LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   LoginHelper: "resource://gre/modules/LoginHelper.jsm",
   LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
@@ -397,18 +398,17 @@ BrowserGlue.prototype = {
     } else if (aboutNewTabService.newTabURL.startsWith("moz-extension://")) {
       newTabSetting = 2;
     } else if (!Services.prefs.getBoolPref("browser.newtabpage.enabled")) {
       newTabSetting = 1;
     } else {
       newTabSetting = 3;
     }
 
-    const homePageURL = Services.prefs.getComplexValue("browser.startup.homepage",
-                                                       Ci.nsIPrefLocalizedString).data;
+    const homePageURL = HomePage.get();
     if (homePageURL === "about:home") {
       homePageSetting = 0;
     } else if (homePageURL === "about:blank") {
       homePageSetting = 1;
     } else if (homePageURL.startsWith("moz-extension://")) {
       homePageSetting = 2;
     } else {
       homePageSetting = 3;
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -261,18 +261,19 @@ add_task(async function testPrefLockedHo
   is(homePageInput.value, "", "The homepage is empty");
   is(homePageInput.disabled, false, "The homepage is enabled after clearing lock");
   is(homeModeEl.disabled, false, "Homepage menulist is enabled after clearing lock");
   buttonPrefs.forEach(pref => {
     is(getButton(pref).disabled, false, `The ${pref} button is enabled when unlocked`);
   });
 
   // Lock the prefs without an extension.
+  let mutationsDone = waitForAllMutations();
   lockPrefs();
-  await waitForAllMutations();
+  await mutationsDone;
 
   // Check that everything is now disabled.
   is(getHomepage(), lockedHomepage, "The reported homepage is set by the pref");
   is(homePageInput.value, lockedHomepage, "The homepage is set by the pref");
   is(homePageInput.disabled, true, "The homepage is disabed when the pref is locked");
   is(homeModeEl.disabled, true, "Homepage menulist is disabled when prefis locked");
   buttonPrefs.forEach(pref => {
     is(getButton(pref).disabled, true, `The ${pref} button is disabled when locked`);
--- a/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
+++ b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
@@ -1,21 +1,23 @@
+ChromeUtils.import("resource:///modules/HomePage.jsm");
+
 add_task(async function testSetHomepageUseCurrent() {
   is(gBrowser.currentURI.spec, "about:blank", "Test starts with about:blank open");
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
   await openPreferencesViaOpenPreferencesAPI("paneHome", {leaveOpen: true});
   // eslint-disable-next-line mozilla/no-cpows-in-tests
   let doc = gBrowser.contentDocument;
   is(gBrowser.currentURI.spec, "about:preferences#home",
      "#home should be in the URI for about:preferences");
-  let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
+  let oldHomepage = HomePage.get();
 
   let useCurrent = doc.getElementById("useCurrentBtn");
   useCurrent.click();
 
   is(gBrowser.tabs.length, 3, "Three tabs should be open");
-  is(Services.prefs.getCharPref("browser.startup.homepage"), "about:blank|about:home",
+  is(HomePage.get(), "about:blank|about:home",
      "about:blank and about:home should be the only homepages set");
 
-  Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
+  HomePage.set(oldHomepage);
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -169,16 +169,17 @@ XPCOMUtils.defineLazyServiceGetters(this
 });
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm",
   GlobalState: "resource:///modules/sessionstore/GlobalState.jsm",
+  HomePage: "resource:///modules/HomePage.jsm",
   PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   RunState: "resource:///modules/sessionstore/RunState.jsm",
   SessionCookies: "resource:///modules/sessionstore/SessionCookies.jsm",
   SessionFile: "resource:///modules/sessionstore/SessionFile.jsm",
   SessionSaver: "resource:///modules/sessionstore/SessionSaver.jsm",
   TabAttributes: "resource:///modules/sessionstore/TabAttributes.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
@@ -3128,17 +3129,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(aWindow.gHomeButton.getHomePage().split("|"));
+      homePages = homePages.concat(HomePage.get().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);
       }
     }
 
new file mode 100644
--- /dev/null
+++ b/browser/modules/HomePage.jsm
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals ChromeUtils, Services */
+/* exported HomePage */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["HomePage"];
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const kPrefName = "browser.startup.homepage";
+
+function getHomepagePref(useDefault) {
+  let homePage;
+  let prefs = Services.prefs;
+  if (useDefault) {
+    prefs = prefs.getDefaultBranch(null);
+  }
+  try {
+    // Historically, this was a localizable pref, but default Firefox builds
+    // don't use this.
+    // Distributions and local customizations might still use this, so let's
+    // keep it.
+    homePage = prefs.getComplexValue(kPrefName,
+                                     Ci.nsIPrefLocalizedString).data;
+  } catch (ex) {}
+
+  if (!homePage) {
+    homePage = prefs.getStringPref(kPrefName);
+  }
+
+  // Apparently at some point users ended up with blank home pages somehow.
+  // If that happens, reset the pref and read it again.
+  if (!homePage && !useDefault) {
+    Services.prefs.clearUserPref(kPrefName);
+    homePage = getHomepagePref(true);
+  }
+
+  return homePage;
+}
+
+let HomePage = {
+  get() {
+    return getHomepagePref();
+  },
+
+  getDefault() {
+    return getHomepagePref(true);
+  },
+
+  get overridden() {
+    return Services.prefs.prefHasUserValue(kPrefName);
+  },
+
+  set(value) {
+    Services.prefs.setStringPref(kPrefName, value);
+  },
+
+  reset() {
+    Services.prefs.clearUserPref(kPrefName);
+  }
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -152,16 +152,17 @@ EXTRA_JS_MODULES += [
     'ContentObservers.js',
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
     'ContextMenu.jsm',
     'ExtensionsUI.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
+    'HomePage.jsm',
     'LaterRun.jsm',
     'LightweightThemeChildListener.jsm',
     'LightWeightThemeWebInstallListener.jsm',
     'NetErrorContent.jsm',
     'OpenInTabsUtils.jsm',
     'PageActions.jsm',
     'PageInfoListener.jsm',
     'PageStyleHandler.jsm',
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/test_HomePage.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* globals ChromeUtils, Services, Cc, Ci, HomePage, Assert, add_task */
+"use strict";
+
+ChromeUtils.import("resource:///modules/HomePage.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+add_task(function testHomePage() {
+  Assert.ok(!HomePage.overridden, "Homepage should not be overriden by default.");
+  let newvalue = "about:blank|about:newtab";
+  HomePage.set(newvalue);
+  Assert.ok(HomePage.overridden, "Homepage should be overriden after set()");
+  Assert.equal(HomePage.get(), newvalue, "Homepage should be ${newvalue}");
+  Assert.notEqual(HomePage.getDefault(), newvalue, "Homepage should be ${newvalue}");
+  HomePage.reset();
+  Assert.ok(!HomePage.overridden, "Homepage should not be overriden by after reset.");
+  Assert.equal(HomePage.get(), HomePage.getDefault(),
+               "Homepage and default should be equal after reset.");
+});
+
+add_task(function readLocalizedHomepage() {
+  let newvalue = "data:text/plain,browser.startup.homepage%3Dabout%3Alocalized";
+  let complexvalue = Cc["@mozilla.org/pref-localizedstring;1"]
+                   .createInstance(Ci.nsIPrefLocalizedString);
+  complexvalue.data = newvalue;
+  Services.prefs.getDefaultBranch(null).setComplexValue(
+    "browser.startup.homepage",
+    Ci.nsIPrefLocalizedString,
+    complexvalue);
+    Assert.ok(!HomePage.overridden, "Complex value only works as default");
+    Assert.equal(HomePage.get(), "about:localized", "Get value from bundle");
+});
+
+add_task(function recoverEmptyHomepage() {
+  Assert.ok(!HomePage.overridden, "Homepage should not be overriden by default.");
+  Services.prefs.setStringPref("browser.startup.homepage", "");
+  Assert.ok(HomePage.overridden, "Homepage is overriden with empty string.");
+  Assert.equal(HomePage.get(), HomePage.getDefault(), "Recover is default");
+  Assert.ok(!HomePage.overridden, "Recover should have set default");
+});
--- a/browser/modules/test/unit/xpcshell.ini
+++ b/browser/modules/test/unit/xpcshell.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 head =
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 
 [test_AttributionCode.js]
 skip-if = os != 'win'
 [test_E10SUtils_nested_URIs.js]
+[test_HomePage.js]
 [test_Sanitizer_interrupted.js]
 [test_SitePermissions.js]
 [test_LaterRun.js]
--- a/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
+++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
@@ -30,17 +30,16 @@ class BrowserWindow(BaseWindow):
         'chrome://branding/locale/brand.dtd',
         'chrome://browser/locale/aboutPrivateBrowsing.dtd',
         'chrome://browser/locale/browser.dtd',
         'chrome://browser/locale/netError.dtd',
     ]
 
     properties = [
         'chrome://branding/locale/brand.properties',
-        'chrome://branding/locale/browserconfig.properties',
         'chrome://browser/locale/browser.properties',
         'chrome://browser/locale/preferences/preferences.properties',
         'chrome://global/locale/browser.properties',
     ]
 
     def __init__(self, *args, **kwargs):
         super(BrowserWindow, self).__init__(*args, **kwargs)
 
@@ -48,18 +47,17 @@ class BrowserWindow(BaseWindow):
         self._tabbar = None
 
     @property
     def default_homepage(self):
         """The default homepage as used by the current locale.
 
         :returns: The default homepage for the current locale.
         """
-        return self.marionette.get_pref('browser.startup.homepage',
-                                        value_type='nsIPrefLocalizedString')
+        return self.marionette.get_pref('browser.startup.homepage')
 
     @property
     def is_private(self):
         """Returns True if this is a Private Browsing window."""
         self.switch_to()
 
         with self.marionette.using_context('chrome'):
             return self.marionette.execute_script("""
--- a/toolkit/components/extensions/parent/ext-browserSettings.js
+++ b/toolkit/components/extensions/parent/ext-browserSettings.js
@@ -212,18 +212,17 @@ this.browserSettings = class extends Ext
               return ExtensionPreferencesManager.setSetting(
                 extension.id, "contextMenuShowEvent", details.value);
             },
           }
         ),
         homepageOverride: getSettingsAPI(
           extension.id, HOMEPAGE_OVERRIDE_SETTING,
           () => {
-            return Services.prefs.getComplexValue(
-              HOMEPAGE_URL_PREF, Ci.nsIPrefLocalizedString).data;
+            return Services.prefs.getStringPref(HOMEPAGE_URL_PREF);
           }, undefined, true),
         imageAnimationBehavior: getSettingsAPI(
           extension.id, "imageAnimationBehavior",
           () => {
             return Services.prefs.getCharPref("image.animation_mode");
           }),
         newTabPosition: getSettingsAPI(
           extension.id, "newTabPosition",
--- a/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings_homepage.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings_homepage.js
@@ -10,18 +10,17 @@ add_task(async function test_homepage_ge
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       permissions: ["browserSettings"],
     },
   });
 
-  let defaultHomepage = Services.prefs.getComplexValue(
-    "browser.startup.homepage", Ci.nsIPrefLocalizedString).data;
+  let defaultHomepage = Services.prefs.getStringPref("browser.startup.homepage");
 
   await extension.startup();
   let homepage = await extension.awaitMessage("homepage");
   equal(homepage.value, defaultHomepage,
         "The homepageOverride setting has the expected value.");
   equal(homepage.levelOfControl, "not_controllable",
         "The homepageOverride setting has the expected levelOfControl.");