summaryrefslogtreecommitdiffstats
path: root/xpfe/components
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /xpfe/components
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpfe/components')
-rw-r--r--xpfe/components/autocomplete/jar.mn9
-rw-r--r--xpfe/components/autocomplete/moz.build7
-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/build/moz.build15
-rw-r--r--xpfe/components/build/nsModule.cpp44
-rw-r--r--xpfe/components/directory/moz.build20
-rw-r--r--xpfe/components/directory/nsDirectoryViewer.cpp1393
-rw-r--r--xpfe/components/directory/nsDirectoryViewer.h126
-rw-r--r--xpfe/components/directory/nsIHTTPIndex.idl50
-rw-r--r--xpfe/components/moz.build12
-rw-r--r--xpfe/components/windowds/moz.build17
-rw-r--r--xpfe/components/windowds/nsIWindowDataSource.idl17
-rw-r--r--xpfe/components/windowds/nsWindowDataSource.cpp519
-rw-r--r--xpfe/components/windowds/nsWindowDataSource.h59
15 files changed, 3980 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..eb4454d28
--- /dev/null
+++ b/xpfe/components/autocomplete/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/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/build/moz.build b/xpfe/components/build/moz.build
new file mode 100644
index 000000000..3d02e0409
--- /dev/null
+++ b/xpfe/components/build/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+ 'nsModule.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '../directory',
+]
diff --git a/xpfe/components/build/nsModule.cpp b/xpfe/components/build/nsModule.cpp
new file mode 100644
index 000000000..43966f94d
--- /dev/null
+++ b/xpfe/components/build/nsModule.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsDirectoryViewer.h"
+#include "rdf.h"
+#include "nsRDFCID.h"
+#include "nsCURILoader.h"
+
+// Factory constructors
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHTTPIndex, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDirectoryViewerFactory)
+
+NS_DEFINE_NAMED_CID(NS_DIRECTORYVIEWERFACTORY_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPINDEX_SERVICE_CID);
+
+static const mozilla::Module::CIDEntry kXPFECIDs[] = {
+ { &kNS_DIRECTORYVIEWERFACTORY_CID, false, nullptr, nsDirectoryViewerFactoryConstructor },
+ { &kNS_HTTPINDEX_SERVICE_CID, false, nullptr, nsHTTPIndexConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kXPFEContracts[] = {
+ { "@mozilla.org/xpfe/http-index-format-factory-constructor", &kNS_DIRECTORYVIEWERFACTORY_CID },
+ { NS_HTTPINDEX_SERVICE_CONTRACTID, &kNS_HTTPINDEX_SERVICE_CID },
+ { NS_HTTPINDEX_DATASOURCE_CONTRACTID, &kNS_HTTPINDEX_SERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kXPFECategories[] = {
+ { "Gecko-Content-Viewers", "application/http-index-format", "@mozilla.org/xpfe/http-index-format-factory-constructor" },
+ { nullptr }
+};
+
+static const mozilla::Module kXPFEModule = {
+ mozilla::Module::kVersion,
+ kXPFECIDs,
+ kXPFEContracts,
+ kXPFECategories
+};
+
+NSMODULE_DEFN(application) = &kXPFEModule;
diff --git a/xpfe/components/directory/moz.build b/xpfe/components/directory/moz.build
new file mode 100644
index 000000000..248da07ec
--- /dev/null
+++ b/xpfe/components/directory/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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 += [
+ 'nsIHTTPIndex.idl',
+]
+
+XPIDL_MODULE = 'directory'
+
+SOURCES += [
+ 'nsDirectoryViewer.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/xpfe/components/directory/nsDirectoryViewer.cpp b/xpfe/components/directory/nsDirectoryViewer.cpp
new file mode 100644
index 000000000..9d23c5e74
--- /dev/null
+++ b/xpfe/components/directory/nsDirectoryViewer.cpp
@@ -0,0 +1,1393 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+/*
+
+ A directory viewer object. Parses "application/http-index-format"
+ per Lou Montulli's original spec:
+
+ http://www.mozilla.org/projects/netlib/dirindexformat.html
+
+ One added change is for a description entry, for when the
+ target does not match the filename
+
+*/
+
+#include "nsDirectoryViewer.h"
+#include "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsIDirIndex.h"
+#include "nsIDocShell.h"
+#include "jsapi.h"
+#include "nsCOMPtr.h"
+#include "nsEnumeratorUtils.h"
+#include "nsEscape.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "rdf.h"
+#include "nsIServiceManager.h"
+#include "nsIXPConnect.h"
+#include "nsEnumeratorUtils.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsITextToSubURI.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIFTPChannel.h"
+#include "nsIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsIProgressEventSink.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMElement.h"
+#include "nsIStreamConverterService.h"
+#include "nsICategoryManager.h"
+#include "nsXPCOMCID.h"
+#include "nsIDocument.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsContentUtils.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+
+static const int FORMAT_XUL = 3;
+
+//----------------------------------------------------------------------
+//
+// Common CIDs
+//
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+// Various protocols we have to special case
+static const char kFTPProtocol[] = "ftp://";
+
+//----------------------------------------------------------------------
+//
+// nsHTTPIndex
+//
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHTTPIndex)
+ NS_INTERFACE_MAP_ENTRY(nsIHTTPIndex)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDirIndexListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIFTPEventSink)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHTTPIndex)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsHTTPIndex, mInner)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHTTPIndex)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHTTPIndex)
+
+NS_IMETHODIMP
+nsHTTPIndex::GetInterface(const nsIID &anIID, void **aResult )
+{
+ if (anIID.Equals(NS_GET_IID(nsIFTPEventSink))) {
+ // If we don't have a container to store the logged data
+ // then don't report ourselves back to the caller
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+ *aResult = static_cast<nsIFTPEventSink*>(this);
+ NS_ADDREF(this);
+ return NS_OK;
+ }
+
+ if (anIID.Equals(NS_GET_IID(nsIPrompt))) {
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsPIDOMWindowOuter> aDOMWindow = do_GetInterface(mRequestor);
+ if (!aDOMWindow)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+
+ return wwatch->GetNewPrompter(aDOMWindow, (nsIPrompt**)aResult);
+ }
+
+ if (anIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsPIDOMWindowOuter> aDOMWindow = do_GetInterface(mRequestor);
+ if (!aDOMWindow)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+
+ return wwatch->GetNewAuthPrompter(aDOMWindow, (nsIAuthPrompt**)aResult);
+ }
+
+ if (anIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsIProgressEventSink> sink = do_GetInterface(mRequestor);
+ if (!sink)
+ return NS_ERROR_NO_INTERFACE;
+
+ *aResult = sink;
+ NS_ADDREF((nsISupports*)*aResult);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::OnFTPControlLog(bool server, const char *msg)
+{
+ NS_ENSURE_TRUE(mRequestor, NS_OK);
+
+ nsCOMPtr<nsIGlobalObject> globalObject = do_GetInterface(mRequestor);
+ NS_ENSURE_TRUE(globalObject, NS_OK);
+
+ // We're going to run script via JS_CallFunctionName, so we need an
+ // AutoEntryScript. This is Gecko specific and not in any spec.
+ dom::AutoEntryScript aes(globalObject,
+ "nsHTTPIndex OnFTPControlLog");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ NS_ENSURE_TRUE(global, NS_OK);
+
+ nsString unicodeMsg;
+ unicodeMsg.AssignWithConversion(msg);
+ JSString* jsMsgStr = JS_NewUCStringCopyZ(cx, unicodeMsg.get());
+ NS_ENSURE_TRUE(jsMsgStr, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::AutoValueArray<2> params(cx);
+ params[0].setBoolean(server);
+ params[1].setString(jsMsgStr);
+
+ JS::Rooted<JS::Value> val(cx);
+ JS_CallFunctionName(cx,
+ global,
+ "OnFTPControlLog",
+ params,
+ &val);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::SetEncoding(const char *encoding)
+{
+ mEncoding = encoding;
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetEncoding(char **encoding)
+{
+ NS_PRECONDITION(encoding, "null ptr");
+ if (! encoding)
+ return(NS_ERROR_NULL_POINTER);
+
+ *encoding = ToNewCString(mEncoding);
+ if (!*encoding)
+ return(NS_ERROR_OUT_OF_MEMORY);
+
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::OnStartRequest(nsIRequest *request, nsISupports* aContext)
+{
+ nsresult rv;
+
+ mParser = do_CreateInstance(NS_DIRINDEXPARSER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->SetEncoding(mEncoding.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->SetListener(this);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->OnStartRequest(request,aContext);
+ if (NS_FAILED(rv)) return rv;
+
+ // This should only run once...
+ // Unless we don't have a container to start with
+ // (ie called from bookmarks as an rdf datasource)
+ if (mBindToGlobalObject && mRequestor) {
+ mBindToGlobalObject = false;
+
+ nsCOMPtr<nsIGlobalObject> globalObject = do_GetInterface(mRequestor);
+ NS_ENSURE_TRUE(globalObject, NS_ERROR_FAILURE);
+
+ // We might run script via JS_SetProperty, so we need an AutoEntryScript.
+ // This is Gecko specific and not in any spec.
+ dom::AutoEntryScript aes(globalObject,
+ "nsHTTPIndex set HTTPIndex property");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+
+ // Using XPConnect, wrap the HTTP index object...
+ static NS_DEFINE_CID(kXPConnectCID, NS_XPCONNECT_CID);
+ nsCOMPtr<nsIXPConnect> xpc(do_GetService(kXPConnectCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ JS::Rooted<JSObject*> jsobj(cx);
+ rv = xpc->WrapNative(cx,
+ global,
+ static_cast<nsIHTTPIndex*>(this),
+ NS_GET_IID(nsIHTTPIndex),
+ jsobj.address());
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to xpconnect-wrap http-index");
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(jsobj,
+ "unable to get jsobj from xpconnect wrapper");
+ if (!jsobj) return NS_ERROR_UNEXPECTED;
+
+ JS::Rooted<JS::Value> jslistener(cx, JS::ObjectValue(*jsobj));
+
+ // ...and stuff it into the global context
+ bool ok = JS_SetProperty(cx, global, "HTTPIndex", jslistener);
+ NS_ASSERTION(ok, "unable to set Listener property");
+ if (!ok)
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aContext) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+ NS_ASSERTION(channel, "request should be a channel");
+
+ // lets hijack the notifications:
+ channel->SetNotificationCallbacks(this);
+
+ // now create the top most resource
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ nsAutoCString entryuriC;
+ rv = uri->GetSpec(entryuriC);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFResource> entry;
+ rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry));
+
+ NS_ConvertUTF8toUTF16 uriUnicode(entryuriC);
+
+ nsCOMPtr<nsIRDFLiteral> URLVal;
+ rv = mDirRDF->GetLiteral(uriUnicode.get(), getter_AddRefs(URLVal));
+
+ Assert(entry, kNC_URL, URLVal, true);
+ mDirectory = do_QueryInterface(entry);
+ }
+ else
+ {
+ // Get the directory from the context
+ mDirectory = do_QueryInterface(aContext);
+ }
+
+ if (!mDirectory) {
+ request->Cancel(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ // Mark the directory as "loading"
+ rv = Assert(mDirectory, kNC_Loading,
+ kTrueLiteral, true);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsHTTPIndex::OnStopRequest(nsIRequest *request,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ // If mDirectory isn't set, then we should just bail. Either an
+ // error occurred and OnStartRequest() never got called, or
+ // something exploded in OnStartRequest().
+ if (! mDirectory)
+ return NS_BINDING_ABORTED;
+
+ mParser->OnStopRequest(request,aContext,aStatus);
+
+ nsresult rv;
+
+ nsXPIDLCString commentStr;
+ mParser->GetComment(getter_Copies(commentStr));
+
+ nsCOMPtr<nsIRDFLiteral> comment;
+ rv = mDirRDF->GetLiteral(NS_ConvertASCIItoUTF16(commentStr).get(), getter_AddRefs(comment));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = Assert(mDirectory, kNC_Comment, comment, true);
+ if (NS_FAILED(rv)) return rv;
+
+ // hack: Remove the 'loading' annotation (ignore errors)
+ AddElement(mDirectory, kNC_Loading, kTrueLiteral);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsHTTPIndex::OnDataAvailable(nsIRequest *request,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ // If mDirectory isn't set, then we should just bail. Either an
+ // error occurred and OnStartRequest() never got called, or
+ // something exploded in OnStartRequest().
+ if (! mDirectory)
+ return NS_BINDING_ABORTED;
+
+ return mParser->OnDataAvailable(request, mDirectory, aStream, aSourceOffset, aCount);
+}
+
+
+nsresult
+nsHTTPIndex::OnIndexAvailable(nsIRequest* aRequest, nsISupports *aContext,
+ nsIDirIndex* aIndex)
+{
+ nsCOMPtr<nsIRDFResource> parentRes = do_QueryInterface(aContext);
+ if (!parentRes) {
+ NS_ERROR("Could not obtain parent resource");
+ return(NS_ERROR_UNEXPECTED);
+ }
+
+ const char* baseStr;
+ parentRes->GetValueConst(&baseStr);
+ if (! baseStr) {
+ NS_ERROR("Could not reconstruct base uri");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // we found the filename; construct a resource for its entry
+ nsAutoCString entryuriC(baseStr);
+
+ nsXPIDLCString filename;
+ nsresult rv = aIndex->GetLocation(getter_Copies(filename));
+ if (NS_FAILED(rv)) return rv;
+ entryuriC.Append(filename);
+
+ // if its a directory, make sure it ends with a trailing slash.
+ uint32_t type;
+ rv = aIndex->GetType(&type);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool isDirType = (type == nsIDirIndex::TYPE_DIRECTORY);
+ if (isDirType && entryuriC.Last() != '/') {
+ entryuriC.Append('/');
+ }
+
+ nsCOMPtr<nsIRDFResource> entry;
+ rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry));
+
+ // At this point, we'll (hopefully) have found the filename and
+ // constructed a resource for it, stored in entry. So now take a
+ // second pass through the values and add as statements to the RDF
+ // datasource.
+
+ if (entry && NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRDFLiteral> lit;
+ nsString str;
+
+ str.AssignWithConversion(entryuriC.get());
+
+ rv = mDirRDF->GetLiteral(str.get(), getter_AddRefs(lit));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = Assert(entry, kNC_URL, lit, true);
+ if (NS_FAILED(rv)) return rv;
+
+ nsXPIDLString xpstr;
+
+ // description
+ rv = aIndex->GetDescription(getter_Copies(xpstr));
+ if (NS_FAILED(rv)) return rv;
+ if (xpstr.Last() == '/')
+ xpstr.Truncate(xpstr.Length() - 1);
+
+ rv = mDirRDF->GetLiteral(xpstr.get(), getter_AddRefs(lit));
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_Description, lit, true);
+ if (NS_FAILED(rv)) return rv;
+
+ // contentlength
+ int64_t size;
+ rv = aIndex->GetSize(&size);
+ if (NS_FAILED(rv)) return rv;
+ int64_t minus1 = UINT64_MAX;
+ if (size != minus1) {
+ int32_t intSize = int32_t(size);
+ // XXX RDF should support 64 bit integers (bug 240160)
+ nsCOMPtr<nsIRDFInt> val;
+ rv = mDirRDF->GetIntLiteral(intSize, getter_AddRefs(val));
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_ContentLength, val, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // lastmodified
+ PRTime tm;
+ rv = aIndex->GetLastModified(&tm);
+ if (NS_FAILED(rv)) return rv;
+ if (tm != -1) {
+ nsCOMPtr<nsIRDFDate> val;
+ rv = mDirRDF->GetDateLiteral(tm, getter_AddRefs(val));
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_LastModified, val, true);
+ }
+
+ // filetype
+ uint32_t type;
+ rv = aIndex->GetType(&type);
+ switch (type) {
+ case nsIDirIndex::TYPE_UNKNOWN:
+ rv = mDirRDF->GetLiteral(u"UNKNOWN", getter_AddRefs(lit));
+ break;
+ case nsIDirIndex::TYPE_DIRECTORY:
+ rv = mDirRDF->GetLiteral(u"DIRECTORY", getter_AddRefs(lit));
+ break;
+ case nsIDirIndex::TYPE_FILE:
+ rv = mDirRDF->GetLiteral(u"FILE", getter_AddRefs(lit));
+ break;
+ case nsIDirIndex::TYPE_SYMLINK:
+ rv = mDirRDF->GetLiteral(u"SYMLINK", getter_AddRefs(lit));
+ break;
+ }
+
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_FileType, lit, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Since the definition of a directory depends on the protocol, we would have
+ // to do string comparisons all the time.
+ // But we're told if we're a container right here - so save that fact
+ if (isDirType)
+ Assert(entry, kNC_IsContainer, kTrueLiteral, true);
+ else
+ Assert(entry, kNC_IsContainer, kFalseLiteral, true);
+
+// instead of
+// rv = Assert(parentRes, kNC_Child, entry, true);
+// if (NS_FAILED(rv)) return rv;
+// defer insertion onto a timer so that the UI isn't starved
+ AddElement(parentRes, kNC_Child, entry);
+ }
+
+ return rv;
+}
+
+nsresult
+nsHTTPIndex::OnInformationAvailable(nsIRequest *aRequest,
+ nsISupports *aCtxt,
+ const nsAString& aInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//----------------------------------------------------------------------
+//
+// nsHTTPIndex implementation
+//
+
+nsHTTPIndex::nsHTTPIndex()
+ : mBindToGlobalObject(true),
+ mRequestor(nullptr)
+{
+}
+
+
+nsHTTPIndex::nsHTTPIndex(nsIInterfaceRequestor* aRequestor)
+ : mBindToGlobalObject(true),
+ mRequestor(aRequestor)
+{
+}
+
+
+nsHTTPIndex::~nsHTTPIndex()
+{
+ // note: these are NOT statics due to the native of nsHTTPIndex
+ // where it may or may not be treated as a singleton
+
+ if (mTimer)
+ {
+ // be sure to cancel the timer, as it holds a
+ // weak reference back to nsHTTPIndex
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ mConnectionList = nullptr;
+ mNodeList = nullptr;
+
+ if (mDirRDF)
+ {
+ // UnregisterDataSource() may fail; just ignore errors
+ mDirRDF->UnregisterDataSource(this);
+ }
+}
+
+
+
+nsresult
+nsHTTPIndex::CommonInit()
+{
+ nsresult rv = NS_OK;
+
+ // set initial/default encoding to ISO-8859-1 (not UTF-8)
+ mEncoding = "ISO-8859-1";
+
+ mDirRDF = do_GetService(kRDFServiceCID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
+ if (NS_FAILED(rv)) {
+ return(rv);
+ }
+
+ mInner = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource", &rv);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"),
+ getter_AddRefs(kNC_Child));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "loading"),
+ getter_AddRefs(kNC_Loading));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Comment"),
+ getter_AddRefs(kNC_Comment));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"),
+ getter_AddRefs(kNC_URL));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"),
+ getter_AddRefs(kNC_Description));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Length"),
+ getter_AddRefs(kNC_ContentLength));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastModifiedDate"),
+ getter_AddRefs(kNC_LastModified));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Type"),
+ getter_AddRefs(kNC_ContentType));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "File-Type"),
+ getter_AddRefs(kNC_FileType));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IsContainer"),
+ getter_AddRefs(kNC_IsContainer));
+
+ rv = mDirRDF->GetLiteral(u"true", getter_AddRefs(kTrueLiteral));
+ if (NS_FAILED(rv)) return(rv);
+ rv = mDirRDF->GetLiteral(u"false", getter_AddRefs(kFalseLiteral));
+ if (NS_FAILED(rv)) return(rv);
+
+ mConnectionList = nsArray::Create();
+
+ // note: don't register DS here
+ return rv;
+}
+
+
+nsresult
+nsHTTPIndex::Init()
+{
+ nsresult rv;
+
+ // set initial/default encoding to ISO-8859-1 (not UTF-8)
+ mEncoding = "ISO-8859-1";
+
+ rv = CommonInit();
+ if (NS_FAILED(rv)) return(rv);
+
+ // (do this last) register this as a named data source with the RDF service
+ rv = mDirRDF->RegisterDataSource(this, false);
+ if (NS_FAILED(rv)) return(rv);
+
+ return(NS_OK);
+}
+
+
+
+nsresult
+nsHTTPIndex::Init(nsIURI* aBaseURL)
+{
+ NS_PRECONDITION(aBaseURL != nullptr, "null ptr");
+ if (! aBaseURL)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ rv = CommonInit();
+ if (NS_FAILED(rv)) return(rv);
+
+ // note: don't register DS here (singleton case)
+
+ rv = aBaseURL->GetSpec(mBaseURL);
+ if (NS_FAILED(rv)) return rv;
+
+ // Mark the base url as a container
+ nsCOMPtr<nsIRDFResource> baseRes;
+ mDirRDF->GetResource(mBaseURL, getter_AddRefs(baseRes));
+ Assert(baseRes, kNC_IsContainer, kTrueLiteral, true);
+
+ return NS_OK;
+}
+
+
+
+nsresult
+nsHTTPIndex::Create(nsIURI* aBaseURL, nsIInterfaceRequestor* aRequestor,
+ nsIHTTPIndex** aResult)
+{
+ *aResult = nullptr;
+
+ nsHTTPIndex* result = new nsHTTPIndex(aRequestor);
+ nsresult rv = result->Init(aBaseURL);
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_ADDREF(result);
+ *aResult = result;
+ }
+ else
+ {
+ delete result;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetBaseURL(char** _result)
+{
+ *_result = ToNewCString(mBaseURL);
+ if (! *_result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetDataSource(nsIRDFDataSource** _result)
+{
+ NS_ADDREF(*_result = this);
+ return NS_OK;
+}
+
+// This function finds the destination when following a given nsIRDFResource
+// If the resource has a URL attribute, we use that. If not, just use
+// the uri.
+//
+// Do NOT try to get the destination of a uri in any other way
+void nsHTTPIndex::GetDestination(nsIRDFResource* r, nsXPIDLCString& dest) {
+ // First try the URL attribute
+ nsCOMPtr<nsIRDFNode> node;
+
+ GetTarget(r, kNC_URL, true, getter_AddRefs(node));
+ nsCOMPtr<nsIRDFLiteral> url;
+
+ if (node)
+ url = do_QueryInterface(node);
+
+ if (!url) {
+ const char* temp;
+ r->GetValueConst(&temp);
+ dest.Adopt(temp ? strdup(temp) : 0);
+ } else {
+ const char16_t* uri;
+ url->GetValueConst(&uri);
+ dest.Adopt(ToNewUTF8String(nsDependentString(uri)));
+ }
+}
+
+// rjc: isWellknownContainerURI() decides whether a URI is a container for which,
+// when asked (say, by the template builder), we'll make a network connection
+// to get its contents. For the moment, all we speak is ftp:// URLs, even though
+// a) we can get "http-index" mimetypes for really anything
+// b) we could easily handle file:// URLs here
+// Q: Why don't we?
+// A: The file system datasource ("rdf:file"); at some point, the two
+// should be perhaps united. Until then, we can't aggregate both
+// "rdf:file" and "http-index" (such as with bookmarks) because we'd
+// get double the # of answers we really want... also, "rdf:file" is
+// less expensive in terms of both memory usage as well as speed
+
+
+
+// We use an rdf attribute to mark if this is a container or not.
+// Note that we still have to do string comparisons as a fallback
+// because stuff like the personal toolbar and bookmarks check whether
+// a URL is a container, and we have no attribute in that case.
+bool
+nsHTTPIndex::isWellknownContainerURI(nsIRDFResource *r)
+{
+ nsCOMPtr<nsIRDFNode> node;
+ GetTarget(r, kNC_IsContainer, true, getter_AddRefs(node));
+ if (node) {
+ bool isContainerFlag;
+ if (NS_SUCCEEDED(node->EqualsNode(kTrueLiteral, &isContainerFlag)))
+ return isContainerFlag;
+ }
+
+ nsXPIDLCString uri;
+ GetDestination(r, uri);
+ return uri.get() && !strncmp(uri, kFTPProtocol, sizeof(kFTPProtocol) - 1) &&
+ (uri.Last() == '/');
+}
+
+
+NS_IMETHODIMP
+nsHTTPIndex::GetURI(char * *uri)
+{
+ NS_PRECONDITION(uri != nullptr, "null ptr");
+ if (! uri)
+ return(NS_ERROR_NULL_POINTER);
+
+ if ((*uri = strdup("rdf:httpindex")) == nullptr)
+ return(NS_ERROR_OUT_OF_MEMORY);
+
+ return(NS_OK);
+}
+
+
+
+NS_IMETHODIMP
+nsHTTPIndex::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue,
+ nsIRDFResource **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ *_retval = nullptr;
+
+ if (mInner)
+ {
+ rv = mInner->GetSource(aProperty, aTarget, aTruthValue, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue,
+ nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ if (mInner)
+ {
+ rv = mInner->GetSources(aProperty, aTarget, aTruthValue, _retval);
+ }
+ else
+ {
+ rv = NS_NewEmptyEnumerator(_retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue,
+ nsIRDFNode **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ *_retval = nullptr;
+
+ if ((aTruthValue) && (aProperty == kNC_Child) && isWellknownContainerURI(aSource))
+ {
+ // fake out the generic builder (i.e. return anything in this case)
+ // so that search containers never appear to be empty
+ NS_IF_ADDREF(aSource);
+ *_retval = aSource;
+ return(NS_OK);
+ }
+
+ if (mInner)
+ {
+ rv = mInner->GetTarget(aSource, aProperty, aTruthValue, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue,
+ nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ if (mInner)
+ {
+ rv = mInner->GetTargets(aSource, aProperty, aTruthValue, _retval);
+ }
+ else
+ {
+ rv = NS_NewEmptyEnumerator(_retval);
+ }
+
+ if ((aProperty == kNC_Child) && isWellknownContainerURI(aSource))
+ {
+ bool doNetworkRequest = true;
+ if (NS_SUCCEEDED(rv) && (_retval))
+ {
+ // check and see if we already have data for the search in question;
+ // if we do, don't bother doing the search again
+ bool hasResults;
+ if (NS_SUCCEEDED((*_retval)->HasMoreElements(&hasResults)) &&
+ hasResults)
+ doNetworkRequest = false;
+ }
+
+ // Note: if we need to do a network request, do it out-of-band
+ // (because the XUL template builder isn't re-entrant)
+ // by using a global connection list and an immediately-firing timer
+ if (doNetworkRequest && mConnectionList)
+ {
+ uint32_t connectionIndex;
+ nsresult idx_rv = mConnectionList->IndexOf(0, aSource, &connectionIndex);
+ if (NS_FAILED(idx_rv))
+ {
+ // add aSource into list of connections to make
+ mConnectionList->AppendElement(aSource, /*weak =*/ false);
+
+ // if we don't have a timer about to fire, create one
+ // which should fire as soon as possible (out-of-band)
+ if (!mTimer)
+ {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer");
+ if (NS_SUCCEEDED(rv))
+ {
+ mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1,
+ nsITimer::TYPE_ONE_SHOT);
+ // Note: don't addref "this" as we'll cancel the
+ // timer in the httpIndex destructor
+ }
+ }
+ }
+ }
+ }
+
+ return(rv);
+}
+
+
+nsresult
+nsHTTPIndex::AddElement(nsIRDFResource *parent, nsIRDFResource *prop, nsIRDFNode *child)
+{
+ nsresult rv;
+
+ if (!mNodeList)
+ {
+ mNodeList = nsArray::Create();
+ }
+
+ // order required: parent, prop, then child
+ mNodeList->AppendElement(parent, /*weak =*/ false);
+ mNodeList->AppendElement(prop, /*weak =*/ false);
+ mNodeList->AppendElement(child, /*weak = */ false);
+
+ if (!mTimer)
+ {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer");
+ if (NS_FAILED(rv)) return(rv);
+
+ mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1,
+ nsITimer::TYPE_ONE_SHOT);
+ // Note: don't addref "this" as we'll cancel the
+ // timer in the httpIndex destructor
+ }
+
+ return(NS_OK);
+}
+
+void
+nsHTTPIndex::FireTimer(nsITimer* aTimer, void* aClosure)
+{
+ nsHTTPIndex *httpIndex = static_cast<nsHTTPIndex *>(aClosure);
+ if (!httpIndex)
+ return;
+
+ // don't return out of this loop as mTimer may need to be cancelled afterwards
+ uint32_t numItems = 0;
+ if (httpIndex->mConnectionList)
+ {
+ httpIndex->mConnectionList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ nsCOMPtr<nsIRDFResource> source =
+ do_QueryElementAt(httpIndex->mConnectionList, 0);
+ httpIndex->mConnectionList->RemoveElementAt(0);
+
+ nsXPIDLCString uri;
+ if (source) {
+ httpIndex->GetDestination(source, uri);
+ }
+
+ if (!uri) {
+ NS_ERROR("Could not reconstruct uri");
+ return;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> url;
+
+ rv = NS_NewURI(getter_AddRefs(url), uri.get());
+ nsCOMPtr<nsIChannel> channel;
+ if (NS_SUCCEEDED(rv) && (url)) {
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ url,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ }
+ if (NS_SUCCEEDED(rv) && (channel)) {
+ channel->SetNotificationCallbacks(httpIndex);
+ rv = channel->AsyncOpen2(httpIndex);
+ }
+ }
+ }
+
+ if (httpIndex->mNodeList)
+ {
+ httpIndex->mNodeList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ // account for order required: src, prop, then target
+ numItems /=3;
+ if (numItems > 10)
+ numItems = 10;
+
+ int32_t loop;
+ for (loop=0; loop<(int32_t)numItems; loop++)
+ {
+ nsCOMPtr<nsIRDFResource> src = do_QueryElementAt(httpIndex->mNodeList, 0);
+ httpIndex->mNodeList->RemoveElementAt(0);
+
+ nsCOMPtr<nsIRDFResource> prop = do_QueryElementAt(httpIndex->mNodeList, 0);
+ httpIndex->mNodeList->RemoveElementAt(0);
+
+ nsCOMPtr<nsIRDFNode> target = do_QueryElementAt(httpIndex->mNodeList, 0);
+ httpIndex->mNodeList->RemoveElementAt(0);
+
+ if (src && prop && target)
+ {
+ if (prop.get() == httpIndex->kNC_Loading)
+ {
+ httpIndex->Unassert(src, prop, target);
+ }
+ else
+ {
+ httpIndex->Assert(src, prop, target, true);
+ }
+ }
+ }
+ }
+ }
+
+ bool refireTimer = false;
+ // check both lists to see if the timer needs to continue firing
+ if (httpIndex->mConnectionList)
+ {
+ httpIndex->mConnectionList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ refireTimer = true;
+ }
+ else
+ {
+ httpIndex->mConnectionList->Clear();
+ }
+ }
+
+ if (httpIndex->mNodeList)
+ {
+ httpIndex->mNodeList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ refireTimer = true;
+ }
+ else
+ {
+ httpIndex->mNodeList->Clear();
+ }
+ }
+
+ // be sure to cancel the timer, as it holds a
+ // weak reference back to nsHTTPIndex
+ httpIndex->mTimer->Cancel();
+ httpIndex->mTimer = nullptr;
+
+ // after firing off any/all of the connections be sure
+ // to cancel the timer if we don't need to refire it
+ if (refireTimer)
+ {
+ httpIndex->mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (httpIndex->mTimer)
+ {
+ httpIndex->mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, aClosure, 10,
+ nsITimer::TYPE_ONE_SHOT);
+ // Note: don't addref "this" as we'll cancel the
+ // timer in the httpIndex destructor
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget,
+ bool aTruthValue)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Unassert(aSource, aProperty, aTarget);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty,
+ nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Change(aSource, aProperty, aOldTarget, aNewTarget);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource,
+ nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Move(aOldSource, aNewSource, aProperty, aTarget);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget, bool aTruthValue, bool *_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::AddObserver(nsIRDFObserver *aObserver)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->AddObserver(aObserver);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::RemoveObserver(nsIRDFObserver *aObserver)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->RemoveObserver(aObserver);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
+{
+ if (!mInner) {
+ *result = false;
+ return NS_OK;
+ }
+ return mInner->HasArcIn(aNode, aArc, result);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
+{
+ if (aArc == kNC_Child && isWellknownContainerURI(aSource)) {
+ *result = true;
+ return NS_OK;
+ }
+
+ if (mInner) {
+ return mInner->HasArcOut(aSource, aArc, result);
+ }
+
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->ArcLabelsIn(aNode, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ *_retval = nullptr;
+
+ nsCOMPtr<nsISimpleEnumerator> child, anonArcs;
+ if (isWellknownContainerURI(aSource))
+ {
+ NS_NewSingletonEnumerator(getter_AddRefs(child), kNC_Child);
+ }
+
+ if (mInner)
+ {
+ mInner->ArcLabelsOut(aSource, getter_AddRefs(anonArcs));
+ }
+
+ return NS_NewUnionEnumerator(_retval, child, anonArcs);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetAllResources(nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->GetAllResources(_retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand,
+ nsISupports *aArguments, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand,
+ nsISupports *aArguments)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::BeginUpdateBatch()
+{
+ return mInner->BeginUpdateBatch();
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::EndUpdateBatch()
+{
+ return mInner->EndUpdateBatch();
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->GetAllCmds(aSource, _retval);
+ }
+ return(rv);
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsDirectoryViewerFactory
+//
+nsDirectoryViewerFactory::nsDirectoryViewerFactory()
+{
+}
+
+
+
+nsDirectoryViewerFactory::~nsDirectoryViewerFactory()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(nsDirectoryViewerFactory, nsIDocumentLoaderFactory)
+
+
+
+NS_IMETHODIMP
+nsDirectoryViewerFactory::CreateInstance(const char *aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ const nsACString& aContentType,
+ nsIDocShell* aContainer,
+ nsISupports* aExtraInfo,
+ nsIStreamListener** aDocListenerResult,
+ nsIContentViewer** aDocViewerResult)
+{
+ nsresult rv;
+
+ bool viewSource = FindInReadable(NS_LITERAL_CSTRING("view-source"),
+ aContentType);
+
+ if (!viewSource &&
+ Preferences::GetInt("network.dir.format", FORMAT_XUL) == FORMAT_XUL) {
+ // ... and setup the original channel's content type
+ (void)aChannel->SetContentType(NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml"));
+
+ // This is where we shunt the HTTP/Index stream into our datasource,
+ // and open the directory viewer XUL file as the content stream to
+ // load in its place.
+
+ // Create a dummy loader that will load a stub XUL document.
+ nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ nsXPIDLCString contractID;
+ rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "application/vnd.mozilla.xul+xml",
+ getter_Copies(contractID));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), "chrome://communicator/content/directory/directory.xul");
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ aLoadGroup);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = factory->CreateInstance(aCommand, channel, aLoadGroup,
+ NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml"),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewerResult);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = channel->AsyncOpen2(listener);
+ if (NS_FAILED(rv)) return rv;
+
+ // Create an HTTPIndex object so that we can stuff it into the script context
+ nsCOMPtr<nsIURI> baseuri;
+ rv = aChannel->GetURI(getter_AddRefs(baseuri));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface(aContainer,&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIHTTPIndex> httpindex;
+ rv = nsHTTPIndex::Create(baseuri, requestor, getter_AddRefs(httpindex));
+ if (NS_FAILED(rv)) return rv;
+
+ // Now shanghai the stream into our http-index parsing datasource
+ // wrapper beastie.
+ listener = do_QueryInterface(httpindex,&rv);
+ *aDocListenerResult = listener.get();
+ NS_ADDREF(*aDocListenerResult);
+
+ return NS_OK;
+ }
+
+ // setup the original channel's content type
+ (void)aChannel->SetContentType(NS_LITERAL_CSTRING("text/html"));
+
+ // Otherwise, lets use the html listing
+ nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ nsXPIDLCString contractID;
+ rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "text/html",
+ getter_Copies(contractID));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListener> listener;
+
+ if (viewSource) {
+ rv = factory->CreateInstance("view-source", aChannel, aLoadGroup,
+ NS_LITERAL_CSTRING("text/html; x-view-type=view-source"),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewerResult);
+ } else {
+ rv = factory->CreateInstance("view", aChannel, aLoadGroup,
+ NS_LITERAL_CSTRING("text/html"),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewerResult);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamConverterService> scs = do_GetService("@mozilla.org/streamConverters;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = scs->AsyncConvertData("application/http-index-format",
+ "text/html",
+ listener,
+ nullptr,
+ aDocListenerResult);
+
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsDirectoryViewerFactory::CreateInstanceForDocument(nsISupports* aContainer,
+ nsIDocument* aDocument,
+ const char *aCommand,
+ nsIContentViewer** aDocViewerResult)
+{
+ NS_NOTYETIMPLEMENTED("didn't expect to get here");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDirectoryViewerFactory::CreateBlankDocument(nsILoadGroup *aLoadGroup,
+ nsIPrincipal *aPrincipal,
+ nsIDocument **_retval) {
+
+ NS_NOTYETIMPLEMENTED("didn't expect to get here");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/xpfe/components/directory/nsDirectoryViewer.h b/xpfe/components/directory/nsDirectoryViewer.h
new file mode 100644
index 000000000..05b68f1b6
--- /dev/null
+++ b/xpfe/components/directory/nsDirectoryViewer.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsdirectoryviewer__h____
+#define nsdirectoryviewer__h____
+
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsIContentViewer.h"
+#include "nsIHTTPIndex.h"
+#include "nsIRDFService.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFLiteral.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsITimer.h"
+#include "nsXPIDLString.h"
+#include "nsIDirIndexListener.h"
+#include "nsIFTPChannel.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIURI.h"
+
+class nsIMutableArray;
+
+class nsDirectoryViewerFactory : public nsIDocumentLoaderFactory
+{
+public:
+ nsDirectoryViewerFactory();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTLOADERFACTORY
+
+protected:
+ virtual ~nsDirectoryViewerFactory();
+};
+
+class nsHTTPIndex final : public nsIHTTPIndex,
+ public nsIRDFDataSource,
+ public nsIStreamListener,
+ public nsIDirIndexListener,
+ public nsIInterfaceRequestor,
+ public nsIFTPEventSink
+{
+private:
+
+ // note: these are NOT statics due to the native of nsHTTPIndex
+ // where it may or may not be treated as a singleton
+
+ nsCOMPtr<nsIRDFResource> kNC_Child;
+ nsCOMPtr<nsIRDFResource> kNC_Comment;
+ nsCOMPtr<nsIRDFResource> kNC_Loading;
+ nsCOMPtr<nsIRDFResource> kNC_URL;
+ nsCOMPtr<nsIRDFResource> kNC_Description;
+ nsCOMPtr<nsIRDFResource> kNC_ContentLength;
+ nsCOMPtr<nsIRDFResource> kNC_LastModified;
+ nsCOMPtr<nsIRDFResource> kNC_ContentType;
+ nsCOMPtr<nsIRDFResource> kNC_FileType;
+ nsCOMPtr<nsIRDFResource> kNC_IsContainer;
+ nsCOMPtr<nsIRDFLiteral> kTrueLiteral;
+ nsCOMPtr<nsIRDFLiteral> kFalseLiteral;
+
+ nsCOMPtr<nsIRDFService> mDirRDF;
+
+protected:
+ // We grab a reference to the content viewer container (which
+ // indirectly owns us) so that we can insert ourselves as a global
+ // in the script context _after_ the XUL doc has been embedded into
+ // content viewer. We'll know that this has happened once we receive
+ // an OnStartRequest() notification
+
+ nsCOMPtr<nsIRDFDataSource> mInner;
+ nsCOMPtr<nsIMutableArray> mConnectionList;
+ nsCOMPtr<nsIMutableArray> mNodeList;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIDirIndexParser> mParser;
+ nsCString mBaseURL;
+ nsCString mEncoding;
+ bool mBindToGlobalObject;
+ nsIInterfaceRequestor* mRequestor; // WEAK
+ nsCOMPtr<nsIRDFResource> mDirectory;
+
+ explicit nsHTTPIndex(nsIInterfaceRequestor* aRequestor);
+ nsresult CommonInit(void);
+ nsresult Init(nsIURI* aBaseURL);
+ void GetDestination(nsIRDFResource* r, nsXPIDLCString& dest);
+ bool isWellknownContainerURI(nsIRDFResource *r);
+ nsresult AddElement(nsIRDFResource *parent, nsIRDFResource *prop,
+ nsIRDFNode *child);
+
+ static void FireTimer(nsITimer* aTimer, void* aClosure);
+
+ virtual ~nsHTTPIndex();
+
+public:
+ nsHTTPIndex();
+ nsresult Init(void);
+
+ static nsresult Create(nsIURI* aBaseURI, nsIInterfaceRequestor* aContainer,
+ nsIHTTPIndex** aResult);
+
+ // nsIHTTPIndex interface
+ NS_DECL_NSIHTTPINDEX
+
+ // NSIRDFDataSource interface
+ NS_DECL_NSIRDFDATASOURCE
+
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ NS_DECL_NSIDIRINDEXLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIFTPEVENTSINK
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHTTPIndex, nsIHTTPIndex)
+};
+
+// {82776710-5690-11d3-BE36-00104BDE6048}
+#define NS_DIRECTORYVIEWERFACTORY_CID \
+{ 0x82776710, 0x5690, 0x11d3, { 0xbe, 0x36, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 } }
+
+#endif // nsdirectoryviewer__h____
diff --git a/xpfe/components/directory/nsIHTTPIndex.idl b/xpfe/components/directory/nsIHTTPIndex.idl
new file mode 100644
index 000000000..47697172b
--- /dev/null
+++ b/xpfe/components/directory/nsIHTTPIndex.idl
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ The interface to an HTTP index
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+interface nsIRDFDataSource;
+interface nsIRDFNode;
+interface nsIRDFResource;
+
+[scriptable, uuid(6F2BDBD0-58C3-11d3-BE36-00104BDE6048)]
+interface nsIHTTPIndex : nsISupports
+{
+ /**
+ * The base URL of the HTTP index
+ */
+ readonly attribute string BaseURL;
+
+ /**
+ * The RDF datasource that contains the HTTP index information.
+ */
+ readonly attribute nsIRDFDataSource DataSource;
+
+ /**
+ * The charset to use for decoding FTP filenames
+ */
+ attribute string encoding;
+};
+
+%{C++
+
+// {{2587e382-1324-11d4-a652-eadbb2be3484}
+#define NS_HTTPINDEX_SERVICE_CID \
+{ 0x2587e382, 0x1324, 0x11d4, { 0xa6, 0x52, 0xea, 0xdb, 0xb2, 0xbe, 0x34, 0x84 } }
+
+#define NS_HTTPINDEX_SERVICE_CONTRACTID \
+ "@mozilla.org/browser/httpindex-service;1"
+
+#define NS_HTTPINDEX_DATASOURCE_CONTRACTID \
+ "@mozilla.org/rdf/datasource;1?name=httpindex"
+
+%}
diff --git a/xpfe/components/moz.build b/xpfe/components/moz.build
new file mode 100644
index 000000000..cb23f336e
--- /dev/null
+++ b/xpfe/components/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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 += [
+ 'windowds',
+ 'directory',
+ 'build',
+]
+
diff --git a/xpfe/components/windowds/moz.build b/xpfe/components/windowds/moz.build
new file mode 100644
index 000000000..0e7536b16
--- /dev/null
+++ b/xpfe/components/windowds/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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 += [
+ 'nsIWindowDataSource.idl',
+]
+
+XPIDL_MODULE = 'windowds'
+
+SOURCES += [
+ 'nsWindowDataSource.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/xpfe/components/windowds/nsIWindowDataSource.idl b/xpfe/components/windowds/nsIWindowDataSource.idl
new file mode 100644
index 000000000..6143a4317
--- /dev/null
+++ b/xpfe/components/windowds/nsIWindowDataSource.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIDOMWindow.idl"
+
+// interface for accessing RDF-specific data from the window datasource
+[scriptable, uuid(3722A5B9-5323-4ed0-BB1A-8299F27A4E89)]
+interface nsIWindowDataSource : nsISupports
+{
+ /**
+ * for the given resource name, return the window
+ */
+ nsIDOMWindow getWindowForResource(in string inResource);
+};
diff --git a/xpfe/components/windowds/nsWindowDataSource.cpp b/xpfe/components/windowds/nsWindowDataSource.cpp
new file mode 100644
index 000000000..3e7a42060
--- /dev/null
+++ b/xpfe/components/windowds/nsWindowDataSource.cpp
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowDataSource.h"
+#include "nsIXULWindow.h"
+#include "rdf.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsIObserverService.h"
+#include "nsIWindowMediator.h"
+#include "nsXPCOMCID.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsString.h"
+
+// just to do the reverse-lookup! sheesh.
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDocShell.h"
+
+uint32_t nsWindowDataSource::windowCount = 0;
+
+nsIRDFResource* nsWindowDataSource::kNC_Name = nullptr;
+nsIRDFResource* nsWindowDataSource::kNC_WindowRoot = nullptr;
+nsIRDFResource* nsWindowDataSource::kNC_KeyIndex = nullptr;
+
+nsIRDFService* nsWindowDataSource::gRDFService = nullptr;
+
+uint32_t nsWindowDataSource::gRefCnt = 0;
+
+#define URINC_WINDOWROOT "NC:WindowMediatorRoot"
+#define URINC_NAME NC_NAMESPACE_URI "Name"
+#define URINC_KEYINDEX NC_NAMESPACE_URI "KeyIndex"
+
+nsresult
+nsWindowDataSource::Init()
+{
+ nsresult rv;
+
+ if (gRefCnt++ == 0) {
+ rv = CallGetService("@mozilla.org/rdf/rdf-service;1", &gRDFService);
+ if (NS_FAILED(rv)) return rv;
+
+ gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_WINDOWROOT), &kNC_WindowRoot);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_NAME), &kNC_Name);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_KEYINDEX), &kNC_KeyIndex);
+ }
+
+ mInner = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFContainerUtils> rdfc =
+ do_GetService("@mozilla.org/rdf/container-utils;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = rdfc->MakeSeq(this, kNC_WindowRoot, getter_AddRefs(mContainer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = windowMediator->AddListener(this);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ }
+ return NS_OK;
+}
+
+nsWindowDataSource::~nsWindowDataSource()
+{
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(kNC_Name);
+ NS_IF_RELEASE(kNC_KeyIndex);
+ NS_IF_RELEASE(kNC_WindowRoot);
+ NS_IF_RELEASE(gRDFService);
+ }
+}
+
+NS_IMETHODIMP
+nsWindowDataSource::Observe(nsISupports *aSubject, const char* aTopic, const char16_t *aData)
+{
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ // release these objects so that they release their reference
+ // to us
+ mContainer = nullptr;
+ mInner = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsWindowDataSource)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsWindowDataSource)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsWindowDataSource)
+ // XXX mContainer?
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsWindowDataSource)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsWindowDataSource)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsWindowDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIWindowMediatorListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWindowDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+// nsIWindowMediatorListener implementation
+// handle notifications from the window mediator and reflect them into
+// RDF
+
+NS_IMETHODIMP
+nsWindowDataSource::OnWindowTitleChange(nsIXULWindow *window,
+ const char16_t *newTitle)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFResource> windowResource;
+ mWindowResources.Get(window, getter_AddRefs(windowResource));
+
+ // oops, make sure this window is in the hashtable!
+ if (!windowResource) {
+ OnOpenWindow(window);
+ mWindowResources.Get(window, getter_AddRefs(windowResource));
+ }
+
+ NS_ENSURE_TRUE(windowResource, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIRDFLiteral> newTitleLiteral;
+ rv = gRDFService->GetLiteral(newTitle, getter_AddRefs(newTitleLiteral));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the old title
+ nsCOMPtr<nsIRDFNode> oldTitleNode;
+ rv = GetTarget(windowResource, kNC_Name, true,
+ getter_AddRefs(oldTitleNode));
+
+ // assert the change
+ if (NS_SUCCEEDED(rv) && oldTitleNode)
+ // has an existing window title, update it
+ rv = Change(windowResource, kNC_Name, oldTitleNode, newTitleLiteral);
+ else
+ // removed from the tasklist
+ rv = Assert(windowResource, kNC_Name, newTitleLiteral, true);
+
+ if (rv != NS_RDF_ASSERTION_ACCEPTED)
+ {
+ NS_ERROR("unable to set window name");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowDataSource::OnOpenWindow(nsIXULWindow *window)
+{
+ nsAutoCString windowId(NS_LITERAL_CSTRING("window-"));
+ windowId.AppendInt(windowCount++, 10);
+
+ nsCOMPtr<nsIRDFResource> windowResource;
+ gRDFService->GetResource(windowId, getter_AddRefs(windowResource));
+
+ mWindowResources.Put(window, windowResource);
+
+ // assert the new window
+ if (mContainer)
+ mContainer->AppendElement(windowResource);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowDataSource::OnCloseWindow(nsIXULWindow *window)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRDFResource> resource;
+ mWindowResources.Get(window, getter_AddRefs(resource));
+ if (!resource) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWindowResources.Remove(window);
+
+ // make sure we're not shutting down
+ if (!mContainer) return NS_OK;
+
+ nsCOMPtr<nsIRDFNode> oldKeyNode;
+ nsCOMPtr<nsIRDFInt> oldKeyInt;
+
+ // get the old keyIndex, if any
+ rv = GetTarget(resource, kNC_KeyIndex, true,
+ getter_AddRefs(oldKeyNode));
+ if (NS_SUCCEEDED(rv) && (rv != NS_RDF_NO_VALUE))
+ oldKeyInt = do_QueryInterface(oldKeyNode);
+
+
+ // update RDF and keyindex - from this point forward we'll ignore
+ // errors, because they just indicate some kind of RDF inconsistency
+ int32_t winIndex = -1;
+ rv = mContainer->IndexOf(resource, &winIndex);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ // unassert the old window, ignore any error
+ mContainer->RemoveElement(resource, true);
+
+ nsCOMPtr<nsISimpleEnumerator> children;
+ rv = mContainer->GetElements(getter_AddRefs(children));
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ bool more = false;
+
+ while (NS_SUCCEEDED(rv = children->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> sup;
+ rv = children->GetNext(getter_AddRefs(sup));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIRDFResource> windowResource = do_QueryInterface(sup, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ int32_t currentIndex = -1;
+ mContainer->IndexOf(windowResource, &currentIndex);
+
+ // can skip updating windows with lower indexes
+ // than the window that was removed
+ if (currentIndex < winIndex)
+ continue;
+
+ nsCOMPtr<nsIRDFNode> newKeyNode;
+ nsCOMPtr<nsIRDFInt> newKeyInt;
+
+ rv = GetTarget(windowResource, kNC_KeyIndex, true,
+ getter_AddRefs(newKeyNode));
+ if (NS_SUCCEEDED(rv) && (rv != NS_RDF_NO_VALUE))
+ newKeyInt = do_QueryInterface(newKeyNode);
+
+ // changing from one key index to another
+ if (oldKeyInt && newKeyInt)
+ Change(windowResource, kNC_KeyIndex, oldKeyInt, newKeyInt);
+ // creating a new keyindex - probably window going
+ // from (none) to "9"
+ else if (newKeyInt)
+ Assert(windowResource, kNC_KeyIndex, newKeyInt, true);
+
+ // somehow inserting a window above this one,
+ // "9" to (none)
+ else if (oldKeyInt)
+ Unassert(windowResource, kNC_KeyIndex, oldKeyInt);
+
+ }
+ return NS_OK;
+}
+
+// nsIWindowDataSource implementation
+
+NS_IMETHODIMP
+nsWindowDataSource::GetWindowForResource(const char *aResourceString,
+ nsIDOMWindow** aResult)
+{
+ if (NS_WARN_IF(!aResourceString)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIRDFResource> windowResource;
+ gRDFService->GetResource(nsDependentCString(aResourceString),
+ getter_AddRefs(windowResource));
+
+ // now reverse-lookup in the hashtable
+ for (auto iter = mWindowResources.Iter(); !iter.Done(); iter.Next()) {
+ nsIXULWindow* window = iter.Key();
+ nsIRDFResource* resource = iter.UserData();
+
+ if (resource == windowResource) {
+ // This sucks, we have to jump through docshell to go from
+ // nsIXULWindow -> nsIDOMWindow.
+ nsCOMPtr<nsIDocShell> docShell;
+ window->GetDocShell(getter_AddRefs(docShell));
+
+ if (docShell) {
+ nsCOMPtr<nsIDOMWindow> result = do_GetInterface(docShell);
+
+ *aResult = result;
+ NS_IF_ADDREF(*aResult);
+ }
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+// nsIRDFDataSource implementation
+// mostly, we just forward to mInner, except:
+// GetURI() - need to return "rdf:window-mediator"
+// GetTarget() - need to handle kNC_KeyIndex
+
+
+NS_IMETHODIMP nsWindowDataSource::GetURI(char * *aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ *aURI = ToNewCString(NS_LITERAL_CSTRING("rdf:window-mediator"));
+
+ if (!*aURI)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsIRDFNode **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // add extra nullptr checking for top-crash bug # 146466
+ if (!gRDFService) return NS_RDF_NO_VALUE;
+ if (!mInner) return NS_RDF_NO_VALUE;
+ if (!mContainer) return NS_RDF_NO_VALUE;
+ // special case kNC_KeyIndex before we forward to mInner
+ if (aProperty == kNC_KeyIndex) {
+
+ int32_t theIndex = 0;
+ nsresult rv = mContainer->IndexOf(aSource, &theIndex);
+ if (NS_FAILED(rv)) return rv;
+
+ // only allow the range of 1 to 9 for single key access
+ if (theIndex < 1 || theIndex > 9) return(NS_RDF_NO_VALUE);
+
+ nsCOMPtr<nsIRDFInt> indexInt;
+ rv = gRDFService->GetIntLiteral(theIndex, getter_AddRefs(indexInt));
+ if (NS_FAILED(rv)) return(rv);
+ if (!indexInt) return(NS_ERROR_FAILURE);
+
+ indexInt.forget(_retval);
+ return NS_OK;
+ }
+
+ return mInner->GetTarget(aSource, aProperty, aTruthValue, _retval);
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsIRDFResource **_retval)
+{
+ if (mInner)
+ return mInner->GetSource(aProperty, aTarget, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetSources(aProperty, aTarget, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetTargets(aSource, aProperty, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue)
+{
+ if (mInner)
+ return mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ if (mInner)
+ return mInner->Unassert(aSource, aProperty, aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget)
+{
+ if (mInner)
+ return mInner->Change(aSource, aProperty, aOldTarget, aNewTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ if (mInner)
+ return mInner->Move(aOldSource, aNewSource, aProperty, aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, bool *_retval)
+{
+ if (mInner)
+ return mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::AddObserver(nsIRDFObserver *aObserver)
+{
+ if (mInner)
+ return mInner->AddObserver(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::RemoveObserver(nsIRDFObserver *aObserver)
+{
+ if (mInner)
+ return mInner->RemoveObserver(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->ArcLabelsIn(aNode, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->ArcLabelsOut(aSource, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetAllResources(nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetAllResources(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsWindowDataSource::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetAllCmds(aSource, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *_retval)
+{
+ if (mInner)
+ return mInner->HasArcIn(aNode, aArc, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *_retval)
+{
+ if (mInner)
+ return mInner->HasArcOut(aSource, aArc, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::BeginUpdateBatch()
+{
+ if (mInner)
+ return mInner->BeginUpdateBatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::EndUpdateBatch()
+{
+ if (mInner)
+ return mInner->EndUpdateBatch();
+ return NS_OK;
+}
+
+// The module goop
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowDataSource, Init)
+
+NS_DEFINE_NAMED_CID(NS_WINDOWDATASOURCE_CID);
+
+static const mozilla::Module::CIDEntry kWindowDSCIDs[] = {
+ { &kNS_WINDOWDATASOURCE_CID, false, nullptr, nsWindowDataSourceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWindowDSContracts[] = {
+ { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "window-mediator", &kNS_WINDOWDATASOURCE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kWindowDSCategories[] = {
+ { "app-startup", "Window Data Source", "service," NS_RDF_DATASOURCE_CONTRACTID_PREFIX "window-mediator" },
+ { nullptr }
+};
+
+static const mozilla::Module kWindowDSModule = {
+ mozilla::Module::kVersion,
+ kWindowDSCIDs,
+ kWindowDSContracts,
+ kWindowDSCategories
+};
+
+NSMODULE_DEFN(nsWindowDataSourceModule) = &kWindowDSModule;
diff --git a/xpfe/components/windowds/nsWindowDataSource.h b/xpfe/components/windowds/nsWindowDataSource.h
new file mode 100644
index 000000000..351ff951b
--- /dev/null
+++ b/xpfe/components/windowds/nsWindowDataSource.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRDFDataSource.h"
+#include "nsIWindowMediatorListener.h"
+#include "nsIWindowDataSource.h"
+#include "nsIObserver.h"
+
+#include "nsHashKeys.h"
+#include "nsIRDFService.h"
+#include "nsIRDFContainer.h"
+#include "nsInterfaceHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+
+// {C744CA3D-840B-460a-8D70-7CE63C51C958}
+#define NS_WINDOWDATASOURCE_CID \
+{ 0xc744ca3d, 0x840b, 0x460a, \
+ { 0x8d, 0x70, 0x7c, 0xe6, 0x3c, 0x51, 0xc9, 0x58 } }
+
+
+class nsWindowDataSource final : public nsIRDFDataSource,
+ public nsIObserver,
+ public nsIWindowMediatorListener,
+ public nsIWindowDataSource
+{
+ public:
+ nsWindowDataSource() { }
+
+ nsresult Init();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsWindowDataSource,
+ nsIRDFDataSource)
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWINDOWMEDIATORLISTENER
+ NS_DECL_NSIWINDOWDATASOURCE
+ NS_DECL_NSIRDFDATASOURCE
+
+ protected:
+ virtual ~nsWindowDataSource();
+
+ private:
+
+ // mapping of window -> RDF resource
+ nsInterfaceHashtable<nsPtrHashKey<nsIXULWindow>, nsIRDFResource> mWindowResources;
+
+ static uint32_t windowCount;
+ static uint32_t gRefCnt;
+
+ nsCOMPtr<nsIRDFDataSource> mInner;
+ nsCOMPtr<nsIRDFContainer> mContainer;
+
+ static nsIRDFResource* kNC_Name;
+ static nsIRDFResource* kNC_KeyIndex;
+ static nsIRDFResource* kNC_WindowRoot;
+ static nsIRDFService* gRDFService;
+};