michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm") michael@0: Cu.import("resource://gre/modules/AddonManager.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: const AMO_ICON = "chrome://browser/skin/images/amo-logo.png"; michael@0: michael@0: let gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutAddons.properties"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() michael@0: window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow) michael@0: .QueryInterface(Ci.nsIDOMChromeWindow)); michael@0: michael@0: var ContextMenus = { michael@0: target: null, michael@0: michael@0: init: function() { michael@0: document.addEventListener("contextmenu", this, false); michael@0: michael@0: document.getElementById("contextmenu-enable").addEventListener("click", ContextMenus.enable.bind(this), false); michael@0: document.getElementById("contextmenu-disable").addEventListener("click", ContextMenus.disable.bind(this), false); michael@0: document.getElementById("contextmenu-uninstall").addEventListener("click", ContextMenus.uninstall.bind(this), false); michael@0: michael@0: // XXX - Hack to fix bug 985867 for now michael@0: document.addEventListener("touchstart", function() { }); michael@0: }, michael@0: michael@0: handleEvent: function(event) { michael@0: // store the target of context menu events so that we know which app to act on michael@0: this.target = event.target; michael@0: while (!this.target.hasAttribute("contextmenu")) { michael@0: this.target = this.target.parentNode; michael@0: } michael@0: michael@0: if (!this.target) { michael@0: document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); michael@0: document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); michael@0: document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); michael@0: return; michael@0: } michael@0: michael@0: let addon = this.target.addon; michael@0: if (addon.scope == AddonManager.SCOPE_APPLICATION) { michael@0: document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); michael@0: } else { michael@0: document.getElementById("contextmenu-uninstall").removeAttribute("hidden"); michael@0: } michael@0: michael@0: let enabled = this.target.getAttribute("isDisabled") != "true"; michael@0: if (enabled) { michael@0: document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); michael@0: document.getElementById("contextmenu-disable").removeAttribute("hidden"); michael@0: } else { michael@0: document.getElementById("contextmenu-enable").removeAttribute("hidden"); michael@0: document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); michael@0: } michael@0: }, michael@0: michael@0: enable: function(event) { michael@0: Addons.setEnabled(true, this.target.addon); michael@0: this.target = null; michael@0: }, michael@0: michael@0: disable: function (event) { michael@0: Addons.setEnabled(false, this.target.addon); michael@0: this.target = null; michael@0: }, michael@0: michael@0: uninstall: function (event) { michael@0: Addons.uninstall(this.target.addon); michael@0: this.target = null; michael@0: } michael@0: } michael@0: michael@0: function init() { michael@0: window.addEventListener("popstate", onPopState, false); michael@0: michael@0: AddonManager.addInstallListener(Addons); michael@0: AddonManager.addAddonListener(Addons); michael@0: Addons.init(); michael@0: showList(); michael@0: ContextMenus.init(); michael@0: michael@0: document.getElementById("header-button").addEventListener("click", openLink, false); michael@0: } michael@0: michael@0: michael@0: function uninit() { michael@0: AddonManager.removeInstallListener(Addons); michael@0: AddonManager.removeAddonListener(Addons); michael@0: } michael@0: michael@0: function openLink(aEvent) { michael@0: try { michael@0: let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); michael@0: michael@0: let url = formatter.formatURLPref(aEvent.currentTarget.getAttribute("pref")); michael@0: let BrowserApp = gChromeWin.BrowserApp; michael@0: BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }); michael@0: } catch (ex) {} michael@0: } michael@0: michael@0: function onPopState(aEvent) { michael@0: // Called when back/forward is used to change the state of the page michael@0: if (aEvent.state) { michael@0: // Show the detail page for an addon michael@0: Addons.showDetails(Addons._getElementForAddon(aEvent.state.id)); michael@0: } else { michael@0: // Clear any previous detail addon michael@0: let detailItem = document.querySelector("#addons-details > .addon-item"); michael@0: detailItem.addon = null; michael@0: michael@0: showList(); michael@0: } michael@0: } michael@0: michael@0: function showList() { michael@0: // Hide the detail page and show the list michael@0: let details = document.querySelector("#addons-details"); michael@0: details.style.display = "none"; michael@0: let list = document.querySelector("#addons-list"); michael@0: list.style.display = "block"; michael@0: } michael@0: michael@0: var Addons = { michael@0: _restartCount: 0, michael@0: michael@0: _createItem: function _createItem(aAddon) { michael@0: let outer = document.createElement("div"); michael@0: outer.setAttribute("addonID", aAddon.id); michael@0: outer.className = "addon-item list-item"; michael@0: outer.setAttribute("role", "button"); michael@0: outer.setAttribute("contextmenu", "addonmenu"); michael@0: outer.addEventListener("click", function() { michael@0: this.showDetails(outer); michael@0: history.pushState({ id: aAddon.id }, document.title); michael@0: }.bind(this), true); michael@0: michael@0: let img = document.createElement("img"); michael@0: img.className = "icon"; michael@0: img.setAttribute("src", aAddon.iconURL || AMO_ICON); michael@0: outer.appendChild(img); michael@0: michael@0: let inner = document.createElement("div"); michael@0: inner.className = "inner"; michael@0: michael@0: let details = document.createElement("div"); michael@0: details.className = "details"; michael@0: inner.appendChild(details); michael@0: michael@0: let titlePart = document.createElement("div"); michael@0: titlePart.textContent = aAddon.name; michael@0: titlePart.className = "title"; michael@0: details.appendChild(titlePart); michael@0: michael@0: let versionPart = document.createElement("div"); michael@0: versionPart.textContent = aAddon.version; michael@0: versionPart.className = "version"; michael@0: details.appendChild(versionPart); michael@0: michael@0: if ("description" in aAddon) { michael@0: let descPart = document.createElement("div"); michael@0: descPart.textContent = aAddon.description; michael@0: descPart.className = "description"; michael@0: inner.appendChild(descPart); michael@0: } michael@0: michael@0: outer.appendChild(inner); michael@0: return outer; michael@0: }, michael@0: michael@0: _createBrowseItem: function _createBrowseItem() { michael@0: let outer = document.createElement("div"); michael@0: outer.className = "addon-item list-item"; michael@0: outer.setAttribute("role", "button"); michael@0: outer.setAttribute("pref", "extensions.getAddons.browseAddons"); michael@0: outer.addEventListener("click", openLink, true); michael@0: michael@0: let img = document.createElement("img"); michael@0: img.className = "icon"; michael@0: img.setAttribute("src", AMO_ICON); michael@0: outer.appendChild(img); michael@0: michael@0: let inner = document.createElement("div"); michael@0: inner.className = "inner"; michael@0: michael@0: let title = document.createElement("div"); michael@0: title.id = "browse-title"; michael@0: title.className = "title"; michael@0: title.textContent = gStringBundle.GetStringFromName("addons.browseAll"); michael@0: inner.appendChild(title); michael@0: michael@0: outer.appendChild(inner); michael@0: return outer; michael@0: }, michael@0: michael@0: _createItemForAddon: function _createItemForAddon(aAddon) { michael@0: let appManaged = (aAddon.scope == AddonManager.SCOPE_APPLICATION); michael@0: let opType = this._getOpTypeForOperations(aAddon.pendingOperations); michael@0: let updateable = (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) > 0; michael@0: let uninstallable = (aAddon.permissions & AddonManager.PERM_CAN_UNINSTALL) > 0; michael@0: michael@0: let blocked = ""; michael@0: switch(aAddon.blocklistState) { michael@0: case Ci.nsIBlocklistService.STATE_BLOCKED: michael@0: blocked = "blocked"; michael@0: break; michael@0: case Ci.nsIBlocklistService.STATE_SOFTBLOCKED: michael@0: blocked = "softBlocked"; michael@0: break; michael@0: case Ci.nsIBlocklistService.STATE_OUTDATED: michael@0: blocked = "outdated"; michael@0: break; michael@0: } michael@0: michael@0: let item = this._createItem(aAddon); michael@0: item.setAttribute("isDisabled", !aAddon.isActive); michael@0: item.setAttribute("opType", opType); michael@0: item.setAttribute("updateable", updateable); michael@0: if (blocked) michael@0: item.setAttribute("blockedStatus", blocked); michael@0: item.setAttribute("optionsURL", aAddon.optionsURL || ""); michael@0: item.addon = aAddon; michael@0: michael@0: return item; michael@0: }, michael@0: michael@0: _getElementForAddon: function(aKey) { michael@0: let list = document.getElementById("addons-list"); michael@0: let element = list.querySelector("div[addonID=" + aKey.quote() + "]"); michael@0: return element; michael@0: }, michael@0: michael@0: init: function init() { michael@0: let self = this; michael@0: AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(aAddons) { michael@0: // Clear all content before filling the addons michael@0: let list = document.getElementById("addons-list"); michael@0: list.innerHTML = ""; michael@0: michael@0: for (let i=0; i .addon-item"); michael@0: detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled")); michael@0: detailItem.setAttribute("opType", aListItem.getAttribute("opType")); michael@0: detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL")); michael@0: let addon = detailItem.addon = aListItem.addon; michael@0: michael@0: let favicon = document.querySelector("#addons-details > .addon-item .icon"); michael@0: favicon.setAttribute("src", addon.iconURL || AMO_ICON); michael@0: michael@0: detailItem.querySelector(".title").textContent = addon.name; michael@0: detailItem.querySelector(".version").textContent = addon.version; michael@0: detailItem.querySelector(".description-full").textContent = addon.description; michael@0: detailItem.querySelector(".status-uninstalled").textContent = michael@0: gStringBundle.formatStringFromName("addonStatus.uninstalled", [addon.name], 1); michael@0: michael@0: let enableBtn = document.getElementById("enable-btn"); michael@0: if (addon.appDisabled) michael@0: enableBtn.setAttribute("disabled", "true"); michael@0: else michael@0: enableBtn.removeAttribute("disabled"); michael@0: michael@0: let uninstallBtn = document.getElementById("uninstall-btn"); michael@0: if (addon.scope == AddonManager.SCOPE_APPLICATION) michael@0: uninstallBtn.setAttribute("disabled", "true"); michael@0: else michael@0: uninstallBtn.removeAttribute("disabled"); michael@0: michael@0: let box = document.querySelector("#addons-details > .addon-item .options-box"); michael@0: box.innerHTML = ""; michael@0: michael@0: // Retrieve the extensions preferences michael@0: try { michael@0: let optionsURL = aListItem.getAttribute("optionsURL"); michael@0: let xhr = new XMLHttpRequest(); michael@0: xhr.open("GET", optionsURL, false); michael@0: xhr.send(); michael@0: if (xhr.responseXML) { michael@0: // Only allow for now michael@0: let settings = xhr.responseXML.querySelectorAll(":root > setting"); michael@0: if (settings.length > 0) { michael@0: for (let i = 0; i < settings.length; i++) { michael@0: var setting = settings[i]; michael@0: var desc = stripTextNodes(setting).trim(); michael@0: if (!setting.hasAttribute("desc")) michael@0: setting.setAttribute("desc", desc); michael@0: box.appendChild(setting); michael@0: } michael@0: // Send an event so add-ons can prepopulate any non-preference based michael@0: // settings michael@0: let event = document.createEvent("Events"); michael@0: event.initEvent("AddonOptionsLoad", true, false); michael@0: window.dispatchEvent(event); michael@0: michael@0: // Also send a notification to match the behavior of desktop Firefox michael@0: let id = aListItem.getAttribute("addonID"); michael@0: Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id); michael@0: } else { michael@0: // No options, so hide the header and reset the list item michael@0: detailItem.setAttribute("optionsURL", ""); michael@0: aListItem.setAttribute("optionsURL", ""); michael@0: } michael@0: } michael@0: } catch (e) { } michael@0: michael@0: let list = document.querySelector("#addons-list"); michael@0: list.style.display = "none"; michael@0: let details = document.querySelector("#addons-details"); michael@0: details.style.display = "block"; michael@0: }, michael@0: michael@0: setEnabled: function setEnabled(aValue, aAddon) { michael@0: let detailItem = document.querySelector("#addons-details > .addon-item"); michael@0: let addon = aAddon || detailItem.addon; michael@0: if (!addon) michael@0: return; michael@0: michael@0: let listItem = this._getElementForAddon(addon.id); michael@0: michael@0: let opType; michael@0: if (addon.type == "theme") { michael@0: if (aValue) { michael@0: // We can have only one theme enabled, so disable the current one if any michael@0: let list = document.getElementById("addons-list"); michael@0: let item = list.firstElementChild; michael@0: while (item) { michael@0: if (item.addon && (item.addon.type == "theme") && (item.addon.isActive)) { michael@0: item.addon.userDisabled = true; michael@0: item.setAttribute("isDisabled", true); michael@0: break; michael@0: } michael@0: item = item.nextSibling; michael@0: } michael@0: } michael@0: addon.userDisabled = !aValue; michael@0: } else if (addon.type == "locale") { michael@0: addon.userDisabled = !aValue; michael@0: } else { michael@0: addon.userDisabled = !aValue; michael@0: opType = this._getOpTypeForOperations(addon.pendingOperations); michael@0: michael@0: if ((addon.pendingOperations & AddonManager.PENDING_ENABLE) || michael@0: (addon.pendingOperations & AddonManager.PENDING_DISABLE)) { michael@0: this.showRestart(); michael@0: } else if (listItem && /needs-(enable|disable)/.test(listItem.getAttribute("opType"))) { michael@0: this.hideRestart(); michael@0: } michael@0: } michael@0: michael@0: if (addon == detailItem.addon) { michael@0: detailItem.setAttribute("isDisabled", !aValue); michael@0: if (opType) michael@0: detailItem.setAttribute("opType", opType); michael@0: else michael@0: detailItem.removeAttribute("opType"); michael@0: } michael@0: michael@0: // Sync to the list item michael@0: if (listItem) { michael@0: listItem.setAttribute("isDisabled", !aValue); michael@0: if (opType) michael@0: listItem.setAttribute("opType", opType); michael@0: else michael@0: listItem.removeAttribute("opType"); michael@0: } michael@0: }, michael@0: michael@0: enable: function enable() { michael@0: this.setEnabled(true); michael@0: }, michael@0: michael@0: disable: function disable() { michael@0: this.setEnabled(false); michael@0: }, michael@0: michael@0: uninstallCurrent: function uninstallCurrent() { michael@0: let detailItem = document.querySelector("#addons-details > .addon-item"); michael@0: michael@0: let addon = detailItem.addon; michael@0: if (!addon) michael@0: return; michael@0: michael@0: this.uninstall(addon); michael@0: }, michael@0: michael@0: uninstall: function uninstall(aAddon) { michael@0: let list = document.getElementById("addons-list"); michael@0: michael@0: if (!aAddon) { michael@0: return; michael@0: } michael@0: michael@0: let listItem = this._getElementForAddon(aAddon.id); michael@0: michael@0: aAddon.uninstall(); michael@0: if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) { michael@0: this.showRestart(); michael@0: michael@0: // A disabled addon doesn't need a restart so it has no pending ops and michael@0: // can't be cancelled michael@0: let opType = this._getOpTypeForOperations(aAddon.pendingOperations); michael@0: if (!aAddon.isActive && opType == "") michael@0: opType = "needs-uninstall"; michael@0: michael@0: detailItem.setAttribute("opType", opType); michael@0: listItem.setAttribute("opType", opType); michael@0: } else { michael@0: list.removeChild(listItem); michael@0: history.back(); michael@0: } michael@0: }, michael@0: michael@0: cancelUninstall: function ev_cancelUninstall() { michael@0: let detailItem = document.querySelector("#addons-details > .addon-item"); michael@0: let addon = detailItem.addon; michael@0: if (!addon) michael@0: return; michael@0: michael@0: addon.cancelUninstall(); michael@0: this.hideRestart(); michael@0: michael@0: let opType = this._getOpTypeForOperations(addon.pendingOperations); michael@0: detailItem.setAttribute("opType", opType); michael@0: michael@0: let listItem = this._getElementForAddon(addon.id); michael@0: listItem.setAttribute("opType", opType); michael@0: }, michael@0: michael@0: showRestart: function showRestart() { michael@0: this._restartCount++; michael@0: gChromeWin.XPInstallObserver.showRestartPrompt(); michael@0: }, michael@0: michael@0: hideRestart: function hideRestart() { michael@0: this._restartCount--; michael@0: if (this._restartCount == 0) michael@0: gChromeWin.XPInstallObserver.hideRestartPrompt(); michael@0: }, michael@0: michael@0: onEnabled: function(aAddon) { michael@0: let listItem = this._getElementForAddon(aAddon.id); michael@0: if (!listItem) michael@0: return; michael@0: michael@0: // Reload the details to pick up any options now that it's enabled. michael@0: listItem.setAttribute("optionsURL", aAddon.optionsURL || ""); michael@0: let detailItem = document.querySelector("#addons-details > .addon-item"); michael@0: if (aAddon == detailItem.addon) michael@0: this.showDetails(listItem); michael@0: }, michael@0: michael@0: onInstallEnded: function(aInstall, aAddon) { michael@0: let needsRestart = false; michael@0: if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE)) michael@0: needsRestart = true; michael@0: else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) michael@0: needsRestart = true; michael@0: michael@0: let list = document.getElementById("addons-list"); michael@0: let element = this._getElementForAddon(aAddon.id); michael@0: if (!element) { michael@0: element = this._createItemForAddon(aAddon); michael@0: list.insertBefore(element, list.firstElementChild); michael@0: } michael@0: michael@0: if (needsRestart) michael@0: element.setAttribute("opType", "needs-restart"); michael@0: }, michael@0: michael@0: onInstallFailed: function(aInstall) { michael@0: }, michael@0: michael@0: onDownloadProgress: function xpidm_onDownloadProgress(aInstall) { michael@0: }, michael@0: michael@0: onDownloadFailed: function(aInstall) { michael@0: }, michael@0: michael@0: onDownloadCancelled: function(aInstall) { michael@0: } michael@0: } michael@0: michael@0: window.addEventListener("load", init, false); michael@0: window.addEventListener("unload", uninit, false);