HomePage.sys.mjs (11406B)
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/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 CustomizableUI: 9 "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", 10 ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", 11 ExtensionPreferencesManager: 12 "resource://gre/modules/ExtensionPreferencesManager.sys.mjs", 13 IgnoreLists: "resource://gre/modules/IgnoreLists.sys.mjs", 14 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 15 }); 16 17 const kPrefName = "browser.startup.homepage"; 18 const kDefaultHomePage = "about:tor"; 19 const kExtensionControllerPref = 20 "browser.startup.homepage_override.extensionControlled"; 21 const kHomePageIgnoreListId = "homepage-urls"; 22 const kWidgetId = "home-button"; 23 const kWidgetRemovedPref = "browser.engagement.home-button.has-removed"; 24 25 function getHomepagePref(useDefault) { 26 let homePage; 27 let prefs = Services.prefs; 28 if (useDefault) { 29 prefs = prefs.getDefaultBranch(null); 30 } 31 try { 32 // Historically, this was a localizable pref, but default Firefox builds 33 // don't use this. 34 // Distributions and local customizations might still use this, so let's 35 // keep it. 36 homePage = prefs.getComplexValue(kPrefName, Ci.nsIPrefLocalizedString).data; 37 } catch (ex) {} 38 39 if (!homePage) { 40 homePage = prefs.getStringPref(kPrefName); 41 } 42 43 // Apparently at some point users ended up with blank home pages somehow. 44 // If that happens, reset the pref and read it again. 45 if (!homePage && !useDefault) { 46 Services.prefs.clearUserPref(kPrefName); 47 homePage = getHomepagePref(true); 48 } 49 50 return homePage; 51 } 52 53 /** 54 * HomePage provides tools to keep track of the current homepage, and the 55 * applications's default homepage. It includes tools to insure that certain 56 * urls are ignored. As a result, all set/get requests for the homepage 57 * preferences should be routed through here. 58 */ 59 export let HomePage = { 60 // This is an array of strings that should be matched against URLs to see 61 // if they should be ignored or not. 62 _ignoreList: [], 63 64 // A promise that is set when initialization starts and resolved when it 65 // completes. 66 _initializationPromise: null, 67 68 /** 69 * Used to initialise the ignore lists. This may be called later than 70 * the first call to get or set, which may cause a used to get an ignored 71 * homepage, but this is deemed acceptable, as we'll correct it once 72 * initialised. 73 */ 74 async delayedStartup() { 75 if (this._initializationPromise) { 76 await this._initializationPromise; 77 return; 78 } 79 80 // Now we have the values, listen for future updates. 81 this._ignoreListListener = this._handleIgnoreListUpdated.bind(this); 82 83 this._initializationPromise = lazy.IgnoreLists.getAndSubscribe( 84 this._ignoreListListener 85 ); 86 87 this._addCustomizableUiListener(); 88 89 const current = await this._initializationPromise; 90 91 await this._handleIgnoreListUpdated({ data: { current } }); 92 }, 93 94 /** 95 * Gets the homepage for the given window. 96 * 97 * @param {DOMWindow} [aWindow] 98 * The window associated with the get, used to check for private browsing 99 * mode. If not supplied, normal mode is assumed. 100 * @returns {string} 101 * Returns the home page value, this could be a single url, or a `|` 102 * separated list of URLs. 103 */ 104 get(aWindow) { 105 let homePages = getHomepagePref(); 106 if ( 107 lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || 108 (aWindow && lazy.PrivateBrowsingUtils.isWindowPrivate(aWindow)) 109 ) { 110 // If an extension controls the setting and does not have private 111 // browsing permission, use the default setting. 112 let extensionControlled = Services.prefs.getBoolPref( 113 kExtensionControllerPref, 114 false 115 ); 116 let privateAllowed = Services.prefs.getBoolPref( 117 "browser.startup.homepage_override.privateAllowed", 118 false 119 ); 120 // There is a potential on upgrade that the prefs are not set yet, so we double check 121 // for moz-extension. 122 if ( 123 !privateAllowed && 124 (extensionControlled || homePages.includes("moz-extension://")) 125 ) { 126 return this.getDefault(); 127 } 128 } 129 130 if (homePages == "about:blank") { 131 homePages = "chrome://browser/content/blanktab.html"; 132 } 133 134 return homePages; 135 }, 136 137 getForErrorPage(win) { 138 if (lazy.PrivateBrowsingUtils.isWindowPrivate(win)) { 139 return win.BROWSER_NEW_TAB_URL; 140 } 141 let url = this.get(win); 142 if (url.includes("|")) { 143 url = url.split("|")[0]; 144 } 145 return url; 146 }, 147 148 /** 149 * @returns {string} 150 * Returns the application default homepage. 151 */ 152 getDefault() { 153 return getHomepagePref(true); 154 }, 155 156 /** 157 * @returns {string} 158 * Returns the original application homepage URL (not from prefs). 159 */ 160 getOriginalDefault() { 161 return kDefaultHomePage; 162 }, 163 164 /** 165 * @returns {boolean} 166 * Returns true if the homepage has been changed. 167 */ 168 get overridden() { 169 return Services.prefs.prefHasUserValue(kPrefName); 170 }, 171 172 /** 173 * @returns {boolean} 174 * Returns true if the homepage preference is locked. 175 */ 176 get locked() { 177 return Services.prefs.prefIsLocked(kPrefName); 178 }, 179 180 /** 181 * @returns {boolean} 182 * Returns true if the current homepage is the application default. 183 */ 184 get isDefault() { 185 return HomePage.get() === kDefaultHomePage; 186 }, 187 188 /** 189 * Sets the homepage preference to a new page. 190 * 191 * @param {string} value 192 * The new value to set the preference to. This could be a single url, or a 193 * `|` separated list of URLs. 194 */ 195 async set(value) { 196 await this.delayedStartup(); 197 198 if (await this.shouldIgnore(value)) { 199 console.error( 200 `Ignoring homepage setting for ${value} as it is on the ignore list.` 201 ); 202 Glean.homepage.preferenceIgnore.record({ value: "set_blocked" }); 203 return false; 204 } 205 Services.prefs.setStringPref(kPrefName, value); 206 this._maybeAddHomeButtonToToolbar(value); 207 return true; 208 }, 209 210 /** 211 * Sets the homepage preference to a new page. This is an synchronous version 212 * that should only be used when we know the source is safe as it bypasses the 213 * ignore list, e.g. when setting directly to about:blank or a value not 214 * supplied externally. 215 * 216 * @param {string} value 217 * The new value to set the preference to. This could be a single url, or a 218 * `|` separated list of URLs. 219 */ 220 safeSet(value) { 221 Services.prefs.setStringPref(kPrefName, value); 222 }, 223 224 /** 225 * Clears the homepage preference if it is not the default. Note that for 226 * policy/locking use, the default homepage might not be about:home after this. 227 */ 228 clear() { 229 Services.prefs.clearUserPref(kPrefName); 230 }, 231 232 /** 233 * Resets the homepage preference to be about:home. 234 */ 235 reset() { 236 Services.prefs.setStringPref(kPrefName, kDefaultHomePage); 237 }, 238 239 /** 240 * Determines if a url should be ignored according to the ignore list. 241 * 242 * @param {string} url 243 * A string that is the url or urls to be ignored. 244 * @returns {boolean} 245 * True if the url should be ignored. 246 */ 247 async shouldIgnore(url) { 248 await this.delayedStartup(); 249 250 const lowerURL = url.toLowerCase(); 251 return this._ignoreList.some(code => lowerURL.includes(code.toLowerCase())); 252 }, 253 254 /** 255 * Handles updates of the ignore list, checking the existing preference and 256 * correcting it as necessary. 257 * 258 * @param {object} eventData 259 * The event data as received from RemoteSettings. 260 */ 261 async _handleIgnoreListUpdated({ data: { current } }) { 262 for (const entry of current) { 263 if (entry.id == kHomePageIgnoreListId) { 264 this._ignoreList = [...entry.matches]; 265 } 266 } 267 268 // Only check if we're overridden as we assume the default value is fine, 269 // or won't be changeable (e.g. enterprise policy). 270 if (this.overridden) { 271 let homePages = getHomepagePref().toLowerCase(); 272 if ( 273 this._ignoreList.some(code => homePages.includes(code.toLowerCase())) 274 ) { 275 if (Services.prefs.getBoolPref(kExtensionControllerPref, false)) { 276 if (Services.appinfo.inSafeMode) { 277 // Add-ons don't get started in safe mode, so just abort this. 278 // We'll get to remove them when we next start in normal mode. 279 return; 280 } 281 // getSetting does not need the module to be loaded. 282 const item = 283 await lazy.ExtensionPreferencesManager.getSetting( 284 "homepage_override" 285 ); 286 if (item && item.id) { 287 // During startup some modules may not be loaded yet, so we load 288 // the setting we need prior to removal. 289 await lazy.ExtensionParent.apiManager.asyncLoadModule( 290 "chrome_settings_overrides" 291 ); 292 lazy.ExtensionPreferencesManager.removeSetting( 293 item.id, 294 "homepage_override" 295 ).catch(console.error); 296 } else { 297 // If we don't have a setting for it, we assume the pref has 298 // been incorrectly set somehow. 299 Services.prefs.clearUserPref(kExtensionControllerPref); 300 Services.prefs.clearUserPref( 301 "browser.startup.homepage_override.privateAllowed" 302 ); 303 } 304 } else { 305 this.clear(); 306 } 307 Glean.homepage.preferenceIgnore.record({ value: "saved_reset" }); 308 } 309 } 310 }, 311 312 onWidgetRemoved(widgetId) { 313 if (widgetId == kWidgetId) { 314 Services.prefs.setBoolPref(kWidgetRemovedPref, true); 315 lazy.CustomizableUI.removeListener(this); 316 } 317 }, 318 319 /** 320 * Add the home button to the toolbar if the user just set a custom homepage. 321 * 322 * This should only be done once, so we check HOME_BUTTON_REMOVED_PREF which 323 * gets set to true when the home button is removed from the toolbar. 324 * 325 * If the home button is already on the toolbar it won't be moved. 326 */ 327 _maybeAddHomeButtonToToolbar(homePage) { 328 if ( 329 homePage !== "about:home" && 330 homePage !== "about:blank" && 331 !Services.prefs.getBoolPref(kExtensionControllerPref, false) && 332 !Services.prefs.getBoolPref(kWidgetRemovedPref, false) && 333 !lazy.CustomizableUI.getWidget(kWidgetId).areaType 334 ) { 335 // Find a spot for the home button, ideally it will be in its default 336 // position beside the stop/refresh button. 337 // Work backwards from the URL bar since it can't be removed and put 338 // the button after the first non-spring we find. 339 let navbarPlacements = lazy.CustomizableUI.getWidgetIdsInArea("nav-bar"); 340 let position = navbarPlacements.indexOf("urlbar-container"); 341 for (let i = position - 1; i >= 0; i--) { 342 if ( 343 !navbarPlacements[i].startsWith("customizableui-special-spring") && 344 !navbarPlacements[i].includes("spacer") 345 ) { 346 position = i + 1; 347 break; 348 } 349 } 350 lazy.CustomizableUI.addWidgetToArea(kWidgetId, "nav-bar", position); 351 } 352 }, 353 354 _addCustomizableUiListener() { 355 if (!Services.prefs.getBoolPref(kWidgetRemovedPref, false)) { 356 lazy.CustomizableUI.addListener(this); 357 } 358 }, 359 };