michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["DirectoryLinksProvider"]; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", michael@0: "resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: /** michael@0: * Gets the currently selected locale for display. michael@0: * @return the selected locale or "en-US" if none is selected michael@0: */ michael@0: function getLocale() { michael@0: let matchOS; michael@0: try { michael@0: matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE); michael@0: } michael@0: catch (e) {} michael@0: michael@0: if (matchOS) { michael@0: return Services.locale.getLocaleComponentForUserAgent(); michael@0: } michael@0: michael@0: try { michael@0: let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, michael@0: Ci.nsIPrefLocalizedString); michael@0: if (locale) { michael@0: return locale.data; michael@0: } michael@0: } michael@0: catch (e) {} michael@0: michael@0: try { michael@0: return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); michael@0: } michael@0: catch (e) {} michael@0: michael@0: return "en-US"; michael@0: } michael@0: michael@0: // The preference that tells whether to match the OS locale michael@0: const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; michael@0: michael@0: // The preference that tells what locale the user selected michael@0: const PREF_SELECTED_LOCALE = "general.useragent.locale"; michael@0: michael@0: // The preference that tells where to obtain directory links michael@0: const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directorySource"; michael@0: michael@0: // The frecency of a directory link michael@0: const DIRECTORY_FRECENCY = 1000; michael@0: michael@0: const LINK_TYPES = Object.freeze([ michael@0: "sponsored", michael@0: "affiliate", michael@0: "organic", michael@0: ]); michael@0: michael@0: /** michael@0: * Singleton that serves as the provider of directory links. michael@0: * Directory links are a hard-coded set of links shown if a user's link michael@0: * inventory is empty. michael@0: */ michael@0: let DirectoryLinksProvider = { michael@0: michael@0: __linksURL: null, michael@0: michael@0: _observers: [], michael@0: michael@0: get _prefs() Object.freeze({ michael@0: linksURL: PREF_DIRECTORY_SOURCE, michael@0: matchOSLocale: PREF_MATCH_OS_LOCALE, michael@0: prefSelectedLocale: PREF_SELECTED_LOCALE, michael@0: }), michael@0: michael@0: get _linksURL() { michael@0: if (!this.__linksURL) { michael@0: try { michael@0: this.__linksURL = Services.prefs.getCharPref(this._prefs["linksURL"]); michael@0: } michael@0: catch (e) { michael@0: Cu.reportError("Error fetching directory links url from prefs: " + e); michael@0: } michael@0: } michael@0: return this.__linksURL; michael@0: }, michael@0: michael@0: get linkTypes() LINK_TYPES, michael@0: michael@0: observe: function DirectoryLinksProvider_observe(aSubject, aTopic, aData) { michael@0: if (aTopic == "nsPref:changed") { michael@0: if (aData == this._prefs["linksURL"]) { michael@0: delete this.__linksURL; michael@0: } michael@0: this._callObservers("onManyLinksChanged"); michael@0: } michael@0: }, michael@0: michael@0: _addPrefsObserver: function DirectoryLinksProvider_addObserver() { michael@0: for (let pref in this._prefs) { michael@0: let prefName = this._prefs[pref]; michael@0: Services.prefs.addObserver(prefName, this, false); michael@0: } michael@0: }, michael@0: michael@0: _removePrefsObserver: function DirectoryLinksProvider_removeObserver() { michael@0: for (let pref in this._prefs) { michael@0: let prefName = this._prefs[pref]; michael@0: Services.prefs.removeObserver(prefName, this); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Fetches the current set of directory links. michael@0: * @param aCallback a callback that is provided a set of links. michael@0: */ michael@0: _fetchLinks: function DirectoryLinksProvider_fetchLinks(aCallback) { michael@0: try { michael@0: NetUtil.asyncFetch(this._linksURL, (aInputStream, aResult, aRequest) => { michael@0: let output; michael@0: if (Components.isSuccessCode(aResult)) { michael@0: try { michael@0: let json = NetUtil.readInputStreamToString(aInputStream, michael@0: aInputStream.available(), michael@0: {charset: "UTF-8"}); michael@0: let locale = getLocale(); michael@0: output = JSON.parse(json)[locale]; michael@0: } michael@0: catch (e) { michael@0: Cu.reportError(e); michael@0: } michael@0: } michael@0: else { michael@0: Cu.reportError(new Error("the fetch of " + this._linksURL + "was unsuccessful")); michael@0: } michael@0: aCallback(output || []); michael@0: }); michael@0: } michael@0: catch (e) { michael@0: Cu.reportError(e); michael@0: aCallback([]); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets the current set of directory links. michael@0: * @param aCallback The function that the array of links is passed to. michael@0: */ michael@0: getLinks: function DirectoryLinksProvider_getLinks(aCallback) { michael@0: this._fetchLinks(rawLinks => { michael@0: // all directory links have a frecency of DIRECTORY_FRECENCY michael@0: aCallback(rawLinks.map((link, position) => { michael@0: link.frecency = DIRECTORY_FRECENCY; michael@0: link.lastVisitDate = rawLinks.length - position; michael@0: return link; michael@0: })); michael@0: }); michael@0: }, michael@0: michael@0: init: function DirectoryLinksProvider_init() { michael@0: this._addPrefsObserver(); michael@0: }, michael@0: michael@0: /** michael@0: * Return the object to its pre-init state michael@0: */ michael@0: reset: function DirectoryLinksProvider_reset() { michael@0: delete this.__linksURL; michael@0: this._removePrefsObserver(); michael@0: this._removeObservers(); michael@0: }, michael@0: michael@0: addObserver: function DirectoryLinksProvider_addObserver(aObserver) { michael@0: this._observers.push(aObserver); michael@0: }, michael@0: michael@0: _callObservers: function DirectoryLinksProvider__callObservers(aMethodName, aArg) { michael@0: for (let obs of this._observers) { michael@0: if (typeof(obs[aMethodName]) == "function") { michael@0: try { michael@0: obs[aMethodName](this, aArg); michael@0: } catch (err) { michael@0: Cu.reportError(err); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _removeObservers: function() { michael@0: while (this._observers.length) { michael@0: this._observers.pop(); michael@0: } michael@0: } michael@0: };