1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
9 ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
10 ExtensionPreferencesManager:
11 "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
12 IgnoreLists: "resource://gre/modules/IgnoreLists.sys.mjs",
13 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
16 const kPrefName = "browser.startup.homepage";
17 const kDefaultHomePage = "about:home";
18 const kExtensionControllerPref =
19 "browser.startup.homepage_override.extensionControlled";
20 const kHomePageIgnoreListId = "homepage-urls";
21 const kWidgetId = "home-button";
22 const kWidgetRemovedPref = "browser.engagement.home-button.has-removed";
24 function getHomepagePref(useDefault) {
26 let prefs = Services.prefs;
28 prefs = prefs.getDefaultBranch(null);
31 // Historically, this was a localizable pref, but default Firefox builds
33 // Distributions and local customizations might still use this, so let's
35 homePage = prefs.getComplexValue(kPrefName, Ci.nsIPrefLocalizedString).data;
39 homePage = prefs.getStringPref(kPrefName);
42 // Apparently at some point users ended up with blank home pages somehow.
43 // If that happens, reset the pref and read it again.
44 if (!homePage && !useDefault) {
45 Services.prefs.clearUserPref(kPrefName);
46 homePage = getHomepagePref(true);
53 * HomePage provides tools to keep track of the current homepage, and the
54 * applications's default homepage. It includes tools to insure that certain
55 * urls are ignored. As a result, all set/get requests for the homepage
56 * preferences should be routed through here.
58 export let HomePage = {
59 // This is an array of strings that should be matched against URLs to see
60 // if they should be ignored or not.
63 // A promise that is set when initialization starts and resolved when it
65 _initializationPromise: null,
68 * Used to initialise the ignore lists. This may be called later than
69 * the first call to get or set, which may cause a used to get an ignored
70 * homepage, but this is deemed acceptable, as we'll correct it once
73 async delayedStartup() {
74 if (this._initializationPromise) {
75 await this._initializationPromise;
79 Services.telemetry.setEventRecordingEnabled("homepage", true);
81 // Now we have the values, listen for future updates.
82 this._ignoreListListener = this._handleIgnoreListUpdated.bind(this);
84 this._initializationPromise = lazy.IgnoreLists.getAndSubscribe(
85 this._ignoreListListener
88 this._addCustomizableUiListener();
90 const current = await this._initializationPromise;
92 await this._handleIgnoreListUpdated({ data: { current } });
96 * Gets the homepage for the given window.
98 * @param {DOMWindow} [aWindow]
99 * The window associated with the get, used to check for private browsing
100 * mode. If not supplied, normal mode is assumed.
102 * Returns the home page value, this could be a single url, or a `|`
103 * separated list of URLs.
106 let homePages = getHomepagePref();
108 lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
109 (aWindow && lazy.PrivateBrowsingUtils.isWindowPrivate(aWindow))
111 // If an extension controls the setting and does not have private
112 // browsing permission, use the default setting.
113 let extensionControlled = Services.prefs.getBoolPref(
114 kExtensionControllerPref,
117 let privateAllowed = Services.prefs.getBoolPref(
118 "browser.startup.homepage_override.privateAllowed",
121 // There is a potential on upgrade that the prefs are not set yet, so we double check
122 // for moz-extension.
125 (extensionControlled || homePages.includes("moz-extension://"))
127 return this.getDefault();
131 if (homePages == "about:blank") {
132 homePages = "chrome://browser/content/blanktab.html";
140 * Returns the application default homepage.
143 return getHomepagePref(true);
148 * Returns the original application homepage URL (not from prefs).
150 getOriginalDefault() {
151 return kDefaultHomePage;
156 * Returns true if the homepage has been changed.
159 return Services.prefs.prefHasUserValue(kPrefName);
164 * Returns true if the homepage preference is locked.
167 return Services.prefs.prefIsLocked(kPrefName);
172 * Returns true if the current homepage is the application default.
175 return HomePage.get() === kDefaultHomePage;
179 * Sets the homepage preference to a new page.
181 * @param {string} value
182 * The new value to set the preference to. This could be a single url, or a
183 * `|` separated list of URLs.
186 await this.delayedStartup();
188 if (await this.shouldIgnore(value)) {
190 `Ignoring homepage setting for ${value} as it is on the ignore list.`
192 Services.telemetry.recordEvent(
200 Services.prefs.setStringPref(kPrefName, value);
201 this._maybeAddHomeButtonToToolbar(value);
206 * Sets the homepage preference to a new page. This is an synchronous version
207 * that should only be used when we know the source is safe as it bypasses the
208 * ignore list, e.g. when setting directly to about:blank or a value not
209 * supplied externally.
211 * @param {string} value
212 * The new value to set the preference to. This could be a single url, or a
213 * `|` separated list of URLs.
216 Services.prefs.setStringPref(kPrefName, value);
220 * Clears the homepage preference if it is not the default. Note that for
221 * policy/locking use, the default homepage might not be about:home after this.
224 Services.prefs.clearUserPref(kPrefName);
228 * Resets the homepage preference to be about:home.
231 Services.prefs.setStringPref(kPrefName, kDefaultHomePage);
235 * Determines if a url should be ignored according to the ignore list.
237 * @param {string} url
238 * A string that is the url or urls to be ignored.
240 * True if the url should be ignored.
242 async shouldIgnore(url) {
243 await this.delayedStartup();
245 const lowerURL = url.toLowerCase();
246 return this._ignoreList.some(code => lowerURL.includes(code.toLowerCase()));
250 * Handles updates of the ignore list, checking the existing preference and
251 * correcting it as necessary.
253 * @param {Object} eventData
254 * The event data as received from RemoteSettings.
256 async _handleIgnoreListUpdated({ data: { current } }) {
257 for (const entry of current) {
258 if (entry.id == kHomePageIgnoreListId) {
259 this._ignoreList = [...entry.matches];
263 // Only check if we're overridden as we assume the default value is fine,
264 // or won't be changeable (e.g. enterprise policy).
265 if (this.overridden) {
266 let homePages = getHomepagePref().toLowerCase();
268 this._ignoreList.some(code => homePages.includes(code.toLowerCase()))
270 if (Services.prefs.getBoolPref(kExtensionControllerPref, false)) {
271 if (Services.appinfo.inSafeMode) {
272 // Add-ons don't get started in safe mode, so just abort this.
273 // We'll get to remove them when we next start in normal mode.
276 // getSetting does not need the module to be loaded.
277 const item = await lazy.ExtensionPreferencesManager.getSetting(
280 if (item && item.id) {
281 // During startup some modules may not be loaded yet, so we load
282 // the setting we need prior to removal.
283 await lazy.ExtensionParent.apiManager.asyncLoadModule(
284 "chrome_settings_overrides"
286 lazy.ExtensionPreferencesManager.removeSetting(
289 ).catch(console.error);
291 // If we don't have a setting for it, we assume the pref has
292 // been incorrectly set somehow.
293 Services.prefs.clearUserPref(kExtensionControllerPref);
294 Services.prefs.clearUserPref(
295 "browser.startup.homepage_override.privateAllowed"
301 Services.telemetry.recordEvent(
311 onWidgetRemoved(widgetId) {
312 if (widgetId == kWidgetId) {
313 Services.prefs.setBoolPref(kWidgetRemovedPref, true);
314 lazy.CustomizableUI.removeListener(this);
319 * Add the home button to the toolbar if the user just set a custom homepage.
321 * This should only be done once, so we check HOME_BUTTON_REMOVED_PREF which
322 * gets set to true when the home button is removed from the toolbar.
324 * If the home button is already on the toolbar it won't be moved.
326 _maybeAddHomeButtonToToolbar(homePage) {
328 homePage !== "about:home" &&
329 homePage !== "about:blank" &&
330 !Services.prefs.getBoolPref(kExtensionControllerPref, false) &&
331 !Services.prefs.getBoolPref(kWidgetRemovedPref, false) &&
332 !lazy.CustomizableUI.getWidget(kWidgetId).areaType
334 // Find a spot for the home button, ideally it will be in its default
335 // position beside the stop/refresh button.
336 // Work backwards from the URL bar since it can't be removed and put
337 // the button after the first non-spring we find.
338 let navbarPlacements = lazy.CustomizableUI.getWidgetIdsInArea("nav-bar");
339 let position = navbarPlacements.indexOf("urlbar-container");
340 for (let i = position - 1; i >= 0; i--) {
341 if (!navbarPlacements[i].startsWith("customizableui-special-spring")) {
346 lazy.CustomizableUI.addWidgetToArea(kWidgetId, "nav-bar", position);
350 _addCustomizableUiListener() {
351 if (!Services.prefs.getBoolPref(kWidgetRemovedPref, false)) {
352 lazy.CustomizableUI.addListener(this);