toolkit/modules/DirectoryLinksProvider.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/modules/DirectoryLinksProvider.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,204 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = ["DirectoryLinksProvider"];
    1.11 +
    1.12 +const Ci = Components.interfaces;
    1.13 +const Cc = Components.classes;
    1.14 +const Cu = Components.utils;
    1.15 +
    1.16 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.17 +Cu.import("resource://gre/modules/Services.jsm");
    1.18 +
    1.19 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    1.20 +  "resource://gre/modules/NetUtil.jsm");
    1.21 +
    1.22 +/**
    1.23 + * Gets the currently selected locale for display.
    1.24 + * @return  the selected locale or "en-US" if none is selected
    1.25 + */
    1.26 +function getLocale() {
    1.27 +  let matchOS;
    1.28 +  try {
    1.29 +    matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
    1.30 +  }
    1.31 +  catch (e) {}
    1.32 +
    1.33 +  if (matchOS) {
    1.34 +    return Services.locale.getLocaleComponentForUserAgent();
    1.35 +  }
    1.36 +
    1.37 +  try {
    1.38 +    let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
    1.39 +                                                Ci.nsIPrefLocalizedString);
    1.40 +    if (locale) {
    1.41 +      return locale.data;
    1.42 +    }
    1.43 +  }
    1.44 +  catch (e) {}
    1.45 +
    1.46 +  try {
    1.47 +    return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
    1.48 +  }
    1.49 +  catch (e) {}
    1.50 +
    1.51 +  return "en-US";
    1.52 +}
    1.53 +
    1.54 +// The preference that tells whether to match the OS locale
    1.55 +const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
    1.56 +
    1.57 +// The preference that tells what locale the user selected
    1.58 +const PREF_SELECTED_LOCALE = "general.useragent.locale";
    1.59 +
    1.60 +// The preference that tells where to obtain directory links
    1.61 +const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directorySource";
    1.62 +
    1.63 +// The frecency of a directory link
    1.64 +const DIRECTORY_FRECENCY = 1000;
    1.65 +
    1.66 +const LINK_TYPES = Object.freeze([
    1.67 +  "sponsored",
    1.68 +  "affiliate",
    1.69 +  "organic",
    1.70 +]);
    1.71 +
    1.72 +/**
    1.73 + * Singleton that serves as the provider of directory links.
    1.74 + * Directory links are a hard-coded set of links shown if a user's link
    1.75 + * inventory is empty.
    1.76 + */
    1.77 +let DirectoryLinksProvider = {
    1.78 +
    1.79 +  __linksURL: null,
    1.80 +
    1.81 +  _observers: [],
    1.82 +
    1.83 +  get _prefs() Object.freeze({
    1.84 +    linksURL: PREF_DIRECTORY_SOURCE,
    1.85 +    matchOSLocale: PREF_MATCH_OS_LOCALE,
    1.86 +    prefSelectedLocale: PREF_SELECTED_LOCALE,
    1.87 +  }),
    1.88 +
    1.89 +  get _linksURL() {
    1.90 +    if (!this.__linksURL) {
    1.91 +      try {
    1.92 +        this.__linksURL = Services.prefs.getCharPref(this._prefs["linksURL"]);
    1.93 +      }
    1.94 +      catch (e) {
    1.95 +        Cu.reportError("Error fetching directory links url from prefs: " + e);
    1.96 +      }
    1.97 +    }
    1.98 +    return this.__linksURL;
    1.99 +  },
   1.100 +
   1.101 +  get linkTypes() LINK_TYPES,
   1.102 +
   1.103 +  observe: function DirectoryLinksProvider_observe(aSubject, aTopic, aData) {
   1.104 +    if (aTopic == "nsPref:changed") {
   1.105 +      if (aData == this._prefs["linksURL"]) {
   1.106 +        delete this.__linksURL;
   1.107 +      }
   1.108 +      this._callObservers("onManyLinksChanged");
   1.109 +    }
   1.110 +  },
   1.111 +
   1.112 +  _addPrefsObserver: function DirectoryLinksProvider_addObserver() {
   1.113 +    for (let pref in this._prefs) {
   1.114 +      let prefName = this._prefs[pref];
   1.115 +      Services.prefs.addObserver(prefName, this, false);
   1.116 +    }
   1.117 +  },
   1.118 +
   1.119 +  _removePrefsObserver: function DirectoryLinksProvider_removeObserver() {
   1.120 +    for (let pref in this._prefs) {
   1.121 +      let prefName = this._prefs[pref];
   1.122 +      Services.prefs.removeObserver(prefName, this);
   1.123 +    }
   1.124 +  },
   1.125 +
   1.126 +  /**
   1.127 +   * Fetches the current set of directory links.
   1.128 +   * @param aCallback a callback that is provided a set of links.
   1.129 +   */
   1.130 +  _fetchLinks: function DirectoryLinksProvider_fetchLinks(aCallback) {
   1.131 +    try {
   1.132 +      NetUtil.asyncFetch(this._linksURL, (aInputStream, aResult, aRequest) => {
   1.133 +        let output;
   1.134 +        if (Components.isSuccessCode(aResult)) {
   1.135 +          try {
   1.136 +            let json = NetUtil.readInputStreamToString(aInputStream,
   1.137 +                                                       aInputStream.available(),
   1.138 +                                                       {charset: "UTF-8"});
   1.139 +            let locale = getLocale();
   1.140 +            output = JSON.parse(json)[locale];
   1.141 +          }
   1.142 +          catch (e) {
   1.143 +            Cu.reportError(e);
   1.144 +          }
   1.145 +        }
   1.146 +        else {
   1.147 +          Cu.reportError(new Error("the fetch of " + this._linksURL + "was unsuccessful"));
   1.148 +        }
   1.149 +        aCallback(output || []);
   1.150 +      });
   1.151 +    }
   1.152 +    catch (e) {
   1.153 +      Cu.reportError(e);
   1.154 +      aCallback([]);
   1.155 +    }
   1.156 +  },
   1.157 +
   1.158 +  /**
   1.159 +   * Gets the current set of directory links.
   1.160 +   * @param aCallback The function that the array of links is passed to.
   1.161 +   */
   1.162 +  getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
   1.163 +    this._fetchLinks(rawLinks => {
   1.164 +      // all directory links have a frecency of DIRECTORY_FRECENCY
   1.165 +      aCallback(rawLinks.map((link, position) => {
   1.166 +        link.frecency = DIRECTORY_FRECENCY;
   1.167 +        link.lastVisitDate = rawLinks.length - position;
   1.168 +        return link;
   1.169 +      }));
   1.170 +    });
   1.171 +  },
   1.172 +
   1.173 +  init: function DirectoryLinksProvider_init() {
   1.174 +    this._addPrefsObserver();
   1.175 +  },
   1.176 +
   1.177 +  /**
   1.178 +   * Return the object to its pre-init state
   1.179 +   */
   1.180 +  reset: function DirectoryLinksProvider_reset() {
   1.181 +    delete this.__linksURL;
   1.182 +    this._removePrefsObserver();
   1.183 +    this._removeObservers();
   1.184 +  },
   1.185 +
   1.186 +  addObserver: function DirectoryLinksProvider_addObserver(aObserver) {
   1.187 +    this._observers.push(aObserver);
   1.188 +  },
   1.189 +
   1.190 +  _callObservers: function DirectoryLinksProvider__callObservers(aMethodName, aArg) {
   1.191 +    for (let obs of this._observers) {
   1.192 +      if (typeof(obs[aMethodName]) == "function") {
   1.193 +        try {
   1.194 +          obs[aMethodName](this, aArg);
   1.195 +        } catch (err) {
   1.196 +          Cu.reportError(err);
   1.197 +        }
   1.198 +      }
   1.199 +    }
   1.200 +  },
   1.201 +
   1.202 +  _removeObservers: function() {
   1.203 +    while (this._observers.length) {
   1.204 +      this._observers.pop();
   1.205 +    }
   1.206 +  }
   1.207 +};

mercurial