summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/content
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-02-10 04:00:58 -0500
committerMatt A. Tobin <email@mattatobin.com>2018-02-10 04:00:58 -0500
commitdeea787c2efbb9c89caec8d9efc023ffafe75613 (patch)
tree6dbe55f7d24e67ecdcc821b8c5492f6c17217852 /toolkit/mozapps/extensions/content
parent37d5300335d81cecbecc99812747a657588c63eb (diff)
downloadUXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar
UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar.gz
UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar.lz
UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.tar.xz
UXP-deea787c2efbb9c89caec8d9efc023ffafe75613.zip
Import Tycho's Add-on Manager
Diffstat (limited to 'toolkit/mozapps/extensions/content')
-rw-r--r--toolkit/mozapps/extensions/content/OpenH264-license.txt59
-rw-r--r--toolkit/mozapps/extensions/content/about.js97
-rw-r--r--toolkit/mozapps/extensions/content/about.xul57
-rw-r--r--toolkit/mozapps/extensions/content/blocklist.css11
-rw-r--r--toolkit/mozapps/extensions/content/blocklist.js72
-rw-r--r--toolkit/mozapps/extensions/content/blocklist.xml58
-rw-r--r--toolkit/mozapps/extensions/content/blocklist.xul46
-rw-r--r--toolkit/mozapps/extensions/content/eula.js21
-rw-r--r--toolkit/mozapps/extensions/content/eula.xul35
-rw-r--r--toolkit/mozapps/extensions/content/extensions.css288
-rw-r--r--toolkit/mozapps/extensions/content/extensions.js3659
-rw-r--r--toolkit/mozapps/extensions/content/extensions.xml2108
-rw-r--r--toolkit/mozapps/extensions/content/extensions.xul689
-rw-r--r--toolkit/mozapps/extensions/content/gmpPrefs.xul8
-rw-r--r--toolkit/mozapps/extensions/content/list.js165
-rw-r--r--toolkit/mozapps/extensions/content/list.xul44
-rw-r--r--toolkit/mozapps/extensions/content/newaddon.js129
-rw-r--r--toolkit/mozapps/extensions/content/newaddon.xul66
-rw-r--r--toolkit/mozapps/extensions/content/pluginPrefs.xul20
-rw-r--r--toolkit/mozapps/extensions/content/selectAddons.css22
-rw-r--r--toolkit/mozapps/extensions/content/selectAddons.js343
-rw-r--r--toolkit/mozapps/extensions/content/selectAddons.xml235
-rw-r--r--toolkit/mozapps/extensions/content/selectAddons.xul124
-rw-r--r--toolkit/mozapps/extensions/content/setting.xml508
-rw-r--r--toolkit/mozapps/extensions/content/update.js663
-rw-r--r--toolkit/mozapps/extensions/content/update.xul196
-rw-r--r--toolkit/mozapps/extensions/content/updateinfo.xsl41
-rw-r--r--toolkit/mozapps/extensions/content/xpinstallConfirm.css8
-rw-r--r--toolkit/mozapps/extensions/content/xpinstallConfirm.js196
-rw-r--r--toolkit/mozapps/extensions/content/xpinstallConfirm.xul37
-rw-r--r--toolkit/mozapps/extensions/content/xpinstallItem.xml51
31 files changed, 10056 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/content/OpenH264-license.txt b/toolkit/mozapps/extensions/content/OpenH264-license.txt
new file mode 100644
index 000000000..ad37989b8
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/OpenH264-license.txt
@@ -0,0 +1,59 @@
+-------------------------------------------------------
+About The Cisco-Provided Binary of OpenH264 Video Codec
+-------------------------------------------------------
+
+Cisco provides this program under the terms of the BSD license.
+
+Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met.
+
+As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice.
+
+For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary
+
+A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org
+
+-----------
+BSD License
+-----------
+
+Copyright © 2014 Cisco Systems, Inc.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------------------------------------
+AVC/H.264 Patent Portfolio License Notice
+-----------------------------------------
+
+The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software:
+
+THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEO”) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM
+
+Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla
+
+---------------------------------------------
+AVC/H.264 Patent Portfolio License Conditions
+---------------------------------------------
+
+In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met:
+
+1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device;
+
+2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary;
+
+3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text:
+
+ "OpenH264 Video Codec provided by Cisco Systems, Inc."
+
+4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user.
+
+
+
+ v1.0
diff --git a/toolkit/mozapps/extensions/content/about.js b/toolkit/mozapps/extensions/content/about.js
new file mode 100644
index 000000000..49ca4acc1
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/about.js
@@ -0,0 +1,97 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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";
+
+function init() {
+ var addon = window.arguments[0];
+ var extensionsStrings = document.getElementById("extensionsStrings");
+
+ document.documentElement.setAttribute("addontype", addon.type);
+
+ if (addon.iconURL) {
+ var extensionIcon = document.getElementById("extensionIcon");
+ extensionIcon.src = addon.iconURL;
+ }
+
+ document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]);
+ var extensionName = document.getElementById("extensionName");
+ extensionName.textContent = addon.name;
+
+ var extensionVersion = document.getElementById("extensionVersion");
+ if (addon.version)
+ extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version]));
+ else
+ extensionVersion.hidden = true;
+
+ var extensionDescription = document.getElementById("extensionDescription");
+ if (addon.description)
+ extensionDescription.textContent = addon.description;
+ else
+ extensionDescription.hidden = true;
+
+ var numDetails = 0;
+
+ var extensionCreator = document.getElementById("extensionCreator");
+ if (addon.creator) {
+ extensionCreator.setAttribute("value", addon.creator);
+ numDetails++;
+ } else {
+ extensionCreator.hidden = true;
+ var extensionCreatorLabel = document.getElementById("extensionCreatorLabel");
+ extensionCreatorLabel.hidden = true;
+ }
+
+ var extensionHomepage = document.getElementById("extensionHomepage");
+ var homepageURL = addon.homepageURL;
+ if (homepageURL) {
+ extensionHomepage.setAttribute("homepageURL", homepageURL);
+ extensionHomepage.setAttribute("tooltiptext", homepageURL);
+ numDetails++;
+ } else {
+ extensionHomepage.hidden = true;
+ }
+
+ numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers);
+ numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators);
+ numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors);
+
+ if (numDetails == 0) {
+ var groove = document.getElementById("groove");
+ groove.hidden = true;
+ var extensionDetailsBox = document.getElementById("extensionDetailsBox");
+ extensionDetailsBox.hidden = true;
+ }
+
+ var acceptButton = document.documentElement.getButton("accept");
+ acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton");
+
+ setTimeout(sizeToContent, 0);
+}
+
+function appendToList(aHeaderId, aNodeId, aItems) {
+ var header = document.getElementById(aHeaderId);
+ var node = document.getElementById(aNodeId);
+
+ if (!aItems || aItems.length == 0) {
+ header.hidden = true;
+ return 0;
+ }
+
+ for (let currentItem of aItems) {
+ var label = document.createElement("label");
+ label.textContent = currentItem;
+ label.setAttribute("class", "contributor");
+ node.appendChild(label);
+ }
+
+ return aItems.length;
+}
+
+function loadHomepage(aEvent) {
+ window.close();
+ openURL(aEvent.target.getAttribute("homepageURL"));
+}
diff --git a/toolkit/mozapps/extensions/content/about.xul b/toolkit/mozapps/extensions/content/about.xul
new file mode 100644
index 000000000..6effcf37a
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/about.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/about.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/extensions/about.dtd">
+
+<dialog id="genericAbout"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="init();"
+ buttons="accept"
+ buttoniconaccept="close"
+ onaccept="close();">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/about.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+ <stringbundleset id="aboutSet">
+ <stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
+ </stringbundleset>
+
+ <vbox id="clientBox" flex="1">
+ <hbox class="basic-info">
+ <vbox pack="center">
+ <image id="extensionIcon"/>
+ </vbox>
+ <vbox flex="1">
+ <label id="extensionName"/>
+ <label id="extensionVersion" crop="end"/>
+ </vbox>
+ </hbox>
+ <description id="extensionDescription" class="boxIndent"/>
+
+ <separator id="groove" class="groove"/>
+
+ <vbox id="extensionDetailsBox" flex="1">
+ <label id="extensionCreatorLabel" class="sectionTitle">&creator.label;</label>
+ <hbox id="creatorBox" class="boxIndent">
+ <label id="extensionCreator" flex="1" crop="end"/>
+ <label id="extensionHomepage" onclick="if (event.button == 0) { loadHomepage(event); }"
+ class="text-link" value="&homepage.label;"/>
+ </hbox>
+
+ <label id="extensionDevelopers" class="sectionTitle">&developers.label;</label>
+ <vbox flex="1" id="developersBox" class="boxIndent"/>
+ <label id="extensionTranslators" class="sectionTitle">&translators.label;</label>
+ <vbox flex="1" id="translatorsBox" class="boxIndent"/>
+ <label id="extensionContributors" class="sectionTitle">&contributors.label;</label>
+ <vbox flex="1" id="contributorsBox" class="boxIndent"/>
+ </vbox>
+ </vbox>
+
+</dialog>
diff --git a/toolkit/mozapps/extensions/content/blocklist.css b/toolkit/mozapps/extensions/content/blocklist.css
new file mode 100644
index 000000000..cb48005a2
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/blocklist.css
@@ -0,0 +1,11 @@
+/* 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/. */
+
+.hardBlockedAddon {
+ -moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#hardblockedaddon");
+}
+
+.softBlockedAddon {
+ -moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#softblockedaddon");
+}
diff --git a/toolkit/mozapps/extensions/content/blocklist.js b/toolkit/mozapps/extensions/content/blocklist.js
new file mode 100644
index 000000000..6b47fd652
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/blocklist.js
@@ -0,0 +1,72 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gArgs;
+
+function init() {
+ var hasHardBlocks = false;
+ var hasSoftBlocks = false;
+ gArgs = window.arguments[0].wrappedJSObject;
+
+ // NOTE: We use strings from the "updates.properties" bundleset to change the
+ // text on the "Cancel" button to "Restart Later". (bug 523784)
+ let bundle = Services.strings.
+ createBundle("chrome://mozapps/locale/update/updates.properties");
+ let cancelButton = document.documentElement.getButton("cancel");
+ cancelButton.setAttribute("label", bundle.GetStringFromName("restartLaterButton"));
+ cancelButton.setAttribute("accesskey",
+ bundle.GetStringFromName("restartLaterButton.accesskey"));
+
+ var richlist = document.getElementById("addonList");
+ var list = gArgs.list;
+ list.sort(function listSort(a, b) { return String.localeCompare(a.name, b.name); });
+ for (let listItem of list) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("name", listItem.name);
+ item.setAttribute("version", listItem.version);
+ item.setAttribute("icon", listItem.icon);
+ if (listItem.blocked) {
+ item.setAttribute("class", "hardBlockedAddon");
+ hasHardBlocks = true;
+ }
+ else {
+ item.setAttribute("class", "softBlockedAddon");
+ hasSoftBlocks = true;
+ }
+ richlist.appendChild(item);
+ }
+
+ if (hasHardBlocks && hasSoftBlocks)
+ document.getElementById("bothMessage").hidden = false;
+ else if (hasHardBlocks)
+ document.getElementById("hardBlockMessage").hidden = false;
+ else
+ document.getElementById("softBlockMessage").hidden = false;
+
+ var link = document.getElementById("moreInfo");
+ if (list.length == 1 && list[0].url) {
+ link.setAttribute("href", list[0].url);
+ }
+ else {
+ var url = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
+ link.setAttribute("href", url);
+ }
+}
+
+function finish(shouldRestartNow) {
+ gArgs.restart = shouldRestartNow;
+ var list = gArgs.list;
+ var items = document.getElementById("addonList").childNodes;
+ for (let i = 0; i < list.length; i++) {
+ if (!list[i].blocked)
+ list[i].disable = items[i].checked;
+ }
+ return true;
+}
diff --git a/toolkit/mozapps/extensions/content/blocklist.xml b/toolkit/mozapps/extensions/content/blocklist.xml
new file mode 100644
index 000000000..74474392f
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/blocklist.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % blocklistDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd" >
+ %blocklistDTD;
+]>
+
+<bindings id="blocklistBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="hardblockedaddon">
+ <content align="start">
+ <xul:image xbl:inherits="src=icon"/>
+ <xul:vbox flex="1">
+ <xul:hbox class="addon-name-version">
+ <xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
+ <xul:label class="addonVersion" xbl:inherits="value=version"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:spacer flex="1"/>
+ <xul:label class="blockedLabel" value="&blocklist.blocked.label;"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ </binding>
+
+ <binding id="softblockedaddon">
+ <content align="start">
+ <xul:image xbl:inherits="src=icon"/>
+ <xul:vbox flex="1">
+ <xul:hbox class="addon-name-version">
+ <xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
+ <xul:label class="addonVersion" xbl:inherits="value=version"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:spacer flex="1"/>
+ <xul:checkbox class="disableCheckbox" checked="true" label="&blocklist.checkbox.label;"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <field name="_checkbox">
+ document.getAnonymousElementByAttribute(this, "class", "disableCheckbox")
+ </field>
+ <property name="checked" readonly="true">
+ <getter>
+ return this._checkbox.checked;
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/toolkit/mozapps/extensions/content/blocklist.xul b/toolkit/mozapps/extensions/content/blocklist.xul
new file mode 100644
index 000000000..240d9e4e1
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/blocklist.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/blocklist.css"?>
+<?xml-stylesheet href="chrome://mozapps/content/extensions/blocklist.css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd">
+%extensionsDTD;
+]>
+
+<dialog windowtype="Addons:Blocklist" title="&blocklist.title;" align="stretch"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="init();" ondialogaccept="return finish(true)"
+ ondialogcancel="return finish(false)"
+ buttons="accept,cancel" style="&blocklist.style;"
+ buttonlabelaccept="&blocklist.accept.label;"
+ buttonaccesskeyaccept="&blocklist.accept.accesskey;">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/blocklist.js"/>
+
+ <hbox align="stretch" flex="1">
+ <vbox pack="start">
+ <image class="error-icon"/>
+ </vbox>
+ <vbox flex="1">
+ <label>&blocklist.summary;</label>
+ <separator class="thin"/>
+ <richlistbox id="addonList" flex="1"/>
+ <separator class="thin"/>
+ <description id="bothMessage" hidden="true" class="bold">&blocklist.softandhard;</description>
+ <description id="hardBlockMessage" hidden="true" class="bold">&blocklist.hardblocked;</description>
+ <description id="softBlockMessage" hidden="true" class="bold">&blocklist.softblocked;</description>
+ <hbox pack="start">
+ <label id="moreInfo" class="text-link" value="&blocklist.moreinfo;"/>
+ </hbox>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/toolkit/mozapps/extensions/content/eula.js b/toolkit/mozapps/extensions/content/eula.js
new file mode 100644
index 000000000..a05f7fe1c
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/eula.js
@@ -0,0 +1,21 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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";
+
+function Startup() {
+ var bundle = document.getElementById("extensionsStrings");
+ var addon = window.arguments[0].addon;
+
+ document.documentElement.setAttribute("addontype", addon.type);
+
+ if (addon.iconURL)
+ document.getElementById("icon").src = addon.iconURL;
+
+ var label = document.createTextNode(bundle.getFormattedString("eulaHeader", [addon.name]));
+ document.getElementById("heading").appendChild(label);
+ document.getElementById("eula").value = addon.eula;
+}
diff --git a/toolkit/mozapps/extensions/content/eula.xul b/toolkit/mozapps/extensions/content/eula.xul
new file mode 100644
index 000000000..10e657951
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/eula.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/eula.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+]>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&eula.title;" width="&eula.width;" height="&eula.height;"
+ buttons="accept,cancel" buttonlabelaccept="&eula.accept;"
+ ondialogaccept="window.arguments[0].accepted = true"
+ onload="Startup();">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/eula.js"/>
+
+ <stringbundleset id="extensionsSet">
+ <stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
+ </stringbundleset>
+
+ <hbox id="heading-container">
+ <image id="icon"/>
+ <label id="heading" flex="1"/>
+ </hbox>
+
+ <textbox id="eula" multiline="true" readonly="true" flex="1"/>
+</dialog>
diff --git a/toolkit/mozapps/extensions/content/extensions.css b/toolkit/mozapps/extensions/content/extensions.css
new file mode 100644
index 000000000..41c140565
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/extensions.css
@@ -0,0 +1,288 @@
+/* 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/. */
+
+@namespace xhtml "http://www.w3.org/1999/xhtml";
+
+/* HTML link elements do weird things to the layout if they are not hidden */
+xhtml|link {
+ display: none;
+}
+
+#categories {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#categories-list");
+}
+
+.category {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#category");
+}
+
+.sort-controls {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#sorters");
+}
+
+.addon[status="installed"] {
+ -moz-box-orient: vertical;
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-generic");
+}
+
+.addon[status="installing"] {
+ -moz-box-orient: vertical;
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-installing");
+}
+
+.addon[pending="uninstall"] {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-uninstalled");
+}
+
+.creator {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#creator-link");
+}
+
+.translators {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#translators-list");
+}
+
+.meta-rating {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#rating");
+}
+
+.download-progress, .download-progress[mode="undetermined"] {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#download-progress");
+}
+
+.install-status {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#install-status");
+}
+
+.detail-row {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#detail-row");
+}
+
+.text-list {
+ white-space: pre-line;
+ -moz-user-select: element;
+}
+
+setting, row[unsupported="true"] {
+ display: none;
+}
+
+setting[type="bool"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-bool");
+}
+
+setting[type="bool"][localized="true"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-localized-bool");
+}
+
+setting[type="bool"]:not([learnmore]) .preferences-learnmore {
+ visibility: collapse;
+}
+
+setting[type="boolint"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-boolint");
+}
+
+setting[type="integer"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-integer");
+}
+
+setting[type="integer"]:not([size]) textbox {
+ -moz-box-flex: 1;
+}
+
+setting[type="control"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-control");
+}
+
+setting[type="string"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-string");
+}
+
+setting[type="color"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-color");
+}
+
+setting[type="file"],
+setting[type="directory"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-path");
+}
+
+setting[type="radio"],
+setting[type="menulist"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi");
+}
+
+#addonitem-popup > menuitem[disabled="true"] {
+ display: none;
+}
+
+#addonitem-popup[addontype="theme"] > #menuitem_enableItem,
+#addonitem-popup[addontype="theme"] > #menuitem_disableItem,
+#addonitem-popup:not([addontype="theme"]) > #menuitem_enableTheme,
+#addonitem-popup:not([addontype="theme"]) > #menuitem_disableTheme {
+ display: none;
+}
+
+#header-searching:not([active]) {
+ visibility: hidden;
+}
+
+#search-list[local="false"] > .addon[remote="false"],
+#search-list[remote="false"] > .addon[remote="true"] {
+ visibility: collapse;
+}
+
+#detail-view {
+ overflow: auto;
+}
+
+.addon:not([notification="warning"]) .warning,
+.addon:not([notification="error"]) .error,
+.addon:not([notification="info"]) .info,
+.addon:not([pending]) .pending,
+.addon:not([upgrade="true"]) .update-postfix,
+.addon:not([native="true"]) .nativeAddon,
+.addon:not([native="false"]) .compatAddon,
+.addon[active="true"] .disabled-postfix,
+.addon[pending="install"] .update-postfix,
+.addon[pending="install"] .disabled-postfix,
+#detail-view:not([notification="warning"]) .warning,
+#detail-view:not([notification="error"]) .error,
+#detail-view:not([notification="info"]) .info,
+#detail-view:not([pending]) .pending,
+#detail-view:not([upgrade="true"]) .update-postfix,
+#detail-view[active="true"] .disabled-postfix,
+#detail-view[loading] .detail-view-container,
+#detail-view:not([loading]) .alert-container,
+.detail-row:not([value]),
+#search-list[remote="false"] #search-allresults-link {
+ display: none;
+}
+
+#addons-page:not([warning]) #list-view > .global-warning-container {
+ display: none;
+}
+#addon-list .date-updated {
+ display: none;
+}
+
+.view-pane:not(#updates-view) .addon .relnotes-toggle,
+.view-pane:not(#updates-view) .addon .include-update,
+#updates-view:not([updatetype="available"]) .addon .include-update,
+#updates-view[updatetype="available"] .addon .update-available-notice {
+ display: none;
+}
+
+#addons-page:not([warning]) .global-warning,
+#addons-page:not([warning="safemode"]) .global-warning-safemode,
+#addons-page:not([warning="checkcompatibility"]) .global-warning-checkcompatibility,
+#addons-page:not([warning="updatesecurity"]) .global-warning-updatesecurity {
+ display: none;
+}
+
+/* Plugins aren't yet disabled by safemode (bug 342333),
+ so don't show that warning when viewing plugins. */
+#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container,
+#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning {
+ display: none;
+}
+
+#addons-page .view-pane:not([type="plugin"]) .plugin-info-container {
+ display: none;
+}
+
+#addons-page .view-pane:not([type="experiment"]) .experiment-info-container {
+ display: none;
+}
+
+.addon .relnotes {
+ -moz-user-select: text;
+}
+#detail-name, #detail-desc, #detail-fulldesc {
+ -moz-user-select: text;
+}
+
+/* Make sure we're not animating hidden images. See bug 623739. */
+#view-port:not([selectedIndex="0"]) #discover-view .loading,
+#discover-view:not([selectedIndex="0"]) .loading {
+ display: none;
+}
+
+/* Elements in unselected richlistitems cannot be focused */
+richlistitem:not([selected]) * {
+ -moz-user-focus: ignore;
+}
+
+#header-search {
+ width: 22em;
+}
+
+#header-utils-btn {
+ -moz-user-focus: normal;
+}
+
+.discover-button[disabled="true"] {
+ display: none;
+}
+
+#experiments-learn-more[disabled="true"] {
+ display: none;
+}
+
+#experiments-change-telemetry[disabled="true"] {
+ display: none;
+}
+
+.view-pane[type="experiment"] .error,
+.view-pane[type="experiment"] .warning,
+.view-pane[type="experiment"] .addon:not([pending="uninstall"]) .pending,
+.view-pane[type="experiment"] .disabled-postfix,
+.view-pane[type="experiment"] .update-postfix,
+.view-pane[type="experiment"] .version,
+#detail-view[type="experiment"] .alert-container,
+#detail-view[type="experiment"] #detail-version,
+#detail-view[type="experiment"] #detail-creator {
+ display: none;
+}
+
+.view-pane:not([type="experiment"]) .experiment-container,
+.view-pane:not([type="experiment"]) #detail-experiment-container {
+ display: none;
+}
+
+.addon[type="experiment"][status="installing"] .experiment-time,
+.addon[type="experiment"][status="installing"] .experiment-state {
+ display: none;
+}
+
+/* Indicator style for extension target application */
+.addon[native] .nativeIndicator {
+ margin-left: 5pt;
+ padding-bottom: 1pt;
+}
+.addon[native][active="false"] .nativeIndicator {
+ opacity: 0.4;
+}
+.addon[native] .nativeAddon {
+ color: #3366FF;
+}
+.addon[native] .compatAddon {
+ color: #FF6600;
+}
+
+/* Translators for Language Pack details */
+.translators > label {
+ -moz-margin-start: 0px;
+ -moz-margin-end: 0px;
+}
diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js
new file mode 100644
index 000000000..a799eeebb
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -0,0 +1,3659 @@
+/* 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";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/addons/AddonRepository.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
+ return Cu.import("resource://gre/modules/devtools/ToolboxProcess.jsm", {}).
+ BrowserToolboxProcess;
+});
+XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
+ "resource:///modules/experiments/Experiments.jsm");
+
+const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
+const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
+const PREF_XPI_ENABLED = "xpinstall.enabled";
+const PREF_MAXRESULTS = "extensions.getAddons.maxResults";
+const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
+const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled";
+const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
+const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
+const PREF_ADDON_DEBUGGING_ENABLED = "devtools.chrome.enabled";
+const PREF_REMOTE_DEBUGGING_ENABLED = "devtools.debugger.remote-enabled";
+
+const LOADING_MSG_DELAY = 100;
+
+const SEARCH_SCORE_MULTIPLIER_NAME = 2;
+const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2;
+
+// Use integers so search scores are sortable by nsIXULSortService
+const SEARCH_SCORE_MATCH_WHOLEWORD = 10;
+const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6;
+const SEARCH_SCORE_MATCH_SUBSTRING = 3;
+
+const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
+const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl";
+
+const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
+
+var gViewDefault = "addons://discover/";
+
+var gStrings = {};
+XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc",
+ "@mozilla.org/intl/stringbundle;1",
+ "nsIStringBundleService");
+
+XPCOMUtils.defineLazyGetter(gStrings, "brand", function brandLazyGetter() {
+ return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties");
+});
+XPCOMUtils.defineLazyGetter(gStrings, "ext", function extLazyGetter() {
+ return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties");
+});
+XPCOMUtils.defineLazyGetter(gStrings, "dl", function dlLazyGetter() {
+ return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties");
+});
+
+XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function brandShortNameLazyGetter() {
+ return this.brand.GetStringFromName("brandShortName");
+});
+XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function appVersionLazyGetter() {
+ return Services.appinfo.version;
+});
+
+document.addEventListener("load", initialize, true);
+window.addEventListener("unload", shutdown, false);
+
+var gPendingInitializations = 1;
+this.__defineGetter__("gIsInitializing", function gIsInitializingGetter() gPendingInitializations > 0);
+
+function initialize(event) {
+ // XXXbz this listener gets _all_ load events for all nodes in the
+ // document... but relies on not being called "too early".
+ if (event.target instanceof XMLStylesheetProcessingInstruction) {
+ return;
+ }
+ document.removeEventListener("load", initialize, true);
+
+ let globalCommandSet = document.getElementById("globalCommandSet");
+ globalCommandSet.addEventListener("command", function(event) {
+ gViewController.doCommand(event.target.id);
+ });
+
+ let viewCommandSet = document.getElementById("viewCommandSet");
+ viewCommandSet.addEventListener("commandupdate", function(event) {
+ gViewController.updateCommands();
+ });
+ viewCommandSet.addEventListener("command", function(event) {
+ gViewController.doCommand(event.target.id);
+ });
+
+ let detailScreenshot = document.getElementById("detail-screenshot");
+ detailScreenshot.addEventListener("load", function(event) {
+ this.removeAttribute("loading");
+ });
+ detailScreenshot.addEventListener("error", function(event) {
+ this.setAttribute("loading", "error");
+ });
+
+ let addonPage = document.getElementById("addons-page");
+ addonPage.addEventListener("dragenter", function(event) {
+ gDragDrop.onDragOver(event);
+ });
+ addonPage.addEventListener("dragover", function(event) {
+ gDragDrop.onDragOver(event);
+ });
+ addonPage.addEventListener("drop", function(event) {
+ gDragDrop.onDrop(event);
+ });
+ addonPage.addEventListener("keypress", function(event) {
+ gHeader.onKeyPress(event);
+ });
+
+ if (!isDiscoverEnabled()) {
+ gViewDefault = "addons://list/extension";
+ }
+
+ gViewController.initialize();
+ gCategories.initialize();
+ gHeader.initialize();
+ gEventManager.initialize();
+ Services.obs.addObserver(sendEMPong, "EM-ping", false);
+ Services.obs.notifyObservers(window, "EM-loaded", "");
+
+ // If the initial view has already been selected (by a call to loadView from
+ // the above notifications) then bail out now
+ if (gViewController.initialViewSelected)
+ return;
+
+ // If there is a history state to restore then use that
+ if (window.history.state) {
+ gViewController.updateState(window.history.state);
+ return;
+ }
+
+ // Default to the last selected category
+ var view = gCategories.node.value;
+
+ // Allow passing in a view through the window arguments
+ if ("arguments" in window && window.arguments.length > 0 &&
+ window.arguments[0] !== null && "view" in window.arguments[0]) {
+ view = window.arguments[0].view;
+ }
+
+ gViewController.loadInitialView(view);
+
+ Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false);
+ Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false);
+}
+
+function notifyInitialized() {
+ if (!gIsInitializing)
+ return;
+
+ gPendingInitializations--;
+ if (!gIsInitializing) {
+ var event = document.createEvent("Events");
+ event.initEvent("Initialized", true, true);
+ document.dispatchEvent(event);
+ }
+}
+
+function shutdown() {
+ gCategories.shutdown();
+ gSearchView.shutdown();
+ gEventManager.shutdown();
+ gViewController.shutdown();
+ Services.obs.removeObserver(sendEMPong, "EM-ping");
+ Services.prefs.removeObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged);
+ Services.prefs.removeObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged);
+}
+
+function sendEMPong(aSubject, aTopic, aData) {
+ Services.obs.notifyObservers(window, "EM-pong", "");
+}
+
+// Used by external callers to load a specific view into the manager
+function loadView(aViewId) {
+ if (!gViewController.initialViewSelected) {
+ // The caller opened the window and immediately loaded the view so it
+ // should be the initial history entry
+
+ gViewController.loadInitialView(aViewId);
+ } else {
+ gViewController.loadView(aViewId);
+ }
+}
+
+function isDiscoverEnabled() {
+ if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID)
+ return false;
+
+ try {
+ if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED))
+ return false;
+ } catch (e) {}
+
+ try {
+ if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED))
+ return false;
+ } catch (e) {}
+
+ return true;
+}
+
+function getExperimentEndDate(aAddon) {
+ if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
+ return 0;
+ }
+
+ if (!aAddon.isActive) {
+ return aAddon.endDate;
+ }
+
+ let experiment = Experiments.instance().getActiveExperiment();
+ if (!experiment) {
+ return 0;
+ }
+
+ return experiment.endDate;
+}
+
+/**
+ * Obtain the main DOMWindow for the current context.
+ */
+function getMainWindow() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+}
+
+function getBrowserElement() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+}
+
+/**
+ * Obtain the DOMWindow that can open a preferences pane.
+ *
+ * This is essentially "get the browser chrome window" with the added check
+ * that the supposed browser chrome window is capable of opening a preferences
+ * pane.
+ *
+ * This may return null if we can't find the browser chrome window.
+ */
+function getMainWindowWithPreferencesPane() {
+ let mainWindow = getMainWindow();
+ if (mainWindow && "openAdvancedPreferences" in mainWindow) {
+ return mainWindow;
+ } else {
+ return null;
+ }
+}
+
+/**
+ * A wrapper around the HTML5 session history service that allows the browser
+ * back/forward controls to work within the manager
+ */
+var HTML5History = {
+ get index() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .sessionHistory.index;
+ },
+
+ get canGoBack() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .canGoBack;
+ },
+
+ get canGoForward() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .canGoForward;
+ },
+
+ back: function HTML5History_back() {
+ window.history.back();
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ forward: function HTML5History_forward() {
+ window.history.forward();
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ pushState: function HTML5History_pushState(aState) {
+ window.history.pushState(aState, document.title);
+ },
+
+ replaceState: function HTML5History_replaceState(aState) {
+ window.history.replaceState(aState, document.title);
+ },
+
+ popState: function HTML5History_popState() {
+ function onStatePopped(aEvent) {
+ window.removeEventListener("popstate", onStatePopped, true);
+ // TODO To ensure we can't go forward again we put an additional entry
+ // for the current state into the history. Ideally we would just strip
+ // the history but there doesn't seem to be a way to do that. Bug 590661
+ window.history.pushState(aEvent.state, document.title);
+ }
+ window.addEventListener("popstate", onStatePopped, true);
+ window.history.back();
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ }
+};
+
+/**
+ * A wrapper around a fake history service
+ */
+var FakeHistory = {
+ pos: 0,
+ states: [null],
+
+ get index() {
+ return this.pos;
+ },
+
+ get canGoBack() {
+ return this.pos > 0;
+ },
+
+ get canGoForward() {
+ return (this.pos + 1) < this.states.length;
+ },
+
+ back: function FakeHistory_back() {
+ if (this.pos == 0)
+ throw Components.Exception("Cannot go back from this point");
+
+ this.pos--;
+ gViewController.updateState(this.states[this.pos]);
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ forward: function FakeHistory_forward() {
+ if ((this.pos + 1) >= this.states.length)
+ throw Components.Exception("Cannot go forward from this point");
+
+ this.pos++;
+ gViewController.updateState(this.states[this.pos]);
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ pushState: function FakeHistory_pushState(aState) {
+ this.pos++;
+ this.states.splice(this.pos, this.states.length);
+ this.states.push(aState);
+ },
+
+ replaceState: function FakeHistory_replaceState(aState) {
+ this.states[this.pos] = aState;
+ },
+
+ popState: function FakeHistory_popState() {
+ if (this.pos == 0)
+ throw Components.Exception("Cannot popState from this view");
+
+ this.states.splice(this.pos, this.states.length);
+ this.pos--;
+
+ gViewController.updateState(this.states[this.pos]);
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ }
+};
+
+// If the window has a session history then use the HTML5 History wrapper
+// otherwise use our fake history implementation
+if (window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .sessionHistory) {
+ var gHistory = HTML5History;
+}
+else {
+ gHistory = FakeHistory;
+}
+
+var gEventManager = {
+ _listeners: {},
+ _installListeners: [],
+
+ initialize: function gEM_initialize() {
+ var self = this;
+ const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling",
+ "onDisabled", "onUninstalling", "onUninstalled",
+ "onInstalled", "onOperationCancelled",
+ "onUpdateAvailable", "onUpdateFinished",
+ "onCompatibilityUpdateAvailable",
+ "onPropertyChanged"];
+ for (let evt of ADDON_EVENTS) {
+ let event = evt;
+ self[event] = function initialize_delegateAddonEvent(...aArgs) {
+ self.delegateAddonEvent(event, aArgs);
+ };
+ }
+
+ const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted",
+ "onDownloadEnded", "onDownloadFailed",
+ "onDownloadProgress", "onDownloadCancelled",
+ "onInstallStarted", "onInstallEnded",
+ "onInstallFailed", "onInstallCancelled",
+ "onExternalInstall"];
+ for (let evt of INSTALL_EVENTS) {
+ let event = evt;
+ self[event] = function initialize_delegateInstallEvent(...aArgs) {
+ self.delegateInstallEvent(event, aArgs);
+ };
+ }
+
+ AddonManager.addManagerListener(this);
+ AddonManager.addInstallListener(this);
+ AddonManager.addAddonListener(this);
+
+ this.refreshGlobalWarning();
+ this.refreshAutoUpdateDefault();
+
+ var contextMenu = document.getElementById("addonitem-popup");
+ contextMenu.addEventListener("popupshowing", function contextMenu_onPopupshowing() {
+ var addon = gViewController.currentViewObj.getSelectedAddon();
+ contextMenu.setAttribute("addontype", addon.type);
+
+ var menuSep = document.getElementById("addonitem-menuseparator");
+ var countMenuItemsBeforeSep = 0;
+ for (let child of contextMenu.children) {
+ if (child == menuSep) {
+ break;
+ }
+ if (child.nodeName == "menuitem" &&
+ gViewController.isCommandEnabled(child.command)) {
+ countMenuItemsBeforeSep++;
+ }
+ }
+
+ // Hide the separator if there are no visible menu items before it
+ menuSep.hidden = (countMenuItemsBeforeSep == 0);
+
+ }, false);
+ },
+
+ shutdown: function gEM_shutdown() {
+ AddonManager.removeManagerListener(this);
+ AddonManager.removeInstallListener(this);
+ AddonManager.removeAddonListener(this);
+ },
+
+ registerAddonListener: function gEM_registerAddonListener(aListener, aAddonId) {
+ if (!(aAddonId in this._listeners))
+ this._listeners[aAddonId] = [];
+ else if (this._listeners[aAddonId].indexOf(aListener) != -1)
+ return;
+ this._listeners[aAddonId].push(aListener);
+ },
+
+ unregisterAddonListener: function gEM_unregisterAddonListener(aListener, aAddonId) {
+ if (!(aAddonId in this._listeners))
+ return;
+ var index = this._listeners[aAddonId].indexOf(aListener);
+ if (index == -1)
+ return;
+ this._listeners[aAddonId].splice(index, 1);
+ },
+
+ registerInstallListener: function gEM_registerInstallListener(aListener) {
+ if (this._installListeners.indexOf(aListener) != -1)
+ return;
+ this._installListeners.push(aListener);
+ },
+
+ unregisterInstallListener: function gEM_unregisterInstallListener(aListener) {
+ var i = this._installListeners.indexOf(aListener);
+ if (i == -1)
+ return;
+ this._installListeners.splice(i, 1);
+ },
+
+ delegateAddonEvent: function gEM_delegateAddonEvent(aEvent, aParams) {
+ var addon = aParams.shift();
+ if (!(addon.id in this._listeners))
+ return;
+
+ var listeners = this._listeners[addon.id];
+ for (let listener of listeners) {
+ if (!(aEvent in listener))
+ continue;
+ try {
+ listener[aEvent].apply(listener, aParams);
+ } catch(e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ delegateInstallEvent: function gEM_delegateInstallEvent(aEvent, aParams) {
+ var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon;
+ // If the install is an update then send the event to all listeners
+ // registered for the existing add-on
+ if (existingAddon)
+ this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams));
+
+ for (let listener of this._installListeners) {
+ if (!(aEvent in listener))
+ continue;
+ try {
+ listener[aEvent].apply(listener, aParams);
+ } catch(e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ refreshGlobalWarning: function gEM_refreshGlobalWarning() {
+ var page = document.getElementById("addons-page");
+
+ if (Services.appinfo.inSafeMode) {
+ page.setAttribute("warning", "safemode");
+ return;
+ }
+
+ if (AddonManager.checkUpdateSecurityDefault &&
+ !AddonManager.checkUpdateSecurity) {
+ page.setAttribute("warning", "updatesecurity");
+ return;
+ }
+
+ if (!AddonManager.checkCompatibility) {
+ page.setAttribute("warning", "checkcompatibility");
+ return;
+ }
+
+ page.removeAttribute("warning");
+ },
+
+ refreshAutoUpdateDefault: function gEM_refreshAutoUpdateDefault() {
+ var updateEnabled = AddonManager.updateEnabled;
+ var autoUpdateDefault = AddonManager.autoUpdateDefault;
+
+ // The checkbox needs to reflect that both prefs need to be true
+ // for updates to be checked for and applied automatically
+ document.getElementById("utils-autoUpdateDefault")
+ .setAttribute("checked", updateEnabled && autoUpdateDefault);
+
+ document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault;
+ document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault;
+ },
+
+ onCompatibilityModeChanged: function gEM_onCompatibilityModeChanged() {
+ this.refreshGlobalWarning();
+ },
+
+ onCheckUpdateSecurityChanged: function gEM_onCheckUpdateSecurityChanged() {
+ this.refreshGlobalWarning();
+ },
+
+ onUpdateModeChanged: function gEM_onUpdateModeChanged() {
+ this.refreshAutoUpdateDefault();
+ }
+};
+
+
+var gViewController = {
+ viewPort: null,
+ currentViewId: "",
+ currentViewObj: null,
+ currentViewRequest: 0,
+ viewObjects: {},
+ viewChangeCallback: null,
+ initialViewSelected: false,
+ lastHistoryIndex: -1,
+
+ initialize: function gVC_initialize() {
+ this.viewPort = document.getElementById("view-port");
+
+ this.viewObjects["search"] = gSearchView;
+ this.viewObjects["discover"] = gDiscoverView;
+ this.viewObjects["list"] = gListView;
+ this.viewObjects["detail"] = gDetailView;
+ this.viewObjects["updates"] = gUpdatesView;
+
+ for each (let view in this.viewObjects)
+ view.initialize();
+
+ window.controllers.appendController(this);
+
+ window.addEventListener("popstate",
+ function window_onStatePopped(e) {
+ gViewController.updateState(e.state);
+ },
+ false);
+ },
+
+ shutdown: function gVC_shutdown() {
+ if (this.currentViewObj)
+ this.currentViewObj.hide();
+ this.currentViewRequest = 0;
+
+ for each(let view in this.viewObjects) {
+ if ("shutdown" in view) {
+ try {
+ view.shutdown();
+ } catch(e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+ }
+
+ window.controllers.removeController(this);
+ },
+
+ updateState: function gVC_updateState(state) {
+ try {
+ this.loadViewInternal(state.view, state.previousView, state);
+ this.lastHistoryIndex = gHistory.index;
+ }
+ catch (e) {
+ // The attempt to load the view failed, try moving further along history
+ if (this.lastHistoryIndex > gHistory.index) {
+ if (gHistory.canGoBack)
+ gHistory.back();
+ else
+ gViewController.replaceView(gViewDefault);
+ } else {
+ if (gHistory.canGoForward)
+ gHistory.forward();
+ else
+ gViewController.replaceView(gViewDefault);
+ }
+ }
+ },
+
+ parseViewId: function gVC_parseViewId(aViewId) {
+ var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/;
+ var [,viewType, viewParam] = aViewId.match(matchRegex) || [];
+ return {type: viewType, param: decodeURIComponent(viewParam)};
+ },
+
+ get isLoading() {
+ return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading");
+ },
+
+ loadView: function gVC_loadView(aViewId) {
+ var isRefresh = false;
+ if (aViewId == this.currentViewId) {
+ if (this.isLoading)
+ return;
+ if (!("refresh" in this.currentViewObj))
+ return;
+ if (!this.currentViewObj.canRefresh())
+ return;
+ isRefresh = true;
+ }
+
+ var state = {
+ view: aViewId,
+ previousView: this.currentViewId
+ };
+ if (!isRefresh) {
+ gHistory.pushState(state);
+ this.lastHistoryIndex = gHistory.index;
+ }
+ this.loadViewInternal(aViewId, this.currentViewId, state);
+ },
+
+ // Replaces the existing view with a new one, rewriting the current history
+ // entry to match.
+ replaceView: function gVC_replaceView(aViewId) {
+ if (aViewId == this.currentViewId)
+ return;
+
+ var state = {
+ view: aViewId,
+ previousView: null
+ };
+ gHistory.replaceState(state);
+ this.loadViewInternal(aViewId, null, state);
+ },
+
+ loadInitialView: function gVC_loadInitialView(aViewId) {
+ var state = {
+ view: aViewId,
+ previousView: null
+ };
+ gHistory.replaceState(state);
+
+ this.loadViewInternal(aViewId, null, state);
+ this.initialViewSelected = true;
+ notifyInitialized();
+ },
+
+ loadViewInternal: function gVC_loadViewInternal(aViewId, aPreviousView, aState) {
+ var view = this.parseViewId(aViewId);
+
+ if (!view.type || !(view.type in this.viewObjects))
+ throw Components.Exception("Invalid view: " + view.type);
+
+ var viewObj = this.viewObjects[view.type];
+ if (!viewObj.node)
+ throw Components.Exception("Root node doesn't exist for '" + view.type + "' view");
+
+ if (this.currentViewObj && aViewId != aPreviousView) {
+ try {
+ let canHide = this.currentViewObj.hide();
+ if (canHide === false)
+ return;
+ this.viewPort.selectedPanel.removeAttribute("loading");
+ } catch (e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+
+ gCategories.select(aViewId, aPreviousView);
+
+ this.currentViewId = aViewId;
+ this.currentViewObj = viewObj;
+
+ this.viewPort.selectedPanel = this.currentViewObj.node;
+ this.viewPort.selectedPanel.setAttribute("loading", "true");
+ this.currentViewObj.node.focus();
+
+ if (aViewId == aPreviousView)
+ this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState);
+ else
+ this.currentViewObj.show(view.param, ++this.currentViewRequest, aState);
+ },
+
+ // Moves back in the document history and removes the current history entry
+ popState: function gVC_popState(aCallback) {
+ this.viewChangeCallback = aCallback;
+ gHistory.popState();
+ },
+
+ notifyViewChanged: function gVC_notifyViewChanged() {
+ this.viewPort.selectedPanel.removeAttribute("loading");
+
+ if (this.viewChangeCallback) {
+ this.viewChangeCallback();
+ this.viewChangeCallback = null;
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("ViewChanged", true, true);
+ this.currentViewObj.node.dispatchEvent(event);
+ },
+
+ commands: {
+ cmd_back: {
+ isEnabled: function cmd_back_isEnabled() {
+ return gHistory.canGoBack;
+ },
+ doCommand: function cmd_back_doCommand() {
+ gHistory.back();
+ }
+ },
+
+ cmd_forward: {
+ isEnabled: function cmd_forward_isEnabled() {
+ return gHistory.canGoForward;
+ },
+ doCommand: function cmd_forward_doCommand() {
+ gHistory.forward();
+ }
+ },
+
+ cmd_focusSearch: {
+ isEnabled: () => true,
+ doCommand: function cmd_focusSearch_doCommand() {
+ gHeader.focusSearchBox();
+ }
+ },
+
+ cmd_restartApp: {
+ isEnabled: function cmd_restartApp_isEnabled() true,
+ doCommand: function cmd_restartApp_doCommand() {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (cancelQuit.data)
+ return; // somebody canceled our quit request
+
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
+ getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ }
+ },
+
+ cmd_enableCheckCompatibility: {
+ isEnabled: function cmd_enableCheckCompatibility_isEnabled() true,
+ doCommand: function cmd_enableCheckCompatibility_doCommand() {
+ AddonManager.checkCompatibility = true;
+ }
+ },
+
+ cmd_enableUpdateSecurity: {
+ isEnabled: function cmd_enableUpdateSecurity_isEnabled() true,
+ doCommand: function cmd_enableUpdateSecurity_doCommand() {
+ AddonManager.checkUpdateSecurity = true;
+ }
+ },
+
+/* Plugincheck service is currently N/A for Pale Moon
+ cmd_pluginCheck: {
+ isEnabled: function cmd_pluginCheck_isEnabled() true,
+ doCommand: function cmd_pluginCheck_doCommand() {
+ openURL(Services.urlFormatter.formatURLPref("plugins.update.url"));
+ }
+ },
+*/
+
+ cmd_toggleAutoUpdateDefault: {
+ isEnabled: function cmd_toggleAutoUpdateDefault_isEnabled() true,
+ doCommand: function cmd_toggleAutoUpdateDefault_doCommand() {
+ if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) {
+ // One or both of the prefs is false, i.e. the checkbox is not checked.
+ // Now toggle both to true. If the user wants us to auto-update
+ // add-ons, we also need to auto-check for updates.
+ AddonManager.updateEnabled = true;
+ AddonManager.autoUpdateDefault = true;
+ } else {
+ // Both prefs are true, i.e. the checkbox is checked.
+ // Toggle the auto pref to false, but don't touch the enabled check.
+ AddonManager.autoUpdateDefault = false;
+ }
+ }
+ },
+
+ cmd_resetAddonAutoUpdate: {
+ isEnabled: function cmd_resetAddonAutoUpdate_isEnabled() true,
+ doCommand: function cmd_resetAddonAutoUpdate_doCommand() {
+ AddonManager.getAllAddons(function cmd_resetAddonAutoUpdate_getAllAddons(aAddonList) {
+ for (let addon of aAddonList) {
+ if ("applyBackgroundUpdates" in addon)
+ addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+ }
+ });
+ }
+ },
+
+ cmd_goToDiscoverPane: {
+ isEnabled: function cmd_goToDiscoverPane_isEnabled() {
+ return gDiscoverView.enabled;
+ },
+ doCommand: function cmd_goToDiscoverPane_doCommand() {
+ gViewController.loadView("addons://discover/");
+ }
+ },
+
+ cmd_goToRecentUpdates: {
+ isEnabled: function cmd_goToRecentUpdates_isEnabled() true,
+ doCommand: function cmd_goToRecentUpdates_doCommand() {
+ gViewController.loadView("addons://updates/recent");
+ }
+ },
+
+ cmd_goToAvailableUpdates: {
+ isEnabled: function cmd_goToAvailableUpdates_isEnabled() true,
+ doCommand: function cmd_goToAvailableUpdates_doCommand() {
+ gViewController.loadView("addons://updates/available");
+ }
+ },
+
+ cmd_showItemDetails: {
+ isEnabled: function cmd_showItemDetails_isEnabled(aAddon) {
+ return !!aAddon && (gViewController.currentViewObj != gDetailView);
+ },
+ doCommand: function cmd_showItemDetails_doCommand(aAddon, aScrollToPreferences) {
+ gViewController.loadView("addons://detail/" +
+ encodeURIComponent(aAddon.id) +
+ (aScrollToPreferences ? "/preferences" : ""));
+ }
+ },
+
+ cmd_findAllUpdates: {
+ inProgress: false,
+ isEnabled: function cmd_findAllUpdates_isEnabled() !this.inProgress,
+ doCommand: function cmd_findAllUpdates_doCommand() {
+ this.inProgress = true;
+ gViewController.updateCommand("cmd_findAllUpdates");
+ document.getElementById("updates-noneFound").hidden = true;
+ document.getElementById("updates-progress").hidden = false;
+ document.getElementById("updates-manualUpdatesFound-btn").hidden = true;
+
+ var pendingChecks = 0;
+ var numUpdated = 0;
+ var numManualUpdates = 0;
+ var restartNeeded = false;
+ var self = this;
+
+ function updateStatus() {
+ if (pendingChecks > 0)
+ return;
+
+ self.inProgress = false;
+ gViewController.updateCommand("cmd_findAllUpdates");
+ document.getElementById("updates-progress").hidden = true;
+ gUpdatesView.maybeRefresh();
+
+ if (numManualUpdates > 0 && numUpdated == 0) {
+ document.getElementById("updates-manualUpdatesFound-btn").hidden = false;
+ return;
+ }
+
+ if (numUpdated == 0) {
+ document.getElementById("updates-noneFound").hidden = false;
+ return;
+ }
+
+ if (restartNeeded) {
+ document.getElementById("updates-downloaded").hidden = false;
+ document.getElementById("updates-restart-btn").hidden = false;
+ } else {
+ document.getElementById("updates-installed").hidden = false;
+ }
+ }
+
+ var updateInstallListener = {
+ onDownloadFailed: function cmd_findAllUpdates_downloadFailed() {
+ pendingChecks--;
+ updateStatus();
+ },
+ onInstallFailed: function cmd_findAllUpdates_installFailed() {
+ pendingChecks--;
+ updateStatus();
+ },
+ onInstallEnded: function cmd_findAllUpdates_installEnded(aInstall, aAddon) {
+ pendingChecks--;
+ numUpdated++;
+ if (isPending(aInstall.existingAddon, "upgrade"))
+ restartNeeded = true;
+ updateStatus();
+ }
+ };
+
+ var updateCheckListener = {
+ onUpdateAvailable: function cmd_findAllUpdates_updateAvailable(aAddon, aInstall) {
+ gEventManager.delegateAddonEvent("onUpdateAvailable",
+ [aAddon, aInstall]);
+ if (AddonManager.shouldAutoUpdate(aAddon)) {
+ aInstall.addListener(updateInstallListener);
+ aInstall.install();
+ } else {
+ pendingChecks--;
+ numManualUpdates++;
+ updateStatus();
+ }
+ },
+ onNoUpdateAvailable: function cmd_findAllUpdates_noUpdateAvailable(aAddon) {
+ pendingChecks--;
+ updateStatus();
+ },
+ onUpdateFinished: function cmd_findAllUpdates_updateFinished(aAddon, aError) {
+ gEventManager.delegateAddonEvent("onUpdateFinished",
+ [aAddon, aError]);
+ }
+ };
+
+ AddonManager.getAddonsByTypes(null, function cmd_findAllUpdates_getAddonsByTypes(aAddonList) {
+ for (let addon of aAddonList) {
+ if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) {
+ pendingChecks++;
+ addon.findUpdates(updateCheckListener,
+ AddonManager.UPDATE_WHEN_USER_REQUESTED);
+ }
+ }
+
+ if (pendingChecks == 0)
+ updateStatus();
+ });
+ }
+ },
+
+ cmd_findItemUpdates: {
+ isEnabled: function cmd_findItemUpdates_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return hasPermission(aAddon, "upgrade");
+ },
+ doCommand: function cmd_findItemUpdates_doCommand(aAddon) {
+ var listener = {
+ onUpdateAvailable: function cmd_findItemUpdates_updateAvailable(aAddon, aInstall) {
+ gEventManager.delegateAddonEvent("onUpdateAvailable",
+ [aAddon, aInstall]);
+ if (AddonManager.shouldAutoUpdate(aAddon))
+ aInstall.install();
+ },
+ onNoUpdateAvailable: function cmd_findItemUpdates_noUpdateAvailable(aAddon) {
+ gEventManager.delegateAddonEvent("onNoUpdateAvailable",
+ [aAddon]);
+ }
+ };
+ gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]);
+ aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+ }
+ },
+
+ cmd_debugItem: {
+ doCommand: function cmd_debugItem_doCommand(aAddon) {
+ BrowserToolboxProcess.init({ addonID: aAddon.id });
+ },
+
+ isEnabled: function cmd_debugItem_isEnabled(aAddon) {
+ let debuggerEnabled = Services.prefs.
+ getBoolPref(PREF_ADDON_DEBUGGING_ENABLED);
+ let remoteEnabled = Services.prefs.
+ getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED);
+ return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled;
+ }
+ },
+
+ cmd_showItemPreferences: {
+ isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
+ if (!aAddon ||
+ (!aAddon.isActive && !aAddon.isGMPlugin) ||
+ !aAddon.optionsURL) {
+ return false;
+ }
+ if (gViewController.currentViewObj == gDetailView &&
+ aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
+ return false;
+ }
+ if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO)
+ return false;
+ return true;
+ },
+ doCommand: function cmd_showItemPreferences_doCommand(aAddon) {
+ if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
+ gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);
+ return;
+ }
+ var optionsURL = aAddon.optionsURL;
+ if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB &&
+ openOptionsInTab(optionsURL)) {
+ return;
+ }
+ var windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ var win = windows.getNext();
+ if (win.closed) {
+ continue;
+ }
+ if (win.document.documentURI == optionsURL) {
+ win.focus();
+ return;
+ }
+ }
+ var features = "chrome,titlebar,toolbar,centerscreen";
+ try {
+ var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply");
+ features += instantApply ? ",dialog=no" : ",modal";
+ } catch (e) {
+ features += ",modal";
+ }
+ openDialog(optionsURL, "", features);
+ }
+ },
+
+ cmd_showItemAbout: {
+ isEnabled: function cmd_showItemAbout_isEnabled(aAddon) {
+ // XXXunf This may be applicable to install items too. See bug 561260
+ return !!aAddon;
+ },
+ doCommand: function cmd_showItemAbout_doCommand(aAddon) {
+ var aboutURL = aAddon.aboutURL;
+ if (aboutURL)
+ openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon);
+ else
+ openDialog("chrome://mozapps/content/extensions/about.xul",
+ "", "chrome,centerscreen,modal", aAddon);
+ }
+ },
+
+ cmd_enableItem: {
+ isEnabled: function cmd_enableItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "enable"));
+ },
+ doCommand: function cmd_enableItem_doCommand(aAddon) {
+ aAddon.userDisabled = false;
+ },
+ getTooltip: function cmd_enableItem_getTooltip(aAddon) {
+ if (!aAddon)
+ return "";
+ if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE)
+ return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip");
+ return gStrings.ext.GetStringFromName("enableAddonTooltip");
+ }
+ },
+
+ cmd_disableItem: {
+ isEnabled: function cmd_disableItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "disable"));
+ },
+ doCommand: function cmd_disableItem_doCommand(aAddon) {
+ aAddon.userDisabled = true;
+ },
+ getTooltip: function cmd_disableItem_getTooltip(aAddon) {
+ if (!aAddon)
+ return "";
+ if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)
+ return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip");
+ return gStrings.ext.GetStringFromName("disableAddonTooltip");
+ }
+ },
+
+ cmd_installItem: {
+ isEnabled: function cmd_installItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE;
+ },
+ doCommand: function cmd_installItem_doCommand(aAddon) {
+ function doInstall() {
+ gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote();
+ }
+
+ if (gViewController.currentViewObj == gDetailView)
+ gViewController.popState(doInstall);
+ else
+ doInstall();
+ }
+ },
+
+ cmd_purchaseItem: {
+ isEnabled: function cmd_purchaseItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return !!aAddon.purchaseURL;
+ },
+ doCommand: function cmd_purchaseItem_doCommand(aAddon) {
+ openURL(aAddon.purchaseURL);
+ }
+ },
+
+ cmd_uninstallItem: {
+ isEnabled: function cmd_uninstallItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return hasPermission(aAddon, "uninstall");
+ },
+ doCommand: function cmd_uninstallItem_doCommand(aAddon) {
+ if (gViewController.currentViewObj != gDetailView) {
+ aAddon.uninstall();
+ return;
+ }
+
+ gViewController.popState(function cmd_uninstallItem_popState() {
+ gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall();
+ });
+ },
+ getTooltip: function cmd_uninstallItem_getTooltip(aAddon) {
+ if (!aAddon)
+ return "";
+ if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL)
+ return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip");
+ return gStrings.ext.GetStringFromName("uninstallAddonTooltip");
+ }
+ },
+
+ cmd_cancelUninstallItem: {
+ isEnabled: function cmd_cancelUninstallItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return isPending(aAddon, "uninstall");
+ },
+ doCommand: function cmd_cancelUninstallItem_doCommand(aAddon) {
+ aAddon.cancelUninstall();
+ }
+ },
+
+ cmd_installFromFile: {
+ isEnabled: function cmd_installFromFile_isEnabled() true,
+ doCommand: function cmd_installFromFile_doCommand() {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ var fp = Cc["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ fp.init(window,
+ gStrings.ext.GetStringFromName("installFromFile.dialogTitle"),
+ nsIFilePicker.modeOpenMultiple);
+ try {
+ fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"),
+ "*.xpi;*.jar");
+ fp.appendFilters(nsIFilePicker.filterAll);
+ } catch (e) { }
+
+ if (fp.show() != nsIFilePicker.returnOK)
+ return;
+
+ var files = fp.files;
+ var installs = [];
+
+ function buildNextInstall() {
+ if (!files.hasMoreElements()) {
+ if (installs.length > 0) {
+ // Display the normal install confirmation for the installs
+ let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+ webInstaller.onWebInstallRequested(getBrowserElement(),
+ document.documentURIObject,
+ installs, installs.length);
+ }
+ return;
+ }
+
+ var file = files.getNext();
+ AddonManager.getInstallForFile(file, function cmd_installFromFile_getInstallForFile(aInstall) {
+ installs.push(aInstall);
+ buildNextInstall();
+ });
+ }
+
+ buildNextInstall();
+ }
+ },
+
+ cmd_cancelOperation: {
+ isEnabled: function cmd_cancelOperation_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return aAddon.pendingOperations != AddonManager.PENDING_NONE;
+ },
+ doCommand: function cmd_cancelOperation_doCommand(aAddon) {
+ if (isPending(aAddon, "install")) {
+ aAddon.install.cancel();
+ } else if (isPending(aAddon, "upgrade")) {
+ aAddon.pendingUpgrade.install.cancel();
+ } else if (isPending(aAddon, "uninstall")) {
+ aAddon.cancelUninstall();
+ } else if (isPending(aAddon, "enable")) {
+ aAddon.userDisabled = true;
+ } else if (isPending(aAddon, "disable")) {
+ aAddon.userDisabled = false;
+ }
+ }
+ },
+
+ cmd_contribute: {
+ isEnabled: function cmd_contribute_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return ("contributionURL" in aAddon && aAddon.contributionURL);
+ },
+ doCommand: function cmd_contribute_doCommand(aAddon) {
+ openURL(aAddon.contributionURL);
+ }
+ },
+
+ cmd_askToActivateItem: {
+ isEnabled: function cmd_askToActivateItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "ask_to_activate"));
+ },
+ doCommand: function cmd_askToActivateItem_doCommand(aAddon) {
+ aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
+ }
+ },
+
+ cmd_alwaysActivateItem: {
+ isEnabled: function cmd_alwaysActivateItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "enable"));
+ },
+ doCommand: function cmd_alwaysActivateItem_doCommand(aAddon) {
+ aAddon.userDisabled = false;
+ }
+ },
+
+ cmd_neverActivateItem: {
+ isEnabled: function cmd_neverActivateItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "disable"));
+ },
+ doCommand: function cmd_neverActivateItem_doCommand(aAddon) {
+ aAddon.userDisabled = true;
+ }
+ },
+
+ cmd_experimentsLearnMore: {
+ isEnabled: function cmd_experimentsLearnMore_isEnabled() {
+ let mainWindow = getMainWindow();
+ return mainWindow && "switchToTabHavingURI" in mainWindow;
+ },
+ doCommand: function cmd_experimentsLearnMore_doCommand() {
+ let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
+ openOptionsInTab(url);
+ },
+ },
+
+ cmd_experimentsOpenTelemetryPreferences: {
+ isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() {
+ return !!getMainWindowWithPreferencesPane();
+ },
+ doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() {
+ let mainWindow = getMainWindowWithPreferencesPane();
+ mainWindow.openAdvancedPreferences("dataChoicesTab");
+ },
+ },
+ },
+
+ supportsCommand: function gVC_supportsCommand(aCommand) {
+ return (aCommand in this.commands);
+ },
+
+ isCommandEnabled: function gVC_isCommandEnabled(aCommand) {
+ if (!this.supportsCommand(aCommand))
+ return false;
+ var addon = this.currentViewObj.getSelectedAddon();
+ return this.commands[aCommand].isEnabled(addon);
+ },
+
+ updateCommands: function gVC_updateCommands() {
+ // wait until the view is initialized
+ if (!this.currentViewObj)
+ return;
+ var addon = this.currentViewObj.getSelectedAddon();
+ for (let commandId in this.commands)
+ this.updateCommand(commandId, addon);
+ },
+
+ updateCommand: function gVC_updateCommand(aCommandId, aAddon) {
+ if (typeof aAddon == "undefined")
+ aAddon = this.currentViewObj.getSelectedAddon();
+ var cmd = this.commands[aCommandId];
+ var cmdElt = document.getElementById(aCommandId);
+ cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon));
+ if ("getTooltip" in cmd) {
+ let tooltip = cmd.getTooltip(aAddon);
+ if (tooltip)
+ cmdElt.setAttribute("tooltiptext", tooltip);
+ else
+ cmdElt.removeAttribute("tooltiptext");
+ }
+ },
+
+ doCommand: function gVC_doCommand(aCommand, aAddon) {
+ if (!this.supportsCommand(aCommand))
+ return;
+ var cmd = this.commands[aCommand];
+ if (!aAddon)
+ aAddon = this.currentViewObj.getSelectedAddon();
+ if (!cmd.isEnabled(aAddon))
+ return;
+ cmd.doCommand(aAddon);
+ },
+
+ onEvent: function gVC_onEvent() {}
+};
+
+function hasInlineOptions(aAddon) {
+ return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE ||
+ aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO);
+}
+
+function openOptionsInTab(optionsURL) {
+ let mainWindow = getMainWindow();
+ if ("switchToTabHavingURI" in mainWindow) {
+ mainWindow.switchToTabHavingURI(optionsURL, true);
+ return true;
+ }
+ return false;
+}
+
+function formatDate(aDate) {
+ return Cc["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Ci.nsIScriptableDateFormat)
+ .FormatDate("",
+ Ci.nsIScriptableDateFormat.dateFormatLong,
+ aDate.getFullYear(),
+ aDate.getMonth() + 1,
+ aDate.getDate()
+ );
+}
+
+
+function hasPermission(aAddon, aPerm) {
+ var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
+ return !!(aAddon.permissions & perm);
+}
+
+
+function isPending(aAddon, aAction) {
+ var action = AddonManager["PENDING_" + aAction.toUpperCase()];
+ return !!(aAddon.pendingOperations & action);
+}
+
+function isInState(aInstall, aState) {
+ var state = AddonManager["STATE_" + aState.toUpperCase()];
+ return aInstall.state == state;
+}
+
+function shouldShowVersionNumber(aAddon) {
+ if (!aAddon.version)
+ return false;
+
+ // The version number is hidden for lightweight themes.
+ if (aAddon.type == "theme")
+ return !/@personas\.mozilla\.org$/.test(aAddon.id);
+
+ return true;
+}
+
+function createItem(aObj, aIsInstall, aIsRemote) {
+ let item = document.createElement("richlistitem");
+
+ item.setAttribute("class", "addon addon-view");
+ item.setAttribute("name", aObj.name);
+ item.setAttribute("type", aObj.type);
+ item.setAttribute("remote", !!aIsRemote);
+
+ if (aIsInstall) {
+ item.mInstall = aObj;
+
+ if (aObj.state != AddonManager.STATE_INSTALLED) {
+ item.setAttribute("status", "installing");
+ return item;
+ }
+ aObj = aObj.addon;
+ }
+
+ item.mAddon = aObj;
+
+ item.setAttribute("status", "installed");
+
+ // set only attributes needed for sorting and XBL binding,
+ // the binding handles the rest
+ item.setAttribute("value", aObj.id);
+
+ if (aObj.type == "experiment") {
+ item.endDate = getExperimentEndDate(aObj);
+ }
+
+ return item;
+}
+
+function sortElements(aElements, aSortBy, aAscending) {
+ // aSortBy is an Array of attributes to sort by, in decending
+ // order of priority.
+
+ const DATE_FIELDS = ["updateDate"];
+ const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"];
+
+ // We're going to group add-ons into the following buckets:
+ //
+ // enabledInstalled
+ // * Enabled
+ // * Incompatible but enabled because compatibility checking is off
+ // * Waiting to be installed
+ // * Waiting to be enabled
+ //
+ // pendingDisable
+ // * Waiting to be disabled
+ //
+ // pendingUninstall
+ // * Waiting to be removed
+ //
+ // disabledIncompatibleBlocked
+ // * Disabled
+ // * Incompatible
+ // * Blocklisted
+
+ const UISTATE_ORDER = ["enabled", "askToActivate", "pendingDisable",
+ "pendingUninstall", "disabled"];
+
+ function dateCompare(a, b) {
+ var aTime = a.getTime();
+ var bTime = b.getTime();
+ if (aTime < bTime)
+ return -1;
+ if (aTime > bTime)
+ return 1;
+ return 0;
+ }
+
+ function numberCompare(a, b) {
+ return a - b;
+ }
+
+ function stringCompare(a, b) {
+ return a.localeCompare(b);
+ }
+
+ function uiStateCompare(a, b) {
+ // If we're in descending order, swap a and b, because
+ // we don't ever want to have descending uiStates
+ if (!aAscending)
+ [a, b] = [b, a];
+
+ return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b));
+ }
+
+ function getValue(aObj, aKey) {
+ if (!aObj)
+ return null;
+
+ if (aObj.hasAttribute(aKey))
+ return aObj.getAttribute(aKey);
+
+ var addon = aObj.mAddon || aObj.mInstall;
+ var addonType = aObj.mAddon && AddonManager.addonTypes[aObj.mAddon.type];
+
+ if (!addon)
+ return null;
+
+ if (aKey == "uiState") {
+ if (addon.pendingOperations == AddonManager.PENDING_DISABLE)
+ return "pendingDisable";
+ if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL)
+ return "pendingUninstall";
+ if (!addon.isActive &&
+ (addon.pendingOperations != AddonManager.PENDING_ENABLE &&
+ addon.pendingOperations != AddonManager.PENDING_INSTALL))
+ return "disabled";
+ if (addonType && (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE)
+ return "askToActivate";
+ else
+ return "enabled";
+ }
+
+ return addon[aKey];
+ }
+
+ // aSortFuncs will hold the sorting functions that we'll
+ // use per element, in the correct order.
+ var aSortFuncs = [];
+
+ for (let i = 0; i < aSortBy.length; i++) {
+ var sortBy = aSortBy[i];
+
+ aSortFuncs[i] = stringCompare;
+
+ if (sortBy == "uiState")
+ aSortFuncs[i] = uiStateCompare;
+ else if (DATE_FIELDS.indexOf(sortBy) != -1)
+ aSortFuncs[i] = dateCompare;
+ else if (NUMERIC_FIELDS.indexOf(sortBy) != -1)
+ aSortFuncs[i] = numberCompare;
+ }
+
+
+ aElements.sort(function elementsSort(a, b) {
+ if (!aAscending)
+ [a, b] = [b, a];
+
+ for (let i = 0; i < aSortFuncs.length; i++) {
+ var sortBy = aSortBy[i];
+ var aValue = getValue(a, sortBy);
+ var bValue = getValue(b, sortBy);
+
+ if (!aValue && !bValue)
+ return 0;
+ if (!aValue)
+ return -1;
+ if (!bValue)
+ return 1;
+ if (aValue != bValue) {
+ var result = aSortFuncs[i](aValue, bValue);
+
+ if (result != 0)
+ return result;
+ }
+ }
+
+ // If we got here, then all values of a and b
+ // must have been equal.
+ return 0;
+
+ });
+}
+
+function sortList(aList, aSortBy, aAscending) {
+ var elements = Array.slice(aList.childNodes, 0);
+ sortElements(elements, [aSortBy], aAscending);
+
+ while (aList.listChild)
+ aList.removeChild(aList.lastChild);
+
+ for (let element of elements)
+ aList.appendChild(element);
+}
+
+function getAddonsAndInstalls(aType, aCallback) {
+ let addons = null, installs = null;
+ let types = (aType != null) ? [aType] : null;
+
+ AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) {
+ addons = aAddonsList;
+ if (installs != null)
+ aCallback(addons, installs);
+ });
+
+ AddonManager.getInstallsByTypes(types, function getAddonsAndInstalls_getInstallsByTypes(aInstallsList) {
+ // skip over upgrade installs and non-active installs
+ installs = aInstallsList.filter(function installsFilter(aInstall) {
+ return !(aInstall.existingAddon ||
+ aInstall.state == AddonManager.STATE_AVAILABLE);
+ });
+
+ if (addons != null)
+ aCallback(addons, installs)
+ });
+}
+
+function doPendingUninstalls(aListBox) {
+ // Uninstalling add-ons can mutate the list so find the add-ons first then
+ // uninstall them
+ var items = [];
+ var listitem = aListBox.firstChild;
+ while (listitem) {
+ if (listitem.getAttribute("pending") == "uninstall" &&
+ !(listitem.opRequiresRestart("uninstall")))
+ items.push(listitem.mAddon);
+ listitem = listitem.nextSibling;
+ }
+
+ for (let addon of items)
+ addon.uninstall();
+}
+
+var gCategories = {
+ node: null,
+ _search: null,
+
+ initialize: function gCategories_initialize() {
+ this.node = document.getElementById("categories");
+ this._search = this.get("addons://search/");
+
+ var types = AddonManager.addonTypes;
+ for (var type in types)
+ this.onTypeAdded(types[type]);
+
+ AddonManager.addTypeListener(this);
+
+ try {
+ this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY);
+ } catch (e) { }
+
+ // If there was no last view or no existing category matched the last view
+ // then the list will default to selecting the search category and we never
+ // want to show that as the first view so switch to the default category
+ if (!this.node.selectedItem || this.node.selectedItem == this._search)
+ this.node.value = gViewDefault;
+
+ var self = this;
+ this.node.addEventListener("select", function node_onSelected() {
+ self.maybeHideSearch();
+ gViewController.loadView(self.node.selectedItem.value);
+ }, false);
+
+ this.node.addEventListener("click", function node_onClicked(aEvent) {
+ var selectedItem = self.node.selectedItem;
+ if (aEvent.target.localName == "richlistitem" &&
+ aEvent.target == selectedItem) {
+ var viewId = selectedItem.value;
+
+ if (gViewController.parseViewId(viewId).type == "search") {
+ viewId += encodeURIComponent(gHeader.searchQuery);
+ }
+
+ gViewController.loadView(viewId);
+ }
+ }, false);
+ },
+
+ shutdown: function gCategories_shutdown() {
+ AddonManager.removeTypeListener(this);
+ },
+
+ _insertCategory: function gCategories_insertCategory(aId, aName, aView, aPriority, aStartHidden) {
+ // If this category already exists then don't re-add it
+ if (document.getElementById("category-" + aId))
+ return;
+
+ var category = document.createElement("richlistitem");
+ category.setAttribute("id", "category-" + aId);
+ category.setAttribute("value", aView);
+ category.setAttribute("class", "category");
+ category.setAttribute("name", aName);
+ category.setAttribute("tooltiptext", aName);
+ category.setAttribute("priority", aPriority);
+ category.setAttribute("hidden", aStartHidden);
+
+ var node;
+ for (node of this.node.children) {
+ var nodePriority = parseInt(node.getAttribute("priority"));
+ // If the new type's priority is higher than this one then this is the
+ // insertion point
+ if (aPriority < nodePriority)
+ break;
+ // If the new type's priority is lower than this one then this is isn't
+ // the insertion point
+ if (aPriority > nodePriority)
+ continue;
+ // If the priorities are equal and the new type's name is earlier
+ // alphabetically then this is the insertion point
+ if (String.localeCompare(aName, node.getAttribute("name")) < 0)
+ break;
+ }
+
+ this.node.insertBefore(category, node);
+ },
+
+ _removeCategory: function gCategories_removeCategory(aId) {
+ var category = document.getElementById("category-" + aId);
+ if (!category)
+ return;
+
+ // If this category is currently selected then switch to the default view
+ if (this.node.selectedItem == category)
+ gViewController.replaceView(gViewDefault);
+
+ this.node.removeChild(category);
+ },
+
+ onTypeAdded: function gCategories_onTypeAdded(aType) {
+ // Ignore types that we don't have a view object for
+ if (!(aType.viewType in gViewController.viewObjects))
+ return;
+
+ var aViewId = "addons://" + aType.viewType + "/" + aType.id;
+
+ var startHidden = false;
+ if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) {
+ var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id);
+ try {
+ startHidden = Services.prefs.getBoolPref(prefName);
+ }
+ catch (e) {
+ // Default to hidden
+ startHidden = true;
+ }
+
+ var self = this;
+ gPendingInitializations++;
+ getAddonsAndInstalls(aType.id, function onTypeAdded_getAddonsAndInstalls(aAddonsList, aInstallsList) {
+ var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0);
+ var item = self.get(aViewId);
+
+ // Don't load view that is becoming hidden
+ if (hidden && aViewId == gViewController.currentViewId)
+ gViewController.loadView(gViewDefault);
+
+ item.hidden = hidden;
+ Services.prefs.setBoolPref(prefName, hidden);
+
+ if (aAddonsList.length > 0 || aInstallsList.length > 0) {
+ notifyInitialized();
+ return;
+ }
+
+ gEventManager.registerInstallListener({
+ onDownloadStarted: function gCategories_onDownloadStarted(aInstall) {
+ this._maybeShowCategory(aInstall);
+ },
+
+ onInstallStarted: function gCategories_onInstallStarted(aInstall) {
+ this._maybeShowCategory(aInstall);
+ },
+
+ onInstallEnded: function gCategories_onInstallEnded(aInstall, aAddon) {
+ this._maybeShowCategory(aAddon);
+ },
+
+ onExternalInstall: function gCategories_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
+ this._maybeShowCategory(aAddon);
+ },
+
+ _maybeShowCategory: function gCategories_maybeShowCategory(aAddon) {
+ if (aType.id == aAddon.type) {
+ self.get(aViewId).hidden = false;
+ Services.prefs.setBoolPref(prefName, false);
+ gEventManager.unregisterInstallListener(this);
+ }
+ }
+ });
+
+ notifyInitialized();
+ });
+ }
+
+ this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority,
+ startHidden);
+ },
+
+ onTypeRemoved: function gCategories_onTypeRemoved(aType) {
+ this._removeCategory(aType.id);
+ },
+
+ get selected() {
+ return this.node.selectedItem ? this.node.selectedItem.value : null;
+ },
+
+ select: function gCategories_select(aId, aPreviousView) {
+ var view = gViewController.parseViewId(aId);
+ if (view.type == "detail" && aPreviousView) {
+ aId = aPreviousView;
+ view = gViewController.parseViewId(aPreviousView);
+ }
+
+ Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId);
+
+ if (this.node.selectedItem &&
+ this.node.selectedItem.value == aId) {
+ this.node.selectedItem.hidden = false;
+ this.node.selectedItem.disabled = false;
+ return;
+ }
+
+ if (view.type == "search")
+ var item = this._search;
+ else
+ var item = this.get(aId);
+
+ if (item) {
+ item.hidden = false;
+ item.disabled = false;
+ this.node.suppressOnSelect = true;
+ this.node.selectedItem = item;
+ this.node.suppressOnSelect = false;
+ this.node.ensureElementIsVisible(item);
+
+ this.maybeHideSearch();
+ }
+ },
+
+ get: function gCategories_get(aId) {
+ var items = document.getElementsByAttribute("value", aId);
+ if (items.length)
+ return items[0];
+ return null;
+ },
+
+ setBadge: function gCategories_setBadge(aId, aCount) {
+ let item = this.get(aId);
+ if (item)
+ item.badgeCount = aCount;
+ },
+
+ maybeHideSearch: function gCategories_maybeHideSearch() {
+ var view = gViewController.parseViewId(this.node.selectedItem.value);
+ this._search.disabled = view.type != "search";
+ }
+};
+
+
+var gHeader = {
+ _search: null,
+ _dest: "",
+
+ initialize: function gHeader_initialize() {
+ this._search = document.getElementById("header-search");
+
+ this._search.addEventListener("command", function search_onCommand(aEvent) {
+ var query = aEvent.target.value;
+ if (query.length == 0)
+ return;
+
+ gViewController.loadView("addons://search/" + encodeURIComponent(query));
+ }, false);
+
+ function updateNavButtonVisibility() {
+ var shouldShow = gHeader.shouldShowNavButtons;
+ document.getElementById("back-btn").hidden = !shouldShow;
+ document.getElementById("forward-btn").hidden = !shouldShow;
+ }
+
+ window.addEventListener("focus", function window_onFocus(aEvent) {
+ if (aEvent.target == window)
+ updateNavButtonVisibility();
+ }, false);
+
+ updateNavButtonVisibility();
+ },
+
+ focusSearchBox: function gHeader_focusSearchBox() {
+ this._search.focus();
+ },
+
+ onKeyPress: function gHeader_onKeyPress(aEvent) {
+ if (String.fromCharCode(aEvent.charCode) == "/") {
+ this.focusSearchBox();
+ return;
+ }
+
+ // XXXunf Temporary until bug 371900 is fixed.
+ let key = document.getElementById("focusSearch").getAttribute("key");
+#ifdef XP_MACOSX
+ let keyModifier = aEvent.metaKey;
+#else
+ let keyModifier = aEvent.ctrlKey;
+#endif
+ if (String.fromCharCode(aEvent.charCode) == key && keyModifier) {
+ this.focusSearchBox();
+ return;
+ }
+ },
+
+ get shouldShowNavButtons() {
+ var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem);
+
+ // If there is no outer frame then make the buttons visible
+ if (docshellItem.rootTreeItem == docshellItem)
+ return true;
+
+ var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var outerDoc = outerWin.document;
+ var node = outerDoc.getElementById("back-button");
+ // If the outer frame has no back-button then make the buttons visible
+ if (!node)
+ return true;
+
+ // If the back-button or any of its parents are hidden then make the buttons
+ // visible
+ while (node != outerDoc) {
+ var style = outerWin.getComputedStyle(node, "");
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+ node = node.parentNode;
+ }
+
+ return false;
+ },
+
+ get searchQuery() {
+ return this._search.value;
+ },
+
+ set searchQuery(aQuery) {
+ this._search.value = aQuery;
+ },
+};
+
+
+var gDiscoverView = {
+ node: null,
+ enabled: true,
+ // Set to true after the view is first shown. If initialization completes
+ // after this then it must also load the discover homepage
+ loaded: false,
+ _browser: null,
+ _loading: null,
+ _error: null,
+ homepageURL: null,
+ _loadListeners: [],
+
+ initialize: function gDiscoverView_initialize() {
+ this.enabled = isDiscoverEnabled();
+ if (!this.enabled) {
+ gCategories.get("addons://discover/").hidden = true;
+ return;
+ }
+
+ this.node = document.getElementById("discover-view");
+ this._loading = document.getElementById("discover-loading");
+ this._error = document.getElementById("discover-error");
+ this._browser = document.getElementById("discover-browser");
+
+ let compatMode = "normal";
+ if (!AddonManager.checkCompatibility)
+ compatMode = "ignore";
+ else if (AddonManager.strictCompatibility)
+ compatMode = "strict";
+
+ var url = Services.prefs.getCharPref(PREF_DISCOVERURL);
+ url = url.replace("%COMPATIBILITY_MODE%", compatMode);
+ url = Services.urlFormatter.formatURL(url);
+
+ var self = this;
+
+ function setURL(aURL) {
+ try {
+ self.homepageURL = Services.io.newURI(aURL, null, null);
+ } catch (e) {
+ self.showError();
+ notifyInitialized();
+ return;
+ }
+
+ self._browser.homePage = self.homepageURL.spec;
+ self._browser.addProgressListener(self);
+
+ if (self.loaded)
+ self._loadURL(self.homepageURL.spec, false, notifyInitialized);
+ else
+ notifyInitialized();
+ }
+
+ if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) {
+ setURL(url);
+ return;
+ }
+
+ gPendingInitializations++;
+ AddonManager.getAllAddons(function initialize_getAllAddons(aAddons) {
+ var list = {};
+ for (let addon of aAddons) {
+ var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%",
+ addon.id);
+ try {
+ if (!Services.prefs.getBoolPref(prefName))
+ continue;
+ } catch (e) { }
+ list[addon.id] = {
+ name: addon.name,
+ version: addon.version,
+ type: addon.type,
+ userDisabled: addon.userDisabled,
+ isCompatible: addon.isCompatible,
+ isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED
+ }
+ }
+
+ setURL(url + "#" + JSON.stringify(list));
+ });
+ },
+
+ destroy: function gDiscoverView_destroy() {
+ try {
+ this._browser.removeProgressListener(this);
+ }
+ catch (e) {
+ // Ignore the case when the listener wasn't already registered
+ }
+ },
+
+ show: function gDiscoverView_show(aParam, aRequest, aState, aIsRefresh) {
+ gViewController.updateCommands();
+
+ // If we're being told to load a specific URL then just do that
+ if (aState && "url" in aState) {
+ this.loaded = true;
+ this._loadURL(aState.url);
+ }
+
+ // If the view has loaded before and still at the homepage (if refreshing),
+ // and the error page is not visible then there is nothing else to do
+ if (this.loaded && this.node.selectedPanel != this._error &&
+ (!aIsRefresh || (this._browser.currentURI &&
+ this._browser.currentURI.spec == this._browser.homePage))) {
+ gViewController.notifyViewChanged();
+ return;
+ }
+
+ this.loaded = true;
+
+ // No homepage means initialization isn't complete, the browser will get
+ // loaded once initialization is complete
+ if (!this.homepageURL) {
+ this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController));
+ return;
+ }
+
+ this._loadURL(this.homepageURL.spec, aIsRefresh,
+ gViewController.notifyViewChanged.bind(gViewController));
+ },
+
+ canRefresh: function gDiscoverView_canRefresh() {
+ if (this._browser.currentURI &&
+ this._browser.currentURI.spec == this._browser.homePage)
+ return false;
+ return true;
+ },
+
+ refresh: function gDiscoverView_refresh(aParam, aRequest, aState) {
+ this.show(aParam, aRequest, aState, true);
+ },
+
+ hide: function gDiscoverView_hide() { },
+
+ showError: function gDiscoverView_showError() {
+ this.node.selectedPanel = this._error;
+ },
+
+ _loadURL: function gDiscoverView_loadURL(aURL, aKeepHistory, aCallback) {
+ if (this._browser.currentURI.spec == aURL) {
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ if (aCallback)
+ this._loadListeners.push(aCallback);
+
+ var flags = 0;
+ if (!aKeepHistory)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
+
+ this._browser.loadURIWithFlags(aURL, flags);
+ },
+
+ onLocationChange: function gDiscoverView_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ // Ignore the about:blank load
+ if (aLocation.spec == "about:blank")
+ return;
+
+ // When using the real session history the inner-frame will update the
+ // session history automatically, if using the fake history though it must
+ // be manually updated
+ if (gHistory == FakeHistory) {
+ var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell);
+
+ var state = {
+ view: "addons://discover/",
+ url: aLocation.spec
+ };
+
+ var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16;
+ if (docshell.loadType & replaceHistory)
+ gHistory.replaceState(state);
+ else
+ gHistory.pushState(state);
+ gViewController.lastHistoryIndex = gHistory.index;
+ }
+
+ gViewController.updateCommands();
+
+ // If the hostname is the same as the new location's host and either the
+ // default scheme is insecure or the new location is secure then continue
+ // with the load
+ if (aLocation.host == this.homepageURL.host &&
+ (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https")))
+ return;
+
+ // Canceling the request will send an error to onStateChange which will show
+ // the error page
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ },
+
+ onSecurityChange: function gDiscoverView_onSecurityChange(aWebProgress, aRequest, aState) {
+ // Don't care about security if the page is not https
+ if (!this.homepageURL.schemeIs("https"))
+ return;
+
+ // If the request was secure then it is ok
+ if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE)
+ return;
+
+ // Canceling the request will send an error to onStateChange which will show
+ // the error page
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ },
+
+ onStateChange: function gDiscoverView_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_IS_REQUEST |
+ Ci.nsIWebProgressListener.STATE_TRANSFERRING;
+ // Once transferring begins show the content
+ if (aStateFlags & transferStart)
+ this.node.selectedPanel = this._browser;
+
+ // Only care about the network events
+ if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)))
+ return;
+
+ // If this is the start of network activity then show the loading page
+ if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START))
+ this.node.selectedPanel = this._loading;
+
+ // Ignore anything except stop events
+ if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
+ return;
+
+ // Consider the successful load of about:blank as still loading
+ if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank")
+ return;
+
+ // If there was an error loading the page or the new hostname is not the
+ // same as the default hostname or the default scheme is secure and the new
+ // scheme is insecure then show the error page
+ const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021;
+ if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) ||
+ (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) {
+ this.showError();
+ } else {
+ // Got a successful load, make sure the browser is visible
+ this.node.selectedPanel = this._browser;
+ gViewController.updateCommands();
+ }
+
+ var listeners = this._loadListeners;
+ this._loadListeners = [];
+
+ for (let listener of listeners)
+ listener();
+ },
+
+ onProgressChange: function gDiscoverView_onProgressChange() { },
+ onStatusChange: function gDiscoverView_onStatusChange() { },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ getSelectedAddon: function gDiscoverView_getSelectedAddon() null
+};
+
+
+var gCachedAddons = {};
+
+var gSearchView = {
+ node: null,
+ _filter: null,
+ _sorters: null,
+ _loading: null,
+ _listBox: null,
+ _emptyNotice: null,
+ _allResultsLink: null,
+ _lastQuery: null,
+ _lastRemoteTotal: 0,
+ _pendingSearches: 0,
+
+ initialize: function gSearchView_initialize() {
+ this.node = document.getElementById("search-view");
+ this._filter = document.getElementById("search-filter-radiogroup");
+ this._sorters = document.getElementById("search-sorters");
+ this._sorters.handler = this;
+ this._loading = document.getElementById("search-loading");
+ this._listBox = document.getElementById("search-list");
+ this._emptyNotice = document.getElementById("search-list-empty");
+ this._allResultsLink = document.getElementById("search-allresults-link");
+
+ if (!AddonManager.isInstallEnabled("application/x-xpinstall"))
+ this._filter.hidden = true;
+
+ var self = this;
+ this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
+ if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+ var item = self._listBox.selectedItem;
+ if (item)
+ item.showInDetailView();
+ }
+ }, false);
+
+ this._filter.addEventListener("command", function filter_onCommand() self.updateView(), false);
+ },
+
+ shutdown: function gSearchView_shutdown() {
+ if (AddonRepository.isSearching)
+ AddonRepository.cancelSearch();
+ },
+
+ get isSearching() {
+ return this._pendingSearches > 0;
+ },
+
+ show: function gSearchView_show(aQuery, aRequest) {
+ gEventManager.registerInstallListener(this);
+
+ this.showEmptyNotice(false);
+ this.showAllResultsLink(0);
+ this.showLoading(true);
+ this._sorters.showprice = false;
+
+ gHeader.searchQuery = aQuery;
+ aQuery = aQuery.trim().toLocaleLowerCase();
+ if (this._lastQuery == aQuery) {
+ this.updateView();
+ gViewController.notifyViewChanged();
+ return;
+ }
+ this._lastQuery = aQuery;
+
+ if (AddonRepository.isSearching)
+ AddonRepository.cancelSearch();
+
+ while (this._listBox.firstChild.localName == "richlistitem")
+ this._listBox.removeChild(this._listBox.firstChild);
+
+ var self = this;
+ gCachedAddons = {};
+ this._pendingSearches = 2;
+ this._sorters.setSort("relevancescore", false);
+
+ var elements = [];
+
+ function createSearchResults(aObjsList, aIsInstall, aIsRemote) {
+ for (let index in aObjsList) {
+ let obj = aObjsList[index];
+ let score = aObjsList.length - index;
+ if (!aIsRemote && aQuery.length > 0) {
+ score = self.getMatchScore(obj, aQuery);
+ if (score == 0)
+ continue;
+ }
+
+ let item = createItem(obj, aIsInstall, aIsRemote);
+ item.setAttribute("relevancescore", score);
+ if (aIsRemote) {
+ gCachedAddons[obj.id] = obj;
+ if (obj.purchaseURL)
+ self._sorters.showprice = true;
+ }
+
+ elements.push(item);
+ }
+ }
+
+ function finishSearch(createdCount) {
+ if (elements.length > 0) {
+ sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
+ for (let element of elements)
+ self._listBox.insertBefore(element, self._listBox.lastChild);
+ self.updateListAttributes();
+ }
+
+ self._pendingSearches--;
+ self.updateView();
+
+ if (!self.isSearching)
+ gViewController.notifyViewChanged();
+ }
+
+ getAddonsAndInstalls(null, function show_getAddonsAndInstalls(aAddons, aInstalls) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ createSearchResults(aAddons, false, false);
+ createSearchResults(aInstalls, true, false);
+ finishSearch();
+ });
+
+ var maxRemoteResults = 0;
+ try {
+ maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS);
+ } catch(e) {}
+
+ if (maxRemoteResults <= 0) {
+ finishSearch(0);
+ return;
+ }
+
+ AddonRepository.searchAddons(aQuery, maxRemoteResults, {
+ searchFailed: function show_SearchFailed() {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ self._lastRemoteTotal = 0;
+
+ // XXXunf Better handling of AMO search failure. See bug 579502
+ finishSearch(0); // Silently fail
+ },
+
+ searchSucceeded: function show_SearchSucceeded(aAddonsList, aAddonCount, aTotalResults) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ if (aTotalResults > maxRemoteResults)
+ self._lastRemoteTotal = aTotalResults;
+ else
+ self._lastRemoteTotal = 0;
+
+ var createdCount = createSearchResults(aAddonsList, false, true);
+ finishSearch(createdCount);
+ }
+ });
+ },
+
+ showLoading: function gSearchView_showLoading(aLoading) {
+ this._loading.hidden = !aLoading;
+ this._listBox.hidden = aLoading;
+ },
+
+ updateView: function gSearchView_updateView() {
+ var showLocal = this._filter.value == "local";
+
+ if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall"))
+ showLocal = true;
+
+ this._listBox.setAttribute("local", showLocal);
+ this._listBox.setAttribute("remote", !showLocal);
+
+ this.showLoading(this.isSearching && !showLocal);
+ if (!this.isSearching) {
+ var isEmpty = true;
+ var results = this._listBox.getElementsByTagName("richlistitem");
+ for (let result of results) {
+ var isRemote = (result.getAttribute("remote") == "true");
+ if ((isRemote && !showLocal) || (!isRemote && showLocal)) {
+ isEmpty = false;
+ break;
+ }
+ }
+
+ this.showEmptyNotice(isEmpty);
+ this.showAllResultsLink(this._lastRemoteTotal);
+ }
+
+ gViewController.updateCommands();
+ },
+
+ hide: function gSearchView_hide() {
+ gEventManager.unregisterInstallListener(this);
+ doPendingUninstalls(this._listBox);
+ },
+
+ getMatchScore: function gSearchView_getMatchScore(aObj, aQuery) {
+ var score = 0;
+ score += this.calculateMatchScore(aObj.name, aQuery,
+ SEARCH_SCORE_MULTIPLIER_NAME);
+ score += this.calculateMatchScore(aObj.description, aQuery,
+ SEARCH_SCORE_MULTIPLIER_DESCRIPTION);
+ return score;
+ },
+
+ calculateMatchScore: function gSearchView_calculateMatchScore(aStr, aQuery, aMultiplier) {
+ var score = 0;
+ if (!aStr || aQuery.length == 0)
+ return score;
+
+ aStr = aStr.trim().toLocaleLowerCase();
+ var haystack = aStr.split(/\s+/);
+ var needles = aQuery.split(/\s+/);
+
+ for (let needle of needles) {
+ for (let hay of haystack) {
+ if (hay == needle) {
+ // matching whole words is best
+ score += SEARCH_SCORE_MATCH_WHOLEWORD;
+ } else {
+ let i = hay.indexOf(needle);
+ if (i == 0) // matching on word boundries is also good
+ score += SEARCH_SCORE_MATCH_WORDBOUNDRY;
+ else if (i > 0) // substring matches not so good
+ score += SEARCH_SCORE_MATCH_SUBSTRING;
+ }
+ }
+ }
+
+ // give progressively higher score for longer queries, since longer queries
+ // are more likely to be unique and therefore more relevant.
+ if (needles.length > 1 && aStr.indexOf(aQuery) != -1)
+ score += needles.length;
+
+ return score * aMultiplier;
+ },
+
+ showEmptyNotice: function gSearchView_showEmptyNotice(aShow) {
+ this._emptyNotice.hidden = !aShow;
+ this._listBox.hidden = aShow;
+ },
+
+ showAllResultsLink: function gSearchView_showAllResultsLink(aTotalResults) {
+ if (aTotalResults == 0) {
+ this._allResultsLink.hidden = true;
+ return;
+ }
+
+ var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults");
+ linkStr = PluralForm.get(aTotalResults, linkStr);
+ linkStr = linkStr.replace("#1", aTotalResults);
+ this._allResultsLink.setAttribute("value", linkStr);
+
+ this._allResultsLink.setAttribute("href",
+ AddonRepository.getSearchURL(this._lastQuery));
+ this._allResultsLink.hidden = false;
+ },
+
+ updateListAttributes: function gSearchView_updateListAttributes() {
+ var item = this._listBox.querySelector("richlistitem[remote='true'][first]");
+ if (item)
+ item.removeAttribute("first");
+ item = this._listBox.querySelector("richlistitem[remote='true'][last]");
+ if (item)
+ item.removeAttribute("last");
+ var items = this._listBox.querySelectorAll("richlistitem[remote='true']");
+ if (items.length > 0) {
+ items[0].setAttribute("first", true);
+ items[items.length - 1].setAttribute("last", true);
+ }
+
+ item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]");
+ if (item)
+ item.removeAttribute("first");
+ item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]");
+ if (item)
+ item.removeAttribute("last");
+ items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])");
+ if (items.length > 0) {
+ items[0].setAttribute("first", true);
+ items[items.length - 1].setAttribute("last", true);
+ }
+
+ },
+
+ onSortChanged: function gSearchView_onSortChanged(aSortBy, aAscending) {
+ var footer = this._listBox.lastChild;
+ this._listBox.removeChild(footer);
+
+ sortList(this._listBox, aSortBy, aAscending);
+ this.updateListAttributes();
+
+ this._listBox.appendChild(footer);
+ },
+
+ onDownloadCancelled: function gSearchView_onDownloadCancelled(aInstall) {
+ this.removeInstall(aInstall);
+ },
+
+ onInstallCancelled: function gSearchView_onInstallCancelled(aInstall) {
+ this.removeInstall(aInstall);
+ },
+
+ removeInstall: function gSearchView_removeInstall(aInstall) {
+ for (let item of this._listBox.childNodes) {
+ if (item.mInstall == aInstall) {
+ this._listBox.removeChild(item);
+ return;
+ }
+ }
+ },
+
+ getSelectedAddon: function gSearchView_getSelectedAddon() {
+ var item = this._listBox.selectedItem;
+ if (item)
+ return item.mAddon;
+ return null;
+ },
+
+ getListItemForID: function gSearchView_getListItemForID(aId) {
+ var listitem = this._listBox.firstChild;
+ while (listitem) {
+ if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
+ return listitem;
+ listitem = listitem.nextSibling;
+ }
+ return null;
+ }
+};
+
+
+var gListView = {
+ node: null,
+ _listBox: null,
+ _emptyNotice: null,
+ _type: null,
+
+ initialize: function gListView_initialize() {
+ this.node = document.getElementById("list-view");
+ this._listBox = document.getElementById("addon-list");
+ this._emptyNotice = document.getElementById("addon-list-empty");
+
+ var self = this;
+ this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
+ if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+ var item = self._listBox.selectedItem;
+ if (item)
+ item.showInDetailView();
+ }
+ }, false);
+ },
+
+ show: function gListView_show(aType, aRequest) {
+ if (!(aType in AddonManager.addonTypes))
+ throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG);
+
+ this._type = aType;
+ this.node.setAttribute("type", aType);
+ this.showEmptyNotice(false);
+
+ while (this._listBox.itemCount > 0)
+ this._listBox.removeItemAt(0);
+
+ if (aType == "plugin") {
+ navigator.plugins.refresh(false);
+ }
+
+ var self = this;
+ getAddonsAndInstalls(aType, function show_getAddonsAndInstalls(aAddonsList, aInstallsList) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ var elements = [];
+
+ for (let addonItem of aAddonsList)
+ elements.push(createItem(addonItem));
+
+ for (let installItem of aInstallsList)
+ elements.push(createItem(installItem, true));
+
+ self.showEmptyNotice(elements.length == 0);
+ if (elements.length > 0) {
+ sortElements(elements, ["uiState", "name"], true);
+ for (let element of elements)
+ self._listBox.appendChild(element);
+ }
+
+ gEventManager.registerInstallListener(self);
+ gViewController.updateCommands();
+ gViewController.notifyViewChanged();
+ });
+ },
+
+ hide: function gListView_hide() {
+ gEventManager.unregisterInstallListener(this);
+ doPendingUninstalls(this._listBox);
+ },
+
+ showEmptyNotice: function gListView_showEmptyNotice(aShow) {
+ this._emptyNotice.hidden = !aShow;
+ },
+
+ onSortChanged: function gListView_onSortChanged(aSortBy, aAscending) {
+ sortList(this._listBox, aSortBy, aAscending);
+ },
+
+ onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
+ // The existing list item will take care of upgrade installs
+ if (aExistingAddon)
+ return;
+
+ this.addItem(aAddon);
+ },
+
+ onDownloadStarted: function gListView_onDownloadStarted(aInstall) {
+ this.addItem(aInstall, true);
+ },
+
+ onInstallStarted: function gListView_onInstallStarted(aInstall) {
+ this.addItem(aInstall, true);
+ },
+
+ onDownloadCancelled: function gListView_onDownloadCancelled(aInstall) {
+ this.removeItem(aInstall, true);
+ },
+
+ onInstallCancelled: function gListView_onInstallCancelled(aInstall) {
+ this.removeItem(aInstall, true);
+ },
+
+ onInstallEnded: function gListView_onInstallEnded(aInstall) {
+ // Remove any install entries for upgrades, their status will appear against
+ // the existing item
+ if (aInstall.existingAddon)
+ this.removeItem(aInstall, true);
+
+ if (aInstall.addon.type == "experiment") {
+ let item = this.getListItemForID(aInstall.addon.id);
+ if (item) {
+ item.endDate = getExperimentEndDate(aInstall.addon);
+ }
+ }
+ },
+
+ addItem: function gListView_addItem(aObj, aIsInstall) {
+ if (aObj.type != this._type)
+ return;
+
+ if (aIsInstall && aObj.existingAddon)
+ return;
+
+ let prop = aIsInstall ? "mInstall" : "mAddon";
+ for (let item of this._listBox.childNodes) {
+ if (item[prop] == aObj)
+ return;
+ }
+
+ let item = createItem(aObj, aIsInstall);
+ this._listBox.insertBefore(item, this._listBox.firstChild);
+ this.showEmptyNotice(false);
+ },
+
+ removeItem: function gListView_removeItem(aObj, aIsInstall) {
+ let prop = aIsInstall ? "mInstall" : "mAddon";
+
+ for (let item of this._listBox.childNodes) {
+ if (item[prop] == aObj) {
+ this._listBox.removeChild(item);
+ this.showEmptyNotice(this._listBox.itemCount == 0);
+ return;
+ }
+ }
+ },
+
+ getSelectedAddon: function gListView_getSelectedAddon() {
+ var item = this._listBox.selectedItem;
+ if (item)
+ return item.mAddon;
+ return null;
+ },
+
+ getListItemForID: function gListView_getListItemForID(aId) {
+ var listitem = this._listBox.firstChild;
+ while (listitem) {
+ if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
+ return listitem;
+ listitem = listitem.nextSibling;
+ }
+ return null;
+ }
+};
+
+
+var gDetailView = {
+ node: null,
+ _addon: null,
+ _loadingTimer: null,
+ _autoUpdate: null,
+
+ initialize: function gDetailView_initialize() {
+ this.node = document.getElementById("detail-view");
+
+ this._autoUpdate = document.getElementById("detail-autoUpdate");
+
+ var self = this;
+ this._autoUpdate.addEventListener("command", function autoUpdate_onCommand() {
+ self._addon.applyBackgroundUpdates = self._autoUpdate.value;
+ }, true);
+ },
+
+ shutdown: function gDetailView_shutdown() {
+ AddonManager.removeManagerListener(this);
+ },
+
+ onUpdateModeChanged: function gDetailView_onUpdateModeChanged() {
+ this.onPropertyChanged(["applyBackgroundUpdates"]);
+ },
+
+ _updateView: function gDetailView_updateView(aAddon, aIsRemote, aScrollToPreferences) {
+ AddonManager.addManagerListener(this);
+ this.clearLoading();
+
+ this._addon = aAddon;
+ gEventManager.registerAddonListener(this, aAddon.id);
+ gEventManager.registerInstallListener(this);
+
+ this.node.setAttribute("type", aAddon.type);
+
+ // If the search category isn't selected then make sure to select the
+ // correct category
+ if (gCategories.selected != "addons://search/")
+ gCategories.select("addons://list/" + aAddon.type);
+
+ document.getElementById("detail-name").textContent = aAddon.name;
+ var icon = aAddon.icon64URL ? aAddon.icon64URL : aAddon.iconURL;
+ document.getElementById("detail-icon").src = icon ? icon : "";
+ document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL);
+ document.getElementById("detail-translators").setTranslators(aAddon.translators, aAddon.type);
+
+ var version = document.getElementById("detail-version");
+ if (shouldShowVersionNumber(aAddon)) {
+ version.hidden = false;
+ version.value = aAddon.version;
+ } else {
+ version.hidden = true;
+ }
+
+ var screenshot = document.getElementById("detail-screenshot");
+ if (aAddon.screenshots && aAddon.screenshots.length > 0) {
+ if (aAddon.screenshots[0].thumbnailURL) {
+ screenshot.src = aAddon.screenshots[0].thumbnailURL;
+ screenshot.width = aAddon.screenshots[0].thumbnailWidth;
+ screenshot.height = aAddon.screenshots[0].thumbnailHeight;
+ } else {
+ screenshot.src = aAddon.screenshots[0].url;
+ screenshot.width = aAddon.screenshots[0].width;
+ screenshot.height = aAddon.screenshots[0].height;
+ }
+ screenshot.setAttribute("loading", "true");
+ screenshot.hidden = false;
+ } else {
+ screenshot.hidden = true;
+ }
+
+ var desc = document.getElementById("detail-desc");
+ desc.textContent = aAddon.description;
+
+ var fullDesc = document.getElementById("detail-fulldesc");
+ if (aAddon.fullDescription) {
+ // The following is part of an awful hack to include the licenses for GMP
+ // plugins without having bug 624602 fixed yet, and intentionally ignores
+ // localisation.
+ if (aAddon.isGMPlugin) {
+ fullDesc.innerHTML = aAddon.fullDescription;
+ } else {
+ fullDesc.textContent = aAddon.fullDescription;
+ }
+
+ fullDesc.hidden = false;
+ } else {
+ fullDesc.hidden = true;
+ }
+
+ var contributions = document.getElementById("detail-contributions");
+ if ("contributionURL" in aAddon && aAddon.contributionURL) {
+ contributions.hidden = false;
+ var amount = document.getElementById("detail-contrib-suggested");
+ if (aAddon.contributionAmount) {
+ amount.value = gStrings.ext.formatStringFromName("contributionAmount2",
+ [aAddon.contributionAmount],
+ 1);
+ amount.hidden = false;
+ } else {
+ amount.hidden = true;
+ }
+ } else {
+ contributions.hidden = true;
+ }
+
+ if ("purchaseURL" in aAddon && aAddon.purchaseURL) {
+ var purchase = document.getElementById("detail-purchase-btn");
+ purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label",
+ [aAddon.purchaseDisplayAmount],
+ 1);
+ purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey");
+ }
+
+ var updateDateRow = document.getElementById("detail-dateUpdated");
+ if (aAddon.updateDate) {
+ var date = formatDate(aAddon.updateDate);
+ updateDateRow.value = date;
+ } else {
+ updateDateRow.value = null;
+ }
+
+ // TODO if the add-on was downloaded from releases.mozilla.org link to the
+ // AMO profile (bug 590344)
+ if (false) {
+ document.getElementById("detail-repository-row").hidden = false;
+ document.getElementById("detail-homepage-row").hidden = true;
+ var repository = document.getElementById("detail-repository");
+ repository.value = aAddon.homepageURL;
+ repository.href = aAddon.homepageURL;
+ } else if (aAddon.homepageURL) {
+ document.getElementById("detail-repository-row").hidden = true;
+ document.getElementById("detail-homepage-row").hidden = false;
+ var homepage = document.getElementById("detail-homepage");
+ homepage.value = aAddon.homepageURL;
+ homepage.href = aAddon.homepageURL;
+ } else {
+ document.getElementById("detail-repository-row").hidden = true;
+ document.getElementById("detail-homepage-row").hidden = true;
+ }
+
+ var rating = document.getElementById("detail-rating");
+ if (aAddon.averageRating) {
+ rating.averageRating = aAddon.averageRating;
+ rating.hidden = false;
+ } else {
+ rating.hidden = true;
+ }
+
+ var reviews = document.getElementById("detail-reviews");
+ if (aAddon.reviewURL) {
+ var text = gStrings.ext.GetStringFromName("numReviews");
+ text = PluralForm.get(aAddon.reviewCount, text)
+ text = text.replace("#1", aAddon.reviewCount);
+ reviews.value = text;
+ reviews.hidden = false;
+ reviews.href = aAddon.reviewURL;
+ } else {
+ reviews.hidden = true;
+ }
+
+ document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL;
+
+ var sizeRow = document.getElementById("detail-size");
+ if (aAddon.size && aIsRemote) {
+ let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size));
+ let formatted = gStrings.dl.GetStringFromName("doneSize");
+ formatted = formatted.replace("#1", size).replace("#2", unit);
+ sizeRow.value = formatted;
+ } else {
+ sizeRow.value = null;
+ }
+
+ var downloadsRow = document.getElementById("detail-downloads");
+ if (aAddon.totalDownloads && aIsRemote) {
+ var downloads = aAddon.totalDownloads;
+ downloadsRow.value = downloads;
+ } else {
+ downloadsRow.value = null;
+ }
+
+ var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade");
+ document.getElementById("detail-updates-row").hidden = !canUpdate;
+
+ if ("applyBackgroundUpdates" in aAddon) {
+ this._autoUpdate.hidden = false;
+ this._autoUpdate.value = aAddon.applyBackgroundUpdates;
+ let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
+ document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
+ } else {
+ this._autoUpdate.hidden = true;
+ document.getElementById("detail-findUpdates-btn").hidden = false;
+ }
+
+ document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
+ !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);
+
+ var gridRows = document.querySelectorAll("#detail-grid rows row");
+ let first = true;
+ for (let gridRow of gridRows) {
+ if (first && window.getComputedStyle(gridRow, null).getPropertyValue("display") != "none") {
+ gridRow.setAttribute("first-row", true);
+ first = false;
+ } else {
+ gridRow.removeAttribute("first-row");
+ }
+ }
+
+ if (this._addon.type == "experiment") {
+ let prefix = "details.experiment.";
+ let active = this._addon.isActive;
+
+ let stateKey = prefix + "state." + (active ? "active" : "complete");
+ let node = document.getElementById("detail-experiment-state");
+ node.value = gStrings.ext.GetStringFromName(stateKey);
+
+ let now = Date.now();
+ let end = getExperimentEndDate(this._addon);
+ let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
+
+ let timeKey = prefix + "time.";
+ let timeMessage;
+ if (days < 1) {
+ timeKey += (active ? "endsToday" : "endedToday");
+ timeMessage = gStrings.ext.GetStringFromName(timeKey);
+ } else {
+ timeKey += (active ? "daysRemaining" : "daysPassed");
+ days = Math.round(days);
+ let timeString = gStrings.ext.GetStringFromName(timeKey);
+ timeMessage = PluralForm.get(days, timeString)
+ .replace("#1", days);
+ }
+
+ document.getElementById("detail-experiment-time").value = timeMessage;
+ }
+
+ this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() {
+ this.updateState();
+ gViewController.notifyViewChanged();
+ }).bind(this));
+ },
+
+ show: function gDetailView_show(aAddonId, aRequest) {
+ let index = aAddonId.indexOf("/preferences");
+ let scrollToPreferences = false;
+ if (index >= 0) {
+ aAddonId = aAddonId.substring(0, index);
+ scrollToPreferences = true;
+ }
+
+ var self = this;
+ this._loadingTimer = setTimeout(function loadTimeOutTimer() {
+ self.node.setAttribute("loading-extended", true);
+ }, LOADING_MSG_DELAY);
+
+ var view = gViewController.currentViewId;
+
+ AddonManager.getAddonByID(aAddonId, function show_getAddonByID(aAddon) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ if (aAddon) {
+ self._updateView(aAddon, false, scrollToPreferences);
+ return;
+ }
+
+ // Look for an add-on pending install
+ AddonManager.getAllInstalls(function show_getAllInstalls(aInstalls) {
+ for (let install of aInstalls) {
+ if (install.state == AddonManager.STATE_INSTALLED &&
+ install.addon.id == aAddonId) {
+ self._updateView(install.addon, false);
+ return;
+ }
+ }
+
+ if (aAddonId in gCachedAddons) {
+ self._updateView(gCachedAddons[aAddonId], true);
+ return;
+ }
+
+ // This might happen due to session restore restoring us back to an
+ // add-on that doesn't exist but otherwise shouldn't normally happen.
+ // Either way just revert to the default view.
+ gViewController.replaceView(gViewDefault);
+ });
+ });
+ },
+
+ hide: function gDetailView_hide() {
+ AddonManager.removeManagerListener(this);
+ this.clearLoading();
+ if (this._addon) {
+ if (hasInlineOptions(this._addon)) {
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
+ this._addon.id);
+ }
+
+ gEventManager.unregisterAddonListener(this, this._addon.id);
+ gEventManager.unregisterInstallListener(this);
+ this._addon = null;
+
+ // Flush the preferences to disk so they survive any crash
+ if (this.node.getElementsByTagName("setting").length)
+ Services.prefs.savePrefFile(null);
+ }
+ },
+
+ updateState: function gDetailView_updateState() {
+ gViewController.updateCommands();
+
+ var pending = this._addon.pendingOperations;
+ if (pending != AddonManager.PENDING_NONE) {
+ this.node.removeAttribute("notification");
+
+ var pending = null;
+ const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall",
+ "upgrade"];
+ for (let op of PENDING_OPERATIONS) {
+ if (isPending(this._addon, op))
+ pending = op;
+ }
+
+ this.node.setAttribute("pending", pending);
+ document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName(
+ "details.notification." + pending,
+ [this._addon.name, gStrings.brandShortName], 2
+ );
+ } else {
+ this.node.removeAttribute("pending");
+
+ if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ this.node.setAttribute("notification", "error");
+ document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.blocked",
+ [this._addon.name], 1
+ );
+ var errorLink = document.getElementById("detail-error-link");
+ errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link");
+ errorLink.href = this._addon.blocklistURL;
+ errorLink.hidden = false;
+ } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility ||
+ (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
+ this.node.setAttribute("notification", "warning");
+ document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.incompatible",
+ [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3
+ );
+ document.getElementById("detail-warning-link").hidden = true;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
+ this.node.setAttribute("notification", "warning");
+ document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.softblocked",
+ [this._addon.name], 1
+ );
+ var warningLink = document.getElementById("detail-warning-link");
+ warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link");
+ warningLink.href = this._addon.blocklistURL;
+ warningLink.hidden = false;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ this.node.setAttribute("notification", "warning");
+ document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.outdated",
+ [this._addon.name], 1
+ );
+ var warningLink = document.getElementById("detail-warning-link");
+ warningLink.value = gStrings.ext.GetStringFromName("details.notification.outdated.link");
+ warningLink.href = Services.urlFormatter.formatURLPref("plugins.update.url");
+ warningLink.hidden = false;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+ this.node.setAttribute("notification", "error");
+ document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.vulnerableUpdatable",
+ [this._addon.name], 1
+ );
+ var errorLink = document.getElementById("detail-error-link");
+ errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link");
+ errorLink.href = this._addon.blocklistURL;
+ errorLink.hidden = false;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
+ this.node.setAttribute("notification", "error");
+ document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.vulnerableNoUpdate",
+ [this._addon.name], 1
+ );
+ var errorLink = document.getElementById("detail-error-link");
+ errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
+ errorLink.href = this._addon.blocklistURL;
+ errorLink.hidden = false;
+ } else if (this._addon.isGMPlugin && !this._addon.isInstalled &&
+ this._addon.isActive) {
+ this.node.setAttribute("notification", "warning");
+ let warning = document.getElementById("detail-warning");
+ warning.textContent =
+ gStrings.ext.formatStringFromName("details.notification.gmpPending",
+ [this._addon.name], 1);
+ } else {
+ this.node.removeAttribute("notification");
+ }
+ }
+
+ let menulist = document.getElementById("detail-state-menulist");
+ let addonType = AddonManager.addonTypes[this._addon.type];
+ if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) {
+ let askItem = document.getElementById("detail-ask-to-activate-menuitem");
+ let alwaysItem = document.getElementById("detail-always-activate-menuitem");
+ let neverItem = document.getElementById("detail-never-activate-menuitem");
+ let hasActivatePermission =
+ ["ask_to_activate", "enable", "disable"].some(perm => hasPermission(this._addon, perm));
+
+ if (!this._addon.isActive) {
+ menulist.selectedItem = neverItem;
+ } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
+ menulist.selectedItem = askItem;
+ } else {
+ menulist.selectedItem = alwaysItem;
+ }
+
+ menulist.disabled = !hasActivatePermission;
+ menulist.hidden = false;
+ menulist.classList.add('no-auto-hide');
+ } else {
+ menulist.hidden = true;
+ }
+
+ this.node.setAttribute("active", this._addon.isActive);
+ },
+
+ clearLoading: function gDetailView_clearLoading() {
+ if (this._loadingTimer) {
+ clearTimeout(this._loadingTimer);
+ this._loadingTimer = null;
+ }
+
+ this.node.removeAttribute("loading-extended");
+ },
+
+ emptySettingsRows: function gDetailView_emptySettingsRows() {
+ var lastRow = document.getElementById("detail-downloads");
+ var rows = lastRow.parentNode;
+ while (lastRow.nextSibling)
+ rows.removeChild(rows.lastChild);
+ },
+
+ fillSettingsRows: function gDetailView_fillSettingsRows(aScrollToPreferences, aCallback) {
+ this.emptySettingsRows();
+ if (!hasInlineOptions(this._addon)) {
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ // 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;
+ }
+
+ var rows = document.getElementById("detail-downloads").parentNode;
+
+ try {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", this._addon.optionsURL, true);
+ xhr.responseType = "xml";
+ xhr.onload = (function fillSettingsRows_onload() {
+ var xml = xhr.responseXML;
+ var settings = xml.querySelectorAll(":root > setting");
+
+ var firstSetting = null;
+ for (var setting of settings) {
+
+ var desc = stripTextNodes(setting).trim();
+ if (!setting.hasAttribute("desc"))
+ setting.setAttribute("desc", desc);
+
+ var type = setting.getAttribute("type");
+ if (type == "file" || type == "directory")
+ setting.setAttribute("fullpath", "true");
+
+ setting = document.importNode(setting, true);
+ var style = setting.getAttribute("style");
+ if (style) {
+ setting.removeAttribute("style");
+ setting.setAttribute("style", style);
+ }
+
+ rows.appendChild(setting);
+ var visible = window.getComputedStyle(setting, null).getPropertyValue("display") != "none";
+ if (!firstSetting && visible) {
+ setting.setAttribute("first-row", true);
+ firstSetting = setting;
+ }
+ }
+
+ // Ensure the page has loaded and force the XBL bindings to be synchronously applied,
+ // then notify observers.
+ if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) {
+ gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() {
+ gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false);
+ if (firstSetting)
+ firstSetting.clientTop;
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
+ gDetailView._addon.id);
+ if (aScrollToPreferences)
+ gDetailView.scrollToPreferencesRows();
+ }, false);
+ } else {
+ if (firstSetting)
+ firstSetting.clientTop;
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
+ this._addon.id);
+ if (aScrollToPreferences)
+ gDetailView.scrollToPreferencesRows();
+ }
+ if (aCallback)
+ aCallback();
+ }).bind(this);
+ xhr.onerror = function fillSettingsRows_onerror(aEvent) {
+ Cu.reportError("Error " + aEvent.target.status +
+ " occurred while receiving " + this._addon.optionsURL);
+ if (aCallback)
+ aCallback();
+ };
+ xhr.send();
+ } catch(e) {
+ Cu.reportError(e);
+ if (aCallback)
+ aCallback();
+ }
+ },
+
+ scrollToPreferencesRows: function gDetailView_scrollToPreferencesRows() {
+ // We find this row, rather than remembering it from above,
+ // in case it has been changed by the observers.
+ let firstRow = gDetailView.node.querySelector('setting[first-row="true"]');
+ if (firstRow) {
+ let top = firstRow.boxObject.y;
+ top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top"));
+
+ let detailViewBoxObject = gDetailView.node.boxObject;
+ top -= detailViewBoxObject.y;
+
+ detailViewBoxObject.scrollTo(0, top);
+ }
+ },
+
+ getSelectedAddon: function gDetailView_getSelectedAddon() {
+ return this._addon;
+ },
+
+ onEnabling: function gDetailView_onEnabling() {
+ this.updateState();
+ },
+
+ onEnabled: function gDetailView_onEnabled() {
+ this.updateState();
+ this.fillSettingsRows();
+ },
+
+ onDisabling: function gDetailView_onDisabling(aNeedsRestart) {
+ this.updateState();
+ if (!aNeedsRestart && hasInlineOptions(this._addon)) {
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
+ this._addon.id);
+ }
+ },
+
+ onDisabled: function gDetailView_onDisabled() {
+ this.updateState();
+ this.emptySettingsRows();
+ },
+
+ onUninstalling: function gDetailView_onUninstalling() {
+ this.updateState();
+ },
+
+ onUninstalled: function gDetailView_onUninstalled() {
+ gViewController.popState();
+ },
+
+ onOperationCancelled: function gDetailView_onOperationCancelled() {
+ this.updateState();
+ },
+
+ onPropertyChanged: function gDetailView_onPropertyChanged(aProperties) {
+ if (aProperties.indexOf("applyBackgroundUpdates") != -1) {
+ this._autoUpdate.value = this._addon.applyBackgroundUpdates;
+ let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
+ document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
+ }
+
+ if (aProperties.indexOf("appDisabled") != -1 ||
+ aProperties.indexOf("userDisabled") != -1)
+ this.updateState();
+ },
+
+ onExternalInstall: function gDetailView_onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) {
+ // Only care about upgrades for the currently displayed add-on
+ if (!aExistingAddon || aExistingAddon.id != this._addon.id)
+ return;
+
+ if (!aNeedsRestart)
+ this._updateView(aAddon, false);
+ else
+ this.updateState();
+ },
+
+ onInstallCancelled: function gDetailView_onInstallCancelled(aInstall) {
+ if (aInstall.addon.id == this._addon.id)
+ gViewController.popState();
+ }
+};
+
+
+var gUpdatesView = {
+ node: null,
+ _listBox: null,
+ _emptyNotice: null,
+ _sorters: null,
+ _updateSelected: null,
+ _categoryItem: null,
+
+ initialize: function gUpdatesView_initialize() {
+ this.node = document.getElementById("updates-view");
+ this._listBox = document.getElementById("updates-list");
+ this._emptyNotice = document.getElementById("updates-list-empty");
+ this._sorters = document.getElementById("updates-sorters");
+ this._sorters.handler = this;
+
+ this._categoryItem = gCategories.get("addons://updates/available");
+
+ this._updateSelected = document.getElementById("update-selected-btn");
+ this._updateSelected.addEventListener("command", function updateSelected_onCommand() {
+ gUpdatesView.installSelected();
+ }, false);
+
+ this.updateAvailableCount(true);
+
+ AddonManager.addAddonListener(this);
+ AddonManager.addInstallListener(this);
+ },
+
+ shutdown: function gUpdatesView_shutdown() {
+ AddonManager.removeAddonListener(this);
+ AddonManager.removeInstallListener(this);
+ },
+
+ show: function gUpdatesView_show(aType, aRequest) {
+ document.getElementById("empty-availableUpdates-msg").hidden = aType != "available";
+ document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent";
+ this.showEmptyNotice(false);
+
+ while (this._listBox.itemCount > 0)
+ this._listBox.removeItemAt(0);
+
+ this.node.setAttribute("updatetype", aType);
+ if (aType == "recent")
+ this._showRecentUpdates(aRequest);
+ else
+ this._showAvailableUpdates(false, aRequest);
+ },
+
+ hide: function gUpdatesView_hide() {
+ this._updateSelected.hidden = true;
+ this._categoryItem.disabled = this._categoryItem.badgeCount == 0;
+ doPendingUninstalls(this._listBox);
+ },
+
+ _showRecentUpdates: function gUpdatesView_showRecentUpdates(aRequest) {
+ var self = this;
+ AddonManager.getAllAddons(function showRecentUpdates_getAllAddons(aAddonsList) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ var elements = [];
+ let threshold = Date.now() - UPDATES_RECENT_TIMESPAN;
+ for (let addon of aAddonsList) {
+ if (!addon.updateDate || addon.updateDate.getTime() < threshold)
+ continue;
+
+ elements.push(createItem(addon));
+ }
+
+ self.showEmptyNotice(elements.length == 0);
+ if (elements.length > 0) {
+ sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
+ for (let element of elements)
+ self._listBox.appendChild(element);
+ }
+
+ gViewController.notifyViewChanged();
+ });
+ },
+
+ _showAvailableUpdates: function gUpdatesView_showAvailableUpdates(aIsRefresh, aRequest) {
+ /* Disable the Update Selected button so it can't get clicked
+ before everything is initialized asynchronously.
+ It will get re-enabled by maybeDisableUpdateSelected(). */
+ this._updateSelected.disabled = true;
+
+ var self = this;
+ AddonManager.getAllInstalls(function showAvailableUpdates_getAllInstalls(aInstallsList) {
+ if (!aIsRefresh && gViewController && aRequest &&
+ aRequest != gViewController.currentViewRequest)
+ return;
+
+ if (aIsRefresh) {
+ self.showEmptyNotice(false);
+ self._updateSelected.hidden = true;
+
+ while (self._listBox.itemCount > 0)
+ self._listBox.removeItemAt(0);
+ }
+
+ var elements = [];
+
+ for (let install of aInstallsList) {
+ if (!self.isManualUpdate(install))
+ continue;
+
+ let item = createItem(install.existingAddon);
+ item.setAttribute("upgrade", true);
+ item.addEventListener("IncludeUpdateChanged", function item_onIncludeUpdateChanged() {
+ self.maybeDisableUpdateSelected();
+ }, false);
+ elements.push(item);
+ }
+
+ self.showEmptyNotice(elements.length == 0);
+ if (elements.length > 0) {
+ self._updateSelected.hidden = false;
+ sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
+ for (let element of elements)
+ self._listBox.appendChild(element);
+ }
+
+ // ensure badge count is in sync
+ self._categoryItem.badgeCount = self._listBox.itemCount;
+
+ gViewController.notifyViewChanged();
+ });
+ },
+
+ showEmptyNotice: function gUpdatesView_showEmptyNotice(aShow) {
+ this._emptyNotice.hidden = !aShow;
+ },
+
+ isManualUpdate: function gUpdatesView_isManualUpdate(aInstall, aOnlyAvailable) {
+ var isManual = aInstall.existingAddon &&
+ !AddonManager.shouldAutoUpdate(aInstall.existingAddon);
+ if (isManual && aOnlyAvailable)
+ return isInState(aInstall, "available");
+ return isManual;
+ },
+
+ maybeRefresh: function gUpdatesView_maybeRefresh() {
+ if (gViewController.currentViewId == "addons://updates/available")
+ this._showAvailableUpdates(true);
+ this.updateAvailableCount();
+ },
+
+ updateAvailableCount: function gUpdatesView_updateAvailableCount(aInitializing) {
+ if (aInitializing)
+ gPendingInitializations++;
+ var self = this;
+ AddonManager.getAllInstalls(function updateAvailableCount_getAllInstalls(aInstallsList) {
+ var count = aInstallsList.filter(function installListFilter(aInstall) {
+ return self.isManualUpdate(aInstall, true);
+ }).length;
+ self._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" &&
+ count == 0;
+ self._categoryItem.badgeCount = count;
+ if (aInitializing)
+ notifyInitialized();
+ });
+ },
+
+ maybeDisableUpdateSelected: function gUpdatesView_maybeDisableUpdateSelected() {
+ for (let item of this._listBox.childNodes) {
+ if (item.includeUpdate) {
+ this._updateSelected.disabled = false;
+ return;
+ }
+ }
+ this._updateSelected.disabled = true;
+ },
+
+ installSelected: function gUpdatesView_installSelected() {
+ for (let item of this._listBox.childNodes) {
+ if (item.includeUpdate)
+ item.upgrade();
+ }
+
+ this._updateSelected.disabled = true;
+ },
+
+ getSelectedAddon: function gUpdatesView_getSelectedAddon() {
+ var item = this._listBox.selectedItem;
+ if (item)
+ return item.mAddon;
+ return null;
+ },
+
+ getListItemForID: function gUpdatesView_getListItemForID(aId) {
+ var listitem = this._listBox.firstChild;
+ while (listitem) {
+ if (listitem.mAddon.id == aId)
+ return listitem;
+ listitem = listitem.nextSibling;
+ }
+ return null;
+ },
+
+ onSortChanged: function gUpdatesView_onSortChanged(aSortBy, aAscending) {
+ sortList(this._listBox, aSortBy, aAscending);
+ },
+
+ onNewInstall: function gUpdatesView_onNewInstall(aInstall) {
+ if (!this.isManualUpdate(aInstall))
+ return;
+ this.maybeRefresh();
+ },
+
+ onInstallStarted: function gUpdatesView_onInstallStarted(aInstall) {
+ this.updateAvailableCount();
+ },
+
+ onInstallCancelled: function gUpdatesView_onInstallCancelled(aInstall) {
+ if (!this.isManualUpdate(aInstall))
+ return;
+ this.maybeRefresh();
+ },
+
+ onPropertyChanged: function gUpdatesView_onPropertyChanged(aAddon, aProperties) {
+ if (aProperties.indexOf("applyBackgroundUpdates") != -1)
+ this.updateAvailableCount();
+ }
+};
+
+function debuggingPrefChanged() {
+ gViewController.updateState();
+ gViewController.updateCommands();
+ gViewController.notifyViewChanged();
+}
+
+var gDragDrop = {
+ onDragOver: function gDragDrop_onDragOver(aEvent) {
+ var types = aEvent.dataTransfer.types;
+ if (types.contains("text/uri-list") ||
+ types.contains("text/x-moz-url") ||
+ types.contains("application/x-moz-file"))
+ aEvent.preventDefault();
+ },
+
+ onDrop: function gDragDrop_onDrop(aEvent) {
+ var dataTransfer = aEvent.dataTransfer;
+ var urls = [];
+
+ // Convert every dropped item into a url
+ for (var i = 0; i < dataTransfer.mozItemCount; i++) {
+ var url = dataTransfer.mozGetDataAt("text/uri-list", i);
+ if (url) {
+ urls.push(url);
+ continue;
+ }
+
+ url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
+ if (url) {
+ urls.push(url.split("\n")[0]);
+ continue;
+ }
+
+ var file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
+ if (file) {
+ urls.push(Services.io.newFileURI(file).spec);
+ continue;
+ }
+ }
+
+ var pos = 0;
+ var installs = [];
+
+ function buildNextInstall() {
+ if (pos == urls.length) {
+ if (installs.length > 0) {
+ // Display the normal install confirmation for the installs
+ let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+ webInstaller.onWebInstallRequested(getBrowserElement(),
+ document.documentURIObject,
+ installs, installs.length);
+ }
+ return;
+ }
+
+ AddonManager.getInstallForURL(urls[pos++], function onDrop_getInstallForURL(aInstall) {
+ installs.push(aInstall);
+ buildNextInstall();
+ }, "application/x-xpinstall");
+ }
+
+ buildNextInstall();
+
+ aEvent.preventDefault();
+ }
+};
diff --git a/toolkit/mozapps/extensions/content/extensions.xml b/toolkit/mozapps/extensions/content/extensions.xml
new file mode 100644
index 000000000..9c15902b5
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -0,0 +1,2108 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+
+<!DOCTYPE page [
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+<!ENTITY % aboutDTD SYSTEM "chrome://mozapps/locale/extensions/about.dtd">
+%aboutDTD;
+]>
+
+<bindings id="addonBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+
+ <!-- Rating - displays current/average rating, allows setting user rating -->
+ <binding id="rating">
+ <content>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(1);"
+ onclick="document.getBindingParent(this).userRating = 1;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(2);"
+ onclick="document.getBindingParent(this).userRating = 2;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(3);"
+ onclick="document.getBindingParent(this).userRating = 3;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(4);"
+ onclick="document.getBindingParent(this).userRating = 4;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(5);"
+ onclick="document.getBindingParent(this).userRating = 5;"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._updateStars();
+ ]]></constructor>
+
+ <property name="stars" readonly="true">
+ <getter><![CDATA[
+ return document.getAnonymousNodes(this);
+ ]]></getter>
+ </property>
+
+ <property name="averageRating">
+ <getter><![CDATA[
+ if (this.hasAttribute("averagerating"))
+ return this.getAttribute("averagerating");
+ return -1;
+ ]]></getter>
+ <setter><![CDATA[
+ this.setAttribute("averagerating", val);
+ if (this.showRating == "average")
+ this._updateStars();
+ ]]></setter>
+ </property>
+
+ <property name="userRating">
+ <getter><![CDATA[
+ if (this.hasAttribute("userrating"))
+ return this.getAttribute("userrating");
+ return -1;
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.showRating != "user")
+ return;
+ this.setAttribute("userrating", val);
+ if (this.showRating == "user")
+ this._updateStars();
+ ]]></setter>
+ </property>
+
+ <property name="showRating">
+ <getter><![CDATA[
+ if (this.hasAttribute("showrating"))
+ return this.getAttribute("showrating");
+ return "average";
+ ]]></getter>
+ <setter><![CDATA[
+ if (val != "average" || val != "user")
+ throw Components.Exception("Invalid value", Components.results.NS_ERROR_ILLEGAL_VALUE);
+ this.setAttribute("showrating", val);
+ this._updateStars();
+ ]]></setter>
+ </property>
+
+ <method name="_updateStars">
+ <body><![CDATA[
+ var stars = this.stars;
+ var rating = this[this.showRating + "Rating"];
+ // average ratings can be non-whole numbers, round them so they
+ // match to their closest star
+ rating = Math.round(rating);
+ for (let i = 0; i < stars.length; i++)
+ stars[i].setAttribute("on", rating > i);
+ ]]></body>
+ </method>
+
+ <method name="_hover">
+ <parameter name="aScore"/>
+ <body><![CDATA[
+ if (this.showRating != "user")
+ return;
+ var stars = this.stars;
+ for (let i = 0; i < stars.length; i++)
+ stars[i].setAttribute("on", i <= (aScore -1));
+ ]]></body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="mouseout">
+ this._updateStars();
+ </handler>
+ </handlers>
+ </binding>
+
+ <!-- Download progress - shows graphical progress of download and any
+ related status message. -->
+ <binding id="download-progress">
+ <content>
+ <xul:stack flex="1">
+ <xul:hbox flex="1">
+ <xul:hbox class="start-cap"/>
+ <xul:progressmeter anonid="progress" class="progress" flex="1"
+ min="0" max="100"/>
+ <xul:hbox class="end-cap"/>
+ </xul:hbox>
+ <xul:hbox class="status-container">
+ <xul:spacer flex="1"/>
+ <xul:label anonid="status" class="status"/>
+ <xul:spacer flex="1"/>
+ <xul:button anonid="cancel-btn" class="cancel"
+ tooltiptext="&progress.cancel.tooltip;"
+ oncommand="document.getBindingParent(this).cancel();"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ var progress = 0;
+ if (this.hasAttribute("progress"))
+ progress = parseInt(this.getAttribute("progress"));
+ this.progress = progress;
+ ]]></constructor>
+
+ <field name="_progress">
+ document.getAnonymousElementByAttribute(this, "anonid", "progress");
+ </field>
+ <field name="_cancel">
+ document.getAnonymousElementByAttribute(this, "anonid", "cancel-btn");
+ </field>
+ <field name="_status">
+ document.getAnonymousElementByAttribute(this, "anonid", "status");
+ </field>
+
+ <property name="progress">
+ <getter><![CDATA[
+ return this._progress.value;
+ ]]></getter>
+ <setter><![CDATA[
+ this._progress.value = val;
+ if (val == this._progress.max)
+ this.setAttribute("complete", true);
+ else
+ this.removeAttribute("complete");
+ ]]></setter>
+ </property>
+
+ <property name="maxProgress">
+ <getter><![CDATA[
+ return this._progress.max;
+ ]]></getter>
+ <setter><![CDATA[
+ if (val == -1) {
+ this._progress.mode = "undetermined";
+ } else {
+ this._progress.mode = "determined";
+ this._progress.max = val;
+ }
+ this.setAttribute("mode", this._progress.mode);
+ ]]></setter>
+ </property>
+
+ <property name="status">
+ <getter><![CDATA[
+ return this._status.value;
+ ]]></getter>
+ <setter><![CDATA[
+ this._status.value = val;
+ ]]></setter>
+ </property>
+
+ <method name="cancel">
+ <body><![CDATA[
+ this.mInstall.cancel();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Sorters - displays and controls the sort state of a list. -->
+ <binding id="sorters">
+ <content orient="horizontal">
+ <xul:button anonid="name-btn" class="sorter"
+ label="&sort.name.label;" tooltiptext="&sort.name.tooltip;"
+ oncommand="this.parentNode._handleChange('name');"/>
+ <xul:button anonid="date-btn" class="sorter"
+ label="&sort.dateUpdated.label;"
+ tooltiptext="&sort.dateUpdated.tooltip;"
+ oncommand="this.parentNode._handleChange('updateDate');"/>
+ <xul:button anonid="price-btn" class="sorter" hidden="true"
+ label="&sort.price.label;"
+ tooltiptext="&sort.price.tooltip;"
+ oncommand="this.parentNode._handleChange('purchaseAmount');"/>
+ <xul:button anonid="relevance-btn" class="sorter" hidden="true"
+ label="&sort.relevance.label;"
+ tooltiptext="&sort.relevance.tooltip;"
+ oncommand="this.parentNode._handleChange('relevancescore');"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (!this.hasAttribute("sortby"))
+ this.setAttribute("sortby", "name");
+
+ if (this.getAttribute("showrelevance") == "true")
+ this._btnRelevance.hidden = false;
+
+ if (this.getAttribute("showprice") == "true")
+ this._btnPrice.hidden = false;
+
+ this._refreshState();
+ ]]></constructor>
+
+ <field name="handler">null</field>
+ <field name="_btnName">
+ document.getAnonymousElementByAttribute(this, "anonid", "name-btn");
+ </field>
+ <field name="_btnDate">
+ document.getAnonymousElementByAttribute(this, "anonid", "date-btn");
+ </field>
+ <field name="_btnPrice">
+ document.getAnonymousElementByAttribute(this, "anonid", "price-btn");
+ </field>
+ <field name="_btnRelevance">
+ document.getAnonymousElementByAttribute(this, "anonid", "relevance-btn");
+ </field>
+
+ <property name="sortBy">
+ <getter><![CDATA[
+ return this.getAttribute("sortby");
+ ]]></getter>
+ <setter><![CDATA[
+ if (val != this.sortBy) {
+ this.setAttribute("sortBy", val);
+ this._refreshState();
+ }
+ ]]></setter>
+ </property>
+
+ <property name="ascending">
+ <getter><![CDATA[
+ return (this.getAttribute("ascending") == "true");
+ ]]></getter>
+ <setter><![CDATA[
+ val = !!val;
+ if (val != this.ascending) {
+ this.setAttribute("ascending", val);
+ this._refreshState();
+ }
+ ]]></setter>
+ </property>
+
+ <property name="showrelevance">
+ <getter><![CDATA[
+ return (this.getAttribute("showrelevance") == "true");
+ ]]></getter>
+ <setter><![CDATA[
+ val = !!val;
+ this.setAttribute("showrelevance", val);
+ this._btnRelevance.hidden = !val;
+ ]]></setter>
+ </property>
+
+ <property name="showprice">
+ <getter><![CDATA[
+ return (this.getAttribute("showprice") == "true");
+ ]]></getter>
+ <setter><![CDATA[
+ val = !!val;
+ this.setAttribute("showprice", val);
+ this._btnPrice.hidden = !val;
+ ]]></setter>
+ </property>
+
+ <method name="setSort">
+ <parameter name="aSort"/>
+ <parameter name="aAscending"/>
+ <body><![CDATA[
+ var sortChanged = false;
+ if (aSort != this.sortBy) {
+ this.setAttribute("sortby", aSort);
+ sortChanged = true;
+ }
+
+ aAscending = !!aAscending;
+ if (this.ascending != aAscending) {
+ this.setAttribute("ascending", aAscending);
+ sortChanged = true;
+ }
+
+ if (sortChanged)
+ this._refreshState();
+ ]]></body>
+ </method>
+
+ <method name="_handleChange">
+ <parameter name="aSort"/>
+ <body><![CDATA[
+ const ASCENDING_SORT_FIELDS = ["name", "purchaseAmount"];
+
+ // Toggle ascending if sort by is not changing, otherwise
+ // name sorting defaults to ascending, others to descending
+ if (aSort == this.sortBy)
+ this.ascending = !this.ascending;
+ else
+ this.setSort(aSort, ASCENDING_SORT_FIELDS.indexOf(aSort) >= 0);
+ ]]></body>
+ </method>
+
+ <method name="_refreshState">
+ <body><![CDATA[
+ var sortBy = this.sortBy;
+ var checkState = this.ascending ? 2 : 1;
+
+ if (sortBy == "name") {
+ this._btnName.checkState = checkState;
+ this._btnName.checked = true;
+ } else {
+ this._btnName.checkState = 0;
+ this._btnName.checked = false;
+ }
+
+ if (sortBy == "updateDate") {
+ this._btnDate.checkState = checkState;
+ this._btnDate.checked = true;
+ } else {
+ this._btnDate.checkState = 0;
+ this._btnDate.checked = false;
+ }
+
+ if (sortBy == "purchaseAmount") {
+ this._btnPrice.checkState = checkState;
+ this._btnPrice.checked = true;
+ } else {
+ this._btnPrice.checkState = 0;
+ this._btnPrice.checked = false;
+ }
+
+ if (sortBy == "relevancescore") {
+ this._btnRelevance.checkState = checkState;
+ this._btnRelevance.checked = true;
+ } else {
+ this._btnRelevance.checkState = 0;
+ this._btnRelevance.checked = false;
+ }
+
+ if (this.handler && "onSortChanged" in this.handler)
+ this.handler.onSortChanged(sortBy, this.ascending);
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Categories list - displays the list of categories on the left pane. -->
+ <binding id="categories-list"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
+ <implementation>
+ <!-- This needs to be overridden to allow the fancy animation while not
+ allowing that item to be selected when hiding. -->
+ <method name="_canUserSelect">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (aItem.hasAttribute("disabled") &&
+ aItem.getAttribute("disabled") == "true")
+ return false;
+ var style = document.defaultView.getComputedStyle(aItem, "");
+ return style.display != "none" && style.visibility == "visible";
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Category item - an item in the category list. -->
+ <binding id="category"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content align="center">
+ <xul:image anonid="icon" class="category-icon"/>
+ <xul:label anonid="name" class="category-name" flex="1" xbl:inherits="value=name"/>
+ <xul:label anonid="badge" class="category-badge" xbl:inherits="value=count"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (!this.hasAttribute("count"))
+ this.setAttribute("count", 0);
+ ]]></constructor>
+
+ <property name="badgeCount">
+ <getter><![CDATA[
+ return this.getAttribute("count");
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.getAttribute("count") == val)
+ return;
+
+ this.setAttribute("count", val);
+ var event = document.createEvent("Events");
+ event.initEvent("CategoryBadgeUpdated", true, true);
+ this.dispatchEvent(event);
+ ]]></setter>
+ </property>
+ </implementation>
+ </binding>
+
+
+ <!-- Creator link - Name of a user/developer, providing a link if relevant. -->
+ <binding id="creator-link">
+ <content>
+ <xul:label anonid="label" value="&addon.createdBy.label;"/>
+ <xul:label anonid="creator-link" class="creator-link text-link"/>
+ <xul:label anonid="creator-name" class="creator-name"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (this.hasAttribute("nameonly") &&
+ this.getAttribute("nameonly") == "true") {
+ this._label.hidden = true;
+ }
+ ]]></constructor>
+
+ <field name="_label">
+ document.getAnonymousElementByAttribute(this, "anonid", "label");
+ </field>
+ <field name="_creatorLink">
+ document.getAnonymousElementByAttribute(this, "anonid", "creator-link");
+ </field>
+ <field name="_creatorName">
+ document.getAnonymousElementByAttribute(this, "anonid", "creator-name");
+ </field>
+
+ <method name="setCreator">
+ <parameter name="aCreator"/>
+ <parameter name="aHomepageURL"/>
+ <body><![CDATA[
+ if (!aCreator) {
+ this.collapsed = true;
+ return;
+ }
+ this.collapsed = false;
+ var url = aCreator.url || aHomepageURL;
+ var showLink = !!url;
+ if (showLink) {
+ this._creatorLink.value = aCreator.name;
+ this._creatorLink.href = url;
+ } else {
+ this._creatorName.value = aCreator.name;
+ }
+ this._creatorLink.hidden = !showLink;
+ this._creatorName.hidden = showLink;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Translators list - Names of a translators of Language Pack. -->
+ <binding id="translators-list">
+ <content>
+ <xul:label anonid="label" value="&translators.label;" class="sectionTitle"/>
+ <xul:vbox flex="1" anonid="translatorsBox" class="boxIndent"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (this.hasAttribute("nameonly") &&
+ this.getAttribute("nameonly") == "true") {
+ this._label.hidden = true;
+ }
+ ]]></constructor>
+
+ <field name="_label">
+ document.getAnonymousElementByAttribute(this, "anonid", "label");
+ </field>
+ <field name="_translatorsBox">
+ document.getAnonymousElementByAttribute(this, "anonid", "translatorsBox");
+ </field>
+
+ <method name="setTranslators">
+ <parameter name="aTranslators"/>
+ <parameter name="aType"/>
+ <body><![CDATA[
+ if (aType != "locale" || !aTranslators || aTranslators.length == 0) {
+ this.collapsed = true;
+ return;
+ }
+ this.collapsed = false;
+ while (this._translatorsBox.firstChild) {
+ this._translatorsBox.removeChild(this._translatorsBox.firstChild);
+ }
+ for (let currentItem of aTranslators) {
+ var label = document.createElement("label");
+ label.textContent = currentItem;
+ label.setAttribute("class", "contributor");
+ this._translatorsBox.appendChild(label);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Install status - Displays the status of an install/upgrade. -->
+ <binding id="install-status">
+ <content>
+ <xul:label anonid="message"/>
+ <xul:progressmeter anonid="progress" class="download-progress"/>
+ <xul:button anonid="purchase-remote-btn" hidden="true"
+ class="addon-control"
+ oncommand="document.getBindingParent(this).purchaseRemote();"/>
+ <xul:button anonid="install-remote-btn" hidden="true"
+ class="addon-control install" label="&addon.install.label;"
+ tooltiptext="&addon.install.tooltip;"
+ oncommand="document.getBindingParent(this).installRemote();"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (this.mInstall)
+ this.initWithInstall(this.mInstall);
+ else if (this.mControl.mAddon.install)
+ this.initWithInstall(this.mControl.mAddon.install);
+ else
+ this.refreshState();
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ if (this.mInstall)
+ this.mInstall.removeListener(this);
+ ]]></destructor>
+
+ <field name="_message">
+ document.getAnonymousElementByAttribute(this, "anonid", "message");
+ </field>
+ <field name="_progress">
+ document.getAnonymousElementByAttribute(this, "anonid", "progress");
+ </field>
+ <field name="_purchaseRemote">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "purchase-remote-btn");
+ </field>
+ <field name="_installRemote">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "install-remote-btn");
+ </field>
+ <field name="_restartNeeded">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "restart-needed");
+ </field>
+ <field name="_undo">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "undo-btn");
+ </field>
+
+ <method name="initWithInstall">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ if (this.mInstall) {
+ this.mInstall.removeListener(this);
+ this.mInstall = null;
+ }
+ this.mInstall = aInstall;
+ this._progress.mInstall = aInstall;
+ this.refreshState();
+ this.mInstall.addListener(this);
+ ]]></body>
+ </method>
+
+ <method name="refreshState">
+ <body><![CDATA[
+ var showInstallRemote = false;
+ var showPurchase = false;
+
+ if (this.mInstall) {
+
+ switch (this.mInstall.state) {
+ case AddonManager.STATE_AVAILABLE:
+ if (this.mControl.getAttribute("remote") != "true")
+ break;
+
+ this._progress.hidden = true;
+ showInstallRemote = true;
+ break;
+ case AddonManager.STATE_DOWNLOADING:
+ this.showMessage("installDownloading");
+ break;
+ case AddonManager.STATE_CHECKING:
+ this.showMessage("installVerifying");
+ break;
+ case AddonManager.STATE_DOWNLOADED:
+ this.showMessage("installDownloaded");
+ break;
+ case AddonManager.STATE_DOWNLOAD_FAILED:
+ // XXXunf expose what error occured (bug 553487)
+ this.showMessage("installDownloadFailed", true);
+ break;
+ case AddonManager.STATE_INSTALLING:
+ this.showMessage("installInstalling");
+ break;
+ case AddonManager.STATE_INSTALL_FAILED:
+ // XXXunf expose what error occured (bug 553487)
+ this.showMessage("installFailed", true);
+ break;
+ case AddonManager.STATE_CANCELLED:
+ this.showMessage("installCancelled", true);
+ break;
+ }
+
+ } else if (this.mControl.mAddon.purchaseURL) {
+ this._progress.hidden = true;
+ showPurchase = true;
+ this._purchaseRemote.label =
+ gStrings.ext.formatStringFromName("addon.purchase.label",
+ [this.mControl.mAddon.purchaseDisplayAmount], 1);
+ this._purchaseRemote.tooltiptext =
+ gStrings.ext.GetStringFromName("addon.purchase.tooltip");
+ }
+
+ this._purchaseRemote.hidden = !showPurchase;
+ this._installRemote.hidden = !showInstallRemote;
+
+ if ("refreshInfo" in this.mControl)
+ this.mControl.refreshInfo();
+ ]]></body>
+ </method>
+
+ <method name="showMessage">
+ <parameter name="aMsgId"/>
+ <parameter name="aHideProgress"/>
+ <body><![CDATA[
+ this._message.setAttribute("hidden", !aHideProgress);
+ this._progress.setAttribute("hidden", !!aHideProgress);
+
+ var msg = gStrings.ext.GetStringFromName(aMsgId);
+ if (aHideProgress)
+ this._message.value = msg;
+ else
+ this._progress.status = msg;
+ ]]></body>
+ </method>
+
+ <method name="purchaseRemote">
+ <body><![CDATA[
+ openURL(this.mControl.mAddon.purchaseURL);
+ ]]></body>
+ </method>
+
+ <method name="installRemote">
+ <body><![CDATA[
+ if (this.mControl.getAttribute("remote") != "true")
+ return;
+
+ if (this.mControl.mAddon.eula) {
+ var data = {
+ addon: this.mControl.mAddon,
+ accepted: false
+ };
+ window.openDialog("chrome://mozapps/content/extensions/eula.xul", "_blank",
+ "chrome,dialog,modal,centerscreen,resizable=no", data);
+ if (!data.accepted)
+ return;
+ }
+
+ delete this.mControl.mAddon;
+ this.mControl.mInstall = this.mInstall;
+ this.mControl.setAttribute("status", "installing");
+ this.mInstall.install();
+ ]]></body>
+ </method>
+
+ <method name="undoAction">
+ <body><![CDATA[
+ if (!this.mAddon)
+ return;
+ var pending = this.mAddon.pendingOperations;
+ if (pending & AddonManager.PENDING_ENABLE)
+ this.mAddon.userDisabled = true;
+ else if (pending & AddonManager.PENDING_DISABLE)
+ this.mAddon.userDisabled = false;
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadStarted">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadEnded">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadProgress">
+ <body><![CDATA[
+ this._progress.maxProgress = this.mInstall.maxProgress;
+ this._progress.progress = this.mInstall.progress;
+ ]]></body>
+ </method>
+
+ <method name="onInstallStarted">
+ <body><![CDATA[
+ this._progress.progress = 0;
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onInstallEnded">
+ <body><![CDATA[
+ this.refreshState();
+ if ("onInstallCompleted" in this.mControl)
+ this.mControl.onInstallCompleted();
+ ]]></body>
+ </method>
+
+ <method name="onInstallFailed">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Addon - base - parent binding of any item representing an addon. -->
+ <binding id="addon-base"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <implementation>
+ <method name="hasPermission">
+ <parameter name="aPerm"/>
+ <body><![CDATA[
+ var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
+ return !!(this.mAddon.permissions & perm);
+ ]]></body>
+ </method>
+
+ <method name="opRequiresRestart">
+ <parameter name="aOperation"/>
+ <body><![CDATA[
+ var operation = AddonManager["OP_NEEDS_RESTART_" + aOperation.toUpperCase()];
+ return !!(this.mAddon.operationsRequiringRestart & operation);
+ ]]></body>
+ </method>
+
+ <method name="typeHasFlag">
+ <parameter name="aFlag"/>
+ <body><![CDATA[
+ let flag = AddonManager["TYPE_" + aFlag];
+ let type = AddonManager.addonTypes[this.mAddon.type];
+
+ return !!(type.flags & flag);
+ ]]></body>
+ </method>
+
+ <method name="isPending">
+ <parameter name="aAction"/>
+ <body><![CDATA[
+ var action = AddonManager["PENDING_" + aAction.toUpperCase()];
+ return !!(this.mAddon.pendingOperations & action);
+ ]]></body>
+ </method>
+
+ <method name="onUninstalled">
+ <body><![CDATA[
+ this.parentNode.removeChild(this);
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Addon - generic - A normal addon item, or an update to one -->
+ <binding id="addon-generic"
+ extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+ <content>
+ <xul:hbox anonid="warning-container"
+ class="warning">
+ <xul:image class="warning-icon"/>
+ <xul:label anonid="warning" flex="1"/>
+ <xul:label anonid="warning-link" class="text-link"/>
+ <xul:button anonid="warning-btn" class="button-link"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ <xul:hbox anonid="error-container"
+ class="error">
+ <xul:image class="error-icon"/>
+ <xul:label anonid="error" flex="1"/>
+ <xul:label anonid="error-link" class="text-link"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ <xul:hbox anonid="pending-container"
+ class="pending">
+ <xul:image class="pending-icon"/>
+ <xul:label anonid="pending" flex="1"/>
+ <xul:button anonid="restart-btn" class="button-link"
+ label="&addon.restartNow.label;"
+ oncommand="document.getBindingParent(this).restart();"/>
+ <xul:button anonid="undo-btn" class="button-link"
+ label="&addon.undoAction.label;"
+ tooltipText="&addon.undoAction.tooltip;"
+ oncommand="document.getBindingParent(this).undo();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+
+ <xul:hbox class="content-container">
+ <xul:vbox class="icon-container">
+ <xul:image anonid="icon" class="icon"/>
+ </xul:vbox>
+ <xul:vbox class="content-inner-container" flex="1">
+ <xul:hbox class="basicinfo-container">
+ <xul:hbox class="name-container">
+ <xul:label anonid="name" class="name" crop="end" flex="1"
+ xbl:inherits="value=name,tooltiptext=name"/>
+ <xul:label anonid="version" class="version"/>
+ <xul:label class="nativeIndicator nativeAddon" value="●" tooltiptext="&addon.nativeAddon;"/>
+ <xul:label class="nativeIndicator compatAddon" value="●" tooltiptext="&addon.compatAddon;"/>
+ <xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/>
+ <xul:label class="update-postfix" value="&addon.update.postfix;"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to make the name crop -->
+ </xul:hbox>
+ <xul:label anonid="date-updated" class="date-updated"
+ unknown="&addon.unknownDate;"/>
+ </xul:hbox>
+ <xul:hbox class="experiment-container">
+ <svg width="6" height="6" viewBox="0 0 6 6" version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ class="experiment-bullet-container">
+ <circle cx="3" cy="3" r="3" class="experiment-bullet"/>
+ </svg>
+ <xul:label anonid="experiment-state" class="experiment-state"/>
+ <xul:label anonid="experiment-time" class="experiment-time"/>
+ </xul:hbox>
+
+ <xul:hbox class="advancedinfo-container" flex="1">
+ <xul:vbox class="description-outer-container" flex="1">
+ <xul:hbox class="description-container">
+ <xul:label anonid="description" class="description" crop="end" flex="1"/>
+ <xul:button anonid="details-btn" class="details button-link"
+ label="&addon.details.label;"
+ tooltiptext="&addon.details.tooltip;"
+ oncommand="document.getBindingParent(this).showInDetailView();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to make the description crop -->
+ </xul:hbox>
+ <xul:vbox anonid="relnotes-container" class="relnotes-container">
+ <xul:label class="relnotes-header" value="&addon.releaseNotes.label;"/>
+ <xul:label anonid="relnotes-loading" value="&addon.loadingReleaseNotes.label;"/>
+ <xul:label anonid="relnotes-error" hidden="true"
+ value="&addon.errorLoadingReleaseNotes.label;"/>
+ <xul:vbox anonid="relnotes" class="relnotes"/>
+ </xul:vbox>
+ <xul:hbox class="relnotes-toggle-container">
+ <xul:button anonid="relnotes-toggle-btn" class="relnotes-toggle"
+ hidden="true" label="&cmd.showReleaseNotes.label;"
+ tooltiptext="&cmd.showReleaseNotes.tooltip;"
+ showlabel="&cmd.showReleaseNotes.label;"
+ showtooltip="&cmd.showReleaseNotes.tooltip;"
+ hidelabel="&cmd.hideReleaseNotes.label;"
+ hidetooltip="&cmd.hideReleaseNotes.tooltip;"
+ oncommand="document.getBindingParent(this).toggleReleaseNotes();"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox class="status-control-wrapper">
+ <xul:hbox class="status-container">
+ <xul:hbox anonid="checking-update" hidden="true">
+ <xul:image class="spinner"/>
+ <xul:label value="&addon.checkingForUpdates.label;"/>
+ </xul:hbox>
+ <xul:vbox anonid="update-available" class="update-available"
+ hidden="true">
+ <xul:checkbox anonid="include-update" class="include-update"
+ label="&addon.includeUpdate.label;" checked="true"
+ oncommand="document.getBindingParent(this).onIncludeUpdateChanged();"/>
+ <xul:hbox class="update-info-container">
+ <xul:label class="update-available-notice"
+ value="&addon.updateAvailable.label;"/>
+ <xul:button anonid="update-btn" class="addon-control update"
+ label="&addon.updateNow.label;"
+ tooltiptext="&addon.updateNow.tooltip;"
+ oncommand="document.getBindingParent(this).upgrade();"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:hbox anonid="install-status" class="install-status"
+ hidden="true"/>
+ </xul:hbox>
+ <xul:hbox anonid="control-container" class="control-container">
+ <xul:button anonid="preferences-btn"
+ class="addon-control preferences"
+#ifdef XP_WIN
+ label="&cmd.showPreferencesWin.label;"
+ tooltiptext="&cmd.showPreferencesWin.tooltip;"
+#else
+ label="&cmd.showPreferencesUnix.label;"
+ tooltiptext="&cmd.showPreferencesUnix.tooltip;"
+#endif
+ oncommand="document.getBindingParent(this).showPreferences();"/>
+ <!-- label="&cmd.debugAddon.label;" -->
+ <xul:button anonid="debug-btn" class="addon-control debug"
+ label="&cmd.debugAddon.label;"
+ oncommand="document.getBindingParent(this).debug();"/>
+
+ <xul:button anonid="enable-btn" class="addon-control enable"
+ label="&cmd.enableAddon.label;"
+ oncommand="document.getBindingParent(this).userDisabled = false;"/>
+ <xul:button anonid="disable-btn" class="addon-control disable"
+ label="&cmd.disableAddon.label;"
+ oncommand="document.getBindingParent(this).userDisabled = true;"/>
+ <xul:button anonid="remove-btn" class="addon-control remove"
+ label="&cmd.uninstallAddon.label;"
+ oncommand="document.getBindingParent(this).uninstall();"/>
+ <xul:menulist anonid="state-menulist"
+ class="addon-control state"
+ tooltiptext="&cmd.stateMenu.tooltip;">
+ <xul:menupopup>
+ <xul:menuitem anonid="ask-to-activate-menuitem"
+ class="addon-control"
+ label="&cmd.askToActivate.label;"
+ tooltiptext="&cmd.askToActivate.tooltip;"
+ oncommand="document.getBindingParent(this).userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;"/>
+ <xul:menuitem anonid="always-activate-menuitem"
+ class="addon-control"
+ label="&cmd.alwaysActivate.label;"
+ tooltiptext="&cmd.alwaysActivate.tooltip;"
+ oncommand="document.getBindingParent(this).userDisabled = false;"/>
+ <xul:menuitem anonid="never-activate-menuitem"
+ class="addon-control"
+ label="&cmd.neverActivate.label;"
+ tooltiptext="&cmd.neverActivate.tooltip;"
+ oncommand="document.getBindingParent(this).userDisabled = true;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._installStatus.mControl = this;
+
+ this.setAttribute("contextmenu", "addonitem-popup");
+
+ this._showStatus("none");
+
+ this._initWithAddon(this.mAddon);
+
+ gEventManager.registerAddonListener(this, this.mAddon.id);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ gEventManager.unregisterAddonListener(this, this.mAddon.id);
+ ]]></destructor>
+
+ <field name="_warningContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning-container");
+ </field>
+ <field name="_warning">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning");
+ </field>
+ <field name="_warningLink">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning-link");
+ </field>
+ <field name="_warningBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning-btn");
+ </field>
+ <field name="_errorContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "error-container");
+ </field>
+ <field name="_error">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "error");
+ </field>
+ <field name="_errorLink">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "error-link");
+ </field>
+ <field name="_pendingContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "pending-container");
+ </field>
+ <field name="_pending">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "pending");
+ </field>
+ <field name="_infoContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "info-container");
+ </field>
+ <field name="_info">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "info");
+ </field>
+ <field name="_version">
+ document.getAnonymousElementByAttribute(this, "anonid", "version");
+ </field>
+ <field name="_experimentState">
+ document.getAnonymousElementByAttribute(this, "anonid", "experiment-state");
+ </field>
+ <field name="_experimentTime">
+ document.getAnonymousElementByAttribute(this, "anonid", "experiment-time");
+ </field>
+ <field name="_icon">
+ document.getAnonymousElementByAttribute(this, "anonid", "icon");
+ </field>
+ <field name="_dateUpdated">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "date-updated");
+ </field>
+ <field name="_description">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "description");
+ </field>
+ <field name="_stateMenulist">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "state-menulist");
+ </field>
+ <field name="_askToActivateMenuitem">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "ask-to-activate-menuitem");
+ </field>
+ <field name="_alwaysActivateMenuitem">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "always-activate-menuitem");
+ </field>
+ <field name="_neverActivateMenuitem">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "never-activate-menuitem");
+ </field>
+ <field name="_preferencesBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "preferences-btn");
+ </field>
+ <field name="_enableBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "enable-btn");
+ </field>
+ <field name="_debugBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "debug-btn");
+ </field>
+ <field name="_disableBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "disable-btn");
+ </field>
+ <field name="_removeBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "remove-btn");
+ </field>
+ <field name="_updateBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "update-btn");
+ </field>
+ <field name="_controlContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "control-container");
+ </field>
+ <field name="_installStatus">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "install-status");
+ </field>
+ <field name="_checkingUpdate">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "checking-update");
+ </field>
+ <field name="_updateAvailable">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "update-available");
+ </field>
+ <field name="_includeUpdate">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "include-update");
+ </field>
+ <field name="_relNotesLoaded">false</field>
+ <field name="_relNotesToggle">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-toggle-btn");
+ </field>
+ <field name="_relNotesLoading">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-loading");
+ </field>
+ <field name="_relNotesError">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-error");
+ </field>
+ <field name="_relNotesContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-container");
+ </field>
+ <field name="_relNotes">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes");
+ </field>
+
+ <property name="userDisabled">
+ <getter><![CDATA[
+ return this.mAddon.userDisabled;
+ ]]></getter>
+ <setter><![CDATA[
+ this.mAddon.userDisabled = val;
+ ]]></setter>
+ </property>
+
+ <property name="includeUpdate">
+ <getter><![CDATA[
+ return this._includeUpdate.checked && !!this.mManualUpdate;
+ ]]></getter>
+ <setter><![CDATA[
+ //XXXunf Eventually, we'll want to persist this for individual
+ // updates - see bug 594619.
+ this._includeUpdate.checked = !!val;
+ ]]></setter>
+ </property>
+
+ <method name="_initWithAddon">
+ <parameter name="aAddon"/>
+ <body><![CDATA[
+ this.mAddon = aAddon;
+
+ this._installStatus.mAddon = this.mAddon;
+ this._updateDates();
+ this._updateState();
+
+ this.setAttribute("name", aAddon.name);
+
+ var iconURL = this.mAddon.iconURL;
+ if (iconURL)
+ this._icon.src = iconURL;
+ else
+ this._icon.src = "";
+
+ if (shouldShowVersionNumber(this.mAddon))
+ this._version.value = this.mAddon.version;
+ else
+ this._version.hidden = true;
+
+ if (this.mAddon.description)
+ this._description.value = this.mAddon.description;
+ else
+ this._description.hidden = true;
+
+ if (!("applyBackgroundUpdates" in this.mAddon) ||
+ (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
+ (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+ !AddonManager.autoUpdateDefault))) {
+ var self = this;
+ AddonManager.getAllInstalls(function(aInstallsList) {
+ // This can return after the binding has been destroyed,
+ // so try to detect that and return early
+ if (!("onNewInstall" in self))
+ return;
+ for (let install of aInstallsList) {
+ if (install.existingAddon &&
+ install.existingAddon.id == self.mAddon.id &&
+ install.state == AddonManager.STATE_AVAILABLE) {
+ self.onNewInstall(install);
+ self.onIncludeUpdateChanged();
+ }
+ }
+ });
+ }
+ ]]></body>
+ </method>
+
+ <method name="_showStatus">
+ <parameter name="aType"/>
+ <body><![CDATA[
+ this._controlContainer.hidden = aType != "none" &&
+ !(aType == "update-available" && !this.hasAttribute("upgrade"));
+
+ this._installStatus.hidden = aType != "progress";
+ if (aType == "progress")
+ this._installStatus.refreshState();
+ this._checkingUpdate.hidden = aType != "checking-update";
+ this._updateAvailable.hidden = aType != "update-available";
+ this._relNotesToggle.hidden = !(this.mManualUpdate ?
+ this.mManualUpdate.releaseNotesURI :
+ this.mAddon.releaseNotesURI);
+ ]]></body>
+ </method>
+
+ <method name="_updateDates">
+ <body><![CDATA[
+ function formatDate(aDate) {
+ return Cc["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Ci.nsIScriptableDateFormat)
+ .FormatDate("",
+ Ci.nsIScriptableDateFormat.dateFormatLong,
+ aDate.getFullYear(),
+ aDate.getMonth() + 1,
+ aDate.getDate()
+ );
+ }
+
+ if (this.mAddon.updateDate)
+ this._dateUpdated.value = formatDate(this.mAddon.updateDate);
+ else
+ this._dateUpdated.value = this._dateUpdated.getAttribute("unknown");
+ ]]></body>
+ </method>
+
+ <method name="_updateState">
+ <body><![CDATA[
+ if (this.parentNode.selectedItem == this)
+ gViewController.updateCommands();
+
+ var pending = this.mAddon.pendingOperations;
+ if (pending != AddonManager.PENDING_NONE) {
+ this.removeAttribute("notification");
+
+ var pending = null;
+ const PENDING_OPERATIONS = ["enable", "disable", "install",
+ "uninstall", "upgrade"];
+ for (let op of PENDING_OPERATIONS) {
+ if (this.isPending(op))
+ pending = op;
+ }
+
+ this.setAttribute("pending", pending);
+ this._pending.textContent = gStrings.ext.formatStringFromName(
+ "notification." + pending,
+ [this.mAddon.name, gStrings.brandShortName], 2
+ );
+ } else {
+ this.removeAttribute("pending");
+
+ var isUpgrade = this.hasAttribute("upgrade");
+ var install = this._installStatus.mInstall;
+
+ if (install && install.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.downloadError",
+ [this.mAddon.name], 1
+ );
+ this._warningBtn.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
+ this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
+ this._warningBtn.hidden = false;
+ this._warningLink.hidden = true;
+ } else if (install && install.state == AddonManager.STATE_INSTALL_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.installError",
+ [this.mAddon.name], 1
+ );
+ this._warningBtn.label = gStrings.ext.GetStringFromName("notification.installError.retry");
+ this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
+ this._warningBtn.hidden = false;
+ this._warningLink.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ this.setAttribute("notification", "error");
+ this._error.textContent = gStrings.ext.formatStringFromName(
+ "notification.blocked",
+ [this.mAddon.name], 1
+ );
+ this._errorLink.value = gStrings.ext.GetStringFromName("notification.blocked.link");
+ this._errorLink.href = this.mAddon.blocklistURL;
+ this._errorLink.hidden = false;
+ } else if (this.mAddon.jetsdk) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.jetsdk",
+ [gStrings.brandShortName, gStrings.appVersion], 2
+ );
+ this._warningLink.hidden = true;
+ this._warningBtn.hidden = true;
+ } else if ((!isUpgrade && !this.mAddon.isCompatible) && (AddonManager.checkCompatibility
+ || (this.mAddon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.incompatible",
+ [this.mAddon.name, gStrings.brandShortName, gStrings.appVersion], 3
+ );
+ this._warningLink.hidden = true;
+ this._warningBtn.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.softblocked",
+ [this.mAddon.name], 1
+ );
+ this._warningLink.value = gStrings.ext.GetStringFromName("notification.softblocked.link");
+ this._warningLink.href = this.mAddon.blocklistURL;
+ this._warningLink.hidden = false;
+ this._warningBtn.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.outdated",
+ [this.mAddon.name], 1
+ );
+ this._warningLink.value = gStrings.ext.GetStringFromName("notification.outdated.link");
+ this._warningLink.href = Services.urlFormatter.formatURLPref("plugins.update.url");
+ this._warningLink.hidden = false;
+ this._warningBtn.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+ this.setAttribute("notification", "error");
+ this._error.textContent = gStrings.ext.formatStringFromName(
+ "notification.vulnerableUpdatable",
+ [this.mAddon.name], 1
+ );
+ this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableUpdatable.link");
+ this._errorLink.href = this.mAddon.blocklistURL;
+ this._errorLink.hidden = false;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
+ this.setAttribute("notification", "error");
+ this._error.textContent = gStrings.ext.formatStringFromName(
+ "notification.vulnerableNoUpdate",
+ [this.mAddon.name], 1
+ );
+ this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
+ this._errorLink.href = this.mAddon.blocklistURL;
+ this._errorLink.hidden = false;
+ } else if (this.mAddon.isGMPlugin && !this.mAddon.isInstalled &&
+ this.mAddon.isActive) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent =
+ gStrings.ext.formatStringFromName("notification.gmpPending",
+ [this.mAddon.name], 1);
+ } else {
+ this.removeAttribute("notification");
+ if (this.mAddon.type == "extension")
+ this.setAttribute("native", this.mAddon.native);
+ }
+ }
+
+ this._preferencesBtn.hidden = (!this.mAddon.optionsURL) ||
+ this.mAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO;
+
+ if (this.typeHasFlag("SUPPORTS_ASK_TO_ACTIVATE")) {
+ this._enableBtn.disabled = true;
+ this._disableBtn.disabled = true;
+ this._askToActivateMenuitem.disabled = !this.hasPermission("ask_to_activate");
+ this._alwaysActivateMenuitem.disabled = !this.hasPermission("enable");
+ this._neverActivateMenuitem.disabled = !this.hasPermission("disable");
+ if (!this.mAddon.isActive) {
+ this._stateMenulist.selectedItem = this._neverActivateMenuitem;
+ } else if (this.mAddon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
+ this._stateMenulist.selectedItem = this._askToActivateMenuitem;
+ } else {
+ this._stateMenulist.selectedItem = this._alwaysActivateMenuitem;
+ }
+ let hasActivatePermission =
+ ["ask_to_activate", "enable", "disable"].some(perm => this.hasPermission(perm));
+ this._stateMenulist.disabled = !hasActivatePermission;
+ this._stateMenulist.hidden = false;
+ this._stateMenulist.classList.add('no-auto-hide');
+ } else {
+ this._stateMenulist.hidden = true;
+ if (this.hasPermission("enable")) {
+ this._enableBtn.hidden = false;
+ let tooltip = gViewController.commands["cmd_enableItem"]
+ .getTooltip(this.mAddon);
+ this._enableBtn.setAttribute("tooltiptext", tooltip);
+ } else {
+ this._enableBtn.hidden = true;
+ }
+
+ if (this.hasPermission("disable")) {
+ this._disableBtn.hidden = false;
+ let tooltip = gViewController.commands["cmd_disableItem"]
+ .getTooltip(this.mAddon);
+ this._disableBtn.setAttribute("tooltiptext", tooltip);
+ } else {
+ this._disableBtn.hidden = true;
+ }
+ }
+
+ if (this.hasPermission("uninstall")) {
+ this._removeBtn.hidden = false;
+ let tooltip = gViewController.commands["cmd_uninstallItem"]
+ .getTooltip(this.mAddon);
+ this._removeBtn.setAttribute("tooltiptext", tooltip);
+ } else {
+ this._removeBtn.hidden = true;
+ }
+
+ this.setAttribute("active", this.mAddon.isActive);
+
+ var showProgress = this.mAddon.purchaseURL || (this.mAddon.install &&
+ this.mAddon.install.state != AddonManager.STATE_INSTALLED);
+ this._showStatus(showProgress ? "progress" : "none");
+
+ let debuggable = this.mAddon.isDebuggable &&
+ Services.prefs.getBoolPref('devtools.chrome.enabled') &&
+ Services.prefs.getBoolPref('devtools.debugger.remote-enabled');
+
+ this._debugBtn.disabled = this._debugBtn.hidden = !debuggable
+
+ if (this.mAddon.type == "experiment") {
+ this.removeAttribute("notification");
+ let prefix = "experiment.";
+ let active = this.mAddon.isActive;
+
+ if (!showProgress) {
+ let stateKey = prefix + "state." + (active ? "active" : "complete");
+ this._experimentState.value = gStrings.ext.GetStringFromName(stateKey);
+
+ let now = Date.now();
+ let end = this.endDate;
+ let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
+
+ let timeKey = prefix + "time.";
+ let timeMessage;
+
+ if (days < 1) {
+ timeKey += (active ? "endsToday" : "endedToday");
+ timeMessage = gStrings.ext.GetStringFromName(timeKey);
+ } else {
+ timeKey += (active ? "daysRemaining" : "daysPassed");
+ days = Math.round(days);
+ let timeString = gStrings.ext.GetStringFromName(timeKey);
+ timeMessage = PluralForm.get(days, timeString)
+ .replace("#1", days);
+ }
+
+ this._experimentTime.value = timeMessage;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_updateUpgradeInfo">
+ <body><![CDATA[
+ // Only update the version string if we're displaying the upgrade info
+ if (this.hasAttribute("upgrade") && shouldShowVersionNumber(this.mAddon))
+ this._version.value = this.mManualUpdate.version;
+ ]]></body>
+ </method>
+
+ <method name="_fetchReleaseNotes">
+ <parameter name="aURI"/>
+ <body><![CDATA[
+ var self = this;
+ if (!aURI || this._relNotesLoaded) {
+ sendToggleEvent();
+ return;
+ }
+
+ var relNotesData = null, transformData = null;
+
+ this._relNotesLoaded = true;
+ this._relNotesLoading.hidden = false;
+ this._relNotesError.hidden = true;
+
+ function sendToggleEvent() {
+ var event = document.createEvent("Events");
+ event.initEvent("RelNotesToggle", true, true);
+ self.dispatchEvent(event);
+ }
+
+ function showRelNotes() {
+ if (!relNotesData || !transformData)
+ return;
+
+ self._relNotesLoading.hidden = true;
+
+ var processor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"]
+ .createInstance(Components.interfaces.nsIXSLTProcessor);
+ processor.flags |= Components.interfaces.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
+
+ processor.importStylesheet(transformData);
+ var fragment = processor.transformToFragment(relNotesData, document);
+ self._relNotes.appendChild(fragment);
+ if (self.hasAttribute("show-relnotes")) {
+ var container = self._relNotesContainer;
+ container.style.height = container.scrollHeight + "px";
+ }
+ sendToggleEvent();
+ }
+
+ function handleError() {
+ dataReq.abort();
+ styleReq.abort();
+ self._relNotesLoading.hidden = true;
+ self._relNotesError.hidden = false;
+ self._relNotesLoaded = false; // allow loading to be re-tried
+ sendToggleEvent();
+ }
+
+ function handleResponse(aEvent) {
+ var req = aEvent.target;
+ var ct = req.getResponseHeader("content-type");
+ if ((!ct || ct.indexOf("text/html") < 0) &&
+ req.responseXML &&
+ req.responseXML.documentElement.namespaceURI != XMLURI_PARSE_ERROR) {
+ if (req == dataReq)
+ relNotesData = req.responseXML;
+ else
+ transformData = req.responseXML;
+ showRelNotes();
+ } else {
+ handleError();
+ }
+ }
+
+ var dataReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ dataReq.open("GET", aURI.spec, true);
+ dataReq.addEventListener("load", handleResponse, false);
+ dataReq.addEventListener("error", handleError, false);
+ dataReq.send(null);
+
+ var styleReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ styleReq.open("GET", UPDATES_RELEASENOTES_TRANSFORMFILE, true);
+ styleReq.addEventListener("load", handleResponse, false);
+ styleReq.addEventListener("error", handleError, false);
+ styleReq.send(null);
+ ]]></body>
+ </method>
+
+ <method name="toggleReleaseNotes">
+ <body><![CDATA[
+ if (this.hasAttribute("show-relnotes")) {
+ this._relNotesContainer.style.height = "0px";
+ this.removeAttribute("show-relnotes");
+ this._relNotesToggle.setAttribute(
+ "label",
+ this._relNotesToggle.getAttribute("showlabel")
+ );
+ this._relNotesToggle.setAttribute(
+ "tooltiptext",
+ this._relNotesToggle.getAttribute("showtooltip")
+ );
+ var event = document.createEvent("Events");
+ event.initEvent("RelNotesToggle", true, true);
+ this.dispatchEvent(event);
+ } else {
+ this._relNotesContainer.style.height = this._relNotesContainer.scrollHeight +
+ "px";
+ this.setAttribute("show-relnotes", true);
+ this._relNotesToggle.setAttribute(
+ "label",
+ this._relNotesToggle.getAttribute("hidelabel")
+ );
+ this._relNotesToggle.setAttribute(
+ "tooltiptext",
+ this._relNotesToggle.getAttribute("hidetooltip")
+ );
+ var uri = this.mManualUpdate ?
+ this.mManualUpdate.releaseNotesURI :
+ this.mAddon.releaseNotesURI;
+ this._fetchReleaseNotes(uri);
+ }
+ ]]></body>
+ </method>
+
+ <method name="restart">
+ <body><![CDATA[
+ gViewController.commands["cmd_restartApp"].doCommand();
+ ]]></body>
+ </method>
+
+ <method name="undo">
+ <body><![CDATA[
+ gViewController.commands["cmd_cancelOperation"].doCommand(this.mAddon);
+ ]]></body>
+ </method>
+
+ <method name="uninstall">
+ <body><![CDATA[
+ // If uninstalling does not require a restart and the type doesn't
+ // support undoing of restartless uninstalls, then we fake it by
+ // just disabling it it, and doing the real uninstall later.
+ if (!this.opRequiresRestart("uninstall") &&
+ !this.typeHasFlag("SUPPORTS_UNDO_RESTARTLESS_UNINSTALL")) {
+ this.setAttribute("wasDisabled", this.mAddon.userDisabled);
+
+ // We must set userDisabled to true first, this will call
+ // _updateState which will clear any pending attribute set.
+ this.mAddon.userDisabled = true;
+
+ // This won't update any other add-on manager views (bug 582002)
+ this.setAttribute("pending", "uninstall");
+ } else {
+ this.mAddon.uninstall(true);
+ }
+ ]]></body>
+ </method>
+
+ <method name="debug">
+ <body><![CDATA[
+ gViewController.doCommand("cmd_debugItem", this.mAddon);
+ ]]></body>
+ </method>
+
+ <method name="showPreferences">
+ <body><![CDATA[
+ gViewController.doCommand("cmd_showItemPreferences", this.mAddon);
+ ]]></body>
+ </method>
+
+ <method name="upgrade">
+ <body><![CDATA[
+ var install = this.mManualUpdate;
+ delete this.mManualUpdate;
+ install.install();
+ ]]></body>
+ </method>
+
+ <method name="retryInstall">
+ <body><![CDATA[
+ var install = this._installStatus.mInstall;
+ if (!install)
+ return;
+ if (install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
+ install.state != AddonManager.STATE_INSTALL_FAILED)
+ return;
+ install.install();
+ ]]></body>
+ </method>
+
+ <method name="showInDetailView">
+ <body><![CDATA[
+ gViewController.loadView("addons://detail/" +
+ encodeURIComponent(this.mAddon.id));
+ ]]></body>
+ </method>
+
+ <method name="onIncludeUpdateChanged">
+ <body><![CDATA[
+ var event = document.createEvent("Events");
+ event.initEvent("IncludeUpdateChanged", true, true);
+ this.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="onEnabling">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onEnabled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onDisabling">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onDisabled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onUninstalling">
+ <parameter name="aRestartRequired"/>
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onOperationCancelled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onPropertyChanged">
+ <parameter name="aProperties"/>
+ <body><![CDATA[
+ if (aProperties.indexOf("appDisabled") != -1 ||
+ aProperties.indexOf("userDisabled") != -1)
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onNoUpdateAvailable">
+ <body><![CDATA[
+ this._showStatus("none");
+ ]]></body>
+ </method>
+
+ <method name="onCheckingUpdate">
+ <body><![CDATA[
+ this._showStatus("checking-update");
+ ]]></body>
+ </method>
+
+ <method name="onCompatibilityUpdateAvailable">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onExternalInstall">
+ <parameter name="aAddon"/>
+ <parameter name="aExistingAddon"/>
+ <parameter name="aNeedsRestart"/>
+ <body><![CDATA[
+ if (aExistingAddon.id != this.mAddon.id)
+ return;
+
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!aNeedsRestart)
+ this._initWithAddon(aAddon);
+ else
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onNewInstall">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+ return;
+ if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+ AddonManager.autoUpdateDefault)
+ return;
+
+ this.mManualUpdate = aInstall;
+ this._showStatus("update-available");
+ this._updateUpgradeInfo();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadStarted">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ this._updateState();
+ this._showStatus("progress");
+ this._installStatus.initWithInstall(aInstall);
+ ]]></body>
+ </method>
+
+ <method name="onInstallStarted">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ this._updateState();
+ this._showStatus("progress");
+ this._installStatus.initWithInstall(aInstall);
+ ]]></body>
+ </method>
+
+ <method name="onInstallEnded">
+ <parameter name="aInstall"/>
+ <parameter name="aAddon"/>
+ <body><![CDATA[
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
+ this._initWithAddon(aAddon);
+ else
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onInstallFailed">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onInstallCancelled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="click" button="0"><![CDATA[
+ switch (event.detail) {
+ case 1:
+ // Prevent double-click where the UI changes on the first click
+ this._lastClickTarget = event.originalTarget;
+ break;
+ case 2:
+ if (event.originalTarget.localName != 'button' &&
+ !event.originalTarget.classList.contains('text-link') &&
+ event.originalTarget == this._lastClickTarget) {
+ this.showInDetailView();
+ }
+ break;
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+
+ <!-- Addon - uninstalled - An uninstalled addon that can be re-installed. -->
+ <binding id="addon-uninstalled"
+ extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+ <content>
+ <xul:hbox class="pending">
+ <xul:image class="pending-icon"/>
+ <xul:label anonid="notice" flex="1"/>
+ <xul:button anonid="restart-btn" class="button-link"
+ label="&addon.restartNow.label;"
+ command="cmd_restartApp"/>
+ <xul:button anonid="undo-btn" class="button-link"
+ label="&addon.undoRemove.label;"
+ tooltiptext="&addon.undoRemove.tooltip;"
+ oncommand="document.getBindingParent(this).cancelUninstall();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._notice.textContent = gStrings.ext.formatStringFromName("uninstallNotice",
+ [this.mAddon.name],
+ 1);
+
+ if (!this.opRequiresRestart("uninstall"))
+ this._restartBtn.setAttribute("hidden", true);
+
+ gEventManager.registerAddonListener(this, this.mAddon.id);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ gEventManager.unregisterAddonListener(this, this.mAddon.id);
+ ]]></destructor>
+
+ <field name="_notice" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "notice");
+ </field>
+ <field name="_restartBtn" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "restart-btn");
+ </field>
+
+ <method name="cancelUninstall">
+ <body><![CDATA[
+ // This assumes that disabling does not require a restart when
+ // uninstalling doesn't. Things will still work if not, the add-on
+ // will just still be active until finally getting uninstalled.
+
+ if (this.isPending("uninstall"))
+ this.mAddon.cancelUninstall();
+ else if (this.getAttribute("wasDisabled") != "true")
+ this.mAddon.userDisabled = false;
+
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+
+ <method name="onOperationCancelled">
+ <body><![CDATA[
+ if (!this.isPending("uninstall"))
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+
+ <method name="onExternalInstall">
+ <parameter name="aAddon"/>
+ <parameter name="aExistingAddon"/>
+ <parameter name="aNeedsRestart"/>
+ <body><![CDATA[
+ if (aExistingAddon.id != this.mAddon.id)
+ return;
+
+ // Make sure any newly installed add-on has the correct disabled state
+ if (this.hasAttribute("wasDisabled"))
+ aAddon.userDisabled = this.getAttribute("wasDisabled") == "true";
+
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!aNeedsRestart)
+ this.mAddon = aAddon;
+
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+
+ <method name="onInstallStarted">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ // Make sure any newly installed add-on has the correct disabled state
+ if (this.hasAttribute("wasDisabled"))
+ aInstall.addon.userDisabled = this.getAttribute("wasDisabled") == "true";
+ ]]></body>
+ </method>
+
+ <method name="onInstallEnded">
+ <parameter name="aInstall"/>
+ <parameter name="aAddon"/>
+ <body><![CDATA[
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
+ this.mAddon = aAddon;
+
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Addon - installing - an addon item that is currently being installed -->
+ <binding id="addon-installing"
+ extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+ <content>
+ <xul:hbox anonid="warning-container" class="warning">
+ <xul:image class="warning-icon"/>
+ <xul:label anonid="warning" flex="1"/>
+ <xul:button anonid="warning-link" class="button-link"
+ oncommand="document.getBindingParent(this).retryInstall();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ <xul:hbox class="content-container">
+ <xul:vbox class="icon-outer-container">
+ <xul:vbox class="icon-container">
+ <xul:image anonid="icon" class="icon"/>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:vbox class="fade name-outer-container" flex="1">
+ <xul:hbox class="name-container">
+ <xul:label anonid="name" class="name" crop="end"/>
+ <xul:label anonid="version" class="version" hidden="true"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox class="install-status-container">
+ <xul:hbox anonid="install-status" class="install-status"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._installStatus.mControl = this;
+ this._installStatus.mInstall = this.mInstall;
+ this.refreshInfo();
+ ]]></constructor>
+
+ <field name="_icon">
+ document.getAnonymousElementByAttribute(this, "anonid", "icon");
+ </field>
+ <field name="_name">
+ document.getAnonymousElementByAttribute(this, "anonid", "name");
+ </field>
+ <field name="_version">
+ document.getAnonymousElementByAttribute(this, "anonid", "version");
+ </field>
+ <field name="_warning">
+ document.getAnonymousElementByAttribute(this, "anonid", "warning");
+ </field>
+ <field name="_warningLink">
+ document.getAnonymousElementByAttribute(this, "anonid", "warning-link");
+ </field>
+ <field name="_installStatus">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "install-status");
+ </field>
+
+ <method name="onInstallCompleted">
+ <body><![CDATA[
+ this.mAddon = this.mInstall.addon;
+ this.setAttribute("name", this.mAddon.name);
+ this.setAttribute("value", this.mAddon.id);
+ this.setAttribute("status", "installed");
+ ]]></body>
+ </method>
+
+ <method name="refreshInfo">
+ <body><![CDATA[
+ this.mAddon = this.mAddon || this.mInstall.addon;
+ if (this.mAddon) {
+ this._icon.src = this.mAddon.iconURL ||
+ (this.mInstall ? this.mInstall.iconURL : "");
+ this._name.value = this.mAddon.name;
+
+ if (this.mAddon.version) {
+ this._version.value = this.mAddon.version;
+ this._version.hidden = false;
+ } else {
+ this._version.hidden = true;
+ }
+
+ } else {
+ this._icon.src = this.mInstall.iconURL;
+ // AddonInstall.name isn't always available - fallback to filename
+ if (this.mInstall.name) {
+ this._name.value = this.mInstall.name;
+ } else if (this.mInstall.sourceURI) {
+ var url = Components.classes["@mozilla.org/network/standard-url;1"]
+ .createInstance(Components.interfaces.nsIStandardURL);
+ url.init(url.URLTYPE_STANDARD, 80, this.mInstall.sourceURI.spec,
+ null, null);
+ url.QueryInterface(Components.interfaces.nsIURL);
+ this._name.value = url.fileName;
+ }
+
+ if (this.mInstall.version) {
+ this._version.value = this.mInstall.version;
+ this._version.hidden = false;
+ } else {
+ this._version.hidden = true;
+ }
+ }
+
+ if (this.mInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.downloadError",
+ [this._name.value], 1
+ );
+ this._warningLink.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
+ this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ } else if (this.mInstall.state == AddonManager.STATE_INSTALL_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.installError",
+ [this._name.value], 1
+ );
+ this._warningLink.label = gStrings.ext.GetStringFromName("notification.installError.retry");
+ this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ } else {
+ this.removeAttribute("notification");
+ }
+ ]]></body>
+ </method>
+
+ <method name="retryInstall">
+ <body><![CDATA[
+ this.mInstall.install();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="detail-row">
+ <content>
+ <xul:label class="detail-row-label" xbl:inherits="value=label"/>
+ <xul:label class="detail-row-value" xbl:inherits="value"/>
+ </content>
+
+ <implementation>
+ <property name="value">
+ <getter><![CDATA[
+ return this.getAttribute("value");
+ ]]></getter>
+ <setter><![CDATA[
+ if (!val)
+ this.removeAttribute("value");
+ else
+ this.setAttribute("value", val);
+ ]]></setter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/toolkit/mozapps/extensions/content/extensions.xul b/toolkit/mozapps/extensions/content/extensions.xul
new file mode 100644
index 000000000..c1a8edc86
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -0,0 +1,689 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/content/extensions/extensions.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/extensions.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/about.css"?>
+
+<!DOCTYPE page [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ id="addons-page" title="&addons.windowTitle;"
+ role="application" windowtype="Addons:Manager"
+ disablefastfind="true">
+
+ <xhtml:link rel="shortcut icon"
+ href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>
+
+ <script type="application/javascript"
+ src="chrome://mozapps/content/extensions/extensions.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/contentAreaUtils.js"/>
+
+ <popupset>
+ <!-- menu for an addon item -->
+ <menupopup id="addonitem-popup">
+ <menuitem id="menuitem_showDetails" command="cmd_showItemDetails"
+ default="true" label="&cmd.showDetails.label;"
+ accesskey="&cmd.showDetails.accesskey;"/>
+ <menuitem id="menuitem_enableItem" command="cmd_enableItem"
+ label="&cmd.enableAddon.label;"
+ accesskey="&cmd.enableAddon.accesskey;"/>
+ <menuitem id="menuitem_disableItem" command="cmd_disableItem"
+ label="&cmd.disableAddon.label;"
+ accesskey="&cmd.disableAddon.accesskey;"/>
+ <menuitem id="menuitem_enableTheme" command="cmd_enableItem"
+ label="&cmd.enableTheme.label;"
+ accesskey="&cmd.enableTheme.accesskey;"/>
+ <menuitem id="menuitem_disableTheme" command="cmd_disableItem"
+ label="&cmd.disableTheme.label;"
+ accesskey="&cmd.disableTheme.accesskey;"/>
+ <menuitem id="menuitem_installItem" command="cmd_installItem"
+ label="&cmd.installAddon.label;"
+ accesskey="&cmd.installAddon.accesskey;"/>
+ <menuitem id="menuitem_uninstallItem" command="cmd_uninstallItem"
+ label="&cmd.uninstallAddon.label;"
+ accesskey="&cmd.uninstallAddon.accesskey;"/>
+ <menuitem id="menuitem_debugItem" command="cmd_debugItem"
+ label="&cmd.debugAddon.label;"/>
+ <menuseparator id="addonitem-menuseparator" />
+ <menuitem id="menuitem_preferences" command="cmd_showItemPreferences"
+#ifdef XP_WIN
+ label="&cmd.preferencesWin.label;"
+ accesskey="&cmd.preferencesWin.accesskey;"/>
+#else
+ label="&cmd.preferencesUnix.label;"
+ accesskey="&cmd.preferencesUnix.accesskey;"/>
+#endif
+ <menuitem id="menuitem_findUpdates" command="cmd_findItemUpdates"
+ label="&cmd.findUpdates.label;"
+ accesskey="&cmd.findUpdates.accesskey;"/>
+ <menuitem id="menuitem_about" command="cmd_showItemAbout"
+ label="&cmd.about.label;"
+ accesskey="&cmd.about.accesskey;"/>
+ </menupopup>
+ </popupset>
+
+ <!-- global commands - these act on all addons, or affect the addons manager
+ in some other way -->
+ <commandset id="globalCommandSet">
+ <command id="cmd_focusSearch"/>
+ <command id="cmd_findAllUpdates"/>
+ <command id="cmd_restartApp"/>
+ <command id="cmd_goToDiscoverPane"/>
+ <command id="cmd_goToRecentUpdates"/>
+ <command id="cmd_goToAvailableUpdates"/>
+ <command id="cmd_installFromFile"/>
+ <command id="cmd_back"/>
+ <command id="cmd_forward"/>
+ <command id="cmd_enableCheckCompatibility"/>
+<!-- <command id="cmd_pluginCheck"/> -->
+ <command id="cmd_enableUpdateSecurity"/>
+ <command id="cmd_toggleAutoUpdateDefault"/>
+ <command id="cmd_resetAddonAutoUpdate"/>
+ <command id="cmd_experimentsLearnMore"/>
+ <command id="cmd_experimentsOpenTelemetryPreferences"/>
+ </commandset>
+
+ <!-- view commands - these act on the selected addon -->
+ <commandset id="viewCommandSet"
+ events="richlistbox-select" commandupdater="true">
+ <command id="cmd_showItemDetails"/>
+ <command id="cmd_findItemUpdates"/>
+ <command id="cmd_showItemPreferences"/>
+ <command id="cmd_showItemAbout"/>
+ <command id="cmd_debugItem"/>
+ <command id="cmd_enableItem"/>
+ <command id="cmd_disableItem"/>
+ <command id="cmd_installItem"/>
+ <command id="cmd_purchaseItem"/>
+ <command id="cmd_uninstallItem"/>
+ <command id="cmd_cancelUninstallItem"/>
+ <command id="cmd_cancelOperation"/>
+ <command id="cmd_contribute"/>
+ <command id="cmd_askToActivateItem"/>
+ <command id="cmd_alwaysActivateItem"/>
+ <command id="cmd_neverActivateItem"/>
+ </commandset>
+
+ <keyset>
+ <!-- XXXunf Disabled until bug 371900 is fixed. -->
+ <key id="focusSearch" key="&search.commandkey;" modifiers="accel"
+ disabled="true"/>
+ </keyset>
+
+ <!-- main header -->
+ <hbox id="header" align="center">
+ <toolbarbutton id="back-btn" class="nav-button header-button" command="cmd_back"
+ tooltiptext="&cmd.back.tooltip;" hidden="true" disabled="true"/>
+ <toolbarbutton id="forward-btn" class="nav-button header-button" command="cmd_forward"
+ tooltiptext="&cmd.forward.tooltip;" hidden="true" disabled="true"/>
+ <spacer flex="1"/>
+ <hbox id="updates-container" align="center">
+ <image class="spinner"/>
+ <label id="updates-noneFound" hidden="true"
+ value="&updates.noneFound.label;"/>
+ <button id="updates-manualUpdatesFound-btn" class="button-link"
+ hidden="true" label="&updates.manualUpdatesFound.label;"
+ command="cmd_goToAvailableUpdates"/>
+ <label id="updates-progress" hidden="true"
+ value="&updates.updating.label;"/>
+ <label id="updates-installed" hidden="true"
+ value="&updates.installed.label;"/>
+ <label id="updates-downloaded" hidden="true"
+ value="&updates.downloaded.label;"/>
+ <button id="updates-restart-btn" class="button-link" hidden="true"
+ label="&updates.restart.label;"
+ command="cmd_restartApp"/>
+ </hbox>
+ <toolbarbutton id="header-utils-btn" class="header-button" type="menu"
+ tooltiptext="&toolsMenu.tooltip;">
+ <menupopup id="utils-menu">
+ <menuitem id="utils-updateNow"
+ label="&updates.checkForUpdates.label;"
+ accesskey="&updates.checkForUpdates.accesskey;"
+ command="cmd_findAllUpdates"/>
+ <menuitem id="utils-viewUpdates"
+ label="&updates.viewUpdates.label;"
+ accesskey="&updates.viewUpdates.accesskey;"
+ command="cmd_goToRecentUpdates"/>
+ <menuseparator id="utils-installFromFile-separator"/>
+ <menuitem id="utils-installFromFile"
+ label="&installAddonFromFile.label;"
+ accesskey="&installAddonFromFile.accesskey;"
+ command="cmd_installFromFile"/>
+ <menuseparator/>
+ <menuitem id="utils-autoUpdateDefault"
+ label="&updates.updateAddonsAutomatically.label;"
+ accesskey="&updates.updateAddonsAutomatically.accesskey;"
+ type="checkbox" autocheck="false"
+ command="cmd_toggleAutoUpdateDefault"/>
+ <menuitem id="utils-resetAddonUpdatesToAutomatic"
+ label="&updates.resetUpdatesToAutomatic.label;"
+ accesskey="&updates.resetUpdatesToAutomatic.accesskey;"
+ command="cmd_resetAddonAutoUpdate"/>
+ <menuitem id="utils-resetAddonUpdatesToManual"
+ label="&updates.resetUpdatesToManual.label;"
+ accesskey="&updates.resetUpdatesToManual.accesskey;"
+ command="cmd_resetAddonAutoUpdate"/>
+ </menupopup>
+ </toolbarbutton>
+ <textbox id="header-search" type="search" searchbutton="true"
+ searchbuttonlabel="&search.buttonlabel;"
+ placeholder="&search.placeholder;"/>
+ </hbox>
+
+ <hbox flex="1">
+
+ <!-- category list -->
+ <richlistbox id="categories">
+ <richlistitem id="category-search" value="addons://search/"
+ class="category"
+ name="&view.search.label;" priority="0"
+ tooltiptext="&view.search.label;" disabled="true"/>
+ <richlistitem id="category-discover" value="addons://discover/"
+ class="category"
+ name="&view.discover.label;" priority="1000"
+ tooltiptext="&view.discover.label;"/>
+ <richlistitem id="category-availableUpdates" value="addons://updates/available"
+ class="category"
+ name="&view.availableUpdates.label;" priority="100000"
+ tooltiptext="&view.availableUpdates.label;"
+ disabled="true"/>
+ <richlistitem id="category-recentUpdates" value="addons://updates/recent"
+ class="category"
+ name="&view.recentUpdates.label;" priority="101000"
+ tooltiptext="&view.recentUpdates.label;" disabled="true"/>
+ </richlistbox>
+
+ <box id="view-port-container" class="main-content" flex="1">
+
+ <!-- view port -->
+ <deck id="view-port" flex="1" selectedIndex="0">
+
+ <!-- discover view -->
+ <deck id="discover-view" flex="1" class="view-pane" selectedIndex="0" tabindex="0">
+ <vbox id="discover-loading" align="center" pack="stretch" flex="1" class="alert-container">
+ <spacer class="alert-spacer-before"/>
+ <hbox class="alert loading" align="center">
+ <image/>
+ <label value="&loading.label;"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <vbox id="discover-error" align="center" pack="stretch" flex="1" class="alert-container">
+ <spacer class="alert-spacer-before"/>
+ <hbox>
+ <spacer class="discover-spacer-before"/>
+ <hbox class="alert" align="center">
+ <image class="discover-logo"/>
+ <vbox flex="1" align="stretch">
+ <label class="discover-title">&discover.title;</label>
+ <description class="discover-description">&discover.description2;</description>
+ <description class="discover-footer">&discover.footer;</description>
+ </vbox>
+ </hbox>
+ <spacer class="discover-spacer-after"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <browser id="discover-browser" type="content" flex="1"
+ disablehistory="true" homepage="about:blank"/>
+ </deck>
+
+ <!-- search view -->
+ <vbox id="search-view" flex="1" class="view-pane" tabindex="0">
+ <hbox class="view-header global-warning-container" align="center">
+ <!-- global warnings -->
+ <hbox class="global-warning" flex="1">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <spacer flex="1"/>
+ <hbox id="search-sorters" class="sort-controls"
+ showrelevance="true" sortby="relevancescore" ascending="false"/>
+ </hbox>
+ <hbox id="search-filter" align="center">
+ <label id="search-filter-label" value="&search.filter2.label;"/>
+ <radiogroup id="search-filter-radiogroup" orient="horizontal"
+ align="center" persist="value" value="remote">
+ <radio id="search-filter-local" class="search-filter-radio"
+ label="&search.filter2.installed.label;" value="local"
+ tooltiptext="&search.filter2.installed.tooltip;"/>
+ <radio id="search-filter-remote" class="search-filter-radio"
+ label="&search.filter2.available.label;" value="remote"
+ tooltiptext="&search.filter2.available.tooltip;"/>
+ </radiogroup>
+ </hbox>
+ <vbox id="search-loading" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <hbox class="alert loading" align="center">
+ <image/>
+ <label value="&loading.label;"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <vbox id="search-list-empty" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <vbox class="alert">
+ <label value="&listEmpty.search.label;"/>
+ <button class="discover-button"
+ id="discover-button-search"
+ label="&listEmpty.button.label;"
+ command="cmd_goToDiscoverPane"/>
+ </vbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <richlistbox id="search-list" class="list" flex="1">
+ <hbox pack="center">
+ <label id="search-allresults-link" class="text-link"/>
+ </hbox>
+ </richlistbox>
+ </vbox>
+
+ <!-- list view -->
+ <vbox id="list-view" flex="1" class="view-pane" align="stretch" tabindex="0">
+ <hbox class="view-header global-warning-container">
+ <!-- global warnings -->
+ <hbox class="global-warning" flex="1">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ </hbox>
+<!-- <hbox class="view-header global-info-container plugin-info-container">
+ <hbox class="global-info" flex="1" align="center">
+ <button class="button-link global-info-plugincheck"
+ label="&info.plugincheck.label;"
+ tooltiptext="&info.plugincheck.tooltip;"
+ command="cmd_pluginCheck"/>
+ <spacer flex="5000"/>
+ </hbox>
+ </hbox> -->
+ <hbox class="view-header global-info-container experiment-info-container">
+ <hbox class="global-info" flex="1" align="center">
+ <label value="&experiment.info.label;"/>
+ <button id="experiments-learn-more"
+ label="&experiment.info.learnmore;"
+ tooltiptext="&experiment.info.learnmore;"
+ accesskey="&experiment.info.learnmore.accesskey;"
+ command="cmd_experimentsLearnMore"/>
+ <button id="experiments-change-telemetry"
+ label="&experiment.info.changetelemetry;"
+ tooltiptext="&experiment.info.changetelemetry;"
+ accesskey="&experiment.info.changetelemetry.accesskey;"
+ command="cmd_experimentsOpenTelemetryPreferences"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap. -->
+ </hbox>
+ </hbox>
+ <vbox id="addon-list-empty" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <vbox class="alert">
+ <label value="&listEmpty.installed.label;"/>
+ <button class="discover-button"
+ id="discover-button-install"
+ label="&listEmpty.button.label;"
+ command="cmd_goToDiscoverPane"/>
+ </vbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <richlistbox id="addon-list" class="list" flex="1"/>
+ </vbox>
+
+ <!-- updates view -->
+ <vbox id="updates-view" flex="1" class="view-pane" tabindex="0">
+ <hbox class="view-header global-warning-container" align="center">
+ <!-- global warnings -->
+ <hbox class="global-warning" flex="1">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <spacer flex="1"/>
+ <hbox id="updates-sorters" class="sort-controls" sortby="updateDate"
+ ascending="false"/>
+ </hbox>
+ <vbox id="updates-list-empty" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <vbox class="alert">
+ <label id="empty-availableUpdates-msg" value="&listEmpty.availableUpdates.label;"/>
+ <label id="empty-recentUpdates-msg" value="&listEmpty.recentUpdates.label;"/>
+ <button label="&listEmpty.findUpdates.label;"
+ command="cmd_findAllUpdates"/>
+ </vbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <hbox id="update-actions" pack="center">
+ <button id="update-selected-btn" hidden="true"
+ label="&updates.updateSelected.label;"
+ tooltiptext="&updates.updateSelected.tooltip;"/>
+ </hbox>
+ <richlistbox id="updates-list" class="list" flex="1"/>
+ </vbox>
+
+ <!-- detail view -->
+ <scrollbox id="detail-view" flex="1" class="view-pane addon-view" orient="vertical" tabindex="0"
+ role="document">
+ <!-- global warnings -->
+ <hbox class="global-warning-container global-warning">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <hbox flex="1">
+ <spacer flex="1"/>
+ <!-- "loading" splash screen -->
+ <vbox class="alert-container">
+ <spacer class="alert-spacer-before"/>
+ <hbox class="alert loading">
+ <image/>
+ <label value="&loading.label;"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <!-- actual detail view -->
+ <vbox class="detail-view-container" flex="3" contextmenu="addonitem-popup">
+ <vbox id="detail-notifications">
+ <hbox id="warning-container" align="center" class="warning">
+ <image class="warning-icon"/>
+ <label id="detail-warning" flex="1"/>
+ <label id="detail-warning-link" class="text-link"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <hbox id="error-container" align="center" class="error">
+ <image class="error-icon"/>
+ <label id="detail-error" flex="1"/>
+ <label id="detail-error-link" class="text-link"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <hbox id="pending-container" align="center" class="pending">
+ <image class="pending-icon"/>
+ <label id="detail-pending" flex="1"/>
+ <button id="detail-restart-btn" class="button-link"
+ label="&addon.restartNow.label;"
+ command="cmd_restartApp"/>
+ <button id="detail-undo-btn" class="button-link"
+ label="&addon.undoAction.label;"
+ tooltipText="&addon.undoAction.tooltip;"
+ command="cmd_cancelOperation"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ </vbox>
+ <hbox align="start">
+ <vbox id="detail-icon-container" align="end">
+ <image id="detail-icon" class="icon"/>
+ </vbox>
+ <vbox flex="1">
+ <vbox id="detail-summary">
+ <hbox id="detail-name-container" class="name-container"
+ align="start">
+ <label id="detail-name" flex="1"/>
+ <label id="detail-version"/>
+ <label class="disabled-postfix" value="&addon.disabled.postfix;"/>
+ <label class="update-postfix" value="&addon.update.postfix;"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the name to wrap -->
+ </hbox>
+ <label id="detail-creator" class="creator"/>
+ <label id="detail-translators" class="translators"/>
+ </vbox>
+ <hbox id="detail-experiment-container">
+ <svg width="8" height="8" viewBox="0 0 8 8" version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ id="detail-experiment-bullet-container">
+ <circle cx="4" cy="4" r="4" id="detail-experiment-bullet"/>
+ </svg>
+ <label id="detail-experiment-state"/>
+ <label id="detail-experiment-time"/>
+ </hbox>
+ <hbox id="detail-desc-container" align="start">
+ <vbox pack="center"> <!-- Necessary to work around bug 394738 -->
+ <image id="detail-screenshot" hidden="true"/>
+ </vbox>
+ <vbox flex="1">
+ <description id="detail-desc"/>
+ <description id="detail-fulldesc"/>
+ </vbox>
+ </hbox>
+ <vbox id="detail-contributions">
+ <description id="detail-contrib-description">
+ &detail.contributions.description;
+ </description>
+ <hbox align="center">
+ <label id="detail-contrib-suggested"/>
+ <spacer flex="1"/>
+ <button id="detail-contrib-btn"
+ label="&cmd.contribute.label;"
+ accesskey="&cmd.contribute.accesskey;"
+ tooltiptext="&cmd.contribute.tooltip;"
+ command="cmd_contribute"/>
+ </hbox>
+ </vbox>
+ <grid id="detail-grid">
+ <columns>
+ <column flex="1"/>
+ <column flex="2"/>
+ </columns>
+ <rows id="detail-rows">
+ <row class="detail-row-complex" id="detail-updates-row">
+ <label class="detail-row-label" value="&detail.updateType;"/>
+ <hbox align="center">
+ <radiogroup id="detail-autoUpdate" orient="horizontal">
+ <!-- The values here need to match the values of
+ AddonManager.AUTOUPDATE_* -->
+ <radio label="&detail.updateDefault.label;"
+ tooltiptext="&detail.updateDefault.tooltip;"
+ value="1"/>
+ <radio label="&detail.updateAutomatic.label;"
+ tooltiptext="&detail.updateAutomatic.tooltip;"
+ value="2"/>
+ <radio label="&detail.updateManual.label;"
+ tooltiptext="&detail.updateManual.tooltip;"
+ value="0"/>
+ </radiogroup>
+ <button id="detail-findUpdates-btn" class="button-link"
+ label="&detail.checkForUpdates.label;"
+ accesskey="&detail.checkForUpdates.accesskey;"
+ tooltiptext="&detail.checkForUpdates.tooltip;"
+ command="cmd_findItemUpdates"/>
+ </hbox>
+ </row>
+ <row class="detail-row" id="detail-dateUpdated" label="&detail.lastupdated.label;"/>
+ <row class="detail-row-complex" id="detail-homepage-row" label="&detail.home;">
+ <label class="detail-row-label" value="&detail.home;"/>
+ <label id="detail-homepage" class="detail-row-value text-link" crop="end"/>
+ </row>
+ <row class="detail-row-complex" id="detail-repository-row" label="&detail.repository;">
+ <label class="detail-row-label" value="&detail.repository;"/>
+ <label id="detail-repository" class="detail-row-value text-link"/>
+ </row>
+ <row class="detail-row" id="detail-size" label="&detail.size;"/>
+ <row class="detail-row-complex" id="detail-rating-row">
+ <label class="detail-row-label" value="&rating2.label;"/>
+ <hbox>
+ <label id="detail-rating" class="meta-value meta-rating"
+ showrating="average"/>
+ <label id="detail-reviews" class="text-link"/>
+ </hbox>
+ </row>
+ <row class="detail-row" id="detail-downloads" label="&detail.numberOfDownloads.label;"/>
+ </rows>
+ </grid>
+ <hbox id="detail-controls">
+ <button id="detail-prefs-btn" class="addon-control preferences"
+#ifdef XP_WIN
+ label="&detail.showPreferencesWin.label;"
+ accesskey="&detail.showPreferencesWin.accesskey;"
+ tooltiptext="&detail.showPreferencesWin.tooltip;"
+#else
+ label="&detail.showPreferencesUnix.label;"
+ accesskey="&detail.showPreferencesUnix.accesskey;"
+ tooltiptext="&detail.showPreferencesUnix.tooltip;"
+#endif
+ command="cmd_showItemPreferences"/>
+ <spacer flex="1"/>
+ <button id="detail-debug-btn" class="addon-control debug"
+ label="Debug"
+ command="cmd_debugItem" />
+ <button id="detail-enable-btn" class="addon-control enable"
+ label="&cmd.enableAddon.label;"
+ accesskey="&cmd.enableAddon.accesskey;"
+ command="cmd_enableItem"/>
+ <button id="detail-disable-btn" class="addon-control disable"
+ label="&cmd.disableAddon.label;"
+ accesskey="&cmd.disableAddon.accesskey;"
+ command="cmd_disableItem"/>
+ <button id="detail-uninstall-btn" class="addon-control remove"
+ label="&cmd.uninstallAddon.label;"
+ accesskey="&cmd.uninstallAddon.accesskey;"
+ command="cmd_uninstallItem"/>
+ <button id="detail-purchase-btn" class="addon-control purchase"
+ command="cmd_purchaseItem"/>
+ <button id="detail-install-btn" class="addon-control install"
+ label="&cmd.installAddon.label;"
+ accesskey="&cmd.installAddon.accesskey;"
+ command="cmd_installItem"/>
+ <menulist id="detail-state-menulist"
+ crop="none" sizetopopup="always"
+ tooltiptext="&cmd.stateMenu.tooltip;">
+ <menupopup>
+ <menuitem id="detail-ask-to-activate-menuitem"
+ class="addon-control"
+ label="&cmd.askToActivate.label;"
+ tooltiptext="&cmd.askToActivate.tooltip;"
+ command="cmd_askToActivateItem"/>
+ <menuitem id="detail-always-activate-menuitem"
+ class="addon-control"
+ label="&cmd.alwaysActivate.label;"
+ tooltiptext="&cmd.alwaysActivate.tooltip;"
+ command="cmd_alwaysActivateItem"/>
+ <menuitem id="detail-never-activate-menuitem"
+ class="addon-control"
+ label="&cmd.neverActivate.label;"
+ tooltiptext="&cmd.neverActivate.tooltip;"
+ command="cmd_neverActivateItem"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </vbox>
+ </hbox>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ </scrollbox>
+
+ </deck>
+
+ </box>
+ </hbox>
+
+</page>
diff --git a/toolkit/mozapps/extensions/content/gmpPrefs.xul b/toolkit/mozapps/extensions/content/gmpPrefs.xul
new file mode 100644
index 000000000..ea7ee92fa
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/gmpPrefs.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!-- This is intentionally empty and a dummy to let the GMPProvider
+ have a preferences button in the list view. -->
diff --git a/toolkit/mozapps/extensions/content/list.js b/toolkit/mozapps/extensions/content/list.js
new file mode 100644
index 000000000..aac87911e
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/list.js
@@ -0,0 +1,165 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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/. */
+
+const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const kDialog = "dialog";
+
+/**
+ * This dialog can be initialized from parameters supplied via window.arguments
+ * or it can be used to display blocklist notification and blocklist blocked
+ * installs via nsIDialogParamBlock as is done by nsIExtensionManager.
+ *
+ * When using this dialog with window.arguments it must be opened modally, the
+ * caller can inspect the user action after the dialog closes by inspecting the
+ * value of the |result| parameter on this object which is set to the dlgtype
+ * of the button used to close the dialog.
+ *
+ * window.arguments[0] is an array of strings to display in the tree. If the
+ * array is empty the tree will not be displayed.
+ * window.arguments[1] a JS Object with the following properties:
+ *
+ * title: A title string, to be displayed in the title bar of the dialog.
+ * message1: A message string, displayed above the addon list
+ * message2: A message string, displayed below the addon list
+ * message3: A bolded message string, displayed below the addon list
+ * moreInfoURL: An url for displaying more information
+ * iconClass : Can be one of the following values (default is alert-icon)
+ * alert-icon, error-icon, or question-icon
+ *
+ * If no value is supplied for message1, message2, message3, or moreInfoURL,
+ * the element is not displayed.
+ *
+ * buttons: {
+ * accept: { label: "A Label for the Accept button",
+ * focused: true },
+ * cancel: { label: "A Label for the Cancel button" },
+ * ...
+ * },
+ *
+ * result: The dlgtype of button that was used to dismiss the dialog.
+ */
+
+"use strict";
+
+var gButtons = { };
+
+function init() {
+ var de = document.documentElement;
+ var items = [];
+ if (window.arguments[0] instanceof Components.interfaces.nsIDialogParamBlock) {
+ // This is a warning about a blocklisted item the user is trying to install
+ var args = window.arguments[0];
+ var softblocked = args.GetInt(0) == 1 ? true : false;
+
+ var extensionsBundle = document.getElementById("extensionsBundle");
+ try {
+ var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter);
+ var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
+ }
+ catch (e) { }
+
+ var params = {
+ moreInfoURL: url,
+ };
+
+ if (softblocked) {
+ params.title = extensionsBundle.getString("softBlockedInstallTitle");
+ params.message1 = extensionsBundle.getFormattedString("softBlockedInstallMsg",
+ [args.GetString(0)]);
+ var accept = de.getButton("accept");
+ accept.label = extensionsBundle.getString("softBlockedInstallAcceptLabel");
+ accept.accessKey = extensionsBundle.getString("softBlockedInstallAcceptKey");
+ de.getButton("cancel").focus();
+ document.addEventListener("dialogaccept", allowInstall, false);
+ }
+ else {
+ params.title = extensionsBundle.getString("blocklistedInstallTitle2");
+ params.message1 = extensionsBundle.getFormattedString("blocklistedInstallMsg2",
+ [args.GetString(0)]);
+ de.buttons = "accept";
+ de.getButton("accept").focus();
+ }
+ }
+ else {
+ items = window.arguments[0];
+ params = window.arguments[1];
+ }
+
+ var addons = document.getElementById("addonsChildren");
+ if (items.length > 0)
+ document.getElementById("addonsTree").hidden = false;
+
+ // Fill the addons list
+ for (var item of items) {
+ var treeitem = document.createElementNS(kXULNS, "treeitem");
+ var treerow = document.createElementNS(kXULNS, "treerow");
+ var treecell = document.createElementNS(kXULNS, "treecell");
+ treecell.setAttribute("label", item);
+ treerow.appendChild(treecell);
+ treeitem.appendChild(treerow);
+ addons.appendChild(treeitem);
+ }
+
+ // Set the messages
+ var messages = ["message1", "message2", "message3"];
+ for (let messageEntry of messages) {
+ if (messageEntry in params) {
+ var message = document.getElementById(messageEntry);
+ message.hidden = false;
+ message.appendChild(document.createTextNode(params[messageEntry]));
+ }
+ }
+
+ document.getElementById("infoIcon").className =
+ params["iconClass"] ? "spaced " + params["iconClass"] : "spaced alert-icon";
+
+ if ("moreInfoURL" in params && params["moreInfoURL"]) {
+ message = document.getElementById("moreInfo");
+ message.value = extensionsBundle.getString("moreInfoText");
+ message.setAttribute("href", params["moreInfoURL"]);
+ document.getElementById("moreInfoBox").hidden = false;
+ }
+
+ // Set the window title
+ if ("title" in params)
+ document.title = params.title;
+
+ // Set up the buttons
+ if ("buttons" in params) {
+ gButtons = params.buttons;
+ var buttonString = "";
+ for (var buttonType in gButtons)
+ buttonString += "," + buttonType;
+ de.buttons = buttonString.substr(1);
+ for (buttonType in gButtons) {
+ var button = de.getButton(buttonType);
+ button.label = gButtons[buttonType].label;
+ if (gButtons[buttonType].focused)
+ button.focus();
+ document.addEventListener(kDialog + buttonType, handleButtonCommand, true);
+ }
+ }
+}
+
+function shutdown() {
+ for (var buttonType in gButtons)
+ document.removeEventListener(kDialog + buttonType, handleButtonCommand, true);
+}
+
+function allowInstall() {
+ var args = window.arguments[0];
+ args.SetInt(1, 1);
+}
+
+/**
+ * Watch for the user hitting one of the buttons to dismiss the dialog
+ * and report the result back to the caller through the |result| property on
+ * the arguments object.
+ */
+function handleButtonCommand(event) {
+ window.arguments[1].result = event.type.substr(kDialog.length);
+}
diff --git a/toolkit/mozapps/extensions/content/list.xul b/toolkit/mozapps/extensions/content/list.xul
new file mode 100644
index 000000000..65efeb6a2
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/list.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<dialog id="addonList" windowtype="Addons:List"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onunload="shutdown();"
+ buttons="accept,cancel" onload="init();">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://mozapps/content/extensions/list.js"/>
+
+ <stringbundle id="extensionsBundle"
+ src="chrome://mozapps/locale/extensions/extensions.properties"/>
+ <stringbundle id="brandBundle"
+ src="chrome://branding/locale/brand.properties"/>
+
+ <hbox align="start">
+ <vbox>
+ <image id="infoIcon"/>
+ </vbox>
+ <vbox class="spaced" style="min-width: 20em; max-width: 40em">
+ <label id="message1" class="spaced" hidden="true"/>
+ <separator class="thin"/>
+ <tree id="addonsTree" rows="6" hidecolumnpicker="true" hidden="true" class="spaced">
+ <treecols style="max-width: 25em;">
+ <treecol flex="1" id="nameColumn" hideheader="true"/>
+ </treecols>
+ <treechildren id="addonsChildren"/>
+ </tree>
+ <label id="message2" class="spaced" hidden="true"/>
+ <label class="bold spaced" id="message3" hidden="true"/>
+ <hbox id="moreInfoBox" hidden="true">
+ <label id="moreInfo" class="text-link spaced"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/toolkit/mozapps/extensions/content/newaddon.js b/toolkit/mozapps/extensions/content/newaddon.js
new file mode 100644
index 000000000..2b2a54af5
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/newaddon.js
@@ -0,0 +1,129 @@
+/* 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/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+
+var gAddon = null;
+
+// If the user enables the add-on through some other UI close this window
+var EnableListener = {
+ onEnabling: function EnableListener_onEnabling(aAddon) {
+ if (aAddon.id == gAddon.id)
+ window.close();
+ }
+}
+AddonManager.addAddonListener(EnableListener);
+
+function initialize() {
+ // About URIs don't implement nsIURL so we have to find the query string
+ // manually
+ let spec = document.location.href;
+ let pos = spec.indexOf("?");
+ let query = "";
+ if (pos >= 0)
+ query = spec.substring(pos + 1);
+
+ // Just assume the query is "id=<id>"
+ let id = query.substring(3);
+ if (!id) {
+ window.location = "about:blank";
+ return;
+ }
+
+ let bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/newaddon.properties");
+
+ AddonManager.getAddonByID(id, function initialize_getAddonByID(aAddon) {
+ // If the add-on doesn't exist or it is already enabled or it cannot be
+ // enabled then this UI is useless, just close it. This shouldn't normally
+ // happen unless session restore restores the tab
+ if (!aAddon || !aAddon.userDisabled ||
+ !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE)) {
+ window.close();
+ return;
+ }
+
+ gAddon = aAddon;
+
+ document.getElementById("addon-info").setAttribute("type", aAddon.type);
+
+ let icon = document.getElementById("icon");
+ if (aAddon.icon64URL)
+ icon.src = aAddon.icon64URL;
+ else if (aAddon.iconURL)
+ icon.src = aAddon.iconURL;
+
+ let name = bundle.formatStringFromName("name", [aAddon.name, aAddon.version],
+ 2);
+ document.getElementById("name").value = name;
+
+ if (aAddon.creator) {
+ let creator = bundle.formatStringFromName("author", [aAddon.creator], 1);
+ document.getElementById("author").value = creator;
+ } else {
+ document.getElementById("author").hidden = true;
+ }
+
+ let uri = "getResourceURI" in aAddon ? aAddon.getResourceURI() : null;
+ let locationLabel = document.getElementById("location");
+ if (uri instanceof Ci.nsIFileURL) {
+ let location = bundle.formatStringFromName("location", [uri.file.path], 1);
+ locationLabel.value = location;
+ locationLabel.setAttribute("tooltiptext", location);
+ } else {
+ document.getElementById("location").hidden = true;
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("AddonDisplayed", true, true);
+ document.dispatchEvent(event);
+ });
+}
+
+function unload() {
+ AddonManager.removeAddonListener(EnableListener);
+}
+
+function continueClicked() {
+ AddonManager.removeAddonListener(EnableListener);
+
+ if (document.getElementById("allow").checked) {
+ gAddon.userDisabled = false;
+
+ if (gAddon.pendingOperations & AddonManager.PENDING_ENABLE) {
+ document.getElementById("allow").disabled = true;
+ document.getElementById("buttonDeck").selectedPanel = document.getElementById("restartPanel");
+ return;
+ }
+ }
+
+ window.close();
+}
+
+function restartClicked() {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (cancelQuit.data)
+ return; // somebody canceled our quit request
+
+ window.close();
+
+ let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
+ getService(Components.interfaces.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+}
+
+function cancelClicked() {
+ gAddon.userDisabled = true;
+ AddonManager.addAddonListener(EnableListener);
+
+ document.getElementById("allow").disabled = false;
+ document.getElementById("buttonDeck").selectedPanel = document.getElementById("continuePanel");
+}
diff --git a/toolkit/mozapps/extensions/content/newaddon.xul b/toolkit/mozapps/extensions/content/newaddon.xul
new file mode 100644
index 000000000..0806f2799
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/newaddon.xul
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/newaddon.css"?>
+
+<!DOCTYPE page [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % newaddonDTD SYSTEM "chrome://mozapps/locale/extensions/newaddon.dtd">
+%newaddonDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml" title="&title;"
+ disablefastfind="true" id="addon-page" onload="initialize()"
+ onunload="unload()" role="application" align="stretch" pack="stretch">
+
+ <xhtml:link rel="shortcut icon" style="display: none"
+ href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>
+
+ <script type="application/javascript"
+ src="chrome://mozapps/content/extensions/newaddon.js"/>
+
+ <scrollbox id="addon-scrollbox" align="center">
+ <spacer id="spacer-start"/>
+
+ <vbox id="addon-container" class="main-content">
+ <description>&intro;</description>
+
+ <hbox id="addon-info">
+ <image id="icon"/>
+ <vbox flex="1">
+ <label id="name"/>
+ <label id="author"/>
+ <label id="location" crop="end"/>
+ </vbox>
+ </hbox>
+
+ <hbox id="warning">
+ <image id="warning-icon"/>
+ <description flex="1">&warning;</description>
+ </hbox>
+
+ <checkbox id="allow" label="&allow;"/>
+ <description id="later">&later;</description>
+
+ <deck id="buttonDeck">
+ <hbox id="continuePanel">
+ <button id="continue-button" label="&continue;"
+ oncommand="continueClicked()"/>
+ </hbox>
+ <hbox id="restartPanel">
+ <spacer id="restartSpacer"/>
+ <description id="restartMessage" flex="1">&restartMessage;</description>
+ <button id="restart-button" label="&restartButton;" oncommand="restartClicked()"/>
+ <button id="cancel-button" label="&cancelButton;" oncommand="cancelClicked()"/>
+ </hbox>
+ </deck>
+ </vbox>
+
+ <spacer id="spacer-end"/>
+ </scrollbox>
+</page>
diff --git a/toolkit/mozapps/extensions/content/pluginPrefs.xul b/toolkit/mozapps/extensions/content/pluginPrefs.xul
new file mode 100644
index 000000000..a7ee17d89
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/pluginPrefs.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE window SYSTEM "chrome://mozapps/locale/plugins/plugins.dtd">
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <setting type="control" title="&plugin.file;">
+ <label class="text-list" id="pluginLibraries"/>
+ </setting>
+ <setting type="control" title="&plugin.mimeTypes;">
+ <label class="text-list" id="pluginMimeTypes"/>
+ </setting>
+ <setting type="bool" pref="dom.ipc.plugins.flash.disable-protected-mode"
+ inverted="true" title="&plugin.flashProtectedMode.label;"
+ id="pluginEnableProtectedMode"
+ learnmore="https://support.mozilla.org/kb/flash-protected-mode-settings" />
+</vbox>
diff --git a/toolkit/mozapps/extensions/content/selectAddons.css b/toolkit/mozapps/extensions/content/selectAddons.css
new file mode 100644
index 000000000..636757721
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/selectAddons.css
@@ -0,0 +1,22 @@
+/* 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/. */
+
+#select .addon {
+ -moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-select");
+}
+
+#confirm .addon {
+ -moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-confirm");
+}
+
+#select-scrollbox,
+#confirm-scrollbox {
+ overflow-y: auto;
+ -moz-box-orient: vertical;
+}
+
+.addon:not([optionalupdate]) .addon-action-update,
+.addon[optionalupdate] .addon-action-message {
+ display: none;
+}
diff --git a/toolkit/mozapps/extensions/content/selectAddons.js b/toolkit/mozapps/extensions/content/selectAddons.js
new file mode 100644
index 000000000..33ab8a84e
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/selectAddons.js
@@ -0,0 +1,343 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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";
+
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+var gView = null;
+
+function showView(aView) {
+ gView = aView;
+
+ gView.show();
+
+ // If the view's show method immediately showed a different view then don't
+ // do anything else
+ if (gView != aView)
+ return;
+
+ let viewNode = document.getElementById(gView.nodeID);
+ viewNode.parentNode.selectedPanel = viewNode;
+
+ // For testing dispatch an event when the view changes
+ var event = document.createEvent("Events");
+ event.initEvent("ViewChanged", true, true);
+ viewNode.dispatchEvent(event);
+}
+
+function showButtons(aCancel, aBack, aNext, aDone) {
+ document.getElementById("cancel").hidden = !aCancel;
+ document.getElementById("back").hidden = !aBack;
+ document.getElementById("next").hidden = !aNext;
+ document.getElementById("done").hidden = !aDone;
+}
+
+function isAddonDistroInstalled(aID) {
+ let branch = Services.prefs.getBranch("extensions.installedDistroAddon.");
+ if (!branch.prefHasUserValue(aID))
+ return false;
+
+ return branch.getBoolPref(aID);
+}
+
+function orderForScope(aScope) {
+ return aScope == AddonManager.SCOPE_PROFILE ? 1 : 0;
+}
+
+var gAddons = {};
+
+var gChecking = {
+ nodeID: "checking",
+
+ _progress: null,
+ _addonCount: 0,
+ _completeCount: 0,
+
+ show: function gChecking_show() {
+ showButtons(true, false, false, false);
+ this._progress = document.getElementById("checking-progress");
+
+ AddonManager.getAllAddons(aAddons => {
+ if (aAddons.length == 0) {
+ window.close();
+ return;
+ }
+
+ aAddons = aAddons.filter(function gChecking_filterAddons(aAddon) {
+ if (aAddon.type == "plugin" || aAddon.type == "service")
+ return false;
+
+ if (aAddon.type == "theme") {
+ // Don't show application shipped themes
+ if (aAddon.scope == AddonManager.SCOPE_APPLICATION)
+ return false;
+ // Don't show already disabled themes
+ if (aAddon.userDisabled)
+ return false;
+ }
+
+ return true;
+ });
+
+ this._addonCount = aAddons.length;
+ this._progress.value = 0;
+ this._progress.max = aAddons.length;
+ this._progress.mode = "determined";
+
+ AddonRepository.repopulateCache().then(() => {
+ for (let addonItem of aAddons) {
+ // Ignore disabled themes
+ if (addonItem.type != "theme" || !addonItem.userDisabled) {
+ gAddons[addonItem.id] = {
+ addon: addonItem,
+ install: null,
+ wasActive: addonItem.isActive
+ }
+ }
+
+ addonItem.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+ }
+ });
+ });
+ },
+
+ onUpdateAvailable: function gChecking_onUpdateAvailable(aAddon, aInstall) {
+ // If the add-on can be upgraded then remember the new version
+ if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)
+ gAddons[aAddon.id].install = aInstall;
+ },
+
+ onUpdateFinished: function gChecking_onUpdateFinished(aAddon, aError) {
+ this._completeCount++;
+ this._progress.value = this._completeCount;
+
+ if (this._completeCount < this._addonCount)
+ return;
+
+ var addons = [gAddons[id] for (id in gAddons)];
+
+ addons.sort(function sortAddons(a, b) {
+ let orderA = orderForScope(a.addon.scope);
+ let orderB = orderForScope(b.addon.scope);
+
+ if (orderA != orderB)
+ return orderA - orderB;
+
+ return String.localeCompare(a.addon.name, b.addon.name);
+ });
+
+ let rows = document.getElementById("select-rows");
+ let lastAddon = null;
+ for (let entry of addons) {
+ if (lastAddon &&
+ orderForScope(entry.addon.scope) != orderForScope(lastAddon.scope)) {
+ let separator = document.createElement("separator");
+ rows.appendChild(separator);
+ }
+
+ let row = document.createElement("row");
+ row.setAttribute("id", entry.addon.id);
+ row.setAttribute("class", "addon");
+ rows.appendChild(row);
+ row.setAddon(entry.addon, entry.install, entry.wasActive,
+ isAddonDistroInstalled(entry.addon.id));
+
+ if (entry.install)
+ entry.install.addListener(gUpdate);
+
+ lastAddon = entry.addon;
+ }
+
+ showView(gSelect);
+ }
+};
+
+var gSelect = {
+ nodeID: "select",
+
+ show: function gSelect_show() {
+ this.updateButtons();
+ },
+
+ updateButtons: function gSelect_updateButtons() {
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName == "separator")
+ continue;
+
+ if (row.action) {
+ showButtons(false, false, true, false);
+ return;
+ }
+ }
+
+ showButtons(false, false, false, true);
+ },
+
+ next: function gSelect_next() {
+ showView(gConfirm);
+ },
+
+ done: function gSelect_done() {
+ window.close();
+ }
+};
+
+var gConfirm = {
+ nodeID: "confirm",
+
+ show: function gConfirm_show() {
+ showButtons(false, true, false, true);
+
+ let box = document.getElementById("confirm-scrollbox").firstChild;
+ while (box) {
+ box.hidden = true;
+ while (box.lastChild != box.firstChild)
+ box.removeChild(box.lastChild);
+ box = box.nextSibling;
+ }
+
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName == "separator")
+ continue;
+
+ let action = row.action;
+ if (!action)
+ continue;
+
+ let list = document.getElementById(action + "-list");
+
+ list.hidden = false;
+ let item = document.createElement("hbox");
+ item.setAttribute("id", row._addon.id);
+ item.setAttribute("class", "addon");
+ item.setAttribute("type", row._addon.type);
+ item.setAttribute("name", row._addon.name);
+ if (action == "update" || action == "enable")
+ item.setAttribute("active", "true");
+ list.appendChild(item);
+
+ if (action == "update")
+ showButtons(false, true, true, false);
+ }
+ },
+
+ back: function gConfirm_back() {
+ showView(gSelect);
+ },
+
+ next: function gConfirm_next() {
+ showView(gUpdate);
+ },
+
+ done: function gConfirm_done() {
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName != "separator")
+ row.apply();
+ }
+
+ window.close();
+ }
+}
+
+var gUpdate = {
+ nodeID: "update",
+
+ _progress: null,
+ _waitingCount: 0,
+ _completeCount: 0,
+ _errorCount: 0,
+
+ show: function gUpdate_show() {
+ showButtons(true, false, false, false);
+
+ this._progress = document.getElementById("update-progress");
+
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName != "separator")
+ row.apply();
+ }
+
+ this._progress.mode = "determined";
+ this._progress.max = this._waitingCount;
+ this._progress.value = this._completeCount;
+ },
+
+ checkComplete: function gUpdate_checkComplete() {
+ this._progress.value = this._completeCount;
+ if (this._completeCount < this._waitingCount)
+ return;
+
+ if (this._errorCount > 0) {
+ showView(gErrors);
+ return;
+ }
+
+ window.close();
+ },
+
+ onDownloadStarted: function gUpdate_onDownloadStarted(aInstall) {
+ this._waitingCount++;
+ },
+
+ onDownloadFailed: function gUpdate_onDownloadFailed(aInstall) {
+ this._errorCount++;
+ this._completeCount++;
+ this.checkComplete();
+ },
+
+ onInstallFailed: function gUpdate_onInstallFailed(aInstall) {
+ this._errorCount++;
+ this._completeCount++;
+ this.checkComplete();
+ },
+
+ onInstallEnded: function gUpdate_onInstallEnded(aInstall) {
+ this._completeCount++;
+ this.checkComplete();
+ }
+};
+
+var gErrors = {
+ nodeID: "errors",
+
+ show: function gErrors_show() {
+ showButtons(false, false, false, true);
+ },
+
+ done: function gErrors_done() {
+ window.close();
+ }
+};
+
+window.addEventListener("load", function loadEventListener() {
+ showView(gChecking); }, false);
+
+// When closing the window cancel any pending or in-progress installs
+window.addEventListener("unload", function unloadEventListener() {
+ for (let id in gAddons) {
+ let entry = gAddons[id];
+ if (!entry.install)
+ return;
+
+ aEntry.install.removeListener(gUpdate);
+
+ if (entry.install.state != AddonManager.STATE_INSTALLED &&
+ entry.install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
+ entry.install.state != AddonManager.STATE_INSTALL_FAILED) {
+ entry.install.cancel();
+ }
+ }
+}, false);
diff --git a/toolkit/mozapps/extensions/content/selectAddons.xml b/toolkit/mozapps/extensions/content/selectAddons.xml
new file mode 100644
index 000000000..dbfc0d400
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/selectAddons.xml
@@ -0,0 +1,235 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE window [
+<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%updateDTD;
+%brandDTD;
+]>
+
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="addon-select">
+ <content>
+ <xul:hbox class="select-keep select-cell">
+ <xul:checkbox class="addon-keep-checkbox" anonid="keep"
+ xbl:inherits="tooltiptext=name"
+ oncommand="document.getBindingParent(this).keepChanged();"/>
+ </xul:hbox>
+ <xul:hbox class="select-icon select-cell">
+ <xul:image class="addon-icon" xbl:inherits="type"/>
+ </xul:hbox>
+ <xul:hbox class="select-name select-cell">
+ <xul:label class="addon-name" crop="end" style="&select.name.style;"
+ xbl:inherits="xbl:text=name"/>
+ </xul:hbox>
+ <xul:hbox class="select-action select-cell">
+ <xul:label class="addon-action-message" style="&select.action.style;"
+ xbl:inherits="xbl:text=action"/>
+ <xul:checkbox anonid="update" checked="true" class="addon-action-update"
+ oncommand="document.getBindingParent(this).updateChanged();"
+ style="&select.action.style;" xbl:inherits="label=action"/>
+ </xul:hbox>
+ <xul:hbox class="select-source select-cell">
+ <xul:label class="addon-source" xbl:inherits="xbl:text=source"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <field name="_addon"/>
+ <field name="_disabled"/>
+ <field name="_install"/>
+ <field name="_wasActive"/>
+ <field name="_keep">document.getAnonymousElementByAttribute(this, "anonid", "keep");</field>
+ <field name="_update">document.getAnonymousElementByAttribute(this, "anonid", "update");</field>
+ <field name="_strings">document.getElementById("strings");</field>
+
+ <property name="action" readonly="true">
+ <getter><![CDATA[
+ if (!this._keep.checked) {
+ if (this._wasActive)
+ return "disable";
+ else
+ return null;
+ }
+
+ if (this._addon.appDisabled && !this._install)
+ return "incompatible";
+
+ if (this._install && (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked))
+ return "update";
+
+ if (this._addon.permissions & AddonManager.PERM_CAN_ENABLE)
+ return "enable";
+
+ return null;
+ ]]></getter>
+ </property>
+
+ <method name="setAddon">
+ <parameter name="aAddon"/>
+ <parameter name="aInstall"/>
+ <parameter name="aWasActive"/>
+ <parameter name="aDistroInstalled"/>
+ <body><![CDATA[
+ this._addon = aAddon;
+ this._install = aInstall;
+ this._wasActive = aWasActive;
+
+ this.setAttribute("name", aAddon.name);
+ this.setAttribute("type", aAddon.type);
+
+ // User and bundled add-ons default to staying enabled,
+ // others default to disabled.
+ switch (aAddon.scope) {
+ case AddonManager.SCOPE_PROFILE:
+ this._keep.checked = !aAddon.userDisabled;
+ if (aDistroInstalled)
+ this.setAttribute("source", this._strings.getString("source.bundled"));
+ else
+ this.setAttribute("source", this._strings.getString("source.profile"));
+ break;
+ default:
+ this._keep.checked = false;
+ this.setAttribute("source", this._strings.getString("source.other"));
+ }
+
+ this.updateAction();
+ ]]></body>
+ </method>
+
+ <method name="setActionMessage">
+ <body><![CDATA[
+ if (!this._keep.checked) {
+ // If the user no longer wants this add-on then it is either being
+ // disabled or nothing is changing. Don't complicate matters by
+ // talking about updates for this case
+
+ if (this._wasActive)
+ this.setAttribute("action", this._strings.getString("action.disabled"));
+ else
+ this.setAttribute("action", "");
+
+ this.removeAttribute("optionalupdate");
+ return;
+ }
+
+ if (this._addon.appDisabled && !this._install) {
+ // If the add-on is incompatible and there is no update available
+ // then it will remain disabled
+
+ this.setAttribute("action", this._strings.getString("action.incompatible"));
+
+ this.removeAttribute("optionalupdate");
+ return;
+ }
+
+ if (this._install) {
+ if (!AddonManager.shouldAutoUpdate(this._addon)) {
+ this.setAttribute("optionalupdate", "true");
+
+ // If manual updating for the add-on then display the right
+ // text depending on whether the update is required to make
+ // the add-on work or not
+ if (this._addon.appDisabled)
+ this.setAttribute("action", this._strings.getString("action.neededupdate"));
+ else
+ this.setAttribute("action", this._strings.getString("action.unneededupdate"));
+ return;
+ }
+
+ this.removeAttribute("optionalupdate");
+
+ // If the update is needed to make the add-on compatible then
+ // say so otherwise just say nothing about it
+ if (this._addon.appDisabled) {
+ this.setAttribute("action", this._strings.getString("action.autoupdate"));
+ return;
+ }
+ }
+ else {
+ this.removeAttribute("optionalupdate");
+ }
+
+ // If the add-on didn't used to be active and it now is (via a
+ // compatibility update) or it can be enabled then the action is to
+ // enable the add-on
+ if (!this._wasActive && (this._addon.isActive || this._addon.permissions & AddonManager.PERM_CAN_ENABLE)) {
+ this.setAttribute("action", this._strings.getString("action.enabled"));
+ return;
+ }
+
+ // In all other cases the add-on is simply remaining enabled
+ this.setAttribute("action", "");
+ ]]></body>
+ </method>
+
+ <method name="updateAction">
+ <body><![CDATA[
+ this.setActionMessage();
+ let installingUpdate = this._install &&
+ (AddonManager.shouldAutoUpdate(this._addon) ||
+ this._update.checked);
+
+ if (this._keep.checked && (!this._addon.appDisabled || installingUpdate))
+ this.setAttribute("active", "true");
+ else
+ this.removeAttribute("active");
+
+ gSelect.updateButtons();
+ ]]></body>
+ </method>
+
+ <method name="updateChanged">
+ <body><![CDATA[
+ this.updateAction();
+ ]]></body>
+ </method>
+
+ <method name="keepChanged">
+ <body><![CDATA[
+ this.updateAction();
+ ]]></body>
+ </method>
+
+ <method name="keep">
+ <body><![CDATA[
+ this._keep.checked = true;
+ this.keepChanged();
+ ]]></body>
+ </method>
+
+ <method name="disable">
+ <body><![CDATA[
+ this._keep.checked = false;
+ this.keepChanged();
+ ]]></body>
+ </method>
+
+ <method name="apply">
+ <body><![CDATA[
+ this._addon.userDisabled = !this._keep.checked;
+
+ if (!this._install || !this._keep.checked)
+ return;
+
+ if (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked)
+ this._install.install();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="addon-confirm">
+ <content>
+ <xul:image class="addon-icon" xbl:inherits="type"/>
+ <xul:label class="addon-name" xbl:inherits="xbl:text=name"/>
+ </content>
+ </binding>
+</bindings>
diff --git a/toolkit/mozapps/extensions/content/selectAddons.xul b/toolkit/mozapps/extensions/content/selectAddons.xul
new file mode 100644
index 000000000..0fa292ecf
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/selectAddons.xul
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/content/extensions/selectAddons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/selectAddons.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%updateDTD;
+%brandDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="&upgrade.style;" id="select-window">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/selectAddons.js"/>
+ <stringbundle id="strings" src="chrome://mozapps/locale/extensions/selectAddons.properties"/>
+
+ <deck id="view-deck" flex="1" align="stretch" pack="stretch">
+ <vbox id="checking" align="stretch">
+ <vbox flex="1">
+ <label id="checking-heading" class="heading">&checking.heading;</label>
+ </vbox>
+ <progressmeter id="checking-progress" class="progress" mode="undetermined"/>
+ <vbox flex="1">
+ <label id="checking-progress-label" class="progress-label">&checking.progress.label;</label>
+ </vbox>
+ </vbox>
+
+ <vbox id="select" align="stretch">
+ <label id="select-heading" class="heading">&select.heading;</label>
+
+ <description id="select-description">&select.description;</description>
+
+ <vbox id="select-list" align="stretch" flex="1">
+ <hbox id="select-header">
+ <hbox class="select-keep select-cell" style="&select.keep.style;">
+ <label value="&select.keep;"/>
+ </hbox>
+ <hbox class="select-icon select-cell"/>
+ <hbox id="heading-name" class="select-name select-cell" style="&select.name.style;">
+ <label value="&select.name;"/>
+ </hbox>
+ <hbox id="heading-action" class="select-action select-cell" style="&select.action.style;">
+ <label value="&select.action;"/>
+ </hbox>
+ <hbox class="select-source select-cell" flex="1">
+ <label value="&select.source;"/>
+ </hbox>
+ </hbox>
+
+ <scrollbox id="select-scrollbox" flex="1">
+ <grid id="select-grid" flex="1">
+ <columns>
+ <column style="&select.keep.style;"/>
+ <column/>
+ <column id="column-name"/>
+ <column id="column-action" class="select-action"/>
+ <column class="select-source" flex="1"/>
+ </columns>
+
+ <rows id="select-rows"/>
+ </grid>
+ </scrollbox>
+ </vbox>
+ </vbox>
+
+ <vbox id="confirm" align="stretch">
+ <label id="confirm-heading" class="heading">&confirm.heading;</label>
+
+ <description id="confirm-description">&confirm.description;</description>
+
+ <scrollbox id="confirm-scrollbox" flex="1">
+ <vbox id="disable-list" class="action-list" hidden="true">
+ <label class="action-header">&action.disable.heading;</label>
+ </vbox>
+
+ <vbox id="incompatible-list" class="action-list" hidden="true">
+ <label class="action-header">&action.incompatible.heading;</label>
+ </vbox>
+
+ <vbox id="update-list" class="action-list" hidden="true">
+ <label class="action-header">&action.update.heading;</label>
+ </vbox>
+
+ <vbox id="enable-list" class="action-list" hidden="true">
+ <label class="action-header">&action.enable.heading;</label>
+ </vbox>
+ </scrollbox>
+ </vbox>
+
+ <vbox id="update" align="stretch">
+ <vbox flex="1">
+ <label id="update-heading" class="heading">&update.heading;</label>
+ </vbox>
+ <progressmeter id="update-progress" class="progress" mode="undetermined"/>
+ <vbox flex="1">
+ <label id="update-progress-label" class="progress-label">&update.progress.label;</label>
+ </vbox>
+ </vbox>
+
+ <vbox id="errors">
+ <vbox flex="1">
+ <label id="errors-heading" class="heading">&errors.heading;</label>
+ </vbox>
+ <description id="errors-description" value="&errors.description;"/>
+ <spacer flex="1"/>
+ </vbox>
+ </deck>
+
+ <hbox id="footer" align="center">
+ <label id="footer-label" flex="1">&footer.label;</label>
+ <button id="cancel" label="&cancel.label;" oncommand="window.close()"/>
+ <button id="back" label="&back.label;" oncommand="gView.back()" hidden="true"/>
+ <button id="next" label="&next.label;" oncommand="gView.next()" hidden="true"/>
+ <button id="done" label="&done.label;" oncommand="gView.done()" hidden="true"/>
+ </hbox>
+
+</window>
diff --git a/toolkit/mozapps/extensions/content/setting.xml b/toolkit/mozapps/extensions/content/setting.xml
new file mode 100644
index 000000000..c4eae1fd3
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/setting.xml
@@ -0,0 +1,508 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE page [
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+]>
+
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="settings">
+ <content orient="vertical">
+ <xul:label class="settings-title" xbl:inherits="xbl:text=label" flex="1"/>
+ <children/>
+ </content>
+ </binding>
+
+ <binding id="setting-base">
+ <implementation>
+ <constructor><![CDATA[
+ this.preferenceChanged();
+
+ this.addEventListener("keypress", function(event) {
+ event.stopPropagation();
+ }, false);
+
+ if (this.usePref)
+ Services.prefs.addObserver(this.pref, this._observer, true);
+ ]]></constructor>
+
+ <field name="_observer"><![CDATA[({
+ _self: this,
+
+ QueryInterface: function(aIID) {
+ const Ci = Components.interfaces;
+ if (aIID.equals(Ci.nsIObserver) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE);
+ },
+
+ observe: function(aSubject, aTopic, aPrefName) {
+ if (aTopic != "nsPref:changed")
+ return;
+
+ if (this._self.pref == aPrefName)
+ this._self.preferenceChanged();
+ }
+ })]]>
+ </field>
+
+ <method name="fireEvent">
+ <parameter name="eventName"/>
+ <parameter name="funcStr"/>
+ <body>
+ <![CDATA[
+ let body = funcStr || this.getAttribute(eventName);
+ if (!body)
+ return;
+
+ try {
+ let event = document.createEvent("Events");
+ event.initEvent(eventName, true, true);
+ let f = new Function("event", body);
+ f.call(this, event);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ // Should be code to set the from the preference input.value
+ throw Components.Exception("No valueFromPreference implementation",
+ Components.results.NS_ERROR_NOT_IMPLEMENTED);
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ // Should be code to set the input.value from the preference
+ throw Components.Exception("No valueToPreference implementation",
+ Components.results.NS_ERROR_NOT_IMPLEMENTED);
+ ]]>
+ </body>
+ </method>
+
+ <method name="inputChanged">
+ <body>
+ <![CDATA[
+ if (this.usePref && !this._updatingInput) {
+ this.valueToPreference();
+ this.fireEvent("oninputchanged");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="preferenceChanged">
+ <body>
+ <![CDATA[
+ if (this.usePref) {
+ this._updatingInput = true;
+ try {
+ this.valueFromPreference();
+ this.fireEvent("onpreferencechanged");
+ } catch (e) {}
+ this._updatingInput = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="usePref" readonly="true" onget="return this.hasAttribute('pref');"/>
+ <property name="pref" readonly="true" onget="return this.getAttribute('pref');"/>
+ <property name="type" readonly="true" onget="return this.getAttribute('type');"/>
+ <property name="value" onget="return this.input.value;" onset="return this.input.value = val;"/>
+
+ <field name="_updatingInput">false</field>
+ <field name="input">document.getAnonymousElementByAttribute(this, "anonid", "input");</field>
+ <field name="settings">
+ this.parentNode.localName == "settings" ? this.parentNode : null;
+ </field>
+ </implementation>
+ </binding>
+
+ <binding id="setting-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ <xul:label class="preferences-learnmore text-link"
+ onclick="document.getBindingParent(this).openLearnMore()">&setting.learnmore;</xul:label>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:checkbox anonid="input" xbl:inherits="disabled,onlabel,offlabel,label=checkboxlabel" oncommand="inputChanged();"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getBoolPref(this.pref);
+ this.value = this.inverted ? !val : val;
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ let val = this.value;
+ Services.prefs.setBoolPref(this.pref, this.inverted ? !val : val);
+ ]]>
+ </body>
+ </method>
+
+ <property name="value" onget="return this.input.checked;" onset="return this.input.setChecked(val);"/>
+ <property name="inverted" readonly="true" onget="return this.getAttribute('inverted');"/>
+
+ <method name="openLearnMore">
+ <body>
+ <![CDATA[
+ window.open(this.getAttribute("learnmore"), "_blank");
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-boolint" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getIntPref(this.pref);
+ this.value = (val == this.getAttribute("on"));
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setIntPref(this.pref, this.getAttribute(this.value ? "on" : "off"));
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-localized-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString).data;
+ if(this.inverted) val = !val;
+ this.value = (val == "true");
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ let val = this.value;
+ if(this.inverted) val = !val;
+ let pref = Components.classes["@mozilla.org/pref-localizedstring;1"].createInstance(Components.interfaces.nsIPrefLocalizedString);
+ pref.data = this.inverted ? (!val).toString() : val.toString();
+ Services.prefs.setComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString, pref);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-integer" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:textbox type="number" anonid="input" oninput="inputChanged();" onchange="inputChanged();"
+ xbl:inherits="disabled,emptytext,min,max,increment,hidespinbuttons,wraparound,size"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getIntPref(this.pref);
+ this.value = val;
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setIntPref(this.pref, this.value);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-control" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <children/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="setting-string" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:textbox anonid="input" flex="1" oninput="inputChanged();"
+ xbl:inherits="disabled,emptytext,type=inputtype,min,max,increment,hidespinbuttons,decimalplaces,wraparound"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ const nsISupportsString = Components.interfaces.nsISupportsString;
+ this.value = Services.prefs.getComplexValue(this.pref, nsISupportsString).data;
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ const nsISupportsString = Components.interfaces.nsISupportsString;
+ let iss = Components.classes["@mozilla.org/supports-string;1"].createInstance(nsISupportsString);
+ iss.data = this.value;
+ Services.prefs.setComplexValue(this.pref, nsISupportsString, iss);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-color" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:colorpicker type="button" anonid="input" xbl:inherits="disabled" onchange="document.getBindingParent(this).inputChanged();"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ // We must wait for the colorpicker's binding to be applied before setting the value
+ if (!this.input.color)
+ this.input.initialize();
+ this.value = Services.prefs.getCharPref(this.pref);
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setCharPref(this.pref, this.value);
+ ]]>
+ </body>
+ </method>
+
+ <property name="value" onget="return this.input.color;" onset="return this.input.color = val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="setting-path" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:button type="button" anonid="button" label="&settings.path.button.label;" xbl:inherits="disabled" oncommand="showPicker();"/>
+ <xul:label anonid="input" flex="1" crop="center" xbl:inherits="disabled"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="showPicker">
+ <body>
+ <![CDATA[
+ var filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ filePicker.init(window, this.getAttribute("title"),
+ this.type == "file" ? Ci.nsIFilePicker.modeOpen : Ci.nsIFilePicker.modeGetFolder);
+ if (this.value) {
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(this.value);
+ filePicker.displayDirectory = this.type == "file" ? file.parent : file;
+ if (this.type == "file") {
+ filePicker.defaultString = file.leafName;
+ }
+ } catch (e) {}
+ }
+ if (filePicker.show() != Ci.nsIFilePicker.returnCancel) {
+ this.value = filePicker.file.path;
+ this.inputChanged();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ this.value = Services.prefs.getCharPref(this.pref);
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setCharPref(this.pref, this.value);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_value"></field>
+
+ <property name="value">
+ <getter>
+ <![CDATA[
+ return this._value;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this._value = val;
+ let label = "";
+ if (val) {
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(val);
+ label = this.hasAttribute("fullpath") ? file.path : file.leafName;
+ } catch (e) {}
+ }
+ this.input.tooltipText = val;
+ return this.input.value = label;
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="setting-multi" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <children includes="radiogroup|menulist"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.control.addEventListener("command", this.inputChanged.bind(this), false);
+ ]]>
+ </constructor>
+
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val;
+ switch (Services.prefs.getPrefType(this.pref)) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ val = Services.prefs.getCharPref(this.pref);
+ break;
+ case Ci.nsIPrefBranch.PREF_INT:
+ val = Services.prefs.getIntPref(this.pref);
+ break;
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ val = Services.prefs.getBoolPref(this.pref).toString();
+ break;
+ default:
+ return;
+ }
+
+ if ("itemCount" in this.control) {
+ for (let i = 0; i < this.control.itemCount; i++) {
+ if (this.control.getItemAtIndex(i).value == val) {
+ this.control.selectedIndex = i;
+ break;
+ }
+ }
+ } else {
+ this.control.setAttribute("value", val);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ // We might not have a pref already set, so we guess the type from the value attribute
+ let val = this.control.selectedItem.value;
+ if (val == "true" || val == "false")
+ Services.prefs.setBoolPref(this.pref, val == "true");
+ else if (/^-?\d+$/.test(val))
+ Services.prefs.setIntPref(this.pref, val);
+ else
+ Services.prefs.setCharPref(this.pref, val);
+ ]]>
+ </body>
+ </method>
+
+ <field name="control">this.getElementsByTagName(this.getAttribute("type") == "radio" ? "radiogroup" : "menulist")[0];</field>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/toolkit/mozapps/extensions/content/update.js b/toolkit/mozapps/extensions/content/update.js
new file mode 100644
index 000000000..4e44b4f5e
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/update.js
@@ -0,0 +1,663 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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/. */
+
+// This UI is only opened from the Extension Manager when the app is upgraded.
+
+"use strict";
+
+const PREF_UPDATE_EXTENSIONS_ENABLED = "extensions.update.enabled";
+const PREF_XPINSTALL_ENABLED = "xpinstall.enabled";
+
+// timeout (in milliseconds) to wait for response to the metadata ping
+const METADATA_TIMEOUT = 30000;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
+let logger = null;
+
+var gUpdateWizard = {
+ // When synchronizing app compatibility info this contains all installed
+ // add-ons. When checking for compatible versions this contains only
+ // incompatible add-ons.
+ addons: [],
+ // Contains a Set of IDs for add-on that were disabled by the application update.
+ affectedAddonIDs: null,
+ // The add-ons that we found updates available for
+ addonsToUpdate: [],
+ shouldSuggestAutoChecking: false,
+ shouldAutoCheck: false,
+ xpinstallEnabled: true,
+ xpinstallLocked: false,
+ // cached AddonInstall entries for add-ons we might want to update,
+ // keyed by add-on ID
+ addonInstalls: new Map(),
+ shuttingDown: false,
+ // Count the add-ons disabled by this update, enabled/disabled by
+ // metadata checks, and upgraded.
+ disabled: 0,
+ metadataEnabled: 0,
+ metadataDisabled: 0,
+ upgraded: 0,
+ upgradeFailed: 0,
+ upgradeDeclined: 0,
+
+ init: function gUpdateWizard_init()
+ {
+ logger = Log.repository.getLogger("addons.update-dialog");
+ // XXX could we pass the addons themselves rather than the IDs?
+ this.affectedAddonIDs = new Set(window.arguments[0]);
+
+ try {
+ this.shouldSuggestAutoChecking =
+ !Services.prefs.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED);
+ }
+ catch (e) {
+ }
+
+ try {
+ this.xpinstallEnabled = Services.prefs.getBoolPref(PREF_XPINSTALL_ENABLED);
+ this.xpinstallLocked = Services.prefs.prefIsLocked(PREF_XPINSTALL_ENABLED);
+ }
+ catch (e) {
+ }
+
+ if (Services.io.offline)
+ document.documentElement.currentPage = document.getElementById("offline");
+ else
+ document.documentElement.currentPage = document.getElementById("versioninfo");
+ },
+
+ onWizardFinish: function gUpdateWizard_onWizardFinish ()
+ {
+ if (this.shouldSuggestAutoChecking)
+ Services.prefs.setBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED, this.shouldAutoCheck);
+ },
+
+ _setUpButton: function gUpdateWizard_setUpButton(aButtonID, aButtonKey, aDisabled)
+ {
+ var strings = document.getElementById("updateStrings");
+ var button = document.documentElement.getButton(aButtonID);
+ if (aButtonKey) {
+ button.label = strings.getString(aButtonKey);
+ try {
+ button.setAttribute("accesskey", strings.getString(aButtonKey + "Accesskey"));
+ }
+ catch (e) {
+ }
+ }
+ button.disabled = aDisabled;
+ },
+
+ setButtonLabels: function gUpdateWizard_setButtonLabels(aBackButton, aBackButtonIsDisabled,
+ aNextButton, aNextButtonIsDisabled,
+ aCancelButton, aCancelButtonIsDisabled)
+ {
+ this._setUpButton("back", aBackButton, aBackButtonIsDisabled);
+ this._setUpButton("next", aNextButton, aNextButtonIsDisabled);
+ this._setUpButton("cancel", aCancelButton, aCancelButtonIsDisabled);
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Update Errors
+ errorItems: [],
+
+ checkForErrors: function gUpdateWizard_checkForErrors(aElementIDToShow)
+ {
+ if (this.errorItems.length > 0)
+ document.getElementById(aElementIDToShow).hidden = false;
+ },
+
+ onWizardClose: function gUpdateWizard_onWizardClose(aEvent)
+ {
+ return this.onWizardCancel();
+ },
+
+ onWizardCancel: function gUpdateWizard_onWizardCancel()
+ {
+ gUpdateWizard.shuttingDown = true;
+ // Allow add-ons to continue downloading and installing
+ // in the background, though some may require a later restart
+ // Pages that are waiting for user input go into the background
+ // on cancel
+ if (gMismatchPage.waiting) {
+ logger.info("Dialog closed in mismatch page");
+ if (gUpdateWizard.addonInstalls.size > 0) {
+ gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]);
+ }
+ return true;
+ }
+
+ // Pages that do asynchronous things will just keep running and check
+ // gUpdateWizard.shuttingDown to trigger background behaviour
+ if (!gInstallingPage.installing) {
+ logger.info("Dialog closed while waiting for updated compatibility information");
+ }
+ else {
+ logger.info("Dialog closed while downloading and installing updates");
+ }
+ return true;
+ }
+};
+
+var gOfflinePage = {
+ onPageAdvanced: function gOfflinePage_onPageAdvanced()
+ {
+ Services.io.offline = false;
+ return true;
+ },
+
+ toggleOffline: function gOfflinePage_toggleOffline()
+ {
+ var nextbtn = document.documentElement.getButton("next");
+ nextbtn.disabled = !nextbtn.disabled;
+ }
+}
+
+// Addon listener to count addons enabled/disabled by metadata checks
+let listener = {
+ onDisabled: function listener_onDisabled(aAddon) {
+ gUpdateWizard.affectedAddonIDs.add(aAddon.id);
+ gUpdateWizard.metadataDisabled++;
+ },
+ onEnabled: function listener_onEnabled(aAddon) {
+ gUpdateWizard.affectedAddonIDs.delete(aAddon.id);
+ gUpdateWizard.metadataEnabled++;
+ }
+};
+
+var gVersionInfoPage = {
+ _completeCount: 0,
+ _totalCount: 0,
+ _versionInfoDone: false,
+ onPageShow: Task.async(function* gVersionInfoPage_onPageShow() {
+ gUpdateWizard.setButtonLabels(null, true,
+ "nextButtonText", true,
+ "cancelButtonText", false);
+
+ gUpdateWizard.disabled = gUpdateWizard.affectedAddonIDs.size;
+
+ // Ensure compatibility overrides are up to date before checking for
+ // individual addon updates.
+ AddonManager.addAddonListener(listener);
+ if (AddonRepository.isMetadataStale()) {
+ // Do the metadata ping, listening for any newly enabled/disabled add-ons.
+ yield AddonRepository.repopulateCache(METADATA_TIMEOUT);
+ if (gUpdateWizard.shuttingDown) {
+ logger.debug("repopulateCache completed after dialog closed");
+ }
+ }
+ // Fetch the add-ons that are still affected by this update.
+ let idlist = [id for (id of gUpdateWizard.affectedAddonIDs)];
+ if (idlist.length < 1) {
+ gVersionInfoPage.onAllUpdatesFinished();
+ return;
+ }
+
+ logger.debug("Fetching affected addons " + idlist.toSource());
+ let fetchedAddons = yield new Promise((resolve, reject) =>
+ AddonManager.getAddonsByIDs(idlist, resolve));
+ // We shouldn't get nulls here, but let's be paranoid...
+ gUpdateWizard.addons = [a for (a of fetchedAddons) if (a)];
+ if (gUpdateWizard.addons.length < 1) {
+ gVersionInfoPage.onAllUpdatesFinished();
+ return;
+ }
+
+ gVersionInfoPage._totalCount = gUpdateWizard.addons.length;
+
+ for (let addon of gUpdateWizard.addons) {
+ logger.debug("VersionInfo Finding updates for ${id}", addon);
+ addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+ }
+ }),
+
+ onAllUpdatesFinished: function gVersionInfoPage_onAllUpdatesFinished() {
+ AddonManager.removeAddonListener(listener);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_disabled",
+ gUpdateWizard.disabled);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_enabled",
+ gUpdateWizard.metadataEnabled);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_disabled",
+ gUpdateWizard.metadataDisabled);
+ // Record 0 for these here in case we exit early; values will be replaced
+ // later if we actually upgrade any.
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded", 0);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed", 0);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined", 0);
+ // Filter out any add-ons that are now enabled.
+ logger.debug("VersionInfo updates finished: found " +
+ [addon.id + ":" + addon.appDisabled for (addon of gUpdateWizard.addons)].toSource());
+ let filteredAddons = [];
+ for (let a of gUpdateWizard.addons) {
+ if (a.appDisabled) {
+ logger.debug("Continuing with add-on " + a.id);
+ filteredAddons.push(a);
+ }
+ else if (gUpdateWizard.addonInstalls.has(a.id)) {
+ gUpdateWizard.addonInstalls.get(a.id).cancel();
+ gUpdateWizard.addonInstalls.delete(a.id);
+ }
+ }
+ gUpdateWizard.addons = filteredAddons;
+
+ if (gUpdateWizard.shuttingDown) {
+ // jump directly to updating auto-update add-ons in the background
+ if (gUpdateWizard.addonInstalls.size > 0) {
+ gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]);
+ }
+ return;
+ }
+
+ if (filteredAddons.length > 0) {
+ if (!gUpdateWizard.xpinstallEnabled && gUpdateWizard.xpinstallLocked) {
+ document.documentElement.currentPage = document.getElementById("adminDisabled");
+ return;
+ }
+ document.documentElement.currentPage = document.getElementById("mismatch");
+ }
+ else {
+ logger.info("VersionInfo: No updates require further action");
+ // VersionInfo compatibility updates resolved all compatibility problems,
+ // close this window and continue starting the application...
+ //XXX Bug 314754 - We need to use setTimeout to close the window due to
+ // the EM using xmlHttpRequest when checking for updates.
+ setTimeout(close, 0);
+ }
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // UpdateListener
+ onUpdateFinished: function gVersionInfoPage_onUpdateFinished(aAddon, status) {
+ ++this._completeCount;
+
+ if (status != AddonManager.UPDATE_STATUS_NO_ERROR) {
+ logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
+ " failed for " + aAddon.id + ": " + status);
+ gUpdateWizard.errorItems.push(aAddon);
+ }
+ else {
+ logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
+ " finished for " + aAddon.id);
+ }
+
+ // If we're not in the background, just make a list of add-ons that have
+ // updates available
+ if (!gUpdateWizard.shuttingDown) {
+ // If we're still in the update check window and the add-on is now active
+ // then it won't have been disabled by startup
+ if (aAddon.active) {
+ AddonManagerPrivate.removeStartupChange(AddonManager.STARTUP_CHANGE_DISABLED, aAddon.id);
+ gUpdateWizard.metadataEnabled++;
+ }
+
+ // Update the status text and progress bar
+ var updateStrings = document.getElementById("updateStrings");
+ var statusElt = document.getElementById("versioninfo.status");
+ var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
+ statusElt.setAttribute("value", statusString);
+
+ // Update the status text and progress bar
+ var progress = document.getElementById("versioninfo.progress");
+ progress.mode = "normal";
+ progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
+ }
+
+ if (this._completeCount == this._totalCount)
+ this.onAllUpdatesFinished();
+ },
+
+ onUpdateAvailable: function gVersionInfoPage_onUpdateAvailable(aAddon, aInstall) {
+ logger.debug("VersionInfo got an install for " + aAddon.id + ": " + aAddon.version);
+ gUpdateWizard.addonInstalls.set(aAddon.id, aInstall);
+ },
+};
+
+var gMismatchPage = {
+ waiting: false,
+
+ onPageShow: function gMismatchPage_onPageShow()
+ {
+ gMismatchPage.waiting = true;
+ gUpdateWizard.setButtonLabels(null, true,
+ "mismatchCheckNow", false,
+ "mismatchDontCheck", false);
+ document.documentElement.getButton("next").focus();
+
+ var incompatible = document.getElementById("mismatch.incompatible");
+ for (let addon of gUpdateWizard.addons) {
+ var listitem = document.createElement("listitem");
+ listitem.setAttribute("label", addon.name + " " + addon.version);
+ incompatible.appendChild(listitem);
+ }
+ }
+};
+
+var gUpdatePage = {
+ _totalCount: 0,
+ _completeCount: 0,
+ onPageShow: function gUpdatePage_onPageShow()
+ {
+ gMismatchPage.waiting = false;
+ gUpdateWizard.setButtonLabels(null, true,
+ "nextButtonText", true,
+ "cancelButtonText", false);
+ document.documentElement.getButton("next").focus();
+
+ gUpdateWizard.errorItems = [];
+
+ this._totalCount = gUpdateWizard.addons.length;
+ for (let addon of gUpdateWizard.addons) {
+ logger.debug("UpdatePage requesting update for " + addon.id);
+ // Redundant call to find updates again here when we already got them
+ // in the VersionInfo page: https://bugzilla.mozilla.org/show_bug.cgi?id=960597
+ addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+ }
+ },
+
+ onAllUpdatesFinished: function gUpdatePage_onAllUpdatesFinished() {
+ if (gUpdateWizard.shuttingDown)
+ return;
+
+ var nextPage = document.getElementById("noupdates");
+ if (gUpdateWizard.addonsToUpdate.length > 0)
+ nextPage = document.getElementById("found");
+ document.documentElement.currentPage = nextPage;
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // UpdateListener
+ onUpdateAvailable: function gUpdatePage_onUpdateAvailable(aAddon, aInstall) {
+ logger.debug("UpdatePage got an update for " + aAddon.id + ": " + aAddon.version);
+ gUpdateWizard.addonsToUpdate.push(aInstall);
+ },
+
+ onUpdateFinished: function gUpdatePage_onUpdateFinished(aAddon, status) {
+ if (status != AddonManager.UPDATE_STATUS_NO_ERROR)
+ gUpdateWizard.errorItems.push(aAddon);
+
+ ++this._completeCount;
+
+ if (!gUpdateWizard.shuttingDown) {
+ // Update the status text and progress bar
+ var updateStrings = document.getElementById("updateStrings");
+ var statusElt = document.getElementById("checking.status");
+ var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
+ statusElt.setAttribute("value", statusString);
+
+ var progress = document.getElementById("checking.progress");
+ progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
+ }
+
+ if (this._completeCount == this._totalCount)
+ this.onAllUpdatesFinished()
+ },
+};
+
+var gFoundPage = {
+ onPageShow: function gFoundPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true,
+ "installButtonText", false,
+ null, false);
+
+ var foundUpdates = document.getElementById("found.updates");
+ var itemCount = gUpdateWizard.addonsToUpdate.length;
+ for (let install of gUpdateWizard.addonsToUpdate) {
+ let listItem = foundUpdates.appendItem(install.name + " " + install.version);
+ listItem.setAttribute("type", "checkbox");
+ listItem.setAttribute("checked", "true");
+ listItem.install = install;
+ }
+
+ if (!gUpdateWizard.xpinstallEnabled) {
+ document.getElementById("xpinstallDisabledAlert").hidden = false;
+ document.getElementById("enableXPInstall").focus();
+ document.documentElement.getButton("next").disabled = true;
+ }
+ else {
+ document.documentElement.getButton("next").focus();
+ document.documentElement.getButton("next").disabled = false;
+ }
+ },
+
+ toggleXPInstallEnable: function gFoundPage_toggleXPInstallEnable(aEvent)
+ {
+ var enabled = aEvent.target.checked;
+ gUpdateWizard.xpinstallEnabled = enabled;
+ var pref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ pref.setBoolPref(PREF_XPINSTALL_ENABLED, enabled);
+ this.updateNextButton();
+ },
+
+ updateNextButton: function gFoundPage_updateNextButton()
+ {
+ if (!gUpdateWizard.xpinstallEnabled) {
+ document.documentElement.getButton("next").disabled = true;
+ return;
+ }
+
+ var oneChecked = false;
+ var foundUpdates = document.getElementById("found.updates");
+ var updates = foundUpdates.getElementsByTagName("listitem");
+ for (let update of updates) {
+ if (!update.checked)
+ continue;
+ oneChecked = true;
+ break;
+ }
+
+ gUpdateWizard.setButtonLabels(null, true,
+ "installButtonText", true,
+ null, false);
+ document.getElementById("found").setAttribute("next", "installing");
+ document.documentElement.getButton("next").disabled = !oneChecked;
+ }
+};
+
+var gInstallingPage = {
+ _installs : [],
+ _errors : [],
+ _strings : null,
+ _currentInstall : -1,
+ _installing : false,
+
+ // Initialize fields we need for installing and tracking progress,
+ // and start iterating through the installations
+ startInstalls: function gInstallingPage_startInstalls(aInstallList) {
+ if (!gUpdateWizard.xpinstallEnabled) {
+ return;
+ }
+
+ logger.debug("Start installs for "
+ + [i.existingAddon.id for (i of aInstallList)].toSource());
+ this._errors = [];
+ this._installs = aInstallList;
+ this._installing = true;
+ this.startNextInstall();
+ },
+
+ onPageShow: function gInstallingPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true,
+ "nextButtonText", true,
+ null, true);
+
+ var foundUpdates = document.getElementById("found.updates");
+ var updates = foundUpdates.getElementsByTagName("listitem");
+ let toInstall = [];
+ for (let update of updates) {
+ if (!update.checked) {
+ logger.info("User chose to cancel update of " + update.label);
+ gUpdateWizard.upgradeDeclined++;
+ update.install.cancel();
+ continue;
+ }
+ toInstall.push(update.install);
+ }
+ this._strings = document.getElementById("updateStrings");
+
+ this.startInstalls(toInstall);
+ },
+
+ startNextInstall: function gInstallingPage_startNextInstall() {
+ if (this._currentInstall >= 0) {
+ this._installs[this._currentInstall].removeListener(this);
+ }
+
+ this._currentInstall++;
+
+ if (this._installs.length == this._currentInstall) {
+ Services.obs.notifyObservers(null, "TEST:all-updates-done", null);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded",
+ gUpdateWizard.upgraded);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed",
+ gUpdateWizard.upgradeFailed);
+ AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined",
+ gUpdateWizard.upgradeDeclined);
+ this._installing = false;
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var nextPage = this._errors.length > 0 ? "installerrors" : "finished";
+ document.getElementById("installing").setAttribute("next", nextPage);
+ document.documentElement.advance();
+ return;
+ }
+
+ let install = this._installs[this._currentInstall];
+
+ if (gUpdateWizard.shuttingDown && !AddonManager.shouldAutoUpdate(install.existingAddon)) {
+ logger.debug("Don't update " + install.existingAddon.id + " in background");
+ gUpdateWizard.upgradeDeclined++;
+ install.cancel();
+ this.startNextInstall();
+ return;
+ }
+ install.addListener(this);
+ install.install();
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // InstallListener
+ onDownloadStarted: function gInstallingPage_onDownloadStarted(aInstall) {
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var strings = document.getElementById("updateStrings");
+ var label = strings.getFormattedString("downloadingPrefix", [aInstall.name]);
+ var actionItem = document.getElementById("actionItem");
+ actionItem.value = label;
+ },
+
+ onDownloadProgress: function gInstallingPage_onDownloadProgress(aInstall) {
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var downloadProgress = document.getElementById("downloadProgress");
+ downloadProgress.value = Math.ceil(100 * aInstall.progress / aInstall.maxProgress);
+ },
+
+ onDownloadEnded: function gInstallingPage_onDownloadEnded(aInstall) {
+ },
+
+ onDownloadFailed: function gInstallingPage_onDownloadFailed(aInstall) {
+ this._errors.push(aInstall);
+
+ gUpdateWizard.upgradeFailed++;
+ this.startNextInstall();
+ },
+
+ onInstallStarted: function gInstallingPage_onInstallStarted(aInstall) {
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var strings = document.getElementById("updateStrings");
+ var label = strings.getFormattedString("installingPrefix", [aInstall.name]);
+ var actionItem = document.getElementById("actionItem");
+ actionItem.value = label;
+ },
+
+ onInstallEnded: function gInstallingPage_onInstallEnded(aInstall, aAddon) {
+ if (!gUpdateWizard.shuttingDown) {
+ // Remember that this add-on was updated during startup
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
+ aAddon.id);
+ }
+
+ gUpdateWizard.upgraded++;
+ this.startNextInstall();
+ },
+
+ onInstallFailed: function gInstallingPage_onInstallFailed(aInstall) {
+ this._errors.push(aInstall);
+
+ gUpdateWizard.upgradeFailed++;
+ this.startNextInstall();
+ }
+};
+
+var gInstallErrorsPage = {
+ onPageShow: function gInstallErrorsPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
+ document.documentElement.getButton("finish").focus();
+ },
+};
+
+// Displayed when there are incompatible add-ons and the xpinstall.enabled
+// pref is false and locked.
+var gAdminDisabledPage = {
+ onPageShow: function gAdminDisabledPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true,
+ "cancelButtonText", true);
+ document.documentElement.getButton("finish").focus();
+ }
+};
+
+// Displayed when selected add-on updates have been installed without error.
+// There can still be add-ons that are not compatible and don't have an update.
+var gFinishedPage = {
+ onPageShow: function gFinishedPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
+ document.documentElement.getButton("finish").focus();
+
+ if (gUpdateWizard.shouldSuggestAutoChecking) {
+ document.getElementById("finishedCheckDisabled").hidden = false;
+ gUpdateWizard.shouldAutoCheck = true;
+ }
+ else
+ document.getElementById("finishedCheckEnabled").hidden = false;
+
+ document.documentElement.getButton("finish").focus();
+ }
+};
+
+// Displayed when there are incompatible add-ons and there are no available
+// updates.
+var gNoUpdatesPage = {
+ onPageShow: function gNoUpdatesPage_onPageLoad(aEvent)
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
+ if (gUpdateWizard.shouldSuggestAutoChecking) {
+ document.getElementById("noupdatesCheckDisabled").hidden = false;
+ gUpdateWizard.shouldAutoCheck = true;
+ }
+ else
+ document.getElementById("noupdatesCheckEnabled").hidden = false;
+
+ gUpdateWizard.checkForErrors("updateCheckErrorNotFound");
+ document.documentElement.getButton("finish").focus();
+ }
+};
diff --git a/toolkit/mozapps/extensions/content/update.xul b/toolkit/mozapps/extensions/content/update.xul
new file mode 100644
index 000000000..094651fa5
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/update.xul
@@ -0,0 +1,196 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# 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/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/update.css" type="text/css"?>
+
+<!DOCTYPE wizard [
+<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/update.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%updateDTD;
+%brandDTD;
+]>
+
+<wizard id="updateWizard"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&updateWizard.title;"
+ windowtype="Addons:Compatibility"
+ branded="true"
+ onload="gUpdateWizard.init();"
+ onwizardfinish="gUpdateWizard.onWizardFinish();"
+ onwizardcancel="return gUpdateWizard.onWizardCancel();"
+ onclose="return gUpdateWizard.onWizardClose(event);"
+ buttons="accept,cancel">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/update.js"/>
+
+ <stringbundleset id="updateSet">
+ <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="updateStrings" src="chrome://mozapps/locale/extensions/update.properties"/>
+ </stringbundleset>
+
+ <wizardpage id="dummy" pageid="dummy"/>
+
+ <wizardpage id="offline" pageid="offline" next="versioninfo"
+ label="&offline.title;"
+ onpageadvanced="return gOfflinePage.onPageAdvanced();">
+ <description>&offline.description;</description>
+ <checkbox id="toggleOffline"
+ checked="true"
+ label="&offline.toggleOffline.label;"
+ accesskey="&offline.toggleOffline.accesskey;"
+ oncommand="gOfflinePage.toggleOffline();"/>
+ </wizardpage>
+
+ <wizardpage id="versioninfo" pageid="versioninfo" next="mismatch"
+ label="&versioninfo.wizard.title;"
+ onpageshow="gVersionInfoPage.onPageShow();">
+ <label>&versioninfo.top.label;</label>
+ <separator class="thin"/>
+ <progressmeter id="versioninfo.progress" mode="undetermined"/>
+ <hbox align="center">
+ <image id="versioninfo.throbber" class="throbber"/>
+ <label flex="1" id="versioninfo.status" crop="right">&versioninfo.waiting;</label>
+ </hbox>
+ <separator/>
+ </wizardpage>
+
+ <wizardpage id="mismatch" pageid="mismatch" next="checking"
+ label="&mismatch.win.title;"
+ onpageshow="gMismatchPage.onPageShow();">
+ <label>&mismatch.top.label;</label>
+ <separator class="thin"/>
+ <listbox id="mismatch.incompatible" flex="1"/>
+ <separator class="thin"/>
+ <label>&mismatch.bottom.label;</label>
+ </wizardpage>
+
+ <wizardpage id="checking" pageid="checking" next="noupdates"
+ label="&checking.wizard.title;"
+ onpageshow="gUpdatePage.onPageShow();">
+ <label>&checking.top.label;</label>
+ <separator class="thin"/>
+ <progressmeter id="checking.progress"/>
+ <hbox align="center">
+ <image id="checking.throbber" class="throbber"/>
+ <label id="checking.status" flex="1" crop="right">&checking.status;</label>
+ </hbox>
+ </wizardpage>
+
+ <wizardpage id="noupdates" pageid="noupdates"
+ label="&noupdates.wizard.title;"
+ onpageshow="gNoUpdatesPage.onPageShow();">
+ <description>&noupdates.intro.desc;</description>
+ <separator class="thin"/>
+ <hbox id="updateCheckErrorNotFound" class="alertBox" hidden="true" align="top">
+ <image id="alert"/>
+ <description flex="1">&noupdates.error.desc;</description>
+ </hbox>
+ <separator class="thin"/>
+ <description id="noupdatesCheckEnabled" hidden="true">
+ &noupdates.checkEnabled.desc;
+ </description>
+ <vbox id="noupdatesCheckDisabled" hidden="true">
+ <description>&finished.checkDisabled.desc;</description>
+ <checkbox label="&enableChecking.label;" checked="true"
+ oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
+ </vbox>
+ <separator flex="1"/>
+#ifndef XP_MACOSX
+ <label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
+ <separator class="thin"/>
+ </wizardpage>
+
+ <wizardpage id="found" pageid="found" next="installing"
+ label="&found.wizard.title;"
+ onpageshow="gFoundPage.onPageShow();">
+ <label>&found.top.label;</label>
+ <separator class="thin"/>
+ <listbox id="found.updates" flex="1" seltype="multiple"
+ onclick="gFoundPage.updateNextButton();"/>
+ <separator class="thin"/>
+ <vbox align="left" id="xpinstallDisabledAlert" hidden="true">
+ <description>&found.disabledXPinstall.label;</description>
+ <checkbox label="&found.enableXPInstall.label;"
+ id="enableXPInstall"
+ accesskey="&found.enableXPInstall.accesskey;"
+ oncommand="gFoundPage.toggleXPInstallEnable(event);"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="installing" pageid="installing" next="finished"
+ label="&installing.wizard.title;"
+ onpageshow="gInstallingPage.onPageShow();">
+ <label>&installing.top.label;</label>
+ <progressmeter id="downloadProgress"/>
+ <hbox align="center">
+ <image id="installing.throbber" class="throbber"/>
+ <label id="actionItem" flex="1" crop="right"/>
+ </hbox>
+ <separator/>
+ </wizardpage>
+
+ <wizardpage id="installerrors" pageid="installerrors"
+ label="&installerrors.wizard.title;"
+ onpageshow="gInstallErrorsPage.onPageShow();">
+ <hbox align="top" class="alertBox">
+ <description flex="1">&installerrors.intro.label;</description>
+ </hbox>
+ <separator flex="1"/>
+#ifndef XP_MACOSX
+ <label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
+ <separator class="thin"/>
+ </wizardpage>
+
+ <wizardpage id="adminDisabled" pageid="adminDisabled"
+ label="&adminDisabled.wizard.title;"
+ onpageshow="gAdminDisabledPage.onPageShow();">
+ <separator/>
+ <hbox class="alertBox" align="top">
+ <image id="alert"/>
+ <description flex="1">&adminDisabled.warning.label;</description>
+ </hbox>
+ <separator flex="1"/>
+#ifndef XP_MACOSX
+ <label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
+ <separator class="thin"/>
+ </wizardpage>
+
+ <wizardpage id="finished" pageid="finished"
+ label="&finished.wizard.title;"
+ onpageshow="gFinishedPage.onPageShow();">
+
+ <label>&finished.top.label;</label>
+ <separator/>
+ <description id="finishedCheckEnabled" hidden="true">
+ &finished.checkEnabled.desc;
+ </description>
+ <vbox id="finishedCheckDisabled" hidden="true">
+ <description>&finished.checkDisabled.desc;</description>
+ <checkbox label="&enableChecking.label;" checked="true"
+ oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
+ </vbox>
+ <separator flex="1"/>
+#ifndef XP_MACOSX
+ <label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
+ <separator class="thin"/>
+ </wizardpage>
+
+</wizard>
+
diff --git a/toolkit/mozapps/extensions/content/updateinfo.xsl b/toolkit/mozapps/extensions/content/updateinfo.xsl
new file mode 100644
index 000000000..5fcccd6d7
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/updateinfo.xsl
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+
+<xsl:stylesheet version="1.0" xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <!-- Any elements not otherwise specified will be stripped but the contents
+ will be displayed. All attributes are stripped from copied elements. -->
+
+ <!-- Block these elements and their contents -->
+ <xsl:template match="xhtml:head|xhtml:script|xhtml:style">
+ </xsl:template>
+
+ <!-- Allowable styling elements -->
+ <xsl:template match="xhtml:b|xhtml:i|xhtml:em|xhtml:strong|xhtml:u|xhtml:q|xhtml:sub|xhtml:sup|xhtml:code">
+ <xsl:copy><xsl:apply-templates/></xsl:copy>
+ </xsl:template>
+
+ <!-- Allowable block formatting elements -->
+ <xsl:template match="xhtml:h1|xhtml:h2|xhtml:h3|xhtml:p|xhtml:div|xhtml:blockquote|xhtml:pre">
+ <xsl:copy><xsl:apply-templates/></xsl:copy>
+ </xsl:template>
+
+ <!-- Allowable list formatting elements -->
+ <xsl:template match="xhtml:ul|xhtml:ol|xhtml:li|xhtml:dl|xhtml:dt|xhtml:dd">
+ <xsl:copy><xsl:apply-templates/></xsl:copy>
+ </xsl:template>
+
+ <!-- These elements are copied and their contents dropped -->
+ <xsl:template match="xhtml:br|xhtml:hr">
+ <xsl:copy/>
+ </xsl:template>
+
+ <!-- The root document -->
+ <xsl:template match="/">
+ <xhtml:body><xsl:apply-templates/></xhtml:body>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/toolkit/mozapps/extensions/content/xpinstallConfirm.css b/toolkit/mozapps/extensions/content/xpinstallConfirm.css
new file mode 100644
index 000000000..583facfec
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/xpinstallConfirm.css
@@ -0,0 +1,8 @@
+/* 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/. */
+
+installitem {
+ -moz-binding: url("chrome://mozapps/content/xpinstall/xpinstallItem.xml#installitem");
+ display: -moz-box;
+}
diff --git a/toolkit/mozapps/extensions/content/xpinstallConfirm.js b/toolkit/mozapps/extensions/content/xpinstallConfirm.js
new file mode 100644
index 000000000..678e37001
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/xpinstallConfirm.js
@@ -0,0 +1,196 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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/. */
+
+var XPInstallConfirm = {};
+
+XPInstallConfirm.init = function XPInstallConfirm_init()
+{
+ Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
+ var _installCountdown;
+ var _installCountdownInterval;
+ var _focused;
+ var _timeout;
+
+ // Default to cancelling the install when the window unloads
+ XPInstallConfirm._installOK = false;
+
+ var bundle = document.getElementById("xpinstallConfirmStrings");
+
+ let args = window.arguments[0].wrappedJSObject;
+
+ // If all installs have already been cancelled in some way then just close
+ // the window
+ if (args.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
+ window.close();
+ return;
+ }
+
+ var _installCountdownLength = 5;
+ try {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ var delay_in_milliseconds = prefs.getIntPref("security.dialog_enable_delay");
+ _installCountdownLength = Math.round(delay_in_milliseconds / 500);
+ } catch (ex) { }
+
+ var itemList = document.getElementById("itemList");
+
+ let installMap = new WeakMap();
+ let installListener = {
+ onDownloadCancelled: function(install) {
+ itemList.removeChild(installMap.get(install));
+ if (--numItemsToInstall == 0)
+ window.close();
+ }
+ };
+
+ var numItemsToInstall = args.installs.length;
+ for (let install of args.installs) {
+ var installItem = document.createElement("installitem");
+ itemList.appendChild(installItem);
+
+ installItem.name = install.addon.name;
+ installItem.url = install.sourceURI.spec;
+ var icon = install.iconURL;
+ if (icon)
+ installItem.icon = icon;
+ var type = install.type;
+ if (type)
+ installItem.type = type;
+ if (install.certName) {
+ installItem.cert = bundle.getFormattedString("signed", [install.certName]);
+ }
+ else {
+ installItem.cert = bundle.getString("unverified");
+ }
+ installItem.signed = install.certName ? "true" : "false";
+
+ installMap.set(install, installItem);
+ install.addListener(installListener);
+ }
+
+ var introString = bundle.getString("itemWarnIntroSingle");
+ if (numItemsToInstall > 4)
+ introString = bundle.getFormattedString("itemWarnIntroMultiple", [numItemsToInstall]);
+ var textNode = document.createTextNode(introString);
+ var introNode = document.getElementById("itemWarningIntro");
+ while (introNode.hasChildNodes())
+ introNode.removeChild(introNode.firstChild);
+ introNode.appendChild(textNode);
+
+ var okButton = document.documentElement.getButton("accept");
+ okButton.focus();
+
+ function okButtonCountdown() {
+ _installCountdown -= 1;
+
+ if (_installCountdown < 1) {
+ okButton.label = bundle.getString("installButtonLabel");
+ okButton.disabled = false;
+ clearInterval(_installCountdownInterval);
+ }
+ else
+ okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
+ }
+
+ function myfocus() {
+ // Clear the timeout if it exists so only the last one will be used.
+ if (_timeout)
+ clearTimeout(_timeout);
+
+ // Use setTimeout since the last focus or blur event to fire is the one we
+ // want
+ _timeout = setTimeout(setWidgetsAfterFocus, 0);
+ }
+
+ function setWidgetsAfterFocus() {
+ if (_focused)
+ return;
+
+ _installCountdown = _installCountdownLength;
+ _installCountdownInterval = setInterval(okButtonCountdown, 500);
+ okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
+ _focused = true;
+ }
+
+ function myblur() {
+ // Clear the timeout if it exists so only the last one will be used.
+ if (_timeout)
+ clearTimeout(_timeout);
+
+ // Use setTimeout since the last focus or blur event to fire is the one we
+ // want
+ _timeout = setTimeout(setWidgetsAfterBlur, 0);
+ }
+
+ function setWidgetsAfterBlur() {
+ if (!_focused)
+ return;
+
+ // Set _installCountdown to the inital value set in setWidgetsAfterFocus
+ // plus 1 so when the window is focused there is immediate ui feedback.
+ _installCountdown = _installCountdownLength + 1;
+ okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
+ okButton.disabled = true;
+ clearInterval(_installCountdownInterval);
+ _focused = false;
+ }
+
+ function myUnload() {
+ if (_installCountdownLength > 0) {
+ document.removeEventListener("focus", myfocus, true);
+ document.removeEventListener("blur", myblur, true);
+ }
+ window.removeEventListener("unload", myUnload, false);
+
+ for (let install of args.installs)
+ install.removeListener(installListener);
+
+ // Now perform the desired action - either install the
+ // addons or cancel the installations
+ if (XPInstallConfirm._installOK) {
+ for (let install of args.installs)
+ install.install();
+ }
+ else {
+ for (let install of args.installs) {
+ if (install.state != AddonManager.STATE_CANCELLED)
+ install.cancel();
+ }
+ }
+ }
+
+ window.addEventListener("unload", myUnload, false);
+
+ if (_installCountdownLength > 0) {
+ document.addEventListener("focus", myfocus, true);
+ document.addEventListener("blur", myblur, true);
+
+ okButton.disabled = true;
+ setWidgetsAfterFocus();
+ }
+ else
+ okButton.label = bundle.getString("installButtonLabel");
+}
+
+XPInstallConfirm.onOK = function XPInstallConfirm_onOk()
+{
+ Components.classes["@mozilla.org/base/telemetry;1"].
+ getService(Components.interfaces.nsITelemetry).
+ getHistogramById("SECURITY_UI").
+ add(Components.interfaces.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
+ // Perform the install or cancel after the window has unloaded
+ XPInstallConfirm._installOK = true;
+ return true;
+}
+
+XPInstallConfirm.onCancel = function XPInstallConfirm_onCancel()
+{
+ // Perform the install or cancel after the window has unloaded
+ XPInstallConfirm._installOK = false;
+ return true;
+}
diff --git a/toolkit/mozapps/extensions/content/xpinstallConfirm.xul b/toolkit/mozapps/extensions/content/xpinstallConfirm.xul
new file mode 100644
index 000000000..f1c29eb73
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/xpinstallConfirm.xul
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://mozapps/content/xpinstall/xpinstallConfirm.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="xpinstallConfirm" title="&dialog.title;" style="&dialog.style;"
+ windowtype="Addons:Install"
+ onload="XPInstallConfirm.init()"
+ ondialogaccept="return XPInstallConfirm.onOK();"
+ ondialogcancel="return XPInstallConfirm.onCancel();">
+
+ <script src="chrome://mozapps/content/xpinstall/xpinstallConfirm.js" type="application/javascript"/>
+
+ <stringbundle id="xpinstallConfirmStrings"
+ src="chrome://mozapps/locale/xpinstall/xpinstallConfirm.properties"/>
+
+ <vbox flex="1" id="dialogContentBox">
+ <hbox id="xpinstallheader" align="start">
+ <image class="alert-icon"/>
+ <vbox flex="1">
+ <description class="warning">&warningPrimary.label;</description>
+ <description>&warningSecondary.label;</description>
+ </vbox>
+ </hbox>
+ <label id="itemWarningIntro"/>
+ <vbox id="itemList" class="listbox" flex="1" style="overflow: auto;"/>
+ </vbox>
+
+</dialog>
diff --git a/toolkit/mozapps/extensions/content/xpinstallItem.xml b/toolkit/mozapps/extensions/content/xpinstallItem.xml
new file mode 100644
index 000000000..5146af84f
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/xpinstallItem.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+
+<!DOCTYPE bindings SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">
+
+<bindings id="xpinstallItemBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="installitem">
+ <resources>
+ <stylesheet src="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css"/>
+ </resources>
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox align="center" pack="center" class="xpinstallIconContainer">
+ <xul:image class="xpinstallItemIcon" xbl:inherits="src=icon"/>
+ </xul:vbox>
+ <xul:vbox flex="1" pack="center">
+ <xul:hbox class="xpinstallItemNameRow" align="center">
+ <xul:label class="xpinstallItemName" xbl:inherits="value=name" crop="right"/>
+ <xul:label class="xpinstallItemSigned" xbl:inherits="value=cert,signed"/>
+ </xul:hbox>
+ <xul:hbox class="xpinstallItemDetailsRow" align="center">
+ <xul:textbox class="xpinstallItemURL" xbl:inherits="value=url" flex="1" readonly="true" crop="right"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <property name="name" onset="this.setAttribute('name', val); return val;"
+ onget="return this.getAttribute('name');"/>
+ <property name="cert" onset="this.setAttribute('cert', val); return val;"
+ onget="return this.getAttribute('cert');"/>
+ <property name="signed" onset="this.setAttribute('signed', val); return val;"
+ onget="return this.getAttribute('signed');"/>
+ <property name="url" onset="this.setAttribute('url', val); return val;"
+ onget="return this.getAttribute('url');"/>
+ <property name="icon" onset="this.setAttribute('icon', val); return val;"
+ onget="return this.getAttribute('icon');"/>
+ <property name="type" onset="this.setAttribute('type', val); return val;"
+ onget="return this.getAttribute('type');"/>
+ </implementation>
+ </binding>
+
+</bindings>
+