gecko/mobile/chrome/content/extensions.js

1114 lines
39 KiB
JavaScript

// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* ***** 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 Mozilla Mobile Browser.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark Finkle <mfinkle@mozilla.com>
*
* 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 PREFIX_ITEM_URI = "urn:mozilla:item:";
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
const PREF_GETADDONS_MAXRESULTS = "extensions.getAddons.maxResults";
const kAddonPageSize = 5;
#ifdef ANDROID
const URI_GENERIC_ICON_XPINSTALL = "drawable://alertaddons";
#else
const URI_GENERIC_ICON_XPINSTALL = "chrome://browser/skin/images/alert-addons-30.png";
#endif
const ADDONS_NOTIFICATION_NAME = "addons";
XPCOMUtils.defineLazyGetter(this, "AddonManager", function() {
Cu.import("resource://gre/modules/AddonManager.jsm");
return AddonManager;
});
XPCOMUtils.defineLazyGetter(this, "AddonRepository", function() {
Cu.import("resource://gre/modules/AddonRepository.jsm");
return AddonRepository;
});
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
Cu.import("resource://gre/modules/NetUtil.jsm");
return NetUtil;
});
var ExtensionsView = {
_strings: {},
_list: null,
_localItem: null,
_repoItem: null,
_msg: null,
_dloadmgr: null,
_restartCount: 0,
_observerIndex: -1,
_getOpTypeForOperations: function ev__getOpTypeForOperations(aOperations) {
if (aOperations & AddonManager.PENDING_UNINSTALL)
return "needs-uninstall";
if (aOperations & AddonManager.PENDING_ENABLE)
return "needs-enable";
if (aOperations & AddonManager.PENDING_DISABLE)
return "needs-disable";
return "";
},
_createItem: function ev__createItem(aAddon, aTypeName) {
let item = document.createElement("richlistitem");
item.setAttribute("id", PREFIX_ITEM_URI + aAddon.id);
item.setAttribute("addonID", aAddon.id);
item.setAttribute("typeName", aTypeName);
item.setAttribute("type", aAddon.type);
item.setAttribute("typeLabel", this._strings["addonType." + aAddon.type]);
item.setAttribute("name", aAddon.name);
item.setAttribute("version", aAddon.version);
item.setAttribute("iconURL", aAddon.iconURL);
item.setAttribute("class", "panel-listitem");
return item;
},
clearSection: function ev_clearSection(aSection) {
let start = null;
let end = null;
if (aSection == "local") {
start = this._localItem;
end = this._repoItem;
}
else {
start = this._repoItem;
}
while (start.nextSibling != end)
this._list.removeChild(start.nextSibling);
},
_messageActions: function ev__messageActions(aData) {
if (aData == "addons-restart-app") {
// Notify all windows that an application quit has been requested
var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
// If nothing aborted, quit the app
if (cancelQuit.data == false) {
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
}
}
},
getElementForAddon: function ev_getElementForAddon(aKey) {
let element = document.getElementById(PREFIX_ITEM_URI + aKey);
if (!element && this._list)
element = this._list.getElementsByAttribute("sourceURL", aKey)[0];
return element;
},
showMessage: function ev_showMessage(aMsg, aValue, aButtonLabel, aShowCloseButton, aNotifyData) {
let notification = this._msg.getNotificationWithValue(aValue);
if (notification)
return;
let self = this;
let buttons = null;
if (aButtonLabel) {
buttons = [ {
label: aButtonLabel,
accessKey: "",
data: aNotifyData,
callback: function(aNotification, aButton) {
self._messageActions(aButton.data);
return true;
}
} ];
}
this._msg.appendNotification(aMsg, aValue, "", this._msg.PRIORITY_WARNING_LOW, buttons).hideclose = !aShowCloseButton;
},
showRestart: function ev_showRestart(aMode) {
// Increment the count in case the view is not completely initialized
this._restartCount++;
// Pick the right message key from the properties file
aMode = aMode || "normal";
if (this._msg) {
this.hideAlerts();
let strings = Strings.browser;
let message = "notificationRestart." + aMode;
this.showMessage(strings.GetStringFromName(message), "restart-app",
strings.GetStringFromName("notificationRestart.button"), false, "addons-restart-app");
}
},
hideRestart: function ev_hideRestart() {
this._restartCount--;
if (this._restartCount == 0 && this._msg) {
let notification = this._msg.getNotificationWithValue("restart-app");
if (notification)
notification.close();
}
},
showOptions: function ev_showOptions(aID) {
this.hideOptions();
let item = this.getElementForAddon(aID);
if (!item)
return;
// if the element is not the selected element, select it
if (item != this._list.selectedItem)
this._list.selectedItem = item;
item.showOptions();
},
hideOptions: function ev_hideOptions() {
if (!this._list)
return;
let items = this._list.childNodes;
for (let i = 0; i < items.length; i++) {
let item = items[i];
if (item.hideOptions)
item.hideOptions();
}
this._list.ensureSelectedElementIsVisible();
},
get visible() {
let items = document.getElementById("panel-items");
if (BrowserUI.isPanelVisible() && items.selectedPanel.id == "addons-container")
return true;
return false;
},
init: function ev_init() {
if (this._dloadmgr)
return;
this._dloadmgr = new AddonInstallListener();
AddonManager.addInstallListener(this._dloadmgr);
// Watch for add-on update notifications
let os = Services.obs;
os.addObserver(this, "addon-update-started", false);
os.addObserver(this, "addon-update-ended", false);
},
delayedInit: function ev__delayedInit() {
if (this._list)
return;
this.init(); // In case the panel is selected before init has been called.
this._list = document.getElementById("addons-list");
this._localItem = document.getElementById("addons-local");
this._repoItem = document.getElementById("addons-repo");
this._msg = document.getElementById("addons-messages");
// Show the restart notification in case a restart is needed, but the view
// was not visible at the time
let notification = this._msg.getNotificationWithValue("restart-app");
if (this._restartCount > 0 && !notification) {
this.showRestart();
this._restartCount--; // showRestart() always increments
}
let strings = Strings.browser;
this._strings["addonType.extension"] = strings.GetStringFromName("addonType.2");
this._strings["addonType.theme"] = strings.GetStringFromName("addonType.4");
this._strings["addonType.locale"] = strings.GetStringFromName("addonType.8");
this._strings["addonType.search"] = strings.GetStringFromName("addonType.1024");
if (!Services.prefs.getBoolPref("extensions.hideUpdateButton"))
document.getElementById("addons-update-all").hidden = false;
let self = this;
setTimeout(function() {
self.getAddonsFromLocal();
self.getAddonsFromRepo("");
}, 0);
},
uninit: function ev_uninit() {
let os = Services.obs;
os.removeObserver(this, "addon-update-started");
os.removeObserver(this, "addon-update-ended");
AddonManager.removeInstallListener(this._dloadmgr);
this.hideAlerts();
},
hideOnSelect: function ev_handleEvent(aEvent) {
// When list selection changes, be sure to close up any open options sections
if (aEvent.target == this._list)
this.hideOptions();
},
_createLocalAddon: function ev__createLocalAddon(aAddon) {
let strings = Strings.browser;
let appManaged = (aAddon.scope == AddonManager.SCOPE_APPLICATION);
let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
let updateable = (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) > 0;
let uninstallable = (aAddon.permissions & AddonManager.PERM_CAN_UNINSTALL) > 0;
let blocked = "";
switch(aAddon.blocklistState) {
case Ci.nsIBlocklistService.STATE_BLOCKED:
blocked = strings.getString("addonBlocked.blocked")
break;
case Ci.nsIBlocklistService.STATE_SOFTBLOCKED:
blocked = strings.getString("addonBlocked.softBlocked");
break;
case Ci.nsIBlocklistService.STATE_OUTDATED:
blocked = srings.getString("addonBlocked.outdated");
break;
}
let listitem = this._createItem(aAddon, "local");
listitem.setAttribute("isDisabled", !aAddon.isActive);
listitem.setAttribute("appDisabled", aAddon.appDisabled);
listitem.setAttribute("appManaged", appManaged);
listitem.setAttribute("description", aAddon.description);
listitem.setAttribute("optionsURL", aAddon.optionsURL || "");
listitem.setAttribute("opType", opType);
listitem.setAttribute("updateable", updateable);
listitem.setAttribute("isReadonly", !uninstallable);
if (blocked)
listitem.setAttribute("blockedStatus", blocked);
listitem.addon = aAddon;
return listitem;
},
getAddonsFromLocal: function ev_getAddonsFromLocal() {
this.clearSection("local");
let self = this;
AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(items) {
let strings = Strings.browser;
let anyUpdateable = false;
for (let i = 0; i < items.length; i++) {
let listitem = self.getElementForAddon(items[i].id)
if (!listitem)
listitem = self._createLocalAddon(items[i]);
if ((items[i].permissions & AddonManager.PERM_CAN_UPGRADE) > 0)
anyUpdateable = true;
self.addItem(listitem);
}
// Load the search engines
let defaults = Services.search.getDefaultEngines({ }).map(function (e) e.name);
function isDefault(aEngine)
defaults.indexOf(aEngine.name) != -1
let defaultDescription = strings.GetStringFromName("addonsSearchEngine.description");
let engines = Services.search.getEngines({ });
for (let e = 0; e < engines.length; e++) {
let engine = engines[e];
let addon = {};
addon.id = engine.name;
addon.type = "search";
addon.name = engine.name;
addon.version = "";
addon.iconURL = engine.iconURI ? engine.iconURI.spec : "";
let listitem = self._createItem(addon, "searchplugin");
listitem._engine = engine;
listitem.setAttribute("isDisabled", engine.hidden ? "true" : "false");
listitem.setAttribute("appDisabled", "false");
listitem.setAttribute("appManaged", isDefault(engine));
listitem.setAttribute("description", engine.description || defaultDescription);
listitem.setAttribute("optionsURL", "");
listitem.setAttribute("opType", engine.hidden ? "needs-disable" : "");
listitem.setAttribute("updateable", "false");
self.addItem(listitem);
}
if (engines.length + items.length == 0)
self.displaySectionMessage("local", strings.GetStringFromName("addonsLocalNone.label"), null, true);
if (!anyUpdateable)
document.getElementById("addons-update-all").disabled = true;
});
},
addItem : function ev_addItem(aItem, aPosition) {
if (aPosition == "repo")
return this._list.appendChild(aItem);
else if (aPosition == "local")
return this._list.insertBefore(aItem, this._localItem.nextSibling);
else
return this._list.insertBefore(aItem, this._repoItem);
},
removeItem : function ev_moveItem(aItem) {
this._list.removeChild(aItem);
},
enable: function ev_enable(aItem) {
let opType;
if (aItem.getAttribute("type") == "search") {
aItem.setAttribute("isDisabled", false);
aItem._engine.hidden = false;
opType = "needs-enable";
} else if (aItem.getAttribute("type") == "theme") {
// we can have only one theme enabled, so disable the current one if any
let theme = null;
let item = this._localItem.nextSibling;
while (item != this._repoItem) {
if (item.addon && (item.addon.type == "theme") && (item.addon.isActive)) {
theme = item;
break;
}
item = item.nextSibling;
}
if (theme)
this.disable(theme);
aItem.addon.userDisabled = false;
aItem.setAttribute("isDisabled", false);
} else {
aItem.addon.userDisabled = false;
opType = this._getOpTypeForOperations(aItem.addon.pendingOperations);
if (aItem.addon.pendingOperations & AddonManager.PENDING_ENABLE) {
this.showRestart();
} else {
aItem.removeAttribute("isDisabled");
if (aItem.getAttribute("opType") == "needs-disable")
this.hideRestart();
};
}
aItem.setAttribute("opType", opType);
},
_getLocalesInAddon: function(aAddon, aCallback) {
if (!aCallback || typeof aCallback != "function")
throw "_getLocalesInAddon requires a callback function";
let uri = aAddon.getResourceURI("chrome.manifest");
NetUtil.asyncFetch(uri, function(aStream, aResult, aRequest) {
var data = NetUtil.readInputStreamToString(aStream, aStream.available());
let reg = new RegExp("locale browser ([a-zA-Z\-]*)", "g");
let res = reg.exec(data)
let list = [];
while(res) {
if (list.indexOf(res[1]) == -1)
list.push(res[1]);
res = reg.exec(data);
}
if (aCallback)
aCallback(list);
});
},
_resetLanguagePref: function(aAddon) {
this._getLocalesInAddon(aAddon, function(aLocales) {
let currentLocale = Services.prefs.getCharPref("general.useragent.locale");
if (aLocales.indexOf(currentLocale) > -1)
Services.prefs.clearUserPref("general.useragent.locale");
});
},
disable: function ev_disable(aItem) {
let opType;
if (aItem.getAttribute("type") == "search") {
aItem.setAttribute("isDisabled", true);
aItem._engine.hidden = true;
opType = "needs-disable";
} else if (aItem.getAttribute("type") == "theme") {
aItem.addon.userDisabled = true;
aItem.setAttribute("isDisabled", true);
} else if (aItem.getAttribute("type") == "locale") {
this._resetLanguagePref(aItem.addon);
aItem.addon.userDisabled = true;
aItem.setAttribute("isDisabled", true);
} else {
aItem.addon.userDisabled = true;
opType = this._getOpTypeForOperations(aItem.addon.pendingOperations);
if (aItem.addon.pendingOperations & AddonManager.PENDING_DISABLE) {
this.showRestart();
} else {
aItem.setAttribute("isDisabled", !aItem.addon.isActive);
if (aItem.getAttribute("opType") == "needs-enable")
this.hideRestart();
}
}
aItem.setAttribute("opType", opType);
},
uninstall: function ev_uninstall(aItem) {
let opType;
if (aItem.getAttribute("type") == "search") {
// Make sure the engine isn't hidden before removing it, to make sure it's
// visible if the user later re-adds it (works around bug 341833)
aItem._engine.hidden = false;
Services.search.removeEngine(aItem._engine);
// the search-engine-modified observer in browser.js will take care of
// updating the list
} else {
if (!aItem.addon) {
this._list.removeChild(aItem);
return;
}
aItem.addon.uninstall();
opType = this._getOpTypeForOperations(aItem.addon.pendingOperations);
if (aItem.addon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
this.showRestart();
// A disabled addon doesn't need a restart so it has no pending ops and
// can't be cancelled
if (!aItem.addon.isActive && opType == "")
opType = "needs-uninstall";
aItem.setAttribute("opType", opType);
} else {
this._list.removeChild(aItem);
}
if (aItem.getAttribute("type") == "locale")
this._resetLanguagePref(aItem.addon);
}
},
cancelUninstall: function ev_cancelUninstall(aItem) {
aItem.addon.cancelUninstall();
this.hideRestart();
let opType = this._getOpTypeForOperations(aItem.addon.pendingOperations);
aItem.setAttribute("opType", opType);
},
installFromRepo: function ev_installFromRepo(aItem) {
aItem.install.install();
// display the progress bar early
let opType = aItem.getAttribute("opType");
if (!opType)
aItem.setAttribute("opType", "needs-install");
},
_isSafeURI: function ev_isSafeURI(aURL) {
if (!aURL)
return true;
try {
var uri = Services.io.newURI(aURL, null, null);
var scheme = uri.scheme;
} catch (ex) {}
return (uri && (scheme == "http" || scheme == "https" || scheme == "ftp"));
},
displaySectionMessage: function ev_displaySectionMessage(aSection, aMessage, aButtonLabel, aHideThrobber) {
let item = document.createElement("richlistitem");
item.setAttribute("typeName", "message");
item.setAttribute("message", aMessage);
if (aButtonLabel)
item.setAttribute("button", aButtonLabel);
else
item.setAttribute("hidebutton", "true");
item.setAttribute("hidethrobber", aHideThrobber);
this.addItem(item, aSection);
return item;
},
getAddonsFromRepo: function ev_getAddonsFromRepo(aTerms, aSelectFirstResult) {
this.clearSection("repo");
// Make sure we're online before attempting to load
Util.forceOnline();
if (AddonRepository.isSearching)
AddonRepository.cancelSearch();
let strings = Strings.browser;
if (aTerms) {
AddonSearchResults.selectFirstResult = aSelectFirstResult;
this.displaySectionMessage("repo", strings.GetStringFromName("addonsSearchStart.label"), strings.GetStringFromName("addonsSearchStart.button"), false);
AddonRepository.searchAddons(aTerms, Services.prefs.getIntPref(PREF_GETADDONS_MAXRESULTS), AddonSearchResults);
}
else {
this.displaySectionMessage("repo", strings.GetStringFromName("addonsSearchStart.label"), strings.GetStringFromName("addonsSearchStart.button"), false);
AddonRepository.retrieveRecommendedAddons(Services.prefs.getIntPref(PREF_GETADDONS_MAXRESULTS), RecommendedSearchResults);
}
},
appendSearchResults: function(aAddons, aShowRating, aShowCount) {
let urlproperties = [ "iconURL", "homepageURL" ];
let foundItem = false;
let appendedAddons = [];
for (let i = 0; i < aAddons.length; i++) {
let addon = aAddons[i];
// Check for a duplicate add-on, already in the search results
// (can happen when blending the recommended and browsed lists)
let element = ExtensionsView.getElementForAddon(addon.install.sourceURI.spec);
if (element)
continue;
// Check for any items with potentially unsafe urls
if (urlproperties.some(function (p) !this._isSafeURI(addon[p]), this))
continue;
if (addon.screenshots && addon.screenshots.length) {
if (addon.screenshots.some(function (aScreenshot) !this._isSafeURI(aScreenshot), this))
continue;
}
// Convert the numeric type to a string
let types = {"2":"extension", "4":"theme", "8":"locale"};
addon.type = types[addon.type];
let listitem = this._createItem(addon, "search");
listitem.setAttribute("description", addon.description);
if (addon.homepageURL)
listitem.setAttribute("homepageURL", addon.homepageURL);
listitem.install = addon.install;
listitem.setAttribute("sourceURL", addon.install.sourceURI.spec);
if (aShowRating)
listitem.setAttribute("rating", addon.averageRating);
let item = this.addItem(listitem, "repo");
appendedAddons.push(listitem);
// Hide any overflow add-ons. The user can see them later by pressing the
// "See More" button
aShowCount--;
if (aShowCount < 0)
item.hidden = true;
}
return appendedAddons;
},
showMoreSearchResults: function showMoreSearchResults() {
// Show more add-ons, if we have them
let showCount = kAddonPageSize;
// Find the first hidden add-on
let item = this._repoItem.nextSibling;
while (item && !item.hidden)
item = item.nextSibling;
// Start showing the hidden add-ons
while (showCount > 0 && item && item.hidden) {
showCount--;
item.hidden = false;
item = item.nextSibling;
}
// Hide the "Show More" button if there are no more to show
if (item == this._list.lastChild)
item.setAttribute("hidepage", "true");
},
displayRecommendedResults: function ev_displaySearchResults(aRecommendedAddons, aBrowseAddons) {
this.clearSection("repo");
let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
let browseURL = formatter.formatURLPref("extensions.getAddons.browseAddons");
let strings = Strings.browser;
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
let whatare = document.createElement("richlistitem");
whatare.setAttribute("typeName", "banner");
whatare.setAttribute("class", "panel-listitem");
whatare.setAttribute("label", strings.GetStringFromName("addonsWhatAre.label"));
let desc = strings.GetStringFromName("addonsWhatAre.description");
desc = desc.replace(/#1/g, brandShortName);
whatare.setAttribute("description", desc);
whatare.setAttribute("button", strings.GetStringFromName("addonsWhatAre.button"));
whatare.setAttribute("onbuttoncommand", "BrowserUI.newTab('" + browseURL + "', Browser.selectedTab);");
this.addItem(whatare, "repo");
if (aRecommendedAddons.length == 0 && aBrowseAddons.length == 0) {
let msg = strings.GetStringFromName("addonsSearchNone.recommended");
let button = strings.GetStringFromName("addonsSearchNone.button");
let item = this.displaySectionMessage("repo", msg, button, true);
this._list.scrollBoxObject.scrollToElement(item);
return;
}
// Locale sensitive sort
function nameCompare(a, b) {
return String.localeCompare(a.name, b.name);
}
aRecommendedAddons.sort(nameCompare);
// Rating sort
function ratingCompare(a, b) {
return a.averageRating < b.averageRating;
}
aBrowseAddons.sort(ratingCompare);
// We only show extra browse add-ons if the recommended count is small. Otherwise, the user
// can see more by pressing the "Show More" button
this.appendSearchResults(aRecommendedAddons, false, aRecommendedAddons.length);
let minOverflow = (aRecommendedAddons.length >= kAddonPageSize ? 0 : kAddonPageSize);
let numAdded = this.appendSearchResults(aBrowseAddons, true, minOverflow).length;
let totalAddons = aRecommendedAddons.length + numAdded;
let showmore = document.createElement("richlistitem");
showmore.setAttribute("typeName", "showmore");
showmore.setAttribute("pagelabel", strings.GetStringFromName("addonsBrowseAll.seeMore"));
showmore.setAttribute("onpagecommand", "ExtensionsView.showMoreSearchResults();");
showmore.setAttribute("hidepage", numAdded > minOverflow ? "false" : "true");
showmore.setAttribute("sitelabel", strings.GetStringFromName("addonsBrowseAll.browseSite"));
showmore.setAttribute("onsitecommand", "ExtensionsView.showMoreResults('" + browseURL + "');");
this.addItem(showmore, "repo");
let evt = document.createEvent("Events");
evt.initEvent("ViewChanged", true, false);
this._list.dispatchEvent(evt);
},
displaySearchResults: function ev_displaySearchResults(aAddons, aTotalResults, aSelectFirstResult) {
this.clearSection("repo");
let strings = Strings.browser;
if (aAddons.length == 0) {
let msg = strings.GetStringFromName("addonsSearchNone.search");
let button = strings.GetStringFromName("addonsSearchSuccess2.button");
let item = this.displaySectionMessage("repo", msg, button, true);
if (aSelectFirstResult)
this._list.scrollBoxObject.scrollToElement(item);
return;
}
let firstAdded = this.appendSearchResults(aAddons, true)[0];
if (aSelectFirstResult && firstAdded) {
this._list.selectItem(firstAdded);
this._list.scrollBoxObject.scrollToElement(firstAdded);
}
let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
if (aTotalResults > aAddons.length) {
let showmore = document.createElement("richlistitem");
showmore.setAttribute("typeName", "showmore");
showmore.setAttribute("hidepage", "true");
let labelBase = strings.GetStringFromName("addonsSearchMore.label");
let label = PluralForm.get(aTotalResults, labelBase).replace("#1", aTotalResults);
showmore.setAttribute("sitelabel", label);
let url = Services.prefs.getCharPref("extensions.getAddons.search.browseURL");
url = url.replace(/%TERMS%/g, encodeURIComponent(this.searchBox.value));
url = formatter.formatURL(url);
showmore.setAttribute("onsitecommand", "ExtensionsView.showMoreResults('" + url + "');");
this.addItem(showmore, "repo");
}
this.displaySectionMessage("repo", null, strings.GetStringFromName("addonsSearchSuccess2.button"), true);
},
showPage: function ev_showPage(aItem) {
let uri = aItem.getAttribute("homepageURL");
if (uri)
BrowserUI.newTab(uri, Browser.selectedTab);
},
get searchBox() {
delete this.searchBox;
return this.searchBox = document.getElementById("addons-search-text");
},
doSearch: function ev_doSearch(aTerms) {
this.searchBox.value = aTerms;
this.getAddonsFromRepo(aTerms, true);
},
resetSearch: function ev_resetSearch() {
this.searchBox.value = "";
this.getAddonsFromRepo("");
},
showMoreResults: function ev_showMoreResults(aURL) {
if (aURL)
BrowserUI.newTab(aURL, Browser.selectedTab);
},
updateAll: function ev_updateAll() {
let aus = Cc["@mozilla.org/browser/addon-update-service;1"].getService(Ci.nsITimerCallback);
aus.notify(null);
if (this._list.selectedItem)
this._list.selectedItem.focus();
},
observe: function ev_observe(aSubject, aTopic, aData) {
if (!document)
return;
let json = aSubject.QueryInterface(Ci.nsISupportsString).data;
let update = JSON.parse(json);
let strings = Strings.browser;
let element = this.getElementForAddon(update.id);
if (!element)
return;
let addon = element.addon;
switch (aTopic) {
case "addon-update-started":
element.setAttribute("updateStatus", strings.GetStringFromName("addonUpdate.checking"));
break;
case "addon-update-ended":
let updateable = false;
let statusMsg = null;
switch (aData) {
case "update":
statusMsg = strings.formatStringFromName("addonUpdate.updating", [update.version], 1);
updateable = true;
break;
case "compatibility":
if (addon.pendingOperations & AddonManager.PENDING_INSTALL || addon.pendingOperations & AddonManager.PENDING_UPGRADE)
updateable = true;
// A compatibility update may require a restart, but will not fire an install
if (addon.pendingOperations & AddonManager.PENDING_ENABLE &&
addon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE) {
statusMsg = strings.GetStringFromName("addonUpdate.compatibility");
this.showRestart();
}
break;
case "error":
statusMsg = strings.GetStringFromName("addonUpdate.error");
break;
case "no-update":
// Ignore if no updated was found. Just let the message go blank.
//statusMsg = strings.GetStringFromName("addonUpdate.noupdate");
break;
default:
// Ignore if no updated was found. Just let the message go blank.
//statusMsg = strings.GetStringFromName("addonUpdate.noupdate");
}
if (statusMsg)
element.setAttribute("updateStatus", statusMsg);
else
element.removeAttribute("updateStatus");
// Tag the add-on so the AddonInstallListener knows it's an update
if (updateable)
element.setAttribute("updating", "true");
break;
}
},
showAlert: function ev_showAlert(aMessage, aForceDisplay) {
let strings = Strings.browser;
let observer = {
observe: function (aSubject, aTopic, aData) {
if (aTopic == "alertclickcallback")
BrowserUI.showPanel("addons-container");
}
};
if (aForceDisplay) {
// Show a toaster alert for restartless add-ons all the time
let toaster = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService);
let image = "chrome://browser/skin/images/alert-addons-30.png";
if (this.visible)
toaster.showAlertNotification(image, strings.GetStringFromName("alertAddons"), aMessage, false, "", null);
else
toaster.showAlertNotification(image, strings.GetStringFromName("alertAddons"), aMessage, true, "", observer);
} else {
// Only show an alert for a normal add-on if the manager is not visible
if (!this.visible) {
let alerts = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
alerts.showAlertNotification(URI_GENERIC_ICON_XPINSTALL, strings.GetStringFromName("alertAddons"),
aMessage, true, "", observer, ADDONS_NOTIFICATION_NAME);
// Use a preference to help us cleanup this notification in case we don't shutdown correctly
Services.prefs.setBoolPref("browser.notifications.pending.addons", true);
Services.prefs.savePrefFile(null);
}
}
},
hideAlerts: function ev_hideAlerts() {
#ifdef ANDROID
let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
if (progressListener)
progressListener.onCancel(ADDONS_NOTIFICATION_NAME);
#endif
// Keep our preference in sync
Services.prefs.clearUserPref("browser.notifications.pending.addons");
},
};
function searchFailed() {
ExtensionsView.clearSection("repo");
let strings = Strings.browser;
let brand = Strings.brand;
let failLabel = strings.formatStringFromName("addonsSearchFail.label",
[brand.GetStringFromName("brandShortName")], 1);
let failButton = strings.GetStringFromName("addonsSearchFail.retryButton");
ExtensionsView.displaySectionMessage("repo", failLabel, failButton, true);
}
///////////////////////////////////////////////////////////////////////////////
// callback for the recommended search
var RecommendedSearchResults = {
cache: null,
searchSucceeded: function(aAddons, aAddonCount, aTotalResults) {
this.cache = aAddons;
AddonRepository.searchAddons(" ", Services.prefs.getIntPref(PREF_GETADDONS_MAXRESULTS), BrowseSearchResults);
},
searchFailed: searchFailed
}
///////////////////////////////////////////////////////////////////////////////
// callback for the browse search
var BrowseSearchResults = {
searchSucceeded: function(aAddons, aAddonCount, aTotalResults) {
ExtensionsView.displayRecommendedResults(RecommendedSearchResults.cache, aAddons);
},
searchFailed: searchFailed
}
///////////////////////////////////////////////////////////////////////////////
// callback for a standard search
var AddonSearchResults = {
// set by ExtensionsView
selectFirstResult: false,
searchSucceeded: function(aAddons, aAddonCount, aTotalResults) {
ExtensionsView.displaySearchResults(aAddons, aTotalResults, this.selectFirstResult);
},
searchFailed: searchFailed
}
///////////////////////////////////////////////////////////////////////////////
// XPInstall download helper
function AddonInstallListener() {
}
AddonInstallListener.prototype = {
onInstallEnded: function(aInstall, aAddon) {
let needsRestart = false;
let mode = "";
if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE)) {
needsRestart = true;
mode = "update";
} else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) {
needsRestart = true;
mode = "normal";
}
this._clearRecommendedCache();
// if we already have a mode, then we need to show a restart notification
// otherwise, we are likely a bootstrapped addon
if (needsRestart)
ExtensionsView.showRestart(mode);
if (aAddon.type != "locale")
this._showInstallCompleteAlert(true, needsRestart);
// only do this if the view has already been inited
if (!ExtensionsView._list)
return;
let element = ExtensionsView.getElementForAddon(aAddon.id);
if (!element) {
element = ExtensionsView._createLocalAddon(aAddon);
ExtensionsView.addItem(element, "local");
}
if (needsRestart) {
element.setAttribute("opType", "needs-restart");
} else {
if (element.getAttribute("typeName") == "search") {
if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)
document.getElementById("addons-update-all").disabled = false;
ExtensionsView.removeItem(element);
element = ExtensionsView._createLocalAddon(aAddon);
ExtensionsView.addItem(element, "local");
}
}
element.setAttribute("status", "success");
// If we are updating an add-on, change the status
if (element.hasAttribute("updating")) {
let strings = Strings.browser;
element.setAttribute("updateStatus", strings.formatStringFromName("addonUpdate.updated", [aAddon.version], 1));
element.removeAttribute("updating");
}
},
onInstallFailed: function(aInstall) {
this._showInstallCompleteAlert(false);
if (ExtensionsView.visible) {
let element = ExtensionsView.getElementForAddon(aInstall.sourceURI.spec);
if (!element)
return;
element.removeAttribute("opType");
let strings = Services.strings.createBundle("chrome://global/locale/xpinstall/xpinstall.properties");
let error = null;
switch (aInstall.error) {
case AddonManager.ERROR_NETWORK_FAILURE:
error = "error-228";
break;
case AddonManager.ERROR_INCORRECT_HASH:
error = "error-261";
break;
case AddonManager.ERROR_CORRUPT_FILE:
error = "error-207";
break;
}
try {
var msg = strings.GetStringFromName(error);
} catch (ex) {
msg = strings.formatStringFromName("unknown.error", [aInstall.error], 1);
}
element.setAttribute("error", msg);
}
},
onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {
let element = ExtensionsView.getElementForAddon(aInstall.sourceURI.spec);
#ifdef ANDROID
let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
if (progressListener)
progressListener.onProgress(ADDONS_NOTIFICATION_NAME, aInstall.progress, aInstall.maxProgress);
#endif
if (!element)
return;
let opType = element.getAttribute("opType");
if (!opType)
element.setAttribute("opType", "needs-install");
let progress = Math.round((aInstall.progress / aInstall.maxProgress) * 100);
element.setAttribute("progress", progress);
},
onDownloadFailed: function(aInstall) {
this.onInstallFailed(aInstall);
},
onDownloadCancelled: function(aInstall) {
let strings = Strings.browser;
let brandBundle = Strings.brand;
let brandShortName = brandBundle.GetStringFromName("brandShortName");
let host = (aInstall.originatingURI instanceof Ci.nsIStandardURL) && aInstall.originatingURI.host;
if (!host)
host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) && aInstall.sourceURI.host;
let error = (host || aInstall.error == 0) ? "addonError" : "addonLocalError";
if (aInstall.error != 0)
error += aInstall.error;
else if (aInstall.addon && aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
error += "Blocklisted";
else if (aInstall.addon && (!aInstall.addon.isCompatible || !aInstall.addon.isPlatformCompatible))
error += "Incompatible";
else {
ExtensionsView.hideAlerts();
return; // no need to show anything in this case
}
let messageString = strings.GetStringFromName(error);
messageString = messageString.replace("#1", aInstall.name);
if (host)
messageString = messageString.replace("#2", host);
messageString = messageString.replace("#3", brandShortName);
messageString = messageString.replace("#4", Services.appinfo.version);
ExtensionsView.showAlert(messageString);
},
_showInstallCompleteAlert: function xpidm_showAlert(aSucceeded, aNeedsRestart) {
let strings = Strings.browser;
let stringName = "alertAddonsFail";
if (aSucceeded) {
stringName = "alertAddonsInstalled";
if (!aNeedsRestart)
stringName += "NoRestart";
}
ExtensionsView.showAlert(strings.GetStringFromName(stringName), !aNeedsRestart);
},
_clearRecommendedCache: function xpidm_clearRecommendedCache() {
let dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
let file = dirService.get("ProfD", Ci.nsILocalFile);
file.append("recommended-addons.json");
if (file.exists())
file.remove(false);
}
};