diff -r 000000000000 -r 6474c204b198 toolkit/modules/DirectoryLinksProvider.jsm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toolkit/modules/DirectoryLinksProvider.jsm Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,204 @@ +/* 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"; + +this.EXPORTED_SYMBOLS = ["DirectoryLinksProvider"]; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); + +/** + * Gets the currently selected locale for display. + * @return the selected locale or "en-US" if none is selected + */ +function getLocale() { + let matchOS; + try { + matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE); + } + catch (e) {} + + if (matchOS) { + return Services.locale.getLocaleComponentForUserAgent(); + } + + try { + let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, + Ci.nsIPrefLocalizedString); + if (locale) { + return locale.data; + } + } + catch (e) {} + + try { + return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); + } + catch (e) {} + + return "en-US"; +} + +// 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 where to obtain directory links +const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directorySource"; + +// The frecency of a directory link +const DIRECTORY_FRECENCY = 1000; + +const LINK_TYPES = Object.freeze([ + "sponsored", + "affiliate", + "organic", +]); + +/** + * Singleton that serves as the provider of directory links. + * Directory links are a hard-coded set of links shown if a user's link + * inventory is empty. + */ +let DirectoryLinksProvider = { + + __linksURL: null, + + _observers: [], + + get _prefs() Object.freeze({ + linksURL: PREF_DIRECTORY_SOURCE, + matchOSLocale: PREF_MATCH_OS_LOCALE, + prefSelectedLocale: PREF_SELECTED_LOCALE, + }), + + get _linksURL() { + if (!this.__linksURL) { + try { + this.__linksURL = Services.prefs.getCharPref(this._prefs["linksURL"]); + } + catch (e) { + Cu.reportError("Error fetching directory links url from prefs: " + e); + } + } + return this.__linksURL; + }, + + get linkTypes() LINK_TYPES, + + observe: function DirectoryLinksProvider_observe(aSubject, aTopic, aData) { + if (aTopic == "nsPref:changed") { + if (aData == this._prefs["linksURL"]) { + delete this.__linksURL; + } + this._callObservers("onManyLinksChanged"); + } + }, + + _addPrefsObserver: function DirectoryLinksProvider_addObserver() { + for (let pref in this._prefs) { + let prefName = this._prefs[pref]; + Services.prefs.addObserver(prefName, this, false); + } + }, + + _removePrefsObserver: function DirectoryLinksProvider_removeObserver() { + for (let pref in this._prefs) { + let prefName = this._prefs[pref]; + Services.prefs.removeObserver(prefName, this); + } + }, + + /** + * Fetches the current set of directory links. + * @param aCallback a callback that is provided a set of links. + */ + _fetchLinks: function DirectoryLinksProvider_fetchLinks(aCallback) { + try { + NetUtil.asyncFetch(this._linksURL, (aInputStream, aResult, aRequest) => { + let output; + if (Components.isSuccessCode(aResult)) { + try { + let json = NetUtil.readInputStreamToString(aInputStream, + aInputStream.available(), + {charset: "UTF-8"}); + let locale = getLocale(); + output = JSON.parse(json)[locale]; + } + catch (e) { + Cu.reportError(e); + } + } + else { + Cu.reportError(new Error("the fetch of " + this._linksURL + "was unsuccessful")); + } + aCallback(output || []); + }); + } + catch (e) { + Cu.reportError(e); + aCallback([]); + } + }, + + /** + * Gets the current set of directory links. + * @param aCallback The function that the array of links is passed to. + */ + getLinks: function DirectoryLinksProvider_getLinks(aCallback) { + this._fetchLinks(rawLinks => { + // all directory links have a frecency of DIRECTORY_FRECENCY + aCallback(rawLinks.map((link, position) => { + link.frecency = DIRECTORY_FRECENCY; + link.lastVisitDate = rawLinks.length - position; + return link; + })); + }); + }, + + init: function DirectoryLinksProvider_init() { + this._addPrefsObserver(); + }, + + /** + * Return the object to its pre-init state + */ + reset: function DirectoryLinksProvider_reset() { + delete this.__linksURL; + this._removePrefsObserver(); + this._removeObservers(); + }, + + addObserver: function DirectoryLinksProvider_addObserver(aObserver) { + this._observers.push(aObserver); + }, + + _callObservers: function DirectoryLinksProvider__callObservers(aMethodName, aArg) { + for (let obs of this._observers) { + if (typeof(obs[aMethodName]) == "function") { + try { + obs[aMethodName](this, aArg); + } catch (err) { + Cu.reportError(err); + } + } + } + }, + + _removeObservers: function() { + while (this._observers.length) { + this._observers.pop(); + } + } +};