gecko-dev/browser/modules/HomePage.jsm

308 lines
9.1 KiB
JavaScript

/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["HomePage"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
ExtensionPreferencesManager:
"resource://gre/modules/ExtensionPreferencesManager.jsm",
IgnoreLists: "resource://gre/modules/IgnoreLists.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
});
const kPrefName = "browser.startup.homepage";
const kDefaultHomePage = "about:home";
const kExtensionControllerPref =
"browser.startup.homepage_override.extensionControlled";
const kHomePageIgnoreListId = "homepage-urls";
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;
}
/**
* HomePage provides tools to keep try of the current homepage, and the
* applications's default homepage. It includes tools to insure that certain
* urls are ignored. As a result, all set/get requests for the homepage
* preferences should be routed through here.
*/
let HomePage = {
// This is an array of strings that should be matched against URLs to see
// if they should be ignored or not.
_ignoreList: [],
// A promise that is set when initialization starts and resolved when it
// completes.
_initializationPromise: null,
/**
* Used to initialise the ignore lists. This may be called later than
* the first call to get or set, which may cause a used to get an ignored
* homepage, but this is deemed acceptable, as we'll correct it once
* initialised.
*/
async delayedStartup() {
if (this._initializationPromise) {
await this._initializationPromise;
return;
}
Services.telemetry.setEventRecordingEnabled("homepage", true);
// Now we have the values, listen for future updates.
this._ignoreListListener = this._handleIgnoreListUpdated.bind(this);
this._initializationPromise = IgnoreLists.getAndSubscribe(
this._ignoreListListener
);
const current = await this._initializationPromise;
await this._handleIgnoreListUpdated({ data: { current } });
},
/**
* Gets the homepage for the given window.
*
* @param {DOMWindow} [aWindow]
* The window associated with the get, used to check for private browsing
* mode. If not supplied, normal mode is assumed.
* @returns {string}
* Returns the home page value, this could be a single url, or a `|`
* separated list of URLs.
*/
get(aWindow) {
let homePages = getHomepagePref();
if (
PrivateBrowsingUtils.permanentPrivateBrowsing ||
(aWindow && PrivateBrowsingUtils.isWindowPrivate(aWindow))
) {
// If an extension controls the setting and does not have private
// browsing permission, use the default setting.
let extensionControlled = Services.prefs.getBoolPref(
kExtensionControllerPref,
false
);
let privateAllowed = Services.prefs.getBoolPref(
"browser.startup.homepage_override.privateAllowed",
false
);
// There is a potential on upgrade that the prefs are not set yet, so we double check
// for moz-extension.
if (
!privateAllowed &&
(extensionControlled || homePages.includes("moz-extension://"))
) {
return this.getDefault();
}
}
return homePages;
},
/**
* @returns {string}
* Returns the application default homepage.
*/
getDefault() {
return getHomepagePref(true);
},
/**
* @returns {string}
* Returns the original application homepage URL (not from prefs).
*/
getOriginalDefault() {
return kDefaultHomePage;
},
/**
* @returns {boolean}
* Returns true if the homepage has been changed.
*/
get overridden() {
return Services.prefs.prefHasUserValue(kPrefName);
},
/**
* @returns {boolean}
* Returns true if the homepage preference is locked.
*/
get locked() {
return Services.prefs.prefIsLocked(kPrefName);
},
/**
* @returns {boolean}
* Returns true if the current homepage is the application default.
*/
get isDefault() {
return HomePage.get() === kDefaultHomePage;
},
/**
* Sets the homepage preference to a new page.
*
* @param {string} value
* The new value to set the preference to. This could be a single url, or a
* `|` separated list of URLs.
*/
async set(value) {
await this.delayedStartup();
if (await this.shouldIgnore(value)) {
Cu.reportError(
`Ignoring homepage setting for ${value} as it is on the ignore list.`
);
Services.telemetry.recordEvent(
"homepage",
"preference",
"ignore",
"set_blocked"
);
return false;
}
Services.prefs.setStringPref(kPrefName, value);
return true;
},
/**
* Sets the homepage preference to a new page. This is an synchronous version
* that should only be used when we know the source is safe as it bypasses the
* ignore list, e.g. when setting directly to about:blank or a value not
* supplied externally.
*
* @param {string} value
* The new value to set the preference to. This could be a single url, or a
* `|` separated list of URLs.
*/
safeSet(value) {
Services.prefs.setStringPref(kPrefName, value);
},
/**
* Clears the homepage preference if it is not the default. Note that for
* policy/locking use, the default homepage might not be about:home after this.
*/
clear() {
Services.prefs.clearUserPref(kPrefName);
},
/**
* Resets the homepage preference to be about:home.
*/
reset() {
Services.prefs.setStringPref(kPrefName, kDefaultHomePage);
},
/**
* Determines if a url should be ignored according to the ignore list.
*
* @param {string} url
* A string that is the url or urls to be ignored.
* @returns {boolean}
* True if the url should be ignored.
*/
async shouldIgnore(url) {
await this.delayedStartup();
const lowerURL = url.toLowerCase();
return this._ignoreList.some(code => lowerURL.includes(code.toLowerCase()));
},
/**
* Handles updates of the ignore list, checking the existing preference and
* correcting it as necessary.
*
* @param {Object} eventData
* The event data as received from RemoteSettings.
*/
async _handleIgnoreListUpdated({ data: { current } }) {
for (const entry of current) {
if (entry.id == kHomePageIgnoreListId) {
this._ignoreList = [...entry.matches];
}
}
// Only check if we're overridden as we assume the default value is fine,
// or won't be changeable (e.g. enterprise policy).
if (this.overridden) {
let homePages = getHomepagePref().toLowerCase();
if (
this._ignoreList.some(code => homePages.includes(code.toLowerCase()))
) {
if (Services.prefs.getBoolPref(kExtensionControllerPref, false)) {
if (Services.appinfo.inSafeMode) {
// Add-ons don't get started in safe mode, so just abort this.
// We'll get to remove them when we next start in normal mode.
return;
}
// getSetting does not need the module to be loaded.
const item = await ExtensionPreferencesManager.getSetting(
"homepage_override"
);
if (item && item.id) {
// During startup some modules may not be loaded yet, so we load
// the setting we need prior to removal.
await ExtensionParent.apiManager.asyncLoadModule(
"chrome_settings_overrides"
);
ExtensionPreferencesManager.removeSetting(
item.id,
"homepage_override"
).catch(Cu.reportError);
} else {
// If we don't have a setting for it, we assume the pref has
// been incorrectly set somehow.
Services.prefs.clearUserPref(kExtensionControllerPref);
Services.prefs.clearUserPref(
"browser.startup.homepage_override.privateAllowed"
);
}
} else {
this.clear();
}
Services.telemetry.recordEvent(
"homepage",
"preference",
"ignore",
"saved_reset"
);
}
}
},
};