|
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 "use strict"; |
|
6 |
|
7 this.EXPORTED_SYMBOLS = ["DirectoryLinksProvider"]; |
|
8 |
|
9 const Ci = Components.interfaces; |
|
10 const Cc = Components.classes; |
|
11 const Cu = Components.utils; |
|
12 |
|
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
14 Cu.import("resource://gre/modules/Services.jsm"); |
|
15 |
|
16 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", |
|
17 "resource://gre/modules/NetUtil.jsm"); |
|
18 |
|
19 /** |
|
20 * Gets the currently selected locale for display. |
|
21 * @return the selected locale or "en-US" if none is selected |
|
22 */ |
|
23 function getLocale() { |
|
24 let matchOS; |
|
25 try { |
|
26 matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE); |
|
27 } |
|
28 catch (e) {} |
|
29 |
|
30 if (matchOS) { |
|
31 return Services.locale.getLocaleComponentForUserAgent(); |
|
32 } |
|
33 |
|
34 try { |
|
35 let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, |
|
36 Ci.nsIPrefLocalizedString); |
|
37 if (locale) { |
|
38 return locale.data; |
|
39 } |
|
40 } |
|
41 catch (e) {} |
|
42 |
|
43 try { |
|
44 return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); |
|
45 } |
|
46 catch (e) {} |
|
47 |
|
48 return "en-US"; |
|
49 } |
|
50 |
|
51 // The preference that tells whether to match the OS locale |
|
52 const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; |
|
53 |
|
54 // The preference that tells what locale the user selected |
|
55 const PREF_SELECTED_LOCALE = "general.useragent.locale"; |
|
56 |
|
57 // The preference that tells where to obtain directory links |
|
58 const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directorySource"; |
|
59 |
|
60 // The frecency of a directory link |
|
61 const DIRECTORY_FRECENCY = 1000; |
|
62 |
|
63 const LINK_TYPES = Object.freeze([ |
|
64 "sponsored", |
|
65 "affiliate", |
|
66 "organic", |
|
67 ]); |
|
68 |
|
69 /** |
|
70 * Singleton that serves as the provider of directory links. |
|
71 * Directory links are a hard-coded set of links shown if a user's link |
|
72 * inventory is empty. |
|
73 */ |
|
74 let DirectoryLinksProvider = { |
|
75 |
|
76 __linksURL: null, |
|
77 |
|
78 _observers: [], |
|
79 |
|
80 get _prefs() Object.freeze({ |
|
81 linksURL: PREF_DIRECTORY_SOURCE, |
|
82 matchOSLocale: PREF_MATCH_OS_LOCALE, |
|
83 prefSelectedLocale: PREF_SELECTED_LOCALE, |
|
84 }), |
|
85 |
|
86 get _linksURL() { |
|
87 if (!this.__linksURL) { |
|
88 try { |
|
89 this.__linksURL = Services.prefs.getCharPref(this._prefs["linksURL"]); |
|
90 } |
|
91 catch (e) { |
|
92 Cu.reportError("Error fetching directory links url from prefs: " + e); |
|
93 } |
|
94 } |
|
95 return this.__linksURL; |
|
96 }, |
|
97 |
|
98 get linkTypes() LINK_TYPES, |
|
99 |
|
100 observe: function DirectoryLinksProvider_observe(aSubject, aTopic, aData) { |
|
101 if (aTopic == "nsPref:changed") { |
|
102 if (aData == this._prefs["linksURL"]) { |
|
103 delete this.__linksURL; |
|
104 } |
|
105 this._callObservers("onManyLinksChanged"); |
|
106 } |
|
107 }, |
|
108 |
|
109 _addPrefsObserver: function DirectoryLinksProvider_addObserver() { |
|
110 for (let pref in this._prefs) { |
|
111 let prefName = this._prefs[pref]; |
|
112 Services.prefs.addObserver(prefName, this, false); |
|
113 } |
|
114 }, |
|
115 |
|
116 _removePrefsObserver: function DirectoryLinksProvider_removeObserver() { |
|
117 for (let pref in this._prefs) { |
|
118 let prefName = this._prefs[pref]; |
|
119 Services.prefs.removeObserver(prefName, this); |
|
120 } |
|
121 }, |
|
122 |
|
123 /** |
|
124 * Fetches the current set of directory links. |
|
125 * @param aCallback a callback that is provided a set of links. |
|
126 */ |
|
127 _fetchLinks: function DirectoryLinksProvider_fetchLinks(aCallback) { |
|
128 try { |
|
129 NetUtil.asyncFetch(this._linksURL, (aInputStream, aResult, aRequest) => { |
|
130 let output; |
|
131 if (Components.isSuccessCode(aResult)) { |
|
132 try { |
|
133 let json = NetUtil.readInputStreamToString(aInputStream, |
|
134 aInputStream.available(), |
|
135 {charset: "UTF-8"}); |
|
136 let locale = getLocale(); |
|
137 output = JSON.parse(json)[locale]; |
|
138 } |
|
139 catch (e) { |
|
140 Cu.reportError(e); |
|
141 } |
|
142 } |
|
143 else { |
|
144 Cu.reportError(new Error("the fetch of " + this._linksURL + "was unsuccessful")); |
|
145 } |
|
146 aCallback(output || []); |
|
147 }); |
|
148 } |
|
149 catch (e) { |
|
150 Cu.reportError(e); |
|
151 aCallback([]); |
|
152 } |
|
153 }, |
|
154 |
|
155 /** |
|
156 * Gets the current set of directory links. |
|
157 * @param aCallback The function that the array of links is passed to. |
|
158 */ |
|
159 getLinks: function DirectoryLinksProvider_getLinks(aCallback) { |
|
160 this._fetchLinks(rawLinks => { |
|
161 // all directory links have a frecency of DIRECTORY_FRECENCY |
|
162 aCallback(rawLinks.map((link, position) => { |
|
163 link.frecency = DIRECTORY_FRECENCY; |
|
164 link.lastVisitDate = rawLinks.length - position; |
|
165 return link; |
|
166 })); |
|
167 }); |
|
168 }, |
|
169 |
|
170 init: function DirectoryLinksProvider_init() { |
|
171 this._addPrefsObserver(); |
|
172 }, |
|
173 |
|
174 /** |
|
175 * Return the object to its pre-init state |
|
176 */ |
|
177 reset: function DirectoryLinksProvider_reset() { |
|
178 delete this.__linksURL; |
|
179 this._removePrefsObserver(); |
|
180 this._removeObservers(); |
|
181 }, |
|
182 |
|
183 addObserver: function DirectoryLinksProvider_addObserver(aObserver) { |
|
184 this._observers.push(aObserver); |
|
185 }, |
|
186 |
|
187 _callObservers: function DirectoryLinksProvider__callObservers(aMethodName, aArg) { |
|
188 for (let obs of this._observers) { |
|
189 if (typeof(obs[aMethodName]) == "function") { |
|
190 try { |
|
191 obs[aMethodName](this, aArg); |
|
192 } catch (err) { |
|
193 Cu.reportError(err); |
|
194 } |
|
195 } |
|
196 } |
|
197 }, |
|
198 |
|
199 _removeObservers: function() { |
|
200 while (this._observers.length) { |
|
201 this._observers.pop(); |
|
202 } |
|
203 } |
|
204 }; |