!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 //@line 38 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/toolkit/mozapps/extensions/src/nsAddonRepository.js"
4 */
5
6 const Cc = Components.classes;
7 const Ci = Components.interfaces;
8 const Cr = Components.results;
9
10 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
11
12 const PREF_GETADDONS_BROWSEADDONS = "extensions.getAddons.browseAddons";
13 const PREF_GETADDONS_BROWSERECOMMENDED = "extensions.getAddons.recommended.browseURL";
14 const PREF_GETADDONS_GETRECOMMENDED = "extensions.getAddons.recommended.url";
15 const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL";
16 const PREF_GETADDONS_GETSEARCHRESULTS = "extensions.getAddons.search.url";
17
18 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
19
20 const API_VERSION = "1.1";
21
AddonSearchResult
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
22 function AddonSearchResult() {
23 }
24
25 AddonSearchResult.prototype = {
26 id: null,
27 name: null,
28 version: null,
29 summary: null,
30 description: null,
31 rating: null,
32 iconURL: null,
33 thumbnailURL: null,
34 homepageURL: null,
35 eula: null,
36 type: null,
37 xpiURL: null,
38 xpiHash: null,
39
40 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonSearchResult])
41 }
42
AddonRepository
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
43 function AddonRepository() {
44 }
45
46 AddonRepository.prototype = {
47 // The current set of results
48 _addons: null,
49
50 // Whether we are currently searching or not
51 _searching: false,
52
53 // Is this a search for recommended add-ons
54 _recommended: false,
55
56 // XHR associated with the current request
57 _request: null,
58
59 // Callback object to notify on completion
60 _callback: null,
61
62 // Maximum number of results to return
63 _maxResults: null,
64
get_homepageURL
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
65 get homepageURL() {
66 return Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
67 .getService(Components.interfaces.nsIURLFormatter)
68 .formatURLPref(PREF_GETADDONS_BROWSEADDONS);
69 },
70
get_isSearching
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
71 get isSearching() {
72 return this._searching;
73 },
74
getRecommendedURL
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
75 getRecommendedURL: function() {
76 var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
77 .getService(Components.interfaces.nsIURLFormatter);
78
79 return urlf.formatURLPref(PREF_GETADDONS_BROWSERECOMMENDED);
80 },
81
getSearchURL
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
82 getSearchURL: function(aSearchTerms) {
83 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
84 .getService(Components.interfaces.nsIPrefBranch);
85 var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
86 .getService(Components.interfaces.nsIURLFormatter);
87
88 var url = prefs.getCharPref(PREF_GETADDONS_BROWSESEARCHRESULTS);
89 url = url.replace(/%TERMS%/g, encodeURIComponent(aSearchTerms));
90 return urlf.formatURL(url);
91 },
92
cancelSearch
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
93 cancelSearch: function() {
94 this._searching = false;
95 if (this._request) {
96 this._request.abort();
97 this._request = null;
98 }
99 this._callback = null;
100 this._addons = null;
101 },
102
retrieveRecommendedAddons
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
103 retrieveRecommendedAddons: function(aMaxResults, aCallback) {
104 if (this._searching)
105 return;
106
107 this._searching = true;
108 this._addons = [];
109 this._callback = aCallback;
110 this._recommended = true;
111 this._maxResults = aMaxResults;
112
113 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
114 .getService(Components.interfaces.nsIPrefBranch);
115 var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
116 .getService(Components.interfaces.nsIURLFormatter);
117
118 var uri = prefs.getCharPref(PREF_GETADDONS_GETRECOMMENDED);
119 uri = uri.replace(/%API_VERSION%/g, API_VERSION);
120 uri = urlf.formatURL(uri);
121 this._loadList(uri);
122 },
123
searchAddons
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
124 searchAddons: function(aSearchTerms, aMaxResults, aCallback) {
125 if (this._searching)
126 return;
127
128 this._searching = true;
129 this._addons = [];
130 this._callback = aCallback;
131 this._recommended = false;
132 this._maxResults = aMaxResults;
133
134 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
135 .getService(Components.interfaces.nsIPrefBranch);
136 var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
137 .getService(Components.interfaces.nsIURLFormatter);
138
139 var uri = prefs.getCharPref(PREF_GETADDONS_GETSEARCHRESULTS);
140 uri = uri.replace(/%API_VERSION%/g, API_VERSION);
141 uri = uri.replace(/%TERMS%/g, encodeURIComponent(aSearchTerms));
142 uri = urlf.formatURL(uri);
143 this._loadList(uri);
144 },
145
146 // Posts results to the callback
_reportSuccess
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
147 _reportSuccess: function(aCount) {
148 this._searching = false;
149 this._request = null;
150 // The callback may want to trigger a new search so clear references early
151 var addons = this._addons;
152 var callback = this._callback;
153 this._callback = null;
154 this._addons = null;
155 callback.searchSucceeded(addons, addons.length, this._recommended ? -1 : aCount);
156 },
157
158 // Notifies the callback of a failure
_reportFailure
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
159 _reportFailure: function(aEvent) {
160 this._searching = false;
161 this._request = null;
162 // The callback may want to trigger a new search so clear references early
163 var callback = this._callback;
164 this._callback = null;
165 this._addons = null;
166 callback.searchFailed();
167 },
168
169 // Parses an add-on entry from an <addon> element
_parseAddon
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
170 _parseAddon: function(element) {
171 var em = Cc["@mozilla.org/extensions/manager;1"].
172 getService(Ci.nsIExtensionManager);
173 var app = Cc["@mozilla.org/xre/app-info;1"].
174 getService(Ci.nsIXULAppInfo).
175 QueryInterface(Ci.nsIXULRuntime);
176
177 var guid = element.getElementsByTagName("guid");
178 if (guid.length != 1)
179 return;
180
181 // Ignore add-ons already seen in the results
182 for (var i = 0; i < this._addons.length; i++)
183 if (this._addons[i].id == guid[0].textContent)
184 return;
185
186 // Ignore installed add-ons
187 if (em.getItemForID(guid[0].textContent) != null)
188 return;
189
190 // Ignore sandboxed add-ons
191 var status = element.getElementsByTagName("status");
192 // The status element has a unique id for each status type. 4 is Public.
193 if (status.length != 1 || status[0].getAttribute("id") != 4)
194 return;
195
196 // Ignore add-ons not compatible with this OS
197 var os = element.getElementsByTagName("compatible_os");
198 // Only the version 0 schema included compatible_os if it isn't there then
199 // we will see os compatibility on the install elements.
200 if (os.length > 0) {
201 var compatible = false;
202 var i = 0;
203 while (i < os.length && !compatible) {
204 if (os[i].textContent == "ALL" || os[i].textContent == app.OS) {
205 compatible = true;
206 break;
207 }
208 i++;
209 }
210 if (!compatible)
211 return;
212 }
213
214 // Ignore add-ons not compatible with this Application
215 compatible = false;
216 var tags = element.getElementsByTagName("compatible_applications");
217 if (tags.length != 1)
218 return;
219 var vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
220 getService(Ci.nsIVersionComparator);
221 var apps = tags[0].getElementsByTagName("appID");
222 var i = 0;
223 while (i < apps.length) {
224 if (apps[i].textContent == app.ID) {
225 var minversion = apps[i].parentNode.getElementsByTagName("min_version")[0].textContent;
226 var maxversion = apps[i].parentNode.getElementsByTagName("max_version")[0].textContent;
227 if ((vc.compare(minversion, app.version) > 0) ||
228 (vc.compare(app.version, maxversion) > 0))
229 return;
230 compatible = true;
231 break;
232 }
233 i++;
234 }
235 if (!compatible)
236 return;
237
238 var addon = new AddonSearchResult();
239 addon.id = guid[0].textContent;
240 addon.rating = -1;
241 var node = element.firstChild;
242 while (node) {
243 if (node instanceof Ci.nsIDOMElement) {
244 switch (node.localName) {
245 case "name":
246 case "version":
247 case "summary":
248 case "description":
249 case "eula":
250 addon[node.localName] = node.textContent;
251 break;
252 case "rating":
253 if (node.textContent.length > 0) {
254 var rating = parseInt(node.textContent);
255 if (rating >= 0)
256 addon.rating = Math.min(5, rating);
257 }
258 break;
259 case "thumbnail":
260 addon.thumbnailURL = node.textContent;
261 break;
262 case "icon":
263 addon.iconURL = node.textContent;
264 break;
265 case "learnmore":
266 addon.homepageURL = node.textContent;
267 break;
268 case "type":
269 // The type element has an id attribute that is the id from AMO's
270 // database. This doesn't match our type values to perform a mapping
271 if (node.getAttribute("id") == 2)
272 addon.type = Ci.nsIUpdateItem.TYPE_THEME;
273 else
274 addon.type = Ci.nsIUpdateItem.TYPE_EXTENSION;
275 break;
276 case "install":
277 // No os attribute means the xpi is compatible with any os
278 if (node.hasAttribute("os")) {
279 var os = node.getAttribute("os").toLowerCase();
280 // If the os is not ALL and not the current OS then ignore this xpi
281 if (os != "all" && os != app.OS.toLowerCase())
282 break;
283 }
284 addon.xpiURL = node.textContent;
285 if (node.hasAttribute("hash"))
286 addon.xpiHash = node.getAttribute("hash");
287 break;
288 }
289 }
290 node = node.nextSibling;
291 }
292
293 // Add only if there was an xpi compatible with this os
294 if (addon.xpiURL)
295 this._addons.push(addon);
296 },
297
298 // Called when a single request has completed, parses out any add-ons and
299 // either notifies the callback or does a new request for more results
_listLoaded
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
300 _listLoaded: function(aEvent) {
301 var request = aEvent.target;
302 var responseXML = request.responseXML;
303
304 if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
305 (request.status != 200 && request.status != 0)) {
306 this._reportFailure();
307 return;
308 }
309 var elements = responseXML.documentElement.getElementsByTagName("addon");
310 for (var i = 0; i < elements.length; i++) {
311 this._parseAddon(elements[i]);
312
313 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
314 .getService(Components.interfaces.nsIPrefBranch);
315 if (this._addons.length == this._maxResults) {
316 this._reportSuccess(elements.length);
317 return;
318 }
319 }
320
321 if (responseXML.documentElement.hasAttribute("total_results"))
322 this._reportSuccess(responseXML.documentElement.getAttribute("total_results"));
323 else
324 this._reportSuccess(elements.length);
325 },
326
327 // Performs a new request for results
_loadList
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
328 _loadList: function(aURI) {
329 this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
330 createInstance(Ci.nsIXMLHttpRequest);
331 this._request.open("GET", aURI, true);
332 this._request.overrideMimeType("text/xml");
333
334 var self = this;
anon:335:28
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
335 this._request.onerror = function(event) { self._reportFailure(event); };
anon:336:27
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
336 this._request.onload = function(event) { self._listLoaded(event); };
337 this._request.send(null);
338 },
339
340 classDescription: "Addon Repository",
341 contractID: "@mozilla.org/extensions/addon-repository;1",
342 classID: Components.ID("{8eaaf524-7d6d-4f7d-ae8b-9277b324008d}"),
343 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonRepository])
344 }
345
NSGetModule
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
346 function NSGetModule(aCompMgr, aFileSpec) {
347 return XPCOMUtils.generateModule([AddonRepository]);
348 }