summaryrefslogtreecommitdiff
path: root/xpfe
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2021-11-24 04:10:05 -0500
committerMatt A. Tobin <email@mattatobin.com>2021-11-24 04:10:05 -0500
commit1a6153783539479f018820f881e433e372a98e08 (patch)
tree12b34676cf10390bb0da73a60044b41e25116010 /xpfe
parentabf12b972dd9c6cb22567dbe4ec70eadb7af8cc7 (diff)
downloadaura-central-1a6153783539479f018820f881e433e372a98e08.tar.gz
Issue MoonchildProductions/GRE%3005 - Exchange editor/ui for xpfe
Diffstat (limited to 'xpfe')
-rw-r--r--xpfe/components/autocomplete/jar.mn9
-rw-r--r--xpfe/components/autocomplete/moz.build6
-rw-r--r--xpfe/components/autocomplete/resources/content/autocomplete.css46
-rw-r--r--xpfe/components/autocomplete/resources/content/autocomplete.xml1646
-rw-r--r--xpfe/components/devtools/content/devtoolsOverlay.js11
-rw-r--r--xpfe/components/devtools/content/devtoolsOverlay.xul31
-rw-r--r--xpfe/components/devtools/content/scratchpad/scratchpad.js699
-rw-r--r--xpfe/components/devtools/content/scratchpad/scratchpad.xul352
-rw-r--r--xpfe/components/devtools/devtools-prefs.js8
-rw-r--r--xpfe/components/devtools/jar.mn14
-rw-r--r--xpfe/components/devtools/locale/devtoolsOverlay.dtd6
-rw-r--r--xpfe/components/devtools/locale/scratchpad.dtd123
-rw-r--r--xpfe/components/devtools/locale/scratchpad.properties35
-rw-r--r--xpfe/components/devtools/modules/PropertyPanel.jsm612
-rw-r--r--xpfe/components/devtools/moz.build10
-rw-r--r--xpfe/components/downloads/content/DownloadProgressListener.js58
-rw-r--r--xpfe/components/downloads/content/downloadmanager.js712
-rw-r--r--xpfe/components/downloads/content/downloadmanager.xul461
-rw-r--r--xpfe/components/downloads/content/progressDialog.js378
-rw-r--r--xpfe/components/downloads/content/progressDialog.xul116
-rw-r--r--xpfe/components/downloads/content/treeView.js700
-rw-r--r--xpfe/components/downloads/content/uploadProgress.js189
-rw-r--r--xpfe/components/downloads/content/uploadProgress.xul34
-rw-r--r--xpfe/components/downloads/download-prefs.js41
-rw-r--r--xpfe/components/downloads/jar.mn29
-rw-r--r--xpfe/components/downloads/locale/downloadmanager.dtd90
-rw-r--r--xpfe/components/downloads/locale/downloadmanager.properties73
-rw-r--r--xpfe/components/downloads/locale/progressDialog.dtd20
-rw-r--r--xpfe/components/downloads/moz.build20
-rw-r--r--xpfe/components/downloads/nsSuiteDownloadManager.manifest5
-rw-r--r--xpfe/components/downloads/public/nsISuiteDownloadManagerUI.idl19
-rw-r--r--xpfe/components/downloads/skin/dl-remove.pngbin0 -> 385 bytes
-rw-r--r--xpfe/components/downloads/skin/downloadButtons.pngbin0 -> 952 bytes
-rw-r--r--xpfe/components/downloads/skin/downloadmanager.css95
-rw-r--r--xpfe/components/downloads/skin/mac/downloadButtons.pngbin0 -> 762 bytes
-rw-r--r--xpfe/components/downloads/skin/mac/downloadmanager.css140
-rw-r--r--xpfe/components/downloads/skin/mac/progressBg.pngbin0 -> 136 bytes
-rw-r--r--xpfe/components/downloads/src/nsDownloadsStartup.js56
-rw-r--r--xpfe/components/downloads/src/nsSuiteDownloadManagerUI.js174
-rw-r--r--xpfe/components/eula/content/eula.js26
-rw-r--r--xpfe/components/eula/content/eula.xul29
-rw-r--r--xpfe/components/eula/jar.mn14
-rw-r--r--xpfe/components/eula/locale/eula.dtd9
-rw-r--r--xpfe/components/eula/moz.build6
-rw-r--r--xpfe/components/eula/skin/eula.css11
-rw-r--r--xpfe/components/moz.build19
-rw-r--r--xpfe/components/preferences/content/pref-advanced.xul20
-rw-r--r--xpfe/components/preferences/content/pref-applicationManager.js98
-rw-r--r--xpfe/components/preferences/content/pref-applicationManager.xul58
-rw-r--r--xpfe/components/preferences/content/pref-applications.js1754
-rw-r--r--xpfe/components/preferences/content/pref-applications.xul100
-rw-r--r--xpfe/components/preferences/content/pref-certs.js32
-rw-r--r--xpfe/components/preferences/content/pref-certs.xul98
-rw-r--r--xpfe/components/preferences/content/pref-download.js157
-rw-r--r--xpfe/components/preferences/content/pref-download.xul147
-rw-r--r--xpfe/components/preferences/content/pref-http.js29
-rw-r--r--xpfe/components/preferences/content/pref-http.xul105
-rw-r--r--xpfe/components/preferences/content/pref-masterpass.js83
-rw-r--r--xpfe/components/preferences/content/pref-masterpass.xul122
-rw-r--r--xpfe/components/preferences/content/pref-proxies-advanced.xul196
-rw-r--r--xpfe/components/preferences/content/pref-proxies.js190
-rw-r--r--xpfe/components/preferences/content/pref-proxies.xul156
-rw-r--r--xpfe/components/preferences/content/pref-smartupdate.js87
-rw-r--r--xpfe/components/preferences/content/pref-smartupdate.xul139
-rw-r--r--xpfe/components/preferences/content/pref-ssl.js82
-rw-r--r--xpfe/components/preferences/content/pref-ssl.xul121
-rw-r--r--xpfe/components/preferences/content/preferences.js98
-rw-r--r--xpfe/components/preferences/content/preferences.xul168
-rw-r--r--xpfe/components/preferences/content/prefpanels.css15
-rw-r--r--xpfe/components/preferences/content/prefpanels.xml59
-rw-r--r--xpfe/components/preferences/content/prefwindow.xml506
-rw-r--r--xpfe/components/preferences/jar.mn56
-rw-r--r--xpfe/components/preferences/locale/pref-advanced.dtd7
-rw-r--r--xpfe/components/preferences/locale/pref-applicationManager.dtd8
-rw-r--r--xpfe/components/preferences/locale/pref-applicationManager.properties10
-rw-r--r--xpfe/components/preferences/locale/pref-applications.dtd14
-rw-r--r--xpfe/components/preferences/locale/pref-applications.properties34
-rw-r--r--xpfe/components/preferences/locale/pref-certs.dtd30
-rw-r--r--xpfe/components/preferences/locale/pref-download.dtd40
-rw-r--r--xpfe/components/preferences/locale/pref-http.dtd27
-rw-r--r--xpfe/components/preferences/locale/pref-masterpass.dtd33
-rw-r--r--xpfe/components/preferences/locale/pref-proxies-advanced.dtd32
-rw-r--r--xpfe/components/preferences/locale/pref-proxies.dtd31
-rw-r--r--xpfe/components/preferences/locale/pref-smartupdate.dtd31
-rw-r--r--xpfe/components/preferences/locale/pref-ssl.dtd32
-rw-r--r--xpfe/components/preferences/locale/preferences.dtd40
-rw-r--r--xpfe/components/preferences/locale/prefutilities.dtd40
-rw-r--r--xpfe/components/preferences/locale/prefutilities.properties34
-rw-r--r--xpfe/components/preferences/moz.build6
-rw-r--r--xpfe/components/preferences/skin/preferences.css16
-rw-r--r--xpfe/components/preferences/skin/prefpanels.css89
-rw-r--r--xpfe/components/profile/content/profileSelection.js350
-rw-r--r--xpfe/components/profile/content/profileSelection.xul85
-rw-r--r--xpfe/components/profile/jar.mn20
-rw-r--r--xpfe/components/profile/locale/profileSelection.dtd39
-rw-r--r--xpfe/components/profile/locale/profileSelection.properties22
-rw-r--r--xpfe/components/profile/moz.build6
-rw-r--r--xpfe/components/profile/skin/migrate.gifbin0 -> 135 bytes
-rw-r--r--xpfe/components/profile/skin/profile.css28
-rw-r--r--xpfe/components/profile/skin/profileManager.css18
-rw-r--r--xpfe/components/profile/skin/profileicon-large.gifbin0 -> 76 bytes
-rw-r--r--xpfe/content/communicator.css172
-rw-r--r--xpfe/content/jar.mn6
-rw-r--r--xpfe/content/moz.build6
-rw-r--r--xpfe/modules/Communicator.jsm73
-rw-r--r--xpfe/modules/moz.build6
-rw-r--r--xpfe/moz.build13
-rw-r--r--xpfe/searchplugins/duckduckgo-palemoon.xml16
-rw-r--r--xpfe/searchplugins/ecosia.xml12
-rw-r--r--xpfe/searchplugins/ekoru.xml10
-rw-r--r--xpfe/searchplugins/moz.build11
-rw-r--r--xpfe/searchplugins/wikipedia.xml18
-rw-r--r--xpfe/xpfe.mozbuild15
113 files changed, 13288 insertions, 0 deletions
diff --git a/xpfe/components/autocomplete/jar.mn b/xpfe/components/autocomplete/jar.mn
new file mode 100644
index 000000000..d3ddf8a61
--- /dev/null
+++ b/xpfe/components/autocomplete/jar.mn
@@ -0,0 +1,9 @@
+# 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/.
+
+toolkit.jar:
+ content/global/autocomplete.xml (resources/content/autocomplete.xml)
+
+comm.jar:
+ content/communicator/autocomplete.css (resources/content/autocomplete.css)
diff --git a/xpfe/components/autocomplete/moz.build b/xpfe/components/autocomplete/moz.build
new file mode 100644
index 000000000..635fa39c9
--- /dev/null
+++ b/xpfe/components/autocomplete/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/xpfe/components/autocomplete/resources/content/autocomplete.css b/xpfe/components/autocomplete/resources/content/autocomplete.css
new file mode 100644
index 000000000..6c67bad2e
--- /dev/null
+++ b/xpfe/components/autocomplete/resources/content/autocomplete.css
@@ -0,0 +1,46 @@
+/* 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/. */
+
+
+.autocomplete-result-popupset {
+ width: 0 !important;
+}
+
+.autocomplete-result-popup {
+ display: -moz-popup !important;
+}
+
+/* the C++ implementation of widgets is too eager to make popups visible.
+ this causes problems (bug 120155 and others), thus this workaround: */
+.autocomplete-result-popup[hidden="true"] {
+ visibility: hidden;
+}
+
+.autocomplete-tree {
+ -moz-user-focus: ignore;
+}
+
+.autocomplete-history-dropmarker {
+ display: none;
+}
+
+.autocomplete-history-dropmarker[enablehistory="true"] {
+ display: -moz-box;
+}
+
+/* The following rule is here to fix bug 96899 (and now 117952).
+ Somehow trees create a situation
+ in which a popupset flows itself as if its popup child is directly within it
+ instead of the placeholder child that should actually be inside the popupset.
+ This is a stopgap measure, and it does not address the real bug. */
+popupset {
+ max-width: 0px;
+ width: 0px;
+ min-width: 0%;
+ min-height: 0%;
+}
+
+treecolpicker {
+ display: none;
+}
diff --git a/xpfe/components/autocomplete/resources/content/autocomplete.xml b/xpfe/components/autocomplete/resources/content/autocomplete.xml
new file mode 100644
index 000000000..93b6dfdb0
--- /dev/null
+++ b/xpfe/components/autocomplete/resources/content/autocomplete.xml
@@ -0,0 +1,1646 @@
+<?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/. -->
+
+
+<bindings id="autocompleteBindings"
+ xmlns="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"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="autocomplete" role="xul:combobox"
+ extends="chrome://global/content/bindings/textbox.xml#textbox">
+ <resources>
+ <stylesheet src="chrome://communicator/content/autocomplete.css"/>
+ <stylesheet src="chrome://global/skin/autocomplete.css"/>
+ </resources>
+
+ <content>
+ <children includes="menupopup"/>
+
+ <xul:hbox class="autocomplete-textbox-container" flex="1" align="center">
+ <children includes="image|deck|stack|box">
+ <xul:image class="autocomplete-icon" allowevents="true"/>
+ </children>
+
+ <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext">
+ <children/>
+ <html:input anonid="input" class="autocomplete-textbox textbox-input"
+ allowevents="true"
+ xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,userAction"/>
+ </xul:hbox>
+ <children includes="hbox"/>
+ </xul:hbox>
+
+ <xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true"
+ xbl:inherits="open,enablehistory" anonid="historydropmarker"/>
+
+ <xul:popupset>
+ <xul:panel type="autocomplete" anonid="popup"
+ ignorekeys="true" noautofocus="true" level="top"
+ xbl:inherits="for=id,nomatch"/>
+ </xul:popupset>
+ </content>
+
+ <implementation implements="nsIDOMXULMenuListElement">
+
+ <constructor><![CDATA[
+ // XXX bug 90337 band-aid until we figure out what's going on here
+ if (this.value != this.mInputElt.value)
+ this.mInputElt.value = this.value;
+ delete this.value;
+
+ // listen for pastes
+ this.mInputElt.controllers.insertControllerAt(0, this.mPasteController);
+
+ // listen for menubar activation
+ window.top.addEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
+
+ // set default property values
+ this.ifSetAttribute("timeout", 50);
+ this.ifSetAttribute("pastetimeout", 1000);
+ this.ifSetAttribute("maxrows", 5);
+ this.ifSetAttribute("showpopup", true);
+ this.ifSetAttribute("disableKeyNavigation", true);
+
+ // initialize the search sessions
+ if (this.hasAttribute("autocompletesearch"))
+ this.initAutoCompleteSearch();
+
+ // hack to work around lack of bottom-up constructor calling
+ if ("initialize" in this.popup)
+ this.popup.initialize();
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this.clearResults(false);
+ window.top.removeEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
+ this.mInputElt.controllers.removeController(this.mPasteController);
+ ]]></destructor>
+
+ <!-- =================== nsIAutoCompleteInput =================== -->
+ <!-- XXX: This implementation is currently incomplete. -->
+
+ <!-- reference to the results popup element -->
+ <field name="popup"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "popup");
+ ]]></field>
+
+ <property name="popupOpen"
+ onget="return this.mMenuOpen;"
+ onset="if (val) this.openPopup(); else this.closePopup(); return val;"/>
+
+ <!-- option to turn off autocomplete -->
+ <property name="disableAutoComplete"
+ onset="this.setAttribute('disableautocomplete', val); return val;"
+ onget="return this.getAttribute('disableautocomplete') == 'true';"/>
+
+ <!-- if the resulting match string is not at the beginning of the typed string,
+ this will optionally autofill like this "bar |>> foobar|" -->
+ <property name="completeDefaultIndex"
+ onset="this.setAttribute('completedefaultindex', val); return val;"
+ onget="return this.getAttribute('completedefaultindex') == 'true';"/>
+
+ <!-- option for completing to the default result whenever the user hits
+ enter or the textbox loses focus -->
+ <property name="forceComplete"
+ onset="this.setAttribute('forcecomplete', val); return val;"
+ onget="return this.getAttribute('forcecomplete') == 'true';"/>
+
+ <property name="minResultsForPopup"
+ onset="this.setAttribute('minresultsforpopup', val); return val;"
+ onget="var t = this.getAttribute('minresultsforpopup'); return t ? parseInt(t) : 1;"/>
+
+ <!-- maximum number of rows to display -->
+ <property name="maxRows"
+ onset="this.setAttribute('maxrows', val); return val;"
+ onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
+
+ <!-- toggles a second column in the results list which contains
+ the string in the comment field of each autocomplete result -->
+ <property name="showCommentColumn"
+ onget="return this.getAttribute('showcommentcolumn') == 'true';">
+ <setter><![CDATA[
+ this.popup.showCommentColumn = val;
+ this.setAttribute('showcommentcolumn', val);
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- number of milliseconds after a keystroke before a search begins -->
+ <property name="timeout"
+ onset="this.setAttribute('timeout', val); return val;"
+ onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
+
+ <property name="searchParam"
+ onget="return this.getAttribute('autocompletesearchparam') || '';"
+ onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
+
+ <property name="searchCount" readonly="true"
+ onget="return this.sessionCount;"/>
+
+ <method name="getSearchAt">
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ var idx = -1;
+ for (var name in this.mSessions)
+ if (++idx == aIndex)
+ return name;
+
+ return null;
+ ]]></body>
+ </method>
+
+ <property name="textValue"
+ onget="return this.value;"
+ onset="this.setTextValue(val); return val;"/>
+
+ <method name="onSearchBegin">
+ <body><![CDATA[
+ this._fireEvent("searchbegin");
+ ]]></body>
+ </method>
+
+ <method name="onSearchComplete">
+ <body><![CDATA[
+ if (this.noMatch)
+ this.setAttribute("nomatch", "true");
+ else
+ this.removeAttribute("nomatch");
+
+ this._fireEvent("searchcomplete");
+ ]]></body>
+ </method>
+
+ <method name="onTextReverted">
+ <body><![CDATA[
+ return this._fireEvent("textreverted");
+ ]]></body>
+ </method>
+
+ <!-- =================== nsIDOMXULMenuListElement =================== -->
+
+ <property name="editable" readonly="true"
+ onget="return true;" />
+
+ <property name="crop"
+ onset="this.setAttribute('crop', val); return val;"
+ onget="return this.getAttribute('crop');"/>
+
+ <property name="label" readonly="true"
+ onget="return this.mInputElt.value;"/>
+
+ <property name="open"
+ onget="return this.getAttribute('open') == 'true';">
+ <setter>
+ <![CDATA[
+ var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
+ if (val) {
+ this.setAttribute('open', true);
+ historyPopup.showPopup();
+ } else {
+ this.removeAttribute('open');
+ historyPopup.hidePopup();
+ }
+ ]]>
+ </setter>
+ </property>
+
+ <!-- =================== PUBLIC PROPERTIES =================== -->
+
+ <property name="value"
+ onget="return this.mInputElt.value;">
+ <setter><![CDATA[
+ this.ignoreInputEvent = true;
+ this.mInputElt.value = val;
+ this.ignoreInputEvent = false;
+ var event = document.createEvent('Events');
+ event.initEvent('ValueChange', true, true);
+ this.mInputElt.dispatchEvent(event);
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="focused"
+ onget="return this.getAttribute('focused') == 'true';"/>
+
+ <method name="initAutoCompleteSearch">
+ <body><![CDATA[
+ var list = this.getAttribute("autocompletesearch").split(" ");
+ for (var i = 0; i < list.length; i++) {
+ var name = list[i];
+ var contractid = "@mozilla.org/autocomplete/search;1?name=" + name;
+ if (contractid in Components.classes) {
+ try {
+ this.mSessions[name] =
+ Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSearch);
+ this.mLastResults[name] = null;
+ this.mLastRows[name] = 0;
+ ++this.sessionCount;
+ } catch (e) {
+ dump("### ERROR - unable to create search \"" + name + "\".\n");
+ }
+ } else {
+ dump("search \"" + name + "\" not found - skipping.\n");
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- the number of sessions currently in use -->
+ <field name="sessionCount">0</field>
+
+ <!-- number of milliseconds after a paste before a search begins -->
+ <property name="pasteTimeout"
+ onset="this.setAttribute('pastetimeout', val); return val;"
+ onget="var t = parseInt(this.getAttribute('pastetimeout')); return t ? t : 0;"/>
+
+ <!-- option for filling the textbox with the best match while typing
+ and selecting the difference -->
+ <property name="autoFill"
+ onset="this.setAttribute('autofill', val); return val;"
+ onget="return this.getAttribute('autofill') == 'true';"/>
+
+ <!-- if this attribute is set, allow different style for
+ non auto-completed lines -->
+ <property name="highlightNonMatches"
+ onset="this.setAttribute('highlightnonmatches', val); return val;"
+ onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
+
+ <!-- option to show the popup containing the results -->
+ <property name="showPopup"
+ onset="this.setAttribute('showpopup', val); return val;"
+ onget="return this.getAttribute('showpopup') == 'true';"/>
+
+ <!-- option to allow scrolling through the list via the tab key, rather than
+ tab moving focus out of the textbox -->
+ <property name="tabScrolling"
+ onset="this.setAttribute('tabscrolling', val); return val;"
+ onget="return this.getAttribute('tabscrolling') == 'true';"/>
+
+ <!-- option to completely ignore any blur events while
+ searches are still going on. This is useful so that nothing
+ gets autopicked if the window is required to lose focus for
+ some reason (eg in LDAP autocomplete, another window may be
+ brought up so that the user can enter a password to authenticate
+ to an LDAP server). -->
+ <property name="ignoreBlurWhileSearching"
+ onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
+ onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
+
+ <!-- state which indicates the current action being performed by the user.
+ Possible values are : none, typing, scrolling -->
+ <property name="userAction"
+ onset="this.setAttribute('userAction', val); return val;"
+ onget="return this.getAttribute('userAction');"/>
+
+ <!-- state which indicates if the last search had no matches -->
+ <field name="noMatch">true</field>
+
+ <!-- state which indicates a search is currently happening -->
+ <field name="isSearching">false</field>
+
+ <!-- state which indicates a search timeout is current waiting -->
+ <property name="isWaiting"
+ onget="return this.mAutoCompleteTimer != 0;"/>
+
+ <!-- =================== PRIVATE PROPERTIES =================== -->
+
+ <field name="mSessions">({})</field>
+ <field name="mLastResults">({})</field>
+ <field name="mLastRows">({})</field>
+ <field name="mLastKeyCode">null</field>
+ <field name="mAutoCompleteTimer">0</field>
+ <field name="mMenuOpen">false</field>
+ <field name="mFireAfterSearch">false</field>
+ <field name="mFinishAfterSearch">false</field>
+ <field name="mNeedToFinish">false</field>
+ <field name="mNeedToComplete">false</field>
+ <field name="mTransientValue">false</field>
+ <field name="mView">null</field>
+ <field name="currentSearchString">""</field>
+ <field name="ignoreInputEvent">false</field>
+ <field name="oninit">null</field>
+ <field name="mDefaultMatchFilled">false</field>
+ <field name="mFirstReturn">true</field>
+ <field name="mIsPasting">false</field>
+
+ <field name="mPasteController"><![CDATA[
+ ({
+ self: this,
+ kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
+ supportsCommand: function(aCommand) {
+ return aCommand == "cmd_paste";
+ },
+ isCommandEnabled: function(aCommand) {
+ return aCommand == "cmd_paste" &&
+ this.self.editor.isSelectionEditable &&
+ this.self.editor.canPaste(this.kGlobalClipboard);
+ },
+ doCommand: function(aCommand) {
+ if (aCommand == "cmd_paste") {
+ this.self.mIsPasting = true;
+ this.self.editor.paste(this.kGlobalClipboard);
+ this.self.mIsPasting = false;
+ }
+ },
+ onEvent: function() {}
+ })
+ ]]></field>
+
+ <field name="mMenuBarListener"><![CDATA[
+ ({
+ self: this,
+ handleEvent: function(aEvent) {
+ try {
+ this.self.finishAutoComplete(false, false, aEvent);
+ this.self.clearTimer();
+ this.self.closePopup();
+ } catch (e) {
+ window.top.removeEventListener("DOMMenuBarActive", this, true);
+ }
+ }
+ })
+ ]]></field>
+
+ <field name="mAutoCompleteObserver"><![CDATA[
+ ({
+ self: this,
+ onSearchResult: function(aSearch, aResult) {
+ for (var name in this.self.mSessions)
+ if (this.self.mSessions[name] == aSearch)
+ this.self.processResults(name, aResult);
+ }
+ })
+ ]]></field>
+
+ <field name="mInputElt"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "input");
+ ]]></field>
+
+ <field name="mMenuAccessKey"><![CDATA[
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch)
+ .getIntPref("ui.key.menuAccessKey");
+ ]]></field>
+
+ <!-- =================== PUBLIC METHODS =================== -->
+
+ <method name="getErrorAt">
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ var obj = aIndex < 0 ? null : this.convertIndexToSession(aIndex);
+ return obj && this.mLastResults[obj.session] &&
+ this.mLastResults[obj.session].errorDescription;
+ ]]></body>
+ </method>
+
+ <!-- get a value from the autocomplete results as a string via an absolute index-->
+ <method name="getResultValueAt">
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ var obj = this.convertIndexToSession(aIndex);
+ return obj ? this.getSessionValueAt(obj.session, obj.index) : null;
+ ]]></body>
+ </method>
+
+ <!-- get a value from the autocomplete results as a string from a specific session -->
+ <method name="getSessionValueAt">
+ <parameter name="aSession"/>
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ var result = this.mLastResults[aSession];
+ return result.errorDescription || result.getValueAt(aIndex);
+ ]]></body>
+ </method>
+
+ <!-- get the total number of results overall -->
+ <method name="getResultCount">
+ <body><![CDATA[
+ return this.view.rowCount;
+ ]]></body>
+ </method>
+
+ <!-- get the first session that has results -->
+ <method name="getDefaultSession">
+ <body><![CDATA[
+ for (var name in this.mLastResults) {
+ var results = this.mLastResults[name];
+ if (results && results.matchCount > 0 && !results.errorDescription)
+ return name;
+ }
+ return null;
+ ]]></body>
+ </method>
+
+ <!-- empty the cached result data and empty the results popup -->
+ <method name="clearResults">
+ <parameter name="aInvalidate"/>
+ <body><![CDATA[
+ this.clearResultData();
+ this.clearResultElements(aInvalidate);
+ ]]></body>
+ </method>
+
+ <!-- =================== PRIVATE METHODS =================== -->
+
+ <!-- ::::::::::::: session searching ::::::::::::: -->
+
+ <!-- -->
+ <method name="callListener">
+ <parameter name="me"/>
+ <parameter name="aAction"/>
+ <body><![CDATA[
+ // bail if the binding was detached or the element removed from
+ // document during the timeout
+ if (!("startLookup" in me) || !me.ownerDocument || !me.parentNode)
+ return;
+
+ me.clearTimer();
+
+ if (me.disableAutoComplete)
+ return;
+
+ switch (aAction) {
+ case "startLookup":
+ me.startLookup();
+ break;
+
+ case "stopLookup":
+ me.stopLookup();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="startLookup">
+ <body><![CDATA[
+ var str = this.currentSearchString;
+ if (!str) {
+ this.clearResults(false);
+ this.closePopup();
+ return;
+ }
+
+ this.isSearching = true;
+ this.mFirstReturn = true;
+ this.mSessionReturns = this.sessionCount;
+ this.mFailureItems = 0;
+ this.mDefaultMatchFilled = false; // clear out our prefill state.
+
+ // Notify the input that the search is beginning.
+ this.onSearchBegin();
+
+ // tell each session to start searching...
+ for (var name in this.mSessions)
+ try {
+ this.mSessions[name].startSearch(str, this.searchParam, this.mLastResults[name], this.mAutoCompleteObserver);
+ } catch (e) {
+ --this.mSessionReturns;
+ this.searchFailed();
+ }
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="stopLookup">
+ <body><![CDATA[
+ for (var name in this.mSessions)
+ this.mSessions[name].stopSearch();
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="processResults">
+ <parameter name="aSessionName"/>
+ <parameter name="aResults"/>
+ <body><![CDATA[
+ if (this.disableAutoComplete)
+ return;
+
+ const ACR = Components.interfaces.nsIAutoCompleteResult;
+ var status = aResults.searchResult;
+ if (status != ACR.RESULT_NOMATCH_ONGOING &&
+ status != ACR.RESULT_SUCCESS_ONGOING)
+ --this.mSessionReturns;
+
+ // check the many criteria for failure
+ if (aResults.errorDescription)
+ ++this.mFailureItems;
+ else if (status == ACR.RESULT_IGNORED ||
+ status == ACR.RESULT_FAILURE ||
+ status == ACR.RESULT_NOMATCH ||
+ status == ACR.RESULT_NOMATCH_ONGOING ||
+ aResults.matchCount == 0 ||
+ aResults.searchString != this.currentSearchString)
+ {
+ this.mLastResults[aSessionName] = null;
+ if (this.mFirstReturn)
+ this.clearResultElements(false);
+ this.mFirstReturn = false;
+ this.searchFailed();
+ return;
+ }
+
+ if (this.mFirstReturn) {
+ if (this.view.mTree)
+ this.view.mTree.beginUpdateBatch();
+ this.clearResultElements(false); // clear results, but don't repaint yet
+ }
+
+ // always call openPopup...we may not have opened it
+ // if a previous search session didn't return enough search results.
+ // it's smart and doesn't try to open itself multiple times...
+ // be sure to add our result elements before calling openPopup as we need
+ // to know the total # of results found so far.
+ this.addResultElements(aSessionName, aResults);
+
+ this.autoFillInput(aSessionName, aResults, false);
+ if (this.mFirstReturn && this.view.mTree)
+ this.view.mTree.endUpdateBatch();
+ this.openPopup();
+ this.mFirstReturn = false;
+
+ // if this is the last session to return...
+ if (this.mSessionReturns == 0)
+ this.postSearchCleanup();
+
+ if (this.mFinishAfterSearch)
+ this.finishAutoComplete(false, this.mFireAfterSearch, null);
+ ]]></body>
+ </method>
+
+ <!-- called each time a search fails, except when failure items need
+ to be displayed. If all searches have failed, clear the list
+ and close the popup -->
+ <method name="searchFailed">
+ <body><![CDATA[
+ // if all searches are done and they all failed...
+ if (this.mSessionReturns == 0 && this.getResultCount() == 0) {
+ if (this.minResultsForPopup == 0) {
+ this.clearResults(true); // clear data and repaint empty
+ this.openPopup();
+ } else {
+ this.closePopup();
+ }
+ }
+
+ // if it's the last session to return, time to clean up...
+ if (this.mSessionReturns == 0)
+ this.postSearchCleanup();
+ ]]></body>
+ </method>
+
+ <!-- does some stuff after a search is done (success or failure) -->
+ <method name="postSearchCleanup">
+ <body><![CDATA[
+ this.isSearching = false;
+
+ // figure out if there are no matches in all search sessions
+ var failed = true;
+ for (var name in this.mSessions) {
+ if (this.mLastResults[name])
+ failed = this.mLastResults[name].errorDescription ||
+ this.mLastResults[name].matchCount == 0;
+ if (!failed)
+ break;
+ }
+ this.noMatch = failed;
+
+ // if we have processed all of our searches, and none of them gave us a default index,
+ // then we should try to auto fill the input field with the first match.
+ // note: autoFillInput is smart enough to kick out if we've already prefilled something...
+ if (!this.noMatch) {
+ var defaultSession = this.getDefaultSession();
+ if (defaultSession)
+ this.autoFillInput(defaultSession, this.mLastResults[defaultSession], true);
+ }
+
+ // Notify the input that the search is complete.
+ this.onSearchComplete();
+ ]]></body>
+ </method>
+
+ <!-- when the focus exits the widget or user hits return,
+ determine what value to leave in the textbox -->
+ <method name="finishAutoComplete">
+ <parameter name="aForceComplete"/>
+ <parameter name="aFireTextCommand"/>
+ <parameter name="aTriggeringEvent"/>
+ <body><![CDATA[
+ this.mFinishAfterSearch = false;
+ this.mFireAfterSearch = false;
+ if (this.mNeedToFinish && !this.disableAutoComplete) {
+ // set textbox value to either override value, or default search result
+ var val = this.popup.overrideValue;
+ if (val) {
+ this.setTextValue(val);
+ this.mNeedToFinish = false;
+ } else if (this.mTransientValue ||
+ !(this.forceComplete ||
+ (aForceComplete &&
+ this.mDefaultMatchFilled &&
+ this.mNeedToComplete))) {
+ this.mNeedToFinish = false;
+ } else if (this.isWaiting) {
+ // if the user typed, the search results are out of date, so let
+ // the search finish, and tell it to come back here when it's done
+ this.mFinishAfterSearch = true;
+ this.mFireAfterSearch = aFireTextCommand;
+ return;
+ } else {
+ // we want to use the default item index for the first session which gave us a valid
+ // default item index...
+ for (var name in this.mLastResults) {
+ var results = this.mLastResults[name];
+ if (results && results.matchCount > 0 &&
+ !results.errorDescription && results.defaultIndex != -1)
+ {
+ val = results.getValueAt(results.defaultIndex);
+ this.setTextValue(val);
+ this.mDefaultMatchFilled = true;
+ this.mNeedToFinish = false;
+ break;
+ }
+ }
+
+ if (this.mNeedToFinish) {
+ // if a search is happening at this juncture, bail out of this function
+ // and let the search finish, and tell it to come back here when it's done
+ if (this.isSearching) {
+ this.mFinishAfterSearch = true;
+ this.mFireAfterSearch = aFireTextCommand;
+ return;
+ }
+
+ this.mNeedToFinish = false;
+ var defaultSession = this.getDefaultSession();
+ if (defaultSession)
+ {
+ // preselect the first one
+ var first = this.getSessionValueAt(defaultSession, 0);
+ this.setTextValue(first);
+ this.mDefaultMatchFilled = true;
+ }
+ }
+ }
+
+ this.stopLookup();
+
+ this.closePopup();
+ }
+
+ this.mNeedToComplete = false;
+ this.clearTimer();
+
+ if (aFireTextCommand)
+ this._fireEvent("textentered", this.userAction, aTriggeringEvent);
+ ]]></body>
+ </method>
+
+ <!-- when the user clicks an entry in the autocomplete popup -->
+ <method name="onResultClick">
+ <body><![CDATA[
+ // set textbox value to either override value, or the clicked result
+ var errItem = this.getErrorAt(this.popup.selectedIndex);
+ var val = this.popup.overrideValue;
+ if (val)
+ this.setTextValue(val);
+ else if (this.popup.selectedIndex != -1) {
+ if (errItem) {
+ this.setTextValue(this.currentSearchString);
+ this.mTransientValue = true;
+ } else {
+ this.setTextValue(this.getResultValueAt(
+ this.popup.selectedIndex));
+ }
+ }
+
+ this.mNeedToFinish = false;
+ this.mNeedToComplete = false;
+
+ this.closePopup();
+
+ this.currentSearchString = "";
+
+ if (errItem)
+ this._fireEvent("errorcommand", errItem);
+ this._fireEvent("textentered", "clicking");
+ ]]></body>
+ </method>
+
+ <!-- when the user hits escape, revert the previously typed value in the textbox -->
+ <method name="undoAutoComplete">
+ <body><![CDATA[
+ var val = this.currentSearchString;
+
+ var ok = this.onTextReverted();
+ if ((ok || ok == undefined) && val)
+ this.setTextValue(val);
+
+ this.userAction = "typing";
+
+ this.currentSearchString = this.value;
+ this.mNeedToComplete = false;
+ ]]></body>
+ </method>
+
+ <!-- convert an absolute result index into a session name/index pair -->
+ <method name="convertIndexToSession">
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ for (var name in this.mLastRows) {
+ if (aIndex < this.mLastRows[name])
+ return { session: name, index: aIndex };
+ aIndex -= this.mLastRows[name];
+ }
+ return null;
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: user input handling ::::::::::::: -->
+
+ <!-- -->
+ <method name="processInput">
+ <body><![CDATA[
+ // stop current lookup in case it's async.
+ this.stopLookup();
+ // stop the queued up lookup on a timer
+ this.clearTimer();
+
+ if (this.disableAutoComplete)
+ return;
+
+ this.userAction = "typing";
+ this.mFinishAfterSearch = false;
+ this.mNeedToFinish = true;
+ this.mTransientValue = false;
+ this.mNeedToComplete = true;
+ var str = this.value;
+ this.currentSearchString = str;
+ this.popup.clearSelection();
+
+ var timeout = this.mIsPasting ? this.pasteTimeout : this.timeout;
+ this.mAutoCompleteTimer = setTimeout(this.callListener, timeout, this, "startLookup");
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="processKeyPress">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ this.mLastKeyCode = aEvent.keyCode;
+
+ var killEvent = false;
+
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_TAB:
+ if (this.tabScrolling) {
+ // don't kill this event if alt-tab or ctrl-tab is hit
+ if (!aEvent.altKey && !aEvent.ctrlKey) {
+ killEvent = this.mMenuOpen;
+ if (killEvent)
+ this.keyNavigation(aEvent);
+ }
+ }
+ break;
+
+ case KeyEvent.DOM_VK_RETURN:
+
+ // if this is a failure item, save it for fireErrorCommand
+ var errItem = this.getErrorAt(this.popup.selectedIndex);
+
+ killEvent = this.mMenuOpen;
+ this.finishAutoComplete(true, true, aEvent);
+ this.closePopup();
+ if (errItem) {
+ this._fireEvent("errorcommand", errItem);
+ }
+ break;
+
+ case KeyEvent.DOM_VK_ESCAPE:
+ this.clearTimer();
+ killEvent = this.mMenuOpen;
+ this.undoAutoComplete();
+ this.closePopup();
+ break;
+
+ case KeyEvent.DOM_VK_LEFT:
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_HOME:
+ case KeyEvent.DOM_VK_END:
+ this.finishAutoComplete(true, false, aEvent);
+ this.clearTimer();
+ this.closePopup();
+ break;
+
+ case KeyEvent.DOM_VK_DOWN:
+ if (!aEvent.altKey) {
+ this.clearTimer();
+ killEvent = this.keyNavigation(aEvent);
+ break;
+ }
+ // Alt+Down falls through to history popup toggling code
+
+ case KeyEvent.DOM_VK_F4:
+ if (!aEvent.ctrlKey && !aEvent.shiftKey && this.getAttribute("enablehistory") == "true") {
+ var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
+ if (historyPopup)
+ historyPopup.showPopup();
+ else
+ historyPopup.hidePopup();
+ }
+ break;
+ case KeyEvent.DOM_VK_PAGE_UP:
+ case KeyEvent.DOM_VK_PAGE_DOWN:
+ case KeyEvent.DOM_VK_UP:
+ if (!aEvent.ctrlKey && !aEvent.metaKey) {
+ this.clearTimer();
+ killEvent = this.keyNavigation(aEvent);
+ }
+ break;
+
+ case KeyEvent.DOM_VK_BACK_SPACE:
+ if (!aEvent.ctrlKey && !aEvent.altKey && !aEvent.shiftKey &&
+ this.selectionStart == this.currentSearchString.length &&
+ this.selectionEnd == this.value.length &&
+ this.mDefaultMatchFilled) {
+ this.mDefaultMatchFilled = false;
+ this.value = this.currentSearchString;
+ }
+
+ if (!/Mac/.test(navigator.platform))
+ break;
+ case KeyEvent.DOM_VK_DELETE:
+ if (/Mac/.test(navigator.platform) && !aEvent.shiftKey)
+ break;
+
+ if (this.mMenuOpen && this.popup.selectedIndex != -1) {
+ var obj = this.convertIndexToSession(this.popup.selectedIndex);
+ if (obj) {
+ var result = this.mLastResults[obj.session];
+ if (!result.errorDescription) {
+ var count = result.matchCount;
+ result.removeValueAt(obj.index, true);
+ this.view.updateResults(this.popup.selectedIndex, result.matchCount - count);
+ killEvent = true;
+ }
+ }
+ }
+ break;
+ }
+
+ if (killEvent) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+
+ return true;
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="processStartComposition">
+ <body><![CDATA[
+ this.finishAutoComplete(false, false, null);
+ this.clearTimer();
+ this.closePopup();
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="keyNavigation">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var k = aEvent.keyCode;
+ if (k == KeyEvent.DOM_VK_TAB ||
+ k == KeyEvent.DOM_VK_UP || k == KeyEvent.DOM_VK_DOWN ||
+ k == KeyEvent.DOM_VK_PAGE_UP || k == KeyEvent.DOM_VK_PAGE_DOWN)
+ {
+ if (!this.mMenuOpen) {
+ // Original xpfe style was to allow the up and down keys to have
+ // their default Mac action if the popup could not be opened.
+ // For compatibility for toolkit we now have to predict which
+ // keys have a default action that we can always allow to fire.
+ if (/Mac/.test(navigator.platform) &&
+ ((k == KeyEvent.DOM_VK_UP &&
+ (this.selectionStart != 0 ||
+ this.selectionEnd != 0)) ||
+ (k == KeyEvent.DOM_VK_DOWN &&
+ (this.selectionStart != this.value.length ||
+ this.selectionEnd != this.value.length))))
+ return false;
+ if (this.currentSearchString != this.value) {
+ this.processInput();
+ return true;
+ }
+ if (this.view.rowCount < this.minResultsForPopup)
+ return true; // used to be false, see above
+
+ this.mNeedToFinish = true;
+ this.openPopup();
+ return true;
+ }
+
+ this.userAction = "scrolling";
+ this.mNeedToComplete = false;
+
+ var reverse = k == KeyEvent.DOM_VK_TAB && aEvent.shiftKey ||
+ k == KeyEvent.DOM_VK_UP ||
+ k == KeyEvent.DOM_VK_PAGE_UP;
+ var page = k == KeyEvent.DOM_VK_PAGE_UP ||
+ k == KeyEvent.DOM_VK_PAGE_DOWN;
+ var selected = this.popup.selectBy(reverse, page);
+
+ // determine which value to place in the textbox
+ this.ignoreInputEvent = true;
+ if (selected != -1) {
+ if (this.getErrorAt(selected)) {
+ if (this.currentSearchString)
+ this.setTextValue(this.currentSearchString);
+ } else {
+ this.setTextValue(this.getResultValueAt(selected));
+ }
+ this.mTransientValue = true;
+ } else {
+ if (this.currentSearchString)
+ this.setTextValue(this.currentSearchString);
+ this.mTransientValue = false;
+ }
+
+ // move cursor to the end
+ this.mInputElt.setSelectionRange(this.value.length, this.value.length);
+ this.ignoreInputEvent = false;
+ }
+ return true;
+ ]]></body>
+ </method>
+
+ <!-- while the user is typing, fill the textbox with the "default" value
+ if one can be assumed, and select the end of the text -->
+ <method name="autoFillInput">
+ <parameter name="aSessionName"/>
+ <parameter name="aResults"/>
+ <parameter name="aUseFirstMatchIfNoDefault"/>
+ <body><![CDATA[
+ if (this.mInputElt.selectionEnd < this.currentSearchString.length ||
+ this.mDefaultMatchFilled)
+ return;
+
+ if (!this.mFinishAfterSearch &&
+ (this.autoFill || this.completeDefaultIndex) &&
+ this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE &&
+ this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) {
+ var indexToUse = aResults.defaultIndex;
+ if (aUseFirstMatchIfNoDefault && indexToUse == -1)
+ indexToUse = 0;
+
+ if (indexToUse != -1) {
+ var resultValue = this.getSessionValueAt(aSessionName, indexToUse);
+ var match = resultValue.toLowerCase();
+ var entry = this.currentSearchString.toLowerCase();
+ this.ignoreInputEvent = true;
+ if (match.indexOf(entry) == 0) {
+ var endPoint = this.value.length;
+ this.setTextValue(this.value + resultValue.substr(endPoint));
+ this.mInputElt.setSelectionRange(endPoint, this.value.length);
+ } else {
+ if (this.completeDefaultIndex) {
+ this.setTextValue(this.value + " >> " + resultValue);
+ this.mInputElt.setSelectionRange(entry.length, this.value.length);
+ } else {
+ var postIndex = resultValue.indexOf(this.value);
+ if (postIndex >= 0) {
+ var startPt = this.value.length;
+ this.setTextValue(this.value +
+ resultValue.substr(startPt+postIndex));
+ this.mInputElt.setSelectionRange(startPt, this.value.length);
+ }
+ }
+ }
+ this.mNeedToComplete = true;
+ this.ignoreInputEvent = false;
+ this.mDefaultMatchFilled = true;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: popup and tree ::::::::::::: -->
+
+ <!-- -->
+ <method name="openPopup">
+ <body><![CDATA[
+ if (!this.mMenuOpen && this.focused &&
+ (this.getResultCount() >= this.minResultsForPopup ||
+ this.mFailureItems)) {
+ var w = this.boxObject.width;
+ if (w != this.popup.boxObject.width)
+ this.popup.setAttribute("width", w);
+ this.popup.showPopup(this, -1, -1, "popup", "bottomleft", "topleft");
+ this.mMenuOpen = true;
+ }
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="closePopup">
+ <body><![CDATA[
+ if (this.popup && this.mMenuOpen) {
+ this.popup.hidePopup();
+ this.mMenuOpen = false;
+ }
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="addResultElements">
+ <parameter name="aSession"/>
+ <parameter name="aResults"/>
+ <body><![CDATA[
+ var count = aResults.errorDescription ? 1 : aResults.matchCount;
+ if (this.focused && this.showPopup) {
+ var row = 0;
+ for (var name in this.mSessions) {
+ row += this.mLastRows[name];
+ if (name == aSession)
+ break;
+ }
+ this.view.updateResults(row, count - this.mLastRows[name]);
+ this.popup.adjustHeight();
+ }
+ this.mLastResults[aSession] = aResults;
+ this.mLastRows[aSession] = count;
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="clearResultElements">
+ <parameter name="aInvalidate"/>
+ <body><![CDATA[
+ for (var name in this.mSessions)
+ this.mLastRows[name] = 0;
+ this.view.clearResults();
+ if (aInvalidate)
+ this.popup.adjustHeight();
+
+ this.noMatch = true;
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="setTextValue">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this.value = aValue;
+
+ // Completing a result should simulate the user typing the result,
+ // so fire an input event.
+ var evt = document.createEvent("UIEvents");
+ evt.initUIEvent("input", true, false, window, 0);
+ var oldIgnoreInput = this.ignoreInputEvent;
+ this.ignoreInputEvent = true;
+ this.dispatchEvent(evt);
+ this.ignoreInputEvent = oldIgnoreInput;
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="clearResultData">
+ <body><![CDATA[
+ for (var name in this.mSessions)
+ this.mLastResults[name] = null;
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: miscellaneous ::::::::::::: -->
+
+ <!-- -->
+ <method name="ifSetAttribute">
+ <parameter name="aAttr"/>
+ <parameter name="aVal"/>
+ <body><![CDATA[
+ if (!this.hasAttribute(aAttr))
+ this.setAttribute(aAttr, aVal);
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <method name="clearTimer">
+ <body><![CDATA[
+ if (this.mAutoCompleteTimer) {
+ clearTimeout(this.mAutoCompleteTimer);
+ this.mAutoCompleteTimer = 0;
+ }
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: event dispatching ::::::::::::: -->
+
+ <method name="_fireEvent">
+ <parameter name="aEventType"/>
+ <parameter name="aEventParam"/>
+ <parameter name="aTriggeringEvent"/>
+ <body>
+ <![CDATA[
+ var noCancel = true;
+ // handle any xml attribute event handlers
+ var handler = this.getAttribute("on"+aEventType);
+ if (handler) {
+ var fn = new Function("eventParam", "domEvent", handler);
+ var returned = fn.apply(this, [aEventParam, aTriggeringEvent]);
+ if (returned == false)
+ noCancel = false;
+ }
+
+ return noCancel;
+ ]]>
+ </body>
+ </method>
+
+ <!-- =================== TREE VIEW =================== -->
+
+ <field name="view"><![CDATA[
+ ({
+ mTextbox: this,
+ mTree: null,
+ mSelection: null,
+ mRowCount: 0,
+
+ clearResults: function()
+ {
+ var oldCount = this.mRowCount;
+ this.mRowCount = 0;
+
+ if (this.mTree) {
+ this.mTree.rowCountChanged(0, -oldCount);
+ this.mTree.scrollToRow(0);
+ }
+ },
+
+ updateResults: function(aRow, aCount)
+ {
+ this.mRowCount += aCount;
+
+ if (this.mTree)
+ this.mTree.rowCountChanged(aRow, aCount);
+ },
+
+ //////////////////////////////////////////////////////////
+ // nsIAutoCompleteController interface
+
+ // this is the only method required by the treebody mouseup handler
+ handleEnter: function(aIsPopupSelection) {
+ this.mTextbox.onResultClick();
+ },
+
+ //////////////////////////////////////////////////////////
+ // nsITreeView interface
+
+ get rowCount() {
+ return this.mRowCount;
+ },
+
+ get selection() {
+ return this.mSelection;
+ },
+
+ set selection(aVal) {
+ return this.mSelection = aVal;
+ },
+
+ setTree: function(aTree)
+ {
+ this.mTree = aTree;
+ },
+
+ getCellText: function(aRow, aCol)
+ {
+ for (var name in this.mTextbox.mSessions) {
+ if (aRow < this.mTextbox.mLastRows[name]) {
+ var result = this.mTextbox.mLastResults[name];
+ switch (aCol.id) {
+ case "treecolAutoCompleteValue":
+ return result.errorDescription || result.getLabelAt(aRow);
+ case "treecolAutoCompleteComment":
+ if (!result.errorDescription)
+ return result.getCommentAt(aRow);
+ default:
+ return "";
+ }
+ }
+ aRow -= this.mTextbox.mLastRows[name];
+ }
+ return "";
+ },
+
+ getRowProperties: function(aIndex)
+ {
+ return "";
+ },
+
+ getCellProperties: function(aIndex, aCol)
+ {
+ // for the value column, append nsIAutoCompleteItem::className
+ // to the property list so that we can style this column
+ // using that property
+ if (aCol.id == "treecolAutoCompleteValue") {
+ for (var name in this.mTextbox.mSessions) {
+ if (aIndex < this.mTextbox.mLastRows[name]) {
+ var result = this.mTextbox.mLastResults[name];
+ if (result.errorDescription)
+ return "";
+ return result.getStyleAt(aIndex);
+ }
+ aIndex -= this.mTextbox.mLastRows[name];
+ }
+ }
+ return "";
+ },
+
+ getColumnProperties: function(aCol)
+ {
+ return "";
+ },
+
+ getImageSrc: function(aRow, aCol)
+ {
+ if (aCol.id == "treecolAutoCompleteValue") {
+ for (var name in this.mTextbox.mSessions) {
+ if (aRow < this.mTextbox.mLastRows[name]) {
+ var result = this.mTextbox.mLastResults[name];
+ if (result.errorDescription)
+ return "";
+ return result.getImageAt(aRow);
+ }
+ aRow -= this.mTextbox.mLastRows[name];
+ }
+ }
+ return "";
+ },
+
+ getParentIndex: function(aRowIndex) { },
+ hasNextSibling: function(aRowIndex, aAfterIndex) { },
+ getLevel: function(aIndex) {},
+ getProgressMode: function(aRow, aCol) {},
+ getCellValue: function(aRow, aCol) {},
+ isContainer: function(aIndex) {},
+ isContainerOpen: function(aIndex) {},
+ isContainerEmpty: function(aIndex) {},
+ isSeparator: function(aIndex) {},
+ isSorted: function() {},
+ toggleOpenState: function(aIndex) {},
+ selectionChanged: function() {},
+ cycleHeader: function(aCol) {},
+ cycleCell: function(aRow, aCol) {},
+ isEditable: function(aRow, aCol) {},
+ isSelectable: function(aRow, aCol) {},
+ setCellValue: function(aRow, aCol, aValue) {},
+ setCellText: function(aRow, aCol, aValue) {},
+ performAction: function(aAction) {},
+ performActionOnRow: function(aAction, aRow) {},
+ performActionOnCell: function(aAction, aRow, aCol) {}
+ });
+ ]]></field>
+
+ </implementation>
+
+ <handlers>
+ <handler event="input"
+ action="if (!this.ignoreInputEvent) this.processInput();"/>
+
+ <handler event="keypress" phase="capturing"
+ action="return this.processKeyPress(event);"/>
+
+ <handler event="compositionstart" phase="capturing"
+ action="this.processStartComposition();"/>
+
+ <handler event="focus" phase="capturing"
+ action="this.userAction = 'typing';"/>
+
+ <handler event="blur" phase="capturing"
+ action="if ( !(this.ignoreBlurWhileSearching &amp;&amp; this.isSearching) ) {this.userAction = 'none'; this.finishAutoComplete(false, false, event);}"/>
+
+ <handler event="mousedown" phase="capturing"
+ action="if ( !this.mMenuOpen ) this.finishAutoComplete(false, false, event);"/>
+ </handlers>
+ </binding>
+
+ <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/popup.xml#popup">
+ <resources>
+ <stylesheet src="chrome://communicator/content/autocomplete.css"/>
+ <stylesheet src="chrome://global/skin/autocomplete.css"/>
+ </resources>
+
+ <content ignorekeys="true" level="top">
+ <xul:tree anonid="tree" class="autocomplete-tree plain" flex="1">
+ <xul:treecols anonid="treecols">
+ <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteValue" flex="2"/>
+ <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteComment" flex="1" hidden="true"/>
+ </xul:treecols>
+ <xul:treechildren anonid="treebody" class="autocomplete-treebody"/>
+ </xul:tree>
+ </content>
+
+ <implementation implements="nsIAutoCompletePopup">
+ <constructor><![CDATA[
+ if (this.textbox && this.textbox.view)
+ this.initialize();
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ if (this.view)
+ this.tree.view = null;
+ ]]></destructor>
+
+ <field name="textbox">
+ document.getBindingParent(this);
+ </field>
+
+ <field name="tree">
+ document.getAnonymousElementByAttribute(this, "anonid", "tree");
+ </field>
+
+ <field name="treecols">
+ document.getAnonymousElementByAttribute(this, "anonid", "treecols");
+ </field>
+
+ <field name="treebody">
+ document.getAnonymousElementByAttribute(this, "anonid", "treebody");
+ </field>
+
+ <field name="view">
+ null
+ </field>
+
+ <!-- Setting tree.view doesn't always immediately create a selection,
+ so we ensure the selection by asking the tree for the view. Note:
+ this.view.selection is quicker if we know the selection exists. -->
+ <property name="selection" onget="return this.tree.view.selection;"/>
+
+ <property name="pageCount"
+ onget="return this.tree.treeBoxObject.getPageLength();"/>
+
+ <field name="maxRows">0</field>
+ <field name="mLastRows">0</field>
+
+ <method name="initialize">
+ <body><![CDATA[
+ this.showCommentColumn = this.textbox.showCommentColumn;
+ this.tree.view = this.textbox.view;
+ this.view = this.textbox.view;
+ this.maxRows = this.textbox.maxRows;
+ ]]></body>
+ </method>
+
+ <property name="showCommentColumn"
+ onget="return !this.treecols.lastChild.hidden;"
+ onset="this.treecols.lastChild.hidden = !val; return val;"/>
+
+ <method name="adjustHeight">
+ <body><![CDATA[
+ // detect the desired height of the tree
+ var bx = this.tree.treeBoxObject;
+ var view = this.view;
+ var rows = this.maxRows || 6;
+ if (!view.rowCount || (rows && view.rowCount < rows))
+ rows = view.rowCount;
+
+ var height = rows * bx.rowHeight;
+
+ if (height == 0)
+ this.tree.setAttribute("collapsed", "true");
+ else {
+ if (this.tree.hasAttribute("collapsed"))
+ this.tree.removeAttribute("collapsed");
+ this.tree.setAttribute("height", height);
+ }
+ ]]></body>
+ </method>
+
+ <method name="clearSelection">
+ <body>
+ this.selection.clearSelection();
+ </body>
+ </method>
+
+ <method name="getNextIndex">
+ <parameter name="aReverse"/>
+ <parameter name="aPage"/>
+ <parameter name="aIndex"/>
+ <parameter name="aMaxRow"/>
+ <body><![CDATA[
+ if (aMaxRow < 0)
+ return -1;
+
+ if (aIndex == -1)
+ return aReverse ? aMaxRow : 0;
+ if (aIndex == (aReverse ? 0 : aMaxRow))
+ return -1;
+
+ var amount = aPage ? this.pageCount - 1 : 1;
+ aIndex = aReverse ? aIndex - amount : aIndex + amount;
+ if (aIndex > aMaxRow)
+ return aMaxRow;
+ if (aIndex < 0)
+ return 0;
+ return aIndex;
+ ]]></body>
+ </method>
+
+ <!-- =================== nsIAutoCompletePopup =================== -->
+
+ <field name="input">
+ null
+ </field>
+
+ <!-- This property is meant to be overriden by bindings extending
+ this one. When the user selects an item from the list by
+ hitting enter or clicking, this method can set the value
+ of the textbox to a different value if it wants to. -->
+ <property name="overrideValue" readonly="true" onget="return null;"/>
+
+ <property name="selectedIndex">
+ <getter>
+ if (!this.view || !this.selection.count)
+ return -1;
+ var start = {}, end = {};
+ this.view.selection.getRangeAt(0, start, end);
+ return start.value;
+ </getter>
+ <setter>
+ if (this.view) {
+ this.selection.select(val);
+ if (val >= 0) {
+ this.view.selection.currentIndex = -1;
+ this.tree.treeBoxObject.ensureRowIsVisible(val);
+ }
+ }
+ return val;
+ </setter>
+ </property>
+
+ <property name="popupOpen" onget="return !!this.input;" readonly="true"/>
+
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body><![CDATA[
+ if (!this.input) {
+ this.tree.view = aInput.controller;
+ this.view = this.tree.view;
+ this.showCommentColumn = aInput.showCommentColumn;
+ this.maxRows = aInput.maxRows;
+ this.invalidate();
+
+ var viewer = aElement
+ .ownerDocument
+ .defaultView
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShell)
+ .contentViewer;
+ var rect = aElement.getBoundingClientRect();
+ var width = Math.round((rect.right - rect.left) * viewer.fullZoom);
+ this.setAttribute("width", width > 100 ? width : 100);
+ // Adjust the direction (which is not inherited) of the autocomplete
+ // popup list, based on the textbox direction. (Bug 707039)
+ this.style.direction = aElement.ownerDocument.defaultView
+ .getComputedStyle(aElement)
+ .direction;
+ this.popupBoxObject.setConsumeRollupEvent(aInput.consumeRollupEvent
+ ? PopupBoxObject.ROLLUP_CONSUME
+ : PopupBoxObject.ROLLUP_NO_CONSUME);
+ this.openPopup(aElement, "after_start", 0, 0, false, false);
+ if (this.state != "closed")
+ this.input = aInput;
+ }
+ ]]></body>
+ </method>
+
+ <method name="closePopup">
+ <body>
+ this.hidePopup();
+ </body>
+ </method>
+
+ <method name="invalidate">
+ <body>
+ if (this.view)
+ this.adjustHeight();
+ this.tree.treeBoxObject.invalidate();
+ </body>
+ </method>
+
+ <method name="selectBy">
+ <parameter name="aReverse"/>
+ <parameter name="aPage"/>
+ <body><![CDATA[
+ try {
+ return this.selectedIndex = this.getNextIndex(aReverse, aPage, this.selectedIndex, this.view.rowCount - 1);
+ } catch (ex) {
+ // do nothing - occasionally timer-related js errors happen here
+ // e.g. "this.selectedIndex has no properties", when you type fast and hit a
+ // navigation key before this popup has opened
+ return -1;
+ }
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing">
+ if (this.textbox)
+ this.textbox.mMenuOpen = true;
+ </handler>
+
+ <handler event="popuphiding">
+ if (this.textbox)
+ this.textbox.mMenuOpen = false;
+ this.clearSelection();
+ this.input = null;
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="autocomplete-treebody">
+ <implementation>
+ <field name="popup">document.getBindingParent(this);</field>
+
+ <field name="mLastMoveTime">Date.now()</field>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseout" action="this.popup.selectedIndex = -1;"/>
+
+ <handler event="mouseup"><![CDATA[
+ var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (rc != -1) {
+ this.popup.selectedIndex = rc;
+ this.popup.view.handleEnter(true);
+ }
+ ]]></handler>
+
+ <handler event="mousemove"><![CDATA[
+ if (Date.now() - this.mLastMoveTime > 30) {
+ var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (rc != -1 && rc != this.popup.selectedIndex)
+ this.popup.selectedIndex = rc;
+ this.mLastMoveTime = Date.now();
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="autocomplete-history-popup"
+ extends="chrome://global/content/bindings/popup.xml#popup-scrollbars">
+ <resources>
+ <stylesheet src="chrome://communicator/content/autocomplete.css"/>
+ <stylesheet src="chrome://global/skin/autocomplete.css"/>
+ </resources>
+
+ <implementation>
+ <method name="removeOpenAttribute">
+ <parameter name="parentNode"/>
+ <body><![CDATA[
+ parentNode.removeAttribute("open");
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popuphiding"><![CDATA[
+ setTimeout(this.removeOpenAttribute, 0, this.parentNode);
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
+
+ <implementation>
+ <method name="showPopup">
+ <body><![CDATA[
+ var textbox = document.getBindingParent(this);
+ var kids = textbox.getElementsByClassName("autocomplete-history-popup");
+ if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup
+ var w = textbox.boxObject.width;
+ if (w != kids[0].boxObject.width)
+ kids[0].width = w;
+ kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
+ textbox.setAttribute("open", "true");
+ }
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mousedown"><![CDATA[
+ this.showPopup();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/xpfe/components/devtools/content/devtoolsOverlay.js b/xpfe/components/devtools/content/devtoolsOverlay.js
new file mode 100644
index 000000000..d4392f133
--- /dev/null
+++ b/xpfe/components/devtools/content/devtoolsOverlay.js
@@ -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/. */
+
+function toScratchpad() {
+ toOpenWindowByType("devtools:scratchpad",
+ "chrome://communicator/content/devtools/scratchpad.xul",
+ "resizable");
+}
+
+
diff --git a/xpfe/components/devtools/content/devtoolsOverlay.xul b/xpfe/components/devtools/content/devtoolsOverlay.xul
new file mode 100644
index 000000000..f26a4d695
--- /dev/null
+++ b/xpfe/components/devtools/content/devtoolsOverlay.xul
@@ -0,0 +1,31 @@
+<?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/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % overlayDTD SYSTEM "chrome://communicator/locale/devtools/devtoolsOverlay.dtd">
+%overlayDTD;
+]>
+
+<overlay id="devtools-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://communicator/content/devtools/devtoolsOverlay.js" />
+
+ <menupopup id="devtoolsPopup">
+ <menuitem id="cmd_scratchpad"
+ insertafter="menu_inspector,javascriptConsole"
+ oncommand="toScratchpad();"
+ label="&scratchpad.label;"/>
+ </menupopup>
+
+ <menupopup id="toolsPopup">
+ <menuitem id="cmd_scratchpad"
+ insertafter="menu_inspector,javascriptConsolemenu_inspector"
+ oncommand="toScratchpad();"
+ label="&scratchpad.label;"/>
+ </menupopup>
+</overlay>
+
diff --git a/xpfe/components/devtools/content/scratchpad/scratchpad.js b/xpfe/components/devtools/content/scratchpad/scratchpad.js
new file mode 100644
index 000000000..351f568cc
--- /dev/null
+++ b/xpfe/components/devtools/content/scratchpad/scratchpad.js
@@ -0,0 +1,699 @@
+/* vim:set ts=2 sw=2 sts=2 et:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Scratchpad.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Rob Campbell <robcee@mozilla.com> (original author)
+ * Erik Vold <erikvvold@gmail.com>
+ * David Dahl <ddahl@mozilla.com>
+ * Mihai Sucan <mihai.sucan@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK *****/
+
+/*
+ * Original version history can be found here:
+ * https://github.com/mozilla/workspace
+ *
+ * Copied and relicensed from the Public Domain.
+ * See bug 653934 for details.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=653934
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource:///modules/communicator/devtools/PropertyPanel.jsm");
+
+const SCRATCHPAD_CONTEXT_CONTENT = 1;
+const SCRATCHPAD_CONTEXT_BROWSER = 2;
+const SCRATCHPAD_WINDOW_URL = "chrome://communicator/content/devtools/scratchpad.xul";
+const SCRATCHPAD_L10N = "chrome://communicator/locale/devtools/scratchpad.properties";
+const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
+
+const PREF_TABSIZE = "devtools.editor.tabsize";
+const PREF_EXPANDTAB = "devtools.editor.expandtab";
+
+/**
+ * The scratchpad object handles the Scratchpad window functionality.
+ */
+var Scratchpad = {
+ /**
+ * The script execution context. This tells Scratchpad in which context the
+ * script shall execute.
+ *
+ * Possible values:
+ * - SCRATCHPAD_CONTEXT_CONTENT to execute code in the context of the current
+ * tab content window object.
+ * - SCRATCHPAD_CONTEXT_BROWSER to execute code in the context of the
+ * currently active chrome window object.
+ */
+ executionContext: SCRATCHPAD_CONTEXT_CONTENT,
+
+ /**
+ * Retrieve the xul:textbox DOM element. This element holds the source code
+ * the user writes and executes.
+ */
+ get textbox() document.getElementById("scratchpad-textbox"),
+
+ /**
+ * Retrieve the xul:statusbarpanel DOM element. The status bar tells the
+ * current code execution context.
+ */
+ get statusbarStatus() document.getElementById("scratchpad-status"),
+
+ /**
+ * Get the selected text from the textbox.
+ */
+ get selectedText()
+ {
+ return this.textbox.value.substring(this.textbox.selectionStart,
+ this.textbox.selectionEnd);
+ },
+
+ /**
+ * Get the most recent chrome window of type navigator:browser.
+ */
+ get browserWindow() Services.wm.getMostRecentWindow("navigator:browser"),
+
+ /**
+ * Reference to the last chrome window of type navigator:browser. We use this
+ * to check if the chrome window changed since the last code evaluation.
+ */
+ _previousWindow: null,
+
+ /**
+ * Get the gBrowser object of the most recent browser window.
+ */
+ get gBrowser()
+ {
+ let recentWin = this.browserWindow;
+ return recentWin ? recentWin.gBrowser : null;
+ },
+
+ insertIntro: function SP_insertIntro()
+ {
+ this.textbox.value = this.strings.GetStringFromName("scratchpadIntro");
+ },
+
+ /**
+ * Cached Cu.Sandbox object for the active tab content window object.
+ */
+ _contentSandbox: null,
+
+ /**
+ * Get the Cu.Sandbox object for the active tab content window object. Note
+ * that the returned object is cached for later reuse. The cached object is
+ * kept only for the current location in the current tab of the current
+ * browser window and it is reset for each context switch,
+ * navigator:browser window switch, tab switch or navigation.
+ */
+ get contentSandbox()
+ {
+ if (!this.browserWindow) {
+ Cu.reportError(this.strings.
+ GetStringFromName("browserWindow.unavailable"));
+ return;
+ }
+
+ if (!this._contentSandbox ||
+ this.browserWindow != this._previousBrowserWindow ||
+ this._previousBrowser != this.gBrowser.selectedBrowser ||
+ this._previousLocation != this.gBrowser.contentWindow.location.href) {
+ let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
+ this._contentSandbox = new Cu.Sandbox(contentWindow,
+ { sandboxPrototype: contentWindow, wantXrays: false });
+
+ this._previousBrowserWindow = this.browserWindow;
+ this._previousBrowser = this.gBrowser.selectedBrowser;
+ this._previousLocation = contentWindow.location.href;
+ }
+
+ return this._contentSandbox;
+ },
+
+ /**
+ * Cached Cu.Sandbox object for the most recently active navigator:browser
+ * chrome window object.
+ */
+ _chromeSandbox: null,
+
+ /**
+ * Get the Cu.Sandbox object for the most recently active navigator:browser
+ * chrome window object. Note that the returned object is cached for later
+ * reuse. The cached object is kept only for the current browser window and it
+ * is reset for each context switch or navigator:browser window switch.
+ */
+ get chromeSandbox()
+ {
+ if (!this.browserWindow) {
+ Cu.reportError(this.strings.
+ GetStringFromName("browserWindow.unavailable"));
+ return;
+ }
+
+ if (!this._chromeSandbox ||
+ this.browserWindow != this._previousBrowserWindow) {
+ this._chromeSandbox = new Cu.Sandbox(this.browserWindow,
+ { sandboxPrototype: this.browserWindow, wantXrays: false });
+
+ this._previousBrowserWindow = this.browserWindow;
+ }
+
+ return this._chromeSandbox;
+ },
+
+ /**
+ * Drop the textbox selection.
+ */
+ deselect: function SP_deselect()
+ {
+ this.textbox.selectionEnd = this.textbox.selectionStart;
+ },
+
+ /**
+ * Select a specific range in the Scratchpad xul:textbox.
+ *
+ * @param number aStart
+ * Selection range start.
+ * @param number aEnd
+ * Selection range end.
+ */
+ selectRange: function SP_selectRange(aStart, aEnd)
+ {
+ this.textbox.selectionStart = aStart;
+ this.textbox.selectionEnd = aEnd;
+ },
+
+ /**
+ * Evaluate a string in the active tab content window.
+ *
+ * @param string aString
+ * The script you want evaluated.
+ * @return mixed
+ * The script evaluation result.
+ */
+ evalInContentSandbox: function SP_evalInContentSandbox(aString)
+ {
+ let result;
+ try {
+ result = Cu.evalInSandbox(aString, this.contentSandbox, "1.8",
+ "Scratchpad", 1);
+ }
+ catch (ex) {
+ this.openWebConsole();
+
+ let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
+
+ let scriptError = Cc["@mozilla.org/scripterror;1"].
+ createInstance(Ci.nsIScriptError);
+
+ scriptError.initWithWindowID(ex.message + "\n" + ex.stack, ex.fileName,
+ "", ex.lineNumber, 0, scriptError.errorFlag,
+ "content javascript",
+ this.getWindowId(contentWindow));
+
+ Services.console.logMessage(scriptError);
+ }
+
+ return result;
+ },
+
+ /**
+ * Evaluate a string in the most recent navigator:browser chrome window.
+ *
+ * @param string aString
+ * The script you want evaluated.
+ * @return mixed
+ * The script evaluation result.
+ */
+ evalInChromeSandbox: function SP_evalInChromeSandbox(aString)
+ {
+ let result;
+ try {
+ result = Cu.evalInSandbox(aString, this.chromeSandbox, "1.8",
+ "Scratchpad", 1);
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ Cu.reportError(ex.stack);
+ this.openErrorConsole();
+ }
+
+ return result;
+ },
+
+ /**
+ * Evaluate a string in the currently desired context, that is either the
+ * chrome window or the tab content window object.
+ *
+ * @param string aString
+ * The script you want to evaluate.
+ * @return mixed
+ * The script evaluation result.
+ */
+ evalForContext: function SP_evaluateForContext(aString)
+ {
+ return this.executionContext == SCRATCHPAD_CONTEXT_CONTENT ?
+ this.evalInContentSandbox(aString) :
+ this.evalInChromeSandbox(aString);
+ },
+
+ /**
+ * Execute the selected text (if any) or the entire textbox content in the
+ * current context.
+ */
+ run: function SP_run()
+ {
+ let selection = this.selectedText || this.textbox.value;
+ let result = this.evalForContext(selection);
+ this.deselect();
+ return [selection, result];
+ },
+
+ /**
+ * Execute the selected text (if any) or the entire textbox content in the
+ * current context. The resulting object is opened up in the Property Panel
+ * for inspection.
+ */
+ inspect: function SP_inspect()
+ {
+ let [selection, result] = this.run();
+
+ if (result) {
+ this.openPropertyPanel(selection, result);
+ }
+ },
+
+ /**
+ * Execute the selected text (if any) or the entire textbox content in the
+ * current context. The evaluation result is inserted into the textbox after
+ * the selected text, or at the end of the textbox value if there is no
+ * selected text.
+ */
+ display: function SP_display()
+ {
+ let selectionStart = this.textbox.selectionStart;
+ let selectionEnd = this.textbox.selectionEnd;
+ if (selectionStart == selectionEnd) {
+ selectionEnd = this.textbox.value.length;
+ }
+
+ let [selection, result] = this.run();
+ if (!result) {
+ return;
+ }
+
+ let firstPiece = this.textbox.value.slice(0, selectionEnd);
+ let lastPiece = this.textbox.value.
+ slice(selectionEnd, this.textbox.value.length);
+
+ let newComment = "/*\n" + result.toString() + "\n*/";
+
+ this.textbox.value = firstPiece + newComment + lastPiece;
+
+ // Select the added comment.
+ this.selectRange(firstPiece.length, firstPiece.length + newComment.length);
+ },
+
+ /**
+ * Open the Property Panel to inspect the given object.
+ *
+ * @param string aEvalString
+ * The string that was evaluated. This is re-used when the user updates
+ * the properties list, by clicking the Update button.
+ * @param object aOutputObject
+ * The object to inspect, which is the aEvalString evaluation result.
+ * @return object
+ * The PropertyPanel object instance.
+ */
+ openPropertyPanel: function SP_openPropertyPanel(aEvalString, aOutputObject)
+ {
+ let self = this;
+ let propPanel;
+ // The property panel has a button:
+ // `Update`: reexecutes the string executed on the command line. The
+ // result will be inspected by this panel.
+ let buttons = [];
+
+ // If there is a evalString passed to this function, then add a `Update`
+ // button to the panel so that the evalString can be reexecuted to update
+ // the content of the panel.
+ if (aEvalString !== null) {
+ buttons.push({
+ label: this.strings.
+ GetStringFromName("propertyPanel.updateButton.label"),
+ accesskey: this.strings.
+ GetStringFromName("propertyPanel.updateButton.accesskey"),
+ oncommand: function () {
+ try {
+ let result = self.evalForContext(aEvalString);
+
+ if (result !== undefined) {
+ propPanel.treeView.data = result;
+ }
+ }
+ catch (ex) { }
+ }
+ });
+ }
+
+ let doc = this.browserWindow.document;
+ let parent = doc.getElementById("mainPopupSet");
+ let title = aOutputObject.toString();
+ propPanel = new PropertyPanel(parent, doc, title, aOutputObject, buttons);
+
+ let panel = propPanel.panel;
+ panel.setAttribute("class", "scratchpad_propertyPanel");
+ panel.openPopup(null, "after_pointer", 0, 0, false, false);
+ panel.sizeTo(200, 400);
+
+ return propPanel;
+ },
+
+ // Menu Operations
+
+ /**
+ * Open a new Scratchpad window.
+ */
+ openScratchpad: function SP_openScratchpad()
+ {
+ Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
+ SCRATCHPAD_WINDOW_FEATURES, null);
+ },
+
+ /**
+ * Export the textbox content to a file.
+ *
+ * @param nsILocalFile aFile
+ * The file where you want to save the textbox content.
+ * @param boolean aNoConfirmation
+ * If the file already exists, ask for confirmation?
+ * @param boolean aSilentError
+ * True if you do not want to display an error when file save fails,
+ * false otherwise.
+ * @param function aCallback
+ * Optional function you want to call when file save completes. It will
+ * get the following arguments:
+ * 1) the nsresult status code for the export operation.
+ */
+ exportToFile: function SP_exportToFile(aFile, aNoConfirmation, aSilentError,
+ aCallback)
+ {
+ if (!aNoConfirmation && aFile.exists() &&
+ !window.confirm(this.strings.
+ GetStringFromName("export.fileOverwriteConfirmation"))) {
+ return;
+ }
+
+ let fs = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ let modeFlags = 0x02 | 0x08 | 0x20;
+ fs.init(aFile, modeFlags, 0644, fs.DEFER_OPEN);
+
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let input = converter.convertToInputStream(this.textbox.value);
+
+ let self = this;
+ NetUtil.asyncCopy(input, fs, function(aStatus) {
+ if (!aSilentError && !Components.isSuccessCode(aStatus)) {
+ window.alert(self.strings.GetStringFromName("saveFile.failed"));
+ }
+
+ if (aCallback) {
+ aCallback.call(self, aStatus);
+ }
+ });
+ },
+
+ /**
+ * Read the content of a file and put it into the textbox.
+ *
+ * @param nsILocalFile aFile
+ * The file you want to save the textbox content into.
+ * @param boolean aSilentError
+ * True if you do not want to display an error when file load fails,
+ * false otherwise.
+ * @param function aCallback
+ * Optional function you want to call when file load completes. It will
+ * get the following arguments:
+ * 1) the nsresult status code for the import operation.
+ * 2) the data that was read from the file, if any.
+ */
+ importFromFile: function SP_importFromFile(aFile, aSilentError, aCallback)
+ {
+ // Prevent file type detection.
+ let channel = NetUtil.newChannel(aFile);
+ channel.contentType = "application/javascript";
+
+ let self = this;
+ NetUtil.asyncFetch(channel, function(aInputStream, aStatus) {
+ let content = null;
+
+ if (Components.isSuccessCode(aStatus)) {
+ content = NetUtil.readInputStreamToString(aInputStream,
+ aInputStream.available());
+ self.textbox.value = content;
+ }
+ else if (!aSilentError) {
+ window.alert(self.strings.GetStringFromName("openFile.failed"));
+ }
+
+ if (aCallback) {
+ aCallback.call(self, aStatus, content);
+ }
+ });
+ },
+
+ /**
+ * Open a file to edit in the Scratchpad.
+ */
+ openFile: function SP_openFile()
+ {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(window, this.strings.GetStringFromName("openFile.title"),
+ Ci.nsIFilePicker.modeOpen);
+ fp.defaultString = "";
+ if (fp.show() != Ci.nsIFilePicker.returnCancel) {
+ document.title = this.filename = fp.file.path;
+ this.importFromFile(fp.file);
+ }
+ },
+
+ /**
+ * Save the textbox content to the currently open file.
+ */
+ saveFile: function SP_saveFile()
+ {
+ if (!this.filename) {
+ return this.saveFileAs();
+ }
+
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(this.filename);
+ this.exportToFile(file, true);
+ },
+
+ /**
+ * Save the textbox content to a new file.
+ */
+ saveFileAs: function SP_saveFileAs()
+ {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(window, this.strings.GetStringFromName("saveFileAs"),
+ Ci.nsIFilePicker.modeSave);
+ fp.defaultString = "scratchpad.js";
+ if (fp.show() != Ci.nsIFilePicker.returnCancel) {
+ document.title = this.filename = fp.file.path;
+ this.exportToFile(fp.file, true);
+ }
+ },
+
+ /**
+ * Open the Error Console.
+ */
+ openErrorConsole: function SP_openErrorConsole()
+ {
+ this.browserWindow.toJavaScriptConsole();
+ },
+
+ /**
+ * Open the Web Console.
+ */
+ openWebConsole: function SP_openWebConsole()
+ {
+ try {
+ if (!this.browserWindow.HUDConsoleUI.getOpenHUD()) {
+ this.browserWindow.HUDConsoleUI.toggleHUD();
+ }
+ this.browserWindow.focus();
+ }
+ catch (ex) {
+ this.openErrorConsole();
+ }
+ },
+
+ /**
+ * Set the current execution context to be the active tab content window.
+ */
+ setContentContext: function SP_setContentContext()
+ {
+ let content = document.getElementById("sp-menu-content");
+ document.getElementById("sp-menu-browser").removeAttribute("checked");
+ content.setAttribute("checked", true);
+ this.executionContext = SCRATCHPAD_CONTEXT_CONTENT;
+ this.statusbarStatus.label = content.getAttribute("label");
+ this.resetContext();
+ },
+
+ /**
+ * Set the current execution context to be the most recent chrome window.
+ */
+ setBrowserContext: function SP_setBrowserContext()
+ {
+ let browser = document.getElementById("sp-menu-browser");
+ document.getElementById("sp-menu-content").removeAttribute("checked");
+ browser.setAttribute("checked", true);
+ this.executionContext = SCRATCHPAD_CONTEXT_BROWSER;
+ this.statusbarStatus.label = browser.getAttribute("label");
+ this.resetContext();
+ },
+
+ /**
+ * Reset the cached Cu.Sandbox object for the current context.
+ */
+ resetContext: function SP_resetContext()
+ {
+ this._chromeSandbox = null;
+ this._contentSandbox = null;
+ this._previousWindow = null;
+ this._previousBrowser = null;
+ this._previousLocation = null;
+ },
+
+ /**
+ * Gets the ID of the outer window of the given DOM window object.
+ *
+ * @param nsIDOMWindow aWindow
+ * @return integer
+ * the outer window ID
+ */
+ getWindowId: function SP_getWindowId(aWindow)
+ {
+ return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+ },
+
+ /**
+ * The Scratchpad window DOMContentLoaded event handler.
+ */
+ onLoad: function SP_onLoad()
+ {
+ let chromeContextMenu = document.getElementById("sp-menu-browser");
+ let errorConsoleMenu = document.getElementById("sp-menu-errorConsole");
+ let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
+ let chromeContextCommand = document.getElementById("sp-cmd-browserContext");
+
+ let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED, true);
+ if (chrome) {
+ chromeContextMenu.removeAttribute("hidden");
+ errorConsoleMenu.removeAttribute("hidden");
+ errorConsoleCommand.removeAttribute("disabled");
+ chromeContextCommand.removeAttribute("disabled");
+ }
+
+ let tabsize = Services.prefs.getIntPref(PREF_TABSIZE, 2);
+ if (tabsize < 1) {
+ // tabsize is invalid, clear back to the default value.
+ Services.prefs.clearUserPref(PREF_TABSIZE);
+ tabsize = Services.prefs.getIntPref(PREF_TABSIZE, 2);
+ }
+
+ let expandtab = Services.prefs.getBoolPref(PREF_EXPANDTAB, true);
+ this._tabCharacter = expandtab ? (new Array(tabsize + 1)).join(" ") : "\t";
+ this.textbox.style.MozTabSize = tabsize;
+
+ // Force LTR direction (otherwise the textbox inherits the locale direction)
+ this.textbox.style.direction = "ltr";
+
+ this.textbox.style.border = "none";
+
+ this.insertIntro();
+
+ // Make the Tab key work.
+ this.textbox.addEventListener("keypress", this.onKeypress.bind(this), false);
+
+ this.textbox.focus();
+ },
+
+ /**
+ * The textbox keypress event handler which allows users to indent code using
+ * the Tab key.
+ *
+ * @param nsIDOMEvent aEvent
+ */
+ onKeypress: function SP_onKeypress(aEvent)
+ {
+ if (aEvent.keyCode == aEvent.DOM_VK_TAB) {
+ this.insertTextAtCaret(this._tabCharacter);
+ aEvent.preventDefault();
+ }
+ },
+
+ /**
+ * Insert text at the current caret location.
+ *
+ * @param string aText
+ */
+ insertTextAtCaret: function SP_insertTextAtCaret(aText)
+ {
+ let firstPiece = this.textbox.value.substring(0, this.textbox.selectionStart);
+ let lastPiece = this.textbox.value.substring(this.textbox.selectionEnd);
+ this.textbox.value = firstPiece + aText + lastPiece;
+
+ let newCaretPosition = firstPiece.length + aText.length;
+ this.selectRange(newCaretPosition, newCaretPosition);
+ },
+};
+
+XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
+ return Services.strings.createBundle(SCRATCHPAD_L10N);
+});
+
+addEventListener("DOMContentLoaded", Scratchpad.onLoad.bind(Scratchpad), false);
+
diff --git a/xpfe/components/devtools/content/scratchpad/scratchpad.xul b/xpfe/components/devtools/content/scratchpad/scratchpad.xul
new file mode 100644
index 000000000..e0a01270a
--- /dev/null
+++ b/xpfe/components/devtools/content/scratchpad/scratchpad.xul
@@ -0,0 +1,352 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+ - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ -
+ - The contents of this file are subject to the Mozilla Public License Version
+ - 1.1 (the "License"); you may not use this file except in compliance with
+ - the License. You may obtain a copy of the License at
+ - http://www.mozilla.org/MPL/
+ -
+ - Software distributed under the License is distributed on an "AS IS" basis,
+ - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ - for the specific language governing rights and limitations under the
+ - License.
+ -
+ - The Original Code is Scratchpad.
+ -
+ - The Initial Developer of the Original Code is
+ - The Mozilla Foundation.
+ - Portions created by the Initial Developer are Copyright (C) 2011
+ - the Initial Developer. All Rights Reserved.
+ -
+ - Contributor(s):
+ - Rob Campbell <robcee@mozilla.com> (original author)
+ - Mihai Sucan <mihai.sucan@gmail.com>
+ - Erik Vold <erikvvold@gmail.com>
+ -
+ - Alternatively, the contents of this file may be used under the terms of
+ - either the GNU General Public License Version 2 or later (the "GPL"), or
+ - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ - in which case the provisions of the GPL or the LGPL are applicable instead
+ - of those above. If you wish to allow use of your version of this file only
+ - under the terms of either the GPL or the LGPL, and not to allow others to
+ - use your version of this file under the terms of the MPL, indicate your
+ - decision by deleting the provisions above and replace them with the notice
+ - and other provisions required by the GPL or the LGPL. If you do not delete
+ - the provisions above, a recipient may use your version of this file under
+ - the terms of any one of the MPL, the GPL or the LGPL.
+ -
+ - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE window [
+<!ENTITY % scratchpadDTD SYSTEM "chrome://communicator/locale/devtools/scratchpad.dtd" >
+ %scratchpadDTD;
+]>
+
+<?xml-stylesheet href="chrome://communicator/skin/communicator.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<window id="main-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&window.title;"
+ windowtype="devtools:scratchpad"
+ screenX="4" screenY="4"
+ width="640" height="480"
+ persist="screenX screenY width height sizemode">
+
+<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+<script type="application/javascript" src="chrome://communicator/content/devtools/scratchpad.js"/>
+
+<commandset id="editMenuCommands"/>
+
+<commandset id="sp-commandset">
+ <command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
+ <command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/>
+ <command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
+ <command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
+
+ <!-- TODO: bug 650340 - implement printFile()
+ <command id="sp-cmd-printFile" oncommand="Scratchpad.printFile();" disabled="true"/>
+ -->
+
+ <command id="sp-cmd-close" oncommand="window.close();"/>
+ <command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
+ <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
+ <command id="sp-cmd-display" oncommand="Scratchpad.display();"/>
+ <command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
+ <command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
+ <command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/>
+ <command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
+ <command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
+</commandset>
+
+<keyset id="sp-keyset">
+ <key id="sp-key-window"
+ key="&newWindowCmd.commandkey;"
+ command="sp-cmd-newWindow"
+ modifiers="accel"/>
+ <key id="sp-key-open"
+ key="&openFileCmd.commandkey;"
+ command="sp-cmd-openFile"
+ modifiers="accel"/>
+ <key id="sp-key-save"
+ key="&saveFileCmd.commandkey;"
+ command="sp-cmd-save"
+ modifiers="accel"/>
+ <key id="sp-key-close"
+ key="&closeCmd.key;"
+ command="sp-cmd-close"
+ modifiers="accel"/>
+
+ <!-- TODO: bug 650340 - implement printFile
+ <key id="sp-key-printFile"
+ key="&printCmd.commandkey;"
+ command="sp-cmd-printFile"
+ modifiers="accel"/>
+ -->
+
+ <key id="key_cut"
+ key="&cutCmd.key;"
+ modifiers="accel"/>
+
+ <key id="key_copy"
+ key="&copyCmd.key;"
+ modifiers="accel"/>
+ <key id="key_paste"
+ key="&pasteCmd.key;"
+ modifiers="accel"/>
+ <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
+ <key id="key_undo" key="&undoCmd.key;" modifiers="accel"/>
+ <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
+ <key id="sp-key-run"
+ key="&run.key;"
+ command="sp-cmd-run"
+ modifiers="accel"/>
+ <!--
+ <key id="sp-key-inspect"
+ key="&inspect.key;"
+ command="sp-cmd-inspect"
+ modifiers="accel"/>
+ <key id="sp-key-display"
+ key="&display.key;"
+ command="sp-cmd-display"
+ modifiers="accel"/>
+ -->
+ <key id="sp-key-errorConsole"
+ key="&errorConsoleCmd.commandkey;"
+ command="sp-cmd-errorConsole"
+ modifiers="accel,shift"/>
+ <key id="sp-key-webConsole"
+ key="&webConsoleCmd.commandkey;"
+ command="sp-cmd-webConsole"
+ modifiers="accel,shift"/>
+</keyset>
+
+
+<menubar id="sp-menubar" xpfe="false">
+ <menu id="sp-file-menu" label="&fileMenu.label;"
+ accesskey="&fileMenu.accesskey;">
+ <menupopup id="sp-menu-filepopup">
+ <menuitem id="sp-menu-newscratchpad"
+ label="&newWindowCmd.label;"
+ accesskey="&newWindowCmd.accesskey;"
+ key="sp-key-window"
+ command="sp-cmd-newWindow"/>
+ <menuseparator/>
+ <menuitem id="sp-menu-open"
+ label="&openFileCmd.label;"
+ command="sp-cmd-openFile"
+ key="sp-key-open"
+ accesskey="&openFileCmd.accesskey;"/>
+ <menuitem id="sp-menu-save"
+ label="&saveFileCmd.label;"
+ accesskey="&saveFileCmd.accesskey;"
+ key="sp-key-save"
+ command="sp-cmd-save"/>
+ <menuitem id="sp-menu-saveas"
+ label="&saveFileAsCmd.label;"
+ accesskey="&saveFileAsCmd.accesskey;"
+ command="sp-cmd-saveas"/>
+ <menuseparator/>
+
+ <!-- TODO: bug 650340 - implement printFile
+ <menuitem id="sp-menu-print"
+ label="&printCmd.label;"
+ accesskey="&printCmd.accesskey;"
+ command="sp-cmd-printFile"/>
+ <menuseparator/>
+ -->
+
+ <menuitem id="sp-menu-close"
+ label="&closeCmd.label;"
+ key="sp-key-close"
+ accesskey="&closeCmd.accesskey;"
+ command="sp-cmd-close"/>
+ </menupopup>
+ </menu>
+
+ <menu id="sp-edit-menu" label="&editMenu.label;"
+ accesskey="&editMenu.accesskey;">
+ <menupopup id="sp-menu_editpopup">
+ <menuitem id="sp-menu-undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ accesskey="&undoCmd.accesskey;"
+ disabled="true"
+ command="cmd_undo"/>
+ <menuitem id="sp-menu-redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ disabled="true"
+ accesskey="&redoCmd.accesskey;"
+ command="cmd_redo"/>
+ <menuseparator/>
+ <menuitem id="sp-menu-cut"
+ label="&cutCmd.label;"
+ key="key_cut"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="sp-menu-copy"
+ label="&copyCmd.label;"
+ key="key_copy"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="sp-menu-paste"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuseparator/>
+ <menuitem id="sp-menu-selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+
+ <!-- TODO: bug 650345 - implement search and replace
+ <menuitem id="sp-menu-find"
+ label="&findOnCmd.label;"
+ accesskey="&findOnCmd.accesskey;"
+ key="key_find"
+ disabled="true"
+ command="cmd_find"/>
+ <menuitem id="sp-menu-findAgain"
+ label="&findAgainCmd.label;"
+ accesskey="&findAgainCmd.accesskey;"
+ key="key_findAgain"
+ disabled="true"
+ command="cmd_findAgain"/>
+ <menuseparator id="sp-execute-separator"/>
+ -->
+
+ </menupopup>
+ </menu>
+
+ <menu id="sp-execute-menu" label="&executeMenu.label;"
+ accesskey="&executeMenu.accesskey;">
+ <menupopup id="sp-menu_executepopup">
+ <menuitem id="sp-text-run"
+ label="&run.label;"
+ accesskey="&run.accesskey;"
+ key="sp-key-run"
+ command="sp-cmd-run"/>
+ <!--
+ <menuitem id="sp-text-inspect"
+ label="&inspect.label;"
+ accesskey="&inspect.accesskey;"
+ key="sp-key-inspect"
+ command="sp-cmd-inspect"/>
+ <menuitem id="sp-text-display"
+ label="&display.label;"
+ accesskey="&display.accesskey;"
+ key="sp-key-display"
+ command="sp-cmd-display"/>
+ -->
+ </menupopup>
+ </menu>
+
+ <menu id="sp-environment-menu"
+ label="&environmentMenu.label;"
+ accesskey="&environmentMenu.accesskey;">
+ <menupopup id="sp-menu-environment">
+ <menuitem id="sp-menu-content"
+ label="&contentContext.label;"
+ accesskey="&contentContext.accesskey;"
+ command="sp-cmd-contentContext"
+ checked="true"
+ type="radio"/>
+ <menuitem id="sp-menu-browser" hidden="true"
+ command="sp-cmd-browserContext"
+ label="&browserContext.label;"
+ accesskey="&browserContext.accesskey;"
+ type="radio"/>
+ <menuseparator/>
+ <menuitem id="sp-menu-resetContext"
+ command="sp-cmd-resetContext"
+ label="&resetContext.label;"
+ accesskey="&resetContext.accesskey;"/>
+ </menupopup>
+ </menu>
+
+ <menu id="sp-tools-menu"
+ label="&toolsMenu.label;"
+ accesskey="&toolsMenu.accesskey;">
+ <menupopup id="sp-menu-tools">
+ <menuitem id="sp-menu-errorConsole" hidden="true"
+ label="&errorConsoleCmd.label;"
+ accesskey="&errorConsoleCmd.accesskey;"
+ key="sp-key-errorConsole"
+ command="sp-cmd-errorConsole"/>
+ <!--
+ <menuitem id="sp-menu-webConsole"
+ label="&webConsoleCmd.label;"
+ accesskey="&webConsoleCmd.accesskey;"
+ key="sp-key-webConsole"
+ command="sp-cmd-webConsole"/>
+ -->
+ </menupopup>
+ </menu>
+</menubar>
+
+<popupset id="scratchpad-popups">
+ <menupopup id="scratchpad-text-popup">
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"/>
+ <menuseparator/>
+ <menuitem id="sp-text-run"
+ label="&run.label;"
+ accesskey="&run.accesskey;"
+ key="sp-key-run"
+ command="sp-cmd-run"/>
+ <!--
+ <menuitem id="sp-text-inspect"
+ label="&inspect.label;"
+ accesskey="&inspect.accesskey;"
+ key="sp-key-inspect"
+ command="sp-cmd-inspect"/>
+ <menuitem id="sp-text-display"
+ label="&display.label;"
+ accesskey="&display.accesskey;"
+ key="sp-key-display"
+ command="sp-cmd-display"/>
+ -->
+ </menupopup>
+</popupset>
+
+<textbox id="scratchpad-textbox"
+ class="monospace"
+ multiline="true"
+ flex="1"
+ context="scratchpad-text-popup"
+ placeholder="&textbox.placeholder1;" />
+<statusbar id="scratchpad-statusbar" align="end">
+ <statusbarpanel id="scratchpad-status"
+ label="&contentContext.label;"
+ class="statusbarpanel-iconic-text"/>
+ <spacer flex="1"/>
+</statusbar>
+</window>
diff --git a/xpfe/components/devtools/devtools-prefs.js b/xpfe/components/devtools/devtools-prefs.js
new file mode 100644
index 000000000..271537b01
--- /dev/null
+++ b/xpfe/components/devtools/devtools-prefs.js
@@ -0,0 +1,8 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+pref("devtools.chrome.enabled", true);
+pref("devtools.editor.tabsize", 2);
+pref("devtools.editor.expandtab", true);
diff --git a/xpfe/components/devtools/jar.mn b/xpfe/components/devtools/jar.mn
new file mode 100644
index 000000000..d12ec0f18
--- /dev/null
+++ b/xpfe/components/devtools/jar.mn
@@ -0,0 +1,14 @@
+# 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/.
+
+comm.jar:
+ content/communicator/devtools/devtoolsOverlay.js (content/devtoolsOverlay.js)
+ content/communicator/devtools/devtoolsOverlay.xul (content/devtoolsOverlay.xul)
+ content/communicator/devtools/scratchpad.js (content/scratchpad/scratchpad.js)
+ content/communicator/devtools/scratchpad.xul (content/scratchpad/scratchpad.xul)
+
+en-US.jar:
+ locale/en-US/communicator/devtools/devtoolsOverlay.dtd (locale/devtoolsOverlay.dtd)
+ locale/en-US/communicator/devtools/scratchpad.dtd (locale/scratchpad.dtd)
+ locale/en-US/communicator/devtools/scratchpad.properties (locale/scratchpad.properties)
diff --git a/xpfe/components/devtools/locale/devtoolsOverlay.dtd b/xpfe/components/devtools/locale/devtoolsOverlay.dtd
new file mode 100644
index 000000000..476ebcbb8
--- /dev/null
+++ b/xpfe/components/devtools/locale/devtoolsOverlay.dtd
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+
+<!ENTITY scratchpad.label "Scratchpad">
+
diff --git a/xpfe/components/devtools/locale/scratchpad.dtd b/xpfe/components/devtools/locale/scratchpad.dtd
new file mode 100644
index 000000000..680a71230
--- /dev/null
+++ b/xpfe/components/devtools/locale/scratchpad.dtd
@@ -0,0 +1,123 @@
+<!-- LOCALIZATION NOTE : FILE This file contains the Scratchpad window strings -->
+<!-- LOCALIZATION NOTE : FILE Do not translate commandkeys -->
+
+<!-- LOCALIZATION NOTE (scratchpad.title):
+ - The Scratchpad is intended to provide a simple text editor for creating
+ - and evaluating bits of JavaScript code for the purposes of function
+ - prototyping, experimentation and convenient scripting.
+ -
+ - It's quite possible that you won't have a good analogue for the word
+ - "Scratchpad" in your locale. You should feel free to find a close
+ - approximation to it or choose a word (or words) that means
+ - "simple discardable text editor". -->
+<!ENTITY window.title "Scratchpad">
+
+<!ENTITY fileMenu.label "File">
+<!ENTITY fileMenu.accesskey "F">
+
+<!ENTITY newWindowCmd.label "New Window">
+<!ENTITY newWindowCmd.accesskey "N">
+<!ENTITY newWindowCmd.commandkey "n">
+
+<!ENTITY openFileCmd.label "Open File…">
+<!ENTITY openFileCmd.accesskey "O">
+<!ENTITY openFileCmd.commandkey "o">
+
+<!ENTITY saveFileCmd.label "Save">
+<!ENTITY saveFileCmd.accesskey "S">
+<!ENTITY saveFileCmd.commandkey "s">
+
+<!ENTITY saveFileAsCmd.label "Save As…">
+<!ENTITY saveFileAsCmd.accesskey "A">
+
+<!ENTITY closeCmd.label "Close">
+<!ENTITY closeCmd.key "W">
+<!ENTITY closeCmd.accesskey "C">
+
+<!ENTITY editMenu.label "Edit">
+<!ENTITY editMenu.accesskey "E">
+
+<!ENTITY undoCmd.label "Undo">
+<!ENTITY undoCmd.key "Z">
+<!ENTITY undoCmd.accesskey "U">
+
+<!ENTITY redoCmd.label "Redo">
+<!ENTITY redoCmd.key "Y">
+<!ENTITY redoCmd.accesskey "R">
+
+<!ENTITY cutCmd.label "Cut">
+<!ENTITY cutCmd.key "X">
+<!ENTITY cutCmd.accesskey "t">
+
+<!ENTITY copyCmd.label "Copy">
+<!ENTITY copyCmd.key "C">
+<!ENTITY copyCmd.accesskey "C">
+
+<!ENTITY pasteCmd.label "Paste">
+<!ENTITY pasteCmd.key "V">
+<!ENTITY pasteCmd.accesskey "P">
+
+<!ENTITY selectAllCmd.label "Select All">
+<!ENTITY selectAllCmd.key "A">
+<!ENTITY selectAllCmd.accesskey "A">
+
+<!ENTITY run.label "Run">
+<!ENTITY run.accesskey "R">
+<!ENTITY run.key "r">
+
+<!ENTITY inspect.label "Inspect">
+<!ENTITY inspect.accesskey "I">
+<!ENTITY inspect.key "i">
+
+<!ENTITY display.label "Display">
+<!ENTITY display.accesskey "D">
+<!ENTITY display.key "l">
+
+<!-- LOCALIZATION NOTE (environmentMenu.label, accesskey): This menu item was
+ - renamed from "Context" to avoid confusion with the right-click context
+ - menu in the text area. It refers to the JavaScript Environment (or context)
+ - the user is evaluating against. I.e., Content (current tab) or Chrome
+ - (browser).
+ -->
+<!ENTITY environmentMenu.label "Environment">
+<!ENTITY environmentMenu.accesskey "N">
+
+
+<!ENTITY contentContext.label "Content">
+<!ENTITY contentContext.accesskey "C">
+
+<!-- LOCALIZATION NOTE (browserContext.label, accesskey): This menu item is used
+ - to select an execution environment for the browser window itself as opposed
+ - to content. This is a feature for browser and addon developers and only
+ - enabled via the devtools.chrome.enabled preference. Formerly, this label
+ - was called "Chrome".
+ -->
+<!ENTITY browserContext.label "Browser">
+<!ENTITY browserContext.accesskey "B">
+
+<!-- LOCALIZATION NOTE (resetContext.label): This command allows the developer
+ - to reset/clear the global object of the environment where the code executes.
+ -->
+<!ENTITY resetContext.label "Reset">
+<!ENTITY resetContext.accesskey "R">
+
+<!ENTITY executeMenu.label "Execute">
+<!ENTITY executeMenu.accesskey "X">
+
+<!ENTITY toolsMenu.label "Tools">
+<!ENTITY toolsMenu.accesskey "T">
+
+<!ENTITY errorConsoleCmd.label "Error Console">
+<!ENTITY errorConsoleCmd.accesskey "C">
+<!ENTITY errorConsoleCmd.commandkey "j">
+
+<!ENTITY webConsoleCmd.label "Web Console">
+<!ENTITY webConsoleCmd.accesskey "W">
+<!ENTITY webConsoleCmd.commandkey "k">
+
+<!-- LOCALIZATION NOTE (textbox.placeholder1): This is some placeholder text
+ - that appears when the Scratchpad's text area is empty and unfocused.
+ - It should be a one-line JavaScript comment, i.e., preceded by '//'
+ -->
+<!ENTITY textbox.placeholder1 "// Enter some JavaScript, select it, right click and select Run, Inspect or Display.">
+
diff --git a/xpfe/components/devtools/locale/scratchpad.properties b/xpfe/components/devtools/locale/scratchpad.properties
new file mode 100644
index 000000000..36f71bc23
--- /dev/null
+++ b/xpfe/components/devtools/locale/scratchpad.properties
@@ -0,0 +1,35 @@
+# LOCALIZATION NOTE (propertyPanel.updateButton.label): Used in the Property
+# Panel that is opened by the Scratchpad window when inspecting an object. This
+# is the Update button label.
+propertyPanel.updateButton.label=Update
+propertyPanel.updateButton.accesskey=U
+
+# LOCALIZATION NOTE (export.fileOverwriteConfirmation): This is displayed when
+# the user attempts to save to an already existing file.
+export.fileOverwriteConfirmation=File exists. Overwrite?
+
+# LOCALIZATION NOTE (browserWindow.unavailable): This error message is shown
+# when Scratchpad does not find any recently active window of navigator:browser
+# type.
+browserWindow.unavailable=Scratchpad cannot find any browser window to execute the code in.
+
+# LOCALIZATION NOTE (openFile.title): This is the file picker title, when you
+# open a file from Scratchpad.
+openFile.title=Open File
+
+# LOCALIZATION NOTE (openFile.failed): This is the message displayed when file
+# open fails.
+openFile.failed=Failed to read the file.
+
+# LOCALIZATION NOTE (saveFileAs): This is the file picker title, when you save
+# a file in Scratchpad.
+saveFileAs=Save File As
+
+# LOCALIZATION NOTE (saveFile.failed): This is the message displayed when file
+# save fails.
+saveFile.failed=The file save operation failed.
+
+# LOCALIZATION NOTE (scratchpadIntro): This is a multi-line comment explaining
+# how to use the Scratchpad. Note that this should be a valid JavaScript
+# comment inside /* and */.
+scratchpadIntro=/*\n * This is a JavaScript Scratchpad.\n *\n * Enter some JavaScript, then Right Click or choose from the Execute Menu:\n * 1. Run to evaluate the selected text,\n * 2. Inspect to bring up an Object Inspector on the result, or,\n * 3. Display to insert the result in a comment after the selection.\n */\n\n
diff --git a/xpfe/components/devtools/modules/PropertyPanel.jsm b/xpfe/components/devtools/modules/PropertyPanel.jsm
new file mode 100644
index 000000000..fde4ebef4
--- /dev/null
+++ b/xpfe/components/devtools/modules/PropertyPanel.jsm
@@ -0,0 +1,612 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is DevTools (HeadsUpDisplay) Console Code
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Rob Campbell <rcampbell@mozilla.com>
+ * Julian Viereck <jviereck@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView",
+ "namesAndValuesOf", "isNonNativeGetter"];
+
+///////////////////////////////////////////////////////////////////////////
+//// Helper for PropertyTreeView
+
+const TYPE_OBJECT = 0, TYPE_FUNCTION = 1, TYPE_ARRAY = 2, TYPE_OTHER = 3;
+
+/**
+ * Figures out the type of aObject and the string to display in the tree.
+ *
+ * @param object aObject
+ * The object to operate on.
+ * @returns object
+ * A object with the form:
+ * {
+ * type: TYPE_OBJECT || TYPE_FUNCTION || TYPE_ARRAY || TYPE_OTHER,
+ * display: string for displaying the object in the tree
+ * }
+ */
+function presentableValueFor(aObject)
+{
+ if (aObject === null || aObject === undefined) {
+ return {
+ type: TYPE_OTHER,
+ display: aObject === undefined ? "undefined" : "null"
+ };
+ }
+
+ let presentable;
+ switch (aObject.constructor && aObject.constructor.name) {
+ case "Array":
+ return {
+ type: TYPE_ARRAY,
+ display: "Array"
+ };
+
+ case "String":
+ return {
+ type: TYPE_OTHER,
+ display: "\"" + aObject + "\""
+ };
+
+ case "Date":
+ case "RegExp":
+ case "Number":
+ case "Boolean":
+ return {
+ type: TYPE_OTHER,
+ display: aObject
+ };
+
+ case "Iterator":
+ return {
+ type: TYPE_OTHER,
+ display: "Iterator"
+ };
+
+ case "Function":
+ presentable = aObject.toString();
+ return {
+ type: TYPE_FUNCTION,
+ display: presentable.substring(0, presentable.indexOf(')') + 1)
+ };
+
+ default:
+ presentable = aObject.toString();
+ let m = /^\[object (\S+)\]/.exec(presentable);
+
+ try {
+ if (typeof aObject == "object" && typeof aObject.next == "function" &&
+ m && m[1] == "Generator") {
+ return {
+ type: TYPE_OTHER,
+ display: m[1]
+ };
+ }
+ }
+ catch (ex) {
+ // window.history.next throws in the typeof check above.
+ return {
+ type: TYPE_OBJECT,
+ display: m ? m[1] : "Object"
+ };
+ }
+
+ if (typeof aObject == "object" && typeof aObject.__iterator__ == "function") {
+ return {
+ type: TYPE_OTHER,
+ display: "Iterator"
+ };
+ }
+
+ return {
+ type: TYPE_OBJECT,
+ display: m ? m[1] : "Object"
+ };
+ }
+}
+
+/**
+ * Tells if the given function is native or not.
+ *
+ * @param function aFunction
+ * The function you want to check if it is native or not.
+ *
+ * @return boolean
+ * True if the given function is native, false otherwise.
+ */
+function isNativeFunction(aFunction)
+{
+ return typeof aFunction == "function" && !("prototype" in aFunction);
+}
+
+/**
+ * Tells if the given property of the provided object is a non-native getter or
+ * not.
+ *
+ * @param object aObject
+ * The object that contains the property.
+ *
+ * @param string aProp
+ * The property you want to check if it is a getter or not.
+ *
+ * @return boolean
+ * True if the given property is a getter, false otherwise.
+ */
+function isNonNativeGetter(aObject, aProp) {
+ if (typeof aObject != "object") {
+ return false;
+ }
+ let desc;
+ while (aObject) {
+ try {
+ if (desc = Object.getOwnPropertyDescriptor(aObject, aProp)) {
+ break;
+ }
+ }
+ catch (ex) {
+ // Native getters throw here. See bug 520882.
+ if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
+ ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
+ return false;
+ }
+ throw ex;
+ }
+ aObject = Object.getPrototypeOf(aObject);
+ }
+ if (desc && desc.get && !isNativeFunction(desc.get)) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Get an array of property name value pairs for the tree.
+ *
+ * @param object aObject
+ * The object to get properties for.
+ * @returns array of object
+ * Objects have the name, value, display, type, children properties.
+ */
+function namesAndValuesOf(aObject)
+{
+ let pairs = [];
+ let value, presentable;
+
+ let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
+
+ for (var propName in aObject) {
+ // See bug 632275: skip deprecated width and height properties.
+ if (isDOMDocument && (propName == "width" || propName == "height")) {
+ continue;
+ }
+
+ // Also skip non-native getters.
+ if (isNonNativeGetter(aObject, propName)) {
+ value = ""; // Value is never displayed.
+ presentable = {type: TYPE_OTHER, display: "Getter"};
+ }
+ else {
+ try {
+ value = aObject[propName];
+ presentable = presentableValueFor(value);
+ }
+ catch (ex) {
+ continue;
+ }
+ }
+
+ let pair = {};
+ pair.name = propName;
+ pair.display = propName + ": " + presentable.display;
+ pair.type = presentable.type;
+ pair.value = value;
+
+ // Convert the pair.name to a number for later sorting.
+ pair.nameNumber = parseFloat(pair.name)
+ if (isNaN(pair.nameNumber)) {
+ pair.nameNumber = false;
+ }
+
+ pairs.push(pair);
+ }
+
+ pairs.sort(function(a, b)
+ {
+ // Sort numbers.
+ if (a.nameNumber !== false && b.nameNumber === false) {
+ return -1;
+ }
+ else if (a.nameNumber === false && b.nameNumber !== false) {
+ return 1;
+ }
+ else if (a.nameNumber !== false && b.nameNumber !== false) {
+ return a.nameNumber - b.nameNumber;
+ }
+ // Sort string.
+ else if (a.name < b.name) {
+ return -1;
+ }
+ else if (a.name > b.name) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ });
+
+ return pairs;
+}
+
+///////////////////////////////////////////////////////////////////////////
+//// PropertyTreeView.
+
+
+/**
+ * This is an implementation of the nsITreeView interface. For comments on the
+ * interface properties, see the documentation:
+ * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsITreeView
+ */
+var PropertyTreeView = function() {
+ this._rows = [];
+};
+
+PropertyTreeView.prototype = {
+
+ /**
+ * Stores the visible rows of the tree.
+ */
+ _rows: null,
+
+ /**
+ * Stores the nsITreeBoxObject for this tree.
+ */
+ _treeBox: null,
+
+ /**
+ * Use this setter to update the content of the tree.
+ *
+ * @param object aObject
+ * The new object to be displayed in the tree.
+ * @returns void
+ */
+ set data(aObject) {
+ let oldLen = this._rows.length;
+ this._rows = this.getChildItems(aObject, true);
+ if (this._treeBox) {
+ this._treeBox.beginUpdateBatch();
+ if (oldLen) {
+ this._treeBox.rowCountChanged(0, -oldLen);
+ }
+ this._treeBox.rowCountChanged(0, this._rows.length);
+ this._treeBox.endUpdateBatch();
+ }
+ },
+
+ /**
+ * Generates the child items for the treeView of a given aItem. If there is
+ * already a children property on the aItem, this cached one is returned.
+ *
+ * @param object aItem
+ * An item of the tree's elements to generate the children for.
+ * @param boolean aRootElement
+ * If set, aItem is handled as an JS object and not as an item
+ * element of the tree.
+ * @returns array of objects
+ * Child items of aItem.
+ */
+ getChildItems: function(aItem, aRootElement)
+ {
+ // If item.children is an array, then the children has already been
+ // computed and can get returned directly.
+ // Skip this checking if aRootElement is true. It could happen, that aItem
+ // is passed as ({children:[1,2,3]}) which would be true, although these
+ // "kind" of children has no value/type etc. data as needed to display in
+ // the tree. As the passed ({children:[1,2,3]}) are instanceof
+ // itsWindow.Array and not this modules's global Array
+ // aItem.children instanceof Array can't be true, but for saftey the
+ // !aRootElement is kept here.
+ if (!aRootElement && aItem && aItem.children instanceof Array) {
+ return aItem.children;
+ }
+
+ let pairs;
+ let newPairLevel;
+
+ if (!aRootElement) {
+ newPairLevel = aItem.level + 1;
+ aItem = aItem.value;
+ }
+ else {
+ newPairLevel = 0;
+ }
+
+ pairs = namesAndValuesOf(aItem);
+
+ for each (var pair in pairs) {
+ pair.level = newPairLevel;
+ pair.isOpened = false;
+ pair.children = pair.type == TYPE_OBJECT || pair.type == TYPE_FUNCTION ||
+ pair.type == TYPE_ARRAY;
+ }
+
+ return pairs;
+ },
+
+ /** nsITreeView interface implementation **/
+
+ selection: null,
+
+ get rowCount() { return this._rows.length; },
+ setTree: function(treeBox) { this._treeBox = treeBox; },
+ getCellText: function(idx, column) { return this._rows[idx].display; },
+ getLevel: function(idx) { return this._rows[idx].level; },
+ isContainer: function(idx) { return !!this._rows[idx].children; },
+ isContainerOpen: function(idx) { return this._rows[idx].isOpened; },
+ isContainerEmpty: function(idx) { return false; },
+ isSeparator: function(idx) { return false; },
+ isSorted: function() { return false; },
+ isEditable: function(idx, column) { return false; },
+ isSelectable: function(row, col) { return true; },
+
+ getParentIndex: function(idx)
+ {
+ if (this.getLevel(idx) == 0) {
+ return -1;
+ }
+ for (var t = idx - 1; t >= 0 ; t--) {
+ if (this.isContainer(t)) {
+ return t;
+ }
+ }
+ return -1;
+ },
+
+ hasNextSibling: function(idx, after)
+ {
+ var thisLevel = this.getLevel(idx);
+ return this._rows.slice(after + 1).some(function (r) r.level == thisLevel);
+ },
+
+ toggleOpenState: function(idx)
+ {
+ var item = this._rows[idx];
+ if (!item.children) {
+ return;
+ }
+
+ this._treeBox.beginUpdateBatch();
+ if (item.isOpened) {
+ item.isOpened = false;
+
+ var thisLevel = item.level;
+ var t = idx + 1, deleteCount = 0;
+ while (t < this._rows.length && this.getLevel(t++) > thisLevel) {
+ deleteCount++;
+ }
+
+ if (deleteCount) {
+ this._rows.splice(idx + 1, deleteCount);
+ this._treeBox.rowCountChanged(idx + 1, -deleteCount);
+ }
+ }
+ else {
+ item.isOpened = true;
+
+ var toInsert = this.getChildItems(item);
+ item.children = toInsert;
+ this._rows.splice.apply(this._rows, [idx + 1, 0].concat(toInsert));
+
+ this._treeBox.rowCountChanged(idx + 1, toInsert.length);
+ }
+ this._treeBox.invalidateRow(idx);
+ this._treeBox.endUpdateBatch();
+ },
+
+ getImageSrc: function(idx, column) { },
+ getProgressMode : function(idx,column) { },
+ getCellValue: function(idx, column) { },
+ cycleHeader: function(col, elem) { },
+ selectionChanged: function() { },
+ cycleCell: function(idx, column) { },
+ performAction: function(action) { },
+ performActionOnCell: function(action, index, column) { },
+ performActionOnRow: function(action, row) { },
+ getRowProperties: function(idx, column, prop) { },
+ getCellProperties: function(idx, column, prop) { },
+ getColumnProperties: function(column, element, prop) { },
+
+ setCellValue: function(row, col, value) { },
+ setCellText: function(row, col, value) { },
+ drop: function(index, orientation, dataTransfer) { },
+ canDrop: function(index, orientation, dataTransfer) { return false; }
+};
+
+///////////////////////////////////////////////////////////////////////////
+//// Helper for creating the panel.
+
+/**
+ * Creates a DOMNode and sets all the attributes of aAttributes on the created
+ * element.
+ *
+ * @param nsIDOMDocument aDocument
+ * Document to create the new DOMNode.
+ * @param string aTag
+ * Name of the tag for the DOMNode.
+ * @param object aAttributes
+ * Attributes set on the created DOMNode.
+ * @returns nsIDOMNode
+ */
+function createElement(aDocument, aTag, aAttributes)
+{
+ let node = aDocument.createElement(aTag);
+ for (var attr in aAttributes) {
+ node.setAttribute(attr, aAttributes[attr]);
+ }
+ return node;
+}
+
+/**
+ * Creates a new DOMNode and appends it to aParent.
+ *
+ * @param nsIDOMDocument aDocument
+ * Document to create the new DOMNode.
+ * @param nsIDOMNode aParent
+ * A parent node to append the created element.
+ * @param string aTag
+ * Name of the tag for the DOMNode.
+ * @param object aAttributes
+ * Attributes set on the created DOMNode.
+ * @returns nsIDOMNode
+ */
+function appendChild(aDocument, aParent, aTag, aAttributes)
+{
+ let node = createElement(aDocument, aTag, aAttributes);
+ aParent.appendChild(node);
+ return node;
+}
+
+///////////////////////////////////////////////////////////////////////////
+//// PropertyPanel
+
+/**
+ * Creates a new PropertyPanel.
+ *
+ * @param nsIDOMNode aParent
+ * Parent node to append the created panel to.
+ * @param nsIDOMDocument aDocument
+ * Document to create the new nodes on.
+ * @param string aTitle
+ * Title for the panel.
+ * @param string aObject
+ * Object to display in the tree.
+ * @param array of objects aButtons
+ * Array with buttons to display at the bottom of the panel.
+ */
+function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
+{
+ // Create the underlying panel
+ this.panel = createElement(aDocument, "panel", {
+ label: aTitle,
+ titlebar: "normal",
+ noautofocus: "true",
+ noautohide: "true",
+ close: "true",
+ });
+
+ // Create the tree.
+ let tree = this.tree = createElement(aDocument, "tree", {
+ flex: 1,
+ hidecolumnpicker: "true"
+ });
+
+ let treecols = aDocument.createElement("treecols");
+ appendChild(aDocument, treecols, "treecol", {
+ primary: "true",
+ flex: 1,
+ hideheader: "true",
+ ignoreincolumnpicker: "true"
+ });
+ tree.appendChild(treecols);
+
+ tree.appendChild(aDocument.createElement("treechildren"));
+ this.panel.appendChild(tree);
+
+ // Create the footer.
+ let footer = createElement(aDocument, "hbox", { align: "end" });
+ appendChild(aDocument, footer, "spacer", { flex: 1 });
+
+ // The footer can have butttons.
+ let self = this;
+ if (aButtons) {
+ aButtons.forEach(function(button) {
+ let buttonNode = appendChild(aDocument, footer, "button", {
+ label: button.label,
+ accesskey: button.accesskey || "",
+ class: button.class || "",
+ });
+ buttonNode.addEventListener("command", button.oncommand, false);
+ });
+ }
+
+ appendChild(aDocument, footer, "resizer", { dir: "bottomend" });
+ this.panel.appendChild(footer);
+
+ aParent.appendChild(this.panel);
+
+ // Create the treeView object.
+ this.treeView = new PropertyTreeView();
+ this.treeView.data = aObject;
+
+ // Set the treeView object on the tree view. This has to be done *after* the
+ // panel is shown. This is because the tree binding must be attached first.
+ this.panel.addEventListener("popupshown", function onPopupShow()
+ {
+ self.panel.removeEventListener("popupshown", onPopupShow, false);
+ self.tree.view = self.treeView;
+ }, false);
+
+ this.panel.addEventListener("popuphidden", function onPopupHide()
+ {
+ self.panel.removeEventListener("popuphidden", onPopupHide, false);
+ self.destroy();
+ }, false);
+}
+
+/**
+ * Destroy the PropertyPanel. This closes the poped up panel and removes
+ * it from the browser DOM.
+ *
+ * @returns void
+ */
+PropertyPanel.prototype.destroy = function PP_destroy()
+{
+ this.panel.parentNode.removeChild(this.panel);
+ this.treeView = null;
+ this.panel = null;
+ this.tree = null;
+
+ if (this.linkNode) {
+ this.linkNode._panelOpen = false;
+ this.linkNode = null;
+ }
+}
diff --git a/xpfe/components/devtools/moz.build b/xpfe/components/devtools/moz.build
new file mode 100644
index 000000000..c538bfdf5
--- /dev/null
+++ b/xpfe/components/devtools/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_JS_MODULES.communicator.devtools += ['modules/PropertyPanel.jsm']
+
+JS_PREFERENCE_FILES += ['devtools-prefs.js']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/xpfe/components/downloads/content/DownloadProgressListener.js b/xpfe/components/downloads/content/DownloadProgressListener.js
new file mode 100644
index 000000000..5341b44d8
--- /dev/null
+++ b/xpfe/components/downloads/content/DownloadProgressListener.js
@@ -0,0 +1,58 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * DownloadProgressListener "class" is used to help update download items shown
+ * in the Download Manager UI such as displaying amount transferred, transfer
+ * rate, and time left for each download.
+ *
+ * This class implements the nsIDownloadProgressListener interface.
+ */
+function DownloadProgressListener() {}
+
+DownloadProgressListener.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIDownloadProgressListener]),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIDownloadProgressListener
+
+ onDownloadStateChange: function(aState, aDownload) {
+ // Update window title in-case we don't get all progress notifications
+ onUpdateProgress();
+
+ switch (aDownload.state) {
+ case nsIDownloadManager.DOWNLOAD_QUEUED:
+ gDownloadTreeView.addDownload(aDownload);
+ break;
+
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY:
+ gDownloadTreeView.addDownload(aDownload);
+ // Should fall through, this is a final state but DOWNLOAD_QUEUED
+ // is skipped. See nsDownloadManager::AddDownload.
+ default:
+ gDownloadTreeView.updateDownload(aDownload);
+ break;
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress, aDownload) {
+ gDownloadTreeView.updateDownload(aDownload);
+
+ // Update window title
+ onUpdateProgress();
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) {
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) {
+ }
+};
diff --git a/xpfe/components/downloads/content/downloadmanager.js b/xpfe/components/downloads/content/downloadmanager.js
new file mode 100644
index 000000000..692bc2264
--- /dev/null
+++ b/xpfe/components/downloads/content/downloadmanager.js
@@ -0,0 +1,712 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+Components.utils.import("resource://gre/modules/DownloadTaskbarProgress.jsm");
+
+const nsIDownloadManager = Components.interfaces.nsIDownloadManager;
+
+var gDownloadTree;
+var gDownloadTreeView;
+var gDownloadManager = Components.classes["@mozilla.org/download-manager;1"]
+ .getService(nsIDownloadManager);
+var gDownloadStatus;
+var gDownloadListener;
+var gSearchBox;
+var gDMUI = Components.classes["@mozilla.org/download-manager-ui;1"]
+ .getService(Components.interfaces.nsIDownloadManagerUI);
+
+function dmStartup()
+{
+ gDownloadTree = document.getElementById("downloadTree");
+ gDownloadStatus = document.getElementById("statusbar-display");
+ gSearchBox = document.getElementById("search-box");
+
+ // Insert as first controller on the whole window
+ window.controllers.insertControllerAt(0, dlTreeController);
+
+ // We need to keep the oview object around globally to access "local"
+ // non-nsITreeView methods
+ gDownloadTreeView = new DownloadTreeView(gDownloadManager);
+ gDownloadTree.view = gDownloadTreeView;
+
+ Services.obs.addObserver(gDownloadObserver, "download-manager-remove-download-guid", false);
+
+ // The DownloadProgressListener (DownloadProgressListener.js) handles
+ // progress notifications.
+ gDownloadListener = new DownloadProgressListener();
+ gDownloadManager.addListener(gDownloadListener);
+
+ // correct keybinding command attributes which don't do our business yet
+ var key = document.getElementById("key_delete");
+ if (key.hasAttribute("command"))
+ key.setAttribute("command", "cmd_stop");
+ key = document.getElementById("key_delete2");
+ if (key.hasAttribute("command"))
+ key.setAttribute("command", "cmd_stop");
+
+ gDownloadTree.focus();
+
+ if (gDownloadTree.view.rowCount > 0)
+ gDownloadTree.view.selection.select(0);
+
+ DownloadTaskbarProgress.onDownloadWindowLoad(window);
+}
+
+function dmShutdown()
+{
+ gDownloadManager.removeListener(gDownloadListener);
+ Services.obs.removeObserver(gDownloadObserver, "download-manager-remove-download-guid");
+ window.controllers.removeController(dlTreeController);
+}
+
+function searchDownloads(aInput)
+{
+ gDownloadTreeView.searchView(aInput);
+}
+
+function sortDownloads(aEventTarget)
+{
+ var column = aEventTarget;
+ var colID = column.id;
+ var sortDirection = null;
+
+ // If the target is a menuitem, handle it and forward to a column
+ if (/^menu_SortBy/.test(colID)) {
+ colID = colID.replace(/^menu_SortBy/, "");
+ column = document.getElementById(colID);
+ var sortedColumn = gDownloadTree.columns.getSortedColumn();
+ if (sortedColumn && sortedColumn.id == colID)
+ sortDirection = sortedColumn.element.getAttribute("sortDirection");
+ else
+ sortDirection = "ascending";
+ }
+ else if (colID == "menu_Unsorted") {
+ // calling .sortView() with an "unsorted" colID returns us to original order
+ colID = "unsorted";
+ column = null;
+ sortDirection = "ascending";
+ }
+ else if (colID == "menu_SortAscending" || colID == "menu_SortDescending") {
+ sortDirection = colID.replace(/^menu_Sort/, "").toLowerCase();
+ var sortedColumn = gDownloadTree.columns.getSortedColumn();
+ if (sortedColumn) {
+ colID = sortedColumn.id;
+ column = sortedColumn.element;
+ }
+ }
+
+ // Abort if this is still no column
+ if (column && column.localName != "treecol")
+ return;
+
+ // Abort on cyler columns, we don't sort them
+ if (column && column.getAttribute("cycler") == "true")
+ return;
+
+ if (!sortDirection) {
+ // If not set above already, toggle the current direction
+ sortDirection = column.getAttribute("sortDirection") == "ascending" ?
+ "descending" : "ascending";
+ }
+
+ // Clear attributes on all columns, we're setting them again after sorting
+ for (let node = document.getElementById("Name"); node; node = node.nextSibling) {
+ node.removeAttribute("sortActive");
+ node.removeAttribute("sortDirection");
+ }
+
+ // Actually sort the tree view
+ gDownloadTreeView.sortView(colID, sortDirection);
+
+ if (column) {
+ // Set attributes to the sorting we did
+ column.setAttribute("sortActive", "true");
+ column.setAttribute("sortDirection", sortDirection);
+ }
+}
+
+function retryDownload(aDownload)
+{
+ aDownload.retry();
+ if (gDownloadTreeView)
+ gDownloadTreeView.removeDownload(aDownload.guid);
+}
+
+function cancelDownload(aDownload)
+{
+ aDownload.cancel();
+ // delete the file if it exists
+ var file = aDownload.targetFile;
+ if (file.exists())
+ file.remove(false);
+}
+
+function openDownload(aDownload)
+{
+ var name = aDownload.displayName;
+ var file = aDownload.targetFile;
+
+ if (file.isExecutable()) {
+ var alertOnEXEOpen = GetBoolPref("browser.download.manager.alertOnEXEOpen",
+ true);
+
+ // On Vista and above, we rely on native security prompting for
+ // downloaded content unless it's disabled.
+ try {
+ var sysInfo = Components.classes["@mozilla.org/system-info;1"]
+ .getService(Components.interfaces.nsIPropertyBag2);
+ if (/^Windows/.test(sysInfo.getProperty("name")) &&
+ (parseFloat(sysInfo.getProperty("version")) >= 6 &&
+ Services.prefs.getBoolPref("browser.download.manager.scanWhenDone")))
+ alertOnEXEOpen = false;
+ } catch (ex) { }
+
+ if (alertOnEXEOpen) {
+ var dlbundle = document.getElementById("dmBundle");
+ var message = dlbundle.getFormattedString("fileExecutableSecurityWarning", [name, name]);
+
+ var title = dlbundle.getString("fileExecutableSecurityWarningTitle");
+ var dontAsk = dlbundle.getString("fileExecutableSecurityWarningDontAsk");
+
+ var checkbox = { value: false };
+ if (!Services.prompt.confirmCheck(window, title, message, dontAsk, checkbox))
+ return;
+ Services.prefs.setBoolPref("browser.download.manager.alertOnEXEOpen", !checkbox.value);
+ }
+ }
+
+ try {
+ var mimeInfo = aDownload.MIMEInfo;
+ if (mimeInfo && mimeInfo.preferredAction == mimeInfo.useHelperApp) {
+ mimeInfo.launchWithFile(file);
+ return;
+ }
+ } catch (ex) { }
+
+ try {
+ file.launch();
+ } catch (ex) {
+ // If launch fails, try sending it through the system's external
+ // file: URL handler
+ var uri = Services.io.newFileURI(file);
+ var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Components.interfaces.nsIExternalProtocolService);
+ protocolSvc.loadUrl(uri);
+ }
+}
+
+function showDownload(aDownload)
+{
+ var file = aDownload.targetFile;
+
+ try {
+ // Show the directory containing the file and select the file
+ file.reveal();
+ } catch (e) {
+ // If reveal fails for some reason (e.g., it's not implemented on unix or
+ // the file doesn't exist), try using the parent if we have it.
+ var parent = file.parent.QueryInterface(Components.interfaces.nsILocalFile);
+
+ try {
+ // "Double click" the parent directory to show where the file should be
+ parent.launch();
+ } catch (e) {
+ // If launch also fails (probably because it's not implemented), let the
+ // OS handler try to open the parent
+ var uri = Services.io.newFileURI(parent);
+ var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Components.interfaces.nsIExternalProtocolService);
+ protocolSvc.loadUrl(uri);
+ }
+ }
+}
+
+function showProperties(aDownload)
+{
+ var dmui = Components.classes["@mozilla.org/download-manager-ui;1"]
+ .getService(Components.interfaces.nsISuiteDownloadManagerUI);
+ dmui.showProgress(window, aDownload);
+}
+
+function onTreeSelect(aEvent)
+{
+ var selectionCount = gDownloadTreeView.selection.count;
+ if (selectionCount == 1) {
+ var selItemData = gDownloadTreeView.getRowData(gDownloadTree.currentIndex);
+ gDownloadStatus.label = GetFileFromString(selItemData.file).path;
+ } else {
+ gDownloadStatus.label = "";
+ }
+
+ window.updateCommands("tree-select");
+}
+
+function onUpdateViewColumns(aMenuItem)
+{
+ while (aMenuItem) {
+ // Each menuitem should be checked if its column is not hidden.
+ var colID = aMenuItem.id.replace(/^menu_Toggle/, "");
+ var column = document.getElementById(colID);
+ aMenuItem.setAttribute("checked", !column.hidden);
+ aMenuItem = aMenuItem.nextSibling;
+ }
+}
+
+function toggleColumn(aMenuItem)
+{
+ var colID = aMenuItem.id.replace(/^menu_Toggle/, "");
+ var column = document.getElementById(colID);
+ column.setAttribute("hidden", !column.hidden);
+}
+
+function onUpdateViewSort(aMenuItem)
+{
+ var unsorted = true;
+ var ascending = true;
+ while (aMenuItem) {
+ switch (aMenuItem.id) {
+ case "": // separator
+ break;
+ case "menu_Unsorted":
+ if (unsorted) // this would work even if Unsorted was last
+ aMenuItem.setAttribute("checked", "true");
+ break;
+ case "menu_SortAscending":
+ aMenuItem.setAttribute("disabled", unsorted);
+ if (!unsorted && ascending)
+ aMenuItem.setAttribute("checked", "true");
+ break;
+ case "menu_SortDescending":
+ aMenuItem.setAttribute("disabled", unsorted);
+ if (!unsorted && !ascending)
+ aMenuItem.setAttribute("checked", "true");
+ break;
+ default:
+ var colID = aMenuItem.id.replace(/^menu_SortBy/, "");
+ var column = document.getElementById(colID);
+ var direction = column.getAttribute("sortDirection");
+ if (column.getAttribute("sortActive") == "true" && direction) {
+ // We've found a sorted column. Remember its direction.
+ ascending = direction == "ascending";
+ unsorted = false;
+ aMenuItem.setAttribute("checked", "true");
+ }
+ }
+ aMenuItem = aMenuItem.nextSibling;
+ }
+}
+
+// This is called by the progress listener.
+var gLastComputedMean = -1;
+var gLastActiveDownloads = 0;
+function onUpdateProgress()
+{
+ var numActiveDownloads = gDownloadManager.activeDownloadCount;
+
+ // Use the default title and reset "last" values if there's no downloads
+ if (numActiveDownloads == 0) {
+ document.title = document.documentElement.getAttribute("statictitle");
+ gLastComputedMean = -1;
+ gLastActiveDownloads = 0;
+
+ return;
+ }
+
+ // Establish the mean transfer speed and amount downloaded.
+ var mean = 0;
+ var base = 0;
+ var dls = gDownloadManager.activeDownloads;
+ while (dls.hasMoreElements()) {
+ var dl = dls.getNext();
+ if (dl.percentComplete < 100 && dl.size > 0) {
+ mean += dl.amountTransferred;
+ base += dl.size;
+ }
+ }
+
+ // Calculate the percent transferred, unless we don't have a total file size
+ var dlbundle = document.getElementById("dmBundle");
+ if (base != 0)
+ mean = Math.floor((mean / base) * 100);
+
+ // Update title of window
+ if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) {
+ gLastComputedMean = mean;
+ gLastActiveDownloads = numActiveDownloads;
+
+ var title;
+ if (base == 0)
+ title = dlbundle.getFormattedString("downloadsTitleFiles",
+ [numActiveDownloads]);
+ else
+ title = dlbundle.getFormattedString("downloadsTitlePercent",
+ [numActiveDownloads, mean]);
+
+ // Get the correct plural form and insert number of downloads and percent
+ title = PluralForm.get(numActiveDownloads, title);
+
+ document.title = title;
+ }
+}
+
+function handlePaste() {
+ let trans = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ trans.init(null);
+
+ let flavors = ["text/x-moz-url", "text/unicode"];
+ flavors.forEach(trans.addDataFlavor);
+
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ // Getting the data or creating the nsIURI might fail
+ try {
+ let data = {};
+ trans.getAnyTransferData({}, data, {});
+ let [url, name] = data.value.QueryInterface(Components.interfaces
+ .nsISupportsString).data.split("\n");
+
+ if (!url)
+ return;
+
+ let uri = Services.io.newURI(url, null, null);
+
+ saveURL(uri.spec, name || uri.spec, null, true, true, null, document);
+ } catch (ex) {}
+}
+
+var gDownloadObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "download-manager-remove-download-guid":
+ if (aSubject instanceof Components.interfaces.nsISupportsCString)
+ // We have a single download.
+ gDownloadTreeView.removeDownload(aSubject.data);
+ else
+ // A null subject here indicates "remove multiple", so we just rebuild.
+ gDownloadTreeView.initTree();
+ break;
+ }
+ }
+};
+
+var dlTreeController = {
+ supportsCommand: function(aCommand)
+ {
+ switch (aCommand) {
+ case "cmd_play":
+ case "cmd_pause":
+ case "cmd_resume":
+ case "cmd_retry":
+ case "cmd_cancel":
+ case "cmd_remove":
+ case "cmd_stop":
+ case "cmd_open":
+ case "cmd_show":
+ case "cmd_openReferrer":
+ case "cmd_copyLocation":
+ case "cmd_properties":
+ case "cmd_paste":
+ case "cmd_selectAll":
+ case "cmd_clearList":
+ return true;
+ }
+ return false;
+ },
+
+ isCommandEnabled: function(aCommand)
+ {
+ var selectionCount = 0;
+ if (gDownloadTreeView && gDownloadTreeView.selection)
+ selectionCount = gDownloadTreeView.selection.count;
+
+ var selItemData = [];
+ if (selectionCount) {
+ // walk all selected rows
+ let start = {};
+ let end = {};
+ let numRanges = gDownloadTreeView.selection.getRangeCount();
+ for (let rg = 0; rg < numRanges; rg++) {
+ gDownloadTreeView.selection.getRangeAt(rg, start, end);
+ for (let row = start.value; row <= end.value; row++)
+ selItemData.push(gDownloadTreeView.getRowData(row));
+ }
+ }
+
+ switch (aCommand) {
+ case "cmd_play":
+ if (!selectionCount)
+ return false;
+ for (let dldata of selItemData) {
+ if (dldata.state != nsIDownloadManager.DOWNLOAD_CANCELED &&
+ dldata.state != nsIDownloadManager.DOWNLOAD_FAILED &&
+ (!dldata.resumable ||
+ (!dldata.isActive &&
+ dldata.state != nsIDownloadManager.DOWNLOAD_PAUSED)))
+ return false;
+ }
+ return true;
+ case "cmd_pause":
+ if (!selectionCount)
+ return false;
+ for (let dldata of selItemData) {
+ if (!dldata.isActive ||
+ dldata.state == nsIDownloadManager.DOWNLOAD_PAUSED ||
+ !dldata.resumable)
+ return false;
+ }
+ return true;
+ case "cmd_resume":
+ if (!selectionCount)
+ return false;
+ for (let dldata of selItemData) {
+ if (dldata.state != nsIDownloadManager.DOWNLOAD_PAUSED ||
+ !dldata.resumable)
+ return false;
+ }
+ return true;
+ case "cmd_open":
+ return selectionCount == 1 &&
+ selItemData[0].state == nsIDownloadManager.DOWNLOAD_FINISHED &&
+ GetFileFromString(selItemData[0].file).exists();
+ case "cmd_show":
+ return selectionCount == 1 &&
+ GetFileFromString(selItemData[0].file).exists();
+ case "cmd_cancel":
+ if (!selectionCount)
+ return false;
+ for (let dldata of selItemData) {
+ if (!dldata.isActive)
+ return false;
+ }
+ return true;
+ case "cmd_retry":
+ if (!selectionCount)
+ return false;
+ for (let dldata of selItemData) {
+ if (dldata.state != nsIDownloadManager.DOWNLOAD_CANCELED &&
+ dldata.state != nsIDownloadManager.DOWNLOAD_FAILED)
+ return false;
+ }
+ return true;
+ case "cmd_remove":
+ if (!selectionCount)
+ return false;
+ for (let dldata of selItemData) {
+ if (dldata.isActive)
+ return false;
+ }
+ return true;
+ case "cmd_openReferrer":
+ return selectionCount == 1 && !!selItemData[0].referrer;
+ case "cmd_stop":
+ case "cmd_copyLocation":
+ return selectionCount > 0;
+ case "cmd_properties":
+ return selectionCount == 1;
+ case "cmd_selectAll":
+ return gDownloadTreeView.rowCount != selectionCount;
+ case "cmd_clearList":
+ return gDownloadTreeView.rowCount && gDownloadManager.canCleanUp;
+ case "cmd_paste":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(aCommand) {
+ var selectionCount = 0;
+ if (gDownloadTreeView && gDownloadTreeView.selection)
+ selectionCount = gDownloadTreeView.selection.count;
+
+ var selItemData = [];
+ if (selectionCount) {
+ // walk all selected rows
+ let start = {};
+ let end = {};
+ let numRanges = gDownloadTreeView.selection.getRangeCount();
+ for (let rg = 0; rg < numRanges; rg++) {
+ gDownloadTreeView.selection.getRangeAt(rg, start, end);
+ for (let row = start.value; row <= end.value; row++)
+ selItemData.push(gDownloadTreeView.getRowData(row));
+ }
+ }
+
+ switch (aCommand) {
+ case "cmd_play":
+ for (let dldata of selItemData) {
+ switch (dldata.state) {
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ dldata.dld.pause();
+ break;
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ dldata.dld.resume();
+ break;
+ case nsIDownloadManager.DOWNLOAD_FAILED:
+ case nsIDownloadManager.DOWNLOAD_CANCELED:
+ retryDownload(dldata.dld);
+ break;
+ }
+ }
+ break;
+ case "cmd_pause":
+ for (let dldata of selItemData)
+ dldata.dld.pause();
+ break;
+ case "cmd_resume":
+ for (let dldata of selItemData)
+ dldata.dld.resume();
+ break;
+ case "cmd_retry":
+ for (let dldata of selItemData)
+ retryDownload(dldata.dld);
+ break;
+ case "cmd_cancel":
+ for (let dldata of selItemData)
+ cancelDownload(dldata.dld);
+ break;
+ case "cmd_remove":
+ for (let dldata of selItemData)
+ dldata.dld.remove();
+ break;
+ case "cmd_stop":
+ for (let dldata of selItemData) {
+ if (dldata.isActive)
+ cancelDownload(dldata.dld);
+ else
+ dldata.dld.remove();
+ }
+ break;
+ case "cmd_open":
+ openDownload(selItemData[0].dld);
+ break;
+ case "cmd_show":
+ showDownload(selItemData[0].dld);
+ break;
+ case "cmd_openReferrer":
+ openUILink(selItemData[0].referrer);
+ break;
+ case "cmd_copyLocation":
+ var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Components.interfaces.nsIClipboardHelper);
+ var uris = [];
+ for (let dldata of selItemData)
+ uris.push(dldata.uri);
+ clipboard.copyString(uris.join("\n"));
+ break;
+ case "cmd_properties":
+ showProperties(selItemData[0].dld);
+ break;
+ case "cmd_selectAll":
+ gDownloadTreeView.selection.selectAll();
+ break;
+ case "cmd_clearList":
+ // Clear the whole list if there's no search
+ if (gSearchBox.value == "") {
+ gDownloadManager.cleanUp();
+ return;
+ }
+
+ // Remove each download starting from the end until we hit a download
+ // that is in progress
+ for (let idx = gDownloadTreeView.rowCount - 1; idx >= 0; idx--) {
+ let dldata = gDownloadTreeView.getRowData(idx);
+ if (!dldata.isActive) {
+ dldata.dld.remove();
+ }
+ }
+
+ // Clear the input as if the user did it and move focus to the list
+ gSearchBox.value = "";
+ searchDownloads("");
+ gDownloadTree.focus();
+ break;
+ case "cmd_paste":
+ handlePaste();
+ break;
+ }
+ },
+
+ onEvent: function(aEvent){
+ switch (aEvent) {
+ case "tree-select":
+ this.onCommandUpdate();
+ }
+ },
+
+ onCommandUpdate: function() {
+ var cmds = ["cmd_play", "cmd_pause", "cmd_resume", "cmd_retry",
+ "cmd_cancel", "cmd_remove", "cmd_stop", "cmd_open", "cmd_show",
+ "cmd_openReferrer", "cmd_copyLocation", "cmd_properties",
+ "cmd_selectAll", "cmd_clearList"];
+ for (let command in cmds)
+ goUpdateCommand(cmds[command]);
+ }
+};
+
+var gDownloadDNDObserver = {
+ onDragStart: function (aEvent)
+ {
+ if (!gDownloadTreeView ||
+ !gDownloadTreeView.selection ||
+ !gDownloadTreeView.selection.count)
+ return;
+
+ var selItemData = gDownloadTreeView.getRowData(gDownloadTree.currentIndex);
+ var file = GetFileFromString(selItemData.file);
+
+ if (!file.exists())
+ return;
+
+ var url = Services.io.newFileURI(file).spec;
+ var dt = aEvent.dataTransfer;
+ dt.mozSetDataAt("application/x-moz-file", file, 0);
+ dt.setData("text/uri-list", url + "\r\n");
+ dt.setData("text/plain", url + "\n");
+ dt.effectAllowed = "copyMove";
+ if (gDownloadTreeView.selection.count == 1)
+ dt.setDragImage(gDownloadStatus, 16, 16);
+ },
+
+ onDragOver: function (aEvent)
+ {
+ if (disallowDrop(aEvent))
+ return;
+
+ var types = aEvent.dataTransfer.types;
+ if (types.includes("text/uri-list") ||
+ types.includes("text/x-moz-url") ||
+ types.includes("text/plain"))
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ },
+
+ onDrop: function(aEvent)
+ {
+ if (disallowDrop(aEvent))
+ return;
+
+ var dt = aEvent.dataTransfer;
+ var url = dt.getData("URL");
+ var name;
+ if (!url) {
+ url = dt.getData("text/x-moz-url") || dt.getData("text/plain");
+ [url, name] = url.split("\n");
+ }
+ if (url) {
+ let doc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
+ saveURL(url, name || url, null, true, true, null, doc);
+ }
+ }
+};
+
+function disallowDrop(aEvent)
+{
+ var dt = aEvent.dataTransfer;
+ var file = dt.mozGetDataAt("application/x-moz-file", 0);
+ // If this is a local file, Don't try to download it again.
+ return file && file instanceof Components.interfaces.nsIFile;
+}
diff --git a/xpfe/components/downloads/content/downloadmanager.xul b/xpfe/components/downloads/content/downloadmanager.xul
new file mode 100644
index 000000000..a6ec8090a
--- /dev/null
+++ b/xpfe/components/downloads/content/downloadmanager.xul
@@ -0,0 +1,461 @@
+<?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://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/downloads/downloadmanager.css" type="text/css"?>
+
+#ifdef BINOC_NAVIGATOR
+<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?>
+#endif
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % downloadsDTD SYSTEM "chrome://communicator/locale/downloads/downloadmanager.dtd">
+%downloadsDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+%globalDTD;
+]>
+
+<window id="downloadManager"
+ title="&downloadManager.title;" statictitle="&downloadManager.title;"
+ onload="dmStartup();" onunload="dmShutdown();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="500" height="400" screenX="10" screenY="10"
+ persist="width height screenX screenY sizemode"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ drawtitle="true"
+ windowtype="Download:Manager">
+
+ <script type="application/javascript"
+ src="chrome://communicator/content/downloads/downloadmanager.js"/>
+ <script type="application/javascript"
+ src="chrome://communicator/content/downloads/DownloadProgressListener.js"/>
+ <script type="application/javascript"
+ src="chrome://communicator/content/downloads/treeView.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/contentAreaUtils.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="dmBundle"
+ src="chrome://communicator/locale/downloads/downloadmanager.properties"/>
+ </stringbundleset>
+
+ <commandset id="dlWinCommands">
+ <commandset id="tasksCommands">
+ <!-- File Menu -->
+ <command id="cmd_close" oncommand="window.close()"/>
+ <!-- Search Box -->
+ <command id="cmd_search_focus"
+ oncommand="gSearchBox.focus();"/>
+ </commandset>
+ <commandset id="editMenuCommands"/>
+ <commandset id="commandUpdate_Downloads"
+ commandupdater="true"
+ events="focus,tree-select"
+ oncommandupdate="dlTreeController.onCommandUpdate()"/>
+
+ <commandset id="downloadCommands">
+ <command id="cmd_play"
+ oncommand="goDoCommand('cmd_play');"/>
+ <command id="cmd_pause"
+ oncommand="goDoCommand('cmd_pause');"/>
+ <command id="cmd_resume"
+ oncommand="goDoCommand('cmd_resume');"/>
+ <command id="cmd_retry"
+ oncommand="goDoCommand('cmd_retry');"/>
+ <command id="cmd_cancel"
+ oncommand="goDoCommand('cmd_cancel');"/>
+ <command id="cmd_remove"
+ oncommand="goDoCommand('cmd_remove');"/>
+ <command id="cmd_stop"
+ oncommand="goDoCommand('cmd_stop');"/>
+ <command id="cmd_open"
+ oncommand="goDoCommand('cmd_open');"/>
+ <command id="cmd_show"
+ oncommand="goDoCommand('cmd_show');"/>
+ <command id="cmd_openReferrer"
+ oncommand="goDoCommand('cmd_openReferrer');"/>
+ <command id="cmd_copyLocation"
+ oncommand="goDoCommand('cmd_copyLocation');"/>
+ <command id="cmd_properties"
+ oncommand="goDoCommand('cmd_properties');"/>
+ <command id="cmd_clearList"
+ oncommand="goDoCommand('cmd_clearList');"/>
+ </commandset>
+ </commandset>
+
+ <keyset id="tasksKeys">
+ <!-- File Menu -->
+ <key id="key_open"
+ keycode="VK_RETURN"
+ command="cmd_open"/>
+ <key id="key_close"/>
+ <!-- Edit Menu -->
+ <key id="key_cut"/>
+ <key id="key_copy"/>
+ <key id="key_paste"
+ command="cmd_paste"/>
+ <key id="key_play"
+ key=" "
+ command="cmd_play"/>
+ <key id="key_delete"/>
+ <key id="key_delete2"/>
+ <key id="key_selectAll"/>
+ <!-- Search Box -->
+ <key id="key_search_focus"
+ command="cmd_search_focus"
+ key="&search.key;"
+ modifiers="accel"/>
+ </keyset>
+
+ <popupset id="downloadPopupset">
+ <menupopup id="downloadContext">
+ <menuitem id="dlContext-pause"
+ label="&cmd.pause.label;"
+ accesskey="&cmd.pause.accesskey;"
+ command="cmd_pause"/>
+ <menuitem id="dlContext-resume"
+ label="&cmd.resume.label;"
+ accesskey="&cmd.resume.accesskey;"
+ command="cmd_resume"/>
+ <menuitem id="dlContext-retry"
+ label="&cmd.retry.label;"
+ accesskey="&cmd.retry.accesskey;"
+ command="cmd_retry"/>
+ <menuitem id="dlContext-cancel"
+ label="&cmd.cancel.label;"
+ accesskey="&cmd.cancel.accesskey;"
+ command="cmd_cancel"/>
+ <menuitem id="dlContext-remove"
+ label="&cmd.remove.label;"
+ accesskey="&cmd.remove.accesskey;"
+ command="cmd_remove"/>
+ <menuseparator/>
+ <menuitem id="dlContext-open"
+ label="&cmd.open.label;"
+ accesskey="&cmd.open.accesskey;"
+ command="cmd_open"
+ default="true"/>
+ <menuitem id="dlContext-show"
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+ command="cmd_show"/>
+ <menuitem id="dlContext-openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"
+ command="cmd_openReferrer"/>
+ <menuitem id="dlContext-copyLocation"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"
+ command="cmd_copyLocation"/>
+ <menuitem id="dlContext-properties"
+ label="&cmd.properties.label;"
+ accesskey="&cmd.properties.accesskey;"
+ command="cmd_properties"/>
+ <menuseparator/>
+ <menuitem id="cMenu_selectAll"/>
+ </menupopup>
+ </popupset>
+
+ <vbox id="titlebar"/>
+
+ <toolbox id="download-toolbox">
+ <menubar id="download-menubar"
+ grippytooltiptext="&menuBar.tooltip;">
+ <menu id="menu_File">
+ <menupopup id="menu_FilePopup">
+ <menuitem id="dlMenu_open"
+ label="&cmd.open.label;"
+ accesskey="&cmd.open.accesskey;"
+ key="key_open"
+ command="cmd_open"/>
+ <menuitem id="dlMenu_show"
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+ command="cmd_show"/>
+ <menuitem id="dlMenu_openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"
+ command="cmd_openReferrer"/>
+ <menuitem id="dlMenu_properties"
+ label="&cmd.properties.label;"
+ accesskey="&cmd.properties.accesskey;"
+ command="cmd_properties"/>
+ <menuseparator/>
+ <menuitem id="menu_close"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_Edit">
+ <menupopup id="menu_EditPopup">
+ <menuitem id="dlMenu_pause"
+ label="&cmd.pause.label;"
+ accesskey="&cmd.pause.accesskey;"
+ command="cmd_pause"/>
+ <menuitem id="dlMenu_resume"
+ label="&cmd.resume.label;"
+ accesskey="&cmd.resume.accesskey;"
+ command="cmd_resume"/>
+ <menuitem id="dlMenu_retry"
+ label="&cmd.retry.label;"
+ accesskey="&cmd.retry.accesskey;"
+ command="cmd_retry"/>
+ <menuitem id="dlMenu_cancel"
+ label="&cmd.cancel.label;"
+ accesskey="&cmd.cancel.accesskey;"
+ command="cmd_cancel"/>
+ <menuseparator/>
+ <menuitem id="dlMenu_remove"
+ label="&cmd.remove.label;"
+ accesskey="&cmd.remove.accesskey;"
+ command="cmd_remove"/>
+ <menuitem id="dlMenu_copyLocation"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"
+ command="cmd_copyLocation"/>
+ <menuseparator/>
+ <menuitem id="dlMenu_clearList"
+ label="&cmd.clearList.label;"
+ accesskey="&cmd.clearList.accesskey;"
+ command="cmd_clearList"/>
+ <menuitem id="menu_selectAll"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_View">
+ <menupopup id="menu_ViewPopup">
+ <menu id="menu_ViewColumns"
+ label="&view.columns.label;"
+ accesskey="&view.columns.accesskey;">
+ <menupopup onpopupshowing="onUpdateViewColumns(this.firstChild);"
+ oncommand="toggleColumn(event.target);">
+ <menuitem id="menu_ToggleName" type="checkbox" disabled="true"
+ label="&col.name.label;"
+ accesskey="&col.name.accesskey;"/>
+ <menuitem id="menu_ToggleStatus" type="checkbox"
+ label="&col.status.label;"
+ accesskey="&col.status.accesskey;"/>
+ <menuitem id="menu_ToggleActionPlay" type="checkbox"
+ label="&col.actionPlay.label;"
+ accesskey="&col.actionPlay.accesskey;"/>
+ <menuitem id="menu_ToggleActionStop" type="checkbox"
+ label="&col.actionStop.label;"
+ accesskey="&col.actionStop.accesskey;"/>
+ <menuitem id="menu_ToggleProgress" type="checkbox"
+ label="&col.progress.label;"
+ accesskey="&col.progress.accesskey;"/>
+ <menuitem id="menu_ToggleTimeRemaining" type="checkbox"
+ label="&col.timeremaining.label;"
+ accesskey="&col.timeremaining.accesskey;"/>
+ <menuitem id="menu_ToggleTransferred" type="checkbox"
+ label="&col.transferred.label;"
+ accesskey="&col.transferred.accesskey;"/>
+ <menuitem id="menu_ToggleTransferRate" type="checkbox"
+ label="&col.transferrate.label;"
+ accesskey="&col.transferrate.accesskey;"/>
+ <menuitem id="menu_ToggleTimeElapsed" type="checkbox"
+ label="&col.timeelapsed.label;"
+ accesskey="&col.timeelapsed.accesskey;"/>
+ <menuitem id="menu_ToggleStartTime" type="checkbox"
+ label="&col.starttime.label;"
+ accesskey="&col.starttime.accesskey;"/>
+ <menuitem id="menu_ToggleEndTime" type="checkbox"
+ label="&col.endtime.label;"
+ accesskey="&col.endtime.accesskey;"/>
+ <menuitem id="menu_ToggleProgressPercent" type="checkbox"
+ label="&col.progresstext.label;"
+ accesskey="&col.progresstext.accesskey;"/>
+ <menuitem id="menu_ToggleSource" type="checkbox"
+ label="&col.source.label;"
+ accesskey="&col.source.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_ViewSortBy" label="&view.sortBy.label;"
+ accesskey="&view.sortBy.accesskey;">
+ <menupopup onpopupshowing="onUpdateViewSort(this.firstChild);"
+ oncommand="sortDownloads(event.target);">
+ <menuitem id="menu_Unsorted" type="radio" name="columns"
+ label="&view.unsorted.label;"
+ accesskey="&view.unsorted.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="menu_SortByName" type="radio" name="columns"
+ label="&col.name.label;"
+ accesskey="&col.name.accesskey;"/>
+ <menuitem id="menu_SortByStatus" type="radio" name="columns"
+ label="&col.status.label;"
+ accesskey="&col.status.accesskey;"/>
+ <menuitem id="menu_SortByProgress" type="radio" name="columns"
+ label="&col.progress.label;"
+ accesskey="&col.progress.accesskey;"/>
+ <menuitem id="menu_SortByTimeRemaining" type="radio" name="columns"
+ label="&col.timeremaining.label;"
+ accesskey="&col.timeremaining.accesskey;"/>
+ <menuitem id="menu_SortByTransferred" type="radio" name="columns"
+ label="&col.transferred.label;"
+ accesskey="&col.transferred.accesskey;"/>
+ <menuitem id="menu_SortByTransferRate" type="radio" name="columns"
+ label="&col.transferrate.label;"
+ accesskey="&col.transferrate.accesskey;"/>
+ <menuitem id="menu_SortByTimeElapsed" type="radio" name="columns"
+ label="&col.timeelapsed.label;"
+ accesskey="&col.timeelapsed.accesskey;"/>
+ <menuitem id="menu_SortByStartTime" type="radio" name="columns"
+ label="&col.starttime.label;"
+ accesskey="&col.starttime.accesskey;"/>
+ <menuitem id="menu_SortByEndTime" type="radio" name="columns"
+ label="&col.endtime.label;"
+ accesskey="&col.endtime.accesskey;"/>
+ <menuitem id="menu_SortByProgressPercent" type="radio" name="columns"
+ label="&col.progresstext.label;"
+ accesskey="&col.progresstext.accesskey;"/>
+ <menuitem id="menu_SortBySource" type="radio" name="columns"
+ label="&col.source.label;"
+ accesskey="&col.source.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="menu_SortAscending" type="radio" name="direction"
+ label="&view.sortAscending.label;"
+ accesskey="&view.sortAscending.accesskey;"/>
+ <menuitem id="menu_SortDescending" type="radio" name="direction"
+ label="&view.sortDescending.label;"
+ accesskey="&view.sortDescending.accesskey;"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+#ifdef BINOC_NAVIGATOR
+ <menu id="tasksMenu">
+ <menupopup id="taskPopup">
+ <menuitem id="dlMenu_find"
+ label="&search.label;"
+ accesskey="&search.accesskey;"
+ command="cmd_search_focus"
+ key="key_search_focus"/>
+ <menuseparator/>
+ </menupopup>
+ </menu>
+ <menu id="windowMenu"/>
+ <menu id="menu_Help"/>
+#endif
+ </menubar>
+ <toolbar class="chromeclass-toolbar"
+ id="downloadToolbar"
+ align="center"
+ grippytooltiptext="&searchBar.tooltip;">
+ <button id="clearListButton" command="cmd_clearList"
+ label="&cmd.clearList.label;"
+ accesskey="&cmd.clearList.accesskey;"
+ tooltiptext="&cmd.clearList.tooltip;"/>
+ <toolbarspring/>
+ <textbox id="search-box"
+ clickSelectsAll="true"
+ type="search"
+ aria-controls="downloadTree"
+ class="compact"
+ placeholder="&search.placeholder;"
+ oncommand="searchDownloads(this.value);"/>
+ </toolbar>
+ </toolbox>
+
+ <tree id="downloadTree"
+ flex="1" type="downloads"
+ class="plain"
+ context="downloadContext"
+ enableColumnDrag="true"
+ onselect="onTreeSelect(event);">
+ <treecols context="" onclick="sortDownloads(event.target)">
+ <treecol id="Name"
+ label="&col.name.label;"
+ tooltiptext="&col.name.tooltip;"
+ flex="3"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Status" hidden="true"
+ label="&col.status.label;"
+ tooltiptext="&col.status.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="ActionPlay" cycler="true"
+ label="&col.actionPlay.label;"
+ tooltiptext="&col.actionPlay.tooltip;"
+ class="treecol-image" fixed="true"
+ persist="hidden ordinal"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="ActionStop" cycler="true"
+ label="&col.actionStop.label;"
+ tooltiptext="&col.actionStop.tooltip;"
+ class="treecol-image" fixed="true"
+ persist="hidden ordinal"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Progress" type="progressmeter"
+ label="&col.progress.label;"
+ tooltiptext="&col.progress.tooltip;"
+ flex="3"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="ProgressPercent" hidden="true"
+ label="&col.progresstext.label;"
+ tooltiptext="&col.progresstext.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="TimeRemaining"
+ label="&col.timeremaining.label;"
+ tooltiptext="&col.timeremaining.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Transferred"
+ label="&col.transferred.label;"
+ tooltiptext="&col.transferred.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="TransferRate"
+ label="&col.transferrate.label;"
+ tooltiptext="&col.transferrate.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="TimeElapsed" hidden="true"
+ label="&col.timeelapsed.label;"
+ tooltiptext="&col.timeelapsed.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="StartTime" hidden="true"
+ label="&col.starttime.label;"
+ tooltiptext="&col.starttime.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="EndTime" hidden="true"
+ label="&col.endtime.label;"
+ tooltiptext="&col.endtime.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Source" hidden="true"
+ label="&col.source.label;"
+ tooltiptext="&col.source.tooltip;"
+ flex="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ </treecols>
+ <treechildren ondblclick="goDoCommand('cmd_open');"
+ ondragstart="gDownloadDNDObserver.onDragStart(event);"
+ ondragover="gDownloadDNDObserver.onDragOver(event);"
+ ondrop="gDownloadDNDObserver.onDrop(event);"/>
+ </tree>
+ <statusbar id="status-bar" class="chromeclass-status">
+ <statusbarpanel id="statusbar-display" flex="1"/>
+#ifdef BINOC_NAVIGATOR
+ <statusbarpanel class="statusbarpanel-iconic" id="offline-status"/>
+#endif
+ </statusbar>
+
+</window>
diff --git a/xpfe/components/downloads/content/progressDialog.js b/xpfe/components/downloads/content/progressDialog.js
new file mode 100644
index 000000000..4fa6ccc3a
--- /dev/null
+++ b/xpfe/components/downloads/content/progressDialog.js
@@ -0,0 +1,378 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+
+// nsIDownloadManager, gDownloadManager, gDownloadListener
+// are defined in downloadmanager.js
+
+var gDownload;
+var gDownloadBundle;
+var gTkDlBundle;
+
+var gDlStatus;
+var gDlSize;
+var gTimeElapsed;
+var gProgressMeter;
+var gProgressText;
+var gCloseWhenDone;
+
+var gLastSec = Infinity;
+var gStartTime = 0;
+var gEndTime = Date.now(); // gets corrected below for calls from dlmgr
+var gDlActive = false;
+var gRetrying = false;
+
+function progressStartup() {
+ gDownload = window.arguments[0];
+
+ var recentDMWindow = Services.wm.getMostRecentWindow("Download:Manager");
+ if (recentDMWindow &&
+ gDownload.guid in recentDMWindow.gDownloadTreeView._dlMap)
+ // we have been opened by a download manager, get the end time from there
+ gEndTime = recentDMWindow.gDownloadTreeView._dlMap[gDownload.guid].endTime;
+
+ // cache elements to save .getElementById() calls
+ gDownloadBundle = document.getElementById("dmBundle");
+ gTkDlBundle = document.getElementById("tkdlBundle");
+ gDlStatus = document.getElementById("dlStatus");
+ gDlSize = document.getElementById("dlSize");
+ gTimeElapsed = document.getElementById("timeElapsed");
+ gProgressMeter = document.getElementById("progressMeter");
+ gProgressText = document.getElementById("progressText");
+ gCloseWhenDone = document.getElementById("closeWhenDone");
+
+ // Insert as first controller on the whole window
+ window.controllers.insertControllerAt(0, ProgressDlgController);
+
+ if (gDownload.isPrivate)
+ gCloseWhenDone.hidden = true;
+ else
+ gCloseWhenDone.checked = Services.prefs.getBoolPref("browser.download.progress.closeWhenDone");
+
+ switch (gDownload.state) {
+ case nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ case nsIDownloadManager.DOWNLOAD_QUEUED:
+ case nsIDownloadManager.DOWNLOAD_SCANNING:
+ gDlActive = true;
+ break;
+ case nsIDownloadManager.DOWNLOAD_FINISHED:
+ if (gCloseWhenDone.checked && window.arguments[1])
+ window.close();
+ default:
+ gDlActive = false;
+ break;
+ }
+
+ var fName = document.getElementById("fileName");
+ var fSource = document.getElementById("fileSource");
+ fName.label = gDownload.displayName;
+ fName.tooltipText = gDownload.target.spec;
+ var fromString;
+ try {
+ fromString = gDownload.source.host;
+ }
+ catch (e) { }
+ if (!fromString)
+ fromString = gDownload.source.prePath;
+ fSource.label = gDownloadBundle.getFormattedString("fromSource", [fromString]);
+ fSource.tooltipText = gDownload.source.spec;
+
+ // The DlProgressListener handles progress notifications.
+ gDownloadListener = new DlProgressListener();
+ gDownloadManager.addPrivacyAwareListener(gDownloadListener);
+
+ updateDownload();
+ updateButtons();
+ window.updateCommands("dlstate-change");
+
+ // Send a notification that we finished
+ setTimeout(() =>
+ Services.obs.notifyObservers(window, "download-manager-ui-done", null), 0);
+}
+
+function progressShutdown() {
+ gDownloadManager.removeListener(gDownloadListener);
+ window.controllers.removeController(ProgressDlgController);
+ if (!gCloseWhenDone.hidden)
+ Services.prefs.setBoolPref("browser.download.progress.closeWhenDone",
+ gCloseWhenDone.checked);
+}
+
+function updateDownload() {
+ switch (gDownload.state) {
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ // At this point, we know if we are an indeterminate download or not.
+ if (gDownload.percentComplete == -1) {
+ gProgressText.hidden = true;
+ gProgressMeter.mode = "undetermined";
+ }
+ else if (gProgressText.hidden) {
+ // If it was undetermined before, unhide text and switch mode.
+ gProgressText.hidden = false;
+ gProgressMeter.mode = "determined";
+ }
+ case nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ case nsIDownloadManager.DOWNLOAD_QUEUED:
+ case nsIDownloadManager.DOWNLOAD_SCANNING:
+ gDlActive = true;
+ gProgressMeter.style.opacity = 1;
+ break;
+ default:
+ gDlActive = false;
+ gProgressMeter.style.opacity = 0.5;
+ break;
+ }
+ if (gDownload.size >= 0) {
+ gProgressMeter.value = gDownload.percentComplete;
+ gProgressText.value = gDownloadBundle.getFormattedString("percentFormat",
+ [gDownload.percentComplete]);
+ }
+ // Update window title
+ var statusString;
+ switch (gDownload.state) {
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ statusString = gDownloadBundle.getString("paused");
+ break;
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ statusString = gDownloadBundle.getString("downloading");
+ break;
+ case nsIDownloadManager.DOWNLOAD_FINISHED:
+ statusString = gDownloadBundle.getString("finished");
+ break;
+ case nsIDownloadManager.DOWNLOAD_FAILED:
+ statusString = gDownloadBundle.getString("failed");
+ break;
+ case nsIDownloadManager.DOWNLOAD_CANCELED:
+ statusString = gDownloadBundle.getString("canceled");
+ break;
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: // Parental Controls
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY: // Security Zone Policy
+ case nsIDownloadManager.DOWNLOAD_DIRTY: // possible virus/spyware
+ statusString = gDownloadBundle.getString("blocked");
+ break;
+ default:
+ statusString = gDownloadBundle.getString("notStarted");
+ break;
+ }
+ var file = GetFileFromString(gDownload.target.spec);
+ if (gDownload.size > 0) {
+ document.title = gDownloadBundle.getFormattedString("progressTitlePercent",
+ [gDownload.percentComplete,
+ file.leafName, statusString]);
+ }
+ else {
+ document.title = gDownloadBundle.getFormattedString("progressTitle",
+ [file.leafName, statusString]);
+ }
+
+ // download size
+ var transfer = DownloadUtils.getTransferTotal(gDownload.amountTransferred,
+ gDownload.size);
+ if (gDownload.state == nsIDownloadManager.DOWNLOAD_DOWNLOADING) {
+ var [rate, unit] = DownloadUtils.convertByteUnits(gDownload.speed);
+ var dlSpeed = gDownloadBundle.getFormattedString("speedFormat", [rate, unit]);
+ gDlSize.value = gDownloadBundle.getFormattedString("sizeSpeed",
+ [transfer, dlSpeed]);
+ }
+ else
+ gDlSize.value = transfer;
+
+ // download status
+ if (gDlActive) {
+ // Calculate the time remaining if we have valid values
+ var seconds = (gDownload.speed > 0) && (gDownload.size > 0)
+ ? (gDownload.size - gDownload.amountTransferred) / gDownload.speed
+ : -1;
+ var [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, gLastSec);
+ gLastSec = newLast;
+ }
+ switch (gDownload.state) {
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: // Parental Controls
+ gDlStatus.value = gTkDlBundle.getString("stateBlocked");
+ break;
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY: // Security Zone Policy
+ gDlStatus.value = gTkDlBundle.getString("stateBlockedPolicy");
+ break;
+ case nsIDownloadManager.DOWNLOAD_DIRTY: // possible virus/spyware
+ gDlStatus.value = gTkDlBundle.getString("stateDirty");
+ break;
+ default:
+ if (gDlActive)
+ gDlStatus.value = gDownloadBundle.getFormattedString("statusActive",
+ [statusString, timeLeft]);
+ else
+ gDlStatus.value = statusString;
+ break;
+ }
+
+ // time elapsed
+ if (!gStartTime && gDownload.startTime)
+ gStartTime = Math.round(gDownload.startTime / 1000)
+ if (gDlActive)
+ gEndTime = Date.now();
+ if (gStartTime && gEndTime && (gEndTime > gStartTime)) {
+ var seconds = (gEndTime - gStartTime) / 1000;
+ var [time1, unit1, time2, unit2] =
+ DownloadUtils.convertTimeUnits(seconds);
+ if (seconds < 3600 || time2 == 0)
+ gTimeElapsed.value = gDownloadBundle.getFormattedString("timeElapsedSingle", [time1, unit1]);
+ else
+ gTimeElapsed.value = gDownloadBundle.getFormattedString("timeElapsedDouble", [time1, unit1, time2, unit2]);
+ }
+ else {
+ gTimeElapsed.value = "";
+ }
+}
+
+function updateButtons() {
+ document.getElementById("pauseButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_pause");
+ document.getElementById("resumeButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_resume");
+ document.getElementById("retryButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_retry");
+ document.getElementById("cancelButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_cancel");
+}
+
+/**
+ * DlProgressListener "class" is used to help update download items shown
+ * in the progress dialog such as displaying amount transferred, transfer
+ * rate, and time left for the download.
+ *
+ * This class implements the nsIDownloadProgressListener interface.
+ */
+function DlProgressListener() {}
+
+DlProgressListener.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIDownloadProgressListener]),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIDownloadProgressListener
+
+ onDownloadStateChange: function(aState, aDownload) {
+ // first, check if we are retrying and this is the new download starting
+ if (gRetrying &&
+ (aDownload.state == nsIDownloadManager.DOWNLOAD_QUEUED ||
+ aDownload.state == nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY) &&
+ aDownload.source.spec == gDownload.source.spec &&
+ aDownload.target.spec == gDownload.target.spec) {
+ gRetrying = false;
+ gDownload = aDownload;
+ }
+ if (aDownload == gDownload) {
+ if (gCloseWhenDone.checked &&
+ (aDownload.state == nsIDownloadManager.DOWNLOAD_FINISHED)) {
+ window.close();
+ }
+ updateDownload();
+ updateButtons();
+ window.updateCommands("dlstate-change");
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress, aDownload) {
+ if (aDownload == gDownload)
+ updateDownload();
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) {
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) {
+ }
+};
+
+var ProgressDlgController = {
+ supportsCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_pause":
+ case "cmd_resume":
+ case "cmd_retry":
+ case "cmd_cancel":
+ case "cmd_open":
+ case "cmd_show":
+ case "cmd_openReferrer":
+ case "cmd_copyLocation":
+ return true;
+ }
+ return false;
+ },
+
+ isCommandEnabled: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_pause":
+ return gDlActive &&
+ gDownload.state != nsIDownloadManager.DOWNLOAD_PAUSED &&
+ gDownload.resumable;
+ case "cmd_resume":
+ return gDownload.state == nsIDownloadManager.DOWNLOAD_PAUSED &&
+ gDownload.resumable;
+ case "cmd_open":
+ return gDownload.state == nsIDownloadManager.DOWNLOAD_FINISHED &&
+ gDownload.targetFile.exists();
+ case "cmd_show":
+ return gDownload.targetFile.exists();
+ case "cmd_cancel":
+ return gDlActive;
+ case "cmd_retry":
+ return gDownload.state == nsIDownloadManager.DOWNLOAD_CANCELED ||
+ gDownload.state == nsIDownloadManager.DOWNLOAD_FAILED;
+ case "cmd_openReferrer":
+ return !!gDownload.referrer;
+ case "cmd_copyLocation":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_pause":
+ gDownload.pause();
+ break;
+ case "cmd_resume":
+ gDownload.resume();
+ break;
+ case "cmd_retry":
+ gRetrying = true;
+ retryDownload(gDownload);
+ break;
+ case "cmd_cancel":
+ cancelDownload(gDownload);
+ break;
+ case "cmd_open":
+ openDownload(gDownload);
+ break;
+ case "cmd_show":
+ showDownload(gDownload);
+ break;
+ case "cmd_openReferrer":
+ openUILink(gDownload.referrer.spec);
+ break;
+ case "cmd_copyLocation":
+ var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Components.interfaces.nsIClipboardHelper);
+ clipboard.copyString(gDownload.source.spec);
+ break;
+ }
+ },
+
+ onEvent: function(aEvent) {
+ },
+
+ onCommandUpdate: function() {
+ var cmds = ["cmd_pause", "cmd_resume", "cmd_retry", "cmd_cancel",
+ "cmd_open", "cmd_show", "cmd_openReferrer", "cmd_copyLocation"];
+ for (let command in cmds)
+ goUpdateCommand(cmds[command]);
+ }
+};
diff --git a/xpfe/components/downloads/content/progressDialog.xul b/xpfe/components/downloads/content/progressDialog.xul
new file mode 100644
index 000000000..9c10a1af7
--- /dev/null
+++ b/xpfe/components/downloads/content/progressDialog.xul
@@ -0,0 +1,116 @@
+<?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://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/downloads/downloadmanager.css" type="text/css"?>
+
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+
+<!DOCTYPE window SYSTEM "chrome://communicator/locale/downloads/progressDialog.dtd">
+
+<window id="dlProgressWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="progressStartup();" onunload="progressShutdown();"
+ title="&progress.title;"
+ persist="screenX screenY"
+ style="width:40em;">
+
+#ifndef BINOC_NAVIGATOR
+ <script type="application/javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+#endif
+ <script type="application/javascript"
+ src="chrome://communicator/content/downloads/downloadmanager.js"/>
+ <script type="application/javascript"
+ src="chrome://communicator/content/downloads/progressDialog.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="dmBundle"
+ src="chrome://communicator/locale/downloads/downloadmanager.properties"/>
+ <stringbundle id="tkdlBundle"
+ src="chrome://global/locale/mozapps/downloads/downloads.properties"/>
+ </stringbundleset>
+
+ <commandset id="dlProgressCommands">
+ <commandset id="commandUpdate_DlProgress"
+ commandupdater="true"
+ events="focus,dlstate-change"
+ oncommandupdate="ProgressDlgController.onCommandUpdate();"/>
+
+ <commandset id="downloadCommands">
+ <command id="cmd_pause"
+ oncommand="goDoCommand('cmd_pause');"/>
+ <command id="cmd_resume"
+ oncommand="goDoCommand('cmd_resume');"/>
+ <command id="cmd_retry"
+ oncommand="goDoCommand('cmd_retry');"/>
+ <command id="cmd_cancel"
+ oncommand="goDoCommand('cmd_cancel');"/>
+ <command id="cmd_open"
+ oncommand="goDoCommand('cmd_open');"/>
+ <command id="cmd_show"
+ oncommand="goDoCommand('cmd_show');"/>
+ <command id="cmd_openReferrer"
+ oncommand="goDoCommand('cmd_openReferrer');"/>
+ <command id="cmd_copyLocation"
+ oncommand="goDoCommand('cmd_copyLocation');"/>
+ <command id="cmd_close" oncommand="window.close();"/>
+ </commandset>
+ </commandset>
+
+ <keyset>
+ <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
+ <key keycode="VK_ESCAPE" command="cmd_close"/>
+ <key key="." modifiers="meta" command="cmd_close"/>
+ </keyset>
+
+ <hbox align="end">
+ <vbox flex="1" align="start">
+ <button id="fileName" crop="center" label="" type="menu">
+ <menupopup id="file-popup">
+ <menuitem id="dlContext-open"
+ label="&cmd.open.label;"
+ accesskey="&cmd.open.accesskey;"
+ command="cmd_open"/>
+ <menuitem id="dlContext-show"
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+ command="cmd_show"/>
+ </menupopup>
+ </button>
+ <button id="fileSource" crop="center" label="" type="menu">
+ <menupopup id="source-popup">
+ <menuitem id="dlContext-openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"
+ command="cmd_openReferrer"/>
+ <menuitem id="dlContext-copyLocation"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"
+ command="cmd_copyLocation"/>
+ </menupopup>
+ </button>
+ <label id="dlSize" value=""/>
+ <label id="timeElapsed" value=""/>
+ <label id="dlStatus" value=""/>
+ </vbox>
+ <button id="pauseButton" class="mini-button"
+ command="cmd_pause" tooltiptext="&cmd.pause.tooltip;"/>
+ <button id="resumeButton" class="mini-button"
+ command="cmd_resume" tooltiptext="&cmd.resume.tooltip;"/>
+ <button id="retryButton" class="mini-button"
+ command="cmd_retry" tooltiptext="&cmd.retry.tooltip;"/>
+ <button id="cancelButton" class="mini-button"
+ command="cmd_cancel" tooltiptext="&cmd.cancel.tooltip;"/>
+ </hbox>
+ <hbox id="progressBox">
+ <progressmeter id="progressMeter" mode="determined" flex="1"/>
+ <label id="progressText" value=""/>
+ </hbox>
+ <checkbox id="closeWhenDone"
+ label="&closeWhenDone.label;"
+ accesskey="&closeWhenDone.accesskey;"/>
+</window>
diff --git a/xpfe/components/downloads/content/treeView.js b/xpfe/components/downloads/content/treeView.js
new file mode 100644
index 000000000..0be76ea64
--- /dev/null
+++ b/xpfe/components/downloads/content/treeView.js
@@ -0,0 +1,700 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+
+const nsITreeView = Components.interfaces.nsITreeView;
+// const nsIDownloadManager is already defined in downloadmanager.js
+
+function DownloadTreeView(aDownloadManager) {
+ this._dm = aDownloadManager;
+ this._dlList = [];
+ this._dlMap = {};
+ this._searchTerms = [];
+}
+
+DownloadTreeView.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([nsITreeView,
+ Components.interfaces.nsIDownloadManagerResult]),
+
+ // ***** nsITreeView attributes and methods *****
+ get rowCount() {
+ return this._dlList.length;
+ },
+
+ selection: null,
+
+ getRowProperties: function(aRow) {
+ var dl = this._dlList[aRow];
+ // (in)active
+ var properties = dl.isActive ? "active": "inactive";
+ // resumable
+ if (dl.resumable)
+ properties += " resumable";
+ // Download states
+ switch (dl.state) {
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ properties += " paused";
+ break;
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ properties += " downloading";
+ break;
+ case nsIDownloadManager.DOWNLOAD_FINISHED:
+ properties += " finished";
+ break;
+ case nsIDownloadManager.DOWNLOAD_FAILED:
+ properties += " failed";
+ break;
+ case nsIDownloadManager.DOWNLOAD_CANCELED:
+ properties += " canceled";
+ break;
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: // Parental Controls
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY: // Security Zone Policy
+ case nsIDownloadManager.DOWNLOAD_DIRTY: // possible virus/spyware
+ properties += " blocked";
+ break;
+ }
+ return properties;
+ },
+ getCellProperties: function(aRow, aColumn) {
+ // Append all row properties to the cell
+ return this.getRowProperties(aRow);
+ },
+ getColumnProperties: function(aColumn) { return ""; },
+ isContainer: function(aRow) { return false; },
+ isContainerOpen: function(aRow) { return false; },
+ isContainerEmpty: function(aRow) { return false; },
+ isSeparator: function(aRow) { return false; },
+ isSorted: function() { return false; },
+ canDrop: function(aIdx, aOrientation) { return false; },
+ drop: function(aIdx, aOrientation) { },
+ getParentIndex: function(aRow) { return -1; },
+ hasNextSibling: function(aRow, aAfterIdx) { return false; },
+ getLevel: function(aRow) { return 0; },
+
+ getImageSrc: function(aRow, aColumn) {
+ if (aColumn.id == "Name")
+ return "moz-icon://" + this._dlList[aRow].file + "?size=16";
+ return "";
+ },
+
+ getProgressMode: function(aRow, aColumn) {
+ if (aColumn.id == "Progress")
+ return this._dlList[aRow].progressMode;
+ return nsITreeView.PROGRESS_NONE;
+ },
+
+ getCellValue: function(aRow, aColumn) {
+ if (aColumn.id == "Progress")
+ return this._dlList[aRow].progress;
+ return "";
+ },
+
+ getCellText: function(aRow, aColumn) {
+ var dl = this._dlList[aRow];
+ switch (aColumn.id) {
+ case "Name":
+ return dl.target;
+ case "Status":
+ switch (dl.state) {
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ return this._dlbundle.getString("paused");
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ return this._dlbundle.getString("downloading");
+ case nsIDownloadManager.DOWNLOAD_FINISHED:
+ return this._dlbundle.getString("finished");
+ case nsIDownloadManager.DOWNLOAD_FAILED:
+ return this._dlbundle.getString("failed");
+ case nsIDownloadManager.DOWNLOAD_CANCELED:
+ return this._dlbundle.getString("canceled");
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: // Parental Controls
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY: // Security Zone Policy
+ case nsIDownloadManager.DOWNLOAD_DIRTY: // possible virus/spyware
+ return this._dlbundle.getString("blocked");
+ }
+ return this._dlbundle.getString("notStarted");
+ case "Progress":
+ if (dl.isActive)
+ return dl.progress;
+ switch (dl.state) {
+ case nsIDownloadManager.DOWNLOAD_FINISHED:
+ return this._dlbundle.getString("finished");
+ case nsIDownloadManager.DOWNLOAD_FAILED:
+ return this._dlbundle.getString("failed");
+ case nsIDownloadManager.DOWNLOAD_CANCELED:
+ return this._dlbundle.getString("canceled");
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: // Parental Controls
+ case nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY: // Security Zone Policy
+ case nsIDownloadManager.DOWNLOAD_DIRTY: // possible virus/spyware
+ return this._dlbundle.getString("blocked");
+ }
+ return this._dlbundle.getString("notStarted");
+ case "ProgressPercent":
+ return dl.progress;
+ case "TimeRemaining":
+ if (dl.isActive) {
+ var dld = dl.dld;
+ var lastSec = (dl.lastSec == null) ? Infinity : dl.lastSec;
+ // Calculate the time remaining if we have valid values
+ var seconds = (dld.speed > 0) && (dl.maxBytes > 0)
+ ? (dl.maxBytes - dl.currBytes) / dld.speed
+ : -1;
+ var [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, lastSec);
+ this._dlList[aRow].lastSec = newLast;
+ return timeLeft;
+ }
+ return "";
+ case "Transferred":
+ return DownloadUtils.getTransferTotal(dl.currBytes, dl.maxBytes);
+ case "TransferRate":
+ switch (dl.state) {
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ var speed = dl.dld.speed;
+ this._dlList[aRow]._speed = speed; // used for sorting
+ var [rate, unit] = DownloadUtils.convertByteUnits(speed);
+ return this._dlbundle.getFormattedString("speedFormat", [rate, unit]);
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ return this._dlbundle.getString("paused");
+ case nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+ case nsIDownloadManager.DOWNLOAD_QUEUED:
+ return this._dlbundle.getString("notStarted");
+ }
+ return "";
+ case "TimeElapsed":
+ if (dl.endTime && dl.startTime && (dl.endTime > dl.startTime)) {
+ var seconds = (dl.endTime - dl.startTime) / 1000;
+ var [time1, unit1, time2, unit2] =
+ DownloadUtils.convertTimeUnits(seconds);
+ if (seconds < 3600 || time2 == 0)
+ return this._dlbundle.getFormattedString("timeSingle", [time1, unit1]);
+ return this._dlbundle.getFormattedString("timeDouble", [time1, unit1, time2, unit2]);
+ }
+ return "";
+ case "StartTime":
+ if (dl.startTime)
+ return this._convertTimeToString(dl.startTime);
+ return "";
+ case "EndTime":
+ if (dl.endTime)
+ return this._convertTimeToString(dl.endTime);
+ return "";
+ case "Source":
+ return dl.uri;
+ }
+ return "";
+ },
+
+ setTree: function(aTree) {
+ this._tree = aTree;
+ this._dlbundle = document.getElementById("dmBundle");
+
+ this.initTree();
+ },
+
+ toggleOpenState: function(aRow) { },
+ cycleHeader: function(aColumn) { },
+ selectionChanged: function() { },
+ cycleCell: function(aRow, aColumn) {
+ var dl = this._dlList[aRow];
+ switch (aColumn.id) {
+ case "ActionPlay":
+ switch (dl.state) {
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ dl.dld.pause();
+ break;
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ dl.dld.resume();
+ break;
+ case nsIDownloadManager.DOWNLOAD_FAILED:
+ case nsIDownloadManager.DOWNLOAD_CANCELED:
+ retryDownload(dl.dld);
+ break;
+ }
+ break;
+ case "ActionStop":
+ if (dl.isActive)
+ cancelDownload(dl.dld);
+ else
+ dl.dld.remove();
+ break;
+ }
+ },
+ isEditable: function(aRow, aColumn) { return false; },
+ isSelectable: function(aRow, aColumn) { return false; },
+ setCellValue: function(aRow, aColumn, aText) { },
+ setCellText: function(aRow, aColumn, aText) { },
+ performAction: function(aAction) { },
+ performActionOnRow: function(aAction, aRow) { },
+ performActionOnCell: function(aAction, aRow, aColumn) { },
+
+ // ***** local public methods *****
+
+ addDownload: function(aDownload) {
+ var attrs = {
+ guid: aDownload.guid,
+ file: aDownload.target.spec,
+ target: aDownload.displayName,
+ uri: aDownload.source.spec,
+ state: aDownload.state,
+ progress: aDownload.percentComplete,
+ progressMode: nsITreeView.PROGRESS_NONE,
+ resumable: aDownload.resumable,
+ startTime: Math.round(aDownload.startTime / 1000),
+ endTime: Date.now(),
+ referrer: null,
+ currBytes: aDownload.amountTransferred,
+ maxBytes: aDownload.size,
+ lastSec: Infinity, // For calculations of remaining time
+ dld: aDownload
+ };
+ switch (attrs.state) {
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ // At this point, we know if we are an indeterminate download or not.
+ attrs.progressMode = attrs.progress == -1 ?
+ nsITreeView.PROGRESS_UNDETERMINED :
+ nsITreeView.PROGRESS_NORMAL;
+ // We also know the referrer at this point.
+ var referrer = aDownload.referrer;
+ if (referrer)
+ attrs.referrer = referrer.spec;
+ case nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ case nsIDownloadManager.DOWNLOAD_QUEUED:
+ case nsIDownloadManager.DOWNLOAD_SCANNING:
+ attrs.isActive = 1;
+ break;
+ default:
+ attrs.isActive = 0;
+ break;
+ }
+
+ // prepend in natural sorting
+ attrs.listIndex = this._lastListIndex--;
+
+ // Prepend data to the download list
+ this._dlList.unshift(attrs);
+ this._dlMap[attrs.guid] = attrs;
+
+ // Tell the tree we added 1 row at index 0
+ this._tree.rowCountChanged(0, 1);
+
+ // Data has changed, so re-sorting might be needed
+ this.sortView("", "", attrs, 0);
+
+ window.updateCommands("tree-select");
+ },
+
+ updateDownload: function(aDownload) {
+ var row = this._getIdxForGUID(aDownload.guid);
+ if (row == -1) {
+ // No download row found to update, but as it's obviously going on,
+ // add it to the list now (can happen with very fast, e.g. local dls)
+ this.addDownload(aDownload);
+ return;
+ }
+ var dl = this._dlList[row];
+ if (dl.currBytes != aDownload.amountTransferred) {
+ dl.endTime = Date.now();
+ dl.currBytes = aDownload.amountTransferred;
+ dl.maxBytes = aDownload.size;
+ dl.progress = aDownload.percentComplete;
+ }
+ if (dl.state != aDownload.state) {
+ dl.state = aDownload.state;
+ dl.resumable = aDownload.resumable;
+ switch (dl.state) {
+ case nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ // At this point, we know if we are an indeterminate download or not.
+ dl.progressMode = dl.progress == -1 ?
+ nsITreeView.PROGRESS_UNDETERMINED :
+ nsITreeView.PROGRESS_NORMAL;
+ // We also know the referrer at this point.
+ var referrer = aDownload.referrer;
+ if (referrer)
+ dl.referrer = referrer.spec;
+ case nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+ case nsIDownloadManager.DOWNLOAD_PAUSED:
+ case nsIDownloadManager.DOWNLOAD_QUEUED:
+ case nsIDownloadManager.DOWNLOAD_SCANNING:
+ dl.isActive = 1;
+ break;
+ default:
+ dl.isActive = 0;
+ dl.progressMode = nsITreeView.PROGRESS_NONE;
+ gDMUI.getAttention();
+ break;
+ }
+ }
+
+ // Repaint the tree row
+ this._tree.invalidateRow(row);
+
+ // Data has changed, so re-sorting might be needed
+ this.sortView("", "", dl, row);
+
+ window.updateCommands("tree-select");
+ },
+
+ removeDownload: function(aGUID) {
+ var row = this._getIdxForGUID(aGUID);
+ // Make sure we have an item to remove
+ if (row < 0) return;
+
+ var index = this.selection.currentIndex;
+ var wasSingleSelection = this.selection.count == 1;
+
+ // Remove data from the download list
+ this._dlList.splice(row, 1);
+ delete this._dlMap[aGUID];
+
+ // Tell the tree we removed 1 row at the given row index
+ this._tree.rowCountChanged(row, -1);
+
+ // Update selection if only removed download was selected
+ if (wasSingleSelection && this.selection.count == 0) {
+ index = Math.min(index, this.rowCount - 1);
+ if (index >= 0)
+ this.selection.select(index);
+ }
+
+ window.updateCommands("tree-select");
+ },
+
+ initTree: function() {
+ if (!this._tree)
+ return
+ // We're resetting the whole list, either because we're creating the tree
+ // or because we need to recreate it
+ this._tree.beginUpdateBatch();
+ this._dlList = [];
+ this._dlMap = {};
+ this._lastListIndex = 0;
+
+ this.selection.clearSelection();
+
+ // sort in reverse and prepend to the list to get continuous list indexes
+ // with increasing negative numbers for default-sort in ascending order
+ var statement = this._dm.DBConnection.createStatement(
+ "SELECT guid, target, name, source, state, startTime, endTime, referrer" +
+ ", currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) AS isActive " +
+ "FROM moz_downloads " +
+ "ORDER BY isActive ASC, endTime ASC, startTime ASC, id DESC");
+
+ statement.bindByIndex(0, nsIDownloadManager.DOWNLOAD_NOTSTARTED);
+ statement.bindByIndex(1, nsIDownloadManager.DOWNLOAD_DOWNLOADING);
+ statement.bindByIndex(2, nsIDownloadManager.DOWNLOAD_PAUSED);
+ statement.bindByIndex(3, nsIDownloadManager.DOWNLOAD_QUEUED);
+ statement.bindByIndex(4, nsIDownloadManager.DOWNLOAD_SCANNING);
+
+ while (statement.executeStep()) {
+ // Try to get the attribute values from the statement
+ let attrs = {
+ guid: statement.getString(0),
+ file: statement.getString(1),
+ target: statement.getString(2),
+ uri: statement.getString(3),
+ state: statement.getInt32(4),
+ progress: 100,
+ progressMode: nsITreeView.PROGRESS_NONE,
+ resumable: false,
+ startTime: Math.round(statement.getInt64(5) / 1000),
+ endTime: Math.round(statement.getInt64(6) / 1000),
+ referrer: statement.getString(7),
+ currBytes: statement.getInt64(8),
+ maxBytes: statement.getInt64(9),
+ isActive: statement.getInt32(10),
+ lastSec: Infinity, // For calculations of remaining time
+ dld: null
+ };
+
+ // Only actually add item to the tree if it's active or matching search
+ let matchSearch = true;
+ if (this._searchTerms) {
+ // Search through the download attributes that are shown to the user and
+ // make it into one big string for easy combined searching
+ // XXX: toolkit uses the target, status and dateTime attributes of their XBL item
+ let combinedSearch = attrs.file.toLowerCase() + " " + attrs.uri.toLowerCase();
+ if (attrs.target)
+ combinedSearch = combinedSearch + " " + attrs.target.toLowerCase();
+
+ if (!attrs.isActive)
+ for (let term of this._searchTerms)
+ if (combinedSearch.indexOf(term) == -1)
+ matchSearch = false;
+ }
+
+ // matchSearch is always true for active downloads, see above
+ if (matchSearch) {
+ attrs.listIndex = this._lastListIndex--;
+ this._dlList.unshift(attrs);
+ this._dlMap[attrs.guid] = attrs;
+ }
+ }
+ statement.finalize();
+ // now read active downloads
+ var downloads = this._dm.activeDownloads;
+ while (downloads.hasMoreElements()) {
+ let dld = downloads.getNext()
+ .QueryInterface(Components.interfaces.nsIDownload);
+ if (dld.guid in this._dlMap) {
+ var dl = this._dlMap[dld.guid];
+ dl.progress = dld.percentComplete;
+ dl.progressMode = dl.progress == -1 ?
+ nsITreeView.PROGRESS_UNDETERMINED :
+ nsITreeView.PROGRESS_NORMAL;
+ dl.resumable = dld.resumable;
+ dl.dld = dld;
+ }
+ }
+ // find sorted column and sort the tree
+ var sortedColumn = this._tree.columns.getSortedColumn();
+ if (sortedColumn) {
+ var direction = sortedColumn.element.getAttribute("sortDirection");
+ this.sortView(sortedColumn.id, direction);
+ }
+ this._tree.endUpdateBatch();
+
+ window.updateCommands("tree-select");
+
+ // ask for nsIDownload objects for finished downloads
+ for (let dl of this._dlList)
+ if (!dl.dld)
+ this._dm.getDownloadByGUID(dl.guid, this);
+
+ // Send a notification that we finished
+ setTimeout(() =>
+ Services.obs.notifyObservers(window, "download-manager-ui-done", null), 0);
+ },
+
+ handleResult: function(aStatus, aDownload) {
+ if (aDownload)
+ this._dlMap[aDownload.guid].dld = aDownload;
+ },
+
+ searchView: function(aInput) {
+ // Stringify the previous search
+ var prevSearch = this._searchTerms.join(" ");
+
+ // Array of space-separated lower-case search terms
+ this._searchTerms = aInput.trim().toLowerCase().split(/\s+/);
+
+ // Don't rebuild the download list if the search didn't change
+ if (this._searchTerms.join(" ") == prevSearch)
+ return;
+
+ // Cache the current selection
+ this._cacheSelection();
+
+ // Rebuild the tree with set search terms
+ this.initTree();
+
+ // Restore the selection
+ this._restoreSelection();
+ },
+
+ sortView: function(aColumnID, aDirection, aDownload, aRow) {
+ var sortAscending = aDirection != "descending";
+
+ if (aColumnID == "" && aDirection == "") {
+ // Re-sort in already selected/cached order
+ var sortedColumn = this._tree.columns.getSortedColumn();
+ if (sortedColumn) {
+ aColumnID = sortedColumn.id;
+ sortAscending = sortedColumn.element.getAttribute("sortDirection") != "descending";
+ }
+ // no need for else, use default case of switch, sortAscending is true
+ }
+
+ // Compare function for two _dlList items
+ var compfunc = function(a, b) {
+ // Active downloads are always at the beginning
+ // i.e. 0 for .isActive is larger (!) than 1
+ if (a.isActive < b.isActive)
+ return 1;
+ if (a.isActive > b.isActive)
+ return -1;
+ // Same active/inactive state, sort normally
+ var comp_a = null;
+ var comp_b = null;
+ switch (aColumnID) {
+ case "Name":
+ comp_a = a.target.toLowerCase();
+ comp_b = b.target.toLowerCase();
+ break;
+ case "Status":
+ comp_a = a.state;
+ comp_b = b.state;
+ break;
+ case "Progress":
+ case "ProgressPercent":
+ // Use original sorting for inactive entries
+ // Use only one isActive to be sure we do the same
+ comp_a = a.isActive ? a.progress : a.listIndex;
+ comp_b = a.isActive ? b.progress : b.listIndex;
+ break;
+ case "TimeRemaining":
+ comp_a = a.isActive ? a.lastSec : a.listIndex;
+ comp_b = a.isActive ? b.lastSec : b.listIndex;
+ break;
+ case "Transferred":
+ comp_a = a.currBytes;
+ comp_b = b.currBytes;
+ break;
+ case "TransferRate":
+ comp_a = a.isActive ? a._speed : a.listIndex;
+ comp_b = a.isActive ? b._speed : b.listIndex;
+ break;
+ case "TimeElapsed":
+ comp_a = (a.endTime && a.startTime && (a.endTime > a.startTime))
+ ? a.endTime - a.startTime
+ : 0;
+ comp_b = (b.endTime && b.startTime && (b.endTime > b.startTime))
+ ? b.endTime - b.startTime
+ : 0;
+ break;
+ case "StartTime":
+ comp_a = a.startTime;
+ comp_b = b.startTime;
+ break;
+ case "EndTime":
+ comp_a = a.endTime;
+ comp_b = b.endTime;
+ break;
+ case "Source":
+ comp_a = a.uri;
+ comp_b = b.uri;
+ break;
+ case "unsorted": // Special case for reverting to original order
+ default:
+ comp_a = a.listIndex;
+ comp_b = b.listIndex;
+ }
+ if (comp_a > comp_b)
+ return sortAscending ? 1 : -1;
+ if (comp_a < comp_b)
+ return sortAscending ? -1 : 1;
+ return 0;
+ }
+
+ // Cache the current selection
+ this._cacheSelection();
+
+ // Do the actual sorting of the array
+ this._dlList.sort(compfunc);
+
+ var row = this._dlList.indexOf(aDownload);
+ if (row == -1)
+ // Repaint the tree
+ this._tree.invalidate();
+ else if (row == aRow)
+ // No effect
+ this._selectionCache = null;
+ else if (row < aRow)
+ // Download moved up from aRow to row
+ this._tree.invalidateRange(row, aRow);
+ else
+ // Download moved down from aRow to row
+ this._tree.invalidateRange(aRow, row)
+
+ // Restore the selection
+ this._restoreSelection();
+ },
+
+ getRowData: function(aRow) {
+ return this._dlList[aRow];
+ },
+
+ // ***** local member vars *****
+
+ _tree: null,
+ _dlBundle: null,
+ _lastListIndex: 0,
+ _selectionCache: null,
+ __dateService: null,
+
+ // ***** local helper functions *****
+
+ get _dateService() {
+ if (!this.__dateService) {
+ this.__dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Components.interfaces.nsIScriptableDateFormat);
+ }
+ return this.__dateService;
+ },
+
+ // Get array index in _dlList for a given download ID
+ _getIdxForGUID: function(aGUID) {
+ if (aGUID in this._dlMap)
+ return this._dlList.indexOf(this._dlMap[aGUID]);
+ return -1;
+ },
+
+ // Cache IDs of selected downloads for later restoration
+ _cacheSelection: function() {
+ // Abort if there's already something cached
+ if (this._selectionCache)
+ return;
+
+ this._selectionCache = [];
+ if (this.selection.count < 1)
+ return;
+
+ // Walk all selected rows and cache theior download IDs
+ var start = {};
+ var end = {};
+ var numRanges = this.selection.getRangeCount();
+ for (let rg = 0; rg < numRanges; rg++){
+ this.selection.getRangeAt(rg, start, end);
+ for (let row = start.value; row <= end.value; row++){
+ this._selectionCache.push(this._dlList[row].guid);
+ }
+ }
+ },
+
+ // Restore selection from cached IDs (as possible)
+ _restoreSelection: function() {
+ // Abort if the cache is empty
+ if (!this._selectionCache)
+ return;
+
+ this.selection.clearSelection();
+ for (let guid of this._selectionCache) {
+ // Find out what row this is now and if possible, add it to the selection
+ var row = this._getIdxForGUID(guid);
+ if (row != -1)
+ this.selection.rangedSelect(row, row, true);
+ }
+ // Work done, clear the cache
+ this._selectionCache = null;
+ },
+
+ _convertTimeToString: function(aTime) {
+ var timeObj = new Date(aTime);
+
+ // Check if it is today and only display the time. Only bother
+ // checking for today if it's within the last 24 hours, since
+ // computing midnight is not really cheap. Sometimes we may get dates
+ // in the future, so always show those.
+ var ago = Date.now() - aTime;
+ var dateFormat = Components.interfaces.nsIScriptableDateFormat.dateFormatShort;
+ if (ago > -10000 && ago < (1000 * 24 * 60 * 60)) {
+ var midnight = new Date();
+ midnight.setHours(0);
+ midnight.setMinutes(0);
+ midnight.setSeconds(0);
+ midnight.setMilliseconds(0);
+
+ if (aTime > midnight.getTime())
+ dateFormat = Components.interfaces.nsIScriptableDateFormat.dateFormatNone;
+ }
+
+ return (this._dateService.FormatDateTime("", dateFormat,
+ Components.interfaces.nsIScriptableDateFormat.timeFormatNoSeconds,
+ timeObj.getFullYear(), timeObj.getMonth() + 1,
+ timeObj.getDate(), timeObj.getHours(),
+ timeObj.getMinutes(), timeObj.getSeconds()));
+ },
+
+};
diff --git a/xpfe/components/downloads/content/uploadProgress.js b/xpfe/components/downloads/content/uploadProgress.js
new file mode 100644
index 000000000..b537ff420
--- /dev/null
+++ b/xpfe/components/downloads/content/uploadProgress.js
@@ -0,0 +1,189 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+
+const kInterval = 750; // Default to .75 seconds.
+
+var gPersist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ .createInstance(Components.interfaces.nsIWebBrowserPersist);
+var gSource = window.arguments[0].QueryInterface(Components.interfaces.nsIFileURL);
+var gTarget = window.arguments[1].QueryInterface(Components.interfaces.nsIURL);
+var gFileName = gSource.file.leafName;
+var gFileSize = gSource.file.fileSize;
+var gPercent = -1;
+var gStartTime;
+var gLastUpdate;
+var gLastSeconds;
+var gBundle;
+var gStatus;
+var gTime;
+var gSize;
+var gProgress;
+var gMeter;
+
+function onLoad()
+{
+ gBundle = document.getElementById("dmBundle");
+ gStatus = document.getElementById("status");
+ gTime = document.getElementById("timeElapsed");
+ gSize = document.getElementById("size");
+ gProgress = document.getElementById("progressText");
+ gMeter = document.getElementById("progress");
+ var status = gBundle.getString("notStarted");
+ document.title =
+ gBundle.getFormattedString("progressTitle", [gFileName, status]);
+ gStatus.value = status;
+ gTime.value = gBundle.getFormattedString("timeSingle",
+ DownloadUtils.convertTimeUnits(0));
+ gSize.value = DownloadUtils.getTransferTotal(0, gFileSize);
+ document.getElementById("target").value =
+ gBundle.getFormattedString("toTarget", [gTarget.resolve(".")]);
+ document.getElementById("source").value =
+ gBundle.getFormattedString("fromSource", [gSource.file.leafName]);
+ gPersist.progressListener = gProgressListener;
+ gPersist.saveURI(gSource, null, null, 0, null, null, gTarget, null);
+ document.documentElement.getButton("cancel").focus();
+}
+
+function onUnload()
+{
+ if (gPersist)
+ gPersist.cancel(Components.results.NS_BINDING_ABORTED);
+ gPersist = null;
+}
+
+function setPercent(aPercent, aStatus)
+{
+ gPercent = aPercent;
+ document.title = gBundle.getFormattedString("progressTitlePercent",
+ [aPercent, gFileName, aStatus]);
+ gProgress.value = gBundle.getFormattedString("percentFormat", [aPercent]);
+ gMeter.mode = "normal";
+ gMeter.value = aPercent;
+}
+
+var gProgressListener = {
+ // ----- nsIWebProgressListener methods -----
+
+ // Look for STATE_STOP and close dialog to indicate completion when it happens.
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aRequest instanceof Components.interfaces.nsIChannel &&
+ aRequest.URI.equals(gTarget) &&
+ aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
+ gPersist = null;
+ var status = gBundle.getString("finished");
+ setPercent(100, status);
+ gStatus.value = status;
+ gSize.value = DownloadUtils.getTransferTotal(gFileSize, gFileSize);
+ setTimeout(window.close, kInterval);
+ }
+ },
+
+ // Handle progress notifications.
+ onProgressChange: function(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange64(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+ },
+
+ onProgressChange64: function(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ if (aRequest instanceof Components.interfaces.nsIChannel &&
+ aRequest.URI.equals(gTarget)) {
+ // Get current time.
+ var now = Date.now();
+
+ // If interval hasn't elapsed, ignore it.
+ if (!gStartTime)
+ gStartTime = now;
+ else if (now - gLastUpdate < kInterval && aCurTotalProgress < gFileSize)
+ return;
+
+ // Update this time.
+ gLastUpdate = now;
+
+ // Update elapsed time.
+ var elapsed = (now - gStartTime) / 1000;
+
+ // Calculate percentage.
+ var status = gBundle.getString("uploading");
+ var percent = -1;
+ if (gFileSize > 0)
+ percent = Math.floor(aCurTotalProgress * 100 / gFileSize);
+ if (percent != gPercent)
+ setPercent(percent, status);
+
+ // Update time remaining.
+ var rate = elapsed && aCurTotalProgress / elapsed;
+ if (rate && gFileSize) {
+ var timeLeft;
+ [timeLeft, gLastSeconds] =
+ DownloadUtils.getTimeLeft((gFileSize - aCurTotalProgress) / rate,
+ gLastSeconds);
+ status = gBundle.getFormattedString("statusActive", [status, timeLeft]);
+ }
+ gStatus.value = status;
+
+ // Update dialog's display of elapsed time.
+ var timeUnits = DownloadUtils.convertTimeUnits(elapsed);
+ var timeString = timeUnits[2] ? "timeDouble" : "timeSingle";
+ gTime.value = gBundle.getFormattedString(timeString, timeUnits);
+
+ // Update size (nn KB of mm KB at xx.x KB/sec)
+ var size = DownloadUtils.getTransferTotal(aCurTotalProgress, gFileSize);
+ if (elapsed)
+ size = gBundle.getFormattedString("sizeSpeed", [size,
+ gBundle.getFormattedString("speedFormat",
+ DownloadUtils.convertByteUnits(rate))]);
+ gSize.value = size;
+ }
+ },
+
+ // Look for error notifications and display alert to user.
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
+ // Check for error condition (only if dialog is still open).
+ if (!Components.results.isSuccessCode(aStatus)) {
+ // Display error alert (using text supplied by back-end).
+ Services.prompt.alert(window, document.title, aMessage);
+ // Close the dialog.
+ window.close();
+ }
+ },
+
+ // Ignore onLocationChange and onSecurityChange notifications.
+ onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) {
+ },
+
+ onSecurityChange: function( aWebProgress, aRequest, aState ) {
+ },
+
+ // ---------- nsISupports methods ----------
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsIWebProgressListener2,
+ Components.interfaces.nsIWebProgressListener,
+ Components.interfaces.nsIInterfaceRequestor]),
+
+ // ---------- nsIInterfaceRequestor methods ----------
+
+ getInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIPrompt) ||
+ aIID.equals(Components.interfaces.nsIAuthPrompt)) {
+ var prompt;
+ if (aIID.equals(Components.interfaces.nsIPrompt))
+ prompt = Services.ww.getNewPrompter(window);
+ else
+ prompt = Services.ww.getNewAuthPrompter(window);
+ return prompt;
+ }
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+}
diff --git a/xpfe/components/downloads/content/uploadProgress.xul b/xpfe/components/downloads/content/uploadProgress.xul
new file mode 100644
index 000000000..4d074b540
--- /dev/null
+++ b/xpfe/components/downloads/content/uploadProgress.xul
@@ -0,0 +1,34 @@
+<?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"?>
+
+<!DOCTYPE dialog>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="cancel"
+ onload="onLoad();"
+ onunload="onUnload();"
+ style="width: 40em;">
+
+ <script type="application/javascript"
+ src="chrome://communicator/content/downloads/uploadProgress.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="dmBundle"
+ src="chrome://communicator/locale/downloads/downloadmanager.properties"/>
+ </stringbundleset>
+
+ <label id="source" value="" crop="center"/>
+ <label id="target" value="" crop="center"/>
+ <label id="size" value=""/>
+ <label id="timeElapsed" value=""/>
+ <label id="status" value=""/>
+ <hbox>
+ <progressmeter id="progress" mode="undetermined" value="0" flex="1"/>
+ <label id="progressText" value="" style="width: 4ch; text-align: right;"/>
+ </hbox>
+</dialog>
diff --git a/xpfe/components/downloads/download-prefs.js b/xpfe/components/downloads/download-prefs.js
new file mode 100644
index 000000000..e0c77f184
--- /dev/null
+++ b/xpfe/components/downloads/download-prefs.js
@@ -0,0 +1,41 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+pref("browser.download.finished_download_sound", false);
+pref("browser.download.finished_sound_url", "");
+pref("browser.download.useDownloadDir", false);
+pref("browser.download.folderList", 1);
+
+pref("browser.download.manager.useToolkitUI", false);
+pref("browser.download.manager.showAlertOnComplete", true);
+pref("browser.download.manager.showAlertInterval", 2000);
+pref("browser.download.manager.retention", 2);
+pref("browser.download.manager.quitBehavior", 0);
+pref("browser.download.manager.addToRecentDocs", true);
+pref("browser.download.manager.scanWhenDone", true);
+pref("browser.download.manager.resumeOnWakeDelay", 10000);
+pref("browser.download.manager.flashCount", 2);
+pref("browser.download.manager.showWhenStarting", true);
+pref("browser.download.manager.focusWhenStarting", false);
+pref("browser.download.manager.closeWhenDone", false);
+pref("browser.download.progress.closeWhenDone", false);
+
+pref("browser.download.show_plugins_in_list", true);
+pref("browser.download.hide_plugins_without_extensions", true);
+
+// Number of milliseconds to wait for the http headers (and thus
+// the Content-Disposition filename) before giving up and falling back to
+// picking a filename without that info in hand so that the user sees some
+// feedback from their action.
+pref("browser.download.saveLinkAsFilenameTimeout", 4000);
+
+// 0 opens the download manager
+// 1 opens a progress dialog
+// 2 and other values, no download manager, no progress dialog.
+pref("browser.download.manager.behavior", 0);
+#ifdef XP_UNIX
+// For the download dialog
+pref("browser.download.progressDnldDialog.enable_launch_reveal_buttons", false);
+#endif \ No newline at end of file
diff --git a/xpfe/components/downloads/jar.mn b/xpfe/components/downloads/jar.mn
new file mode 100644
index 000000000..defaff5f5
--- /dev/null
+++ b/xpfe/components/downloads/jar.mn
@@ -0,0 +1,29 @@
+# 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/.
+
+comm.jar:
+ content/communicator/downloads/downloadmanager.js (content/downloadmanager.js)
+* content/communicator/downloads/downloadmanager.xul (content/downloadmanager.xul)
+ content/communicator/downloads/DownloadProgressListener.js (content/DownloadProgressListener.js)
+* content/communicator/downloads/progressDialog.xul (content/progressDialog.xul)
+ content/communicator/downloads/progressDialog.js (content/progressDialog.js)
+ content/communicator/downloads/uploadProgress.xul (content/uploadProgress.xul)
+ content/communicator/downloads/uploadProgress.js (content/uploadProgress.js)
+ content/communicator/downloads/treeView.js (content/treeView.js)
+
+en-US.jar:
+ locale/en-US/communicator/downloads/downloadmanager.dtd (locale/downloadmanager.dtd)
+ locale/en-US/communicator/downloads/downloadmanager.properties (locale/downloadmanager.properties)
+ locale/en-US/communicator/downloads/progressDialog.dtd (locale/progressDialog.dtd)
+
+classic.jar:
+ skin/classic/communicator/downloads/dl-remove.png (skin/dl-remove.png)
+#ifdef XP_MACOSX
+ skin/classic/communicator/downloads/downloadButtons.png (skin/mac/downloadButtons.png)
+ skin/classic/communicator/downloads/downloadmanager.css (skin/mac/downloadmanager.css)
+ skin/classic/communicator/downloads/progressBg.png (skin/mac/progressBg.png)
+#else
+ skin/classic/communicator/downloads/downloadButtons.png (skin/downloadButtons.png)
+ skin/classic/communicator/downloads/downloadmanager.css (skin/downloadmanager.css)
+#endif
diff --git a/xpfe/components/downloads/locale/downloadmanager.dtd b/xpfe/components/downloads/locale/downloadmanager.dtd
new file mode 100644
index 000000000..26160d84a
--- /dev/null
+++ b/xpfe/components/downloads/locale/downloadmanager.dtd
@@ -0,0 +1,90 @@
+<!-- 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/. -->
+
+<!ENTITY downloadManager.title "Download Manager">
+
+<!ENTITY menuBar.tooltip "Menu Bar">
+<!ENTITY searchBar.tooltip "Search Bar">
+
+<!ENTITY search.placeholder "Search Downloads">
+<!ENTITY search.label "Search Downloads">
+<!ENTITY search.accesskey "S">
+<!ENTITY search.key "f">
+
+<!ENTITY cmd.clearList.label "Clear List">
+<!ENTITY cmd.clearList.tooltip "Removes completed, canceled, and failed downloads from the list">
+<!ENTITY cmd.clearList.accesskey "C">
+
+<!ENTITY col.name.label "Name">
+<!ENTITY col.name.accesskey "N">
+<!ENTITY col.name.tooltip "File Name">
+<!ENTITY col.status.label "Status">
+<!ENTITY col.status.accesskey "S">
+<!ENTITY col.status.tooltip "Status">
+<!ENTITY col.actionPlay.label "Pause/Resume/Retry">
+<!ENTITY col.actionPlay.accesskey "u">
+<!ENTITY col.actionPlay.tooltip "Pause/Resume/Retry">
+<!ENTITY col.actionStop.label "Cancel/Remove">
+<!ENTITY col.actionStop.accesskey "C">
+<!ENTITY col.actionStop.tooltip "Cancel/Remove">
+<!ENTITY col.progress.label "Progress">
+<!ENTITY col.progress.accesskey "P">
+<!ENTITY col.progress.tooltip "Progress">
+<!ENTITY col.timeremaining.label "Time Left">
+<!ENTITY col.timeremaining.accesskey "L">
+<!ENTITY col.timeremaining.tooltip "Time Left">
+<!ENTITY col.transferred.label "Transferred">
+<!ENTITY col.transferred.accesskey "T">
+<!ENTITY col.transferred.tooltip "Transferred">
+<!ENTITY col.transferrate.label "Speed">
+<!ENTITY col.transferrate.accesskey "d">
+<!ENTITY col.transferrate.tooltip "Speed">
+<!ENTITY col.timeelapsed.label "Time Elapsed">
+<!ENTITY col.timeelapsed.accesskey "E">
+<!ENTITY col.timeelapsed.tooltip "Time Elapsed">
+<!ENTITY col.starttime.label "Start Time">
+<!ENTITY col.starttime.accesskey "a">
+<!ENTITY col.starttime.tooltip "Start Time">
+<!ENTITY col.endtime.label "End Time">
+<!ENTITY col.endtime.accesskey "i">
+<!ENTITY col.endtime.tooltip "End Time">
+<!ENTITY col.progresstext.label "&#37;">
+<!ENTITY col.progresstext.accesskey "&#37;">
+<!ENTITY col.progresstext.tooltip "Progress (&#37;)">
+<!ENTITY col.source.label "Source">
+<!ENTITY col.source.accesskey "o">
+<!ENTITY col.source.tooltip "Source">
+
+<!ENTITY view.columns.label "Show Columns">
+<!ENTITY view.columns.accesskey "C">
+<!ENTITY view.sortBy.label "Sort by">
+<!ENTITY view.sortBy.accesskey "S">
+
+<!ENTITY view.unsorted.label "Unsorted">
+<!ENTITY view.unsorted.accesskey "U">
+<!ENTITY view.sortAscending.label "A > Z Sort Order">
+<!ENTITY view.sortAscending.accesskey "A">
+<!ENTITY view.sortDescending.label "Z > A Sort Order">
+<!ENTITY view.sortDescending.accesskey "Z">
+
+<!ENTITY cmd.pause.label "Pause">
+<!ENTITY cmd.pause.accesskey "P">
+<!ENTITY cmd.resume.label "Resume">
+<!ENTITY cmd.resume.accesskey "R">
+<!ENTITY cmd.retry.label "Retry">
+<!ENTITY cmd.retry.accesskey "t">
+<!ENTITY cmd.cancel.label "Cancel">
+<!ENTITY cmd.cancel.accesskey "C">
+<!ENTITY cmd.remove.label "Remove From List">
+<!ENTITY cmd.remove.accesskey "e">
+<!ENTITY cmd.open.label "Open">
+<!ENTITY cmd.open.accesskey "O">
+<!ENTITY cmd.show.label "Open Containing Folder">
+<!ENTITY cmd.show.accesskey "F">
+<!ENTITY cmd.goToDownloadPage.label "Go to Download Page">
+<!ENTITY cmd.goToDownloadPage.accesskey "G">
+<!ENTITY cmd.copyDownloadLink.label "Copy Download Link">
+<!ENTITY cmd.copyDownloadLink.accesskey "L">
+<!ENTITY cmd.properties.label "Properties…">
+<!ENTITY cmd.properties.accesskey "s">
diff --git a/xpfe/components/downloads/locale/downloadmanager.properties b/xpfe/components/downloads/locale/downloadmanager.properties
new file mode 100644
index 000000000..4e6499e7b
--- /dev/null
+++ b/xpfe/components/downloads/locale/downloadmanager.properties
@@ -0,0 +1,73 @@
+# 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/.
+
+paused=Paused
+downloading=Downloading
+uploading=Uploading
+notStarted=Not Started
+failed=Failed
+finished=Finished
+canceled=Canceled
+blocked=Blocked
+
+# LOCALIZATION NOTE (downloadsTitleFiles, downloadsTitlePercent): Semi-colon list of
+# plural forms. See: http://developer.mozilla.org/en/Localization_and_Plurals
+# %1$S number of files; %2$S overall download percent (only for downloadsTitlePercent)
+# %% will appear as a single % sign, so %2$S%% is the percent number plus the % sign
+# examples: 2% of 1 file - Download Manager; 22% of 11 files - Download Manager
+downloadsTitleFiles=%1$S file - Download Manager;%1$S files - Download Manager
+downloadsTitlePercent=%2$S%% of %1$S file - Download Manager;%2$S%% of %1$S files - Download Manager
+
+# LOCALIZATION NOTE (progressTitle):
+# %1$S is the file name, %2$S is the download state
+# examples: coolvideo.ogg - Finished; Borealis-nightly.zip - Paused
+progressTitle=%1$S - %2$S
+# LOCALIZATION NOTE (progressTitlePercent):
+# %1$S is download percent, %2$S is the file name, %3$S is the download state
+# %% will appear as a single % sign, so %1$S%% is the percent number plus the % sign
+# examples: 42% of coolvideo.ogg - Paused; 98% of Borealis-nightly.zip - Downloading
+progressTitlePercent=%1$S%% of %2$S - %3$S
+
+# LOCALIZATION NOTE (percentFormat): %1$S is download percent
+# %% will appear as a single % sign, so %1$S%% is the percent number plus the % sign
+percentFormat=%1$S%%
+
+# LOCALIZATION NOTE (speedFormat):
+# %1$S rate number; %2$S rate unit
+# units are taken from toolkit's downloads.properties
+# example: 2.2 MB/sec
+speedFormat=%1$S %2$S/sec
+
+# LOCALIZATION NOTE (timeSingle): %1$S time number; %2$S time unit
+# example: 1 minute; 11 hours
+timeSingle=%1$S %2$S
+# LOCALIZATION NOTE (timeDouble):
+# %1$S time number; %2$S time unit; %3$S time sub number; %4$S time sub unit
+# example: 11 hours, 2 minutes; 1 day, 22 hours
+timeDouble=%1$S %2$S, %3$S %4$S
+
+# LOCALIZATION NOTE (timeElapsedSingle): %1$S time number; %2$S time unit
+# example: 1 minute elapsed; 11 hours elapsed
+timeElapsedSingle=%1$S %2$S elapsed
+# LOCALIZATION NOTE (timeElapsedDouble):
+# %1$S time number; %2$S time unit; %3$S time sub number; %4$S time sub unit
+# example: 11 hours, 2 minutes elapsed; 1 day, 22 hours elapsed
+timeElapsedDouble=%1$S %2$S, %3$S %4$S elapsed
+
+# LOCALIZATION NOTE (sizeSpeed):
+# %1$S is transfer progress; %2$S download speed
+# example: 1.1 of 11.1 GB (2.2 MB/sec)
+sizeSpeed=%1$S (%2$S)
+
+# LOCALIZATION NOTE (statusActive): — is the "em dash" (long dash)
+# %1$S download status; %2$S time remaining
+# example: Paused — 11 hours, 2 minutes remaining
+statusActive=%1$S — %2$S
+
+fromSource=From %S
+toTarget=To %S
+
+fileExecutableSecurityWarning="%S" is an executable file. Executable files may contain viruses or other malicious code that could harm your computer. Use caution when opening this file. Are you sure you want to launch "%S"?
+fileExecutableSecurityWarningTitle=Open Executable File?
+fileExecutableSecurityWarningDontAsk=Don't ask me this again
diff --git a/xpfe/components/downloads/locale/progressDialog.dtd b/xpfe/components/downloads/locale/progressDialog.dtd
new file mode 100644
index 000000000..733faf736
--- /dev/null
+++ b/xpfe/components/downloads/locale/progressDialog.dtd
@@ -0,0 +1,20 @@
+<!-- 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/. -->
+
+<!ENTITY progress.title "Download in Progress…">
+<!ENTITY closeWindow.key "w">
+<!ENTITY cmd.pause.tooltip "Pause">
+<!ENTITY cmd.resume.tooltip "Resume">
+<!ENTITY cmd.retry.tooltip "Retry">
+<!ENTITY cmd.cancel.tooltip "Cancel">
+<!ENTITY cmd.open.label "Open">
+<!ENTITY cmd.open.accesskey "O">
+<!ENTITY cmd.show.label "Open Containing Folder">
+<!ENTITY cmd.show.accesskey "F">
+<!ENTITY cmd.goToDownloadPage.label "Go to Download Page">
+<!ENTITY cmd.goToDownloadPage.accesskey "G">
+<!ENTITY cmd.copyDownloadLink.label "Copy Download Link">
+<!ENTITY cmd.copyDownloadLink.accesskey "L">
+<!ENTITY closeWhenDone.label "Close this window when the download is complete.">
+<!ENTITY closeWhenDone.accesskey "w">
diff --git a/xpfe/components/downloads/moz.build b/xpfe/components/downloads/moz.build
new file mode 100644
index 000000000..20d06f2dc
--- /dev/null
+++ b/xpfe/components/downloads/moz.build
@@ -0,0 +1,20 @@
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsISuiteDownloadManagerUI.idl',
+]
+
+XPIDL_MODULE = 'navigatorcompsbase'
+
+EXTRA_COMPONENTS += [
+ 'nsSuiteDownloadManager.manifest',
+ 'src/nsDownloadsStartup.js',
+ 'src/nsSuiteDownloadManagerUI.js',
+]
+
+JS_PREFERENCE_PP_FILES += ['download-prefs.js']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/xpfe/components/downloads/nsSuiteDownloadManager.manifest b/xpfe/components/downloads/nsSuiteDownloadManager.manifest
new file mode 100644
index 000000000..1c7f22eee
--- /dev/null
+++ b/xpfe/components/downloads/nsSuiteDownloadManager.manifest
@@ -0,0 +1,5 @@
+component {08bbb4af-7bff-4b16-8ff7-d62f3ec5aa0c} nsSuiteDownloadManagerUI.js
+contract @mozilla.org/download-manager-ui;1 {08bbb4af-7bff-4b16-8ff7-d62f3ec5aa0c}
+component {49507fe5-2cee-4824-b6a3-e999150ce9b8} nsDownloadsStartup.js
+contract @mozilla.org/suite/downloadsstartup;1 {49507fe5-2cee-4824-b6a3-e999150ce9b8}
+category profile-after-change DownloadsStartup @mozilla.org/suite/downloadsstartup;1 \ No newline at end of file
diff --git a/xpfe/components/downloads/public/nsISuiteDownloadManagerUI.idl b/xpfe/components/downloads/public/nsISuiteDownloadManagerUI.idl
new file mode 100644
index 000000000..6e780c564
--- /dev/null
+++ b/xpfe/components/downloads/public/nsISuiteDownloadManagerUI.idl
@@ -0,0 +1,19 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+#include "nsIDownloadManagerUI.idl"
+interface nsIDOMWindow;
+
+[scriptable, uuid(23e0112d-2924-4e74-8886-157b60c4af24)]
+interface nsISuiteDownloadManagerUI : nsIDownloadManagerUI
+{
+ readonly attribute nsIDOMWindow recentWindow;
+ void showManager([optional] in nsIInterfaceRequestor aWindowContext,
+ [optional] in nsIDownload aDownload,
+ [optional] in short aReason);
+ void showProgress([optional] in nsIInterfaceRequestor aWindowContext,
+ [optional] in nsIDownload aDownload,
+ [optional] in short aReason);
+};
diff --git a/xpfe/components/downloads/skin/dl-remove.png b/xpfe/components/downloads/skin/dl-remove.png
new file mode 100644
index 000000000..167ecbb08
--- /dev/null
+++ b/xpfe/components/downloads/skin/dl-remove.png
Binary files differ
diff --git a/xpfe/components/downloads/skin/downloadButtons.png b/xpfe/components/downloads/skin/downloadButtons.png
new file mode 100644
index 000000000..4fe7963ab
--- /dev/null
+++ b/xpfe/components/downloads/skin/downloadButtons.png
Binary files differ
diff --git a/xpfe/components/downloads/skin/downloadmanager.css b/xpfe/components/downloads/skin/downloadmanager.css
new file mode 100644
index 000000000..db3e1e2a8
--- /dev/null
+++ b/xpfe/components/downloads/skin/downloadmanager.css
@@ -0,0 +1,95 @@
+/* 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 url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* Note to themers:
+ On rows and all cells, those properties for download states are available:
+ active, inactive, resumable, paused, downloading, finished, failed, canceled, blocked
+*/
+
+treechildren::-moz-tree-image(Name) {
+ margin-inline-end: 2px;
+}
+
+#pauseButton,
+treechildren::-moz-tree-image(ActionPlay, downloading, resumable) {
+ /* pause */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+#ActionPlay,
+#resumeButton,
+treechildren::-moz-tree-image(ActionPlay, paused, resumable) {
+ /* resume */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+#retryButton,
+treechildren::-moz-tree-image(ActionPlay, failed),
+treechildren::-moz-tree-image(ActionPlay, canceled) {
+ /* retry */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+#ActionStop,
+#cancelButton,
+treechildren::-moz-tree-image(ActionStop, active) {
+ /* cancel */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(ActionStop, inactive) {
+ /* remove */
+ list-style-image: url("chrome://communicator/skin/downloads/dl-remove.png");
+ -moz-image-region: auto;
+}
+
+/* progress dialogs */
+#dlProgressWindow {
+ /* match dialog.css */
+ padding-top: 8px;
+ padding-bottom: 10px;
+ padding-inline-start: 8px;
+ padding-inline-end: 10px;
+}
+
+/* label with dropdown, actually done as a button type=menu */
+#fileName, #fileSource {
+ -moz-appearance: none;
+ background-color: transparent;
+ margin: 0px 5px;
+ border: 0px;
+ min-width: 0px;
+ min-height: 0px;
+}
+
+#fileName > .button-box,
+#fileSource > .button-box {
+ -moz-appearance: none;
+ padding: 0px;
+}
+
+#fileName {
+ font-weight: bold;
+}
+
+.mini-button {
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ min-width: 0;
+ min-height: 0;
+}
+
+.mini-button > .button-box {
+ -moz-appearance: none;
+ padding: 0 !important;
+}
diff --git a/xpfe/components/downloads/skin/mac/downloadButtons.png b/xpfe/components/downloads/skin/mac/downloadButtons.png
new file mode 100644
index 000000000..6f3c7c985
--- /dev/null
+++ b/xpfe/components/downloads/skin/mac/downloadButtons.png
Binary files differ
diff --git a/xpfe/components/downloads/skin/mac/downloadmanager.css b/xpfe/components/downloads/skin/mac/downloadmanager.css
new file mode 100644
index 000000000..36b2399b9
--- /dev/null
+++ b/xpfe/components/downloads/skin/mac/downloadmanager.css
@@ -0,0 +1,140 @@
+/* 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 url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* Note to themers:
+ On rows and all cells, those properties for download states are available:
+ active, inactive, resumable, paused, downloading, finished, failed, canceled, blocked
+*/
+
+#clearListButton {
+ -moz-appearance: toolbarbutton;
+ text-shadow: 0 1px rgba(255, 255, 255, 0.4);
+ margin: 0 6px;
+ min-height: 18px;
+ min-width: 0;
+}
+
+#clearListButton:-moz-lwtheme:not([disabled="true"]) {
+ color: inherit;
+ text-shadow: inherit;
+}
+
+treechildren::-moz-tree-image {
+ margin-inline-start: -1px;
+}
+
+treechildren::-moz-tree-image(Name) {
+ margin-inline-end: 2px;
+}
+
+#ActionPlay {
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(ActionPlay, downloading, resumable),
+#pauseButton {
+ /* pause */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+treechildren::-moz-tree-image(ActionPlay, paused, resumable),
+#resumeButton {
+ /* resume */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(ActionPlay, failed),
+treechildren::-moz-tree-image(ActionPlay, canceled),
+#retryButton {
+ /* retry */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+#ActionStop {
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(ActionStop, active),
+#cancelButton {
+ /* cancel */
+ list-style-image: url("chrome://communicator/skin/downloads/downloadButtons.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(ActionStop, inactive) {
+ /* remove */
+ list-style-image: url("chrome://communicator/skin/downloads/dl-remove.png");
+ -moz-image-region: auto;
+}
+
+/* There's no way this will look like a native progressbar, but we can at least
+ try to mimic the aqua, graphite and non-active colors. */
+treechildren::-moz-tree-progressmeter {
+ background: url("chrome://communicator/skin/downloads/progressBg.png") repeat-x;
+ color: rgba(0, 115, 255, 0.5);
+ border: none;
+ padding-bottom: 1px;
+ margin-top: 3px;
+}
+
+treechildren:-moz-system-metric(mac-graphite-theme)::-moz-tree-progressmeter {
+ color: rgba(43, 71, 106, 0.5);
+}
+
+treechildren:-moz-window-inactive::-moz-tree-progressmeter {
+ color: rgba(0, 0, 0, 0.1);
+}
+
+/* progress dialogs */
+
+#dlProgressWindow {
+ padding: 14px;
+}
+
+/* focusable label, focus ring like .link-text but not a link */
+#fileName, #fileSource {
+ border: 1px solid transparent;
+ /* 1px is used for border, make margins smaller by that */
+ margin-top: 0px;
+ margin-bottom: 1px;
+ margin-inline-start: 5px;
+ margin-inline-end: 4px;
+}
+
+#fileName:focus,
+#fileSource:focus {
+ border: 1px dotted -moz-DialogText;
+}
+
+#fileName {
+ font-weight: bold;
+ margin-bottom: 6px;
+}
+
+.mini-button {
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ min-width: 0;
+ min-height: 0;
+}
+
+.mini-button > .button-box {
+ -moz-appearance: none;
+ padding: 0 !important;
+}
+
+#progressBox {
+ margin-top: 6px;
+ margin-bottom: 6px;
+}
diff --git a/xpfe/components/downloads/skin/mac/progressBg.png b/xpfe/components/downloads/skin/mac/progressBg.png
new file mode 100644
index 000000000..0586429da
--- /dev/null
+++ b/xpfe/components/downloads/skin/mac/progressBg.png
Binary files differ
diff --git a/xpfe/components/downloads/src/nsDownloadsStartup.js b/xpfe/components/downloads/src/nsDownloadsStartup.js
new file mode 100644
index 000000000..fd09cb1b2
--- /dev/null
+++ b/xpfe/components/downloads/src/nsDownloadsStartup.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* 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 component enables the Legacy API for downloads at startup.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * CID and Contract ID of the JavaScript implementation of nsITransfer.
+ */
+const kTransferCid = Components.ID("{b02be33b-d47c-4bd3-afd9-402a942426b0}");
+const kTransferContractId = "@mozilla.org/transfer;1";
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsStartup
+
+function DownloadsStartup() { }
+
+DownloadsStartup.prototype = {
+ classID: Components.ID("{49507fe5-2cee-4824-b6a3-e999150ce9b8}"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsStartup),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function DS_observe(aSubject, aTopic, aData)
+ {
+ const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
+ // Override the JavaScript nsITransfer implementation with the
+ // Legacy version.
+ Components.manager.QueryInterface(nsIComponentRegistrar)
+ .registerFactory(kTransferCid, "",
+ kTransferContractId, null);
+ },
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsStartup]);
diff --git a/xpfe/components/downloads/src/nsSuiteDownloadManagerUI.js b/xpfe/components/downloads/src/nsSuiteDownloadManagerUI.js
new file mode 100644
index 000000000..d01f2e0e4
--- /dev/null
+++ b/xpfe/components/downloads/src/nsSuiteDownloadManagerUI.js
@@ -0,0 +1,174 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const TOOLKIT_MANAGER_URL = "chrome://mozapps/content/downloads/downloads.xul";
+const DOWNLOAD_MANAGER_URL = "chrome://communicator/content/downloads/downloadmanager.xul";
+const PREF_FOCUS_WHEN_STARTING = "browser.download.manager.focusWhenStarting";
+const PREF_FLASH_COUNT = "browser.download.manager.flashCount";
+const PREF_DM_BEHAVIOR = "browser.download.manager.behavior";
+const PREF_FORCE_TOOLKIT_UI = "browser.download.manager.useToolkitUI";
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownloadManagerUI class
+
+function nsDownloadManagerUI() {}
+
+nsDownloadManagerUI.prototype = {
+ classID: Components.ID("{08bbb4af-7bff-4b16-8ff7-d62f3ec5aa0c}"),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIDownloadManagerUI
+
+ show: function show(aWindowContext, aDownload, aReason, aUsePrivateUI)
+ {
+ var behavior = 0;
+ if (aReason != Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED) {
+ if (aUsePrivateUI)
+ behavior = 1;
+ else try {
+ behavior = Services.prefs.getIntPref(PREF_DM_BEHAVIOR);
+ if (Services.prefs.getBoolPref(PREF_FORCE_TOOLKIT_UI))
+ behavior = 0; //We are forcing toolkit UI, force manager behavior
+ } catch (e) { }
+ }
+
+ switch (behavior) {
+ case 0:
+ this.showManager(aWindowContext, aDownload, aReason);
+ break;
+ case 1:
+ this.showProgress(aWindowContext, aDownload, aReason);
+ }
+
+ return; // No UI for behavior >= 2
+ },
+
+ visible: false, // needed for private downloads to work
+
+ getAttention: function getAttention()
+ {
+ var window = this.recentWindow;
+ if (!window)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // This preference may not be set, so defaulting to two.
+ var flashCount = 2;
+ try {
+ flashCount = Services.prefs.getIntPref(PREF_FLASH_COUNT);
+ } catch (e) { }
+
+ window.getAttentionWithCycleCount(flashCount);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISuiteDownloadManagerUI
+
+ get recentWindow() {
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ return wm.getMostRecentWindow("Download:Manager");
+ },
+
+ showManager: function showManager(aWindowContext, aDownload, aReason)
+ {
+ // First we see if it is already visible
+ let window = this.recentWindow;
+ if (window) {
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ var focus = prefs.getBoolPref(PREF_FOCUS_WHEN_STARTING);
+ if (focus || aReason == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED)
+ window.focus();
+ else
+ this.getAttention();
+ return;
+ }
+
+ let parent = null;
+ // We try to get a window to use as the parent here. If we don't have one,
+ // the download manager will close immediately after opening if the pref
+ // browser.download.manager.closeWhenDone is set to true.
+ try {
+ if (aWindowContext)
+ parent = aWindowContext.getInterface(Ci.nsIDOMWindow);
+ } catch (e) { /* it's OK to not have a parent window */ }
+
+ // We pass the download manager and the nsIDownload we want selected (if any)
+ var params = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ params.appendElement(aDownload, false);
+
+ // Pass in the reason as well
+ let reason = Cc["@mozilla.org/supports-PRInt16;1"].
+ createInstance(Ci.nsISupportsPRInt16);
+ reason.data = aReason;
+ params.appendElement(reason, false);
+
+ var manager = DOWNLOAD_MANAGER_URL;
+ try {
+ if (Services.prefs.getBoolPref(PREF_FORCE_TOOLKIT_UI))
+ manager = TOOLKIT_MANAGER_URL;
+ } catch(ex) {}
+
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ ww.openWindow(parent,
+ manager,
+ null,
+ "all,dialog=no",
+ params);
+ },
+
+ showProgress: function showProgress(aWindowContext, aDownload, aReason)
+ {
+ // Fail if our passed in download is invalid
+ if (!aDownload)
+ return;
+
+ var parent = null;
+ // We try to get a window to use as the parent here. If we don't have one,
+ // the progress window will close immediately after opening if the pref
+ // browser.download.manager.closeWhenDone is set to true.
+ try {
+ if (aWindowContext)
+ parent = aWindowContext.getInterface(Ci.nsIDOMWindow);
+ } catch (e) { /* it's OK to not have a parent window */ }
+
+ var params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ params.appendElement(aDownload, false);
+
+ // Pass in the reason as well
+ let reason = Cc["@mozilla.org/supports-PRInt16;1"].
+ createInstance(Ci.nsISupportsPRInt16);
+ reason.data = aReason;
+ params.appendElement(reason, false);
+
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ ww.openWindow(parent,
+ "chrome://communicator/content/downloads/progressDialog.xul",
+ null,
+ "chrome,titlebar,centerscreen,minimizable=yes,dialog=no",
+ params);
+ },
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI,
+ Ci.nsISuiteDownloadManagerUI])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDownloadManagerUI]);
diff --git a/xpfe/components/eula/content/eula.js b/xpfe/components/eula/content/eula.js
new file mode 100644
index 000000000..fe11d0971
--- /dev/null
+++ b/xpfe/components/eula/content/eula.js
@@ -0,0 +1,26 @@
+Components.utils.import("resource://gre/modules/Communicator.jsm");
+
+function Startup() {
+ main = document.getElementById("main");
+ let textbox = document.createElement("textbox");
+ [
+ ["id", "eula"],
+ ["readonly", "true"],
+ ["multiline", "true"],
+ ["cols", "80"],
+ ["rows", "20"],
+ ["style", "resize: none; font-family: -moz-fixed;"],
+ ["value", Communicator.readfile("GreD", "license.txt")]
+ ].forEach(([name, value]) => textbox.setAttribute(name, value));
+ main.appendChild(textbox);
+}
+
+function onAccept() {
+ Communicator.service.prefs.setBoolPref("app.eula.accepted", true);
+}
+
+function onCancel() {
+ Communicator.service.prefs.setBoolPref("app.eula.accepted", false);
+ Communicator.service.startup.quit(Communicator.service.startup.eForceQuit);
+}
+
diff --git a/xpfe/components/eula/content/eula.xul b/xpfe/components/eula/content/eula.xul
new file mode 100644
index 000000000..a7ee23148
--- /dev/null
+++ b/xpfe/components/eula/content/eula.xul
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/eula/eula.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % eulaDTD SYSTEM "chrome://communicator/locale/eula/eula.dtd">
+%eulaDTD;
+]>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ id="comm-eula"
+ title="&brandFullName;"
+ buttons="accept,cancel"
+ buttonlabelaccept="&eula.accept.label;"
+ buttonlabelcancel="&eula.cancel.label;"
+ ondialogaccept="return onAccept();"
+ ondialogcancel="onCancel();"
+ onload="Startup();">
+
+ <script type="application/javascript" src="chrome://communicator/content/eula/eula.js"/>
+
+ <dialogheader id="eula-header" class="header-large" title="&eula.header.title;" description="&eula.header.description;"/>
+ <separator/>
+ <vbox id="main"/>
+ <separator/>
+</dialog> \ No newline at end of file
diff --git a/xpfe/components/eula/jar.mn b/xpfe/components/eula/jar.mn
new file mode 100644
index 000000000..f04ee8e27
--- /dev/null
+++ b/xpfe/components/eula/jar.mn
@@ -0,0 +1,14 @@
+#filter substitution
+# 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/.
+
+comm.jar:
+ content/communicator/eula/eula.js (content/eula.js)
+ content/communicator/eula/eula.xul (content/eula.xul)
+
+en-US.jar:
+ locale/en-US/communicator/eula/eula.dtd (locale/eula.dtd)
+
+classic.jar:
+ skin/classic/communicator/eula/eula.css (skin/eula.css)
diff --git a/xpfe/components/eula/locale/eula.dtd b/xpfe/components/eula/locale/eula.dtd
new file mode 100644
index 000000000..e764bb726
--- /dev/null
+++ b/xpfe/components/eula/locale/eula.dtd
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+
+<!ENTITY eula.title "&brandFullName;">
+<!ENTITY eula.accept.label "Accept">
+<!ENTITY eula.cancel.label "Decline">
+<!ENTITY eula.header.title "License Agreement">
+<!ENTITY eula.header.description "Please review the license terms before using &brandFullName;">
diff --git a/xpfe/components/eula/moz.build b/xpfe/components/eula/moz.build
new file mode 100644
index 000000000..697e0cda1
--- /dev/null
+++ b/xpfe/components/eula/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/xpfe/components/eula/skin/eula.css b/xpfe/components/eula/skin/eula.css
new file mode 100644
index 000000000..5a308d930
--- /dev/null
+++ b/xpfe/components/eula/skin/eula.css
@@ -0,0 +1,11 @@
+#eula {
+ -moz-appearance: none;
+ color: -moz-FieldText;
+ background-color: -moz-Field;
+ margin: 1em;
+ border: 1px solid;
+ -moz-border-top-colors: ActiveBorder;
+ -moz-border-right-colors: ActiveBorder;
+ -moz-border-bottom-colors: ActiveBorder;
+ -moz-border-left-colors: ActiveBorder;
+} \ No newline at end of file
diff --git a/xpfe/components/moz.build b/xpfe/components/moz.build
new file mode 100644
index 000000000..2ba4b5fe1
--- /dev/null
+++ b/xpfe/components/moz.build
@@ -0,0 +1,19 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+ 'eula',
+ 'profile',
+]
+
+if CONFIG['BINOC_NAVIGATOR']:
+ DIRS += [
+ 'autocomplete',
+ 'devtools',
+ 'preferences',
+ ]
+
+if CONFIG['BINOC_DOWNLOADS']:
+ DIRS += ['downloads'] \ No newline at end of file
diff --git a/xpfe/components/preferences/content/pref-advanced.xul b/xpfe/components/preferences/content/pref-advanced.xul
new file mode 100644
index 000000000..faae8b693
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-advanced.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 overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
+ <!ENTITY % prefAdvancedDTD SYSTEM "chrome://communicator/locale/pref/pref-advanced.dtd"> %prefAdvancedDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="advanced_pane"
+ label="&pref.advanced.title;"
+ style="padding: 0px !important;">
+ <iframe src="chrome://global/content/config.xul"
+ flex="1"
+ style="height: 100%;"/>
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-applicationManager.js b/xpfe/components/preferences/content/pref-applicationManager.js
new file mode 100644
index 000000000..5a8e5e61a
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-applicationManager.js
@@ -0,0 +1,98 @@
+/* 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/. */
+
+// As pref-applications.js is always loaded, we can (and should!) reuse
+// the nsI* constants from there, if needed also any services we need.
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gAppManagerDialog = {
+ _removed: [],
+
+ init: function appManager_init() {
+ this.handlerInfo = window.arguments[0];
+
+ var bundle = document.getElementById("appManagerBundle");
+ var contentText;
+ if (this.handlerInfo.type == TYPE_MAYBE_FEED)
+ contentText = bundle.getString("descriptionHandleWebFeeds");
+ else {
+ var description = gApplicationsPane._describeType(this.handlerInfo);
+ var key = (this.handlerInfo.wrappedHandlerInfo instanceof nsIMIMEInfo) ?
+ "descriptionHandleFile" :
+ "descriptionHandleProtocol";
+ contentText = bundle.getFormattedString(key, [description]);
+ }
+ document.getElementById("appDescription").textContent = contentText;
+
+ var list = document.getElementById("appList");
+ var apps = this.handlerInfo.possibleApplicationHandlers.enumerate();
+ while (apps.hasMoreElements()) {
+ let app = apps.getNext();
+ app.QueryInterface(nsIHandlerApp);
+ var item = list.appendItem(app.name);
+ item.className = "listitem-iconic";
+ item.setAttribute("image", gApplicationsPane._getIconURLForHandlerApp(app));
+ item.app = app;
+ }
+
+ list.selectedIndex = 0;
+ },
+
+ onOK: function appManager_onOK() {
+ if (!this._removed.length) {
+ // return early to avoid calling the |store| method.
+ return;
+ }
+
+ for (var i = 0; i < this._removed.length; ++i)
+ this.handlerInfo.removePossibleApplicationHandler(this._removed[i]);
+
+ this.handlerInfo.store();
+ },
+
+ onCancel: function appManager_onCancel() {
+ // do nothing
+ },
+
+ remove: function appManager_remove() {
+ var list = document.getElementById("appList");
+ this._removed.push(list.selectedItem.app);
+ var index = list.selectedIndex;
+ list.removeItemAt(index);
+ if (list.getRowCount() == 0) {
+ // The list is now empty, make the bottom part disappear
+ document.getElementById("appDetails").hidden = true;
+ }
+ else {
+ // Select the item at the same index, if we removed the last
+ // item of the list, select the previous item
+ if (index == list.getRowCount())
+ --index;
+ list.selectedIndex = index;
+ }
+ },
+
+ onSelect: function appManager_onSelect() {
+ var list = document.getElementById("appList");
+ if (!list.selectedItem) {
+ document.getElementById("cmd_delete").setAttribute("disabled", "true");
+ return;
+ }
+ document.getElementById("cmd_delete").removeAttribute("disabled");
+ var app = list.selectedItem.app;
+ var address = "";
+ if (app instanceof nsILocalHandlerApp)
+ address = app.executable.path;
+ else if (app instanceof nsIWebHandlerApp)
+ address = app.uriTemplate;
+ else if (app instanceof nsIWebContentHandlerInfo)
+ address = app.uri;
+ document.getElementById("appLocation").value = address;
+ var bundle = document.getElementById("appManagerBundle");
+ var appType = app instanceof nsILocalHandlerApp ? "descriptionLocalApp"
+ : "descriptionWebApp";
+ document.getElementById("appType").value = bundle.getString(appType);
+ }
+};
diff --git a/xpfe/components/preferences/content/pref-applicationManager.xul b/xpfe/components/preferences/content/pref-applicationManager.xul
new file mode 100644
index 000000000..503c64176
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-applicationManager.xul
@@ -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/. -->
+
+<?xml-stylesheet href="chrome://communicator/skin/"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://communicator/locale/pref/pref-applicationManager.dtd">
+
+<dialog id="appManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel"
+ onload="gAppManagerDialog.init();"
+ ondialogaccept="gAppManagerDialog.onOK();"
+ ondialogcancel="gAppManagerDialog.onCancel();"
+ title="&appManager.title;"
+ style="&appManager.style;"
+ persist="screenX screenY">
+
+ <script type="application/javascript"
+ src="chrome://communicator/content/pref/pref-applications.js"/>
+ <script type="application/javascript"
+ src="chrome://communicator/content/pref/pref-applicationManager.js"/>
+
+ <commandset id="appManagerCommandSet">
+ <command id="cmd_delete"
+ oncommand="gAppManagerDialog.remove();"
+ disabled="true"/>
+ </commandset>
+
+ <keyset id="appManagerKeyset">
+ <key id="delete" keycode="VK_DELETE" command="cmd_delete"/>
+ </keyset>
+
+ <stringbundleset id="appManagerBundleset">
+ <stringbundle id="appManagerBundle"
+ src="chrome://communicator/locale/pref/pref-applicationManager.properties"/>
+ </stringbundleset>
+
+ <description id="appDescription"/>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <listbox id="appList" onselect="gAppManagerDialog.onSelect();" flex="1"/>
+ <vbox>
+ <button id="remove"
+ label="&remove.label;"
+ accesskey="&remove.accesskey;"
+ command="cmd_delete"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+ <vbox id="appDetails">
+ <separator class="thin"/>
+ <label id="appType"/>
+ <textbox id="appLocation" readonly="true" class="plain"/>
+ </vbox>
+</dialog>
diff --git a/xpfe/components/preferences/content/pref-applications.js b/xpfe/components/preferences/content/pref-applications.js
new file mode 100644
index 000000000..0305d1337
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-applications.js
@@ -0,0 +1,1754 @@
+/* -*- Mode: Java; tab-width: 2; c-basic-offset: 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/. */
+
+function Startup()
+{
+ gApplicationsPane.init();
+}
+
+//****************************************************************************//
+// Constants & Enumeration Values
+
+// constants for interfaces we need multiple times
+const nsIHandlerApp = Components.interfaces.nsIHandlerApp;
+const nsIHandlerInfo = Components.interfaces.nsIHandlerInfo;
+const nsILocalHandlerApp = Components.interfaces.nsILocalHandlerApp;
+const nsIWebHandlerApp = Components.interfaces.nsIWebHandlerApp;
+const nsIWebContentHandlerInfo = Components.interfaces.nsIWebContentHandlerInfo;
+const nsIFilePicker = Components.interfaces.nsIFilePicker;
+const nsIMIMEInfo = Components.interfaces.nsIMIMEInfo;
+const nsIPropertyBag = Components.interfaces.nsIPropertyBag;
+
+// global services
+var handlerSvc = Components.classes["@mozilla.org/uriloader/handler-service;1"]
+ .getService(Components.interfaces.nsIHandlerService);
+var categoryMgr = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+var mimeSvc = Components.classes["@mozilla.org/mime;1"]
+ .getService(Components.interfaces.nsIMIMEService);
+var converterSvc = Components.classes["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]
+ .getService(Components.interfaces.nsIWebContentConverterService);
+var shellSvc = null;
+if ("@binaryoutcast.com/navigator/shell-service;1" in Components.classes)
+ shellSvc = Components.classes["@binaryoutcast.com/navigator/shell-service;1"]
+ .getService(Components.interfaces.nsIShellService);
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+
+const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
+
+// Preferences that affect which entries to show in the list.
+const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
+const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
+ "browser.download.hide_plugins_without_extensions";
+
+/*
+ * Preferences where we store handling information about the feed type.
+ *
+ * browser.feeds.handler
+ * - "messenger", "reader" (clarified further using the .default preference),
+ * or "ask" -- indicates the default handler being used to process feeds;
+ * "messenger" is obsolete, use "reader" instead; to specify that the
+ * handler is messenger, set browser.feeds.handler.default to "messenger";
+ *
+ * browser.feeds.handler.default
+ * - "messenger", "client" or "web" -- indicates the chosen feed reader used
+ * to display feeds, either transiently (i.e., when the "use as default"
+ * checkbox is unchecked, corresponds to when browser.feeds.handler=="ask")
+ * or more permanently (i.e., the item displayed in the dropdown in Feeds
+ * preferences)
+ *
+ * browser.feeds.handler.webservice
+ * - the URL of the currently selected web service used to read feeds
+ *
+ * browser.feeds.handlers.application
+ * - nsILocalFile, stores the current client-side feed reading app if one has
+ * been chosen
+ */
+const PREF_FEED_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_FEED_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_FEED_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_FEED_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_FEED_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_FEED_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_FEED_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_FEED_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_FEED_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_FEED_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_FEED_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_FEED_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+// The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify
+// the actions the application can take with content of various types.
+// But since nsIHandlerInfo doesn't support plugins, there's no value
+// identifying the "use plugin" action, so we use this constant instead.
+const kActionUsePlugin = -3;
+const kActionChooseApp = -2;
+const kActionManageApp = -1;
+
+//****************************************************************************//
+// Utilities
+
+function getFileDisplayName(aFile) {
+ if ("nsILocalFileWin" in Components.interfaces &&
+ aFile instanceof Components.interfaces.nsILocalFileWin) {
+ try {
+ return aFile.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+ else if ("nsILocalFileMac" in Components.interfaces &&
+ aFile instanceof Components.interfaces.nsILocalFileMac) {
+ try {
+ return aFile.bundleDisplayName;
+ } catch (e) {}
+ }
+ return aFile.leafName;
+}
+
+function getLocalHandlerApp(aFile) {
+ var localHandlerApp = Components.classes["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(nsILocalHandlerApp);
+ localHandlerApp.name = getFileDisplayName(aFile);
+ localHandlerApp.executable = aFile;
+
+ return localHandlerApp;
+}
+
+/**
+ * An enumeration of items in a JS array.
+ *
+ * FIXME: use ArrayConverter once it lands (bug 380839).
+ *
+ * @constructor
+ */
+function ArrayEnumerator(aItems) {
+ this._index = 0;
+ this._contents = aItems;
+}
+
+ArrayEnumerator.prototype = {
+ _index: 0,
+
+ hasMoreElements: function() {
+ return this._index < this._contents.length;
+ },
+
+ getNext: function() {
+ return this._contents[this._index++];
+ }
+};
+
+function isFeedType(t) {
+ return t == TYPE_MAYBE_FEED || t == TYPE_MAYBE_VIDEO_FEED || t == TYPE_MAYBE_AUDIO_FEED;
+}
+
+//****************************************************************************//
+// HandlerInfoWrapper
+
+/**
+ * This object wraps nsIHandlerInfo with some additional functionality
+ * the Applications prefpane needs to display and allow modification of
+ * the list of handled types.
+ *
+ * We create an instance of this wrapper for each entry we might display
+ * in the prefpane, and we compose the instances from various sources,
+ * including navigator.plugins and the handler service.
+ *
+ * We don't implement all the original nsIHandlerInfo functionality,
+ * just the stuff that the prefpane needs.
+ *
+ * In theory, all of the custom functionality in this wrapper should get
+ * pushed down into nsIHandlerInfo eventually.
+ */
+function HandlerInfoWrapper(aType, aHandlerInfo) {
+ this.type = aType;
+ this.wrappedHandlerInfo = aHandlerInfo;
+}
+
+HandlerInfoWrapper.prototype = {
+ // The wrapped nsIHandlerInfo object. In general, this object is private,
+ // but there are a couple cases where callers access it directly for things
+ // we haven't (yet?) implemented, so we make it a public property.
+ wrappedHandlerInfo: null,
+
+
+ //**************************************************************************//
+ // nsIHandlerInfo
+
+ // The MIME type or protocol scheme.
+ type: null,
+
+ get description() {
+ if (this.wrappedHandlerInfo.description)
+ return this.wrappedHandlerInfo.description;
+
+ if (this.primaryExtension) {
+ var extension = this.primaryExtension.toUpperCase();
+ return document.getElementById("bundlePrefApplications")
+ .getFormattedString("fileEnding", [extension]);
+ }
+
+ return this.type;
+ },
+
+ get preferredApplicationHandler() {
+ return this.wrappedHandlerInfo.preferredApplicationHandler;
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue;
+
+ // Make sure the preferred handler is in the set of possible handlers.
+ if (aNewValue)
+ this.addPossibleApplicationHandler(aNewValue)
+ },
+
+ get possibleApplicationHandlers() {
+ return this.wrappedHandlerInfo.possibleApplicationHandlers;
+ },
+
+ addPossibleApplicationHandler: function(aNewHandler) {
+ try {
+ if (this.possibleApplicationHandlers.indexOf(0, aNewHandler) != -1)
+ return;
+ } catch (e) {
+ }
+ this.possibleApplicationHandlers.appendElement(aNewHandler, false);
+ },
+
+ removePossibleApplicationHandler: function(aHandler) {
+ var defaultApp = this.preferredApplicationHandler;
+ if (defaultApp && aHandler.equals(defaultApp)) {
+ // If the app we remove was the default app, we must make sure
+ // it won't be used anymore
+ this.alwaysAskBeforeHandling = true;
+ this.preferredApplicationHandler = null;
+ }
+
+ try {
+ var handlerIdx = this.possibleApplicationHandlers.indexOf(0, aHandler);
+ this.possibleApplicationHandlers.removeElementAt(handlerIdx);
+ } catch (e) {
+ }
+ },
+
+ get hasDefaultHandler() {
+ return this.wrappedHandlerInfo.hasDefaultHandler;
+ },
+
+ get defaultDescription() {
+ return this.wrappedHandlerInfo.defaultDescription;
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ // If we have an enabled plugin, then the action is to use that plugin.
+ if (this.plugin && !this.isDisabledPluginType)
+ return kActionUsePlugin;
+
+ // If the action is to use a helper app, but we don't have a preferred
+ // handler app, then switch to using the system default, if any; otherwise
+ // fall back to saving to disk, which is the default action in nsMIMEInfo.
+ // Note: "save to disk" is an invalid value for protocol info objects,
+ // but the alwaysAskBeforeHandling getter will detect that situation
+ // and always return true in that case to override this invalid value.
+ if (this.wrappedHandlerInfo.preferredAction == nsIHandlerInfo.useHelperApp &&
+ !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
+ return this.wrappedHandlerInfo.hasDefaultHandler ?
+ nsIHandlerInfo.useSystemDefault :
+ nsIHandlerInfo.saveToDisk;
+ }
+
+ return this.wrappedHandlerInfo.preferredAction;
+ },
+
+ set preferredAction(aNewValue) {
+ // We don't modify the preferred action if the new action is to use a plugin
+ // because handler info objects don't understand our custom "use plugin"
+ // value. Also, leaving it untouched means that we can automatically revert
+ // to the old setting if the user ever removes the plugin.
+
+ if (aNewValue != kActionUsePlugin)
+ this.wrappedHandlerInfo.preferredAction = aNewValue;
+ },
+
+ get alwaysAskBeforeHandling() {
+ // If this type is handled only by a plugin, we can't trust the value
+ // in the handler info object, since it'll be a default based on the absence
+ // of any user configuration, and the default in that case is to always ask,
+ // even though we never ask for content handled by a plugin, so special case
+ // plugin-handled types by returning false here.
+ if (this.plugin && this.handledOnlyByPlugin)
+ return false;
+
+ // If this is a protocol type and the preferred action is "save to disk",
+ // which is invalid for such types, then return true here to override that
+ // action. This could happen when the preferred action is to use a helper
+ // app, but the preferredApplicationHandler is invalid, and there isn't
+ // a default handler, so the preferredAction getter returns save to disk
+ // instead.
+ if (!(this.wrappedHandlerInfo instanceof nsIMIMEInfo) &&
+ this.preferredAction == nsIHandlerInfo.saveToDisk)
+ return true;
+
+ return this.wrappedHandlerInfo.alwaysAskBeforeHandling;
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue;
+ },
+
+
+ //**************************************************************************//
+ // nsIMIMEInfo
+
+ // The primary file extension associated with this type, if any.
+ //
+ // XXX Plugin objects contain an array of MimeType objects with "suffixes"
+ // properties; if this object has an associated plugin, shouldn't we check
+ // those properties for an extension?
+ get primaryExtension() {
+ try {
+ if (this.wrappedHandlerInfo instanceof nsIMIMEInfo &&
+ this.wrappedHandlerInfo.primaryExtension)
+ return this.wrappedHandlerInfo.primaryExtension;
+ } catch(ex) {}
+
+ return null;
+ },
+
+
+ //**************************************************************************//
+ // Plugin Handling
+
+ // A plugin that can handle this type, if any.
+ //
+ // Note: just because we have one doesn't mean it *will* handle the type.
+ // That depends on whether or not the type is in the list of types for which
+ // plugin handling is disabled.
+ plugin: null,
+
+ // Whether or not this type is only handled by a plugin or is also handled
+ // by some user-configured action as specified in the handler info object.
+ //
+ // Note: we can't just check if there's a handler info object for this type,
+ // because OS and user configuration is mixed up in the handler info object,
+ // so we always need to retrieve it for the OS info and can't tell whether
+ // it represents only OS-default information or user-configured information.
+ //
+ // FIXME: once handler info records are broken up into OS-provided records
+ // and user-configured records, stop using this boolean flag and simply
+ // check for the presence of a user-configured record to determine whether
+ // or not this type is only handled by a plugin. Filed as bug 395142.
+ handledOnlyByPlugin: undefined,
+
+ get isDisabledPluginType() {
+ return this._getDisabledPluginTypes().indexOf(this.type) != -1;
+ },
+
+ _getDisabledPluginTypes: function() {
+ var types = "";
+
+ if (Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES))
+ types = Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
+
+ // Only split if the string isn't empty so we don't end up with an array
+ // containing a single empty string.
+ return types ? types.split(",") : [];
+ },
+
+ disablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ if (disabledPluginTypes.indexOf(this.type) == -1)
+ disabledPluginTypes.push(this.type);
+
+ Services.prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ categoryMgr.deleteCategoryEntry("Gecko-Content-Viewers",
+ this.type,
+ false);
+ },
+
+ enablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ var type = this.type;
+ disabledPluginTypes = disabledPluginTypes.filter(v => v != type);
+
+ Services.prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ categoryMgr.
+ addCategoryEntry("Gecko-Content-Viewers",
+ this.type,
+ "@mozilla.org/content/plugin/document-loader-factory;1",
+ false,
+ true);
+ },
+
+
+ //**************************************************************************//
+ // Storage
+
+ store: function() {
+ handlerSvc.store(this.wrappedHandlerInfo);
+ },
+
+
+ //**************************************************************************//
+ // Icons
+
+ get smallIcon() {
+ return this._getIcon(16);
+ },
+
+ get largeIcon() {
+ return this._getIcon(32);
+ },
+
+ _getIcon: function(aSize) {
+ if (this.primaryExtension)
+ return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize;
+
+ if (this.wrappedHandlerInfo instanceof nsIMIMEInfo)
+ return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type;
+
+ // We're falling back to a generic icon when we can't get a URL for one
+ // (for example in the case of protocol schemes).
+ return null;
+ },
+
+ // The type class is used for setting icons through CSS for types that don't
+ // explicitly set their icons.
+ typeClass: "unknown"
+
+};
+
+
+//****************************************************************************//
+// Feed Handler Info
+
+/**
+ * This object implements nsIHandlerInfo for the feed types. It's a separate
+ * object because we currently store handling information for the feed type
+ * in a set of preferences rather than the nsIHandlerService-managed datastore.
+ *
+ * This object inherits from HandlerInfoWrapper in order to get functionality
+ * that isn't special to the feed type.
+ *
+ * XXX Should we inherit from HandlerInfoWrapper? After all, we override
+ * most of that wrapper's properties and methods, and we have to dance around
+ * the fact that the wrapper expects to have a wrappedHandlerInfo, which we
+ * don't provide.
+ */
+
+function FeedHandlerInfo(aMIMEType) {
+ HandlerInfoWrapper.call(this, aMIMEType, null);
+}
+
+FeedHandlerInfo.prototype = {
+ //**************************************************************************//
+ // nsIHandlerInfo
+
+ get description() {
+ return document.getElementById("bundlePrefApplications")
+ .getString(this.typeClass);
+ },
+
+ get preferredApplicationHandler() {
+ switch (document.getElementById(this._prefSelectedReader).value) {
+ case "client":
+ var file = document.getElementById(this._prefSelectedApp).value;
+ if (file)
+ return getLocalHandlerApp(file);
+
+ return null;
+
+ case "web":
+ var uri = document.getElementById(this._prefSelectedWeb).value;
+ if (!uri)
+ return null;
+ return converterSvc.getWebContentHandlerByURI(this.type, uri);
+
+ case "messenger":
+ default:
+ // When the pref is set to messenger, we handle feeds internally,
+ // we don't forward them to a local or web handler app, so there is
+ // no preferred handler.
+ return null;
+ }
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ if (aNewValue instanceof nsILocalHandlerApp) {
+ document.getElementById(this._prefSelectedApp).value = aNewValue.executable;
+ document.getElementById(this._prefSelectedReader).value = "client";
+ }
+ else if (aNewValue instanceof nsIWebContentHandlerInfo) {
+ document.getElementById(this._prefSelectedWeb).value = aNewValue.uri;
+ document.getElementById(this._prefSelectedReader).value = "web";
+ // Make the web handler be the new "auto handler" for feeds.
+ // Note: we don't have to unregister the auto handler when the user picks
+ // a non-web handler (local app, RSS News & Blogs, etc.) because the service
+ // only uses the "auto handler" when the selected reader is a web handler.
+ // We also don't have to unregister it when the user turns on "always ask"
+ // (i.e. preview in browser), since that also overrides the auto handler.
+ converterSvc.setAutoHandler(this.type, aNewValue);
+ }
+ },
+
+ _possibleApplicationHandlers: null,
+
+ get possibleApplicationHandlers() {
+ if (this._possibleApplicationHandlers)
+ return this._possibleApplicationHandlers;
+
+ // A minimal implementation of nsIMutableArray. It only supports the two
+ // methods its callers invoke, namely appendElement, nsIArray::enumerate
+ // and nsIArray::indexOf.
+ this._possibleApplicationHandlers = {
+ _inner: [],
+ _removed: [],
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIMutableArray) ||
+ aIID.equals(Components.interfaces.nsIArray) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ get length() {
+ return this._inner.length;
+ },
+
+ enumerate: function() {
+ return new ArrayEnumerator(this._inner);
+ },
+
+ indexOf: function indexOf(startIndex, element) {
+ return this._inner.indexOf(element, startIndex);
+ },
+
+ appendElement: function(aHandlerApp, aWeak) {
+ this._inner.push(aHandlerApp);
+ },
+
+ removeElementAt: function(aIndex) {
+ this._removed.push(this._inner[aIndex]);
+ this._inner.splice(aIndex, 1);
+ },
+
+ queryElementAt: function(aIndex, aInterface) {
+ return this._inner[aIndex].QueryInterface(aInterface);
+ }
+ };
+
+ // Add the selected local app if it's different from the OS default handler.
+ // Unlike for other types, we can store only one local app at a time for the
+ // feed type, since we store it in a preference that historically stores
+ // only a single path. But we display all the local apps the user chooses
+ // while the prefpane is open, only dropping the list when the user closes
+ // the prefpane, for maximum usability and consistency with other types.
+ var preferredAppFile = document.getElementById(this._prefSelectedApp).value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ let defaultApp = this._defaultApplicationHandler;
+ if (!defaultApp || !defaultApp.equals(preferredApp))
+ this._possibleApplicationHandlers.appendElement(preferredApp, false);
+ }
+
+ if (converterSvc) {
+ // Add the registered web handlers. There can be any number of these.
+ var webHandlers = converterSvc.getContentHandlers(this.type, {});
+ for each (let webHandler in webHandlers)
+ this._possibleApplicationHandlers.appendElement(webHandler, false);
+ }
+
+ return this._possibleApplicationHandlers;
+ },
+
+ __defaultApplicationHandler: undefined,
+ get _defaultApplicationHandler() {
+ if (this.__defaultApplicationHandler !== undefined)
+ return this.__defaultApplicationHandler;
+
+ var defaultFeedReader = null;
+ try {
+ defaultFeedReader = shellSvc.defaultFeedReader;
+ }
+ catch(ex) {
+ // no default reader
+ }
+
+ if (defaultFeedReader) {
+ let handlerApp = Components.classes["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(nsIHandlerApp);
+ handlerApp.name = getFileDisplayName(defaultFeedReader);
+ handlerApp.QueryInterface(nsILocalHandlerApp);
+ handlerApp.executable = defaultFeedReader;
+
+ this.__defaultApplicationHandler = handlerApp;
+ }
+ else {
+ this.__defaultApplicationHandler = null;
+ }
+
+ return this.__defaultApplicationHandler;
+ },
+
+ get hasDefaultHandler() {
+ try {
+ if (shellSvc.defaultFeedReader)
+ return true;
+ }
+ catch(ex) {
+ // no default reader
+ }
+
+ return false;
+ },
+
+ get defaultDescription() {
+ if (this.hasDefaultHandler)
+ return this._defaultApplicationHandler.name;
+
+ // Should we instead return null?
+ return "";
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ switch (document.getElementById(this._prefSelectedAction).value) {
+
+ case "reader": {
+ let preferredApp = this.preferredApplicationHandler;
+ let defaultApp = this._defaultApplicationHandler;
+
+ // If we have a valid preferred app, return useSystemDefault if it's
+ // the default app; otherwise return useHelperApp.
+ if (gApplicationsPane.isValidHandlerApp(preferredApp)) {
+ if (defaultApp && defaultApp.equals(preferredApp))
+ return nsIHandlerInfo.useSystemDefault;
+
+ return nsIHandlerInfo.useHelperApp;
+ }
+
+ // The pref is set to "reader", but we don't have a valid preferred app.
+ // What do we do now? Not sure this is the best option (perhaps we
+ // should direct the user to the default app, if any), but for now let's
+ // direct the user to live bookmarks.
+ return nsIHandlerInfo.handleInternally;
+ }
+
+ // If the action is "ask", then alwaysAskBeforeHandling will override
+ // the action, so it doesn't matter what we say it is, it just has to be
+ // something that doesn't cause the controller to hide the type.
+ case "ask":
+ case "messenger":
+ default:
+ return nsIHandlerInfo.handleInternally;
+ }
+ },
+
+ set preferredAction(aNewValue) {
+ switch (aNewValue) {
+
+ case nsIHandlerInfo.handleInternally:
+ document.getElementById(this._prefSelectedReader).value = "messenger";
+ break;
+
+ case nsIHandlerInfo.useHelperApp:
+ document.getElementById(this._prefSelectedAction).value = "reader";
+ // The controller has already set preferredApplicationHandler
+ // to the new helper app.
+ break;
+
+ case nsIHandlerInfo.useSystemDefault:
+ document.getElementById(this._prefSelectedAction).value = "reader";
+ this.preferredApplicationHandler = this._defaultApplicationHandler;
+ break;
+ }
+ },
+
+ get alwaysAskBeforeHandling() {
+ return document.getElementById(this._prefSelectedAction).value == "ask";
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ if (aNewValue == true)
+ document.getElementById(this._prefSelectedAction).value = "ask";
+ else
+ document.getElementById(this._prefSelectedAction).value = "reader";
+ },
+
+ // Whether or not we are currently storing the action selected by the user.
+ // We use this to suppress notification-triggered updates to the list when
+ // we make changes that may spawn such updates, specifically when we change
+ // the action for the feed type, which results in feed preference updates,
+ // which spawn "pref changed" notifications that would otherwise cause us
+ // to rebuild the view unnecessarily.
+ _storingAction: false,
+
+
+ //**************************************************************************//
+ // nsIMIMEInfo
+
+ primaryExtension: "xml",
+
+
+ //**************************************************************************//
+ // Storage
+
+ // Changes to the preferred action and handler take effect immediately
+ // (we write them out to the preferences right as they happen),
+ // so we when the controller calls store() after modifying the handlers,
+ // the only thing we need to store is the removal of possible handlers
+ // XXX Should we hold off on making the changes until this method gets called?
+ store: function() {
+ for each (let app in this._possibleApplicationHandlers._removed) {
+ if (app instanceof nsILocalHandlerApp) {
+ let pref = document.getElementById(PREF_FEED_SELECTED_APP);
+ var preferredAppFile = pref.value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ if (app.equals(preferredApp))
+ pref.reset();
+ }
+ }
+ else {
+ app.QueryInterface(nsIWebContentHandlerInfo);
+ converterSvc.removeContentHandler(app.contentType, app.uri);
+ }
+ }
+ this._possibleApplicationHandlers._removed = [];
+ },
+
+
+ //**************************************************************************//
+ // Icons
+
+ smallIcon: null,
+
+ largeIcon: null,
+
+ // The type class is used for setting icons through CSS for types that don't
+ // explicitly set their icons.
+ typeClass: "webFeed",
+
+ __proto__: HandlerInfoWrapper.prototype
+};
+
+var feedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_FEED),
+ _prefSelectedApp: PREF_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_FEED_SELECTED_READER,
+ typeClass: "webFeed"
+}
+
+var videoFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_VIDEO_FEED),
+ _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_VIDEO_FEED_SELECTED_READER,
+ typeClass: "videoPodcastFeed"
+}
+
+var audioFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_AUDIO_FEED),
+ _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_AUDIO_FEED_SELECTED_READER,
+ typeClass: "audioPodcastFeed"
+}
+
+
+//****************************************************************************//
+// Prefpane Controller
+
+var gApplicationsPane = {
+ // The set of types the app knows how to handle. A hash of HandlerInfoWrapper
+ // objects, indexed by type.
+ _handledTypes: {},
+
+ // The list of types we can show, sorted by the sort column/direction.
+ // An array of HandlerInfoWrapper objects. We build this list when we first
+ // load the data and then rebuild it when users change a pref that affects
+ // what types we can show or change the sort column/direction.
+ // Note: this isn't necessarily the list of types we *will* show; if the user
+ // provides a filter string, we'll only show the subset of types in this list
+ // that match that string.
+ _visibleTypes: [],
+
+ // A count of the number of times each visible type description appears.
+ // We use these counts to determine whether or not to annotate descriptions
+ // with their types to distinguish duplicate descriptions from each other.
+ // A hash of integer counts, indexed by string description.
+ _visibleTypeDescriptionCount: {},
+
+
+ //**************************************************************************//
+ // Convenience & Performance Shortcuts
+
+ // These get defined by init().
+ _brandShortName : null,
+ _prefsBundle : null,
+ _list : null,
+ _filter : null,
+
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ init: function() {
+ // Initialize shortcuts to some commonly accessed elements & values.
+ this._brandShortName =
+ document.getElementById("bundleBrand").getString("brandShortName");
+ this._prefsBundle = document.getElementById("bundlePrefApplications");
+ this._list = document.getElementById("handlersView");
+ this._filter = document.getElementById("filter");
+
+ // Observe preferences that influence what we display so we can rebuild
+ // the view when they change.
+ Services.prefs.addObserver(PREF_SHOW_PLUGINS_IN_LIST, this, false);
+ Services.prefs.addObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this, false);
+ Services.prefs.addObserver(PREF_FEED_SELECTED_APP, this, false);
+ Services.prefs.addObserver(PREF_FEED_SELECTED_WEB, this, false);
+ Services.prefs.addObserver(PREF_FEED_SELECTED_ACTION, this, false);
+
+ Services.prefs.addObserver(PREF_VIDEO_FEED_SELECTED_APP, this, false);
+ Services.prefs.addObserver(PREF_VIDEO_FEED_SELECTED_WEB, this, false);
+ Services.prefs.addObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this, false);
+
+ Services.prefs.addObserver(PREF_AUDIO_FEED_SELECTED_APP, this, false);
+ Services.prefs.addObserver(PREF_AUDIO_FEED_SELECTED_WEB, this, false);
+ Services.prefs.addObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this, false);
+
+ // Listen for window unload so we can remove our preference observers.
+ window.addEventListener("unload", this, false);
+
+ // Listen for user events on the listbox and its children
+ this._list.addEventListener("select", this, false);
+ this._list.addEventListener("command", this, false);
+
+ // Figure out how we should be sorting the list. We persist sort settings
+ // across sessions, so we can't assume the default sort column/direction.
+ this._sortColumn = document.getElementById("typeColumn");
+ if (document.getElementById("actionColumn").hasAttribute("sortDirection")) {
+ this._sortColumn = document.getElementById("actionColumn");
+ // The typeColumn element always has a sortDirection attribute,
+ // either because it was persisted or because the default value
+ // from the xul file was used. If we are sorting on the other
+ // column, we should remove it.
+ document.getElementById("typeColumn").removeAttribute("sortDirection");
+ }
+
+ // Load the data and build the list of handlers.
+ this._loadData();
+ this._rebuildVisibleTypes();
+ this._sortVisibleTypes();
+ this._rebuildView();
+
+ // Notify observers that the UI is now ready
+ Services.obs.notifyObservers(window, "app-handler-pane-loaded", null);
+ },
+
+ destroy: function() {
+ this._list.removeEventListener("command", this, false);
+ this._list.removeEventListener("select", this, false);
+ window.removeEventListener("unload", this, false);
+ Services.prefs.removeObserver(PREF_SHOW_PLUGINS_IN_LIST, this);
+ Services.prefs.removeObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this);
+ Services.prefs.removeObserver(PREF_FEED_SELECTED_APP, this);
+ Services.prefs.removeObserver(PREF_FEED_SELECTED_WEB, this);
+ Services.prefs.removeObserver(PREF_FEED_SELECTED_ACTION, this);
+
+ Services.prefs.removeObserver(PREF_VIDEO_FEED_SELECTED_APP, this);
+ Services.prefs.removeObserver(PREF_VIDEO_FEED_SELECTED_WEB, this);
+ Services.prefs.removeObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this);
+
+ Services.prefs.removeObserver(PREF_AUDIO_FEED_SELECTED_APP, this);
+ Services.prefs.removeObserver(PREF_AUDIO_FEED_SELECTED_WEB, this);
+ Services.prefs.removeObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this);
+ },
+
+
+ //**************************************************************************//
+ // nsISupports
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIObserver) ||
+ aIID.equals(Components.interfaces.nsIDOMEventListener ||
+ aIID.equals(Components.interfaces.nsISupports)))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ //**************************************************************************//
+ // nsIObserver
+
+ observe: function (aSubject, aTopic, aData) {
+ // Rebuild the list when there are changes to preferences that influence
+ // whether or not to show certain entries in the list.
+ if (aTopic == "nsPref:changed" && !this._storingAction) {
+ // These two prefs alter the list of visible types, so we have to rebuild
+ // that list when they change.
+ if (aData == PREF_SHOW_PLUGINS_IN_LIST ||
+ aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) {
+ this._rebuildVisibleTypes();
+ this._sortVisibleTypes();
+ }
+
+ // All the prefs we observe can affect what we display, so we rebuild
+ // the view when any of them changes.
+ this._rebuildView();
+ }
+ },
+
+
+ //**************************************************************************//
+ // nsIDOMEventListener
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ this.destroy();
+ break;
+ case "select":
+ if (this._list.selectedItem)
+ this._list.setAttribute("lastSelectedType",
+ this._list.selectedItem.type);
+ break;
+ case "command":
+ var target = aEvent.originalTarget;
+ switch (target.localName) {
+ case "listitem":
+ if (!this._list.disabled &&
+ target.type == this._list.getAttribute("lastSelectedType"))
+ this._list.selectedItem = target;
+ break;
+ case "listcell":
+ this.rebuildActionsMenu();
+ break;
+ case "menuitem":
+ switch (parseInt(target.value)) {
+ case kActionChooseApp:
+ this.chooseApp();
+ break;
+ case kActionManageApp:
+ this.manageApp();
+ break;
+ default:
+ this.onSelectAction(target);
+ break;
+ }
+ break;
+ }
+ }
+ },
+
+
+ //**************************************************************************//
+ // Composed Model Construction
+
+ _loadData: function() {
+ this._loadFeedHandler();
+ this._loadPluginHandlers();
+ this._loadApplicationHandlers();
+ },
+
+ _loadFeedHandler: function() {
+ this._handledTypes[TYPE_MAYBE_FEED] = feedHandlerInfo;
+ feedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_VIDEO_FEED] = videoFeedHandlerInfo;
+ videoFeedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_AUDIO_FEED] = audioFeedHandlerInfo;
+ audioFeedHandlerInfo.handledOnlyByPlugin = false;
+ },
+
+ /**
+ * Load the set of handlers defined by plugins.
+ *
+ * Note: if there's more than one plugin for a given MIME type, we assume
+ * the last one is the one that the application will use. That may not be
+ * correct, but it's how we've been doing it for years.
+ *
+ * Perhaps we should instead query navigator.mimeTypes for the set of types
+ * supported by the application and then get the plugin from each MIME type's
+ * enabledPlugin property. But if there's a plugin for a type, we need
+ * to know about it even if it isn't enabled, since we're going to give
+ * the user an option to enable it.
+ *
+ * I'll also note that my reading of nsPluginTag::RegisterWithCategoryManager
+ * suggests that enabledPlugin is only determined during registration
+ * and does not get updated when plugin.disable_full_page_plugin_for_types
+ * changes (unless modification of that preference spawns reregistration).
+ * So even if we could use enabledPlugin to get the plugin that would be used,
+ * we'd still need to check the pref ourselves to find out if it's enabled.
+ */
+ _loadPluginHandlers: function() {
+ let pluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+ .getService(Components.interfaces.nsIPluginHost);
+ for (let pluginTag of pluginHost.getPluginTags()) {
+ for (let type of pluginTag.getMimeTypes()) {
+ let handlerInfoWrapper;
+ if (type in this._handledTypes)
+ handlerInfoWrapper = this._handledTypes[type];
+ else {
+ let wrappedHandlerInfo =
+ mimeSvc.getFromTypeAndExtension(type, null);
+ handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
+ handlerInfoWrapper.handledOnlyByPlugin = true;
+ this._handledTypes[type] = handlerInfoWrapper;
+ }
+
+ handlerInfoWrapper.plugin = pluginHost.getPluginTagForType(type);
+ }
+ }
+ },
+
+ /**
+ * Load the set of handlers defined by the application datastore.
+ */
+ _loadApplicationHandlers: function() {
+ var wrappedHandlerInfos = handlerSvc.enumerate();
+ while (wrappedHandlerInfos.hasMoreElements()) {
+ let wrappedHandlerInfo =
+ wrappedHandlerInfos.getNext().QueryInterface(nsIHandlerInfo);
+ let type = wrappedHandlerInfo.type;
+
+ let handlerInfoWrapper;
+ if (type in this._handledTypes)
+ handlerInfoWrapper = this._handledTypes[type];
+ else {
+ handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
+ this._handledTypes[type] = handlerInfoWrapper;
+ }
+
+ handlerInfoWrapper.handledOnlyByPlugin = false;
+ }
+ },
+
+
+ //**************************************************************************//
+ // View Construction
+
+ _rebuildVisibleTypes: function() {
+ // Reset the list of visible types and the visible type description counts.
+ this._visibleTypes = [];
+ this._visibleTypeDescriptionCount = {};
+
+ // Get the preferences that help determine what types to show.
+ var showPlugins = Services.prefs.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST);
+ var hidePluginsWithoutExtensions =
+ Services.prefs.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS);
+
+ for (let type in this._handledTypes) {
+ let handlerInfo = this._handledTypes[type];
+
+ // Hide plugins without associated extensions if so prefed so we don't
+ // show a whole bunch of obscure types handled by plugins on Mac.
+ // Note: though protocol types don't have extensions, we still show them;
+ // the pref is only meant to be applied to MIME types, since plugins are
+ // only associated with MIME types.
+ // FIXME: should we also check the "suffixes" property of the plugin?
+ // Filed as bug 395135.
+ if (hidePluginsWithoutExtensions && handlerInfo.handledOnlyByPlugin &&
+ handlerInfo.wrappedHandlerInfo instanceof nsIMIMEInfo &&
+ !handlerInfo.primaryExtension)
+ continue;
+
+ // Hide types handled only by plugins if so prefed.
+ if (handlerInfo.handledOnlyByPlugin && !showPlugins)
+ continue;
+
+ // We couldn't find any reason to exclude the type, so include it.
+ this._visibleTypes.push(handlerInfo);
+
+ if (handlerInfo.description in this._visibleTypeDescriptionCount)
+ this._visibleTypeDescriptionCount[handlerInfo.description]++;
+ else
+ this._visibleTypeDescriptionCount[handlerInfo.description] = 1;
+ }
+ },
+
+ _rebuildView: function() {
+ // Clear the list of entries (the first 2 elements are <listcols> and
+ // <listhead>, they should never get removed).
+ while (this._list.childNodes.length > 2)
+ this._list.lastChild.remove();
+
+ var visibleTypes = this._visibleTypes;
+
+ // If the user is filtering the list, then only show matching types.
+ if (this._filter.value)
+ visibleTypes = visibleTypes.filter(this._matchesFilter, this);
+
+ for each (let visibleType in visibleTypes) {
+ let item = document.createElement("listitem");
+ item.setAttribute("allowevents", "true");
+ item.setAttribute("type", visibleType.type);
+ item.setAttribute("typeDescription", this._describeType(visibleType));
+ if (visibleType.smallIcon)
+ item.setAttribute("typeIcon", visibleType.smallIcon);
+ else
+ item.setAttribute("typeClass", visibleType.typeClass);
+ item.setAttribute("actionDescription",
+ this._describePreferredAction(visibleType));
+
+ if (!this._setIconClassForPreferredAction(visibleType, item)) {
+ var sysIcon = this._getIconURLForPreferredAction(visibleType);
+ if (sysIcon)
+ item.setAttribute("actionIcon", sysIcon);
+ else
+ item.setAttribute("appHandlerIcon", "app");
+ }
+
+ this._list.appendChild(item);
+ }
+ },
+
+ _matchesFilter: function(aType) {
+ var filterValue = this._filter.value.toLowerCase();
+ return this._describeType(aType).toLowerCase().indexOf(filterValue) != -1 ||
+ this._describePreferredAction(aType).toLowerCase().indexOf(filterValue) != -1;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the type represented by the given
+ * handler info object. Normally this is just the description provided by
+ * the info object, but if more than one object presents the same description,
+ * then we annotate the duplicate descriptions with the type itself to help
+ * users distinguish between those types.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type being described
+ * @returns {string} a description of the type
+ */
+ _describeType: function(aHandlerInfo) {
+ if (this._visibleTypeDescriptionCount[aHandlerInfo.description] > 1)
+ return this._prefsBundle.getFormattedString("typeDescriptionWithType",
+ [aHandlerInfo.description,
+ aHandlerInfo.type]);
+
+ return aHandlerInfo.description;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the preferred action to take on
+ * the type represented by the given handler info object.
+ *
+ * XXX Should this be part of the HandlerInfoWrapper interface? It would
+ * violate the separation of model and view, but it might make more sense
+ * nonetheless (f.e. it would make sortTypes easier).
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type whose preferred action
+ * is being described
+ * @returns {string} a description of the action
+ */
+ _describePreferredAction: function(aHandlerInfo) {
+ // alwaysAskBeforeHandling overrides the preferred action, so if that flag
+ // is set, then describe that behavior instead. For most types, this is
+ // the "alwaysAsk" string, but for the feed type we show something special.
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ if (isFeedType(aHandlerInfo.type))
+ return this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ else
+ return this._prefsBundle.getString("alwaysAsk");
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case nsIHandlerInfo.saveToDisk:
+ return this._prefsBundle.getString("saveFile");
+
+ case nsIHandlerInfo.useHelperApp:
+ var preferredApp = aHandlerInfo.preferredApplicationHandler;
+ var name = (preferredApp instanceof nsILocalHandlerApp) ?
+ getFileDisplayName(preferredApp.executable) :
+ preferredApp.name;
+ return this._prefsBundle.getFormattedString("useApp", [name]);
+
+ case nsIHandlerInfo.handleInternally:
+ // For the feed type, handleInternally means News & Blogs.
+ if (isFeedType(aHandlerInfo.type))
+ return this._prefsBundle.getFormattedString("addNewsBlogsInApp",
+ [this._brandShortName]);
+
+ // For other types, handleInternally looks like either useHelperApp
+ // or useSystemDefault depending on whether or not there's a preferred
+ // handler app.
+ return (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler)) ?
+ aHandlerInfo.preferredApplicationHandler.name :
+ aHandlerInfo.defaultDescription;
+
+ // XXX Why don't we say the app will handle the type internally?
+ // Is it because the app can't actually do that? But if that's true,
+ // then why would a preferredAction ever get set to this value
+ // in the first place?
+
+ case nsIHandlerInfo.useSystemDefault:
+ return this._prefsBundle.getFormattedString("useDefault",
+ [aHandlerInfo.defaultDescription]);
+
+ case kActionUsePlugin:
+ return this._prefsBundle.getFormattedString("usePluginIn",
+ [aHandlerInfo.plugin.name,
+ this._brandShortName]);
+ }
+ // we should never end up here but do a return to end up with a value
+ return null;
+ },
+
+ /**
+ * Whether or not the given handler app is valid.
+ *
+ * @param aHandlerApp {nsIHandlerApp} the handler app in question
+ *
+ * @returns {boolean} whether or not it's valid
+ */
+ isValidHandlerApp: function(aHandlerApp) {
+ if (!aHandlerApp)
+ return false;
+
+ if (aHandlerApp instanceof nsILocalHandlerApp)
+ return this._isValidHandlerExecutable(aHandlerApp.executable);
+
+ if (aHandlerApp instanceof nsIWebHandlerApp)
+ return aHandlerApp.uriTemplate;
+
+ if (aHandlerApp instanceof nsIWebContentHandlerInfo)
+ return aHandlerApp.uri;
+
+ return false;
+ },
+
+ _isValidHandlerExecutable: function(aExecutable) {
+ var file = Services.dirsvc.get("XREExeF",
+ Components.interfaces.nsILocalFile);
+ return aExecutable &&
+ aExecutable.exists() &&
+ aExecutable.isExecutable() &&
+ aExecutable.leafName != file.leafName;
+ },
+
+ /**
+ * Rebuild the actions menu for the selected entry. Gets called by
+ * the listcell constructor when an entry in the list gets selected.
+ * Note that this would not work from onselect on the listbox because
+ * the XBL needs to be applied _before_ calling this function!
+ */
+ rebuildActionsMenu: function() {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+ var cell =
+ document.getAnonymousElementByAttribute(typeItem, "anonid", "action-cell");
+ var menu =
+ document.getAnonymousElementByAttribute(cell, "anonid", "action-menu");
+ var menuPopup = menu.menupopup;
+
+ // Clear out existing items.
+ while (menuPopup.hasChildNodes())
+ menuPopup.lastChild.remove();
+
+ {
+ let askMenuItem = document.createElement("menuitem");
+ askMenuItem.setAttribute("class", "handler-action");
+ askMenuItem.setAttribute("value", nsIHandlerInfo.alwaysAsk);
+ let label;
+ if (isFeedType(handlerInfo.type))
+ label = this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ else
+ label = this._prefsBundle.getString("alwaysAsk");
+ askMenuItem.setAttribute("label", label);
+ askMenuItem.setAttribute("tooltiptext", label);
+ askMenuItem.setAttribute("appHandlerIcon", "ask");
+ menuPopup.appendChild(askMenuItem);
+ }
+
+ // Create a menu item for saving to disk.
+ // Note: this option isn't available to protocol types, since we don't know
+ // what it means to save a URL having a certain scheme to disk, nor is it
+ // available to feeds, since the feed code doesn't implement the capability.
+ if ((handlerInfo.wrappedHandlerInfo instanceof nsIMIMEInfo) &&
+ !isFeedType(handlerInfo.type)) {
+ let saveMenuItem = document.createElement("menuitem");
+ saveMenuItem.setAttribute("class", "handler-action");
+ saveMenuItem.setAttribute("value", nsIHandlerInfo.saveToDisk);
+ let label = this._prefsBundle.getString("saveFile");
+ saveMenuItem.setAttribute("label", label);
+ saveMenuItem.setAttribute("tooltiptext", label);
+ saveMenuItem.setAttribute("appHandlerIcon", "save");
+ menuPopup.appendChild(saveMenuItem);
+ }
+
+ // If this is the feed type, add a News & Blogs item.
+ if (isFeedType(handlerInfo.type)) {
+ let internalMenuItem = document.createElement("menuitem");
+ internalMenuItem.setAttribute("class", "handler-action");
+ internalMenuItem.setAttribute("value", nsIHandlerInfo.handleInternally);
+ let label = this._prefsBundle.getFormattedString("addNewsBlogsInApp",
+ [this._brandShortName]);
+ internalMenuItem.setAttribute("label", label);
+ internalMenuItem.setAttribute("tooltiptext", label);
+ internalMenuItem.setAttribute("appHandlerIcon", "feed");
+ menuPopup.appendChild(internalMenuItem);
+ }
+
+ // Add a separator to distinguish these items from the helper app items
+ // that follow them.
+ let menuSeparator = document.createElement("menuseparator");
+ menuPopup.appendChild(menuSeparator);
+
+ // Create a menu item for the OS default application, if any.
+ if (handlerInfo.hasDefaultHandler) {
+ let defaultMenuItem = document.createElement("menuitem");
+ defaultMenuItem.setAttribute("class", "handler-action");
+ defaultMenuItem.setAttribute("value", nsIHandlerInfo.useSystemDefault);
+ let label = this._prefsBundle.getFormattedString("useDefault",
+ [handlerInfo.defaultDescription]);
+ defaultMenuItem.setAttribute("label", label);
+ defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
+ let sysIcon = this._getIconURLForSystemDefault(handlerInfo);
+ if (sysIcon)
+ defaultMenuItem.setAttribute("image", sysIcon);
+ else
+ defaultMenuItem.setAttribute("appHandlerIcon", "app");
+
+ menuPopup.appendChild(defaultMenuItem);
+ }
+
+ // Create menu items for possible handlers.
+ let preferredApp = handlerInfo.preferredApplicationHandler;
+ let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate();
+ var possibleAppMenuItems = [];
+ while (possibleApps.hasMoreElements()) {
+ let possibleApp = possibleApps.getNext();
+ if (!this.isValidHandlerApp(possibleApp))
+ continue;
+
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("class", "handler-action");
+ menuItem.setAttribute("value", nsIHandlerInfo.useHelperApp);
+ let label;
+ if (possibleApp instanceof nsILocalHandlerApp)
+ label = getFileDisplayName(possibleApp.executable);
+ else
+ label = possibleApp.name;
+ label = this._prefsBundle.getFormattedString("useApp", [label]);
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ let sysIcon = this._getIconURLForHandlerApp(possibleApp);
+ if (sysIcon)
+ menuItem.setAttribute("image", sysIcon);
+ else
+ menuItem.setAttribute("appHandlerIcon", "app");
+
+ // Attach the handler app object to the menu item so we can use it
+ // to make changes to the datastore when the user selects the item.
+ menuItem.handlerApp = possibleApp;
+
+ menuPopup.appendChild(menuItem);
+ possibleAppMenuItems.push(menuItem);
+ }
+
+ // Create a menu item for the plugin.
+ if (handlerInfo.plugin) {
+ let pluginMenuItem = document.createElement("menuitem");
+ pluginMenuItem.setAttribute("class", "handler-action");
+ pluginMenuItem.setAttribute("value", kActionUsePlugin);
+ let label = this._prefsBundle.getFormattedString("usePluginIn",
+ [handlerInfo.plugin.name,
+ this._brandShortName]);
+ pluginMenuItem.setAttribute("label", label);
+ pluginMenuItem.setAttribute("tooltiptext", label);
+ pluginMenuItem.setAttribute("appHandlerIcon", "plugin");
+ menuPopup.appendChild(pluginMenuItem);
+ }
+
+ // Create a menu item for selecting a local application.
+#ifdef XP_WIN
+ // On Windows, selecting an application to open another application
+ // would be meaningless so we special case executables.
+ var executableType = mimeSvc.getTypeFromExtension("exe");
+ if (handlerInfo.type != executableType)
+#endif
+ {
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("class", "handler-action");
+ menuItem.setAttribute("value", kActionChooseApp);
+ let label = this._prefsBundle.getString("useOtherApp");
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Create a menu item for managing applications.
+ if (possibleAppMenuItems.length) {
+ let menuItem = document.createElement("menuseparator");
+ menuPopup.appendChild(menuItem);
+ menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("class", "handler-action");
+ menuItem.setAttribute("value", kActionManageApp);
+ menuItem.setAttribute("label", this._prefsBundle.getString("manageApp"));
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Select the item corresponding to the preferred action. If the always
+ // ask flag is set, it overrides the preferred action. Otherwise we pick
+ // the item identified by the preferred action (when the preferred action
+ // is to use a helper app, we have to pick the specific helper app item).
+ if (handlerInfo.alwaysAskBeforeHandling)
+ menu.value = nsIHandlerInfo.alwaysAsk;
+ else if (handlerInfo.preferredAction == nsIHandlerInfo.useHelperApp &&
+ preferredApp)
+ menu.selectedItem =
+ possibleAppMenuItems.filter(v => v.handlerApp.equals(preferredApp))[0];
+ else
+ menu.value = handlerInfo.preferredAction;
+ },
+
+
+ //**************************************************************************//
+ // Sorting & Filtering
+
+ _sortColumn: null,
+
+ /**
+ * Sort the list when the user clicks on a column header.
+ */
+ sort: function (event) {
+ var column = event.target;
+
+ // If the user clicked on a new sort column, remove the direction indicator
+ // from the old column.
+ if (this._sortColumn && this._sortColumn != column)
+ this._sortColumn.removeAttribute("sortDirection");
+
+ this._sortColumn = column;
+
+ // Set (or switch) the sort direction indicator.
+ if (column.getAttribute("sortDirection") == "ascending")
+ column.setAttribute("sortDirection", "descending");
+ else
+ column.setAttribute("sortDirection", "ascending");
+
+ this._sortVisibleTypes();
+ this._rebuildView();
+ },
+
+ /**
+ * Sort the list of visible types by the current sort column/direction.
+ */
+ _sortVisibleTypes: function() {
+ var t = this;
+
+ function sortByType(a, b) {
+ return t._describeType(a).toLowerCase()
+ .localeCompare(t._describeType(b).toLowerCase());
+ }
+
+ function sortByAction(a, b) {
+ return t._describePreferredAction(a).toLowerCase()
+ .localeCompare(t._describePreferredAction(b).toLowerCase());
+ }
+
+ switch (this._sortColumn.getAttribute("value")) {
+ case "type":
+ this._visibleTypes.sort(sortByType);
+ break;
+ case "action":
+ this._visibleTypes.sort(sortByAction);
+ break;
+ }
+
+ if (this._sortColumn.getAttribute("sortDirection") == "descending")
+ this._visibleTypes.reverse();
+ },
+
+
+ //**************************************************************************//
+ // Changes
+
+ onSelectAction: function(aActionItem) {
+ this._storingAction = true;
+
+ try {
+ this._storeAction(aActionItem);
+ }
+ finally {
+ this._storingAction = false;
+ }
+ },
+
+ _storeAction: function(aActionItem) {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ let action = parseInt(aActionItem.getAttribute("value"));
+
+ // Set the plugin state if we're enabling or disabling a plugin.
+ if (action == kActionUsePlugin)
+ handlerInfo.enablePluginType();
+ else if (handlerInfo.plugin && !handlerInfo.isDisabledPluginType)
+ handlerInfo.disablePluginType();
+
+ // Set the preferred application handler.
+ // We leave the existing preferred app in the list when we set
+ // the preferred action to something other than useHelperApp so that
+ // legacy datastores that don't have the preferred app in the list
+ // of possible apps still include the preferred app in the list of apps
+ // the user can choose to handle the type.
+ if (action == nsIHandlerInfo.useHelperApp)
+ handlerInfo.preferredApplicationHandler = aActionItem.handlerApp;
+
+ // Set the preferred action.
+ handlerInfo.preferredAction = action;
+
+ // Set the "always ask" flag.
+ handlerInfo.alwaysAskBeforeHandling = action == nsIHandlerInfo.alwaysAsk;
+
+ handlerInfo.store();
+
+ // Make sure the handler info object is flagged to indicate that there is
+ // now some user configuration for the type.
+ handlerInfo.handledOnlyByPlugin = false;
+
+ // Update the action label and image to reflect the new preferred action.
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ var sysIcon = this._getIconURLForPreferredAction(handlerInfo);
+ if (sysIcon)
+ typeItem.setAttribute("actionIcon", sysIcon);
+ else
+ typeItem.setAttribute("appHandlerIcon", "app");
+ }
+ },
+
+ manageApp: function() {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ document.documentElement.openSubDialog("chrome://communicator/content/pref/pref-applicationManager.xul",
+ "", handlerInfo);
+
+ // Rebuild the actions menu so that we revert to the previous selection,
+ // or "Always ask" if the previous default application has been removed
+ this.rebuildActionsMenu();
+
+ // update the listitem too. Will be visible when selecting another row
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ var sysIcon = this._getIconURLForPreferredAction(handlerInfo);
+ if (sysIcon)
+ typeItem.setAttribute("actionIcon", sysIcon);
+ else
+ typeItem.setAttribute("appHandlerIcon", "app");
+ }
+ },
+
+ chooseApp: function() {
+ var handlerApp;
+
+#ifdef XP_WIN
+ var params = {};
+ var handlerInfo = this._handledTypes[this._list.selectedItem.type];
+
+ if (isFeedType(handlerInfo.type)) {
+ // MIME info will be null, create a temp object.
+ params.mimeInfo = mimeSvc.getFromTypeAndExtension(handlerInfo.type,
+ handlerInfo.primaryExtension);
+ } else {
+ params.mimeInfo = handlerInfo.wrappedHandlerInfo;
+ }
+
+ params.title = this._prefsBundle.getString("fpTitleChooseApp");
+ params.description = handlerInfo.description;
+ params.filename = null;
+ params.handlerApp = null;
+
+ window.openDialog("chrome://global/content/appPicker.xul", null,
+ "chrome,modal,centerscreen,titlebar,dialog=yes",
+ params);
+
+ if (this.isValidHandlerApp(params.handlerApp)) {
+ handlerApp = params.handlerApp;
+
+ // Add the app to the type's list of possible handlers.
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+ }
+#else
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ var winTitle = this._prefsBundle.getString("fpTitleChooseApp");
+ fp.init(window, winTitle, nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterApps);
+
+ // Prompt the user to pick an app. If they pick one, and it's a valid
+ // selection, then add it to the list of possible handlers.
+ if (fp.show() == nsIFilePicker.returnOK && fp.file &&
+ this._isValidHandlerExecutable(fp.file)) {
+ handlerApp = Components.classes["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(nsILocalHandlerApp);
+ handlerApp.name = getFileDisplayName(fp.file);
+ handlerApp.executable = fp.file;
+
+ // Add the app to the type's list of possible handlers.
+ let handlerInfo = this._handledTypes[this._list.selectedItem.type];
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+ }
+#endif
+
+ // Rebuild the actions menu whether the user picked an app or canceled.
+ // If they picked an app, we want to add the app to the menu and select it.
+ // If they canceled, we want to go back to their previous selection.
+ this.rebuildActionsMenu();
+
+ // If the user picked a new app from the menu, select it.
+ if (handlerApp) {
+ let typeItem = this._list.selectedItem;
+ var actionsCell =
+ document.getAnonymousElementByAttribute(typeItem, "anonid", "action-cell");
+ var actionsMenu =
+ document.getAnonymousElementByAttribute(actionsCell, "anonid", "action-menu");
+ let menuItems = actionsMenu.menupopup.childNodes;
+ for (let i = 0; i < menuItems.length; i++) {
+ let menuItem = menuItems[i];
+ if (menuItem.handlerApp && menuItem.handlerApp.equals(handlerApp)) {
+ actionsMenu.selectedIndex = i;
+ this.onSelectAction(menuItem);
+ break;
+ }
+ }
+ }
+ },
+
+ _setIconClassForPreferredAction: function(aHandlerInfo, aElement) {
+ // If this returns true, the attribute that CSS sniffs for was set to something
+ // so you shouldn't manually set an icon URI.
+ // This removes the existing actionIcon attribute if any, even if returning false.
+ aElement.removeAttribute("actionIcon");
+
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ aElement.setAttribute("appHandlerIcon", "ask");
+ return true;
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case nsIHandlerInfo.saveToDisk:
+ aElement.setAttribute("appHandlerIcon", "save");
+ return true;
+
+ case nsIHandlerInfo.handleInternally:
+ if (isFeedType(aHandlerInfo.type)) {
+ aElement.setAttribute("appHandlerIcon", "feed");
+ return true;
+ }
+ break;
+
+ case kActionUsePlugin:
+ aElement.setAttribute("appHandlerIcon", "plugin");
+ return true;
+ }
+ aElement.removeAttribute("appHandlerIcon");
+ return false;
+ },
+
+ _getIconURLForPreferredAction: function(aHandlerInfo) {
+ switch (aHandlerInfo.preferredAction) {
+ case nsIHandlerInfo.useSystemDefault:
+ return this._getIconURLForSystemDefault(aHandlerInfo);
+
+ case nsIHandlerInfo.useHelperApp:
+ let preferredApp = aHandlerInfo.preferredApplicationHandler;
+ if (this.isValidHandlerApp(preferredApp))
+ return this._getIconURLForHandlerApp(preferredApp);
+ break;
+ }
+ // This should never happen, but if preferredAction is set to some weird
+ // value, then fall back to the generic application icon.
+ return null;
+ },
+
+ _getIconURLForHandlerApp: function(aHandlerApp) {
+ if (aHandlerApp instanceof nsILocalHandlerApp)
+ return this._getIconURLForFile(aHandlerApp.executable);
+
+ if (Services.prefs.getBoolPref("browser.chrome.favicons")) { // q.v. Bug 514671
+ if (aHandlerApp instanceof nsIWebHandlerApp)
+ return this._getIconURLForWebApp(aHandlerApp.uriTemplate);
+
+ if (aHandlerApp instanceof nsIWebContentHandlerInfo)
+ return this._getIconURLForWebApp(aHandlerApp.uri)
+ }
+
+ // We know nothing about other kinds of handler apps.
+ return "";
+ },
+
+ _getIconURLForFile: function(aFile) {
+ var fph = Services.io.getProtocolHandler("file")
+ .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+ var urlSpec = fph.getURLSpecFromFile(aFile);
+
+ return "moz-icon://" + urlSpec + "?size=16";
+ },
+
+ _getIconURLForWebApp: function(aWebAppURITemplate) {
+ var uri = Services.io.newURI(aWebAppURITemplate, null, null);
+
+ // Unfortunately we need to use favicon.ico here, but we don't know
+ // about any other possibility to retrieve an icon for the web app/site
+ // without loading a specific full URL and parsing it for a possible
+ // shortcut icon.
+
+ return /^https?/.test(uri.scheme) ? uri.resolve("/favicon.ico") : "";
+ },
+
+ _getIconURLForSystemDefault: function(aHandlerInfo) {
+ // Handler info objects for MIME types on some OSes implement a property bag
+ // interface from which we can get an icon for the default app, so if we're
+ // dealing with a MIME type on one of those OSes, then try to get the icon.
+ if ("wrappedHandlerInfo" in aHandlerInfo) {
+ let wrappedHandlerInfo = aHandlerInfo.wrappedHandlerInfo;
+
+ if (wrappedHandlerInfo instanceof nsIMIMEInfo &&
+ wrappedHandlerInfo instanceof nsIPropertyBag) {
+ try {
+ let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL");
+ if (url)
+ return url + "?size=16";
+ }
+ catch(ex) {}
+ }
+ }
+
+ // If this isn't a MIME type object on an OS that supports retrieving
+ // the icon, or if we couldn't retrieve the icon for some other reason,
+ // then use a generic icon.
+ return null;
+ }
+
+};
diff --git a/xpfe/components/preferences/content/pref-applications.xul b/xpfe/components/preferences/content/pref-applications.xul
new file mode 100644
index 000000000..73d583801
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-applications.xul
@@ -0,0 +1,100 @@
+<?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 overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
+ <!ENTITY % prefApplicationsDTD SYSTEM "chrome://communicator/locale/pref/pref-applications.dtd"> %prefApplicationsDTD;
+]>
+
+<overlay id="ApplicationsPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="applications_pane"
+ label="&pref.applications.title;"
+ script="chrome://communicator/content/pref/pref-applications.js">
+
+ <preferences id="feedsPreferences">
+ <preference id="browser.feeds.handler"
+ name="browser.feeds.handler"
+ type="string"/>
+ <preference id="browser.feeds.handler.default"
+ name="browser.feeds.handler.default"
+ type="string"/>
+ <preference id="browser.feeds.handlers.application"
+ name="browser.feeds.handlers.application"
+ type="file"/>
+ <preference id="browser.feeds.handlers.webservice"
+ name="browser.feeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.videoFeeds.handler"
+ name="browser.videoFeeds.handler"
+ type="string"/>
+ <preference id="browser.videoFeeds.handler.default"
+ name="browser.videoFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.videoFeeds.handlers.application"
+ name="browser.videoFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.videoFeeds.handlers.webservice"
+ name="browser.videoFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.audioFeeds.handler"
+ name="browser.audioFeeds.handler"
+ type="string"/>
+ <preference id="browser.audioFeeds.handler.default"
+ name="browser.audioFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.audioFeeds.handlers.application"
+ name="browser.audioFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.audioFeeds.handlers.webservice"
+ name="browser.audioFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="pref.downloads.disable_button.edit_actions"
+ name="pref.downloads.disable_button.edit_actions"
+ type="bool"/>
+ </preferences>
+
+ <stringbundleset id="appBundleset">
+ <stringbundle id="bundleBrand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundlePrefApplications"
+ src="chrome://communicator/locale/pref/pref-applications.properties"/>
+ </stringbundleset>
+
+ <hbox align="center">
+ <textbox id="filter"
+ flex="1"
+ type="search"
+ placeholder="&search.placeholder;"
+ clickSelectsAll="true"
+ aria-controls="handlersView"
+ oncommand="gApplicationsPane._rebuildView();"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <listbox id="handlersView" persist="lastSelectedType" flex="1"
+ preference="pref.downloads.disable_button.edit_actions">
+ <listcols>
+ <listcol width="1" flex="1"/>
+ <listcol width="1" flex="1"/>
+ </listcols>
+ <listhead>
+ <listheader id="typeColumn" label="&typeColumn.label;" value="type"
+ accesskey="&typeColumn.accesskey;" persist="sortDirection"
+ onclick="gApplicationsPane.sort(event);"
+ sortDirection="ascending"/>
+ <listheader id="actionColumn" label="&actionColumn2.label;" value="action"
+ accesskey="&actionColumn2.accesskey;" persist="sortDirection"
+ onclick="gApplicationsPane.sort(event);"/>
+ </listhead>
+ </listbox>
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-certs.js b/xpfe/components/preferences/content/pref-certs.js
new file mode 100644
index 000000000..a630f0aa9
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-certs.js
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+function Startup()
+{
+ var securityOCSPEnabled = document.getElementById("security.OCSP.enabled");
+ DoEnabling(securityOCSPEnabled.value);
+}
+
+function DoEnabling(aOCSPPrefValue)
+{
+ EnableElementById("requireWorkingOCSP", aOCSPPrefValue != 0, false);
+}
+
+function OpenCertManager()
+{
+ document.documentElement
+ .openWindow("mozilla:certmanager",
+ "chrome://pippki/content/certManager.xul",
+ "", null);
+}
+
+function OpenDeviceManager()
+{
+ document.documentElement
+ .openWindow("mozilla:devicemanager",
+ "chrome://pippki/content/device_manager.xul",
+ "", null);
+}
diff --git a/xpfe/components/preferences/content/pref-certs.xul b/xpfe/components/preferences/content/pref-certs.xul
new file mode 100644
index 000000000..76b507d4f
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-certs.xul
@@ -0,0 +1,98 @@
+<?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://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % prefCertsDTD SYSTEM "chrome://pippki/locale/pref-certs.dtd">
+ %prefCertsDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="certs_pane"
+ label="&pref.certs.title;"
+ script="chrome://pippki/content/pref-certs.js">
+ <preferences id="cert_preferences">
+ <preference id="security.default_personal_cert"
+ name="security.default_personal_cert"
+ type="string"/>
+ <preference id="security.disable_button.openCertManager"
+ name="security.disable_button.openCertManager"
+ type="bool"/>
+ <preference id="security.disable_button.openDeviceManager"
+ name="security.disable_button.openDeviceManager"
+ type="bool"/>
+ <preference id="security.OCSP.enabled"
+ name="security.OCSP.enabled"
+ type="int"
+ onchange="DoEnabling(this.value);"/>
+ <preference id="security.OCSP.require"
+ name="security.OCSP.require"
+ type="bool"/>
+ </preferences>
+
+
+ <groupbox align="start">
+ <caption label="&SSLClientAuthMethod.caption;"/>
+ <description>&certselect.description;</description>
+ <radiogroup id="certSelection"
+ orient="horizontal"
+ preference="security.default_personal_cert"
+ aria-labelledby="CertGroupCaption CertSelectionDesc">
+ <radio value="Select Automatically"
+ label="&certselect.auto;"
+ accesskey="&certselect.auto.accesskey;"/>
+ <radio value="Ask Every Time"
+ label="&certselect.ask;"
+ accesskey="&certselect.ask.accesskey;"/>
+ </radiogroup>
+ </groupbox>
+
+ <!-- Certificate manager -->
+ <groupbox>
+ <caption label="&managecerts.caption;"/>
+ <description>&managecerts.text;</description>
+ <hbox align="center">
+ <button label="&managecerts.button;"
+ oncommand="OpenCertManager();"
+ id="openCertManagerButton"
+ accesskey="&managecerts.accesskey;"
+ preference="security.disable_button.openCertManager"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Device manager -->
+ <groupbox>
+ <caption label="&managedevices.caption;"/>
+ <description>&managedevices.text;</description>
+ <hbox align="center">
+ <button label="&managedevices.button;"
+ oncommand="OpenDeviceManager();"
+ id="openDeviceManagerButton"
+ accesskey="&managedevices.accesskey;"
+ preference="security.disable_button.openDeviceManager"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Validation -->
+ <groupbox align="start">
+ <caption label="&validation.ocsp.caption;"/>
+ <checkbox id="enableOCSPBox"
+ label="&enableOCSP.label;"
+ accesskey="&enableOCSP.accesskey;"
+ onsynctopreference="return +this.checked;"
+ preference="security.OCSP.enabled"/>
+ <separator class="thin"/>
+ <checkbox id="requireWorkingOCSP"
+ label="&validation.requireOCSP.description;"
+ accesskey="&validation.requireOCSP.accesskey;"
+ preference="security.OCSP.require"/>
+ </groupbox>
+
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-download.js b/xpfe/components/preferences/content/pref-download.js
new file mode 100644
index 000000000..d09b8c4c3
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-download.js
@@ -0,0 +1,157 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 kDesktop = 0;
+const kDownloads = 1;
+const kUserDir = 2;
+var gFPHandler;
+var gSoundUrlPref;
+
+function Startup()
+{
+ // Define globals
+ gFPHandler = Services.io.getProtocolHandler("file")
+ .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+ gSoundUrlPref = document.getElementById("browser.download.finished_sound_url");
+ SetSoundEnabled(document.getElementById("browser.download.finished_download_sound").value);
+
+ // if we don't have the alert service, hide the pref UI for using alerts to
+ // notify on download completion
+ // see bug #158711
+ var downloadDoneNotificationAlertUI = document.getElementById("finishedNotificationAlert");
+ downloadDoneNotificationAlertUI.hidden = !("@mozilla.org/alerts-service;1" in Components.classes);
+}
+
+/**
+ * Enables/disables the folder field and Browse button based on whether a
+ * default download directory is being used.
+ */
+function ReadUseDownloadDir()
+{
+ var downloadFolder = document.getElementById("downloadFolder");
+ var chooseFolder = document.getElementById("chooseFolder");
+ var preference = document.getElementById("browser.download.useDownloadDir");
+ downloadFolder.disabled = !preference.value;
+ chooseFolder.disabled = !preference.value;
+}
+
+/**
+ * Displays a file picker in which the user can choose the location where
+ * downloads are automatically saved, updating preferences and UI in
+ * response to the choice, if one is made.
+ */
+function ChooseFolder()
+{
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ var prefutilitiesBundle = document.getElementById("bundle_prefutilities");
+ var title = prefutilitiesBundle.getString("downloadfolder");
+ fp.init(window, title, nsIFilePicker.modeGetFolder);
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ var folderListPref = document.getElementById("browser.download.folderList");
+ fp.displayDirectory = IndexToFolder(folderListPref.value); // file
+
+ if (fp.show() == nsIFilePicker.returnOK) {
+ var currentDirPref = document.getElementById("browser.download.dir");
+ currentDirPref.value = fp.file;
+ folderListPref.value = FolderToIndex(fp.file);
+ // Note, the real prefs will not be updated yet, so dnld manager's
+ // userDownloadsDirectory may not return the right folder after
+ // this code executes. displayDownloadDirPref will be called on
+ // the assignment above to update the UI.
+ }
+}
+
+/**
+ * Initializes the download folder display settings based on the user's
+ * preferences.
+ */
+function DisplayDownloadDirPref()
+{
+ var folderListPref = document.getElementById("browser.download.folderList");
+ var currentDirPref = IndexToFolder(folderListPref.value); // file
+ var prefutilitiesBundle = document.getElementById("bundle_prefutilities");
+ var iconUrlSpec = gFPHandler.getURLSpecFromFile(currentDirPref);
+ var downloadFolder = document.getElementById("downloadFolder");
+ downloadFolder.image = "moz-icon://" + iconUrlSpec + "?size=16";
+
+ // Display a 'pretty' label or the path in the UI.
+ switch (FolderToIndex(currentDirPref)) {
+ case kDesktop:
+ downloadFolder.label = prefutilitiesBundle.getString("desktopFolderName");
+ break;
+ case kDownloads:
+ downloadFolder.label = prefutilitiesBundle.getString("downloadsFolderName");
+ break;
+ default:
+ downloadFolder.label = currentDirPref ? currentDirPref.path : "";
+ break;
+ }
+}
+
+/**
+ * Returns the Downloads folder as determined by the XPCOM directory service
+ * via the download manager's attribute defaultDownloadsDirectory.
+ */
+function GetDownloadsFolder()
+{
+ return Components.classes["@mozilla.org/download-manager;1"]
+ .getService(Components.interfaces.nsIDownloadManager)
+ .defaultDownloadsDirectory;
+}
+
+/**
+ * Determines the type of the given folder.
+ *
+ * @param aFolder
+ * the folder whose type is to be determined
+ * @returns integer
+ * kDesktop if aFolder is the Desktop or is unspecified,
+ * kDownloads if aFolder is the Downloads folder,
+ * kUserDir otherwise
+ */
+function FolderToIndex(aFolder)
+{
+ if (!aFolder || aFolder.equals(GetDesktopFolder()))
+ return kDesktop;
+ if (aFolder.equals(GetDownloadsFolder()))
+ return kDownloads;
+ return kUserDir;
+}
+
+/**
+ * Converts an integer into the corresponding folder.
+ *
+ * @param aIndex
+ * an integer
+ * @returns the Desktop folder if aIndex == kDesktop,
+ * the Downloads folder if aIndex == kDownloads,
+ * the folder stored in browser.download.dir
+ */
+function IndexToFolder(aIndex)
+{
+ var folder;
+ switch (aIndex) {
+ default:
+ folder = document.getElementById("browser.download.dir").value;
+ if (folder && folder.exists())
+ return folder;
+ case kDownloads:
+ folder = GetDownloadsFolder();
+ if (folder && folder.exists())
+ return folder;
+ case kDesktop:
+ return GetDesktopFolder();
+ }
+}
+
+function SetSoundEnabled(aEnable)
+{
+ EnableElementById("downloadSndURL", aEnable, false);
+ document.getElementById("downloadSndPlay").disabled = !aEnable;
+}
diff --git a/xpfe/components/preferences/content/pref-download.xul b/xpfe/components/preferences/content/pref-download.xul
new file mode 100644
index 000000000..246d0d72a
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-download.xul
@@ -0,0 +1,147 @@
+<?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 overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % prefDownloadDTD SYSTEM "chrome://communicator/locale/pref/pref-download.dtd">
+%prefDownloadDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="download_pane"
+ label="&pref.download.title;"
+ script="chrome://communicator/content/pref/pref-download.js">
+
+ <preferences>
+ <preference id="browser.download.manager.behavior"
+ name="browser.download.manager.behavior"
+ type="int"/>
+ <preference id="browser.download.manager.focusWhenStarting"
+ name="browser.download.manager.focusWhenStarting"
+ type="bool" inverted="true"/>
+ <preference id="browser.download.useDownloadDir"
+ name="browser.download.useDownloadDir"
+ type="bool"/>
+ <preference id="browser.download.dir"
+ name="browser.download.dir"
+ type="file"/>
+ <preference id="browser.download.folderList"
+ name="browser.download.folderList"
+ type="int"
+ onchange="DisplayDownloadDirPref();"/>
+ <preference id="browser.download.manager.retention"
+ name="browser.download.manager.retention"
+ type="int"/>
+ <preference id="browser.download.finished_download_sound"
+ name="browser.download.finished_download_sound"
+ type="bool"
+ onchange="SetSoundEnabled(this.value);"/>
+ <preference id="browser.download.manager.showAlertOnComplete"
+ name="browser.download.manager.showAlertOnComplete"
+ type="bool"/>
+ <preference id="browser.download.finished_sound_url"
+ name="browser.download.finished_sound_url"
+ type="string"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&downloadBehavior.label;"/>
+ <radiogroup id="downloadBehavior"
+ preference="browser.download.manager.behavior">
+ <radio value="2"
+ label="&doNothing.label;"
+ accesskey="&doNothing.accesskey;"/>
+ <radio value="1"
+ label="&openProgressDialog.label;"
+ accesskey="&openProgressDialog.accesskey;"/>
+ <radio value="0"
+ label="&openDM.label;"
+ accesskey="&openDM.accesskey;"/>
+ </radiogroup>
+ <checkbox id="focusWhenStarting"
+ class="indent"
+ preference="browser.download.manager.focusWhenStarting"
+ label="&flashWhenOpen.label;"
+ accesskey="&flashWhenOpen.accesskey;"/>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&downloadLocation.label;"/>
+ <radiogroup id="saveWhere"
+ preference="browser.download.useDownloadDir"
+ onsyncfrompreference="return document.getElementById('download_pane').ReadUseDownloadDir();">
+ <hbox id="saveToRow">
+ <radio id="saveTo" value="true"
+ label="&saveTo.label;"
+ accesskey="&saveTo.accesskey;"
+ aria-labelledby="saveTo downloadFolder"/>
+ <filefield id="downloadFolder" flex="1"
+ preference="browser.download.dir"
+ preference-editable="true"
+ aria-labelledby="saveTo"
+ onsyncfrompreference="document.getElementById('download_pane').DisplayDownloadDirPref();"/>
+ <button id="chooseFolder" oncommand="ChooseFolder();"
+ label="&chooseDownloadFolder.label;"
+ accesskey="&chooseDownloadFolder.accesskey;"/>
+ </hbox>
+ <radio id="alwaysAsk" value="false"
+ label="&alwaysAsk.label;"
+ accesskey="&alwaysAsk.accesskey;"/>
+ </radiogroup>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&downloadHistory.label;"/>
+ <hbox align="center">
+ <label value="&removeEntries.label;"
+ accesskey="&removeEntries.accesskey;"
+ control="downloadHistory"/>
+ <menulist id="downloadHistory"
+ preference="browser.download.manager.retention">
+ <menupopup>
+ <menuitem value="0" label="&whenCompleted.label;"/>
+ <menuitem value="1" label="&whenQuittingApp.label;"/>
+ <menuitem value="2" label="&neverRemove.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&finishedBehavior.label;"/>
+ <hbox align="center">
+ <checkbox id="finishedNotificationSound"
+ label="&playSound.label;"
+ preference="browser.download.finished_download_sound"
+ accesskey="&playSound.accesskey;"/>
+ <checkbox id="finishedNotificationAlert"
+ label="&showAlert.label;"
+ preference="browser.download.manager.showAlertOnComplete"
+ accesskey="&showAlert.accesskey;"/>
+ </hbox>
+
+ <hbox align="center" class="indent">
+ <filefield id="downloadSndURL"
+ flex="1"
+ preference="browser.download.finished_sound_url"
+ preference-editable="true"
+ onsyncfrompreference="return WriteSoundField(this, document.getElementById('download_pane').gSoundUrlPref.value);"/>
+ <hbox align="center">
+ <button id="downloadSndBrowse"
+ label="&browse.label;"
+ accesskey="&browse.accesskey;"
+ oncommand="SelectSound(gSoundUrlPref);">
+ <observes element="downloadSndURL" attribute="disabled"/>
+ </button>
+ <button id="downloadSndPlay"
+ label="&playButton.label;"
+ accesskey="&playButton.accesskey;"
+ oncommand="PlaySound(gSoundUrlPref.value, false);"/>
+ </hbox>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-http.js b/xpfe/components/preferences/content/pref-http.js
new file mode 100644
index 000000000..d65449038
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-http.js
@@ -0,0 +1,29 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 gLightningUAold;
+
+function Startup()
+{
+ CheckPipelining();
+ CheckPipeliningProxy();
+}
+
+function CheckPipelining()
+{
+ var prefHTTPVersion = document.getElementById("network.http.version");
+
+ var enabled = prefHTTPVersion.value == "1.1";
+ EnableElementById("enablePipelining", enabled, false);
+}
+
+function CheckPipeliningProxy()
+{
+ var prefHTTPVersion = document.getElementById("network.http.proxy.version");
+
+ var enabled = prefHTTPVersion.value == "1.1";
+ EnableElementById("enablePipeliningProxy", enabled, false);
+}
+
diff --git a/xpfe/components/preferences/content/pref-http.xul b/xpfe/components/preferences/content/pref-http.xul
new file mode 100644
index 000000000..5b4f869b5
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-http.xul
@@ -0,0 +1,105 @@
+<?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 overlay SYSTEM "chrome://communicator/locale/pref/pref-http.dtd">
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="http_pane"
+ label="&pref.http.title;"
+ script="chrome://communicator/content/pref/pref-http.js">
+
+ <preferences>
+ <preference id="network.http.version"
+ name="network.http.version"
+ type="string"
+ onchange="CheckPipelining();"/>
+ <preference id="network.http.pipelining"
+ name="network.http.pipelining"
+ type="bool"/>
+ <preference id="network.http.proxy.version"
+ name="network.http.proxy.version"
+ type="string"
+ onchange="CheckPipeliningProxy();"/>
+ <preference id="network.http.proxy.pipelining"
+ name="network.http.proxy.pipelining"
+ type="bool"/>
+ <preference id="general.useragent.compatMode.gecko"
+ name="general.useragent.compatMode.gecko"
+ type="bool"/>
+ <preference id="general.useragent.compatMode.firefox"
+ name="general.useragent.compatMode.firefox"
+ type="bool"/>
+ <preference id="calendar.useragent.extra"
+ name="calendar.useragent.extra"
+ type="string"/>
+ </preferences>
+
+ <description>&prefPara;</description>
+
+ <hbox align="start">
+ <groupbox flex="1">
+ <caption label="&prefDirect.label;"/>
+ <vbox class="indent" align="start">
+ <radiogroup id="httpVersion"
+ preference="network.http.version">
+ <radio value="1.0"
+ label="&prefEnableHTTP10.label;"
+ accesskey="&prefEnableHTTP10.accesskey;"/>
+ <radio value="1.1"
+ label="&prefEnableHTTP11.label;"
+ accesskey="&prefEnableHTTP11.accesskey;"/>
+ </radiogroup>
+
+ <separator class="thin"/>
+
+ <checkbox id="enablePipelining"
+ label="&prefEnablePipelining.label;"
+ accesskey="&prefEnablePipelining.accesskey;"
+ preference="network.http.pipelining"/>
+ </vbox>
+ </groupbox>
+
+ <groupbox flex="1">
+ <caption label="&prefProxy.label;"/>
+ <vbox class="indent" align="start">
+ <radiogroup id="httpVersionProxy"
+ preference="network.http.proxy.version">
+ <radio value="1.0"
+ label="&prefEnableHTTP10.label;"
+ accesskey="&prefEnableHTTP10Proxy.accesskey;"/>
+ <radio value="1.1"
+ label="&prefEnableHTTP11.label;"
+ accesskey="&prefEnableHTTP11Proxy.accesskey;"/>
+ </radiogroup>
+
+ <separator class="thin"/>
+
+ <checkbox id="enablePipeliningProxy"
+ label="&prefEnablePipelining.label;"
+ accesskey="&prefEnablePipeliningProxy.accesskey;"
+ preference="network.http.proxy.pipelining"/>
+ </vbox>
+ </groupbox>
+ </hbox>
+
+ <description>&prefPipeWarning;</description>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&prefUseragent.label;"/>
+ <checkbox id="uaGeckoCompat"
+ label="&prefGeckoCompat.label;"
+ accesskey="&prefGeckoCompat.accesskey;"
+ preference="general.useragent.compatMode.gecko"/>
+ <checkbox id="uaFirefoxCompat"
+ label="&prefFirefoxCompat.label;"
+ accesskey="&prefFirefoxCompat.accesskey;"
+ preference="general.useragent.compatMode.firefox"/>
+ </groupbox>
+
+ <description>&prefCompatWarning;</description>
+ </prefpane>
+
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-masterpass.js b/xpfe/components/preferences/content/pref-masterpass.js
new file mode 100644
index 000000000..921bf16fb
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-masterpass.js
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 nsIPK11Token = Components.interfaces.nsIPK11Token;
+var gInternalToken;
+
+function Startup() {
+ var tokendb = Components.classes["@mozilla.org/security/pk11tokendb;1"]
+ .getService(Components.interfaces.nsIPK11TokenDB);
+ gInternalToken = tokendb.getInternalKeyToken();
+}
+
+function ReadAskForPassword() {
+ var value;
+ switch (gInternalToken.getAskPasswordTimes()) {
+ case nsIPK11Token.ASK_FIRST_TIME:
+ value = 0;
+ break;
+ case nsIPK11Token.ASK_EVERY_TIME:
+ value = 1;
+ break;
+ case nsIPK11Token.ASK_EXPIRE_TIME:
+ value = 2;
+ break;
+ }
+
+ EnableLifetimeTextbox(value);
+ return value;
+}
+
+function WriteAskForPassword(field) {
+ var askTimes;
+ switch (field.value) {
+ case "0":
+ askTimes = nsIPK11Token.ASK_FIRST_TIME;
+ break;
+ case "1":
+ askTimes = nsIPK11Token.ASK_EVERY_TIME;
+ break;
+ case "2":
+ askTimes = nsIPK11Token.ASK_EXPIRE_TIME;
+ break;
+ }
+ var timeout = gInternalToken.getAskPasswordTimeout();
+ gInternalToken.setAskPasswordDefaults(askTimes, timeout);
+
+ return field.value;
+}
+
+function ReadPasswordLifetime() {
+ return gInternalToken.getAskPasswordTimeout();
+}
+
+function WritePasswordLifetime(field) {
+ var askTimes = gInternalToken.getAskPasswordTimes();
+ gInternalToken.setAskPasswordDefaults(askTimes, field.value);
+ return field.value;
+}
+
+function EnableLifetimeTextbox(aPrefValue) {
+ EnableElementById("passwordTimeout", aPrefValue == 2, false);
+}
+
+function ChangePW()
+{
+ var p = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
+ .createInstance(Components.interfaces.nsIDialogParamBlock);
+ p.SetString(1, "");
+ window.openDialog("chrome://pippki/content/changepassword.xul", "",
+ "chrome,centerscreen,modal", p);
+}
+
+function ResetPW()
+{
+ var p = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
+ .createInstance(Components.interfaces.nsIDialogParamBlock);
+ p.SetString(1, gInternalToken.tokenName);
+ window.openDialog("chrome://pippki/content/resetpassword.xul", "",
+ "chrome,centerscreen,modal", p);
+}
diff --git a/xpfe/components/preferences/content/pref-masterpass.xul b/xpfe/components/preferences/content/pref-masterpass.xul
new file mode 100644
index 000000000..18e07c115
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-masterpass.xul
@@ -0,0 +1,122 @@
+<?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://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % prefPass SYSTEM "chrome://communicator/locale/pref/pref-masterpass.dtd">
+ %brandDTD;
+ %prefPass;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="masterpass_pane"
+ label="&pref.masterpass.title;"
+ script="chrome://communicator/content/pref/pref-masterpass.js">
+
+ <preferences id="masterpass_preferences">
+ <preference id="signon.rememberSignons"
+ name="signon.rememberSignons"
+ type="bool"/>
+ <preference id="pref.advanced.password.disable_button.view_stored_password"
+ name="pref.advanced.password.disable_button.view_stored_password"
+ type="bool"/>
+ <preference id="security.disable_button.changePassword"
+ name="security.disable_button.changePassword"
+ type="bool"/>
+ <preference id="security.ask_for_password"
+ name="security.ask_for_password"
+ type="int"
+ onchange="EnableLifetimeTextbox(this.value);"/>
+ <preference id="security.password_lifetime"
+ name="security.password_lifetime"
+ type="int"/>
+ <preference id="security.disable_button.resetPassword"
+ name="security.disable_button.resetPassword"
+ type="bool"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&signonHeader.caption;"/>
+ <description>&signonDescription.label;</description>
+ <hbox>
+ <checkbox id="signonRememberSignons"
+ label="&signonEnabled.label;"
+ accesskey="&signonEnabled.accesskey;"
+ preference="signon.rememberSignons"/>
+ </hbox>
+ <hbox pack="end">
+ <button id="viewStoredPassword"
+ label="&viewSignons.label;"
+ accesskey="&viewSignons.accesskey;"
+ oncommand="toPasswordManager();"
+ preference="pref.advanced.password.disable_button.view_stored_password"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Change Password -->
+ <groupbox>
+ <caption label="&changepassword.caption;"/>
+ <description>&changepassword.text;</description>
+ <hbox>
+ <button label="&changepassword.button;"
+ oncommand="ChangePW();"
+ id="changePasswordButton"
+ accesskey="&changepassword.accesskey;"
+ preference="security.disable_button.changePassword"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Password Prefs -->
+ <groupbox>
+ <caption label="&managepassword.caption;"/>
+ <description>&managepassword.text;</description>
+ <radiogroup id="passwordAskTimes"
+ preference="security.ask_for_password"
+ onsyncfrompreference="return document.getElementById('masterpass_pane').ReadAskForPassword();"
+ onsynctopreference="return document.getElementById('masterpass_pane').WriteAskForPassword(this);">
+
+ <!-- note that these values are different than what NSS uses, which
+ are (0, -1, 1) respectively -->
+ <radio value="0"
+ label="&managepassword.askfirsttime;"
+ accesskey="&managepassword.askfirsttime.accesskey;"/>
+ <radio value="1"
+ label="&managepassword.askeverytime;"
+ accesskey="&managepassword.askeverytime.accesskey;"/>
+ <hbox align="center">
+ <radio id="askTimeout"
+ value="2"
+ label="&managepassword.asktimeout;"
+ accesskey="&managepassword.asktimeout.accesskey;"/>
+ <textbox id="passwordTimeout"
+ size="4"
+ type="number"
+ aria-labelledby="askTimeout passwordTimeout passwordTimeoutAfter"
+ preference="security.password_lifetime"
+ onsyncfrompreference="return document.getElementById('masterpass_pane').ReadPasswordLifetime();"
+ onsynctopreference="return document.getElementById('masterpass_pane').WritePasswordLifetime(this);"/>
+ <label id="passwordTimeoutAfter"
+ value="&managepassword.timeout.unit;"/>
+ </hbox>
+ </radiogroup>
+ </groupbox>
+
+ <!-- Reset Password -->
+ <groupbox>
+ <caption label="&resetpassword.caption;"/>
+ <description>&resetpassword.text;</description>
+ <hbox>
+ <button label="&resetpassword.button;"
+ oncommand="ResetPW();"
+ id="resetPasswordButton"
+ accesskey="&resetpassword.accesskey;"
+ preference="security.disable_button.resetPassword"/>
+ </hbox>
+ </groupbox>
+
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-proxies-advanced.xul b/xpfe/components/preferences/content/pref-proxies-advanced.xul
new file mode 100644
index 000000000..f042ffe4d
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-proxies-advanced.xul
@@ -0,0 +1,196 @@
+<?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://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE prefwindow SYSTEM "chrome://communicator/locale/pref/pref-proxies-advanced.dtd" >
+
+<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="advancedProxyPreferences"
+ type="child"
+ onload="AdvancedInit();"
+ title="&pref.proxies.advanced.title;"
+ persist="screenX screenY">
+
+ <script type="application/javascript"
+ src="chrome://communicator/content/pref/pref-proxies.js"/>
+ <script type="application/javascript"
+ src="chrome://communicator/content/pref/preferences.js"/>
+
+ <prefpane helpTopic="nav-prefs-advanced-proxy-advanced"
+ helpURI="chrome://communicator/locale/help/suitehelp.rdf">
+ <preferences>
+ <preference id="network.proxy.http"
+ name="network.proxy.http"
+ type="string"
+ onchange="DoProxyHostCopy(this.value);"/>
+ <preference id="network.proxy.http_port"
+ name="network.proxy.http_port"
+ type="int"
+ onchange="DoProxyPortCopy(this.value);"/>
+ <preference id="network.proxy.ssl"
+ name="network.proxy.ssl"
+ type="string"/>
+ <preference id="network.proxy.ssl_port"
+ name="network.proxy.ssl_port"
+ type="int"/>
+ <preference id="network.proxy.ftp"
+ name="network.proxy.ftp"
+ type="string"/>
+ <preference id="network.proxy.ftp_port"
+ name="network.proxy.ftp_port"
+ type="int"/>
+ <preference id="network.proxy.share_proxy_settings"
+ name="network.proxy.share_proxy_settings"
+ type="bool"
+ onchange="DoProxyCopy(this.value);"/>
+ <preference id="network.proxy.socks"
+ name="network.proxy.socks"
+ type="string"/>
+ <preference id="network.proxy.socks_port"
+ name="network.proxy.socks_port"
+ type="int"/>
+ <preference id="network.proxy.socks_version"
+ name="network.proxy.socks_version"
+ type="int"/>
+ <preference id="network.proxy.socks_remote_dns"
+ name="network.proxy.socks_remote_dns"
+ type="bool"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&protocols.caption;"/>
+ <description style="width: 1px;">&protocols.description;</description>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ <hbox align="center" pack="end">
+ <label value="&http.label;"
+ accesskey="&http.accesskey;"
+ control="networkProxyHTTP"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxyHTTP"
+ preference="network.proxy.http"
+ flex="1"
+ class="uri-element"/>
+ <label value="&port.label;"
+ accesskey="&HTTPPort.accesskey;"
+ control="networkProxyHTTP_Port"/>
+ <textbox id="networkProxyHTTP_Port"
+ preference="network.proxy.http_port"
+ type="number"
+ max="65535"
+ size="5"/>
+ </hbox>
+ </row>
+
+ <row>
+ <spacer/>
+ <hbox>
+ <checkbox id="networkProxyShareSettings"
+ label="&reuseProxy.label;"
+ accesskey="&reuseProxy.accesskey;"
+ preference="network.proxy.share_proxy_settings"/>
+ </hbox>
+ </row>
+
+ <row>
+ <hbox align="center" pack="end">
+ <label value="&ssl.label;"
+ accesskey="&ssl.accesskey;"
+ control="networkProxySSL"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxySSL"
+ preference="network.proxy.ssl"
+ flex="1"
+ class="uri-element"/>
+ <label value="&port.label;"
+ accesskey="&SSLPort.accesskey;"
+ control="networkProxySSL_Port"/>
+ <textbox id="networkProxySSL_Port"
+ preference="network.proxy.ssl_port"
+ type="number"
+ max="65535"
+ size="5"/>
+ </hbox>
+ </row>
+
+ <row>
+ <hbox align="center" pack="end">
+ <label value="&ftp.label;" accesskey="&ftp.accesskey;"
+ control="networkProxyFTP"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxyFTP"
+ preference="network.proxy.ftp"
+ flex="1"
+ class="uri-element"/>
+ <label value="&port.label;"
+ accesskey="&FTPPort.accesskey;"
+ control="networkProxyFTP_Port"/>
+ <textbox id="networkProxyFTP_Port"
+ preference="network.proxy.ftp_port"
+ type="number"
+ max="65535"
+ size="5"/>
+ </hbox>
+ </row>
+
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&socks.caption;"/>
+ <description style="width: 1px;">&socks.description;</description>
+
+ <hbox align="center" pack="end">
+ <label value="&socks.label;"
+ accesskey="&socks.accesskey;"
+ control="networkProxySOCKS"/>
+ <textbox id="networkProxySOCKS"
+ preference="network.proxy.socks"
+ flex="1"
+ class="uri-element"/>
+ <label value="&port.label;"
+ accesskey="&SOCKSport.accesskey;"
+ control="networkProxySOCKS_Port"/>
+ <textbox id="networkProxySOCKS_Port"
+ type="number"
+ preference="network.proxy.socks_port"
+ max="65535"
+ size="5"/>
+ </hbox>
+
+ <radiogroup id="networkProxySOCKSVersion"
+ orient="horizontal"
+ preference="network.proxy.socks_version">
+ <radio id="networkProxySOCKSVersion4"
+ value="4"
+ label="&socks4.label;"
+ accesskey="&socks4.accesskey;"/>
+ <radio id="networkProxySOCKSVersion5"
+ value="5"
+ label="&socks5.label;"
+ accesskey="&socks5.accesskey;"/>
+ </radiogroup>
+
+ <hbox align="left">
+ <checkbox id="networkProxySOCKSRemoteDNS"
+ label="&socksRemoteDNS.label;"
+ accesskey="&socksRemoteDNS.accesskey;"
+ preference="network.proxy.socks_remote_dns"/>
+ </hbox>
+
+ </groupbox>
+ </prefpane>
+</prefwindow>
diff --git a/xpfe/components/preferences/content/pref-proxies.js b/xpfe/components/preferences/content/pref-proxies.js
new file mode 100644
index 000000000..b31e64f34
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-proxies.js
@@ -0,0 +1,190 @@
+/* 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 kNoProxy = 0;
+const kManualProxy = 1;
+const kAutoConfigProxy = 2;
+const kObsoleteProxy = 3;
+const kAutoDiscoverProxy = 4;
+const kSystemProxy = 5;
+
+var gInstantApply;
+var gHTTP;
+var gHTTPPort;
+var gSSL;
+var gSSLPort;
+var gFTP;
+var gFTPPort;
+var gAutoURL;
+var gProxyType;
+var gShareSettings;
+
+// Only used by main prefwindow
+function Startup()
+{
+ InitCommonGlobals();
+ gAutoURL = document.getElementById("network.proxy.autoconfig_url");
+ gProxyType = document.getElementById("network.proxy.type");
+
+ // Check for system proxy settings class and unhide UI if present
+ if ("@mozilla.org/system-proxy-settings;1" in Components.classes)
+ document.getElementById("systemPref").hidden = false;
+
+ // Calculate a sane default for network.proxy.share_proxy_settings.
+ if (gShareSettings.value == null)
+ gShareSettings.value = DefaultForShareSettingsPref();
+
+ // The pref value 3 (kObsoleteProxy) for network.proxy.type is unused to
+ // maintain backwards compatibility. Treat 3 (kObsoleteProxy) equally to
+ // 0 (kNoProxy). See bug 115720.
+ if (gProxyType.value == kObsoleteProxy)
+ gProxyType.value = kNoProxy;
+
+ DoEnabling();
+}
+
+// Only used by child prefwindow
+function AdvancedInit()
+{
+ InitCommonGlobals();
+ DoProxyCopy(gShareSettings.value);
+}
+
+function InitCommonGlobals()
+{
+ gInstantApply = document.documentElement.instantApply;
+ gHTTP = document.getElementById("network.proxy.http");
+ gHTTPPort = document.getElementById("network.proxy.http_port");
+ gSSL = document.getElementById("network.proxy.ssl");
+ gSSLPort = document.getElementById("network.proxy.ssl_port");
+ gFTP = document.getElementById("network.proxy.ftp");
+ gFTPPort = document.getElementById("network.proxy.ftp_port");
+ gShareSettings = document.getElementById("network.proxy.share_proxy_settings");
+}
+
+// Returns true if all protocol specific proxies and all their
+// ports are set to the same value, false otherwise.
+function DefaultForShareSettingsPref()
+{
+ return gHTTP.value == gSSL.value &&
+ gHTTP.value == gFTP.value &&
+ gHTTPPort.value == gSSLPort.value &&
+ gHTTPPort.value == gFTPPort.value;
+}
+
+function DoEnabling()
+{
+ // convenience arrays
+ var manual = ["networkProxyHTTP", "networkProxyHTTP_Port",
+ "networkProxyNone", "advancedButton"];
+ var auto = ["networkProxyAutoconfigURL", "autoReload"];
+
+ switch (gProxyType.value)
+ {
+ case kNoProxy:
+ case kAutoDiscoverProxy:
+ case kSystemProxy:
+ Disable(manual);
+ Disable(auto);
+ break;
+ case kManualProxy:
+ Disable(auto);
+ if (!gProxyType.locked)
+ EnableUnlockedElements(manual, true);
+ break;
+ case kAutoConfigProxy:
+ default:
+ Disable(manual);
+ if (!gProxyType.locked)
+ {
+ EnableElementById("networkProxyAutoconfigURL", true, false);
+ EnableUnlockedButton(gAutoURL);
+ }
+ break;
+ }
+}
+
+function Disable(aElementIds)
+{
+ for (var i = 0; i < aElementIds.length; i++)
+ document.getElementById(aElementIds[i]).setAttribute("disabled", "true");
+}
+
+function EnableUnlockedElements(aElementIds, aEnable)
+{
+ for (var i = 0; i < aElementIds.length; i++)
+ EnableElementById(aElementIds[i], aEnable, false);
+}
+
+function EnableUnlockedButton(aElement)
+{
+ var enable = gInstantApply ||
+ (aElement.valueFromPreferences == aElement.value);
+ EnableElementById("autoReload", enable, false);
+}
+
+function ReloadPAC() {
+ // This reloads the PAC URL stored in preferences.
+ // When not in instant apply mode, the button that calls this gets
+ // disabled if the preference and what is showing in the UI differ.
+ Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService().reloadPAC();
+}
+
+function FixProxyURL(aURL)
+{
+ const nsIURIFixup = Components.interfaces.nsIURIFixup;
+ var URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(nsIURIFixup);
+ try
+ {
+ aURL.value = URIFixup.createFixupURI(aURL.value,
+ nsIURIFixup.FIXUP_FLAG_NONE).spec;
+ }
+ catch (e) {}
+
+ if (!gInstantApply)
+ EnableUnlockedButton(aURL);
+}
+
+function OpenAdvancedDialog()
+{
+ document.documentElement.openSubDialog("chrome://communicator/content/pref/pref-proxies-advanced.xul",
+ "AdvancedProxyPreferences", null);
+}
+
+function DoProxyCopy(aChecked)
+{
+ DoProxyHostCopy(gHTTP.value);
+ DoProxyPortCopy(gHTTPPort.value);
+ var nonshare = ["networkProxySSL", "networkProxySSL_Port",
+ "networkProxyFTP", "networkProxyFTP_Port"];
+ EnableUnlockedElements(nonshare, !aChecked);
+}
+
+function DoProxyHostCopy(aValue)
+{
+ if (!gShareSettings.value)
+ return;
+
+ gSSL.value = aValue;
+ gFTP.value = aValue;
+}
+
+function DoProxyPortCopy(aValue)
+{
+ if (!gShareSettings.value)
+ return;
+
+ gSSLPort.value = aValue;
+ gFTPPort.value = aValue;
+}
+
+function UpdateProxies()
+{
+ var noProxiesPref = document.getElementById("network.proxy.no_proxies_on");
+
+ noProxiesPref.value = noProxiesPref.value.replace(/[;, \n]+/g, ", ")
+ .replace(/^, |, $/g, "");
+}
diff --git a/xpfe/components/preferences/content/pref-proxies.xul b/xpfe/components/preferences/content/pref-proxies.xul
new file mode 100644
index 000000000..acd1a1f05
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-proxies.xul
@@ -0,0 +1,156 @@
+<?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 overlay SYSTEM "chrome://communicator/locale/pref/pref-proxies.dtd">
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="proxies_pane"
+ label="&pref.proxies.title;"
+ script="chrome://communicator/content/pref/pref-proxies.js">
+ <preferences id="proxies_preferences">
+ <preference id="network.proxy.type"
+ name="network.proxy.type"
+ type="int"
+ onchange="DoEnabling();"/>
+ <preference id="network.proxy.autoconfig_url"
+ name="network.proxy.autoconfig_url"
+ type="string"/>
+ <preference id="pref.advanced.proxies.disable_button.reload"
+ name="pref.advanced.proxies.disable_button.reload"
+ type="bool"/>
+ <preference id="network.proxy.http"
+ name="network.proxy.http"
+ type="string"
+ onchange="DoProxyHostCopy(this.value);"/>
+ <preference id="network.proxy.http_port"
+ name="network.proxy.http_port"
+ type="int"
+ onchange="DoProxyPortCopy(this.value);"/>
+ <preference id="pref.advanced.proxies.disable_button.advanced"
+ name="pref.advanced.proxies.disable_button.advanced"
+ type="bool"/>
+ <preference id="network.proxy.no_proxies_on"
+ name="network.proxy.no_proxies_on"
+ type="string"/>
+ <preference id="network.proxy.ssl"
+ name="network.proxy.ssl"
+ type="string"/>
+ <preference id="network.proxy.ssl_port"
+ name="network.proxy.ssl_port"
+ type="int"/>
+ <preference id="network.proxy.ftp"
+ name="network.proxy.ftp"
+ type="string"/>
+ <preference id="network.proxy.ftp_port"
+ name="network.proxy.ftp_port"
+ type="int"/>
+ <preference id="network.proxy.share_proxy_settings"
+ name="network.proxy.share_proxy_settings"
+ type="bool"/>
+ </preferences>
+
+ <description>&pref.proxies.desc;</description>
+ <groupbox>
+ <caption label="&proxyTitle.label;"/>
+ <radiogroup id="networkProxyType"
+ preference="network.proxy.type"
+ align="stretch">
+ <vbox align="start">
+ <radio value="0"
+ label="&directTypeRadio.label;"
+ accesskey="&directTypeRadio.accesskey;"/>
+ <radio value="4"
+ label="&wpadTypeRadio.label;"
+ accesskey="&wpadTypeRadio.accesskey;"/>
+ <radio value="5"
+ label="&systemTypeRadio.label;"
+ accesskey="&systemTypeRadio.accesskey;"
+ id="systemPref"
+ hidden="true"/>
+ <radio value="2"
+ label="&autoTypeRadio.label;"
+ accesskey="&autoTypeRadio.accesskey;"/>
+ </vbox>
+
+ <hbox class="indent" align="center">
+ <textbox id="networkProxyAutoconfigURL"
+ flex="1"
+ class="uri-element"
+ onchange="FixProxyURL(this);"
+ preference="network.proxy.autoconfig_url"/>
+ <button id="autoReload"
+ label="&reload.label;"
+ accesskey="&reload.accesskey;"
+ oncommand="ReloadPAC();"
+ preference="pref.advanced.proxies.disable_button.reload"/>
+ </hbox>
+
+ <vbox align="start">
+ <radio value="1"
+ label="&manualTypeRadio.label;"
+ accesskey="&manualTypeRadio.accesskey;"/>
+ </vbox>
+
+ <grid class="indent">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label value="&http.label;"
+ accesskey="&http.accesskey;"
+ control="networkProxyHTTP"/>
+ </hbox>
+ <textbox id="networkProxyHTTP"
+ preference="network.proxy.http"
+ class="uri-element"/>
+ </row>
+
+ <row>
+ <hbox align="center" pack="end">
+ <label value="&port.label;"
+ accesskey="&HTTPPort.accesskey;"
+ control="networkProxyHTTP_Port"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxyHTTP_Port"
+ preference="network.proxy.http_port"
+ type="number"
+ max="65535"
+ size="5"/>
+ <spacer flex="1"/>
+ <button id="advancedButton"
+ label="&advanced.label;"
+ accesskey="&advanced.accesskey;"
+ align="end"
+ oncommand="OpenAdvancedDialog();"
+ preference="pref.advanced.proxies.disable_button.advanced"/>
+ </hbox>
+ </row>
+
+ <row align="baseline">
+ <hbox align="center" pack="end">
+ <label value="&noproxy.label;"
+ accesskey="&noproxy.accesskey;"
+ control="networkProxyNone"/>
+ </hbox>
+ <textbox id="networkProxyNone"
+ multiline="true"
+ preference="network.proxy.no_proxies_on"
+ class="uri-element"
+ onchange="UpdateProxies();"/>
+ </row>
+ <row>
+ <spacer/>
+ <description control="networkProxyNone">&noproxyExplain.label;
+ </description>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-smartupdate.js b/xpfe/components/preferences/content/pref-smartupdate.js
new file mode 100644
index 000000000..740048ac0
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-smartupdate.js
@@ -0,0 +1,87 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 gCanCheckForUpdates;
+
+function Startup()
+{
+ var hasUpdater = "nsIApplicationUpdateService" in Components.interfaces;
+
+ if (hasUpdater)
+ {
+ var aus = Components.classes["@mozilla.org/updates/update-service;1"]
+ .getService(Components.interfaces.nsIApplicationUpdateService);
+ gCanCheckForUpdates = aus.canCheckForUpdates;
+
+ UpdateAddonsItems();
+ UpdateAppItems();
+ }
+ else
+ {
+ var appGroupBox = document.getElementById("appUpdatesGroupBox");
+ appGroupBox.hidden = true;
+ }
+}
+
+/*
+ * Preferences:
+ *
+ * app.update.enabled
+ * - boolean:
+ * - true if updates to the application are enabled, false otherwise
+ * extensions.update.enabled
+ * - boolean:
+ * - true if updates to extensions and themes are enabled, false otherwise
+ * app.update.auto
+ * - true if updates should be automatically downloaded and installed,
+ * false if the user should be asked what he wants to do when an
+ * update is available
+ */
+function UpdateAddonsItems()
+{
+ var addOnsCheck = !document.getElementById("xpinstall.enabled").value;
+
+ document.getElementById("addOnsUpdatesEnabled").disabled =
+ addOnsCheck ||
+ document.getElementById("extensions.update.enabled").locked;
+
+ document.getElementById("addOnsUpdateFrequency").disabled =
+ !document.getElementById("xpinstall.enabled").value ||
+ !document.getElementById("extensions.update.enabled").value ||
+ document.getElementById("extensions.update.interval").locked;
+
+ document.getElementById("allowedSitesLink").disabled =
+ addOnsCheck;
+
+ document.getElementById("addOnsModeAutoEnabled").disabled =
+ addOnsCheck ||
+ !document.getElementById("extensions.update.enabled").value ||
+ document.getElementById("extensions.update.enabled").locked;
+}
+
+function UpdateAppItems()
+{
+ var enabledPref = document.getElementById("app.update.enabled");
+
+ document.getElementById("appUpdatesEnabled").disabled =
+ !gCanCheckForUpdates || enabledPref.locked;
+
+ document.getElementById("appUpdateFrequency").disabled =
+ !enabledPref.value || !gCanCheckForUpdates ||
+ document.getElementById("app.update.interval").locked;
+
+ document.getElementById("appModeAutoEnabled").disabled =
+ !enabledPref.value || !gCanCheckForUpdates;
+}
+
+/**
+ * Displays the history of installed updates.
+ */
+function ShowUpdateHistory()
+{
+ Components.classes["@mozilla.org/updates/update-prompt;1"]
+ .createInstance(Components.interfaces.nsIUpdatePrompt)
+ .showUpdateHistory(window);
+}
diff --git a/xpfe/components/preferences/content/pref-smartupdate.xul b/xpfe/components/preferences/content/pref-smartupdate.xul
new file mode 100644
index 000000000..a6d3fcb8e
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-smartupdate.xul
@@ -0,0 +1,139 @@
+<?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 overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % prefSmartUpdateDTD SYSTEM "chrome://communicator/locale/pref/pref-smartupdate.dtd">
+%prefSmartUpdateDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="smartupdate_pane"
+ label="&pref.smartUpdate.title;"
+ script="chrome://communicator/content/pref/pref-smartupdate.js">
+
+ <preferences id="smartupdate_preferences">
+ <preference id="xpinstall.enabled"
+ name="xpinstall.enabled"
+ type="bool"
+ onchange="UpdateAddonsItems();"/>
+ <preference id="extensions.update.enabled"
+ name="extensions.update.enabled"
+ type="bool"
+ onchange="UpdateAddonsItems();"/>
+ <preference id="extensions.update.interval"
+ name="extensions.update.interval"
+ type="int"/>
+ <preference id="extensions.update.autoUpdateDefault"
+ name="extensions.update.autoUpdateDefault"
+ type="bool"/>
+ <preference id="extensions.getAddons.cache.enabled"
+ name="extensions.getAddons.cache.enabled"
+ type="bool"/>
+ <preference id="app.update.enabled"
+ name="app.update.enabled"
+ type="bool"
+ onchange="UpdateAppItems();"/>
+ <preference id="app.update.auto"
+ name="app.update.auto"
+ type="bool"
+ onchange="UpdateAppItems();"/>
+ <preference id="app.update.interval"
+ name="app.update.interval"
+ type="int"/>
+ <preference id="app.update.disable_button.showUpdateHistory"
+ name="app.update.disable_button.showUpdateHistory"
+ type="bool"/>
+ </preferences>
+
+ <groupbox id="appUpdatesGroupBox">
+ <caption label="&appUpdates.caption;"/>
+ <hbox>
+ <checkbox id="appUpdatesEnabled"
+ label="&autoAppUpdates.label;"
+ accesskey="&autoAppUpdates.accesskey;"
+ preference="app.update.enabled"/>
+ <radiogroup id="appUpdateFrequency"
+ orient="horizontal"
+ preference="app.update.interval">
+ <radio id="appFreqDaily"
+ label="&daily.label;"
+ accesskey="&appDaily.accesskey;"
+ value="86400"/>
+ <radio id="appFreqWeekly"
+ label="&weekly.label;"
+ accesskey="&appWeekly.accesskey;"
+ value="604800"/>
+ </radiogroup>
+ </hbox>
+ <checkbox id="appModeAutoEnabled"
+ class="indent"
+ label="&appModeAutomatic.label;"
+ flex="1"
+ accesskey="&appModeAutomatic.accesskey;"
+ preference="app.update.auto"/>
+ <hbox pack="end">
+ <button id="showUpdateHistory"
+ label="&updateHistoryButton.label;"
+ accesskey="&updateHistoryButton.accesskey;"
+ preference="app.update.disable_button.showUpdateHistory"
+ oncommand="ShowUpdateHistory();"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&addOnsTitle.label;"/>
+ <hbox align="center">
+ <checkbox id="XPInstallEnabled"
+ label="&addOnsAllow.label;"
+ flex="1"
+ accesskey="&addOnsAllow.accesskey;"
+ preference="xpinstall.enabled"/>
+ <label id="allowedSitesLink"
+ class="text-link"
+ value="&allowedSitesLink.label;"
+ onclick="toPermissionsManager('install');"/>
+ </hbox>
+ <hbox class="indent">
+ <checkbox id="addOnsUpdatesEnabled"
+ label="&autoAddOnsUpdates.label;"
+ accesskey="&autoAddOnsUpdates.accesskey;"
+ preference="extensions.update.enabled"/>
+ <radiogroup id="addOnsUpdateFrequency"
+ orient="horizontal"
+ preference="extensions.update.interval">
+ <radio id="addOnsFreqDaily"
+ label="&daily.label;"
+ accesskey="&addOnsDaily.accesskey;"
+ value="86400"/>
+ <radio id="addOnsFreqWeekly"
+ label="&weekly.label;"
+ accesskey="&addOnsWeekly.accesskey;"
+ value="604800"/>
+ </radiogroup>
+ </hbox>
+ <hbox class="indent">
+ <checkbox id="addOnsModeAutoEnabled"
+ class="indent"
+ label="&addOnsModeAutomatic.label;"
+ flex="1"
+ accesskey="&addOnsModeAutomatic.accesskey;"
+ preference="extensions.update.autoUpdateDefault"/>
+ </hbox>
+ <hbox align="center">
+ <checkbox id="enablePersonalized"
+ flex="1"
+ label="&enablePersonalized.label;"
+ accesskey="&enablePersonalized.accesskey;"
+ preference="extensions.getAddons.cache.enabled"/>
+ <label id="addonManagerLink"
+ class="text-link"
+ onclick="toEM('addons://list/extension');"
+ value="&addonManagerLink.label;"/>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/pref-ssl.js b/xpfe/components/preferences/content/pref-ssl.js
new file mode 100644
index 000000000..1e807f740
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-ssl.js
@@ -0,0 +1,82 @@
+/* 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/. */
+
+function Startup()
+{
+ // map associating preference values with checkbox element IDs
+ gSslPrefElements = new Map([[1, "allowTLS10"],
+ [2, "allowTLS11"],
+ [3, "allowTLS12"],
+ [4, "allowTLS13"]]);
+
+ // initial setting of checkboxes based on preference values
+ UpdateSslBoxes();
+}
+
+function UpdateSslBoxes()
+{
+ // get minimum and maximum allowed protocol and locked status
+ let minVersion = document.getElementById("security.tls.version.min").value;
+ let maxVersion = document.getElementById("security.tls.version.max").value;
+ let minLocked = document.getElementById("security.tls.version.min").locked;
+ let maxLocked = document.getElementById("security.tls.version.max").locked;
+
+ // check if allowable limits are violated, use default values if they are
+ if (minVersion > maxVersion || !gSslPrefElements.has(minVersion)
+ || !gSslPrefElements.has(maxVersion))
+ {
+ minVersion = document.getElementById("security.tls.version.min").defaultValue;
+ maxVersion = document.getElementById("security.tls.version.max").defaultValue;
+ }
+
+ // set checked, disabled, and locked status for each protocol checkbox
+ for (let [version, id] of gSslPrefElements)
+ {
+ let currentBox = document.getElementById(id);
+ currentBox.checked = version >= minVersion && version <= maxVersion;
+
+ if ((minLocked && maxLocked) || (minLocked && version <= minVersion) ||
+ (maxLocked && version >= maxVersion))
+ {
+ // boxes subject to a preference's locked status are disabled and grayed
+ currentBox.removeAttribute("nogray");
+ currentBox.disabled = true;
+ }
+ else
+ {
+ // boxes which the user can't uncheck are disabled but not grayed
+ currentBox.setAttribute("nogray", "true");
+ currentBox.disabled = (version > minVersion && version < maxVersion) ||
+ (version == minVersion && version == maxVersion);
+ }
+ }
+}
+
+function UpdateSslPrefs()
+{
+ // this is called whenever a checkbox changes
+ let minVersion = -1;
+ let maxVersion = -1;
+
+ // find the first and last checkboxes which are now checked
+ for (let [version, id] of gSslPrefElements)
+ {
+ if (document.getElementById(id).checked)
+ {
+ if (minVersion < 0) // first box checked
+ minVersion = version;
+ maxVersion = version; // last box checked so far
+ }
+ }
+
+ // if minVersion is valid, then maxVersion is as well -> update prefs
+ if (minVersion >= 0)
+ {
+ document.getElementById("security.tls.version.min").value = minVersion;
+ document.getElementById("security.tls.version.max").value = maxVersion;
+ }
+
+ // update checkbox values and visibility based on prefs again
+ UpdateSslBoxes();
+}
diff --git a/xpfe/components/preferences/content/pref-ssl.xul b/xpfe/components/preferences/content/pref-ssl.xul
new file mode 100644
index 000000000..aa4cbbf49
--- /dev/null
+++ b/xpfe/components/preferences/content/pref-ssl.xul
@@ -0,0 +1,121 @@
+<?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://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % prefSslDTD SYSTEM "chrome://pippki/locale/pref-ssl.dtd">
+ %prefSslDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="ssl_pane"
+ label="&pref.ssltls.title;"
+ script="chrome://pippki/content/pref-ssl.js">
+ <preferences id="ssl_preferences">
+ <preference id="security.tls.version.min"
+ name="security.tls.version.min"
+ type="int"/>
+ <preference id="security.tls.version.max"
+ name="security.tls.version.max"
+ type="int"/>
+ <preference id="security.warn_entering_secure"
+ name="security.warn_entering_secure"
+ type="bool"/>
+ <preference id="security.warn_leaving_secure"
+ name="security.warn_leaving_secure"
+ type="bool"/>
+ <preference id="security.warn_submit_insecure"
+ name="security.warn_submit_insecure"
+ type="bool"/>
+ <preference id="security.warn_mixed_active_content"
+ name="security.warn_mixed_active_content"
+ type="bool"/>
+ <preference id="security.mixed_content.block_active_content"
+ name="security.mixed_content.block_active_content"
+ type="bool"/>
+ <preference id="security.warn_mixed_display_content"
+ name="security.warn_mixed_display_content"
+ type="bool"/>
+ <preference id="security.mixed_content.block_display_content"
+ name="security.mixed_content.block_display_content"
+ type="bool"/>
+
+ <!-- Opportunistic Encryption -->
+ <preference id="network.http.upgrade-insecure-requests"
+ name="network.http.upgrade-insecure-requests"
+ type="bool"/>
+ <preference id="network.http.altsvc.oe"
+ name="network.http.altsvc.oe"
+ type="bool"/>
+ </preferences>
+
+ <groupbox align="start">
+ <caption label="&SSLTLSProtocolVersions.caption;"/>
+ <description>&limit.description;</description>
+
+ <hbox align="center">
+ <label id="allowEnable"
+ value="&limit.enable.label;"/>
+ <checkbox id="allowTLS10"
+ class="nogray-disabled"
+ label="&limit.tls10.label;"
+ accesskey="&limit.tls10.accesskey;"
+ oncommand="UpdateSslPrefs();"/>
+ <checkbox id="allowTLS11"
+ class="nogray-disabled"
+ label="&limit.tls11.label;"
+ accesskey="&limit.tls11.accesskey;"
+ oncommand="UpdateSslPrefs();"/>
+ <checkbox id="allowTLS12"
+ class="nogray-disabled"
+ label="&limit.tls12.label;"
+ accesskey="&limit.tls12.accesskey;"
+ oncommand="UpdateSslPrefs();"/>
+ <checkbox id="allowTLS13"
+ class="nogray-disabled"
+ label="&limit.tls13.label;"
+ accesskey="&limit.tls13.accesskey;"
+ oncommand="UpdateSslPrefs();"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox align="start">
+ <caption label="&SSLMixedContent.caption;"/>
+ <description>&mixed.description;</description>
+ <checkbox id="warnMixedActiveContent"
+ label="&warn.mixedactivecontent;"
+ accesskey="&warn.mixedactivecontent.accesskey;"
+ preference="security.warn_mixed_active_content"/>
+ <checkbox id="blockActiveContent"
+ label="&block.activecontent;"
+ accesskey="&block.activecontent.accesskey;"
+ preference="security.mixed_content.block_active_content"/>
+ <checkbox id="warnMixedDisplayContent"
+ label="&warn.mixeddisplaycontent;"
+ accesskey="&warn.mixeddisplaycontent.accesskey;"
+ preference="security.warn_mixed_display_content"/>
+ <checkbox id="blockDisplayContent"
+ label="&block.displaycontent;"
+ accesskey="&block.displaycontent.accesskey;"
+ preference="security.mixed_content.block_display_content"/>
+ </groupbox>
+
+ <!-- Opportunistic Encryption -->
+ <groupbox id="OpportunisticEncryption"
+ align="start">
+ <caption label="&OpEnc.label;"/>
+ <checkbox id="enableUIROpEnc"
+ label="&enableUIROpEnc.label;"
+ preference="network.http.upgrade-insecure-requests" />
+ <checkbox id="enableAltSvcOpEnc"
+ label="&enableAltSvcOpEnc.label;"
+ preference="network.http.altsvc.oe" />
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/xpfe/components/preferences/content/preferences.js b/xpfe/components/preferences/content/preferences.js
new file mode 100644
index 000000000..511d3c039
--- /dev/null
+++ b/xpfe/components/preferences/content/preferences.js
@@ -0,0 +1,98 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+// The content of this file is loaded into the scope of the
+// prefwindow and will be available to all prefpanes!
+
+function OnLoad()
+{
+ // Make sure that the preferences window fits the screen.
+ let dialog = document.documentElement;
+ let curHeight = dialog.scrollHeight;
+ let curWidth = dialog.scrollWidth;
+
+ // Leave some space for desktop toolbar and window decoration.
+ let maxHeight = window.screen.availHeight - 48;
+ let maxWidth = window.screen.availWidth - 24;
+
+ // Trigger overflow situation within 40px for bug 868495 expansions.
+ let setHeight = curHeight > maxHeight - 40 ? maxHeight : curHeight;
+ let setWidth = curWidth > maxWidth ? maxWidth : curWidth;
+
+ if (setHeight == curHeight && setWidth == curWidth)
+ dialog.setAttribute("overflow", "visible");
+
+ window.innerHeight = setHeight;
+ window.innerWidth = setWidth;
+
+ if (document.getElementById("advancedChildren").childElementCount == 0) {
+ document.getElementById("advancedItem").setAttribute("open", false);
+ document.getElementById("advancedItem").setAttribute("container", false);
+ }
+}
+
+function EnableElementById(aElementId, aEnable, aFocus)
+{
+ EnableElement(document.getElementById(aElementId), aEnable, aFocus);
+}
+
+function EnableElement(aElement, aEnable, aFocus)
+{
+ let pref = document.getElementById(aElement.getAttribute("preference"));
+ let enabled = aEnable && !pref.locked;
+
+ aElement.disabled = !enabled;
+
+ if (enabled && aFocus)
+ aElement.focus();
+}
+
+function WriteSoundField(aField, aValue)
+{
+ var file = GetFileFromString(aValue);
+ if (file)
+ {
+ aField.file = file;
+ aField.label = (/Mac/.test(navigator.platform)) ? file.leafName : file.path;
+ }
+}
+
+function SelectSound(aSoundUrlPref)
+{
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ var prefutilitiesBundle = document.getElementById("bundle_prefutilities");
+ fp.init(window, prefutilitiesBundle.getString("choosesound"),
+ nsIFilePicker.modeOpen);
+
+ var file = GetFileFromString(aSoundUrlPref.value);
+ if (file && file.parent && file.parent.exists())
+ fp.displayDirectory = file.parent;
+
+ var filterExts = "*.wav; *.wave";
+ // On Mac, allow AIFF and CAF files too.
+ if (/Mac/.test(navigator.platform))
+ filterExts += "; *.aif; *.aiff; *.caf";
+ fp.appendFilter(prefutilitiesBundle.getString("SoundFiles"), filterExts);
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ if (fp.show() == nsIFilePicker.returnOK)
+ aSoundUrlPref.value = fp.fileURL.spec;
+}
+
+function PlaySound(aValue, aMail)
+{
+ const nsISound = Components.interfaces.nsISound;
+ var sound = Components.classes["@mozilla.org/sound;1"]
+ .createInstance(nsISound);
+
+ if (aValue)
+ sound.play(Services.io.newURI(aValue, null, null));
+ else if (aMail && !/Mac/.test(navigator.platform))
+ sound.playEventSound(nsISound.EVENT_NEW_MAIL_RECEIVED);
+ else
+ sound.beep();
+}
diff --git a/xpfe/components/preferences/content/preferences.xul b/xpfe/components/preferences/content/preferences.xul
new file mode 100644
index 000000000..82ff62de9
--- /dev/null
+++ b/xpfe/components/preferences/content/preferences.xul
@@ -0,0 +1,168 @@
+<?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 type="text/css" href="chrome://communicator/skin/"?>
+<?xml-stylesheet type="text/css" href="chrome://communicator/content/communicator.css"?>
+<?xml-stylesheet type="text/css" href="chrome://communicator/content/pref/prefpanels.css"?>
+<?xml-stylesheet type="text/css" href="chrome://communicator/skin/prefpanels.css"?>
+<?xml-stylesheet type="text/css" href="chrome://communicator/skin/preferences.css"?>
+
+<!DOCTYPE prefwindow [
+ <!ENTITY % dtdPrefs SYSTEM "chrome://communicator/locale/pref/preferences.dtd"> %dtdPrefs;
+]>
+
+<prefwindow id="prefDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&prefWindow.title;"
+ height="660px"
+ width="690px"
+ overflow="auto"
+ onload="OnLoad();"
+ windowtype="mozilla:preferences"
+ buttons="accept,cancel"
+ autopanes="true">
+
+ <script type="application/javascript" src="chrome://communicator/content/pref/preferences.js"/>
+ <!-- Used by pref-smartupdate, pref-privatedata, pref-cookies, pref-images, pref-popups and pref-passwords, as well as pref-sync (gSyncUtils.*open* -> openUILinkIn) -->
+ <script type="application/javascript" src="chrome://communicator/content/utilityOverlay.js"/>
+ <script type="application/javascript" src="chrome://communicator/content/tasksOverlay.js"/>
+
+ <stringbundleset id="prefBundleset">
+ <stringbundle id="bundle_prefutilities"
+ src="chrome://communicator/locale/pref/prefutilities.properties"/>
+ <stringbundle id="languageNamesBundle"
+ src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="regionNamesBundle"
+ src="chrome://global/locale/regionNames.properties"/>
+ </stringbundleset>
+
+ <tree id="prefsTree"
+ style="width: 30ch;"
+ seltype="single"
+ hidecolumnpicker="true"
+ hidden="true"
+ flex="1">
+
+ <treechildren id="prefsPanelChildren">
+ <treeitem id="mainItem">
+ <treechildren id="mainChildren">
+ <treeitem id="downloadItem"
+ label="&download.label;"
+ prefpane="download_pane"
+ url="chrome://communicator/content/pref/pref-download.xul"/>
+ <treeitem id="applicationsItem"
+ label="&applications.label;"
+ prefpane="applications_pane"
+ url="chrome://communicator/content/pref/pref-applications.xul"/>
+ <treeitem id="smartupdateItem"
+ label="&smart.label;"
+ prefpane="smartupdate_pane"
+ url="chrome://communicator/content/pref/pref-smartupdate.xul"/>
+ </treechildren>
+ </treeitem>
+
+ <treeitem open="true"
+ container="true"
+ id="contentItem"
+ label="&content.label;"
+ prefpane="content_pane"
+ url="chrome://communicator/content/pref/pref-content.xul">
+ <treechildren id="contentChildren">
+ <treeitem id="colorsItem"
+ label="&colors.label;"
+ prefpane="colors_pane"
+ url="chrome://communicator/content/pref/pref-colors.xul"/>
+ <treeitem id="fontsItem"
+ label="&fonts.label;"
+ prefpane="fonts_pane"
+ url="chrome://communicator/content/pref/pref-fonts.xul"/>
+ <treeitem id="languagesItem"
+ label="&languages.label;"
+ prefpane="languages_pane"
+ url="chrome://communicator/content/pref/pref-languages.xul"/>
+ <treeitem id="scriptsItem"
+ label="&scriptsAndWindows.label;"
+ prefpane="scripts_pane"
+ url="chrome://communicator/content/pref/pref-scripts.xul"/>
+ <treeitem id="mousewheelItem"
+ label="&mousewheel.label;"
+ prefpane="mousewheel_pane"
+ url="chrome://communicator/content/pref/pref-mousewheel.xul"/>
+ </treechildren>
+ </treeitem>
+
+ <!-- Privacy items -->
+ <treeitem open="true"
+ container="true"
+ id="privacyItem"
+ prefpane="privacy_pane"
+ label="&privacy.label;"
+ url="chrome://communicator/content/pref/pref-privacy.xul">
+ <treechildren id="privacyChildren">
+ <treeitem id="privatedataItem"
+ label="&privatedata.label;"
+ prefpane="privatedata_pane"
+ url="chrome://communicator/content/pref/pref-privatedata.xul"/>
+ <treeitem id="historyItem"
+ label="&history.label;"
+ prefpane="history_pane"
+ url="chrome://communicator/content/pref/pref-history.xul"/>
+ <treeitem id="cookiesItem"
+ label="&cookies.label;"
+ prefpane="cookies_pane"
+ url="chrome://communicator/content/pref/pref-cookies.xul"/>
+ <treeitem id="cacheItem"
+ label="&cache.label;"
+ prefpane="cache_pane"
+ url="chrome://communicator/content/pref/pref-cache.xul"/>
+ <treeitem id="offlineAppsItem"
+ label="&offlineApps.label;"
+ prefpane="offlineapps_pane"
+ url="chrome://communicator/content/pref/pref-offlineapps.xul"/>
+ </treechildren>
+ </treeitem>
+
+
+ <!-- Networking & Security items -->
+ <treeitem open="true"
+ container="true"
+ id="httpItem"
+ label="&httpnetworking.label;"
+ prefpane="http_pane"
+ url="chrome://communicator/content/pref/pref-http.xul">
+ <treechildren id="securityChildren">
+ <treeitem id="proxiesItem"
+ label="&proxies.label;"
+ prefpane="proxies_pane"
+ url="chrome://communicator/content/pref/pref-proxies.xul"/>
+ <treeitem id="sslItem"
+ label="&ssltls.label;"
+ prefpane="ssl_pane"
+ url="chrome://pippki/content/pref-ssl.xul"/>
+ <treeitem id="certItem"
+ label="&certs.label;"
+ prefpane="certs_pane"
+ url="chrome://pippki/content/pref-certs.xul"/>
+ <treeitem id="masterpassItem"
+ label="&masterpass.label;"
+ prefpane="masterpass_pane"
+ url="chrome://communicator/content/pref/pref-masterpass.xul"/>
+ </treechildren>
+ </treeitem>
+
+ <!-- Advanced items xptoolkit -->
+ <treeitem open="true"
+ container="true"
+ id="advancedItem"
+ label="&advance.label;"
+ prefpane="advanced_pane"
+ url="chrome://communicator/content/pref/pref-advanced.xul">
+ <treechildren id="advancedChildren"/>
+ </treeitem>
+ </treechildren>
+ </tree>
+
+</prefwindow>
diff --git a/xpfe/components/preferences/content/prefpanels.css b/xpfe/components/preferences/content/prefpanels.css
new file mode 100644
index 000000000..af78b1b10
--- /dev/null
+++ b/xpfe/components/preferences/content/prefpanels.css
@@ -0,0 +1,15 @@
+/* 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/. */
+
+#handlersView > listitem {
+ -moz-binding: url("chrome://communicator/content/pref/prefpanels.xml#handler");
+}
+
+.listcell-iconic.handler-action[selected="true"] {
+ -moz-binding: url("chrome://communicator/content/pref/prefpanels.xml#handler-action-selected");
+}
+
+#offlineAppsList > listitem {
+ -moz-binding: url("chrome://communicator/content/pref/prefpanels.xml#offlineapp");
+}
diff --git a/xpfe/components/preferences/content/prefpanels.xml b/xpfe/components/preferences/content/prefpanels.xml
new file mode 100644
index 000000000..347eac975
--- /dev/null
+++ b/xpfe/components/preferences/content/prefpanels.xml
@@ -0,0 +1,59 @@
+<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % applicationsDTD SYSTEM "chrome://communicator/locale/pref/pref-applications.dtd">
+ %applicationsDTD;
+]>
+
+<bindings id="handlerBindings"
+ 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="handler" extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <implementation>
+ <constructor>
+ this.doCommand();
+ </constructor>
+ <property name="type" readonly="true">
+ <getter>
+ return this.getAttribute("type");
+ </getter>
+ </property>
+ </implementation>
+ <content>
+ <xul:listcell class="listcell-iconic handler-type" align="center" crop="end"
+ xbl:inherits="tooltiptext=typeDescription,label=typeDescription,image=typeIcon,typeClass"/>
+ <xul:listcell anonid="action-cell" class="listcell-iconic handler-action" align="center" crop="end"
+ xbl:inherits="tooltiptext=actionDescription,label=actionDescription,image=actionIcon,appHandlerIcon,selected"/>
+ </content>
+ </binding>
+
+ <binding id="handler-action-selected" extends="chrome://global/content/bindings/listbox.xml#listcell">
+ <content>
+ <xul:menulist anonid="action-menu" class="actionsMenu" flex="1" crop="end" selectedIndex="1">
+ <xul:menupopup/>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ this.doCommand();
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="offlineapp" extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <xul:listcell xbl:inherits="label=host"/>
+ <xul:listcell xbl:inherits="label=usage"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/xpfe/components/preferences/content/prefwindow.xml b/xpfe/components/preferences/content/prefwindow.xml
new file mode 100644
index 000000000..dee0a3152
--- /dev/null
+++ b/xpfe/components/preferences/content/prefwindow.xml
@@ -0,0 +1,506 @@
+<?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/. -->
+
+<!--
+ SeaMonkey Extended Preferences Window Framework
+
+ The binding implemented here mostly works like its toolkit ancestor, with
+ one important difference: the <prefwindow> recognizes the first <tree> in
+ its content and assumes that this is the navigation tree:
+
+ <prefwindow>
+ <tree>
+ ...
+ <treeitem id="prefTreeItemA" prefpane="prefPaneA">
+ ...
+ </tree>
+ <prefpane id="prefPaneB">...</prefpane>
+ <prefpane id="prefPaneA">
+ </prefwindow>
+
+ The <tree> structure defines the hierarchical layout of the preference
+ window's navigation tree. A <treeitem>'s "prefpane" attribute references
+ one of the <prefpane>s given on the <prefwindow>'s main level.
+ All <prefpane>s not referenced by a <treeitem> will be appended to the
+ navigation tree's top level. <treeitem>s can be nested as needed, but
+ <treeitem>s without a related <prefpane> will be hidden.
+
+ Furthermore, if the <prefwindow> has attribute "autopanes" set to "true",
+ non-existing <prefpane>s will be generated automatically from certain
+ attributes of the <treeitem>:
+ - "url" must contain the <prefpane>'s url
+ - "prefpane" should contain the <prefpane>'s desired id,
+ otherwise its url will be used as id
+ - "helpTopic" may contain an index into SeaMonkey's help
+
+ Unlike in XPFE, where preferences panels were loaded into a separate
+ iframe, <prefpane>s are an integral part of the <prefwindow> document,
+ by virtue of loadOverlay. Hence <script>s will be loaded into the
+ <prefwindow> scope and possibly clash. To avoid this, <prefpane>s should
+ specify a "script" attribute with a whitespace delimited list of scripts
+ to load into the <prefpane>'s context. The subscriptloader will take care
+ of any internal scoping, so no this.* fest is necessary inside the script.
+
+ <prefwindow> users who want to share the very same file between SeaMonkey
+ and other toolkit apps should hide the <tree> (set <tree>.hidden=true);
+ this binding will then unhide the <tree> if necessary, ie more than just
+ one <prefpane> exists.
+ Also, the <tree> will get the class "prefnavtree" added, so that it may be
+ prestyled by the SeaMonkey themes.
+ Setting <prefwindow xpfe="false"> will enforce the application of just the
+ basic toolkit <prefwindow> even in SeaMonkey. The same "xpfe" attribute
+ exists for <prefpane>, too.
+-->
+
+<!DOCTYPE bindings [
+ <!ENTITY % dtdPrefs SYSTEM "chrome://communicator/locale/pref/preferences.dtd"> %dtdPrefs;
+ <!ENTITY % dtdGlobalPrefs SYSTEM "chrome://global/locale/preferences.dtd"> %dtdGlobalPrefs;
+]>
+
+<bindings id="prefwindowBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="prefwindow"
+ extends="chrome://global/content/bindings/preferences.xml#prefwindow">
+ <resources>
+ <stylesheet src="chrome://communicator/skin/preferences.css"/>
+ </resources>
+
+ <!-- The only difference between the following <content> and its toolkit
+ ancestor is the help button and the 'navTrees' <vbox> before the 'paneDeck'! -->
+ <content dlgbuttons="accept,cancel" persist="lastSelected screenX screenY"
+ closebuttonlabel="&preferencesCloseButton.label;"
+ closebuttonaccesskey="&preferencesCloseButton.accesskey;"
+ role="dialog">
+ <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
+ role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
+ <xul:hbox flex="1" class="paneDeckContainer">
+ <xul:vbox anonid="navTrees">
+ <children includes="tree"/>
+ </xul:vbox>
+ <xul:vbox flex="1">
+ <xul:dialogheader anonid="paneHeader" hidden="true"/>
+ <xul:deck anonid="paneDeck" flex="1">
+ <children includes="prefpane"/>
+ </xul:deck>
+ </xul:vbox>
+ </xul:hbox>
+ <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons" pack="end">
+#ifdef XP_UNIX
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
+ <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
+ <xul:spacer anonid="spacer" flex="1"/>
+ <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
+ <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
+#else
+ <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
+ <xul:spacer anonid="spacer" flex="1"/>
+ <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
+ <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+#endif
+ </xul:hbox>
+ <xul:hbox>
+ <children/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ // grab the first child tree and try to tie it to the prefpanes
+ var tree = this.getElementsByTagName('tree')[0];
+ this.initNavigationTree(tree);
+ // hide the toolkit pref strip if we have a tree
+ if (this._navigationTree)
+ this._selector.hidden = true;
+ ]]>
+ </constructor>
+
+ <field name="_navigationTree">null</field>
+
+ <!-- <prefwindow> users can call this method to exchange the <tree> -->
+ <method name="initNavigationTree">
+ <parameter name="aTreeElement"/>
+ <body>
+ <![CDATA[
+ this._navigationTree = null;
+ if (!aTreeElement)
+ return;
+
+ // don't grab trees in prefpanes etc.
+ if (aTreeElement.parentNode != this)
+ return;
+
+ // autogenerate <prefpane>s from <treecell>.url if requested
+ var autopanes = (this.getAttribute('autopanes') == 'true');
+ if (!autopanes)
+ {
+ // without autopanes, we can return early: don't bother
+ // with a navigation tree if we only have one prefpane
+ aTreeElement.hidden = (this.preferencePanes.length < 2);
+ if (aTreeElement.hidden)
+ return;
+ }
+
+ // ensure that we have a tree body
+ if (!aTreeElement.getElementsByTagName('treechildren').length)
+ aTreeElement.appendChild(document.createElement('treechildren'));
+
+ // ensure that we have a tree column
+ if (!aTreeElement.getElementsByTagName('treecol').length)
+ {
+ var navcols = document.createElement('treecols');
+ var navcol = document.createElement('treecol');
+ navcol.setAttribute('id', 'navtreecol');
+ navcol.setAttribute('primary', true);
+ navcol.setAttribute('flex', 1);
+ navcol.setAttribute('hideheader', true);
+ navcols.appendChild(navcol);
+ aTreeElement.appendChild(navcols);
+ aTreeElement.setAttribute('hidecolumnpicker', true);
+ }
+
+ // add the class "prefnavtree", so that themes can set defaults
+ aTreeElement.className += ' prefnavtree';
+
+ // Do some magic with the treeitem ingredient:
+ // - if it has a label attribute but no treerow child,
+ // generate a treerow with a treecell child with that label
+ // - if it has a prefpane attribute, tie it to that panel
+ // - if still no panel found and a url attribute is present,
+ // autogenerate the prefpane and connect to it
+ var treeitems = aTreeElement.getElementsByTagName('treeitem');
+ for (var i = 0; i < treeitems.length; ++i)
+ {
+ var node = treeitems[i];
+ var label = node.getAttribute('label');
+ if (label)
+ {
+ // autocreate the treecell?
+ var row = node.firstChild;
+ while (row && row.nodeName != 'treerow')
+ row = row.nextSibling;
+ if (!row)
+ {
+ var itemrow = document.createElement('treerow');
+ var itemcell = document.createElement('treecell');
+ itemcell.setAttribute('label', label);
+ itemrow.appendChild(itemcell);
+ node.appendChild(itemrow);
+ }
+ }
+ var paneID = node.getAttribute('prefpane');
+ var pane = paneID && document.getElementById(paneID);
+ if (!pane && autopanes)
+ {
+ // if we have a url, create a <prefpane> for it
+ var paneURL = node.getAttribute('url');
+ if (paneURL)
+ {
+ // reuse paneID if present, else use the url as id
+ pane = document.createElement('prefpane');
+ pane.setAttribute('id', paneID || paneURL);
+ pane.setAttribute('src', paneURL);
+ pane.setAttribute('label', label || paneID || paneURL);
+ var helpTopic = node.getAttribute('helpTopic');
+ if (helpTopic)
+ {
+ pane.setAttribute('helpURI', 'chrome://communicator/locale/help/suitehelp.rdf');
+ pane.setAttribute('helpTopic', helpTopic);
+ }
+ // add pane to prefwindow
+ this.appendChild(pane);
+ }
+ }
+ node.prefpane = pane;
+ if (pane)
+ pane.preftreeitem = node;
+ // hide unused treeitems
+ node.hidden = !pane;
+ }
+
+ // now that the number of <prefpane>s is known, try to return early:
+ // don't bother with a navigation tree if we only have one prefpane
+ aTreeElement.hidden = (this.preferencePanes.length < 2);
+ if (aTreeElement.hidden)
+ return;
+ this._navigationTree = aTreeElement;
+
+ // append any still unreferenced <prefpane>s to the tree's top level
+ for (var j = 0; j < this.preferencePanes.length; ++j)
+ {
+ // toolkit believes in fancy pane resizing - we don't
+ var lostpane = this.preferencePanes[j];
+ lostpane.setAttribute('flex', 1);
+
+ if (!("preftreeitem" in lostpane))
+ {
+ var treebody = this._navigationTree
+ .getElementsByTagName('treechildren')[0];
+ var treeitem = document.createElement('treeitem');
+ var treerow = document.createElement('treerow');
+ var treecell = document.createElement('treecell');
+ var label = lostpane.getAttribute('label');
+ if (!label)
+ label = lostpane.getAttribute('id');
+ treecell.setAttribute('label', label);
+ treerow.appendChild(treecell);
+ treeitem.appendChild(treerow);
+ treebody.appendChild(treeitem);
+ treeitem.prefpane = lostpane;
+ lostpane.preftreeitem = treeitem;
+ }
+ }
+
+ // Some parts of the toolkit base binding's initialization code (like
+ // panel select events) "fire" before we get here. Thus, we may need
+ // to sync the tree manually now (again), if we added any panels or
+ // if toolkit failed to select one.
+ // (This is a loose copy from the toolkit ctor.)
+ var lastPane = this.lastSelected &&
+ document.getElementById(this.lastSelected);
+ if (!lastPane)
+ this.lastSelected = "";
+ if ("arguments" in window && window.arguments[0])
+ {
+ var initialPane = document.getElementById(window.arguments[0]);
+ if (initialPane && initialPane.nodeName == "prefpane")
+ {
+ this.currentPane = initialPane;
+ this.lastSelected = initialPane.id;
+ }
+ }
+ else if (lastPane)
+ this.currentPane = lastPane;
+ try
+ {
+ this.showPane(this.currentPane); // may need to load it first
+ this.syncTreeWithPane(this.currentPane, true);
+ }
+ catch (e)
+ {
+ dump('***** broken prefpane: ' + this.currentPane.id + '\n' + e + '\n');
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- don't do any fancy animations -->
+ <property name="_shouldAnimate" onget="return false;"/>
+
+ <method name="setPaneTitle">
+ <parameter name="aPaneElement"/>
+ <body>
+#ifndef XP_MACOSX
+ <![CDATA[
+ // show pane title, if given
+ var paneHeader = document.getAnonymousElementByAttribute(this, 'anonid', 'paneHeader');
+ var paneHeaderLabel = '';
+ if (aPaneElement)
+ paneHeaderLabel = aPaneElement.getAttribute('label');
+ paneHeader.hidden = !paneHeaderLabel;
+ if (!paneHeader.hidden)
+ paneHeader.setAttribute('title', paneHeaderLabel);
+ ]]>
+#endif
+ </body>
+ </method>
+
+ <method name="syncPaneWithTree">
+ <parameter name="aTreeIndex"/>
+ <body>
+ <![CDATA[
+ var pane = null;
+ if ((this._navigationTree) && (aTreeIndex >= 0))
+ {
+ // load the prefpane associated with this treeitem
+ var treeitem = this._navigationTree.contentView
+ .getItemAtIndex(aTreeIndex);
+ if ('prefpane' in treeitem)
+ {
+ pane = treeitem.prefpane;
+ if (pane && (this.currentPane != pane))
+ {
+ try
+ {
+ this.showPane(pane); // may need to load it first
+ }
+ catch (e)
+ {
+ dump('***** broken prefpane: ' + pane.id + '\n' + e + '\n');
+ pane = null;
+ }
+ }
+ }
+ }
+ // don't show broken panels
+ this._paneDeck.hidden = (pane == null);
+ this.setPaneTitle(pane);
+ ]]>
+ </body>
+ </method>
+
+ <method name="syncTreeWithPane">
+ <parameter name="aPane"/>
+ <parameter name="aExpand"/>
+ <body>
+ <![CDATA[
+ if (this._navigationTree && aPane)
+ {
+ if ('preftreeitem' in aPane)
+ {
+ // make sure the treeitem is visible
+ var container = aPane.preftreeitem;
+ if (!aExpand)
+ container = container.parentNode.parentNode;
+ while (container != this._navigationTree)
+ {
+ container.setAttribute('open', true);
+ container = container.parentNode.parentNode;
+ }
+
+ // mark selected pane in navigation tree
+ var index = this._navigationTree.contentView
+ .getIndexOfItem(aPane.preftreeitem);
+ this._navigationTree.view.selection.select(index);
+ }
+ }
+ this.setPaneTitle(aPane);
+ if (this.getAttribute("overflow") != "auto")
+ {
+ if (this.scrollHeight > window.innerHeight)
+ window.innerHeight = this.scrollHeight;
+ if (this.scrollWidth > window.innerWidth)
+ window.innerWidth = this.scrollWidth;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- copied from contextHelp.js
+ Opens up the Help Viewer with the specified topic and helpFileURI. -->
+ <method name="openHelp">
+ <parameter name="topic"/>
+ <parameter name="helpFileURI"/>
+ <body>
+ <![CDATA[
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.prompt.alert(this.window, "Help", "Help information is not currently available.");
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="dialoghelp">
+ <![CDATA[
+ this.openHelp(this.currentPane.helpTopic, this.currentPane.getAttribute("helpURI"));
+ ]]>
+ </handler>
+ <handler event="select">
+ <![CDATA[
+ // navigation tree select or deck change?
+ var target = event.originalTarget;
+ if (target == this._navigationTree)
+ {
+ this.syncPaneWithTree(target.currentIndex);
+ }
+ else if (target == this._paneDeck)
+ {
+ // deck.selectedIndex is a string!
+ var pane = this.preferencePanes[Number(target.selectedIndex)];
+ this.syncTreeWithPane(pane, false);
+ }
+ ]]>
+ </handler>
+
+ <handler event="paneload">
+ <![CDATA[
+ // panes may load asynchronously,
+ // so we have to "late-sync" those to our navigation tree
+ this.syncTreeWithPane(event.originalTarget, false);
+ ]]>
+ </handler>
+
+ <handler event="keypress" key="&focusSearch.key;" modifiers="accel">
+ <![CDATA[
+ var searchBox = this.currentPane.getElementsByAttribute("type", "search")[0];
+ if (searchBox)
+ {
+ searchBox.focus();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="prefpane"
+ extends="chrome://global/content/bindings/preferences.xml#prefpane">
+ <resources>
+ <stylesheet src="chrome://communicator/skin/preferences.css"/>
+ </resources>
+
+ <handlers>
+ <handler event="paneload">
+ <![CDATA[
+ // Since all <prefpane>s now share the same global document, their
+ // <script>s might clash. Thus we expect the "script" attribute to
+ // contain a whitespace delimited list of script files to be loaded
+ // into the <prefpane>'s context.
+ var subScriptLoader = null;
+ try
+ {
+ subScriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader);
+ }
+ catch (e)
+ {
+ var msg = 'prefpane.paneload: no mozIJSSubScriptLoader!\n' + e;
+ Components.utils.reportError(msg);
+ return;
+ }
+
+ // list of scripts to load
+ var scripts = this.getAttribute('script').match(/\S+/g);
+ if (!scripts)
+ return;
+ var count = scripts.length;
+ for (var i = 0; i < count; ++i)
+ {
+ var script = scripts[i];
+ if (script)
+ {
+ try
+ {
+ subScriptLoader.loadSubScript(script, this);
+ }
+ catch (e)
+ {
+ var msg = 'prefpane.paneload: loadSubScript("'
+ + script + '") failed:\n' + e;
+ Components.utils.reportError(msg);
+ }
+ }
+ }
+
+ // if we have a Startup method, call it
+ if ('Startup' in this)
+ this.Startup();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/xpfe/components/preferences/jar.mn b/xpfe/components/preferences/jar.mn
new file mode 100644
index 000000000..9b1002278
--- /dev/null
+++ b/xpfe/components/preferences/jar.mn
@@ -0,0 +1,56 @@
+#filter substitution
+# 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/.
+
+comm.jar:
+* content/communicator/bindings/prefwindow.xml (content/prefwindow.xml)
+ content/communicator/pref/preferences.xul (content/preferences.xul)
+ content/communicator/pref/preferences.js (content/preferences.js)
+ content/communicator/pref/prefpanels.css (content/prefpanels.css)
+ content/communicator/pref/prefpanels.xml (content/prefpanels.xml)
+ content/communicator/pref/pref-advanced.xul (content/pref-advanced.xul)
+ content/communicator/pref/pref-http.js (content/pref-http.js)
+ content/communicator/pref/pref-http.xul (content/pref-http.xul)
+ content/communicator/pref/pref-masterpass.js (content/pref-masterpass.js)
+ content/communicator/pref/pref-masterpass.xul (content/pref-masterpass.xul)
+ content/communicator/pref/pref-proxies.js (content/pref-proxies.js)
+ content/communicator/pref/pref-proxies.xul (content/pref-proxies.xul)
+ content/communicator/pref/pref-proxies-advanced.xul (content/pref-proxies-advanced.xul)
+ content/communicator/pref/pref-applications.xul (content/pref-applications.xul)
+* content/communicator/pref/pref-applications.js (content/pref-applications.js)
+ content/communicator/pref/pref-applicationManager.js (content/pref-applicationManager.js)
+ content/communicator/pref/pref-applicationManager.xul (content/pref-applicationManager.xul)
+ content/communicator/pref/pref-download.js (content/pref-download.js)
+ content/communicator/pref/pref-download.xul (content/pref-download.xul)
+ content/communicator/pref/pref-smartupdate.js (content/pref-smartupdate.js)
+ content/communicator/pref/pref-smartupdate.xul (content/pref-smartupdate.xul)
+
+pippki.jar:
+ content/pippki/pref-certs.js (content/pref-certs.js)
+ content/pippki/pref-certs.xul (content/pref-certs.xul)
+ content/pippki/pref-ssl.js (content/pref-ssl.js)
+ content/pippki/pref-ssl.xul (content/pref-ssl.xul)
+
+en-US.jar:
+ locale/en-US/communicator/pref/prefutilities.dtd (locale/prefutilities.dtd)
+ locale/en-US/communicator/pref/prefutilities.properties (locale/prefutilities.properties)
+ locale/en-US/communicator/pref/preferences.dtd (locale/preferences.dtd)
+ locale/en-US/communicator/pref/pref-advanced.dtd (locale/pref-advanced.dtd)
+ locale/en-US/communicator/pref/pref-http.dtd (locale/pref-http.dtd)
+ locale/en-US/communicator/pref/pref-masterpass.dtd (locale/pref-masterpass.dtd)
+ locale/en-US/communicator/pref/pref-proxies.dtd (locale/pref-proxies.dtd)
+ locale/en-US/communicator/pref/pref-proxies-advanced.dtd (locale/pref-proxies-advanced.dtd)
+ locale/en-US/communicator/pref/pref-applications.dtd (locale/pref-applications.dtd)
+ locale/en-US/communicator/pref/pref-applications.properties (locale/pref-applications.properties)
+ locale/en-US/communicator/pref/pref-applicationManager.dtd (locale/pref-applicationManager.dtd)
+ locale/en-US/communicator/pref/pref-applicationManager.properties (locale/pref-applicationManager.properties)
+ locale/en-US/communicator/pref/pref-download.dtd (locale/pref-download.dtd)
+ locale/en-US/communicator/pref/pref-smartupdate.dtd (locale/pref-smartupdate.dtd)
+
+ locale/en-US/pippki/pref-certs.dtd (locale/pref-certs.dtd)
+ locale/en-US/pippki/pref-ssl.dtd (locale/pref-ssl.dtd)
+
+classic.jar:
+ skin/classic/communicator/preferences.css (skin/preferences.css)
+ skin/classic/communicator/prefpanels.css (skin/prefpanels.css) \ No newline at end of file
diff --git a/xpfe/components/preferences/locale/pref-advanced.dtd b/xpfe/components/preferences/locale/pref-advanced.dtd
new file mode 100644
index 000000000..79e8fb92d
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-advanced.dtd
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+
+<!--LOCALIZATION NOTE : FILE 'Advanced' prefs settings -->
+<!ENTITY pref.advanced.title "Additional Settings">
+<!ENTITY pref.advanced.description "There are no settings here.">
diff --git a/xpfe/components/preferences/locale/pref-applicationManager.dtd b/xpfe/components/preferences/locale/pref-applicationManager.dtd
new file mode 100644
index 000000000..86883d0e3
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-applicationManager.dtd
@@ -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/. -->
+
+<!ENTITY appManager.title "Application details">
+<!ENTITY appManager.style "width: 40ch; min-height: 20em;">
+<!ENTITY remove.label "Remove">
+<!ENTITY remove.accesskey "R">
diff --git a/xpfe/components/preferences/locale/pref-applicationManager.properties b/xpfe/components/preferences/locale/pref-applicationManager.properties
new file mode 100644
index 000000000..0d35bd49f
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-applicationManager.properties
@@ -0,0 +1,10 @@
+# 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/.
+
+descriptionHandleProtocol=The following applications can be used to handle %S links
+descriptionHandleWebFeeds=The following applications can be used to handle Web Feeds
+descriptionHandleFile=The following applications can be used to handle %S content
+
+descriptionWebApp=This web application is hosted at:
+descriptionLocalApp=This application is located at:
diff --git a/xpfe/components/preferences/locale/pref-applications.dtd b/xpfe/components/preferences/locale/pref-applications.dtd
new file mode 100644
index 000000000..ca9828f71
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-applications.dtd
@@ -0,0 +1,14 @@
+<!-- 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/. -->
+
+<!--LOCALIZATION NOTE : FILE The Applications prefs dialog -->
+<!ENTITY pref.applications.title "Helper Applications">
+
+<!ENTITY typeColumn.label "Content Type">
+<!ENTITY typeColumn.accesskey "T">
+
+<!ENTITY actionColumn2.label "Action">
+<!ENTITY actionColumn2.accesskey "A">
+
+<!ENTITY search.placeholder "Search Types and Actions">
diff --git a/xpfe/components/preferences/locale/pref-applications.properties b/xpfe/components/preferences/locale/pref-applications.properties
new file mode 100644
index 000000000..48098c6bb
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-applications.properties
@@ -0,0 +1,34 @@
+# 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/.
+
+#### Applications
+
+fileEnding=%S file
+saveFile=Save File
+
+# LOCALIZATION NOTE (useApp, useDefault): %S = Application name
+useApp=Use %S
+useDefault=Use %S (default)
+
+useOtherApp=Use other…
+fpTitleChooseApp=Select Helper Application
+manageApp=Application Details…
+webFeed=Web Feed
+videoPodcastFeed=Video Podcast
+audioPodcastFeed=Podcast
+alwaysAsk=Always ask
+
+# LOCALIZATION NOTE (usePluginIn):
+# %1$S = plugin name (for example "QuickTime Plugin-in 7.2")
+# %2$S = brandShortName from brand.properties (for example "Minefield")
+usePluginIn=Use %S (in %S)
+
+# LOCALIZATION NOTE (previewInApp, addNewsBlogsInApp): %S = brandShortName
+previewInApp=Preview in %S
+addNewsBlogsInApp=Subscribe in %S
+
+# LOCALIZATION NOTE (typeDescriptionWithType):
+# %1$S = type description (for example "Portable Document Format")
+# %2$S = type (for example "application/pdf")
+typeDescriptionWithType=%S (%S)
diff --git a/xpfe/components/preferences/locale/pref-certs.dtd b/xpfe/components/preferences/locale/pref-certs.dtd
new file mode 100644
index 000000000..a29ee665a
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-certs.dtd
@@ -0,0 +1,30 @@
+<!-- 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/. -->
+
+<!ENTITY managecerts.caption "Manage Certificates">
+<!ENTITY managecerts.text "Use the Certificate Manager to manage your personal certificates, as well as those of other people and certificate authorities.">
+<!ENTITY managecerts.button "Manage Certificates…">
+<!ENTITY managecerts.accesskey "M">
+<!ENTITY managedevices.caption "Manage Security Devices">
+<!ENTITY managedevices.text "Use this button to manage your security devices, such as smart cards.">
+<!ENTITY managedevices.button "Manage Security Devices…">
+<!ENTITY managedevices.accesskey "S">
+
+<!ENTITY ssl.label "SSL">
+
+<!ENTITY pref.certs.title "Certificates">
+<!ENTITY certs.label "Certificates">
+
+<!ENTITY validation.ocsp.caption "OCSP">
+<!ENTITY enableOCSP.label "Use the Online Certificate Status Protocol (OCSP) to confirm the current validity of certificates">
+<!ENTITY enableOCSP.accesskey "U">
+<!ENTITY validation.requireOCSP.description "When an OCSP server connection fails, treat the certificate as invalid">
+<!ENTITY validation.requireOCSP.accesskey "W">
+
+<!ENTITY SSLClientAuthMethod.caption "Client Certificate Selection">
+<!ENTITY certselect.description "Decide how &brandFullName; selects a security certificate to present to websites that require one:">
+<!ENTITY certselect.auto "Select Automatically">
+<!ENTITY certselect.auto.accesskey "A">
+<!ENTITY certselect.ask "Ask Every Time">
+<!ENTITY certselect.ask.accesskey "E"> \ No newline at end of file
diff --git a/xpfe/components/preferences/locale/pref-download.dtd b/xpfe/components/preferences/locale/pref-download.dtd
new file mode 100644
index 000000000..bb79a45cc
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-download.dtd
@@ -0,0 +1,40 @@
+<!-- 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/. -->
+
+<!ENTITY pref.download.title "Downloads">
+
+<!ENTITY downloadBehavior.label "When starting a download">
+<!ENTITY doNothing.label "Don't open anything">
+<!ENTITY doNothing.accesskey "D">
+<!ENTITY openProgressDialog.label "Open a progress dialog">
+<!ENTITY openProgressDialog.accesskey "O">
+<!ENTITY openDM.label "Open the download manager">
+<!ENTITY openDM.accesskey "m">
+<!ENTITY flashWhenOpen.label "Just flash the download manager if it is already open">
+<!ENTITY flashWhenOpen.accesskey "f">
+
+<!ENTITY downloadLocation.label "When saving a file">
+<!ENTITY saveTo.label "Save files to">
+<!ENTITY saveTo.accesskey "v">
+<!ENTITY chooseDownloadFolder.label "Choose Folder…">
+<!ENTITY chooseDownloadFolder.accesskey "C">
+<!ENTITY alwaysAsk.label "Always ask me where to save files">
+<!ENTITY alwaysAsk.accesskey "A">
+
+<!ENTITY downloadHistory.label "Download history">
+<!ENTITY removeEntries.label "Remove download entries">
+<!ENTITY removeEntries.accesskey "R">
+<!ENTITY whenCompleted.label "When they have completed">
+<!ENTITY whenQuittingApp.label "When quitting &brandShortName;">
+<!ENTITY neverRemove.label "Never">
+
+<!ENTITY finishedBehavior.label "When a download completes">
+<!ENTITY playSound.label "Play a sound">
+<!ENTITY playSound.accesskey "P">
+<!ENTITY showAlert.label "Show an alert">
+<!ENTITY showAlert.accesskey "S">
+<!ENTITY browse.label "Browse…">
+<!ENTITY browse.accesskey "B">
+<!ENTITY playButton.label "Play">
+<!ENTITY playButton.accesskey "l">
diff --git a/xpfe/components/preferences/locale/pref-http.dtd b/xpfe/components/preferences/locale/pref-http.dtd
new file mode 100644
index 000000000..e51ddd541
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-http.dtd
@@ -0,0 +1,27 @@
+<!-- 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/. -->
+
+<!ENTITY pref.http.title "Networking &amp; Security">
+
+<!-- Network-->
+<!ENTITY prefDirect.label "Direct Connection Options">
+<!ENTITY prefProxy.label "Proxy Connection Options">
+<!ENTITY prefEnableHTTP10.label "Use HTTP 1.0">
+<!ENTITY prefEnableHTTP10.accesskey "U">
+<!ENTITY prefEnableHTTP10Proxy.accesskey "S">
+<!ENTITY prefEnableHTTP11.label "Use HTTP 1.1">
+<!ENTITY prefEnableHTTP11.accesskey "E">
+<!ENTITY prefEnableHTTP11Proxy.accesskey "T">
+<!ENTITY prefEnablePipelining.label "Enable Pipelining">
+<!ENTITY prefEnablePipelining.accesskey "P">
+<!ENTITY prefEnablePipeliningProxy.accesskey "N">
+<!ENTITY prefPara "HTTP connections may be fine-tuned using these options to enhance either performance or compatibility. Some proxy servers, for example, are known to require HTTP/1.0 (see the release notes for details).">
+<!ENTITY prefPipeWarning "WARNING: pipelining is an experimental feature, designed to improve page-load performance, that is unfortunately not well supported by some web servers and proxies.">
+<!ENTITY prefUseragent.label "User Agent String">
+<!ENTITY prefGeckoCompat.label "Advertise Gecko compatibility">
+<!ENTITY prefGeckoCompat.accesskey "G">
+<!ENTITY prefFirefoxCompat.label "Advertise Firefox compatibility">
+<!ENTITY prefFirefoxCompat.accesskey "F">
+<!ENTITY prefCompatWarning "WARNING: Changing these settings may result in websites or services not working properly.">
+
diff --git a/xpfe/components/preferences/locale/pref-masterpass.dtd b/xpfe/components/preferences/locale/pref-masterpass.dtd
new file mode 100644
index 000000000..de71c1c91
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-masterpass.dtd
@@ -0,0 +1,33 @@
+<!-- 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/. -->
+
+<!ENTITY pref.masterpass.title "Passwords">
+
+<!ENTITY signonHeader.caption "Password Manager">
+<!ENTITY signonDescription.label "Password Manager stores your login information for password-protected websites, mail servers, and news servers, and enters the information automatically when needed.">
+
+<!ENTITY signonEnabled.label "Remember passwords">
+<!ENTITY signonEnabled.accesskey "R">
+<!ENTITY viewSignons.label "Manage Stored Passwords">
+<!ENTITY viewSignons.accesskey "M">
+
+<!ENTITY managepassword.caption "Master Password Timeout">
+<!ENTITY managepassword.text "&brandShortName; will ask for your master password:">
+<!ENTITY managepassword.askfirsttime "The first time it is needed">
+<!ENTITY managepassword.askfirsttime.accesskey "T">
+<!ENTITY managepassword.askeverytime "Every time it is needed">
+<!ENTITY managepassword.askeverytime.accesskey "E">
+<!ENTITY managepassword.asktimeout "If it has not been used for ">
+<!ENTITY managepassword.asktimeout.accesskey "n">
+<!ENTITY managepassword.timeout.unit "minutes or longer">
+
+<!ENTITY changepassword.caption "Master Password">
+<!ENTITY changepassword.text "Your master password protects sensitive information such as web passwords and certificates.">
+<!ENTITY changepassword.button "Set or Change Master Password…">
+<!ENTITY changepassword.accesskey "C">
+
+<!ENTITY resetpassword.caption "Reset Master Password">
+<!ENTITY resetpassword.text "If you reset your master password, all of your stored passwords, form data, personal certificates, and private keys will be lost.">
+<!ENTITY resetpassword.button "Reset Password">
+<!ENTITY resetpassword.accesskey "R">
diff --git a/xpfe/components/preferences/locale/pref-proxies-advanced.dtd b/xpfe/components/preferences/locale/pref-proxies-advanced.dtd
new file mode 100644
index 000000000..319aacb25
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-proxies-advanced.dtd
@@ -0,0 +1,32 @@
+<!-- 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/. -->
+
+<!--LOCALIZATION NOTE : FILE The Advanced Proxy Preferences dialog -->
+<!ENTITY pref.proxies.advanced.title "Advanced Proxy Preferences">
+<!ENTITY protocols.caption "Protocol-Specific Proxies">
+<!ENTITY protocols.description "Normally the same proxy can handle all protocols listed here.">
+<!ENTITY http.label "HTTP Proxy:">
+<!ENTITY http.accesskey "x">
+<!ENTITY ssl.label "SSL Proxy:">
+<!ENTITY ssl.accesskey "L">
+<!ENTITY ftp.label "FTP Proxy:">
+<!ENTITY ftp.accesskey "F">
+<!ENTITY reuseProxy.label "Use HTTP Proxy settings for all protocols">
+<!ENTITY reuseProxy.accesskey "U">
+<!ENTITY port.label "Port:">
+<!ENTITY HTTPPort.accesskey "P">
+<!ENTITY SSLPort.accesskey "o">
+<!ENTITY FTPPort.accesskey "r">
+
+<!ENTITY socks.caption "Generic Proxy">
+<!ENTITY socks.description "A SOCKS proxy is a generic proxy sometimes used in corporate or similar environments.">
+<!ENTITY socks.label "SOCKS Proxy:">
+<!ENTITY socks.accesskey "S">
+<!ENTITY socks4.label "SOCKS v4">
+<!ENTITY socks4.accesskey "C">
+<!ENTITY socks5.label "SOCKS v5">
+<!ENTITY socks5.accesskey "K">
+<!ENTITY socksRemoteDNS.label "Use for resolving hostnames (recommended for SOCKS v5)">
+<!ENTITY socksRemoteDNS.accesskey "e">
+<!ENTITY SOCKSport.accesskey "t">
diff --git a/xpfe/components/preferences/locale/pref-proxies.dtd b/xpfe/components/preferences/locale/pref-proxies.dtd
new file mode 100644
index 000000000..01baaefd7
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-proxies.dtd
@@ -0,0 +1,31 @@
+<!-- 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/. -->
+
+<!-- extracted from content/pref-proxies.xul -->
+
+<!--LOCALIZATION NOTE : FILE The Proxies preferences dialog -->
+<!ENTITY pref.proxies.title "Proxies">
+<!ENTITY pref.proxies.desc "A Proxy is a network service that can filter and speed up your Internet connection.">
+<!ENTITY proxyTitle.label "Configure Proxies to Access the Internet">
+<!ENTITY directTypeRadio.label "Direct connection to the Internet">
+<!ENTITY directTypeRadio.accesskey "D">
+<!ENTITY systemTypeRadio.label "Use system proxy settings">
+<!ENTITY systemTypeRadio.accesskey "U">
+<!ENTITY manualTypeRadio.label "Manual proxy configuration:">
+<!ENTITY manualTypeRadio.accesskey "M">
+<!ENTITY wpadTypeRadio.label "Automatically discover the proxy configuration">
+<!ENTITY wpadTypeRadio.accesskey "A">
+<!ENTITY autoTypeRadio.label "Automatic proxy configuration URL:">
+<!ENTITY autoTypeRadio.accesskey "c">
+<!ENTITY reload.label "Reload">
+<!ENTITY reload.accesskey "R">
+<!ENTITY http.label "Proxy:">
+<!ENTITY http.accesskey "P">
+<!ENTITY port.label "Port:">
+<!ENTITY HTTPPort.accesskey "o">
+<!ENTITY advanced.label "Advanced…">
+<!ENTITY advanced.accesskey "v">
+<!ENTITY noproxy.label "No Proxy for:">
+<!ENTITY noproxy.accesskey "N">
+<!ENTITY noproxyExplain.label "Example: .mozilla.org, .net.nz, 192.168.1.0/24">
diff --git a/xpfe/components/preferences/locale/pref-smartupdate.dtd b/xpfe/components/preferences/locale/pref-smartupdate.dtd
new file mode 100644
index 000000000..22845003c
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-smartupdate.dtd
@@ -0,0 +1,31 @@
+<!-- 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/. -->
+
+<!--LOCALIZATION NOTE : FILE UI for Software Updates prefs -->
+<!ENTITY pref.smartUpdate.title "Software Installation">
+<!ENTITY addOnsTitle.label "Add-ons">
+<!ENTITY addOnsAllow.label "Allow websites to install add-ons and updates">
+<!ENTITY addOnsAllow.accesskey "b">
+<!ENTITY allowedSitesLink.label "Allowed Websites">
+<!ENTITY autoAddOnsUpdates.label "Automatically check for updates">
+<!ENTITY autoAddOnsUpdates.accesskey "o">
+<!ENTITY daily.label "daily">
+<!ENTITY addOnsDaily.accesskey "d">
+<!ENTITY weekly.label "weekly">
+<!ENTITY addOnsWeekly.accesskey "k">
+<!ENTITY addOnsModeAutomatic.label "Automatically download and install the updates">
+<!ENTITY addOnsModeAutomatic.accesskey "m">
+<!ENTITY enablePersonalized.label "Personalize add-on recommendations">
+<!ENTITY enablePersonalized.accesskey "P">
+<!ENTITY addonManagerLink.label "Manage Add-ons">
+
+<!ENTITY appUpdates.caption "&brandShortName;">
+<!ENTITY autoAppUpdates.label "Automatically check for updates">
+<!ENTITY autoAppUpdates.accesskey "t">
+<!ENTITY appDaily.accesskey "a">
+<!ENTITY appWeekly.accesskey "e">
+<!ENTITY appModeAutomatic.label "Automatically download and install the update">
+<!ENTITY appModeAutomatic.accesskey "u">
+<!ENTITY updateHistoryButton.label "Show Update History…">
+<!ENTITY updateHistoryButton.accesskey "S">
diff --git a/xpfe/components/preferences/locale/pref-ssl.dtd b/xpfe/components/preferences/locale/pref-ssl.dtd
new file mode 100644
index 000000000..e33c42878
--- /dev/null
+++ b/xpfe/components/preferences/locale/pref-ssl.dtd
@@ -0,0 +1,32 @@
+<!-- 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/. -->
+
+<!ENTITY SSLTLSProtocolVersions.caption "Transport Layer Security">
+<!ENTITY SSLMixedContent.caption "Mixed Content">
+
+<!ENTITY pref.ssltls.title "Encryption">
+<!ENTITY limit.description "You can restrict which encryption protocols to use for secure connections. Choose a single version or a contiguous range of versions.">
+<!ENTITY limit.enable.label "Enable:">
+<!ENTITY limit.tls10.label "TLS 1.0">
+<!ENTITY limit.tls10.accesskey "T">
+<!ENTITY limit.tls11.label "TLS 1.1">
+<!ENTITY limit.tls11.accesskey "1">
+<!ENTITY limit.tls12.label "TLS 1.2">
+<!ENTITY limit.tls12.accesskey "2">
+<!ENTITY limit.tls13.label "TLS 1.3">
+<!ENTITY limit.tls13.accesskey "3">
+
+<!ENTITY mixed.description "Encrypted pages may contain unencrypted content that is vulnerable to eavesdropping or forgery. &brandFullName; can detect and block it:">
+<!ENTITY warn.mixedactivecontent "Warn me when encrypted pages contain insecure content">
+<!ENTITY warn.mixedactivecontent.accesskey "W">
+<!ENTITY block.activecontent "Don't load insecure content on encrypted pages">
+<!ENTITY block.activecontent.accesskey "D">
+<!ENTITY warn.mixeddisplaycontent "Warn me when encrypted pages contain other types of mixed content">
+<!ENTITY warn.mixeddisplaycontent.accesskey "c">
+<!ENTITY block.displaycontent "Don't load other types of mixed content on encrypted pages">
+<!ENTITY block.displaycontent.accesskey "m">
+
+<!ENTITY OpEnc.label "Opportunistic Encryption">
+<!ENTITY enableUIROpEnc.label "Enable Upgrade Insecure Requests">
+<!ENTITY enableAltSvcOpEnc.label "Enable HTTP Alternative Services"> \ No newline at end of file
diff --git a/xpfe/components/preferences/locale/preferences.dtd b/xpfe/components/preferences/locale/preferences.dtd
new file mode 100644
index 000000000..a76f4e1f4
--- /dev/null
+++ b/xpfe/components/preferences/locale/preferences.dtd
@@ -0,0 +1,40 @@
+<!-- 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/. -->
+
+<!--LOCALIZATION NOTE (.label): Preferences categories that appear on the left of the preferences dialog -->
+<!ENTITY prefWindow.title "Preferences">
+<!ENTITY prefWindow.size "width: 115ch; height: 43em;">
+<!ENTITY categoryHeader "Category">
+
+<!ENTITY appear.label "Appearance">
+<!ENTITY content.label "Content">
+<!ENTITY fonts.label "Fonts">
+<!ENTITY colors.label "Colors">
+
+<!ENTITY history.label "History">
+<!ENTITY languages.label "Languages &amp; Spelling">
+<!ENTITY applications.label "Helper Applications">
+<!ENTITY download.label "Downloads">
+
+<!ENTITY privacy.label "Privacy">
+
+<!ENTITY privatedata.label "Private Data">
+<!ENTITY cookies.label "Cookies">
+<!ENTITY masterpass.label "Passwords">
+<!ENTITY ssltls.label "Encryption">
+<!ENTITY certs.label "Certificates">
+
+<!ENTITY sync.label "Sync">
+
+<!ENTITY advance.label "Additional Settings">
+<!ENTITY advancedConfig.label "Advanced Configuration">
+<!ENTITY scriptsAndWindows.label "Scripts &amp; Plugins">
+<!ENTITY cache.label "Cache">
+<!ENTITY offlineApps.label "Offline Apps">
+<!ENTITY proxies.label "Proxies">
+<!ENTITY httpnetworking.label "Networking &amp; Security">
+<!ENTITY smart.label "Software Installation">
+<!ENTITY mousewheel.label "Mouse Wheel">
+
+<!ENTITY focusSearch.key "f">
diff --git a/xpfe/components/preferences/locale/prefutilities.dtd b/xpfe/components/preferences/locale/prefutilities.dtd
new file mode 100644
index 000000000..9c6f2b852
--- /dev/null
+++ b/xpfe/components/preferences/locale/prefutilities.dtd
@@ -0,0 +1,40 @@
+<!-- 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/. -->
+
+<!ENTITY FallbackCharset.auto "Default for Current Locale">
+<!-- LOCALIZATION NOTE (FallbackCharset.arabic):
+ Translate "Arabic" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.arabic "Arabic">
+<!ENTITY FallbackCharset.baltic "Baltic">
+<!ENTITY FallbackCharset.ceiso "Central European, ISO">
+<!ENTITY FallbackCharset.cewindows "Central European, Microsoft">
+<!-- LOCALIZATION NOTE (FallbackCharset.simplified):
+ Translate "Chinese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.simplified "Chinese, Simplified">
+<!-- LOCALIZATION NOTE (FallbackCharset.traditional):
+ Translate "Chinese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.traditional "Chinese, Traditional">
+<!ENTITY FallbackCharset.cyrillic "Cyrillic">
+<!-- LOCALIZATION NOTE (FallbackCharset.greek):
+ Translate "Greek" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.greek "Greek">
+<!-- LOCALIZATION NOTE (FallbackCharset.hebrew):
+ Translate "Hebrew" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.hebrew "Hebrew">
+<!-- LOCALIZATION NOTE (FallbackCharset.japanese):
+ Translate "Japanese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.japanese "Japanese">
+<!-- LOCALIZATION NOTE (FallbackCharset.korean):
+ Translate "Korean" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.korean "Korean">
+<!-- LOCALIZATION NOTE (FallbackCharset.thai):
+ Translate "Thai" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.thai "Thai">
+<!-- LOCALIZATION NOTE (FallbackCharset.turkish):
+ Translate "Turkish" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.turkish "Turkish">
+<!-- LOCALIZATION NOTE (FallbackCharset.vietnamese):
+ Translate "Vietnamese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY FallbackCharset.vietnamese "Vietnamese">
+<!ENTITY FallbackCharset.other "Other (including Western European)">
diff --git a/xpfe/components/preferences/locale/prefutilities.properties b/xpfe/components/preferences/locale/prefutilities.properties
new file mode 100644
index 000000000..c56282e02
--- /dev/null
+++ b/xpfe/components/preferences/locale/prefutilities.properties
@@ -0,0 +1,34 @@
+# 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/.
+
+cachefolder=Choose Cache Folder
+#LOCALIZATION NOTE (%1$S) is the size and (%2$S) is the unit of disk space.
+cacheSizeInfo=Your cache is currently using %1$S %2$S of disk space.
+
+# Offline apps
+offlineAppSizeInfo=Your offline storage currently uses %1$S %2$S of disk space.
+offlineAppRemoveTitle=Remove offline website data
+offlineAppRemovePrompt=After removing this data, %S will not be available offline. Are you sure you want to remove this offline website?
+offlineAppRemoveConfirm=Remove offline data
+
+# LOCALIZATION NOTE: The next string is for the disk usage of the
+# offline application
+# e.g. offlineAppUsage : "50.23 MB"
+# %1$S = size (in bytes or megabytes, ...)
+# %2$S = unit of measure (bytes, KB, MB, ...)
+offlineAppUsage=%1$S %2$S
+
+choosehomepage=Choose Home Page
+downloadfolder=Choose a Download Folder
+desktopFolderName=Desktop
+downloadsFolderName=Downloads
+choosesound=Choose a sound
+
+SoundFiles=Sounds
+
+labelDefaultFont=Default (%font_family%)
+
+syncUnlink.title=Do you want to unlink your device?
+syncUnlink.label=This device will no longer be associated with your Sync account. All of your personal data, both on this device and in your Sync account, will remain intact.
+syncUnlinkConfirm.label=Unlink
diff --git a/xpfe/components/preferences/moz.build b/xpfe/components/preferences/moz.build
new file mode 100644
index 000000000..e0eb66aac
--- /dev/null
+++ b/xpfe/components/preferences/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/xpfe/components/preferences/skin/preferences.css b/xpfe/components/preferences/skin/preferences.css
new file mode 100644
index 000000000..4b039b4bd
--- /dev/null
+++ b/xpfe/components/preferences/skin/preferences.css
@@ -0,0 +1,16 @@
+/* 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/. */
+
+/* Styles used by all preference windows and panes of SeaMonkey */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* ::::: Main Window ::::: */
+
+prefwindow {
+ padding-top: 8px;
+ padding-bottom: 0px;
+ padding-inline-start: 8px;
+ padding-inline-end: 10px;
+}
diff --git a/xpfe/components/preferences/skin/prefpanels.css b/xpfe/components/preferences/skin/prefpanels.css
new file mode 100644
index 000000000..880e47cd0
--- /dev/null
+++ b/xpfe/components/preferences/skin/prefpanels.css
@@ -0,0 +1,89 @@
+/* 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/. */
+
+/* ==== prefpanels.css ==================================================
+ == Styles used by all preference panels in the Communicator suite.
+ ====================================================================== */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* ::::: Fonts ::::: */
+
+#sizeVar,
+#sizeMono {
+ width: 4em;
+}
+
+.prefpanel-font-list {
+ -moz-box-flex: 1;
+}
+
+/* checkbox which is disabled for changes but non-gray text (i.e., in effect) */
+
+checkbox.nogray-disabled[disabled="true"][nogray="true"] {
+ color: inherit;
+ text-shadow: inherit;
+}
+
+/* ::::: Applications ::::: */
+.handler-action > .menu-iconic-left {
+ /**
+ * Make the icons appear.
+ * Note: we display the icon box for every item whether or not it has an icon
+ * so the labels of all the items align vertically.
+ */
+ display: -moz-box;
+ min-width: 16px;
+}
+
+/* Set icons on app pane elements */
+
+.handler-action > .listcell-icon,
+.handler-type > .listcell-icon {
+ height: 16px;
+ width: 16px;
+}
+
+.handler-action[appHandlerIcon="app"] {
+ list-style-image: url("chrome://communicator/skin/icons/application.png");
+}
+
+.handler-action[appHandlerIcon="ask"] {
+ list-style-image: url("chrome://communicator/skin/icons/alwaysAsk.png");
+}
+
+.handler-action[appHandlerIcon="save"] {
+ list-style-image: url("chrome://communicator/skin/icons/save.png");
+}
+
+.handler-action[appHandlerIcon="feed"] {
+ list-style-image: url("chrome://communicator/skin/icons/feedIcon16.png");
+}
+
+.handler-action[appHandlerIcon="plugin"] {
+ list-style-image: url("chrome://communicator/skin/icons/plugin.png");
+}
+
+.handler-type[typeClass="unknown"] {
+ list-style-image: url("moz-icon://goat?size=16");
+}
+
+.handler-type[typeClass="webFeed"],
+.handler-type[typeClass="videoPodcastFeed"],
+.handler-type[typeClass="audioPodcastFeed"] {
+ list-style-image: url("chrome://communicator/skin/icons/feedIcon16.png");
+}
+
+/* ::::: Search ::::: */
+
+#engineList > .menulist-label-box > .menulist-icon {
+ height: 16px;
+ width: 16px;
+}
+
+/* ::::: Sync ::::: */
+
+#syncDesc {
+ padding: 0 12em;
+}
diff --git a/xpfe/components/profile/content/profileSelection.js b/xpfe/components/profile/content/profileSelection.js
new file mode 100644
index 000000000..2af63ccb1
--- /dev/null
+++ b/xpfe/components/profile/content/profileSelection.js
@@ -0,0 +1,350 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gProfileBundle;
+var gBrandBundle;
+var gProfileService;
+var gPromptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+var gProfileManagerMode = "selection";
+var gDialogParams = window.arguments[0]
+ .QueryInterface(Components.interfaces.nsIDialogParamBlock);
+
+function StartUp()
+{
+ gProfileBundle = document.getElementById("bundle_profile");
+ gBrandBundle = document.getElementById("bundle_brand");
+ if (gDialogParams.objects) {
+ document.documentElement.getButton("accept").setAttribute("label",
+ document.documentElement.getAttribute("buttonlabelstart"));
+ document.documentElement.getButton("cancel").setAttribute("label",
+ document.documentElement.getAttribute("buttonlabelexit"));
+ document.getElementById('intro').textContent =
+ document.getElementById('intro').getAttribute("start");
+ document.getElementById('offlineState').hidden = false;
+ gDialogParams.SetInt(0, 0);
+ }
+
+ gProfileService = Components.classes["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Components.interfaces.nsIToolkitProfileService);
+ var profileEnum = gProfileService.profiles;
+ var selectedProfile = null;
+ try {
+ selectedProfile = gProfileService.selectedProfile;
+ }
+ catch (ex) {
+ }
+ while (profileEnum.hasMoreElements()) {
+ AddItem(profileEnum.getNext().QueryInterface(Components.interfaces.nsIToolkitProfile),
+ selectedProfile);
+ }
+
+ var autoSelect = document.getElementById("autoSelect");
+ if (Services.prefs.getBoolPref("profile.manage_only_at_launch"))
+ autoSelect.hidden = true;
+ else
+ autoSelect.checked = gProfileService.startWithLastProfile;
+
+ DoEnabling();
+}
+
+// function : <profileSelection.js>::AddItem();
+// purpose : utility function for adding items to a tree.
+function AddItem(aProfile, aProfileToSelect)
+{
+ var tree = document.getElementById("profiles");
+ var treeitem = document.createElement("treeitem");
+ var treerow = document.createElement("treerow");
+ var treecell = document.createElement("treecell");
+ var treetip = document.getElementById("treetip");
+ var profileDir = gProfileService.getProfileByName(aProfile.name).rootDir;
+
+ treecell.setAttribute("label", aProfile.name);
+ treerow.appendChild(treecell);
+ treeitem.appendChild(treerow);
+ treeitem.setAttribute("tooltip", profileDir.path);
+ treetip.setAttribute("value", profileDir.path);
+ tree.lastChild.appendChild(treeitem);
+ treeitem.profile = aProfile;
+ if (aProfile == aProfileToSelect) {
+ var profileIndex = tree.view.getIndexOfItem(treeitem);
+ tree.view.selection.select(profileIndex);
+ tree.treeBoxObject.ensureRowIsVisible(profileIndex);
+ }
+}
+
+// function : <profileSelection.js>::AcceptDialog();
+// purpose : sets the current profile to the selected profile (user choice: "Start Mozilla")
+function AcceptDialog()
+{
+ var autoSelect = document.getElementById("autoSelect");
+ if (!autoSelect.hidden) {
+ gProfileService.startWithLastProfile = autoSelect.checked;
+ gProfileService.flush();
+ }
+
+ var profileTree = document.getElementById("profiles");
+ var selected = profileTree.view.getItemAtIndex(profileTree.currentIndex);
+
+ if (!gDialogParams.objects) {
+ var dirServ = Components.classes['@mozilla.org/file/directory_service;1']
+ .getService(Components.interfaces.nsIProperties);
+ var profD = dirServ.get("ProfD", Components.interfaces.nsIFile);
+ var profLD = dirServ.get("ProfLD", Components.interfaces.nsIFile);
+
+ if (selected.profile.rootDir.equals(profD) &&
+ selected.profile.localDir.equals(profLD))
+ return true;
+ }
+
+ try {
+ var profileLock = selected.profile.lock({});
+ gProfileService.selectedProfile = selected.profile;
+ gProfileService.defaultProfile = selected.profile;
+ gProfileService.flush();
+ if (gDialogParams.objects) {
+ gDialogParams.objects.insertElementAt(profileLock, 0, false);
+ gProfileService.startOffline = document.getElementById("offlineState").checked;
+ gDialogParams.SetInt(0, 1);
+ gDialogParams.SetString(0, selected.profile.name);
+ return true;
+ }
+ profileLock.unlock();
+ } catch (e) {
+ var brandName = gBrandBundle.getString("brandShortName");
+ var message = gProfileBundle.getFormattedString("dirLocked",
+ [brandName, selected.profile.name]);
+ gPromptService.alert(window, null, message);
+ return false;
+ }
+
+ // Although switching profile works by performing a restart internally,
+ // the user is quitting the old profile, so make it look like a quit.
+ var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Components.interfaces.nsISupportsPRBool);
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(cancelQuit, "quit-application-requested", null);
+ if (cancelQuit.data)
+ return false;
+
+ try {
+ var env = Components.classes["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ env.set("XRE_PROFILE_NAME", selected.profile.name);
+ env.set("XRE_PROFILE_PATH", selected.profile.rootDir.path);
+ env.set("XRE_PROFILE_LOCAL_PATH", selected.profile.localDir.path);
+ var app = Components.classes["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Components.interfaces.nsIAppStartup);
+ app.quit(app.eAttemptQuit | app.eRestart);
+ return true;
+ }
+ catch (e) {
+ env.set("XRE_PROFILE_NAME", "");
+ env.set("XRE_PROFILE_PATH", "");
+ env.set("XRE_PROFILE_LOCAL_PATH", "");
+ return false;
+ }
+}
+
+// invoke the createProfile Wizard
+function CreateProfileWizard()
+{
+ window.openDialog('chrome://mozapps/content/profile/createProfileWizard.xul',
+ '', 'centerscreen,chrome,modal,titlebar');
+}
+
+// update the display to show the additional profile
+function CreateProfile(aProfile)
+{
+ gProfileService.flush();
+ AddItem(aProfile, aProfile);
+}
+
+// rename the selected profile
+function RenameProfile()
+{
+ var profileTree = document.getElementById("profiles");
+ var selected = profileTree.view.getItemAtIndex(profileTree.currentIndex);
+ var profileName = selected.profile.name;
+ var newName = {value: profileName};
+ var dialogTitle = gProfileBundle.getString("renameProfileTitle");
+ var msg = gProfileBundle.getFormattedString("renameProfilePrompt", [profileName]);
+ if (gPromptService.prompt(window, dialogTitle, msg, newName, null, {value: 0}) &&
+ newName.value != profileName) {
+ if (!/\S/.test(newName.value)) {
+ gPromptService.alert(window,
+ gProfileBundle.getString("profileNameInvalidTitle"),
+ gProfileBundle.getString("profileNameEmpty"));
+ return false;
+ }
+
+ if (/([\\*:?<>|\/\"])/.test(newName.value)) {
+ gPromptService.alert(window,
+ gProfileBundle.getString("profileNameInvalidTitle"),
+ gProfileBundle.getFormattedString("invalidChar", [RegExp.$1]));
+ return false;
+ }
+
+ try {
+ gProfileService.getProfileByName(newName.value);
+ gPromptService.alert(window,
+ gProfileBundle.getString("profileExistsTitle"),
+ gProfileBundle.getString("profileExists"));
+ return false;
+ }
+ catch (e) {
+ }
+
+ selected.profile.name = newName.value;
+ gProfileService.flush();
+ selected.firstChild.firstChild.setAttribute("label", newName.value);
+ }
+}
+
+function ConfirmDelete()
+{
+ var profileTree = document.getElementById("profiles");
+ var selected = profileTree.view.getItemAtIndex(profileTree.currentIndex);
+ if (!selected.profile.rootDir.exists()) {
+ DeleteProfile(false);
+ return;
+ }
+
+ try {
+ var profileLock = selected.profile.lock({});
+ var dialogTitle = gProfileBundle.getString("deleteTitle");
+ var dialogText;
+
+ var path = selected.profile.rootDir.path;
+ dialogText = gProfileBundle.getFormattedString("deleteProfile", [path]);
+ var buttonPressed = gPromptService.confirmEx(window, dialogTitle, dialogText,
+ (gPromptService.BUTTON_TITLE_IS_STRING * gPromptService.BUTTON_POS_0) +
+ (gPromptService.BUTTON_TITLE_CANCEL * gPromptService.BUTTON_POS_1) +
+ (gPromptService.BUTTON_TITLE_IS_STRING * gPromptService.BUTTON_POS_2),
+ gProfileBundle.getString("dontDeleteFiles"), null,
+ gProfileBundle.getString("deleteFiles"), null, {value: 0});
+ profileLock.unlock();
+ if (buttonPressed != 1)
+ DeleteProfile(buttonPressed == 2);
+ } catch (e) {
+ var dialogTitle = gProfileBundle.getString("deleteTitle");
+ var brandName = gBrandBundle.getString("brandShortName");
+ var dialogText = gProfileBundle.getFormattedString("deleteLocked",
+ [brandName, selected.profile.name]);
+ gPromptService.alert(window, dialogTitle, dialogText);
+ }
+}
+
+// Delete the profile, with the delete flag set as per instruction above.
+function DeleteProfile(aDeleteFiles)
+{
+ var profileTree = document.getElementById("profiles");
+ var selected = profileTree.view.getItemAtIndex(profileTree.currentIndex);
+ var previous = profileTree.currentIndex && profileTree.currentIndex - 1;
+
+ try {
+ selected.profile.remove(aDeleteFiles);
+ gProfileService.flush();
+ selected.remove();
+
+ if (profileTree.view.rowCount != 0) {
+ profileTree.view.selection.select(previous);
+ profileTree.treeBoxObject.ensureRowIsVisible(previous);
+ }
+
+ // set the button state
+ DoEnabling();
+ }
+ catch (ex) {
+ dump("Exception during profile deletion.\n");
+ }
+}
+
+function SwitchProfileManagerMode()
+{
+ var captionLine;
+ var prattleIndex;
+
+ if (gProfileManagerMode == "selection") {
+ prattleIndex = 1;
+ captionLine = gProfileBundle.getString("manageTitle");
+
+ document.getElementById("profiles").focus();
+
+ // hide the manage profiles button...
+ document.documentElement.getButton("extra2").hidden = true;
+ gProfileManagerMode = "manager";
+ }
+ else {
+ prattleIndex = 0;
+ captionLine = gProfileBundle.getString("selectTitle");
+ gProfileManagerMode = "selection";
+ }
+
+ // swap deck
+ document.getElementById("prattle").selectedIndex = prattleIndex;
+
+ // change the title of the profile manager/selection window.
+ document.getElementById("header").setAttribute("description", captionLine);
+ document.title = captionLine;
+}
+
+// do button enabling based on tree selection
+function DoEnabling()
+{
+ var acceptButton = document.documentElement.getButton("accept");
+ var deleteButton = document.getElementById("deleteButton");
+ var renameButton = document.getElementById("renameButton");
+
+ var disabled = document.getElementById("profiles").view.selection.count == 0;
+ acceptButton.disabled = disabled;
+ deleteButton.disabled = disabled;
+ renameButton.disabled = disabled;
+}
+
+// handle key event on tree
+function HandleKeyEvent(aEvent)
+{
+ if (gProfileManagerMode != "manager")
+ return;
+
+ switch (aEvent.keyCode)
+ {
+ case KeyEvent.DOM_VK_BACK_SPACE:
+ case KeyEvent.DOM_VK_DELETE:
+ if (!document.getElementById("deleteButton").disabled)
+ ConfirmDelete();
+ break;
+ case KeyEvent.DOM_VK_F2:
+ if (!document.getElementById("renameButton").disabled)
+ RenameProfile();
+ }
+}
+
+function HandleClickEvent(aEvent)
+{
+ if (aEvent.button == 0 && aEvent.target.parentNode.view.selection.count != 0 && AcceptDialog()) {
+ window.close();
+ return true;
+ }
+
+ return false;
+}
+
+function HandleToolTipEvent(aEvent)
+{
+ var treeTip = document.getElementById("treetip");
+ var tree = document.getElementById("profiles");
+
+ var cell = tree.treeBoxObject.getCellAt(aEvent.clientX, aEvent.clientY);
+ if (cell.row < 0)
+ aEvent.preventDefault();
+ else
+ treeTip.label = tree.view.getItemAtIndex(cell.row).tooltip;
+}
diff --git a/xpfe/components/profile/content/profileSelection.xul b/xpfe/components/profile/content/profileSelection.xul
new file mode 100644
index 000000000..3fa211b85
--- /dev/null
+++ b/xpfe/components/profile/content/profileSelection.xul
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!--
+
+ 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://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/profile/profile.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % profileDTD SYSTEM "chrome://communicator/locale/profile/profileSelection.dtd">
+%profileDTD;
+]>
+
+<dialog id="profileWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&windowTitle.label;"
+ windowtype="mozilla:profileSelection"
+ orient="vertical"
+ style="width: 42em;"
+ buttons="accept,cancel,extra2"
+ buttonlabelaccept="&select.label;"
+ buttonlabelstart="&start.label;"
+ buttonlabelexit="&exit.label;"
+ buttonlabelextra2="&manage.label;"
+ buttonaccesskeyextra2="&manage.accesskey;"
+ ondialogaccept="return AcceptDialog();"
+ ondialogextra2="SwitchProfileManagerMode();"
+ onload="StartUp();">
+
+ <stringbundle id="bundle_profile"
+ src="chrome://communicator/locale/profile/profileSelection.properties"/>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+
+ <script type="application/javascript" src="chrome://communicator/content/profile/profileSelection.js"/>
+ <script type="application/javascript" src="chrome://mozapps/content/profile/createProfileWizard.js"/>
+
+ <dialogheader class="header-large" id="header" title="&profileManager.title;" description="&windowTitle.label;"/>
+
+ <hbox class="wizard-box" flex="1">
+
+ <!-- instructions -->
+ <deck id="prattle">
+ <description id="intro" start="&introStart.label;">&introSwitch.label;</description>
+ <vbox>
+ <description id="label">&profileManagerText.label;</description>
+ <separator/>
+ <hbox>
+ <vbox flex="1" id="managebuttons">
+ <button id="newButton" label="&newButton.label;" accesskey="&newButton.accesskey;" oncommand="CreateProfileWizard();"/>
+ <button id="renameButton" label="&renameButton.label;" accesskey="&renameButton.accesskey;" oncommand="RenameProfile();"/>
+ <button id="deleteButton" label="&deleteButton.label;" accesskey="&deleteButton.accesskey;" oncommand="ConfirmDelete();"/>
+ </vbox>
+ <spacer flex="2"/>
+ </hbox>
+ </vbox>
+ </deck>
+
+ <separator class="thin" orient="vertical"/>
+
+ <vbox flex="1">
+ <tooltip id="treetip"
+ onpopupshowing="HandleToolTipEvent(event);">
+ </tooltip>
+ <tree id="profiles" flex="1" seltype="single"
+ hidecolumnpicker="true"
+ onselect="DoEnabling();"
+ onkeypress="HandleKeyEvent(event);">
+ <treecols>
+ <treecol label="&availableProfiles.label;" flex="1" sortLocked="true"/>
+ </treecols>
+ <treechildren tooltip="treetip"
+ ondblclick="HandleClickEvent(event);"/>
+ </tree>
+ <checkbox id="offlineState" label="&offlineState.label;" accesskey="&offlineState.accesskey;" hidden="true"/>
+ <checkbox id="autoSelect" label="&autoSelect.label;" accesskey="&autoSelect.accesskey;"/>
+ </vbox>
+ </hbox>
+
+</dialog>
diff --git a/xpfe/components/profile/jar.mn b/xpfe/components/profile/jar.mn
new file mode 100644
index 000000000..590d7a685
--- /dev/null
+++ b/xpfe/components/profile/jar.mn
@@ -0,0 +1,20 @@
+#filter substitution
+# 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/.
+
+comm.jar:
+% override chrome://mozapps/content/profile/profileSelection.xul chrome://communicator/content/profile/profileSelection.xul
+
+ content/communicator/profile/profileSelection.js (content/profileSelection.js)
+ content/communicator/profile/profileSelection.xul (content/profileSelection.xul)
+
+en-US.jar:
+* locale/en-US/communicator/profile/profileSelection.dtd (locale/profileSelection.dtd)
+ locale/en-US/communicator/profile/profileSelection.properties (locale/profileSelection.properties)
+
+classic.jar:
+ skin/classic/communicator/profile/migrate.gif (skin/migrate.gif)
+ skin/classic/communicator/profile/profile.css (skin/profile.css)
+ skin/classic/communicator/profile/profileManager.css (skin/profileManager.css)
+ skin/classic/communicator/profile/profileicon-large.gif (skin/profileicon-large.gif)
diff --git a/xpfe/components/profile/locale/profileSelection.dtd b/xpfe/components/profile/locale/profileSelection.dtd
new file mode 100644
index 000000000..858292f8d
--- /dev/null
+++ b/xpfe/components/profile/locale/profileSelection.dtd
@@ -0,0 +1,39 @@
+<!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!-- 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/. -->
+
+#ifdef BINOC_MAIL
+#define APP_SPECIFIC_PROFILE_OBJECT stored messages
+#else
+#define APP_SPECIFIC_PROFILE_OBJECT bookmarks
+#endif
+
+<!ENTITY windowTitle.label "Select User Profile">
+<!ENTITY profileManager.title "&brandFullName; Profile Manager">
+
+<!ENTITY manage.label "Manage Profiles…">
+<!ENTITY manage.accesskey "M">
+<!ENTITY select.label "Use Profile">
+
+<!ENTITY availableProfiles.label "Available Profiles">
+
+#expand <!ENTITY introStart.label "To access your personal profile, which contains your preferences, __APP_SPECIFIC_PROFILE_OBJECT__, and other personalized information, please choose your profile from the list, and click &start.label; to begin your session.">
+#expand <!ENTITY introSwitch.label "To switch to another profile, which contains preferences, __APP_SPECIFIC_PROFILE_OBJECT__, and other personalized information, please choose that profile from the list, and click &select.label; to begin using that profile.">
+#expand <!ENTITY profileManagerText.label "&brandShortName; stores information about your preferences, __APP_SPECIFIC_PROFILE_OBJECT__, and other user items in your user profile.">
+
+<!ENTITY autoSelect.label "Default to this profile">
+<!ENTITY autoSelect.accesskey "S">
+
+<!ENTITY start.label "Start &brandShortName;">
+<!ENTITY exit.label "Exit">
+
+<!ENTITY newButton.label "Create Profile…">
+<!ENTITY newButton.accesskey "C">
+<!ENTITY renameButton.label "Rename Profile…">
+<!ENTITY renameButton.accesskey "R">
+<!ENTITY deleteButton.label "Delete Profile…">
+<!ENTITY deleteButton.accesskey "D">
+
+<!ENTITY offlineState.label "Work offline">
+<!ENTITY offlineState.accesskey "o">
diff --git a/xpfe/components/profile/locale/profileSelection.properties b/xpfe/components/profile/locale/profileSelection.properties
new file mode 100644
index 000000000..5b5ad1a8a
--- /dev/null
+++ b/xpfe/components/profile/locale/profileSelection.properties
@@ -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/.
+
+deleteLocked=%S cannot delete the profile "%S" because it is in use.
+deleteProfile=Deleting a profile will remove the profile from the list of available profiles and cannot be undone.\n\nYou may also choose to delete the profile data files, including your saved mail, settings, and certificates. This option will delete the folder "%S" and cannot be undone.\n\nWould you like to delete the profile data files?\n\n
+
+manageTitle=Manage User Profiles
+selectTitle=Select User Profile
+
+dirLocked=%S cannot use the profile "%S". It may be in use, unavailable or damaged.\n\nPlease choose another profile or create a new one.
+
+renameProfileTitle=Rename Profile
+renameProfilePrompt=Rename the profile "%S" to:
+profileNameInvalidTitle=Invalid profile name
+profileNameEmpty=An empty profile name is not allowed.
+invalidChar=The character "%S" is not allowed in profile names. Please choose a different name.
+deleteTitle=Delete Profile
+deleteFiles=Delete Files
+dontDeleteFiles=Don't Delete Files
+profileExists=A profile with this name already exists. Please choose another name.
+profileExistsTitle=Profile Exists
diff --git a/xpfe/components/profile/moz.build b/xpfe/components/profile/moz.build
new file mode 100644
index 000000000..697e0cda1
--- /dev/null
+++ b/xpfe/components/profile/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/xpfe/components/profile/skin/migrate.gif b/xpfe/components/profile/skin/migrate.gif
new file mode 100644
index 000000000..5b4380995
--- /dev/null
+++ b/xpfe/components/profile/skin/migrate.gif
Binary files differ
diff --git a/xpfe/components/profile/skin/profile.css b/xpfe/components/profile/skin/profile.css
new file mode 100644
index 000000000..1e7f3c37c
--- /dev/null
+++ b/xpfe/components/profile/skin/profile.css
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+@import url("chrome://global/skin/global.css");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+treechildren::-moz-tree-image {
+ margin-inline-end: 2px;
+ list-style-image: url("chrome://communicator/skin/profile/profileicon-large.gif");
+}
+
+treechildren::-moz-tree-image(rowMigrate-no) {
+ list-style-image: url("chrome://communicator/skin/profile/migrate.gif");
+}
+
+/* profile selection dialog */
+#intro,
+#label {
+ width: 17em;
+}
+
+#managebuttons > button {
+ min-width: 8em;
+}
diff --git a/xpfe/components/profile/skin/profileManager.css b/xpfe/components/profile/skin/profileManager.css
new file mode 100644
index 000000000..4fd88568a
--- /dev/null
+++ b/xpfe/components/profile/skin/profileManager.css
@@ -0,0 +1,18 @@
+/* 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 url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+#dialoginfo {
+ width: 36em;
+}
+
+#table-housing {
+ background-color: white;
+ height: 100%;
+}
+
+#buttons-box {
+ margin-inline-start: 1em;
+}
diff --git a/xpfe/components/profile/skin/profileicon-large.gif b/xpfe/components/profile/skin/profileicon-large.gif
new file mode 100644
index 000000000..749bf955b
--- /dev/null
+++ b/xpfe/components/profile/skin/profileicon-large.gif
Binary files differ
diff --git a/xpfe/content/communicator.css b/xpfe/content/communicator.css
new file mode 100644
index 000000000..a934b0561
--- /dev/null
+++ b/xpfe/content/communicator.css
@@ -0,0 +1,172 @@
+/* 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 url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+%ifdef BINOC_NAVIGATOR
+/* ::::: print preview toolbar ::::: */
+
+toolbar[printpreview="true"] {
+ -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
+}
+
+/* ::::: notification box ::::: */
+
+.browser-notificationbox {
+ -moz-binding: url("chrome://communicator/content/bindings/notification.xml#browser-notificationbox");
+}
+
+.browser-notificationbox[popupnotification="true"] {
+ -moz-binding: url("chrome://communicator/content/bindings/notification.xml#popup-notification");
+}
+
+notification[value="addon-install-started"] {
+ -moz-binding: url("chrome://communicator/content/bindings/notification.xml#addon-progress-notification");
+}
+
+/* ::::: toolbaritem ::::: */
+toolbaritem {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#basecontrol");
+}
+%endif
+
+/* With the move to the new toolkit, SeaMonkey needs to overwrite certain bindings
+ * if it wants to keep its distinctive likeness. The now hidden new toolkit bindings
+ * will stay accessible via a set xpfe="false" attribute, though, where necessary.
+ */
+
+/******* toolkit access layer *******/
+/* These rules reintroduce the toolkit bindings overwritten later below */
+%ifdef BINOC_NAVIGATOR
+toolbox[xpfe="false"] {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbox");
+}
+
+toolbox[xpfe="false"] > toolbar,
+toolbar[xpfe="false"][type="menubar"],
+toolbar[xpfe="false"] {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar");
+}
+
+menubar[xpfe="false"],
+toolbar > toolbaritem > menubar,
+toolbar > menubar {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#menubar");
+}
+
+toolbar > toolbarpaletteitem > toolbaritem > menubar {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#menubar") !important;
+}
+
+.menubar-items {
+ -moz-box-orient: vertical; /* for flex hack */
+}
+
+.menubar-items > menubar {
+ -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
+}
+%endif
+
+prefwindow[xpfe="false"] {
+ -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefwindow");
+}
+
+prefpane[xpfe="false"] {
+ -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefpane");
+}
+
+%ifdef BINOC_NAVIGATOR
+findbar[xpfe="false"] {
+ -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar");
+}
+%endif
+
+prefwindow[xpfe="false"] > .paneDeckContainer,
+prefpane[xpfe="false"] > .content-box {
+ overflow: hidden;
+}
+
+/******* SeaMonkey XPFE *******/
+/* These bindings reflect SeaMonkey XPFE, modulo new toolkit features. */
+%ifdef BINOC_NAVIGATOR
+toolbox {
+ -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#grippytoolbox");
+}
+
+toolbar {
+ -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#grippytoolbar");
+}
+
+toolbar[type="menubar"] {
+ -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#grippytoolbar-menubar");
+}
+
+toolbargrippy {
+ -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#toolbargrippy");
+}
+
+menubar {
+ -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#grippymenubar");
+}
+%endif
+
+prefwindow {
+ -moz-binding: url("chrome://communicator/content/bindings/prefwindow.xml#prefwindow");
+}
+
+prefpane {
+ -moz-binding: url("chrome://communicator/content/bindings/prefwindow.xml#prefpane");
+}
+
+%ifdef BINOC_NAVIGATOR
+findbar {
+ -moz-binding: url("chrome://communicator/content/bindings/findbar.xml#findbar");
+}
+%endif
+
+prefwindow > .paneDeckContainer,
+prefpane > .content-box {
+ overflow: visible;
+}
+
+prefwindow[overflow="auto"] > .paneDeckContainer,
+prefwindow[overflow="auto"] prefpane > .content-box {
+ overflow: auto;
+}
+
+%ifdef BINOC_NAVIGATOR
+.statusbarpanel-backgroundbox {
+ -moz-binding: url("chrome://communicator/content/bindings/general.xml#statusbarpanel-backgroundbox");
+}
+
+textbox[enablehistory="true"] > .autocomplete-history-dropmarker {
+ display: -moz-box;
+}
+
+/******* sync *******/
+#sync-notifications {
+ -moz-binding: url("chrome://communicator/content/sync/syncNotification.xml#notificationbox");
+ overflow-y: visible !important;
+}
+
+#sync-notifications > notification {
+ -moz-binding: url("chrome://communicator/content/sync/syncNotification.xml#notification");
+}
+
+/******* autohide toolbars *******/
+
+toolbar[type="menubar"][autohide="true"]
+{
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-menubar-autohide");
+ overflow: hidden;
+}
+
+toolbar[type="menubar"][autohide="true"][inactive="true"]
+{
+ min-height: 0px !important;
+ height: 0px !important;
+ -moz-appearance: none !important;
+ border-style: none !important;
+}
+%endif \ No newline at end of file
diff --git a/xpfe/content/jar.mn b/xpfe/content/jar.mn
new file mode 100644
index 000000000..977eb7a7c
--- /dev/null
+++ b/xpfe/content/jar.mn
@@ -0,0 +1,6 @@
+# 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/.
+
+comm.jar:
+* content/communicator/communicator.css (communicator.css)
diff --git a/xpfe/content/moz.build b/xpfe/content/moz.build
new file mode 100644
index 000000000..697e0cda1
--- /dev/null
+++ b/xpfe/content/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/xpfe/modules/Communicator.jsm b/xpfe/modules/Communicator.jsm
new file mode 100644
index 000000000..f9f4731ce
--- /dev/null
+++ b/xpfe/modules/Communicator.jsm
@@ -0,0 +1,73 @@
+/* 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.EXPORTED_SYMBOLS = ["Communicator"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+this.Communicator = {
+ service: Services,
+ xpcom: XPCOMUtils,
+ platform:
+#ifdef MOZ_WIDGET_GTK
+ "linux",
+#elif XP_WIN
+ "win",
+#elif XP_MACOSX
+ "macosx",
+#elif MOZ_WIDGET_ANDROID
+ "android",
+#elif XP_LINUX
+ "linux",
+#else
+ "other",
+#endif
+ isPlatformAndVersionAtLeast: function(platform, version) {
+ let platformVersion = Services.sysinfo.getProperty("version");
+ return platform == this.platform &&
+ Services.vc.compare(platformVersion, version) >= 0;
+ },
+ isPlatformAndVersionAtMost: function(platform, version) {
+ let platformVersion = Services.sysinfo.getProperty("version");
+ return platform == this.platform &&
+ Services.vc.compare(platformVersion, version) <= 0;
+ },
+ showLicenseWindow: function() {
+ var eulaDone = null;
+ eulaDone = Services.prefs.getBoolPref("app.eula.accepted", false);
+
+ if (!eulaDone) {
+ Services.ww.openWindow(null, "chrome://communicator/content/eula/eula.xul",
+ "_blank", "chrome,centerscreen,modal,resizable=no", null);
+ }
+ },
+ readfile: function(aDSDir, aFile) {
+ Components.utils.import("resource://gre/modules/FileUtils.jsm");
+ Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+ var file = FileUtils.getFile(aDSDir, [aFile]);
+
+ if (!file.exists()) {
+ Components.utils.reportError("Communicator.readfile: " + aFile + " does not exist in " + aDSDir);
+ return "No Data";
+ }
+
+ var stream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+
+ try {
+ stream.init(file, -1, 0, 0);
+ var data = NetUtil.readInputStreamToString(stream, stream.available());
+ }
+ catch (ex) {
+ Components.utils.reportError("Communicator.readfile: file stream failure in " + aDSdir + "/" + aFile);
+ return "No data";
+ }
+
+ stream.close();
+
+ return data;
+ },
+}
diff --git a/xpfe/modules/moz.build b/xpfe/modules/moz.build
new file mode 100644
index 000000000..22751cd65
--- /dev/null
+++ b/xpfe/modules/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_PP_JS_MODULES += ['Communicator.jsm'] \ No newline at end of file
diff --git a/xpfe/moz.build b/xpfe/moz.build
new file mode 100644
index 000000000..846936e5d
--- /dev/null
+++ b/xpfe/moz.build
@@ -0,0 +1,13 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+ 'components',
+ 'modules',
+ 'searchplugins'
+]
+
+if CONFIG['BINOC_NAVIGATOR']:
+ DIRS += ['content'] \ No newline at end of file
diff --git a/xpfe/searchplugins/duckduckgo-palemoon.xml b/xpfe/searchplugins/duckduckgo-palemoon.xml
new file mode 100644
index 000000000..57395e32d
--- /dev/null
+++ b/xpfe/searchplugins/duckduckgo-palemoon.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>DuckDuckGo</ShortName>
+ <Description>Search DuckDuckGo</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <LongName>Search Plugin for DuckDuckGo (HTTPS version)</LongName>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA11RgALs6oACbQ9wAj0v8AI9L/ACfQ9wAu0agANdUYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzzN4CNdL/oK/z//////////////////////+jsPv/BDXX/wAz0t4AAAAAAAAAAAAAAAAAAAAAAAAAAAAyzvNSduD//////8jK/v+P+Lf/IbQL/17RPP+J3Y//wOKX//////9YeuX/ADLO8wAAAAAAAAAAAAAAAAAw091piOX/8/X9/1Fx5P9xhu//WOWZ/0W9Lv9Lwjn/J8BB/xyDAP9bdfL/9fP//2mI5v8AMNPdAAAAAAc610YRQ9f//////0Zr4P8AGdD/sb32////////////wrv//wAh1/8MPab/ACPc/05r4///////EkPX/wc610YANtWkrr/y/6S48P8AJ9L/AB3R/+/w/v///////////3+D7f8AQeL/AYTw/wFr5/8AMNb/p7Tv/6698v8AM9WkADLW//////8yXt//AC3V/wAw1/////////////z///8A0P7/AKb1/wWI7P8AuPf/AJ3w/zZW3P//////ADHV/wAx2P//////AzrZ/wAu1/84ZOL////////////e////AND//wC1+f8Atff/AZbv/wY62f8ELNf//////wAw1/8AMtn//////wAw2f8ALNn/kKrz////+//cwbH////////////R////Rcb8/wDO/f8A/P//AHzo//////8AMNj/ADXa//////8vXuL/ACna/4yq9///79T/jUkg/9i+r///////r2Q0/7Cozv8BKdr/AirY/zdZ4P//////ADTa/wI72tOuv/T/prr0/wAl2v+JqPb//7yW/+bUxv/9+/n////u//W+n/+Op/L/ADPd/wAv2v+ru/T/r7/0/wI72tMLQd1DEEjg//////9Cbef/ADng///////////////////////R3///AC3g/wAy3v9SeOn//////xFI4P8LQd1DAAAAAAM64PNmiuz/9/j//2mN7f/m7P3///////////9Cb+n/ACXd/wAt3v9rju3//////2iL7P8DOuDzAAAAAAAAAAAAAAAAAT3g/0p16f//////3OT8/3OS7v8AKt3/ACPc/zhn5/+xw/b//////0956v8CPeD/AAAAAAAAAAAAAAAAAAAAAAAAAAAEPODzBUDh/5uz8//7/f7/////////////////prz0/wtF4v8FQeDzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtF5kYDQOOkADrj/wA44v8AOeP/ADzk/wVB46QPReZGAAAAAAAAAAAAAAAAAAAAAPAPAADgBwAAwAMAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAADAAwAA4AcAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChIzyAnRNFwJ0TQryND0d8nRNH/J0TR/ydE0f8nRNH/I0PR3ydE0K8nRNFwKEjPIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChE00AlRdK/J0XS/ydF0v8nRdL/XXPd/11z3f94i+P/k6Lp/5Oi6f9rf+D/NVDV/ydF0v8nRdL/JUXSvyhE00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAzxAnRNOvJ0XT/ydF0/8lRdK/KEXSYOvu+6/+/v6//v7+v/39/c////////////7+/r/J0fOAKEXSYCVF0r8nRdP/J0XT/ydE068gQM8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlRdUwJ0bT7ydG0/8nRtHPKETTQAAAAADHx8dA2vHhn5TYpN/o9+z/////////////////8PL83ydG0o8lRdUwAAAAAChE00AnRtHPJ0bT/ydG0+8lRdUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKEXVYCdG1P8nRtT/KEbTgAAAAAAmRtZQI0PU38jIyP/F6s//Rrtk/0a7ZP9/yIr/c796/4vLkv+JpNf/M3Kq/zyWh/8zeKTfJkbWUAAAAAAoRtOAJ0bU/ydG1P8oRdVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVF1TAnR9X/J0fV/yhF1WAgQM8QJ0fTrydH1f9CW8//2tra/6Pdsv9Gu2T/Rrtk/0WzWv9Gu2T/Rrtk/0a7ZP9Gu2T/Rrtk/z6egP8nR9X/J0fTryBAzxAoRdVgJ0fV/ydH1f8lRdUwAAAAAAAAAAAAAAAAAAAAAAAAAAAgQM8QJ0fV7ydH1f8oSNVgIEDPECdH1c8nR9X/J0fV/1xwyf/t7e3/o92y/0a7ZP9Gu2T/Ra5U/0a7ZP9Gu2T/Rrtk/0a7ZP9Gu2T/Pp6A/ydH1f8nR9X/J0fVzyBAzxAoSNVgJ0fV/ydH1e8gQM8QAAAAAAAAAAAAAAAAAAAAACdH1q8nR9b/KEjVgCBQzxAnR9bPJ0fW/ydH1v8nR9b/gIzB//r6+v+j3bL/Rrtk/13Ed/+i26//ruG7/z6egf8+noH/Rrtk/0a7ZP86kI//J0fW/ydH1v8nR9b/J0fWzyBQzxAoSNWAJ0fW/ydH1q8AAAAAAAAAAAAAAAAoSNdAJkjW/yZH1s8AAAAAJEfWryZI1v8mSNb/JkjW/yZI1v+jqsT//////+j37P/R7tj////////////W3ff/JkjW/yZI1v8uZbr/PJeI/zJzrP8mSNb/JkjW/yZI1v8mSNb/JEfWrwAAAAAmR9bPJkjW/yhI10AAAAAAAAAAACVI1r8mSNf/KEjXQCZJ1lAmSNf/JkjX/yZI1/8mSNf/JkjX/9HR0f///////////////////////////5Ok6/8mSNf/JkjX/yZI1/8mSNf/JkjX/yZI1/8mSNf/JkjX/yZI1/8mSNf/JknWUChI10AmSNf/JUjWvwAAAAAoSNcgJknY/yZH2M8AAAAAI0nY3yZJ2P8mSdj/JknY/yZJ2P9KZM//39/f////////////////////////////XHfi/yZJ2P8mSdj/JknY/yZJ2P8mSdj/JknY/yZJ2P8mSdj/JknY/yZJ2P8jSdjfAAAAACZH2M8mSdj/KEjXICdJ2HAmSdj/JUjXYCVK2jAmSdj/JknY/yZJ2P8mSdj/JknY/2V4yf/t7e3///////////////////////////9cd+L/HXTj/xSf7/8Nwfj/CdL8/wnS/P8J0vz/ELDz/xt85v8mSdj/JknY/yZJ2P8lStowJUjXYCZJ2P8nSdhwJErZryZK2f8oSNcgJUnajyZK2f8mStn/JkrZ/yZK2f8mStn/iJPA////////////////////////////0ff+/xjV/P8J0vz/Drn1/xiO6/8Yjuv/GI7r/xCw8/8Lyvr/CdL8/xmF6P8mStn/JkrZ/yVJ2o8oSNcgJkrZ/yRK2a8jStrfI0rZ3wAAAAAlSdq/Jkra/yZK2v8mStr/Jkra/yZK2v+xtsf///////////////////////////8o2Pz/CdL8/wvK+v8mStr/Jkra/yZK2v8mStr/Jkra/yZK2v8iW97/Jkra/yZK2v8mStr/JUnavwAAAAAjStnfI0ra3yZK2v8lSdq/AAAAACZH2O8mStr/Jkra/yZK2v8mStr/L1HY/9HR0f///////////////////////////yjY/P8J0vz/CdL8/xCw9P8QsPT/ELD0/xSf7/8ddeX/Jkra/yZK2v8mStr/Jkra/yZK2v8mR9jvAAAAACVJ2r8mStr/Jkvb/yVJ2r8AAAAAJkvb/yZL2/8mS9v/Jkvb/yZL2/9KZtL/4+Pj////////////////////////////4Pn//0fd/f8J0vz/CdL8/wnS/P8J0vz/CdL8/wnS/P8Lyvr/Fpfu/yJc3/8mS9v/Jkvb/yZL2/8AAAAAJUnavyZL2/8mS9z/JUncvwAAAAAmS9z/Jkvc/yZL3P8mS9z/Jkvc/26AyP/x8fH//////////////////////////////////////9H3/v/C9P7/o+7+/2fa+/8Oufb/CdL8/wnS/P8J0vz/CdL8/xiP7P8mS9z/Jkvc/wAAAAAlSdy/Jkvc/yZM3P8lTNy/AAAAACZJ2e8mTNz/Jkzc/yZM3P8mTNz/iJTB////////////qnth/5VaOf/x6eX///////////////////////Hp5f/x6eX/ydL2/yZM3P8kVN7/G37o/xKo8v8QsfT/HXbm/yZM3P8mSdnvAAAAACVM3L8mTNz/I0vc3yZJ2u8AAAAAJUzevyZM3f8mTN3/Jkzd/yZM3f+fqc3///////////+VWjn/v5yI/+re1///////////////////////jk8s/7iRe//J0vb/Jkzd/yZM3f8mTN3/Jkzd/yZM3f8mTN3/Jkzd/yVM3r8AAAAAI0vc3yNL3N8kTd2vJk3d/yhQ3yAlTd2PJk3d/yZN3f8mTd3/Jk3d/6St0v////////////Hp5f/q3tf///////////////////////////+xhm7/49PK/6Cx8P8mTd3/Jk3d/yZN3f8mTd3/Jk3d/yZN3f8mTd3/JU3djyhQ3yAmTd3/JE3drydN33AmTd7/J03fcCVK3zAmTd7/Jk3e/yZN3v8mTd7/pK7S///////Sp5r/////////////////////////////////////////////////T27k/yZN3v8mTd7/Jk3e/yZN3v8mTd7/Jk3e/yZN3v8lSt8wJ03fcCZN3v8nTd9wKFDfICZO3/8mTt3PAAAAACVN3r8mTt//Jk7f/yZO3/+EltX//////+fRyv/SqaD/59LO///////////////////////at63/vIBy/7Glxf8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8mTt//JU3evwAAAAAmTt3PJk7f/yhQ3yAAAAAAJE/dryZO3/8oUN9AKFDfQCZO3/8mTt//Jk7f/zhb2v/o6/T/////////////////////////////////////////////////XHrn/yZO3/8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8oUN9AKFDfQCZO3/8kT92vAAAAAAAAAAAoUN9AJk7g/yZO4M8AAAAAJk/hnyZO4P8mTuD/Jk7g/05v5v/k6fv//////////////////////////////////////3eR7P8mTuD/Jk7g/yZO4P8mTuD/Jk7g/yZO4P8mTuD/Jk/hnwAAAAAmTuDPJk7g/yhQ30AAAAAAAAAAAAAAAAAjT+GfJU/h/yVO4Y8gUN8QIk7gzyVP4f8lT+H/SWnW/0lp1v+bq+H/8fHx/////////////////6Cy8v9OcOb/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yJO4M8gUN8QJU7hjyVP4f8jT+GfAAAAAAAAAAAAAAAAAAAAACBQ3xAlTOHvJU/h/yVQ4mAgUN8QIk7hzyVP4f+ktOv///////////////////////H0/f9phur/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yVP4f8iTuHPIFDfECVQ4mAlT+H/JUzh7yBQ3xAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ3zAlUOLvJVDi/yVQ4mAgUN8QI1Din4mb2//J0/j/ydP4/6299P93ku3/M1vk/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/I1DinyBQ3xAlUOJgJVDi/yVQ4u8lUN8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAlUOLvJVDi/yVQ4o8AAAAAJFDjQCVQ4r8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDivyRQ40AAAAAAJVDijyVQ4v8lUOLvJVDkMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAjUeTfJVHj/yNR5N8kUONAAAAAACVQ5DAmUuOAJVHivyNR5N8lUeP/JVHj/yNR5N8lUeK/JlLjgCVQ5DAAAAAAJFDjQCNR5N8lUeP/I1Hk3yVQ5DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBQ3xAjUuSfJVHk/yVR5P8jUeTfJFLkcChQ5yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoUOcgJFLkcCNR5N8lUeT/JVHk/yNS5J8gUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkUONAI1LknyVS5P8lUuT/JVLk/yVS5O8lUeS/JVHkvyVR5L8lUeS/JVLk7yVS5P8lUuT/JVLk/yRS468kUONAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFDfECVS5GAjUuWfIlPlzyVS5f8lUuX/JVLl/yVS5f8iU+XPI1LlnyVS5GAgUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/ggBB/wgAEP4AAAB8AAAAPAAAADiAAAEYAAAAEQAAAIAAAAAAAAAAAgAAAEIAAABCAAAAQgAAAEIAAABCAAAAQAAAAAAAAAABAAAAiAAAABiAAAEcAAAAPAAAAD4AAAB/CAAQ/4IAQf/AfgP/8AAP//wAP/</Image>
+ <Url type="text/html" method="get" template="https://duckduckgo.com/">
+ <Param name="t" value="palemoon"/>
+ <Param name="q" value="{searchTerms}"/>
+ </Url>
+ <Url type="application/x-suggestions+json" method="GET" template="https://duckduckgo.com/ac/">
+ <Param name="type" value="list"/>
+ <Param name="q" value="{searchTerms}"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/xpfe/searchplugins/ecosia.xml b/xpfe/searchplugins/ecosia.xml
new file mode 100644
index 000000000..04c8d229b
--- /dev/null
+++ b/xpfe/searchplugins/ecosia.xml
@@ -0,0 +1,12 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>Ecosia</ShortName>
+ <Description>Search Ecosia</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Contact>info@ecosia.org</Contact>
+ <LongName>Ecosia Search</LongName>
+ <Image width="16" height="16">
+ data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAACMuAAAjLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8qzQBuaw3UrmsN6u5rDfruaw37bmsN+25rDfSuaw3fLmsNyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2rTokrLFGurqsNv+5rDf/uaw3/7msN/+5rDf/uaw3/7urNP/AqS7suqw2aAAAAAAAAAAAAAAAAAAAAAC/qjApkbpn4mvJlf/EqCr/uaw3/7msN/+5rDf/uaw3/7urNP+rsUj/ib5x/7qsNv+9qzKBAAAAAAAAAAC5rDcLwKkvzom9cf813Nb/lrlh/8KoLP+5rDf/uaw3/7msN//BqS3/eMSF/yXj6v+BwHv/lbli/7atO1IAAAAAuaw3bsCqL/+Rumb/K+Di/z3ZzP+dtln/vqox/7msN/+5rDf/waku/23Ikv8s4OH/ONvS/5m4Xv+7qzXZuaw3CbmsN9DBqS7/hL93/zDe3f8v393/RdbD/7OuPv+7qzX/uqw2/8WoKf99wn//Lt/e/y/e3f99wn//v6ow/7msN0+7qzT7s64+/0bWwf8y3tn/L97d/03TuP+usET/vKoz/7isOP+vr0P/XM6n/zDe3P813Nb/L97d/5O6Zf/EpymOu6s0/7OuPv8+2cv/J+Hn/1HStP+0rjz/vasy/76qMP9zxYr/NtzV/zTd1/823NX/NtzV/zLd2f9I1b//mbheqsGpLf+gtVX/bseR/3fEhv+wr0L/vaoy/7msN/+/qjD/Wc+q/yvg4/813Nb/Md7b/zfc1P833NT/Mt7a/zbc1aqHvnT6bMiT/522WP+wr0L/vqox/7msN/+5rDf/vaoy/6C1VP8/2cr/N9zT/2vJlf9hzKD/NtzU/zbc1f813NaONdzWz3HGjv9ky53/prNN/8SoKv+8qzT/uaw3/7msOP/EqCr/ecOE/0HYx/9V0K//N9vT/zXc1v823NX/NtzVTjXc120w3tz/Lt/e/0zUu/+Fv3X/rrBF/7msN/+7qzX/vaoy/6qxSf9G1sH/L9/d/zPd2P8x3tv/L9/e2C/f3Qk23NUKNtzVzDbc1v823NX/OdvQ/0nVvv+xr0H/ta07/7+qL/+7qzT/r69D/2LMoP823NX/VNGx/2TLnVEAAAAAAAAAADbc1Sc03dfgQNnJ/2bKm/862tD/pLRP/1vOqf9S0rP/ib1x/8CpL/+4rDj/qLJM/7qsNn4AAAAAAAAAAAAAAAAAAAAAM93YI0vUvLtux5H/VdGw/3DHj/9Zz6r/Xc2m/3rDgv+5rDf/u6s1672rM2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyaYjUburNaytsUbZuK056cGpLuS/qjDGuaw3gLmsNx4AAAAAAAAAAAAAAAAAAAAA+D8AAOAPAADAAwAAgAMAAIABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAACAAQAAgAMAAMAHAADgDwAA+B8AAA==
+ </Image>
+ <Url type="text/html" method="get" template="https://www.ecosia.org/search?q={searchTerms}&amp;addon=opensearch"/>
+ <Url type="application/x-suggestions+json" template="https://ac.ecosia.org/autocomplete/?q={searchTerms}&amp;type=list"/>
+</OpenSearchDescription>
diff --git a/xpfe/searchplugins/ekoru.xml b/xpfe/searchplugins/ekoru.xml
new file mode 100644
index 000000000..84f75bddd
--- /dev/null
+++ b/xpfe/searchplugins/ekoru.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>Ekoru</ShortName>
+ <Description>Ekoru - fight climate change with every search</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <LongName>Ekoru Search</LongName>
+ <Image height="16" width="16" type="image/x-icon">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABMLAAATCwAAAAAAAAAAAAAAAAAAxKIkAKyyPgLUmhFN2JkKwtKfEObJoxrvyaQb8NCgEenTmQvTzJQRe8CUHg/FlBYAkpLMAAAAAAAAAAAAxaEoABa5/wDUng9Z1aAJ562pI/96skP+Y7RQ9mS1Uvh3tEX/qaoi/82eB/rOmQycyJQaEsqWFQAAAAAAAAAAANagBwDSnxA606EJ45OuNP9Qt1neQbVYdDyyWDs/s1dDQrdcgFC6ZN2Psjj/zKQF/s2fCoqunzsIt54sDv9tAADQnSQN158IsJ+tKv9Ot1XiRq9QPuShCg63qiY9uqsnPt2fEA1Cs2MyTLtt0JyyLP/OpQTrvKUcR6enKnnMnhce2J4NUMalEfJht1T+Q7RTc7yqIjaKt07BZsBv+GnAbveAu1q6q687Jz65cE9hu2D1vakO/8ilCoiRqjKNw6AaddyhCKOprCX/Sblf2oesM0CTtkTMT8SC6z7Fj347xpRrSMiQ2nLAcKpxrUsYS71wx6KxK//Opge2gqs2d7ykGb7dpAjdkK82/0S1WqqxrCNzar9q/z3EioS3aWABMtSuADrKoHBVypPToLBFFUW9d6OLtkP/zKgKx3+uNW26phfk2aUG+YaxO/9Es1mHq64sj1jDfP8+wYJmQ9G0J0DOqmNBzqOrWMmRkp+WKwZCvnuniLZI/8enCrt8rz1xv6YW99mlBfuJsTr/RLRbd7KtJ4JiwnT/QMODlVLHmgtMzaNRWcmTXIC+chFEum0kRMN/2pqzO//FqA+Sd68/iMWjE/jcpgbilq80/0K4Yoi/qRxQhrlS+kLGi+ZDvHxMZpdOBGWRPAJGu3YnPcSEql/CcP+1rh/wqakiUISuNrzQogzc3KQHpLarH/9Rt1vCg6gzIK6vJ7Z1vmH/RsWK7UHDi7o9xYm0QMeM4VfEfP+hszT9w6gWilOzU1GfrCbz1qEKldajD0vTpgrweLNE+EK0WF3Cqhsota8jupi2QPt/u1r/eb1c/4u5TP+rsSruwKwXh2axRjJ4sTzHxaUQ59ifEDnMoSkJ2qIJobioGf9gtE/hQbRUTbKoJxW+qx1YuasdkbusGZ7CrBh8vaocOk+xUDR2sDu7vacT/tagCoOvmU8C1qAQANSfFifXnwjMtKgd/2qzRe5Os1CaQbNTTT2yUjRBsFI3QLJST1aySI2GrjDhwqUQ/9afCLDTnBwU1J0XALqZOwDhowAA0p4VLdafCazGpA/zpaoi/4yuLvt7rzTyfK408o2sLfuwqBn/0aMH8digCZbRnBgb1Z0OAK2OZwAAAAAAqIddAMqeEADHmyAM1Z4OUdegB6bUoQbe0aIH+NGiB/vWoAjj1Z8IqdafD0zQoyEI06MYAAAAAAAAAAAAwA8AAIAHAACAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAADAAwAA4AcAAA==</Image>
+ <Url type="text/html" method="get" template="https://www.ekoru.org/?ext=palemoon&amp;q={searchTerms}"/>
+ <Url type="application/x-suggestions+json" template="https://ac.ekoru.org/?ext=palemoon&amp;q={searchTerms}"/>
+</OpenSearchDescription>
diff --git a/xpfe/searchplugins/moz.build b/xpfe/searchplugins/moz.build
new file mode 100644
index 000000000..562edb037
--- /dev/null
+++ b/xpfe/searchplugins/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# 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/.
+
+FINAL_TARGET_FILES.searchplugins += [
+ 'duckduckgo-palemoon.xml',
+ 'ecosia.xml',
+ 'ekoru.xml',
+ 'wikipedia.xml',
+]
diff --git a/xpfe/searchplugins/wikipedia.xml b/xpfe/searchplugins/wikipedia.xml
new file mode 100644
index 000000000..6bfb0ccd8
--- /dev/null
+++ b/xpfe/searchplugins/wikipedia.xml
@@ -0,0 +1,18 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (en)</ShortName>
+<Description>Wikipedia, the free encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://en.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="http://en.wikipedia.org/wiki/Special:Search">
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://en.wikipedia.org/wiki/Special:Search</SearchForm>
+</SearchPlugin>
diff --git a/xpfe/xpfe.mozbuild b/xpfe/xpfe.mozbuild
new file mode 100644
index 000000000..a636ac42d
--- /dev/null
+++ b/xpfe/xpfe.mozbuild
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# 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/.
+
+include('/system/toolkit.mozbuild')
+
+if CONFIG['MOZ_CALENDAR']:
+ DIRS += [
+ '/calendar/libical',
+ '/calendar/lightning',
+ '/calendar/timezones'
+ ]
+
+DIRS += ['/xpfe']