diff options
Diffstat (limited to 'application/basilisk/components/newtab/aboutNewTabService.js')
-rw-r--r-- | application/basilisk/components/newtab/aboutNewTabService.js | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/application/basilisk/components/newtab/aboutNewTabService.js b/application/basilisk/components/newtab/aboutNewTabService.js new file mode 100644 index 000000000..a43c4dc22 --- /dev/null +++ b/application/basilisk/components/newtab/aboutNewTabService.js @@ -0,0 +1,289 @@ +/* + * 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 XPCOMUtils, NewTabPrefsProvider, Services, + Locale, UpdateUtils, NewTabRemoteResources +*/ +"use strict"; + +const {utils: Cu, interfaces: Ci} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", + "resource://gre/modules/UpdateUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider", + "resource:///modules/NewTabPrefsProvider.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Locale", + "resource://gre/modules/Locale.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources", + "resource:///modules/NewTabRemoteResources.jsm"); + +const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml"; + +const REMOTE_NEWTAB_PATH = "/newtab/v%VERSION%/%CHANNEL%/%LOCALE%/index.html"; + +const ABOUT_URL = "about:newtab"; + +// Pref that tells if remote newtab is enabled +const PREF_REMOTE_ENABLED = "browser.newtabpage.remote"; + +// Pref branch necesssary for testing +const PREF_REMOTE_CS_TEST = "browser.newtabpage.remote.content-signing-test"; + +// The preference that tells whether to match the OS locale +const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; + +// The preference that tells what locale the user selected +const PREF_SELECTED_LOCALE = "general.useragent.locale"; + +// The preference that tells what remote mode is enabled. +const PREF_REMOTE_MODE = "browser.newtabpage.remote.mode"; + +// The preference that tells which remote version is expected. +const PREF_REMOTE_VERSION = "browser.newtabpage.remote.version"; + +const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]); + +function AboutNewTabService() { + NewTabPrefsProvider.prefs.on(PREF_REMOTE_ENABLED, this._handleToggleEvent.bind(this)); + + this._updateRemoteMaybe = this._updateRemoteMaybe.bind(this); + + // trigger remote change if needed, according to pref + this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED)); +} + +/* + * A service that allows for the overriding, at runtime, of the newtab page's url. + * Additionally, the service manages pref state between a remote and local newtab page. + * + * There is tight coupling with browser/about/AboutRedirector.cpp. + * + * 1. Browser chrome access: + * + * When the user issues a command to open a new tab page, usually clicking a button + * in the browser chrome or using shortcut keys, the browser chrome code invokes the + * service to obtain the newtab URL. It then loads that URL in a new tab. + * + * When not overridden, the default URL emitted by the service is "about:newtab". + * When overridden, it returns the overriden URL. + * + * 2. Redirector Access: + * + * When the URL loaded is about:newtab, the default behavior, or when entered in the + * URL bar, the redirector is hit. The service is then called to return either of + * two URLs, a chrome or remote one, based on the browser.newtabpage.remote pref. + * + * NOTE: "about:newtab" will always result in a default newtab page, and never an overridden URL. + * + * Access patterns: + * + * The behavior is different when accessing the service via browser chrome or via redirector + * largely to maintain compatibility with expectations of add-on developers. + * + * Loading a chrome resource, or an about: URL in the redirector with either the + * LOAD_NORMAL or LOAD_REPLACE flags yield unexpected behaviors, so a roundtrip + * to the redirector from browser chrome is avoided. + */ +AboutNewTabService.prototype = { + + _newTabURL: ABOUT_URL, + _remoteEnabled: false, + _remoteURL: null, + _overridden: false, + + classID: Components.ID("{dfcd2adc-7867-4d3a-ba70-17501f208142}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutNewTabService]), + _xpcom_categories: [{ + service: true + }], + + _handleToggleEvent(prefName, stateEnabled, forceState) { // jshint unused:false + if (this.toggleRemote(stateEnabled, forceState)) { + Services.obs.notifyObservers(null, "newtab-url-changed", ABOUT_URL); + } + }, + + /** + * React to changes to the remote newtab pref. + * + * If browser.newtabpage.remote is true, this will change the default URL to the + * remote newtab page URL. If browser.newtabpage.remote is false, the default URL + * will be a local chrome URL. + * + * This will only act if there is a change of state and if not overridden. + * + * @returns {Boolean} Returns if there has been a state change + * + * @param {Boolean} stateEnabled remote state to set to + * @param {Boolean} forceState force state change + */ + toggleRemote(stateEnabled, forceState) { + + if (!forceState && (this._overriden || stateEnabled === this._remoteEnabled)) { + // exit there is no change of state + return false; + } + + let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST); + if (stateEnabled) { + if (!csTest) { + this._remoteURL = this.generateRemoteURL(); + } else { + this._remoteURL = this._newTabURL; + } + NewTabPrefsProvider.prefs.on( + PREF_SELECTED_LOCALE, + this._updateRemoteMaybe); + NewTabPrefsProvider.prefs.on( + PREF_MATCH_OS_LOCALE, + this._updateRemoteMaybe); + NewTabPrefsProvider.prefs.on( + PREF_REMOTE_MODE, + this._updateRemoteMaybe); + NewTabPrefsProvider.prefs.on( + PREF_REMOTE_VERSION, + this._updateRemoteMaybe); + this._remoteEnabled = true; + } else { + NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe); + NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe); + NewTabPrefsProvider.prefs.off(PREF_REMOTE_MODE, this._updateRemoteMaybe); + NewTabPrefsProvider.prefs.off(PREF_REMOTE_VERSION, this._updateRemoteMaybe); + this._remoteEnabled = false; + } + if (!csTest) { + this._newTabURL = ABOUT_URL; + } + return true; + }, + + /* + * Generate a default url based on remote mode, version, locale and update channel + */ + generateRemoteURL() { + let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel); + let path = REMOTE_NEWTAB_PATH + .replace("%VERSION%", this.remoteVersion) + .replace("%LOCALE%", Locale.getLocale()) + .replace("%CHANNEL%", releaseName); + let mode = Services.prefs.getCharPref(PREF_REMOTE_MODE); + if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) { + mode = "production"; + } + return NewTabRemoteResources.MODE_CHANNEL_MAP[mode].origin + path; + }, + + /* + * Returns the default URL. + * + * This URL only depends on the browser.newtabpage.remote pref. Overriding + * the newtab page has no effect on the result of this function. + * + * The result is also the remote URL if this is in a test (PREF_REMOTE_CS_TEST) + * + * @returns {String} the default newtab URL, remote or local depending on browser.newtabpage.remote + */ + get defaultURL() { + let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST); + if (this._remoteEnabled || csTest) { + return this._remoteURL; + } + return LOCAL_NEWTAB_URL; + }, + + /* + * Updates the remote location when the page is not overriden. + * + * Useful when there is a dependent pref change + */ + _updateRemoteMaybe() { + if (!this._remoteEnabled || this._overridden) { + return; + } + + let url = this.generateRemoteURL(); + if (url !== this._remoteURL) { + this._remoteURL = url; + Services.obs.notifyObservers(null, "newtab-url-changed", + this._remoteURL); + } + }, + + /** + * Returns the release name from an Update Channel name + * + * @returns {String} a release name based on the update channel. Defaults to nightly + */ + releaseFromUpdateChannel(channelName) { + return VALID_CHANNELS.has(channelName) ? channelName : "nightly"; + }, + + get newTabURL() { + return this._newTabURL; + }, + + get remoteVersion() { + return Services.prefs.getCharPref(PREF_REMOTE_VERSION); + }, + + get remoteReleaseName() { + return this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel); + }, + + set newTabURL(aNewTabURL) { + let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST); + aNewTabURL = aNewTabURL.trim(); + if (aNewTabURL === ABOUT_URL) { + // avoid infinite redirects in case one sets the URL to about:newtab + this.resetNewTabURL(); + return; + } else if (aNewTabURL === "") { + aNewTabURL = "about:blank"; + } + let remoteURL = this.generateRemoteURL(); + let prefRemoteEnabled = Services.prefs.getBoolPref(PREF_REMOTE_ENABLED); + let isResetLocal = !prefRemoteEnabled && aNewTabURL === LOCAL_NEWTAB_URL; + let isResetRemote = prefRemoteEnabled && aNewTabURL === remoteURL; + + if (isResetLocal || isResetRemote) { + if (this._overriden && !csTest) { + // only trigger a reset if previously overridden and this is no test + this.resetNewTabURL(); + } + return; + } + // turn off remote state if needed + if (!csTest) { + this.toggleRemote(false); + } else { + // if this is a test, we want the remoteURL to be set + this._remoteURL = aNewTabURL; + } + this._newTabURL = aNewTabURL; + this._overridden = true; + Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL); + }, + + get overridden() { + return this._overridden; + }, + + get remoteEnabled() { + return this._remoteEnabled; + }, + + resetNewTabURL() { + this._overridden = false; + this._newTabURL = ABOUT_URL; + this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED), true); + Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutNewTabService]); |