diff options
Diffstat (limited to 'mobile/android/chrome/content/aboutAddons.js')
-rw-r--r-- | mobile/android/chrome/content/aboutAddons.js | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/mobile/android/chrome/content/aboutAddons.js b/mobile/android/chrome/content/aboutAddons.js new file mode 100644 index 000000000..becf56a32 --- /dev/null +++ b/mobile/android/chrome/content/aboutAddons.js @@ -0,0 +1,609 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/*globals gChromeWin */ + +var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm") +Cu.import("resource://gre/modules/AddonManager.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const AMO_ICON = "chrome://browser/skin/images/amo-logo.png"; + +var gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutAddons.properties"); + +XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow) + .QueryInterface(Ci.nsIDOMChromeWindow); +}); +XPCOMUtils.defineLazyModuleGetter(window, "Preferences", + "resource://gre/modules/Preferences.jsm"); + +var ContextMenus = { + target: null, + + init: function() { + document.addEventListener("contextmenu", this, false); + + document.getElementById("contextmenu-enable").addEventListener("click", ContextMenus.enable.bind(this), false); + document.getElementById("contextmenu-disable").addEventListener("click", ContextMenus.disable.bind(this), false); + document.getElementById("contextmenu-uninstall").addEventListener("click", ContextMenus.uninstall.bind(this), false); + + // XXX - Hack to fix bug 985867 for now + document.addEventListener("touchstart", function() { }); + }, + + handleEvent: function(event) { + // store the target of context menu events so that we know which app to act on + this.target = event.target; + while (!this.target.hasAttribute("contextmenu")) { + this.target = this.target.parentNode; + } + + if (!this.target) { + document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); + document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); + document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); + return; + } + + let addon = this.target.addon; + if (addon.scope == AddonManager.SCOPE_APPLICATION) { + document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); + } else { + document.getElementById("contextmenu-uninstall").removeAttribute("hidden"); + } + + // Hide the enable/disable context menu items if the add-on was disabled by + // Firefox (e.g. unsigned or blocklisted add-on). + if (addon.appDisabled) { + document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); + document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); + return; + } + + let enabled = this.target.getAttribute("isDisabled") != "true"; + if (enabled) { + document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); + document.getElementById("contextmenu-disable").removeAttribute("hidden"); + } else { + document.getElementById("contextmenu-enable").removeAttribute("hidden"); + document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); + } + }, + + enable: function(event) { + Addons.setEnabled(true, this.target.addon); + this.target = null; + }, + + disable: function (event) { + Addons.setEnabled(false, this.target.addon); + this.target = null; + }, + + uninstall: function (event) { + Addons.uninstall(this.target.addon); + this.target = null; + } +} + +function init() { + window.addEventListener("popstate", onPopState, false); + + AddonManager.addInstallListener(Addons); + AddonManager.addAddonListener(Addons); + Addons.init(); + showList(); + ContextMenus.init(); +} + + +function uninit() { + AddonManager.removeInstallListener(Addons); + AddonManager.removeAddonListener(Addons); +} + +function openLink(url) { + let BrowserApp = gChromeWin.BrowserApp; + BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }); +} + +function onPopState(aEvent) { + // Called when back/forward is used to change the state of the page + if (aEvent.state) { + // Show the detail page for an addon + Addons.showDetails(Addons._getElementForAddon(aEvent.state.id)); + } else { + // Clear any previous detail addon + let detailItem = document.querySelector("#addons-details > .addon-item"); + detailItem.addon = null; + + showList(); + } +} + +function showList() { + // Hide the detail page and show the list + let details = document.querySelector("#addons-details"); + details.style.display = "none"; + let list = document.querySelector("#addons-list"); + list.style.display = "block"; + document.documentElement.removeAttribute("details"); +} + +var Addons = { + _restartCount: 0, + + _createItem: function _createItem(aAddon) { + let outer = document.createElement("div"); + outer.setAttribute("addonID", aAddon.id); + outer.className = "addon-item list-item"; + outer.setAttribute("role", "button"); + outer.setAttribute("contextmenu", "addonmenu"); + outer.addEventListener("click", function() { + this.showDetails(outer); + history.pushState({ id: aAddon.id }, document.title); + }.bind(this), true); + + let img = document.createElement("img"); + img.className = "icon"; + img.setAttribute("src", aAddon.iconURL || AMO_ICON); + outer.appendChild(img); + + let inner = document.createElement("div"); + inner.className = "inner"; + + let details = document.createElement("div"); + details.className = "details"; + inner.appendChild(details); + + let titlePart = document.createElement("div"); + titlePart.textContent = aAddon.name; + titlePart.className = "title"; + details.appendChild(titlePart); + + let versionPart = document.createElement("div"); + versionPart.textContent = aAddon.version; + versionPart.className = "version"; + details.appendChild(versionPart); + + if ("description" in aAddon) { + let descPart = document.createElement("div"); + descPart.textContent = aAddon.description; + descPart.className = "description"; + inner.appendChild(descPart); + } + + outer.appendChild(inner); + return outer; + }, + + _createBrowseItem: function _createBrowseItem() { + let outer = document.createElement("div"); + outer.className = "addon-item list-item"; + outer.setAttribute("role", "button"); + outer.addEventListener("click", function(event) { + try { + let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); + openLink(formatter.formatURLPref("extensions.getAddons.browseAddons")); + } catch (e) { + Cu.reportError(e); + } + }, true); + + let img = document.createElement("img"); + img.className = "icon"; + img.setAttribute("src", AMO_ICON); + outer.appendChild(img); + + let inner = document.createElement("div"); + inner.className = "inner"; + + let title = document.createElement("div"); + title.id = "browse-title"; + title.className = "title"; + title.textContent = gStringBundle.GetStringFromName("addons.browseAll"); + inner.appendChild(title); + + outer.appendChild(inner); + return outer; + }, + + _createItemForAddon: function _createItemForAddon(aAddon) { + 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; + + // TODO(matt): Add support for OPTIONS_TYPE_INLINE_BROWSER once bug 1302504 lands. + let optionsURL; + switch (aAddon.optionsType) { + case AddonManager.OPTIONS_TYPE_INLINE: + optionsURL = aAddon.optionsURL || ""; + break; + default: + optionsURL = ""; + } + + let blocked = ""; + switch(aAddon.blocklistState) { + case Ci.nsIBlocklistService.STATE_BLOCKED: + blocked = "blocked"; + break; + case Ci.nsIBlocklistService.STATE_SOFTBLOCKED: + blocked = "softBlocked"; + break; + case Ci.nsIBlocklistService.STATE_OUTDATED: + blocked = "outdated"; + break; + } + + let item = this._createItem(aAddon); + item.setAttribute("isDisabled", !aAddon.isActive); + item.setAttribute("isUnsigned", aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING); + item.setAttribute("opType", opType); + item.setAttribute("updateable", updateable); + if (blocked) + item.setAttribute("blockedStatus", blocked); + item.setAttribute("optionsURL", optionsURL); + item.addon = aAddon; + + return item; + }, + + _getElementForAddon: function(aKey) { + let list = document.getElementById("addons-list"); + let element = list.querySelector("div[addonID=\"" + CSS.escape(aKey) + "\"]"); + return element; + }, + + init: function init() { + let self = this; + AddonManager.getAllAddons(function(aAddons) { + // Clear all content before filling the addons + let list = document.getElementById("addons-list"); + list.innerHTML = ""; + + aAddons.sort(function(a,b) { + return a.name.localeCompare(b.name); + }); + for (let i=0; i<aAddons.length; i++) { + // Don't create item for system add-ons. + if (aAddons[i].isSystem) + continue; + + let item = self._createItemForAddon(aAddons[i]); + list.appendChild(item); + } + + // Add a "Browse all Firefox Add-ons" item to the bottom of the list. + let browseItem = self._createBrowseItem(); + list.appendChild(browseItem); + }); + + document.getElementById("uninstall-btn").addEventListener("click", Addons.uninstallCurrent.bind(this), false); + document.getElementById("cancel-btn").addEventListener("click", Addons.cancelUninstall.bind(this), false); + document.getElementById("disable-btn").addEventListener("click", Addons.disable.bind(this), false); + document.getElementById("enable-btn").addEventListener("click", Addons.enable.bind(this), false); + + document.getElementById("unsigned-learn-more").addEventListener("click", function() { + openLink(Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons"); + }, false); + }, + + _getOpTypeForOperations: function _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 ""; + }, + + showDetails: function showDetails(aListItem) { + // This function removes and returns the text content of aNode without + // removing any child elements. Removing the text nodes ensures any XBL + // bindings apply properly. + function stripTextNodes(aNode) { + var text = ""; + for (var i = 0; i < aNode.childNodes.length; i++) { + if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { + text += aNode.childNodes[i].textContent; + aNode.removeChild(aNode.childNodes[i--]); + } else { + text += stripTextNodes(aNode.childNodes[i]); + } + } + return text; + } + + let detailItem = document.querySelector("#addons-details > .addon-item"); + detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled")); + detailItem.setAttribute("isUnsigned", aListItem.getAttribute("isUnsigned")); + detailItem.setAttribute("opType", aListItem.getAttribute("opType")); + detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL")); + let addon = detailItem.addon = aListItem.addon; + + let favicon = document.querySelector("#addons-details > .addon-item .icon"); + favicon.setAttribute("src", addon.iconURL || AMO_ICON); + + detailItem.querySelector(".title").textContent = addon.name; + detailItem.querySelector(".version").textContent = addon.version; + detailItem.querySelector(".description-full").textContent = addon.description; + detailItem.querySelector(".status-uninstalled").textContent = + gStringBundle.formatStringFromName("addonStatus.uninstalled", [addon.name], 1); + + let enableBtn = document.getElementById("enable-btn"); + if (addon.appDisabled) { + enableBtn.setAttribute("disabled", "true"); + } else { + enableBtn.removeAttribute("disabled"); + } + + let uninstallBtn = document.getElementById("uninstall-btn"); + if (addon.scope == AddonManager.SCOPE_APPLICATION) { + uninstallBtn.setAttribute("disabled", "true"); + } else { + uninstallBtn.removeAttribute("disabled"); + } + + let box = document.querySelector("#addons-details > .addon-item .options-box"); + box.innerHTML = ""; + + // Retrieve the extensions preferences + try { + let optionsURL = aListItem.getAttribute("optionsURL"); + let xhr = new XMLHttpRequest(); + xhr.open("GET", optionsURL, true); + xhr.onload = function(e) { + if (xhr.responseXML) { + // Only allow <setting> for now + let settings = xhr.responseXML.querySelectorAll(":root > setting"); + if (settings.length > 0) { + for (let i = 0; i < settings.length; i++) { + var setting = settings[i]; + var desc = stripTextNodes(setting).trim(); + if (!setting.hasAttribute("desc")) { + setting.setAttribute("desc", desc); + } + box.appendChild(setting); + } + // Send an event so add-ons can prepopulate any non-preference based + // settings + let event = document.createEvent("Events"); + event.initEvent("AddonOptionsLoad", true, false); + window.dispatchEvent(event); + } else { + // Reset the options URL to hide the options header if there are no + // valid settings to show. + detailItem.setAttribute("optionsURL", ""); + } + + // Also send a notification to match the behavior of desktop Firefox + let id = aListItem.getAttribute("addonID"); + Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id); + } + } + xhr.send(null); + } catch (e) { } + + let list = document.querySelector("#addons-list"); + list.style.display = "none"; + let details = document.querySelector("#addons-details"); + details.style.display = "block"; + document.documentElement.setAttribute("details", "true"); + }, + + setEnabled: function setEnabled(aValue, aAddon) { + let detailItem = document.querySelector("#addons-details > .addon-item"); + let addon = aAddon || detailItem.addon; + if (!addon) + return; + + let listItem = this._getElementForAddon(addon.id); + + let opType; + if (addon.type == "theme") { + if (aValue) { + // We can have only one theme enabled, so disable the current one if any + let list = document.getElementById("addons-list"); + let item = list.firstElementChild; + while (item) { + if (item.addon && (item.addon.type == "theme") && (item.addon.isActive)) { + item.addon.userDisabled = true; + item.setAttribute("isDisabled", true); + break; + } + item = item.nextSibling; + } + } + addon.userDisabled = !aValue; + } else if (addon.type == "locale") { + addon.userDisabled = !aValue; + } else { + addon.userDisabled = !aValue; + opType = this._getOpTypeForOperations(addon.pendingOperations); + + if ((addon.pendingOperations & AddonManager.PENDING_ENABLE) || + (addon.pendingOperations & AddonManager.PENDING_DISABLE)) { + this.showRestart(); + } else if (listItem && /needs-(enable|disable)/.test(listItem.getAttribute("opType"))) { + this.hideRestart(); + } + } + + if (addon == detailItem.addon) { + detailItem.setAttribute("isDisabled", !aValue); + if (opType) + detailItem.setAttribute("opType", opType); + else + detailItem.removeAttribute("opType"); + } + + // Sync to the list item + if (listItem) { + listItem.setAttribute("isDisabled", !aValue); + if (opType) + listItem.setAttribute("opType", opType); + else + listItem.removeAttribute("opType"); + } + }, + + enable: function enable() { + this.setEnabled(true); + }, + + disable: function disable() { + this.setEnabled(false); + }, + + uninstallCurrent: function uninstallCurrent() { + let detailItem = document.querySelector("#addons-details > .addon-item"); + + let addon = detailItem.addon; + if (!addon) + return; + + this.uninstall(addon); + }, + + uninstall: function uninstall(aAddon) { + if (!aAddon) { + return; + } + + let listItem = this._getElementForAddon(aAddon.id); + aAddon.uninstall(); + + if (aAddon.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 + let opType = this._getOpTypeForOperations(aAddon.pendingOperations); + if (!aAddon.isActive && opType == "") + opType = "needs-uninstall"; + + detailItem.setAttribute("opType", opType); + listItem.setAttribute("opType", opType); + } + }, + + cancelUninstall: function ev_cancelUninstall() { + let detailItem = document.querySelector("#addons-details > .addon-item"); + let addon = detailItem.addon; + if (!addon) + return; + + addon.cancelUninstall(); + this.hideRestart(); + + let opType = this._getOpTypeForOperations(addon.pendingOperations); + detailItem.setAttribute("opType", opType); + + let listItem = this._getElementForAddon(addon.id); + listItem.setAttribute("opType", opType); + }, + + showRestart: function showRestart() { + this._restartCount++; + gChromeWin.XPInstallObserver.showRestartPrompt(); + }, + + hideRestart: function hideRestart() { + this._restartCount--; + if (this._restartCount == 0) + gChromeWin.XPInstallObserver.hideRestartPrompt(); + }, + + onEnabled: function(aAddon) { + let listItem = this._getElementForAddon(aAddon.id); + if (!listItem) + return; + + // Reload the details to pick up any options now that it's enabled. + listItem.setAttribute("optionsURL", aAddon.optionsURL || ""); + let detailItem = document.querySelector("#addons-details > .addon-item"); + if (aAddon == detailItem.addon) + this.showDetails(listItem); + }, + + onInstallEnded: function(aInstall, aAddon) { + let needsRestart = false; + if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE)) + needsRestart = true; + else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) + needsRestart = true; + + let list = document.getElementById("addons-list"); + let element = this._getElementForAddon(aAddon.id); + if (!element) { + element = this._createItemForAddon(aAddon); + list.insertBefore(element, list.firstElementChild); + } + + if (needsRestart) + element.setAttribute("opType", "needs-restart"); + }, + + onInstalled: function(aAddon) { + let list = document.getElementById("addons-list"); + let element = this._getElementForAddon(aAddon.id); + if (!element) { + element = this._createItemForAddon(aAddon); + + // Themes aren't considered active on install, so set existing as disabled, and new one enabled. + if (aAddon.type == "theme") { + let item = list.firstElementChild; + while (item) { + if (item.addon && (item.addon.type == "theme")) { + item.setAttribute("isDisabled", true); + } + item = item.nextSibling; + } + element.setAttribute("isDisabled", false); + } + + list.insertBefore(element, list.firstElementChild); + } + }, + + onUninstalled: function(aAddon) { + let list = document.getElementById("addons-list"); + let element = this._getElementForAddon(aAddon.id); + list.removeChild(element); + + // Go back if we're in the detail view of the add-on that was uninstalled. + let detailItem = document.querySelector("#addons-details > .addon-item"); + if (detailItem.addon.id == aAddon.id) { + history.back(); + } + }, + + onInstallFailed: function(aInstall) { + }, + + onDownloadProgress: function xpidm_onDownloadProgress(aInstall) { + }, + + onDownloadFailed: function(aInstall) { + }, + + onDownloadCancelled: function(aInstall) { + } +} + +window.addEventListener("load", init, false); +window.addEventListener("unload", uninit, false); |