1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/chrome/content/aboutAddons.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,538 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; 1.11 + 1.12 +Cu.import("resource://gre/modules/Services.jsm") 1.13 +Cu.import("resource://gre/modules/AddonManager.jsm"); 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 + 1.16 +const AMO_ICON = "chrome://browser/skin/images/amo-logo.png"; 1.17 + 1.18 +let gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutAddons.properties"); 1.19 + 1.20 +XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() 1.21 + window.QueryInterface(Ci.nsIInterfaceRequestor) 1.22 + .getInterface(Ci.nsIWebNavigation) 1.23 + .QueryInterface(Ci.nsIDocShellTreeItem) 1.24 + .rootTreeItem 1.25 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.26 + .getInterface(Ci.nsIDOMWindow) 1.27 + .QueryInterface(Ci.nsIDOMChromeWindow)); 1.28 + 1.29 +var ContextMenus = { 1.30 + target: null, 1.31 + 1.32 + init: function() { 1.33 + document.addEventListener("contextmenu", this, false); 1.34 + 1.35 + document.getElementById("contextmenu-enable").addEventListener("click", ContextMenus.enable.bind(this), false); 1.36 + document.getElementById("contextmenu-disable").addEventListener("click", ContextMenus.disable.bind(this), false); 1.37 + document.getElementById("contextmenu-uninstall").addEventListener("click", ContextMenus.uninstall.bind(this), false); 1.38 + 1.39 + // XXX - Hack to fix bug 985867 for now 1.40 + document.addEventListener("touchstart", function() { }); 1.41 + }, 1.42 + 1.43 + handleEvent: function(event) { 1.44 + // store the target of context menu events so that we know which app to act on 1.45 + this.target = event.target; 1.46 + while (!this.target.hasAttribute("contextmenu")) { 1.47 + this.target = this.target.parentNode; 1.48 + } 1.49 + 1.50 + if (!this.target) { 1.51 + document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); 1.52 + document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); 1.53 + document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); 1.54 + return; 1.55 + } 1.56 + 1.57 + let addon = this.target.addon; 1.58 + if (addon.scope == AddonManager.SCOPE_APPLICATION) { 1.59 + document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); 1.60 + } else { 1.61 + document.getElementById("contextmenu-uninstall").removeAttribute("hidden"); 1.62 + } 1.63 + 1.64 + let enabled = this.target.getAttribute("isDisabled") != "true"; 1.65 + if (enabled) { 1.66 + document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); 1.67 + document.getElementById("contextmenu-disable").removeAttribute("hidden"); 1.68 + } else { 1.69 + document.getElementById("contextmenu-enable").removeAttribute("hidden"); 1.70 + document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); 1.71 + } 1.72 + }, 1.73 + 1.74 + enable: function(event) { 1.75 + Addons.setEnabled(true, this.target.addon); 1.76 + this.target = null; 1.77 + }, 1.78 + 1.79 + disable: function (event) { 1.80 + Addons.setEnabled(false, this.target.addon); 1.81 + this.target = null; 1.82 + }, 1.83 + 1.84 + uninstall: function (event) { 1.85 + Addons.uninstall(this.target.addon); 1.86 + this.target = null; 1.87 + } 1.88 +} 1.89 + 1.90 +function init() { 1.91 + window.addEventListener("popstate", onPopState, false); 1.92 + 1.93 + AddonManager.addInstallListener(Addons); 1.94 + AddonManager.addAddonListener(Addons); 1.95 + Addons.init(); 1.96 + showList(); 1.97 + ContextMenus.init(); 1.98 + 1.99 + document.getElementById("header-button").addEventListener("click", openLink, false); 1.100 +} 1.101 + 1.102 + 1.103 +function uninit() { 1.104 + AddonManager.removeInstallListener(Addons); 1.105 + AddonManager.removeAddonListener(Addons); 1.106 +} 1.107 + 1.108 +function openLink(aEvent) { 1.109 + try { 1.110 + let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); 1.111 + 1.112 + let url = formatter.formatURLPref(aEvent.currentTarget.getAttribute("pref")); 1.113 + let BrowserApp = gChromeWin.BrowserApp; 1.114 + BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }); 1.115 + } catch (ex) {} 1.116 +} 1.117 + 1.118 +function onPopState(aEvent) { 1.119 + // Called when back/forward is used to change the state of the page 1.120 + if (aEvent.state) { 1.121 + // Show the detail page for an addon 1.122 + Addons.showDetails(Addons._getElementForAddon(aEvent.state.id)); 1.123 + } else { 1.124 + // Clear any previous detail addon 1.125 + let detailItem = document.querySelector("#addons-details > .addon-item"); 1.126 + detailItem.addon = null; 1.127 + 1.128 + showList(); 1.129 + } 1.130 +} 1.131 + 1.132 +function showList() { 1.133 + // Hide the detail page and show the list 1.134 + let details = document.querySelector("#addons-details"); 1.135 + details.style.display = "none"; 1.136 + let list = document.querySelector("#addons-list"); 1.137 + list.style.display = "block"; 1.138 +} 1.139 + 1.140 +var Addons = { 1.141 + _restartCount: 0, 1.142 + 1.143 + _createItem: function _createItem(aAddon) { 1.144 + let outer = document.createElement("div"); 1.145 + outer.setAttribute("addonID", aAddon.id); 1.146 + outer.className = "addon-item list-item"; 1.147 + outer.setAttribute("role", "button"); 1.148 + outer.setAttribute("contextmenu", "addonmenu"); 1.149 + outer.addEventListener("click", function() { 1.150 + this.showDetails(outer); 1.151 + history.pushState({ id: aAddon.id }, document.title); 1.152 + }.bind(this), true); 1.153 + 1.154 + let img = document.createElement("img"); 1.155 + img.className = "icon"; 1.156 + img.setAttribute("src", aAddon.iconURL || AMO_ICON); 1.157 + outer.appendChild(img); 1.158 + 1.159 + let inner = document.createElement("div"); 1.160 + inner.className = "inner"; 1.161 + 1.162 + let details = document.createElement("div"); 1.163 + details.className = "details"; 1.164 + inner.appendChild(details); 1.165 + 1.166 + let titlePart = document.createElement("div"); 1.167 + titlePart.textContent = aAddon.name; 1.168 + titlePart.className = "title"; 1.169 + details.appendChild(titlePart); 1.170 + 1.171 + let versionPart = document.createElement("div"); 1.172 + versionPart.textContent = aAddon.version; 1.173 + versionPart.className = "version"; 1.174 + details.appendChild(versionPart); 1.175 + 1.176 + if ("description" in aAddon) { 1.177 + let descPart = document.createElement("div"); 1.178 + descPart.textContent = aAddon.description; 1.179 + descPart.className = "description"; 1.180 + inner.appendChild(descPart); 1.181 + } 1.182 + 1.183 + outer.appendChild(inner); 1.184 + return outer; 1.185 + }, 1.186 + 1.187 + _createBrowseItem: function _createBrowseItem() { 1.188 + let outer = document.createElement("div"); 1.189 + outer.className = "addon-item list-item"; 1.190 + outer.setAttribute("role", "button"); 1.191 + outer.setAttribute("pref", "extensions.getAddons.browseAddons"); 1.192 + outer.addEventListener("click", openLink, true); 1.193 + 1.194 + let img = document.createElement("img"); 1.195 + img.className = "icon"; 1.196 + img.setAttribute("src", AMO_ICON); 1.197 + outer.appendChild(img); 1.198 + 1.199 + let inner = document.createElement("div"); 1.200 + inner.className = "inner"; 1.201 + 1.202 + let title = document.createElement("div"); 1.203 + title.id = "browse-title"; 1.204 + title.className = "title"; 1.205 + title.textContent = gStringBundle.GetStringFromName("addons.browseAll"); 1.206 + inner.appendChild(title); 1.207 + 1.208 + outer.appendChild(inner); 1.209 + return outer; 1.210 + }, 1.211 + 1.212 + _createItemForAddon: function _createItemForAddon(aAddon) { 1.213 + let appManaged = (aAddon.scope == AddonManager.SCOPE_APPLICATION); 1.214 + let opType = this._getOpTypeForOperations(aAddon.pendingOperations); 1.215 + let updateable = (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) > 0; 1.216 + let uninstallable = (aAddon.permissions & AddonManager.PERM_CAN_UNINSTALL) > 0; 1.217 + 1.218 + let blocked = ""; 1.219 + switch(aAddon.blocklistState) { 1.220 + case Ci.nsIBlocklistService.STATE_BLOCKED: 1.221 + blocked = "blocked"; 1.222 + break; 1.223 + case Ci.nsIBlocklistService.STATE_SOFTBLOCKED: 1.224 + blocked = "softBlocked"; 1.225 + break; 1.226 + case Ci.nsIBlocklistService.STATE_OUTDATED: 1.227 + blocked = "outdated"; 1.228 + break; 1.229 + } 1.230 + 1.231 + let item = this._createItem(aAddon); 1.232 + item.setAttribute("isDisabled", !aAddon.isActive); 1.233 + item.setAttribute("opType", opType); 1.234 + item.setAttribute("updateable", updateable); 1.235 + if (blocked) 1.236 + item.setAttribute("blockedStatus", blocked); 1.237 + item.setAttribute("optionsURL", aAddon.optionsURL || ""); 1.238 + item.addon = aAddon; 1.239 + 1.240 + return item; 1.241 + }, 1.242 + 1.243 + _getElementForAddon: function(aKey) { 1.244 + let list = document.getElementById("addons-list"); 1.245 + let element = list.querySelector("div[addonID=" + aKey.quote() + "]"); 1.246 + return element; 1.247 + }, 1.248 + 1.249 + init: function init() { 1.250 + let self = this; 1.251 + AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(aAddons) { 1.252 + // Clear all content before filling the addons 1.253 + let list = document.getElementById("addons-list"); 1.254 + list.innerHTML = ""; 1.255 + 1.256 + for (let i=0; i<aAddons.length; i++) { 1.257 + let item = self._createItemForAddon(aAddons[i]); 1.258 + list.appendChild(item); 1.259 + } 1.260 + 1.261 + // Add a "Browse all Firefox Add-ons" item to the bottom of the list. 1.262 + let browseItem = self._createBrowseItem(); 1.263 + list.appendChild(browseItem); 1.264 + }); 1.265 + 1.266 + document.getElementById("uninstall-btn").addEventListener("click", Addons.uninstallCurrent.bind(this), false); 1.267 + document.getElementById("cancel-btn").addEventListener("click", Addons.cancelUninstall.bind(this), false); 1.268 + document.getElementById("disable-btn").addEventListener("click", Addons.disable.bind(this), false); 1.269 + document.getElementById("enable-btn").addEventListener("click", Addons.enable.bind(this), false); 1.270 + }, 1.271 + 1.272 + _getOpTypeForOperations: function _getOpTypeForOperations(aOperations) { 1.273 + if (aOperations & AddonManager.PENDING_UNINSTALL) 1.274 + return "needs-uninstall"; 1.275 + if (aOperations & AddonManager.PENDING_ENABLE) 1.276 + return "needs-enable"; 1.277 + if (aOperations & AddonManager.PENDING_DISABLE) 1.278 + return "needs-disable"; 1.279 + return ""; 1.280 + }, 1.281 + 1.282 + showDetails: function showDetails(aListItem) { 1.283 + // This function removes and returns the text content of aNode without 1.284 + // removing any child elements. Removing the text nodes ensures any XBL 1.285 + // bindings apply properly. 1.286 + function stripTextNodes(aNode) { 1.287 + var text = ""; 1.288 + for (var i = 0; i < aNode.childNodes.length; i++) { 1.289 + if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { 1.290 + text += aNode.childNodes[i].textContent; 1.291 + aNode.removeChild(aNode.childNodes[i--]); 1.292 + } else { 1.293 + text += stripTextNodes(aNode.childNodes[i]); 1.294 + } 1.295 + } 1.296 + return text; 1.297 + } 1.298 + 1.299 + let detailItem = document.querySelector("#addons-details > .addon-item"); 1.300 + detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled")); 1.301 + detailItem.setAttribute("opType", aListItem.getAttribute("opType")); 1.302 + detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL")); 1.303 + let addon = detailItem.addon = aListItem.addon; 1.304 + 1.305 + let favicon = document.querySelector("#addons-details > .addon-item .icon"); 1.306 + favicon.setAttribute("src", addon.iconURL || AMO_ICON); 1.307 + 1.308 + detailItem.querySelector(".title").textContent = addon.name; 1.309 + detailItem.querySelector(".version").textContent = addon.version; 1.310 + detailItem.querySelector(".description-full").textContent = addon.description; 1.311 + detailItem.querySelector(".status-uninstalled").textContent = 1.312 + gStringBundle.formatStringFromName("addonStatus.uninstalled", [addon.name], 1); 1.313 + 1.314 + let enableBtn = document.getElementById("enable-btn"); 1.315 + if (addon.appDisabled) 1.316 + enableBtn.setAttribute("disabled", "true"); 1.317 + else 1.318 + enableBtn.removeAttribute("disabled"); 1.319 + 1.320 + let uninstallBtn = document.getElementById("uninstall-btn"); 1.321 + if (addon.scope == AddonManager.SCOPE_APPLICATION) 1.322 + uninstallBtn.setAttribute("disabled", "true"); 1.323 + else 1.324 + uninstallBtn.removeAttribute("disabled"); 1.325 + 1.326 + let box = document.querySelector("#addons-details > .addon-item .options-box"); 1.327 + box.innerHTML = ""; 1.328 + 1.329 + // Retrieve the extensions preferences 1.330 + try { 1.331 + let optionsURL = aListItem.getAttribute("optionsURL"); 1.332 + let xhr = new XMLHttpRequest(); 1.333 + xhr.open("GET", optionsURL, false); 1.334 + xhr.send(); 1.335 + if (xhr.responseXML) { 1.336 + // Only allow <setting> for now 1.337 + let settings = xhr.responseXML.querySelectorAll(":root > setting"); 1.338 + if (settings.length > 0) { 1.339 + for (let i = 0; i < settings.length; i++) { 1.340 + var setting = settings[i]; 1.341 + var desc = stripTextNodes(setting).trim(); 1.342 + if (!setting.hasAttribute("desc")) 1.343 + setting.setAttribute("desc", desc); 1.344 + box.appendChild(setting); 1.345 + } 1.346 + // Send an event so add-ons can prepopulate any non-preference based 1.347 + // settings 1.348 + let event = document.createEvent("Events"); 1.349 + event.initEvent("AddonOptionsLoad", true, false); 1.350 + window.dispatchEvent(event); 1.351 + 1.352 + // Also send a notification to match the behavior of desktop Firefox 1.353 + let id = aListItem.getAttribute("addonID"); 1.354 + Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id); 1.355 + } else { 1.356 + // No options, so hide the header and reset the list item 1.357 + detailItem.setAttribute("optionsURL", ""); 1.358 + aListItem.setAttribute("optionsURL", ""); 1.359 + } 1.360 + } 1.361 + } catch (e) { } 1.362 + 1.363 + let list = document.querySelector("#addons-list"); 1.364 + list.style.display = "none"; 1.365 + let details = document.querySelector("#addons-details"); 1.366 + details.style.display = "block"; 1.367 + }, 1.368 + 1.369 + setEnabled: function setEnabled(aValue, aAddon) { 1.370 + let detailItem = document.querySelector("#addons-details > .addon-item"); 1.371 + let addon = aAddon || detailItem.addon; 1.372 + if (!addon) 1.373 + return; 1.374 + 1.375 + let listItem = this._getElementForAddon(addon.id); 1.376 + 1.377 + let opType; 1.378 + if (addon.type == "theme") { 1.379 + if (aValue) { 1.380 + // We can have only one theme enabled, so disable the current one if any 1.381 + let list = document.getElementById("addons-list"); 1.382 + let item = list.firstElementChild; 1.383 + while (item) { 1.384 + if (item.addon && (item.addon.type == "theme") && (item.addon.isActive)) { 1.385 + item.addon.userDisabled = true; 1.386 + item.setAttribute("isDisabled", true); 1.387 + break; 1.388 + } 1.389 + item = item.nextSibling; 1.390 + } 1.391 + } 1.392 + addon.userDisabled = !aValue; 1.393 + } else if (addon.type == "locale") { 1.394 + addon.userDisabled = !aValue; 1.395 + } else { 1.396 + addon.userDisabled = !aValue; 1.397 + opType = this._getOpTypeForOperations(addon.pendingOperations); 1.398 + 1.399 + if ((addon.pendingOperations & AddonManager.PENDING_ENABLE) || 1.400 + (addon.pendingOperations & AddonManager.PENDING_DISABLE)) { 1.401 + this.showRestart(); 1.402 + } else if (listItem && /needs-(enable|disable)/.test(listItem.getAttribute("opType"))) { 1.403 + this.hideRestart(); 1.404 + } 1.405 + } 1.406 + 1.407 + if (addon == detailItem.addon) { 1.408 + detailItem.setAttribute("isDisabled", !aValue); 1.409 + if (opType) 1.410 + detailItem.setAttribute("opType", opType); 1.411 + else 1.412 + detailItem.removeAttribute("opType"); 1.413 + } 1.414 + 1.415 + // Sync to the list item 1.416 + if (listItem) { 1.417 + listItem.setAttribute("isDisabled", !aValue); 1.418 + if (opType) 1.419 + listItem.setAttribute("opType", opType); 1.420 + else 1.421 + listItem.removeAttribute("opType"); 1.422 + } 1.423 + }, 1.424 + 1.425 + enable: function enable() { 1.426 + this.setEnabled(true); 1.427 + }, 1.428 + 1.429 + disable: function disable() { 1.430 + this.setEnabled(false); 1.431 + }, 1.432 + 1.433 + uninstallCurrent: function uninstallCurrent() { 1.434 + let detailItem = document.querySelector("#addons-details > .addon-item"); 1.435 + 1.436 + let addon = detailItem.addon; 1.437 + if (!addon) 1.438 + return; 1.439 + 1.440 + this.uninstall(addon); 1.441 + }, 1.442 + 1.443 + uninstall: function uninstall(aAddon) { 1.444 + let list = document.getElementById("addons-list"); 1.445 + 1.446 + if (!aAddon) { 1.447 + return; 1.448 + } 1.449 + 1.450 + let listItem = this._getElementForAddon(aAddon.id); 1.451 + 1.452 + aAddon.uninstall(); 1.453 + if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) { 1.454 + this.showRestart(); 1.455 + 1.456 + // A disabled addon doesn't need a restart so it has no pending ops and 1.457 + // can't be cancelled 1.458 + let opType = this._getOpTypeForOperations(aAddon.pendingOperations); 1.459 + if (!aAddon.isActive && opType == "") 1.460 + opType = "needs-uninstall"; 1.461 + 1.462 + detailItem.setAttribute("opType", opType); 1.463 + listItem.setAttribute("opType", opType); 1.464 + } else { 1.465 + list.removeChild(listItem); 1.466 + history.back(); 1.467 + } 1.468 + }, 1.469 + 1.470 + cancelUninstall: function ev_cancelUninstall() { 1.471 + let detailItem = document.querySelector("#addons-details > .addon-item"); 1.472 + let addon = detailItem.addon; 1.473 + if (!addon) 1.474 + return; 1.475 + 1.476 + addon.cancelUninstall(); 1.477 + this.hideRestart(); 1.478 + 1.479 + let opType = this._getOpTypeForOperations(addon.pendingOperations); 1.480 + detailItem.setAttribute("opType", opType); 1.481 + 1.482 + let listItem = this._getElementForAddon(addon.id); 1.483 + listItem.setAttribute("opType", opType); 1.484 + }, 1.485 + 1.486 + showRestart: function showRestart() { 1.487 + this._restartCount++; 1.488 + gChromeWin.XPInstallObserver.showRestartPrompt(); 1.489 + }, 1.490 + 1.491 + hideRestart: function hideRestart() { 1.492 + this._restartCount--; 1.493 + if (this._restartCount == 0) 1.494 + gChromeWin.XPInstallObserver.hideRestartPrompt(); 1.495 + }, 1.496 + 1.497 + onEnabled: function(aAddon) { 1.498 + let listItem = this._getElementForAddon(aAddon.id); 1.499 + if (!listItem) 1.500 + return; 1.501 + 1.502 + // Reload the details to pick up any options now that it's enabled. 1.503 + listItem.setAttribute("optionsURL", aAddon.optionsURL || ""); 1.504 + let detailItem = document.querySelector("#addons-details > .addon-item"); 1.505 + if (aAddon == detailItem.addon) 1.506 + this.showDetails(listItem); 1.507 + }, 1.508 + 1.509 + onInstallEnded: function(aInstall, aAddon) { 1.510 + let needsRestart = false; 1.511 + if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE)) 1.512 + needsRestart = true; 1.513 + else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) 1.514 + needsRestart = true; 1.515 + 1.516 + let list = document.getElementById("addons-list"); 1.517 + let element = this._getElementForAddon(aAddon.id); 1.518 + if (!element) { 1.519 + element = this._createItemForAddon(aAddon); 1.520 + list.insertBefore(element, list.firstElementChild); 1.521 + } 1.522 + 1.523 + if (needsRestart) 1.524 + element.setAttribute("opType", "needs-restart"); 1.525 + }, 1.526 + 1.527 + onInstallFailed: function(aInstall) { 1.528 + }, 1.529 + 1.530 + onDownloadProgress: function xpidm_onDownloadProgress(aInstall) { 1.531 + }, 1.532 + 1.533 + onDownloadFailed: function(aInstall) { 1.534 + }, 1.535 + 1.536 + onDownloadCancelled: function(aInstall) { 1.537 + } 1.538 +} 1.539 + 1.540 +window.addEventListener("load", init, false); 1.541 +window.addEventListener("unload", uninit, false);