diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /xpfe/components | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpfe/components')
-rw-r--r-- | xpfe/components/autocomplete/jar.mn | 9 | ||||
-rw-r--r-- | xpfe/components/autocomplete/moz.build | 7 | ||||
-rw-r--r-- | xpfe/components/autocomplete/resources/content/autocomplete.css | 46 | ||||
-rw-r--r-- | xpfe/components/autocomplete/resources/content/autocomplete.xml | 1646 | ||||
-rw-r--r-- | xpfe/components/build/moz.build | 15 | ||||
-rw-r--r-- | xpfe/components/build/nsModule.cpp | 44 | ||||
-rw-r--r-- | xpfe/components/directory/moz.build | 20 | ||||
-rw-r--r-- | xpfe/components/directory/nsDirectoryViewer.cpp | 1393 | ||||
-rw-r--r-- | xpfe/components/directory/nsDirectoryViewer.h | 126 | ||||
-rw-r--r-- | xpfe/components/directory/nsIHTTPIndex.idl | 50 | ||||
-rw-r--r-- | xpfe/components/moz.build | 12 | ||||
-rw-r--r-- | xpfe/components/windowds/moz.build | 17 | ||||
-rw-r--r-- | xpfe/components/windowds/nsIWindowDataSource.idl | 17 | ||||
-rw-r--r-- | xpfe/components/windowds/nsWindowDataSource.cpp | 519 | ||||
-rw-r--r-- | xpfe/components/windowds/nsWindowDataSource.h | 59 |
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 0000000000..d3ddf8a61e --- /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 0000000000..eb4454d28f --- /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 0000000000..6c67bad2ed --- /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 0000000000..93b6dfdb0c --- /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 && 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 0000000000..3d02e04090 --- /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 0000000000..43966f94d1 --- /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 0000000000..248da07ec3 --- /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 0000000000..9d23c5e746 --- /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 0000000000..05b68f1b6d --- /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 0000000000..47697172bc --- /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 0000000000..cb23f336e2 --- /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 0000000000..0e7536b162 --- /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 0000000000..6143a4317a --- /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 0000000000..3e7a420607 --- /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, ¤tIndex); + + // 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 0000000000..351ff951b1 --- /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; +}; |