Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 <?xml version="1.0"?>
2 <!-- This Source Code Form is subject to the terms of the Mozilla Public
3 - License, v. 2.0. If a copy of the MPL was not distributed with this
4 - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
7 <bindings id="autocompleteBindings"
8 xmlns="http://www.mozilla.org/xbl"
9 xmlns:html="http://www.w3.org/1999/xhtml"
10 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
11 xmlns:xbl="http://www.mozilla.org/xbl">
13 <binding id="autocomplete" role="xul:combobox"
14 extends="chrome://global/content/bindings/textbox.xml#textbox">
15 <resources>
16 <stylesheet src="chrome://global/content/autocomplete.css"/>
17 <stylesheet src="chrome://global/skin/autocomplete.css"/>
18 </resources>
20 <content>
21 <children includes="menupopup"/>
23 <xul:hbox class="autocomplete-textbox-container" flex="1" align="center">
24 <children includes="image|deck|stack|box">
25 <xul:image class="autocomplete-icon" allowevents="true"/>
26 </children>
28 <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext">
29 <children/>
30 <html:input anonid="input" class="autocomplete-textbox textbox-input"
31 allowevents="true"
32 xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,userAction"/>
33 </xul:hbox>
34 <children includes="hbox"/>
35 </xul:hbox>
37 <xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true"
38 xbl:inherits="open,enablehistory" anonid="historydropmarker"/>
40 <xul:popupset>
41 <xul:panel type="autocomplete" anonid="popup"
42 ignorekeys="true" noautofocus="true" level="top"
43 xbl:inherits="for=id,nomatch"/>
44 </xul:popupset>
45 </content>
47 <implementation implements="nsIDOMXULMenuListElement">
49 <constructor><![CDATA[
50 // XXX bug 90337 band-aid until we figure out what's going on here
51 if (this.value != this.mInputElt.value)
52 this.mInputElt.value = this.value;
53 delete this.value;
55 // listen for pastes
56 this.mInputElt.controllers.insertControllerAt(0, this.mPasteController);
58 // listen for menubar activation
59 window.top.addEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
61 // set default property values
62 this.ifSetAttribute("timeout", 50);
63 this.ifSetAttribute("pastetimeout", 1000);
64 this.ifSetAttribute("maxrows", 5);
65 this.ifSetAttribute("showpopup", true);
66 this.ifSetAttribute("disableKeyNavigation", true);
68 // initialize the search sessions
69 if (this.hasAttribute("autocompletesearch"))
70 this.initAutoCompleteSearch();
72 // hack to work around lack of bottom-up constructor calling
73 if ("initialize" in this.popup)
74 this.popup.initialize();
75 ]]></constructor>
77 <destructor><![CDATA[
78 this.clearResults(false);
79 window.top.removeEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
80 this.mInputElt.controllers.removeController(this.mPasteController);
81 ]]></destructor>
83 <!-- =================== nsIAutoCompleteInput =================== -->
84 <!-- XXX: This implementation is currently incomplete. -->
86 <!-- reference to the results popup element -->
87 <field name="popup"><![CDATA[
88 document.getAnonymousElementByAttribute(this, "anonid", "popup");
89 ]]></field>
91 <property name="popupOpen"
92 onget="return this.mMenuOpen;"
93 onset="if (val) this.openPopup(); else this.closePopup(); return val;"/>
95 <!-- option to turn off autocomplete -->
96 <property name="disableAutoComplete"
97 onset="this.setAttribute('disableautocomplete', val); return val;"
98 onget="return this.getAttribute('disableautocomplete') == 'true';"/>
100 <!-- if the resulting match string is not at the beginning of the typed string,
101 this will optionally autofill like this "bar |>> foobar|" -->
102 <property name="completeDefaultIndex"
103 onset="this.setAttribute('completedefaultindex', val); return val;"
104 onget="return this.getAttribute('completedefaultindex') == 'true';"/>
106 <!-- option for completing to the default result whenever the user hits
107 enter or the textbox loses focus -->
108 <property name="forceComplete"
109 onset="this.setAttribute('forcecomplete', val); return val;"
110 onget="return this.getAttribute('forcecomplete') == 'true';"/>
112 <property name="minResultsForPopup"
113 onset="this.setAttribute('minresultsforpopup', val); return val;"
114 onget="var t = this.getAttribute('minresultsforpopup'); return t ? parseInt(t) : 1;"/>
116 <!-- maximum number of rows to display -->
117 <property name="maxRows"
118 onset="this.setAttribute('maxrows', val); return val;"
119 onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
121 <!-- toggles a second column in the results list which contains
122 the string in the comment field of each autocomplete result -->
123 <property name="showCommentColumn"
124 onget="return this.getAttribute('showcommentcolumn') == 'true';">
125 <setter><![CDATA[
126 this.popup.showCommentColumn = val;
127 this.setAttribute('showcommentcolumn', val);
128 return val;
129 ]]></setter>
130 </property>
132 <!-- number of milliseconds after a keystroke before a search begins -->
133 <property name="timeout"
134 onset="this.setAttribute('timeout', val); return val;"
135 onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
137 <property name="searchParam"
138 onget="return this.getAttribute('autocompletesearchparam') || '';"
139 onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
141 <property name="searchCount" readonly="true"
142 onget="return this.sessionCount;"/>
144 <method name="getSearchAt">
145 <parameter name="aIndex"/>
146 <body><![CDATA[
147 var idx = -1;
148 for (var name in this.mSessions)
149 if (++idx == aIndex)
150 return name;
152 return null;
153 ]]></body>
154 </method>
156 <property name="textValue"
157 onget="return this.value;"
158 onset="this.setTextValue(val); return val;"/>
160 <method name="onSearchBegin">
161 <body><![CDATA[
162 this._fireEvent("searchbegin");
163 ]]></body>
164 </method>
166 <method name="onSearchComplete">
167 <body><![CDATA[
168 if (this.noMatch)
169 this.setAttribute("nomatch", "true");
170 else
171 this.removeAttribute("nomatch");
173 this._fireEvent("searchcomplete");
174 ]]></body>
175 </method>
177 <method name="onTextReverted">
178 <body><![CDATA[
179 return this._fireEvent("textreverted");
180 ]]></body>
181 </method>
183 <!-- =================== nsIDOMXULMenuListElement =================== -->
185 <property name="editable" readonly="true"
186 onget="return true;" />
188 <property name="crop"
189 onset="this.setAttribute('crop', val); return val;"
190 onget="return this.getAttribute('crop');"/>
192 <property name="label" readonly="true"
193 onget="return this.mInputElt.value;"/>
195 <property name="open"
196 onget="return this.getAttribute('open') == 'true';">
197 <setter>
198 <![CDATA[
199 var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
200 if (val) {
201 this.setAttribute('open', true);
202 historyPopup.showPopup();
203 } else {
204 this.removeAttribute('open');
205 historyPopup.hidePopup();
206 }
207 ]]>
208 </setter>
209 </property>
211 <!-- =================== PUBLIC PROPERTIES =================== -->
213 <property name="value"
214 onget="return this.mInputElt.value;">
215 <setter><![CDATA[
216 this.ignoreInputEvent = true;
217 this.mInputElt.value = val;
218 this.ignoreInputEvent = false;
219 var event = document.createEvent('Events');
220 event.initEvent('ValueChange', true, true);
221 this.mInputElt.dispatchEvent(event);
222 return val;
223 ]]></setter>
224 </property>
226 <property name="focused"
227 onget="return this.getAttribute('focused') == 'true';"/>
229 <method name="initAutoCompleteSearch">
230 <body><![CDATA[
231 var list = this.getAttribute("autocompletesearch").split(" ");
232 for (var i = 0; i < list.length; i++) {
233 var name = list[i];
234 var contractid = "@mozilla.org/autocomplete/search;1?name=" + name;
235 if (contractid in Components.classes) {
236 try {
237 this.mSessions[name] =
238 Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSearch);
239 this.mLastResults[name] = null;
240 this.mLastRows[name] = 0;
241 ++this.sessionCount;
242 } catch (e) {
243 dump("### ERROR - unable to create search \"" + name + "\".\n");
244 }
245 } else {
246 dump("search \"" + name + "\" not found - skipping.\n");
247 }
248 }
249 ]]></body>
250 </method>
252 <!-- the number of sessions currently in use -->
253 <field name="sessionCount">0</field>
255 <!-- number of milliseconds after a paste before a search begins -->
256 <property name="pasteTimeout"
257 onset="this.setAttribute('pastetimeout', val); return val;"
258 onget="var t = parseInt(this.getAttribute('pastetimeout')); return t ? t : 0;"/>
260 <!-- option for filling the textbox with the best match while typing
261 and selecting the difference -->
262 <property name="autoFill"
263 onset="this.setAttribute('autofill', val); return val;"
264 onget="return this.getAttribute('autofill') == 'true';"/>
266 <!-- if this attribute is set, allow different style for
267 non auto-completed lines -->
268 <property name="highlightNonMatches"
269 onset="this.setAttribute('highlightnonmatches', val); return val;"
270 onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
272 <!-- option to show the popup containing the results -->
273 <property name="showPopup"
274 onset="this.setAttribute('showpopup', val); return val;"
275 onget="return this.getAttribute('showpopup') == 'true';"/>
277 <!-- option to allow scrolling through the list via the tab key, rather than
278 tab moving focus out of the textbox -->
279 <property name="tabScrolling"
280 onset="return this.setAttribute('tabscrolling', val); return val;"
281 onget="return this.getAttribute('tabscrolling') == 'true';"/>
283 <!-- option to completely ignore any blur events while
284 searches are still going on. This is useful so that nothing
285 gets autopicked if the window is required to lose focus for
286 some reason (eg in LDAP autocomplete, another window may be
287 brought up so that the user can enter a password to authenticate
288 to an LDAP server). -->
289 <property name="ignoreBlurWhileSearching"
290 onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
291 onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
293 <!-- state which indicates the current action being performed by the user.
294 Possible values are : none, typing, scrolling -->
295 <property name="userAction"
296 onset="this.setAttribute('userAction', val); return val;"
297 onget="return this.getAttribute('userAction');"/>
299 <!-- state which indicates if the last search had no matches -->
300 <field name="noMatch">true</field>
302 <!-- state which indicates a search is currently happening -->
303 <field name="isSearching">false</field>
305 <!-- state which indicates a search timeout is current waiting -->
306 <property name="isWaiting"
307 onget="return this.mAutoCompleteTimer != 0;"/>
309 <!-- =================== PRIVATE PROPERTIES =================== -->
311 <field name="mSessions">({})</field>
312 <field name="mLastResults">({})</field>
313 <field name="mLastRows">({})</field>
314 <field name="mLastKeyCode">null</field>
315 <field name="mAutoCompleteTimer">0</field>
316 <field name="mMenuOpen">false</field>
317 <field name="mFireAfterSearch">false</field>
318 <field name="mFinishAfterSearch">false</field>
319 <field name="mNeedToFinish">false</field>
320 <field name="mNeedToComplete">false</field>
321 <field name="mTransientValue">false</field>
322 <field name="mView">null</field>
323 <field name="currentSearchString">""</field>
324 <field name="ignoreInputEvent">false</field>
325 <field name="oninit">null</field>
326 <field name="mDefaultMatchFilled">false</field>
327 <field name="mFirstReturn">true</field>
328 <field name="mIsPasting">false</field>
330 <field name="mPasteController"><![CDATA[
331 ({
332 self: this,
333 kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
334 supportsCommand: function(aCommand) {
335 return aCommand == "cmd_paste";
336 },
337 isCommandEnabled: function(aCommand) {
338 return aCommand == "cmd_paste" &&
339 this.self.editor.isSelectionEditable &&
340 this.self.editor.canPaste(this.kGlobalClipboard);
341 },
342 doCommand: function(aCommand) {
343 if (aCommand == "cmd_paste") {
344 this.self.mIsPasting = true;
345 this.self.editor.paste(this.kGlobalClipboard);
346 this.self.mIsPasting = false;
347 }
348 },
349 onEvent: function() {}
350 })
351 ]]></field>
353 <field name="mMenuBarListener"><![CDATA[
354 ({
355 self: this,
356 handleEvent: function(aEvent) {
357 try {
358 this.self.finishAutoComplete(false, false, aEvent);
359 this.self.clearTimer();
360 this.self.closePopup();
361 } catch (e) {
362 window.top.removeEventListener("DOMMenuBarActive", this, true);
363 }
364 }
365 })
366 ]]></field>
368 <field name="mAutoCompleteObserver"><![CDATA[
369 ({
370 self: this,
371 onSearchResult: function(aSearch, aResult) {
372 for (var name in this.self.mSessions)
373 if (this.self.mSessions[name] == aSearch)
374 this.self.processResults(name, aResult);
375 }
376 })
377 ]]></field>
379 <field name="mInputElt"><![CDATA[
380 document.getAnonymousElementByAttribute(this, "anonid", "input");
381 ]]></field>
383 <field name="mMenuAccessKey"><![CDATA[
384 Components.classes["@mozilla.org/preferences-service;1"]
385 .getService(Components.interfaces.nsIPrefBranch)
386 .getIntPref("ui.key.menuAccessKey");
387 ]]></field>
389 <!-- =================== PUBLIC METHODS =================== -->
391 <method name="getErrorAt">
392 <parameter name="aIndex"/>
393 <body><![CDATA[
394 var obj = aIndex < 0 ? null : this.convertIndexToSession(aIndex);
395 return obj && this.mLastResults[obj.session] &&
396 this.mLastResults[obj.session].errorDescription;
397 ]]></body>
398 </method>
400 <!-- get a value from the autocomplete results as a string via an absolute index-->
401 <method name="getResultValueAt">
402 <parameter name="aIndex"/>
403 <body><![CDATA[
404 var obj = this.convertIndexToSession(aIndex);
405 return obj ? this.getSessionValueAt(obj.session, obj.index) : null;
406 ]]></body>
407 </method>
409 <!-- get a value from the autocomplete results as a string from a specific session -->
410 <method name="getSessionValueAt">
411 <parameter name="aSession"/>
412 <parameter name="aIndex"/>
413 <body><![CDATA[
414 var result = this.mLastResults[aSession];
415 return result.errorDescription || result.getValueAt(aIndex);
416 ]]></body>
417 </method>
419 <!-- get the total number of results overall -->
420 <method name="getResultCount">
421 <body><![CDATA[
422 return this.view.rowCount;
423 ]]></body>
424 </method>
426 <!-- get the first session that has results -->
427 <method name="getDefaultSession">
428 <body><![CDATA[
429 for (var name in this.mLastResults) {
430 var results = this.mLastResults[name];
431 if (results && results.matchCount > 0 && !results.errorDescription)
432 return name;
433 }
434 return null;
435 ]]></body>
436 </method>
438 <!-- empty the cached result data and empty the results popup -->
439 <method name="clearResults">
440 <parameter name="aInvalidate"/>
441 <body><![CDATA[
442 this.clearResultData();
443 this.clearResultElements(aInvalidate);
444 ]]></body>
445 </method>
447 <!-- =================== PRIVATE METHODS =================== -->
449 <!-- ::::::::::::: session searching ::::::::::::: -->
451 <!-- -->
452 <method name="callListener">
453 <parameter name="me"/>
454 <parameter name="aAction"/>
455 <body><![CDATA[
456 // bail if the binding was detached or the element removed from
457 // document during the timeout
458 if (!("startLookup" in me) || !me.ownerDocument || !me.parentNode)
459 return;
461 me.clearTimer();
463 if (me.disableAutoComplete)
464 return;
466 switch (aAction) {
467 case "startLookup":
468 me.startLookup();
469 break;
471 case "stopLookup":
472 me.stopLookup();
473 break;
474 }
475 ]]></body>
476 </method>
478 <!-- -->
479 <method name="startLookup">
480 <body><![CDATA[
481 var str = this.currentSearchString;
482 if (!str) {
483 this.clearResults(false);
484 this.closePopup();
485 return;
486 }
488 this.isSearching = true;
489 this.mFirstReturn = true;
490 this.mSessionReturns = this.sessionCount;
491 this.mFailureItems = 0;
492 this.mDefaultMatchFilled = false; // clear out our prefill state.
494 // Notify the input that the search is beginning.
495 this.onSearchBegin();
497 // tell each session to start searching...
498 for (var name in this.mSessions)
499 try {
500 this.mSessions[name].startSearch(str, this.searchParam, this.mLastResults[name], this.mAutoCompleteObserver);
501 } catch (e) {
502 --this.mSessionReturns;
503 this.searchFailed();
504 }
505 ]]></body>
506 </method>
508 <!-- -->
509 <method name="stopLookup">
510 <body><![CDATA[
511 for (var name in this.mSessions)
512 this.mSessions[name].stopSearch();
513 ]]></body>
514 </method>
516 <!-- -->
517 <method name="processResults">
518 <parameter name="aSessionName"/>
519 <parameter name="aResults"/>
520 <body><![CDATA[
521 if (this.disableAutoComplete)
522 return;
524 const ACR = Components.interfaces.nsIAutoCompleteResult;
525 var status = aResults.searchResult;
526 if (status != ACR.RESULT_NOMATCH_ONGOING &&
527 status != ACR.RESULT_SUCCESS_ONGOING)
528 --this.mSessionReturns;
530 // check the many criteria for failure
531 if (aResults.errorDescription)
532 ++this.mFailureItems;
533 else if (status == ACR.RESULT_IGNORED ||
534 status == ACR.RESULT_FAILURE ||
535 status == ACR.RESULT_NOMATCH ||
536 status == ACR.RESULT_NOMATCH_ONGOING ||
537 aResults.matchCount == 0 ||
538 aResults.searchString != this.currentSearchString)
539 {
540 this.mLastResults[aSessionName] = null;
541 if (this.mFirstReturn)
542 this.clearResultElements(false);
543 this.mFirstReturn = false;
544 this.searchFailed();
545 return;
546 }
548 if (this.mFirstReturn) {
549 if (this.view.mTree)
550 this.view.mTree.beginUpdateBatch();
551 this.clearResultElements(false); // clear results, but don't repaint yet
552 }
554 // always call openPopup...we may not have opened it
555 // if a previous search session didn't return enough search results.
556 // it's smart and doesn't try to open itself multiple times...
557 // be sure to add our result elements before calling openResultPopuup as we need
558 // to know the total # of results found so far.
559 this.addResultElements(aSessionName, aResults);
561 this.autoFillInput(aSessionName, aResults, false);
562 if (this.mFirstReturn && this.view.mTree)
563 this.view.mTree.endUpdateBatch();
564 this.openPopup();
565 this.mFirstReturn = false;
567 // if this is the last session to return...
568 if (this.mSessionReturns == 0)
569 this.postSearchCleanup();
571 if (this.mFinishAfterSearch)
572 this.finishAutoComplete(false, this.mFireAfterSearch, null);
573 ]]></body>
574 </method>
576 <!-- called each time a search fails, except when failure items need
577 to be displayed. If all searches have failed, clear the list
578 and close the popup -->
579 <method name="searchFailed">
580 <body><![CDATA[
581 // if all searches are done and they all failed...
582 if (this.mSessionReturns == 0 && this.getResultCount() == 0) {
583 if (this.minResultsForPopup == 0) {
584 this.clearResults(true); // clear data and repaint empty
585 this.openPopup();
586 } else {
587 this.closePopup();
588 }
589 }
591 // if it's the last session to return, time to clean up...
592 if (this.mSessionReturns == 0)
593 this.postSearchCleanup();
594 ]]></body>
595 </method>
597 <!-- does some stuff after a search is done (success or failure) -->
598 <method name="postSearchCleanup">
599 <body><![CDATA[
600 this.isSearching = false;
602 // figure out if there are no matches in all search sessions
603 var failed = true;
604 for (var name in this.mSessions) {
605 if (this.mLastResults[name])
606 failed = this.mLastResults[name].errorDescription ||
607 this.mLastResults[name].matchCount == 0;
608 if (!failed)
609 break;
610 }
611 this.noMatch = failed;
613 // if we have processed all of our searches, and none of them gave us a default index,
614 // then we should try to auto fill the input field with the first match.
615 // note: autoFillInput is smart enough to kick out if we've already prefilled something...
616 if (!this.noMatch) {
617 var defaultSession = this.getDefaultSession();
618 if (defaultSession)
619 this.autoFillInput(defaultSession, this.mLastResults[defaultSession], true);
620 }
622 // Notify the input that the search is complete.
623 this.onSearchComplete();
624 ]]></body>
625 </method>
627 <!-- when the focus exits the widget or user hits return,
628 determine what value to leave in the textbox -->
629 <method name="finishAutoComplete">
630 <parameter name="aForceComplete"/>
631 <parameter name="aFireTextCommand"/>
632 <parameter name="aTriggeringEvent"/>
633 <body><![CDATA[
634 this.mFinishAfterSearch = false;
635 this.mFireAfterSearch = false;
636 if (this.mNeedToFinish && !this.disableAutoComplete) {
637 // set textbox value to either override value, or default search result
638 var val = this.popup.overrideValue;
639 if (val) {
640 this.setTextValue(val);
641 this.mNeedToFinish = false;
642 } else if (this.mTransientValue ||
643 !(this.forceComplete ||
644 (aForceComplete &&
645 this.mDefaultMatchFilled &&
646 this.mNeedToComplete))) {
647 this.mNeedToFinish = false;
648 } else if (this.isWaiting) {
649 // if the user typed, the search results are out of date, so let
650 // the search finish, and tell it to come back here when it's done
651 this.mFinishAfterSearch = true;
652 this.mFireAfterSearch = aFireTextCommand;
653 return;
654 } else {
655 // we want to use the default item index for the first session which gave us a valid
656 // default item index...
657 for (var name in this.mLastResults) {
658 var results = this.mLastResults[name];
659 if (results && results.matchCount > 0 &&
660 !results.errorDescription && results.defaultIndex != -1)
661 {
662 val = results.getValueAt(results.defaultIndex);
663 this.setTextValue(val);
664 this.mDefaultMatchFilled = true;
665 this.mNeedToFinish = false;
666 break;
667 }
668 }
670 if (this.mNeedToFinish) {
671 // if a search is happening at this juncture, bail out of this function
672 // and let the search finish, and tell it to come back here when it's done
673 if (this.isSearching) {
674 this.mFinishAfterSearch = true;
675 this.mFireAfterSearch = aFireTextCommand;
676 return;
677 }
679 this.mNeedToFinish = false;
680 var defaultSession = this.getDefaultSession();
681 if (defaultSession)
682 {
683 // preselect the first one
684 var first = this.getSessionValueAt(defaultSession, 0);
685 this.setTextValue(first);
686 this.mDefaultMatchFilled = true;
687 }
688 }
689 }
691 this.stopLookup();
693 this.closePopup();
694 }
696 this.mNeedToComplete = false;
697 this.clearTimer();
699 if (aFireTextCommand)
700 this._fireEvent("textentered", this.userAction, aTriggeringEvent);
701 ]]></body>
702 </method>
704 <!-- when the user clicks an entry in the autocomplete popup -->
705 <method name="onResultClick">
706 <body><![CDATA[
707 // set textbox value to either override value, or the clicked result
708 var errItem = this.getErrorAt(this.popup.selectedIndex);
709 var val = this.popup.overrideValue;
710 if (val)
711 this.setTextValue(val);
712 else if (this.popup.selectedIndex != -1) {
713 if (errItem) {
714 this.setTextValue(this.currentSearchString);
715 this.mTransientValue = true;
716 } else {
717 this.setTextValue(this.getResultValueAt(
718 this.popup.selectedIndex));
719 }
720 }
722 this.mNeedToFinish = false;
723 this.mNeedToComplete = false;
725 this.closePopup();
727 this.currentSearchString = "";
729 if (errItem)
730 this._fireEvent("errorcommand", errItem);
731 this._fireEvent("textentered", "clicking");
732 ]]></body>
733 </method>
735 <!-- when the user hits escape, revert the previously typed value in the textbox -->
736 <method name="undoAutoComplete">
737 <body><![CDATA[
738 var val = this.currentSearchString;
740 var ok = this.onTextReverted();
741 if ((ok || ok == undefined) && val)
742 this.setTextValue(val);
744 this.userAction = "typing";
746 this.currentSearchString = this.value;
747 this.mNeedToComplete = false;
748 ]]></body>
749 </method>
751 <!-- convert an absolute result index into a session name/index pair -->
752 <method name="convertIndexToSession">
753 <parameter name="aIndex"/>
754 <body><![CDATA[
755 for (var name in this.mLastRows) {
756 if (aIndex < this.mLastRows[name])
757 return { session: name, index: aIndex };
758 aIndex -= this.mLastRows[name];
759 }
760 return null;
761 ]]></body>
762 </method>
764 <!-- ::::::::::::: user input handling ::::::::::::: -->
766 <!-- -->
767 <method name="processInput">
768 <body><![CDATA[
769 // stop current lookup in case it's async.
770 this.stopLookup();
771 // stop the queued up lookup on a timer
772 this.clearTimer();
774 if (this.disableAutoComplete)
775 return;
777 this.userAction = "typing";
778 this.mFinishAfterSearch = false;
779 this.mNeedToFinish = true;
780 this.mTransientValue = false;
781 this.mNeedToComplete = true;
782 var str = this.value;
783 this.currentSearchString = str;
784 this.popup.clearSelection();
786 var timeout = this.mIsPasting ? this.pasteTimeout : this.timeout;
787 this.mAutoCompleteTimer = setTimeout(this.callListener, timeout, this, "startLookup");
788 ]]></body>
789 </method>
791 <!-- -->
792 <method name="processKeyPress">
793 <parameter name="aEvent"/>
794 <body><![CDATA[
795 this.mLastKeyCode = aEvent.keyCode;
797 var killEvent = false;
799 switch (aEvent.keyCode) {
800 case KeyEvent.DOM_VK_TAB:
801 if (this.tabScrolling) {
802 // don't kill this event if alt-tab or ctrl-tab is hit
803 if (!aEvent.altKey && !aEvent.ctrlKey) {
804 killEvent = this.mMenuOpen;
805 if (killEvent)
806 this.keyNavigation(aEvent);
807 }
808 }
809 break;
811 case KeyEvent.DOM_VK_RETURN:
813 // if this is a failure item, save it for fireErrorCommand
814 var errItem = this.getErrorAt(this.popup.selectedIndex);
816 killEvent = this.mMenuOpen;
817 this.finishAutoComplete(true, true, aEvent);
818 this.closePopup();
819 if (errItem) {
820 this._fireEvent("errorcommand", errItem);
821 }
822 break;
824 case KeyEvent.DOM_VK_ESCAPE:
825 this.clearTimer();
826 killEvent = this.mMenuOpen;
827 this.undoAutoComplete();
828 this.closePopup();
829 break;
831 case KeyEvent.DOM_VK_LEFT:
832 case KeyEvent.DOM_VK_RIGHT:
833 case KeyEvent.DOM_VK_HOME:
834 case KeyEvent.DOM_VK_END:
835 this.finishAutoComplete(true, false, aEvent);
836 this.clearTimer();
837 this.closePopup();
838 break;
840 case KeyEvent.DOM_VK_DOWN:
841 if (!aEvent.altKey) {
842 this.clearTimer();
843 killEvent = this.keyNavigation(aEvent);
844 break;
845 }
846 // Alt+Down falls through to history popup toggling code
848 case KeyEvent.DOM_VK_F4:
849 if (!aEvent.ctrlKey && !aEvent.shiftKey && this.getAttribute("enablehistory") == "true") {
850 var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
851 if (historyPopup)
852 historyPopup.showPopup();
853 else
854 historyPopup.hidePopup();
855 }
856 break;
857 case KeyEvent.DOM_VK_PAGE_UP:
858 case KeyEvent.DOM_VK_PAGE_DOWN:
859 case KeyEvent.DOM_VK_UP:
860 if (!aEvent.ctrlKey && !aEvent.metaKey) {
861 this.clearTimer();
862 killEvent = this.keyNavigation(aEvent);
863 }
864 break;
866 case KeyEvent.DOM_VK_BACK_SPACE:
867 if (!aEvent.ctrlKey && !aEvent.altKey && !aEvent.shiftKey &&
868 this.selectionStart == this.currentSearchString.length &&
869 this.selectionEnd == this.value.length &&
870 this.mDefaultMatchFilled) {
871 this.mDefaultMatchFilled = false;
872 this.value = this.currentSearchString;
873 }
875 if (!/Mac/.test(navigator.platform))
876 break;
877 case KeyEvent.DOM_VK_DELETE:
878 if (/Mac/.test(navigator.platform) && !aEvent.shiftKey)
879 break;
881 if (this.mMenuOpen && this.popup.selectedIndex != -1) {
882 var obj = this.convertIndexToSession(this.popup.selectedIndex);
883 if (obj) {
884 var result = this.mLastResults[obj.session];
885 if (!result.errorDescription) {
886 var count = result.matchCount;
887 result.removeValueAt(obj.index, true);
888 this.view.updateResults(this.popup.selectedIndex, result.matchCount - count);
889 killEvent = true;
890 }
891 }
892 }
893 break;
894 }
896 if (killEvent) {
897 aEvent.preventDefault();
898 aEvent.stopPropagation();
899 }
901 return true;
902 ]]></body>
903 </method>
905 <!-- -->
906 <method name="processStartComposition">
907 <body><![CDATA[
908 this.finishAutoComplete(false, false, null);
909 this.clearTimer();
910 this.closePopup();
911 ]]></body>
912 </method>
914 <!-- -->
915 <method name="keyNavigation">
916 <parameter name="aEvent"/>
917 <body><![CDATA[
918 var k = aEvent.keyCode;
919 if (k == KeyEvent.DOM_VK_TAB ||
920 k == KeyEvent.DOM_VK_UP || k == KeyEvent.DOM_VK_DOWN ||
921 k == KeyEvent.DOM_VK_PAGE_UP || k == KeyEvent.DOM_VK_PAGE_DOWN)
922 {
923 if (!this.mMenuOpen) {
924 // Original xpfe style was to allow the up and down keys to have
925 // their default Mac action if the popup could not be opened.
926 // For compatibility for toolkit we now have to predict which
927 // keys have a default action that we can always allow to fire.
928 if (/Mac/.test(navigator.platform) &&
929 ((k == KeyEvent.DOM_VK_UP &&
930 (this.selectionStart != 0 ||
931 this.selectionEnd != 0)) ||
932 (k == KeyEvent.DOM_VK_DOWN &&
933 (this.selectionStart != this.value.length ||
934 this.selectionEnd != this.value.length))))
935 return false;
936 if (this.currentSearchString != this.value) {
937 this.processInput();
938 return true;
939 }
940 if (this.view.rowCount < this.minResultsForPopup)
941 return true; // used to be false, see above
943 this.mNeedToFinish = true;
944 this.openPopup();
945 return true;
946 }
948 this.userAction = "scrolling";
949 this.mNeedToComplete = false;
951 var reverse = k == KeyEvent.DOM_VK_TAB && aEvent.shiftKey ||
952 k == KeyEvent.DOM_VK_UP ||
953 k == KeyEvent.DOM_VK_PAGE_UP;
954 var page = k == KeyEvent.DOM_VK_PAGE_UP ||
955 k == KeyEvent.DOM_VK_PAGE_DOWN;
956 var selected = this.popup.selectBy(reverse, page);
958 // determine which value to place in the textbox
959 this.ignoreInputEvent = true;
960 if (selected != -1) {
961 if (this.getErrorAt(selected)) {
962 if (this.currentSearchString)
963 this.setTextValue(this.currentSearchString);
964 } else {
965 this.setTextValue(this.getResultValueAt(selected));
966 }
967 this.mTransientValue = true;
968 } else {
969 if (this.currentSearchString)
970 this.setTextValue(this.currentSearchString);
971 this.mTransientValue = false;
972 }
974 // move cursor to the end
975 this.mInputElt.setSelectionRange(this.value.length, this.value.length);
976 this.ignoreInputEvent = false;
977 }
978 return true;
979 ]]></body>
980 </method>
982 <!-- while the user is typing, fill the textbox with the "default" value
983 if one can be assumed, and select the end of the text -->
984 <method name="autoFillInput">
985 <parameter name="aSessionName"/>
986 <parameter name="aResults"/>
987 <parameter name="aUseFirstMatchIfNoDefault"/>
988 <body><![CDATA[
989 if (this.mInputElt.selectionEnd < this.currentSearchString.length ||
990 this.mDefaultMatchFilled)
991 return;
993 if (!this.mFinishAfterSearch &&
994 (this.autoFill || this.completeDefaultIndex) &&
995 this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE &&
996 this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) {
997 var indexToUse = aResults.defaultIndex;
998 if (aUseFirstMatchIfNoDefault && indexToUse == -1)
999 indexToUse = 0;
1001 if (indexToUse != -1) {
1002 var resultValue = this.getSessionValueAt(aSessionName, indexToUse);
1003 var match = resultValue.toLowerCase();
1004 var entry = this.currentSearchString.toLowerCase();
1005 this.ignoreInputEvent = true;
1006 if (match.indexOf(entry) == 0) {
1007 var endPoint = this.value.length;
1008 this.setTextValue(this.value + resultValue.substr(endPoint));
1009 this.mInputElt.setSelectionRange(endPoint, this.value.length);
1010 } else {
1011 if (this.completeDefaultIndex) {
1012 this.setTextValue(this.value + " >> " + resultValue);
1013 this.mInputElt.setSelectionRange(entry.length, this.value.length);
1014 } else {
1015 var postIndex = resultValue.indexOf(this.value);
1016 if (postIndex >= 0) {
1017 var startPt = this.value.length;
1018 this.setTextValue(this.value +
1019 resultValue.substr(startPt+postIndex));
1020 this.mInputElt.setSelectionRange(startPt, this.value.length);
1021 }
1022 }
1023 }
1024 this.mNeedToComplete = true;
1025 this.ignoreInputEvent = false;
1026 this.mDefaultMatchFilled = true;
1027 }
1028 }
1029 ]]></body>
1030 </method>
1032 <!-- ::::::::::::: popup and tree ::::::::::::: -->
1034 <!-- -->
1035 <method name="openPopup">
1036 <body><![CDATA[
1037 if (!this.mMenuOpen && this.focused &&
1038 (this.getResultCount() >= this.minResultsForPopup ||
1039 this.mFailureItems)) {
1040 var w = this.boxObject.width;
1041 if (w != this.popup.boxObject.width)
1042 this.popup.setAttribute("width", w);
1043 this.popup.showPopup(this, -1, -1, "popup", "bottomleft", "topleft");
1044 this.mMenuOpen = true;
1045 }
1046 ]]></body>
1047 </method>
1049 <!-- -->
1050 <method name="closePopup">
1051 <body><![CDATA[
1052 if (this.popup && this.mMenuOpen) {
1053 this.popup.hidePopup();
1054 this.mMenuOpen = false;
1055 }
1056 ]]></body>
1057 </method>
1059 <!-- -->
1060 <method name="addResultElements">
1061 <parameter name="aSession"/>
1062 <parameter name="aResults"/>
1063 <body><![CDATA[
1064 var count = aResults.errorDescription ? 1 : aResults.matchCount;
1065 if (this.focused && this.showPopup) {
1066 var row = 0;
1067 for (var name in this.mSessions) {
1068 row += this.mLastRows[name];
1069 if (name == aSession)
1070 break;
1071 }
1072 this.view.updateResults(row, count - this.mLastRows[name]);
1073 this.popup.adjustHeight();
1074 }
1075 this.mLastResults[aSession] = aResults;
1076 this.mLastRows[aSession] = count;
1077 ]]></body>
1078 </method>
1080 <!-- -->
1081 <method name="clearResultElements">
1082 <parameter name="aInvalidate"/>
1083 <body><![CDATA[
1084 for (var name in this.mSessions)
1085 this.mLastRows[name] = 0;
1086 this.view.clearResults();
1087 if (aInvalidate)
1088 this.popup.adjustHeight();
1090 this.noMatch = true;
1091 ]]></body>
1092 </method>
1094 <!-- -->
1095 <method name="setTextValue">
1096 <parameter name="aValue"/>
1097 <body><![CDATA[
1098 this.value = aValue;
1100 // Completing a result should simulate the user typing the result,
1101 // so fire an input event.
1102 var evt = document.createEvent("UIEvents");
1103 evt.initUIEvent("input", true, false, window, 0);
1104 var oldIgnoreInput = this.ignoreInputEvent;
1105 this.ignoreInputEvent = true;
1106 this.dispatchEvent(evt);
1107 this.ignoreInputEvent = oldIgnoreInput;
1108 ]]></body>
1109 </method>
1111 <!-- -->
1112 <method name="clearResultData">
1113 <body><![CDATA[
1114 for (var name in this.mSessions)
1115 this.mLastResults[name] = null;
1116 ]]></body>
1117 </method>
1119 <!-- ::::::::::::: miscellaneous ::::::::::::: -->
1121 <!-- -->
1122 <method name="ifSetAttribute">
1123 <parameter name="aAttr"/>
1124 <parameter name="aVal"/>
1125 <body><![CDATA[
1126 if (!this.hasAttribute(aAttr))
1127 this.setAttribute(aAttr, aVal);
1128 ]]></body>
1129 </method>
1131 <!-- -->
1132 <method name="clearTimer">
1133 <body><![CDATA[
1134 if (this.mAutoCompleteTimer) {
1135 clearTimeout(this.mAutoCompleteTimer);
1136 this.mAutoCompleteTimer = 0;
1137 }
1138 ]]></body>
1139 </method>
1141 <!-- ::::::::::::: event dispatching ::::::::::::: -->
1143 <method name="_fireEvent">
1144 <parameter name="aEventType"/>
1145 <parameter name="aEventParam"/>
1146 <parameter name="aTriggeringEvent"/>
1147 <body>
1148 <![CDATA[
1149 var noCancel = true;
1150 // handle any xml attribute event handlers
1151 var handler = this.getAttribute("on"+aEventType);
1152 if (handler) {
1153 var fn = new Function("eventParam", "domEvent", handler);
1154 var returned = fn.apply(this, [aEventParam, aTriggeringEvent]);
1155 if (returned == false)
1156 noCancel = false;
1157 }
1159 return noCancel;
1160 ]]>
1161 </body>
1162 </method>
1164 <!-- =================== TREE VIEW =================== -->
1166 <field name="view"><![CDATA[
1167 ({
1168 mTextbox: this,
1169 mTree: null,
1170 mSelection: null,
1171 mRowCount: 0,
1173 clearResults: function()
1174 {
1175 var oldCount = this.mRowCount;
1176 this.mRowCount = 0;
1178 if (this.mTree) {
1179 this.mTree.rowCountChanged(0, -oldCount);
1180 this.mTree.scrollToRow(0);
1181 }
1182 },
1184 updateResults: function(aRow, aCount)
1185 {
1186 this.mRowCount += aCount;
1188 if (this.mTree)
1189 this.mTree.rowCountChanged(aRow, aCount);
1190 },
1192 //////////////////////////////////////////////////////////
1193 // nsIAutoCompleteController interface
1195 // this is the only method required by the treebody mouseup handler
1196 handleEnter: function(aIsPopupSelection) {
1197 this.mTextbox.onResultClick();
1198 },
1200 //////////////////////////////////////////////////////////
1201 // nsITreeView interface
1203 get rowCount() {
1204 return this.mRowCount;
1205 },
1207 get selection() {
1208 return this.mSelection;
1209 },
1211 set selection(aVal) {
1212 return this.mSelection = aVal;
1213 },
1215 setTree: function(aTree)
1216 {
1217 this.mTree = aTree;
1218 },
1220 getCellText: function(aRow, aCol)
1221 {
1222 for (var name in this.mTextbox.mSessions) {
1223 if (aRow < this.mTextbox.mLastRows[name]) {
1224 var result = this.mTextbox.mLastResults[name];
1225 switch (aCol.id) {
1226 case "treecolAutoCompleteValue":
1227 return result.errorDescription || result.getLabelAt(aRow);
1228 case "treecolAutoCompleteComment":
1229 if (!result.errorDescription)
1230 return result.getCommentAt(aRow);
1231 default:
1232 return "";
1233 }
1234 }
1235 aRow -= this.mTextbox.mLastRows[name];
1236 }
1237 return "";
1238 },
1240 getRowProperties: function(aIndex)
1241 {
1242 return "";
1243 },
1245 getCellProperties: function(aIndex, aCol)
1246 {
1247 // for the value column, append nsIAutoCompleteItem::className
1248 // to the property list so that we can style this column
1249 // using that property
1250 if (aCol.id == "treecolAutoCompleteValue") {
1251 for (var name in this.mTextbox.mSessions) {
1252 if (aIndex < this.mTextbox.mLastRows[name]) {
1253 var result = this.mTextbox.mLastResults[name];
1254 if (result.errorDescription)
1255 return "";
1256 return result.getStyleAt(aIndex);
1257 }
1258 aIndex -= this.mTextbox.mLastRows[name];
1259 }
1260 }
1261 return "";
1262 },
1264 getColumnProperties: function(aCol)
1265 {
1266 return "";
1267 },
1269 getImageSrc: function(aRow, aCol)
1270 {
1271 if (aCol.id == "treecolAutoCompleteValue") {
1272 for (var name in this.mTextbox.mSessions) {
1273 if (aRow < this.mTextbox.mLastRows[name]) {
1274 var result = this.mTextbox.mLastResults[name];
1275 if (result.errorDescription)
1276 return "";
1277 return result.getImageAt(aRow);
1278 }
1279 aRow -= this.mTextbox.mLastRows[name];
1280 }
1281 }
1282 return "";
1283 },
1285 getParentIndex: function(aRowIndex) { },
1286 hasNextSibling: function(aRowIndex, aAfterIndex) { },
1287 getLevel: function(aIndex) {},
1288 getProgressMode: function(aRow, aCol) {},
1289 getCellValue: function(aRow, aCol) {},
1290 isContainer: function(aIndex) {},
1291 isContainerOpen: function(aIndex) {},
1292 isContainerEmpty: function(aIndex) {},
1293 isSeparator: function(aIndex) {},
1294 isSorted: function() {},
1295 toggleOpenState: function(aIndex) {},
1296 selectionChanged: function() {},
1297 cycleHeader: function(aCol) {},
1298 cycleCell: function(aRow, aCol) {},
1299 isEditable: function(aRow, aCol) {},
1300 isSelectable: function(aRow, aCol) {},
1301 setCellValue: function(aRow, aCol, aValue) {},
1302 setCellText: function(aRow, aCol, aValue) {},
1303 performAction: function(aAction) {},
1304 performActionOnRow: function(aAction, aRow) {},
1305 performActionOnCell: function(aAction, aRow, aCol) {}
1306 });
1307 ]]></field>
1309 </implementation>
1311 <handlers>
1312 <handler event="input"
1313 action="if (!this.ignoreInputEvent) this.processInput();"/>
1315 <handler event="keypress" phase="capturing"
1316 action="return this.processKeyPress(event);"/>
1318 <handler event="compositionstart" phase="capturing"
1319 action="this.processStartComposition();"/>
1321 <handler event="focus" phase="capturing"
1322 action="this.userAction = 'typing';"/>
1324 <handler event="blur" phase="capturing"
1325 action="if ( !(this.ignoreBlurWhileSearching && this.isSearching) ) {this.userAction = 'none'; this.finishAutoComplete(false, false, event);}"/>
1327 <handler event="mousedown" phase="capturing"
1328 action="if ( !this.mMenuOpen ) this.finishAutoComplete(false, false, event);"/>
1329 </handlers>
1330 </binding>
1332 <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/popup.xml#popup">
1333 <resources>
1334 <stylesheet src="chrome://global/content/autocomplete.css"/>
1335 <stylesheet src="chrome://global/skin/autocomplete.css"/>
1336 </resources>
1338 <content ignorekeys="true" level="top">
1339 <xul:tree anonid="tree" class="autocomplete-tree plain" flex="1">
1340 <xul:treecols anonid="treecols">
1341 <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteValue" flex="2"/>
1342 <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteComment" flex="1" hidden="true"/>
1343 </xul:treecols>
1344 <xul:treechildren anonid="treebody" class="autocomplete-treebody"/>
1345 </xul:tree>
1346 </content>
1348 <implementation implements="nsIAutoCompletePopup">
1349 <constructor><![CDATA[
1350 if (this.textbox && this.textbox.view)
1351 this.initialize();
1352 ]]></constructor>
1354 <destructor><![CDATA[
1355 if (this.view)
1356 this.tree.view = null;
1357 ]]></destructor>
1359 <field name="textbox">
1360 document.getBindingParent(this);
1361 </field>
1363 <field name="tree">
1364 document.getAnonymousElementByAttribute(this, "anonid", "tree");
1365 </field>
1367 <field name="treecols">
1368 document.getAnonymousElementByAttribute(this, "anonid", "treecols");
1369 </field>
1371 <field name="treebody">
1372 document.getAnonymousElementByAttribute(this, "anonid", "treebody");
1373 </field>
1375 <field name="view">
1376 null
1377 </field>
1379 <!-- Setting tree.view doesn't always immediately create a selection,
1380 so we ensure the selection by asking the tree for the view. Note:
1381 this.view.selection is quicker if we know the selection exists. -->
1382 <property name="selection" onget="return this.tree.view.selection;"/>
1384 <property name="pageCount"
1385 onget="return this.tree.treeBoxObject.getPageLength();"/>
1387 <field name="maxRows">0</field>
1388 <field name="mLastRows">0</field>
1390 <method name="initialize">
1391 <body><![CDATA[
1392 this.showCommentColumn = this.textbox.showCommentColumn;
1393 this.tree.view = this.textbox.view;
1394 this.view = this.textbox.view;
1395 this.maxRows = this.textbox.maxRows;
1396 ]]></body>
1397 </method>
1399 <property name="showCommentColumn"
1400 onget="return !this.treecols.lastChild.hidden;"
1401 onset="this.treecols.lastChild.hidden = !val; return val;"/>
1403 <method name="adjustHeight">
1404 <body><![CDATA[
1405 // detect the desired height of the tree
1406 var bx = this.tree.treeBoxObject;
1407 var view = this.view;
1408 var rows = this.maxRows || 6;
1409 if (!view.rowCount || (rows && view.rowCount < rows))
1410 rows = view.rowCount;
1412 var height = rows * bx.rowHeight;
1414 if (height == 0)
1415 this.tree.setAttribute("collapsed", "true");
1416 else {
1417 if (this.tree.hasAttribute("collapsed"))
1418 this.tree.removeAttribute("collapsed");
1419 this.tree.setAttribute("height", height);
1420 }
1421 ]]></body>
1422 </method>
1424 <method name="clearSelection">
1425 <body>
1426 this.selection.clearSelection();
1427 </body>
1428 </method>
1430 <method name="getNextIndex">
1431 <parameter name="aReverse"/>
1432 <parameter name="aPage"/>
1433 <parameter name="aIndex"/>
1434 <parameter name="aMaxRow"/>
1435 <body><![CDATA[
1436 if (aMaxRow < 0)
1437 return -1;
1439 if (aIndex == -1)
1440 return aReverse ? aMaxRow : 0;
1441 if (aIndex == (aReverse ? 0 : aMaxRow))
1442 return -1;
1444 var amount = aPage ? this.pageCount - 1 : 1;
1445 aIndex = aReverse ? aIndex - amount : aIndex + amount;
1446 if (aIndex > aMaxRow)
1447 return aMaxRow;
1448 if (aIndex < 0)
1449 return 0;
1450 return aIndex;
1451 ]]></body>
1452 </method>
1454 <!-- =================== nsIAutoCompletePopup =================== -->
1456 <field name="input">
1457 null
1458 </field>
1460 <!-- This property is meant to be overriden by bindings extending
1461 this one. When the user selects an item from the list by
1462 hitting enter or clicking, this method can set the value
1463 of the textbox to a different value if it wants to. -->
1464 <property name="overrideValue" readonly="true" onget="return null;"/>
1466 <property name="selectedIndex">
1467 <getter>
1468 if (!this.view || !this.selection.count)
1469 return -1;
1470 var start = {}, end = {};
1471 this.view.selection.getRangeAt(0, start, end);
1472 return start.value;
1473 </getter>
1474 <setter>
1475 if (this.view) {
1476 this.selection.select(val);
1477 if (val >= 0) {
1478 this.view.selection.currentIndex = -1;
1479 this.tree.treeBoxObject.ensureRowIsVisible(val);
1480 }
1481 }
1482 return val;
1483 </setter>
1484 </property>
1486 <property name="popupOpen" onget="return !!this.input;" readonly="true"/>
1488 <method name="openAutocompletePopup">
1489 <parameter name="aInput"/>
1490 <parameter name="aElement"/>
1491 <body><![CDATA[
1492 if (!this.input) {
1493 this.tree.view = aInput.controller;
1494 this.view = this.tree.view;
1495 this.showCommentColumn = aInput.showCommentColumn;
1496 this.maxRows = aInput.maxRows;
1497 this.invalidate();
1499 var viewer = aElement
1500 .ownerDocument
1501 .defaultView
1502 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
1503 .getInterface(Components.interfaces.nsIWebNavigation)
1504 .QueryInterface(Components.interfaces.nsIDocShell)
1505 .contentViewer
1506 .QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
1507 var rect = aElement.getBoundingClientRect();
1508 var width = Math.round((rect.right - rect.left) * viewer.fullZoom);
1509 this.setAttribute("width", width > 100 ? width : 100);
1510 // Adjust the direction (which is not inherited) of the autocomplete
1511 // popup list, based on the textbox direction. (Bug 707039)
1512 this.style.direction = aElement.ownerDocument.defaultView
1513 .getComputedStyle(aElement)
1514 .direction;
1515 const nsIPopupBoxObject = Components.interfaces.nsIPopupBoxObject;
1516 this.popupBoxObject.setConsumeRollupEvent(aInput.consumeRollupEvent
1517 ? nsIPopupBoxObject.ROLLUP_CONSUME
1518 : nsIPopupBoxObject.ROLLUP_NO_CONSUME);
1519 this.openPopup(aElement, "after_start", 0, 0, false, false);
1520 if (this.state != "closed")
1521 this.input = aInput;
1522 }
1523 ]]></body>
1524 </method>
1526 <method name="closePopup">
1527 <body>
1528 this.hidePopup();
1529 </body>
1530 </method>
1532 <method name="invalidate">
1533 <body>
1534 if (this.view)
1535 this.adjustHeight();
1536 this.tree.treeBoxObject.invalidate();
1537 </body>
1538 </method>
1540 <method name="selectBy">
1541 <parameter name="aReverse"/>
1542 <parameter name="aPage"/>
1543 <body><![CDATA[
1544 try {
1545 return this.selectedIndex = this.getNextIndex(aReverse, aPage, this.selectedIndex, this.view.rowCount - 1);
1546 } catch (ex) {
1547 // do nothing - occasionally timer-related js errors happen here
1548 // e.g. "this.selectedIndex has no properties", when you type fast and hit a
1549 // navigation key before this popup has opened
1550 return -1;
1551 }
1552 ]]></body>
1553 </method>
1554 </implementation>
1556 <handlers>
1557 <handler event="popupshowing">
1558 if (this.textbox)
1559 this.textbox.mMenuOpen = true;
1560 </handler>
1562 <handler event="popuphiding">
1563 if (this.textbox)
1564 this.textbox.mMenuOpen = false;
1565 this.clearSelection();
1566 this.input = null;
1567 </handler>
1568 </handlers>
1569 </binding>
1571 <binding id="autocomplete-treebody">
1572 <implementation>
1573 <field name="popup">document.getBindingParent(this);</field>
1575 <field name="mLastMoveTime">Date.now()</field>
1576 </implementation>
1578 <handlers>
1579 <handler event="mouseout" action="this.popup.selectedIndex = -1;"/>
1581 <handler event="mouseup"><![CDATA[
1582 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
1583 if (rc != -1) {
1584 this.popup.selectedIndex = rc;
1585 this.popup.view.handleEnter(true);
1586 }
1587 ]]></handler>
1589 <handler event="mousemove"><![CDATA[
1590 if (Date.now() - this.mLastMoveTime > 30) {
1591 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
1592 if (rc != -1 && rc != this.popup.selectedIndex)
1593 this.popup.selectedIndex = rc;
1594 this.mLastMoveTime = Date.now();
1595 }
1596 ]]></handler>
1597 </handlers>
1598 </binding>
1600 <binding id="autocomplete-history-popup"
1601 extends="chrome://global/content/bindings/popup.xml#popup-scrollbars">
1602 <resources>
1603 <stylesheet src="chrome://global/content/autocomplete.css"/>
1604 <stylesheet src="chrome://global/skin/autocomplete.css"/>
1605 </resources>
1607 <implementation>
1608 <method name="removeOpenAttribute">
1609 <parameter name="parentNode"/>
1610 <body><![CDATA[
1611 parentNode.removeAttribute("open");
1612 ]]></body>
1613 </method>
1614 </implementation>
1616 <handlers>
1617 <handler event="popuphiding"><![CDATA[
1618 setTimeout(this.removeOpenAttribute, 0, this.parentNode);
1619 ]]></handler>
1620 </handlers>
1621 </binding>
1623 <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
1625 <implementation>
1626 <method name="showPopup">
1627 <body><![CDATA[
1628 var textbox = document.getBindingParent(this);
1629 var kids = textbox.getElementsByClassName("autocomplete-history-popup");
1630 if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup
1631 var w = textbox.boxObject.width;
1632 if (w != kids[0].boxObject.width)
1633 kids[0].width = w;
1634 kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
1635 textbox.setAttribute("open", "true");
1636 }
1637 ]]></body>
1638 </method>
1639 </implementation>
1641 <handlers>
1642 <handler event="mousedown"><![CDATA[
1643 this.showPopup();
1644 ]]></handler>
1645 </handlers>
1646 </binding>
1648 </bindings>