/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the Extension Manager. # # The Initial Developer of the Original Code is mozilla.org # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Dave Townsend # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** */ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/AddonManager.jsm"); const PREF_GETADDONS_BROWSEADDONS = "extensions.getAddons.browseAddons"; const PREF_GETADDONS_BROWSERECOMMENDED = "extensions.getAddons.recommended.browseURL"; const PREF_GETADDONS_GETRECOMMENDED = "extensions.getAddons.recommended.url"; const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL"; const PREF_GETADDONS_GETSEARCHRESULTS = "extensions.getAddons.search.url"; const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"; const API_VERSION = "1.2"; function AddonSearchResult() { } AddonSearchResult.prototype = { id: null, name: null, version: null, summary: null, description: null, rating: null, iconURL: null, thumbnailURL: null, homepageURL: null, eula: null, type: null, xpiURL: null, xpiHash: null, QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonSearchResult]) } function AddonRepository() { } AddonRepository.prototype = { // The current set of results _addons: null, // Whether we are currently searching or not _searching: false, // Is this a search for recommended add-ons _recommended: false, // XHR associated with the current request _request: null, // Callback object to notify on completion _callback: null, // Maximum number of results to return _maxResults: null, get homepageURL() { return Components.classes["@mozilla.org/toolkit/URLFormatterService;1"] .getService(Components.interfaces.nsIURLFormatter) .formatURLPref(PREF_GETADDONS_BROWSEADDONS); }, get isSearching() { return this._searching; }, getRecommendedURL: function() { var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"] .getService(Components.interfaces.nsIURLFormatter); return urlf.formatURLPref(PREF_GETADDONS_BROWSERECOMMENDED); }, getSearchURL: function(aSearchTerms) { var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"] .getService(Components.interfaces.nsIURLFormatter); var url = prefs.getCharPref(PREF_GETADDONS_BROWSESEARCHRESULTS); url = url.replace(/%TERMS%/g, encodeURIComponent(aSearchTerms)); return urlf.formatURL(url); }, cancelSearch: function() { this._searching = false; if (this._request) { this._request.abort(); this._request = null; } this._callback = null; this._addons = null; }, retrieveRecommendedAddons: function(aMaxResults, aCallback) { if (this._searching) return; this._searching = true; this._addons = []; this._callback = aCallback; this._recommended = true; this._maxResults = aMaxResults; var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"] .getService(Components.interfaces.nsIURLFormatter); var uri = prefs.getCharPref(PREF_GETADDONS_GETRECOMMENDED); uri = uri.replace(/%API_VERSION%/g, API_VERSION); uri = urlf.formatURL(uri); this._loadList(uri); }, searchAddons: function(aSearchTerms, aMaxResults, aCallback) { if (this._searching) return; this._searching = true; this._addons = []; this._callback = aCallback; this._recommended = false; this._maxResults = aMaxResults; var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"] .getService(Components.interfaces.nsIURLFormatter); var uri = prefs.getCharPref(PREF_GETADDONS_GETSEARCHRESULTS); uri = uri.replace(/%API_VERSION%/g, API_VERSION); // We double encode due to bug 427155 uri = uri.replace(/%TERMS%/g, encodeURIComponent(encodeURIComponent(aSearchTerms))); uri = urlf.formatURL(uri); this._loadList(uri); }, // Posts results to the callback _reportSuccess: function(aCount) { this._searching = false; this._request = null; // The callback may want to trigger a new search so clear references early var addons = this._addons; var callback = this._callback; this._callback = null; this._addons = null; callback.searchSucceeded(addons, addons.length, this._recommended ? -1 : aCount); }, // Notifies the callback of a failure _reportFailure: function(aEvent) { this._searching = false; this._request = null; // The callback may want to trigger a new search so clear references early var callback = this._callback; this._callback = null; this._addons = null; callback.searchFailed(); }, // Parses an add-on entry from an element _parseAddon: function(element, known_ids) { var app = Cc["@mozilla.org/xre/app-info;1"]. getService(Ci.nsIXULAppInfo). QueryInterface(Ci.nsIXULRuntime); var guidList = element.getElementsByTagName("guid"); if (guidList.length != 1) return; var guid = guidList[0].textContent.trim(); // Ignore add-ons already seen in the results for (var i = 0; i < this._addons.length; i++) if (this._addons[i].id == guid) return; // Ignore installed add-ons if (known_ids.indexOf(guid) != -1) return; // Ignore sandboxed add-ons var status = element.getElementsByTagName("status"); // The status element has a unique id for each status type. 4 is Public. if (status.length != 1 || status[0].getAttribute("id") != 4) return; // Ignore add-ons not compatible with this OS var osList = element.getElementsByTagName("compatible_os"); // Only the version 0 schema included compatible_os if it isn't there then // we will see os compatibility on the install elements. if (osList.length > 0) { var compatible = false; var i = 0; while (i < osList.length && !compatible) { var os = osList[i].textContent.trim(); if (os == "ALL" || os == app.OS) { compatible = true; break; } i++; } if (!compatible) return; } // Ignore add-ons not compatible with this Application compatible = false; var tags = element.getElementsByTagName("compatible_applications"); if (tags.length != 1) return; var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]. getService(Ci.nsIVersionComparator); var apps = tags[0].getElementsByTagName("appID"); var i = 0; while (i < apps.length) { if (apps[i].textContent.trim() == app.ID) { var parent = apps[i].parentNode; var minversion = parent.getElementsByTagName("min_version")[0].textContent.trim(); var maxversion = parent.getElementsByTagName("max_version")[0].textContent.trim(); if ((vc.compare(minversion, app.version) > 0) || (vc.compare(app.version, maxversion) > 0)) return; compatible = true; break; } i++; } if (!compatible) return; var addon = new AddonSearchResult(); addon.id = guid; addon.rating = -1; var node = element.firstChild; while (node) { if (node instanceof Ci.nsIDOMElement) { switch (node.localName) { case "name": case "version": case "summary": case "description": case "eula": addon[node.localName] = node.textContent.trim(); break; case "rating": if (node.textContent.length > 0) { var rating = parseInt(node.textContent); if (rating >= 0) addon.rating = Math.min(5, rating); } break; case "thumbnail": addon.thumbnailURL = node.textContent.trim(); break; case "icon": addon.iconURL = node.textContent.trim(); break; case "learnmore": addon.homepageURL = node.textContent.trim(); break; case "type": // The type element has an id attribute that is the id from AMO's // database. This doesn't match our type values to perform a mapping if (node.getAttribute("id") == 2) addon.type = Ci.nsIAddonSearchResult.TYPE_THEME; else addon.type = Ci.nsIAddonSearchResult.TYPE_EXTENSION; break; case "install": // No os attribute means the xpi is compatible with any os if (node.hasAttribute("os")) { var os = node.getAttribute("os").toLowerCase(); // If the os is not ALL and not the current OS then ignore this xpi if (os != "all" && os != app.OS.toLowerCase()) break; } addon.xpiURL = node.textContent.trim(); if (node.hasAttribute("hash")) addon.xpiHash = node.getAttribute("hash"); break; } } node = node.nextSibling; } // Add only if there was an xpi compatible with this os if (addon.xpiURL) this._addons.push(addon); }, // Called when a single request has completed, parses out any add-ons and // either notifies the callback or does a new request for more results _listLoaded: function(aEvent) { var request = aEvent.target; var responseXML = request.responseXML; if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || (request.status != 200 && request.status != 0)) { this._reportFailure(); return; } var self = this; AddonManager.getAllAddons(function(addons) { var known_ids = [a.id for each(a in addons)]; var elements = responseXML.documentElement.getElementsByTagName("addon"); for (var i = 0; i < elements.length; i++) { self._parseAddon(elements[i], known_ids); var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); if (self._addons.length == self._maxResults) { self._reportSuccess(elements.length); return; } } if (responseXML.documentElement.hasAttribute("total_results")) self._reportSuccess(responseXML.documentElement.getAttribute("total_results")); else self._reportSuccess(elements.length); }); }, // Performs a new request for results _loadList: function(aURI) { this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. createInstance(Ci.nsIXMLHttpRequest); this._request.open("GET", aURI, true); this._request.overrideMimeType("text/xml"); var self = this; this._request.onerror = function(event) { self._reportFailure(event); }; this._request.onload = function(event) { self._listLoaded(event); }; this._request.send(null); }, classDescription: "Addon Repository", contractID: "@mozilla.org/extensions/addon-repository;1", classID: Components.ID("{8eaaf524-7d6d-4f7d-ae8b-9277b324008d}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonRepository]) } function NSGetModule(aCompMgr, aFileSpec) { return XPCOMUtils.generateModule([AddonRepository]); }