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"?>
3 # -*- Mode: HTML -*-
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 <!DOCTYPE bindings [
9 <!ENTITY % actionsDTD SYSTEM "chrome://global/locale/actions.dtd">
10 %actionsDTD;
11 ]>
13 <bindings id="autocompleteBindings"
14 xmlns="http://www.mozilla.org/xbl"
15 xmlns:html="http://www.w3.org/1999/xhtml"
16 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
17 xmlns:xbl="http://www.mozilla.org/xbl">
19 <binding id="autocomplete" role="xul:combobox"
20 extends="chrome://global/content/bindings/textbox.xml#textbox">
21 <resources>
22 <stylesheet src="chrome://global/skin/autocomplete.css"/>
23 </resources>
25 <content sizetopopup="pref">
26 <xul:hbox class="autocomplete-textbox-container" flex="1" xbl:inherits="focused">
27 <children includes="image|deck|stack|box">
28 <xul:image class="autocomplete-icon" allowevents="true"/>
29 </children>
31 <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
32 <children/>
33 <html:input anonid="input" class="autocomplete-textbox textbox-input"
34 allowevents="true"
35 xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/>
36 </xul:hbox>
37 <children includes="hbox"/>
38 </xul:hbox>
40 <xul:dropmarker anonid="historydropmarker" class="autocomplete-history-dropmarker"
41 allowevents="true"
42 xbl:inherits="open,enablehistory,parentfocused=focused"/>
44 <xul:popupset anonid="popupset" class="autocomplete-result-popupset"/>
46 <children includes="toolbarbutton"/>
47 </content>
49 <implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement">
50 <field name="mController">null</field>
51 <field name="mSearchNames">null</field>
52 <field name="mIgnoreInput">false</field>
53 <field name="mEnterEvent">null</field>
55 <field name="_searchBeginHandler">null</field>
56 <field name="_searchCompleteHandler">null</field>
57 <field name="_textEnteredHandler">null</field>
58 <field name="_textRevertedHandler">null</field>
60 <constructor><![CDATA[
61 this.mController = Components.classes["@mozilla.org/autocomplete/controller;1"].
62 getService(Components.interfaces.nsIAutoCompleteController);
64 this._searchBeginHandler = this.initEventHandler("searchbegin");
65 this._searchCompleteHandler = this.initEventHandler("searchcomplete");
66 this._textEnteredHandler = this.initEventHandler("textentered");
67 this._textRevertedHandler = this.initEventHandler("textreverted");
69 // For security reasons delay searches on pasted values.
70 this.inputField.controllers.insertControllerAt(0, this._pasteController);
71 ]]></constructor>
73 <destructor><![CDATA[
74 this.inputField.controllers.removeController(this._pasteController);
75 ]]></destructor>
77 <!-- =================== nsIAutoCompleteInput =================== -->
79 <field name="popup"><![CDATA[
80 // Wrap in a block so that the let statements don't
81 // create properties on 'this' (bug 635252).
82 {
83 let popup = null;
84 let popupId = this.getAttribute("autocompletepopup");
85 if (popupId)
86 popup = document.getElementById(popupId);
87 if (!popup) {
88 popup = document.createElement("panel");
89 popup.setAttribute("type", "autocomplete");
90 popup.setAttribute("noautofocus", "true");
92 let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
93 popupset.appendChild(popup);
94 }
95 popup.mInput = this;
96 popup;
97 }
98 ]]></field>
100 <property name="controller" onget="return this.mController;" readonly="true"/>
102 <property name="popupOpen"
103 onget="return this.popup.popupOpen;"
104 onset="if (val) this.openPopup(); else this.closePopup();"/>
106 <property name="disableAutoComplete"
107 onset="this.setAttribute('disableautocomplete', val); return val;"
108 onget="return this.getAttribute('disableautocomplete') == 'true';"/>
110 <property name="completeDefaultIndex"
111 onset="this.setAttribute('completedefaultindex', val); return val;"
112 onget="return this.getAttribute('completedefaultindex') == 'true';"/>
114 <property name="completeSelectedIndex"
115 onset="this.setAttribute('completeselectedindex', val); return val;"
116 onget="return this.getAttribute('completeselectedindex') == 'true';"/>
118 <property name="forceComplete"
119 onset="this.setAttribute('forcecomplete', val); return val;"
120 onget="return this.getAttribute('forcecomplete') == 'true';"/>
122 <property name="minResultsForPopup"
123 onset="this.setAttribute('minresultsforpopup', val); return val;"
124 onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>
126 <property name="showCommentColumn"
127 onset="this.setAttribute('showcommentcolumn', val); return val;"
128 onget="return this.getAttribute('showcommentcolumn') == 'true';"/>
130 <property name="showImageColumn"
131 onset="this.setAttribute('showimagecolumn', val); return val;"
132 onget="return this.getAttribute('showimagecolumn') == 'true';"/>
134 <property name="timeout"
135 onset="this.setAttribute('timeout', val); return val;">
136 <getter><![CDATA[
137 // For security reasons delay searches on pasted values.
138 if (this._valueIsPasted) {
139 let t = parseInt(this.getAttribute('pastetimeout'));
140 return isNaN(t) ? 1000 : t;
141 }
143 let t = parseInt(this.getAttribute('timeout'));
144 return isNaN(t) ? 50 : t;
145 ]]></getter>
146 </property>
148 <property name="searchParam"
149 onget="return this.getAttribute('autocompletesearchparam') || '';"
150 onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
152 <property name="searchCount" readonly="true"
153 onget="this.initSearchNames(); return this.mSearchNames.length;"/>
155 <field name="PrivateBrowsingUtils" readonly="true">
156 let utils = {};
157 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", utils);
158 utils.PrivateBrowsingUtils
159 </field>
161 <property name="inPrivateContext" readonly="true"
162 onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/>
164 <!-- This is the maximum number of drop-down rows we get when we
165 hit the drop marker beside fields that have it (like the URLbar).-->
166 <field name="maxDropMarkerRows" readonly="true">14</field>
168 <method name="getSearchAt">
169 <parameter name="aIndex"/>
170 <body><![CDATA[
171 this.initSearchNames();
172 return this.mSearchNames[aIndex];
173 ]]></body>
174 </method>
176 <property name="textValue"
177 onget="return this.value;">
178 <setter><![CDATA[
179 // Completing a result should simulate the user typing the result,
180 // so fire an input event.
181 // Trim popup selected values, but never trim results coming from
182 // autofill.
183 if (this.popup.selectedIndex == -1)
184 this._disableTrim = true;
185 this.value = val;
186 this._disableTrim = false;
188 var evt = document.createEvent("UIEvents");
189 evt.initUIEvent("input", true, false, window, 0);
190 this.mIgnoreInput = true;
191 this.dispatchEvent(evt);
192 this.mIgnoreInput = false;
193 return this.value;
194 ]]></setter>
195 </property>
197 <method name="selectTextRange">
198 <parameter name="aStartIndex"/>
199 <parameter name="aEndIndex"/>
200 <body><![CDATA[
201 this.inputField.setSelectionRange(aStartIndex, aEndIndex);
202 ]]></body>
203 </method>
205 <method name="onSearchBegin">
206 <body><![CDATA[
207 if (this._searchBeginHandler)
208 this._searchBeginHandler();
209 ]]></body>
210 </method>
212 <method name="onSearchComplete">
213 <body><![CDATA[
214 if (this.mController.matchCount == 0)
215 this.setAttribute("nomatch", "true");
216 else
217 this.removeAttribute("nomatch");
219 if (this._searchCompleteHandler)
220 this._searchCompleteHandler();
221 ]]></body>
222 </method>
224 <method name="onTextEntered">
225 <body><![CDATA[
226 let rv = false;
227 if (this._textEnteredHandler)
228 rv = this._textEnteredHandler(this.mEnterEvent);
229 this.mEnterEvent = null;
230 return rv;
231 ]]></body>
232 </method>
234 <method name="onTextReverted">
235 <body><![CDATA[
236 if (this._textRevertedHandler)
237 return this._textRevertedHandler();
238 return false;
239 ]]></body>
240 </method>
242 <!-- =================== nsIDOMXULMenuListElement =================== -->
244 <property name="editable" readonly="true"
245 onget="return true;" />
247 <property name="crop"
248 onset="this.setAttribute('crop',val); return val;"
249 onget="return this.getAttribute('crop');"/>
251 <property name="open"
252 onget="return this.getAttribute('open') == 'true';">
253 <setter><![CDATA[
254 if (val)
255 this.showHistoryPopup();
256 else
257 this.closePopup();
258 ]]></setter>
259 </property>
261 <!-- =================== PUBLIC MEMBERS =================== -->
263 <field name="valueIsTyped">false</field>
264 <field name="_disableTrim">false</field>
265 <property name="value">
266 <getter><![CDATA[
267 if (typeof this.onBeforeValueGet == "function") {
268 var result = this.onBeforeValueGet();
269 if (result)
270 return result.value;
271 }
272 return this.inputField.value;
273 ]]></getter>
274 <setter><![CDATA[
275 this.mIgnoreInput = true;
277 if (typeof this.onBeforeValueSet == "function")
278 val = this.onBeforeValueSet(val);
280 if (typeof this.trimValue == "function" && !this._disableTrim)
281 val = this.trimValue(val);
283 this.valueIsTyped = false;
284 this.inputField.value = val;
286 if (typeof this.formatValue == "function")
287 this.formatValue();
289 this.mIgnoreInput = false;
290 var event = document.createEvent('Events');
291 event.initEvent('ValueChange', true, true);
292 this.inputField.dispatchEvent(event);
293 return val;
294 ]]></setter>
295 </property>
297 <property name="focused" readonly="true"
298 onget="return this.getAttribute('focused') == 'true';"/>
300 <!-- maximum number of rows to display at a time -->
301 <property name="maxRows"
302 onset="this.setAttribute('maxrows', val); return val;"
303 onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
305 <!-- option to allow scrolling through the list via the tab key, rather than
306 tab moving focus out of the textbox -->
307 <property name="tabScrolling"
308 onset="return this.setAttribute('tabscrolling', val); return val;"
309 onget="return this.getAttribute('tabscrolling') == 'true';"/>
311 <!-- disable key navigation handling in the popup results -->
312 <property name="disableKeyNavigation"
313 onset="this.setAttribute('disablekeynavigation', val); return val;"
314 onget="return this.getAttribute('disablekeynavigation') == 'true';"/>
316 <!-- option to completely ignore any blur events while
317 searches are still going on. This is useful so that nothing
318 gets autopicked if the window is required to lose focus for
319 some reason (eg in LDAP autocomplete, another window may be
320 brought up so that the user can enter a password to authenticate
321 to an LDAP server). -->
322 <property name="ignoreBlurWhileSearching"
323 onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
324 onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
326 <!-- option to highlight entries that don't have any matches -->
327 <property name="highlightNonMatches"
328 onset="this.setAttribute('highlightnonmatches', val); return val;"
329 onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
331 <!-- =================== PRIVATE MEMBERS =================== -->
333 <!-- ::::::::::::: autocomplete controller ::::::::::::: -->
335 <method name="attachController">
336 <body><![CDATA[
337 this.mController.input = this;
338 ]]></body>
339 </method>
341 <method name="detachController">
342 <body><![CDATA[
343 try {
344 if (this.mController.input == this)
345 this.mController.input = null;
346 } catch (ex) {
347 // nothing really to do.
348 }
349 ]]></body>
350 </method>
352 <!-- ::::::::::::: popup opening ::::::::::::: -->
354 <method name="openPopup">
355 <body><![CDATA[
356 this.popup.openAutocompletePopup(this, this);
357 ]]></body>
358 </method>
360 <method name="closePopup">
361 <body><![CDATA[
362 this.popup.setAttribute("consumeoutsideclicks", "false");
363 this.popup.closePopup();
364 ]]></body>
365 </method>
367 <method name="showHistoryPopup">
368 <body><![CDATA[
369 // history dropmarker pushed state
370 function cleanup(popup) {
371 popup.removeEventListener("popupshowing", onShow, false);
372 }
373 function onShow(event) {
374 var popup = event.target, input = popup.input;
375 cleanup(popup);
376 input.setAttribute("open", "true");
377 function onHide() {
378 input.removeAttribute("open");
379 popup.setAttribute("consumeoutsideclicks", "false");
380 popup.removeEventListener("popuphiding", onHide, false);
381 }
382 popup.addEventListener("popuphiding", onHide, false);
383 }
384 this.popup.addEventListener("popupshowing", onShow, false);
385 setTimeout(cleanup, 1000, this.popup);
387 // Store our "normal" maxRows on the popup, so that it can reset the
388 // value when the popup is hidden.
389 this.popup._normalMaxRows = this.maxRows;
391 // Increase our maxRows temporarily, since we want the dropdown to
392 // be bigger in this case. The popup's popupshowing/popuphiding
393 // handlers will take care of resetting this.
394 this.maxRows = this.maxDropMarkerRows;
396 // Ensure that we have focus.
397 if (!this.focused)
398 this.focus();
399 this.popup.setAttribute("consumeoutsideclicks", "true");
400 this.attachController();
401 this.mController.startSearch("");
402 ]]></body>
403 </method>
405 <method name="toggleHistoryPopup">
406 <body><![CDATA[
407 if (!this.popup.popupOpen)
408 this.showHistoryPopup();
409 else
410 this.closePopup();
411 ]]></body>
412 </method>
414 <!-- ::::::::::::: event dispatching ::::::::::::: -->
416 <method name="initEventHandler">
417 <parameter name="aEventType"/>
418 <body><![CDATA[
419 let handlerString = this.getAttribute("on" + aEventType);
420 if (handlerString) {
421 return (new Function("eventType", "param", handlerString)).bind(this, aEventType);
422 }
423 return null;
424 ]]></body>
425 </method>
427 <!-- ::::::::::::: key handling ::::::::::::: -->
429 <method name="onKeyPress">
430 <parameter name="aEvent"/>
431 <body><![CDATA[
432 if (aEvent.target.localName != "textbox")
433 return true; // Let child buttons of autocomplete take input
435 //XXXpch this is so bogus...
436 if (aEvent.defaultPrevented)
437 return false;
439 var cancel = false;
441 // Catch any keys that could potentially move the caret. Ctrl can be
442 // used in combination with these keys on Windows and Linux; and Alt
443 // can be used on OS X, so make sure the unused one isn't used.
444 if (!this.disableKeyNavigation &&
445 #ifdef XP_MACOSX
446 !aEvent.ctrlKey) {
447 #else
448 !aEvent.altKey) {
449 #endif
450 switch (aEvent.keyCode) {
451 case KeyEvent.DOM_VK_LEFT:
452 case KeyEvent.DOM_VK_RIGHT:
453 case KeyEvent.DOM_VK_HOME:
454 cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
455 break;
456 }
457 }
459 // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
460 if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) {
461 switch (aEvent.keyCode) {
462 case KeyEvent.DOM_VK_TAB:
463 if (this.tabScrolling && this.popup.popupOpen)
464 cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
465 KeyEvent.DOM_VK_UP :
466 KeyEvent.DOM_VK_DOWN);
467 else if (this.forceComplete && this.mController.matchCount >= 1)
468 this.mController.handleTab();
469 break;
470 case KeyEvent.DOM_VK_UP:
471 case KeyEvent.DOM_VK_DOWN:
472 case KeyEvent.DOM_VK_PAGE_UP:
473 case KeyEvent.DOM_VK_PAGE_DOWN:
474 cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
475 break;
476 }
477 }
479 // Handle keys we know aren't part of a shortcut, even with Alt or
480 // Ctrl.
481 switch (aEvent.keyCode) {
482 case KeyEvent.DOM_VK_ESCAPE:
483 cancel = this.mController.handleEscape();
484 break;
485 case KeyEvent.DOM_VK_RETURN:
486 #ifdef XP_MACOSX
487 // Prevent the default action, since it will beep on Mac
488 if (aEvent.metaKey)
489 aEvent.preventDefault();
490 #endif
491 this.mEnterEvent = aEvent;
492 cancel = this.mController.handleEnter(false);
493 break;
494 case KeyEvent.DOM_VK_DELETE:
495 #ifdef XP_MACOSX
496 case KeyEvent.DOM_VK_BACK_SPACE:
497 if (aEvent.shiftKey)
498 #endif
499 cancel = this.mController.handleDelete();
500 break;
501 case KeyEvent.DOM_VK_DOWN:
502 case KeyEvent.DOM_VK_UP:
503 if (aEvent.altKey)
504 this.toggleHistoryPopup();
505 break;
506 #ifndef XP_MACOSX
507 case KeyEvent.DOM_VK_F4:
508 this.toggleHistoryPopup();
509 break;
510 #endif
511 }
513 if (cancel) {
514 aEvent.stopPropagation();
515 aEvent.preventDefault();
516 }
518 return true;
519 ]]></body>
520 </method>
522 <!-- ::::::::::::: miscellaneous ::::::::::::: -->
524 <method name="initSearchNames">
525 <body><![CDATA[
526 if (!this.mSearchNames) {
527 var names = this.getAttribute("autocompletesearch");
528 if (!names)
529 this.mSearchNames = [];
530 else
531 this.mSearchNames = names.split(" ");
532 }
533 ]]></body>
534 </method>
536 <method name="_focus">
537 <!-- doesn't reset this.mController -->
538 <body><![CDATA[
539 this._dontBlur = true;
540 this.focus();
541 this._dontBlur = false;
542 ]]></body>
543 </method>
545 <method name="resetActionType">
546 <body><![CDATA[
547 if (this.mIgnoreInput)
548 return;
549 this.removeAttribute("actiontype");
550 ]]></body>
551 </method>
553 <field name="_valueIsPasted">false</field>
554 <field name="_pasteController"><![CDATA[
555 ({
556 _autocomplete: this,
557 _kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
558 supportsCommand: function(aCommand) aCommand == "cmd_paste",
559 doCommand: function(aCommand) {
560 this._autocomplete._valueIsPasted = true;
561 this._autocomplete.editor.paste(this._kGlobalClipboard);
562 this._autocomplete._valueIsPasted = false;
563 },
564 isCommandEnabled: function(aCommand) {
565 return this._autocomplete.editor.isSelectionEditable &&
566 this._autocomplete.editor.canPaste(this._kGlobalClipboard);
567 },
568 onEvent: function() {}
569 })
570 ]]></field>
571 </implementation>
573 <handlers>
574 <handler event="input"><![CDATA[
575 if (!this.mIgnoreInput && this.mController.input == this) {
576 this.valueIsTyped = true;
577 this.mController.handleText();
578 }
579 this.resetActionType();
580 ]]></handler>
582 <handler event="keypress" phase="capturing"
583 action="return this.onKeyPress(event);"/>
585 <handler event="compositionstart" phase="capturing"
586 action="if (this.mController.input == this) this.mController.handleStartComposition();"/>
588 <handler event="compositionend" phase="capturing"
589 action="if (this.mController.input == this) this.mController.handleEndComposition();"/>
591 <handler event="focus" phase="capturing"
592 action="this.attachController();"/>
594 <handler event="blur" phase="capturing"
595 action="if (!this._dontBlur) this.detachController();"/>
596 </handlers>
597 </binding>
599 <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
600 <resources>
601 <stylesheet src="chrome://global/skin/tree.css"/>
602 <stylesheet src="chrome://global/skin/autocomplete.css"/>
603 </resources>
605 <content ignorekeys="true" level="top" consumeoutsideclicks="false">
606 <xul:tree anonid="tree" class="autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single">
607 <xul:treecols anonid="treecols">
608 <xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/>
609 </xul:treecols>
610 <xul:treechildren class="autocomplete-treebody"/>
611 </xul:tree>
612 </content>
614 <implementation>
615 <field name="mShowCommentColumn">false</field>
616 <field name="mShowImageColumn">false</field>
618 <property name="showCommentColumn"
619 onget="return this.mShowCommentColumn;">
620 <setter>
621 <![CDATA[
622 if (!val && this.mShowCommentColumn) {
623 // reset the flex on the value column and remove the comment column
624 document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 1);
625 this.removeColumn("treecolAutoCompleteComment");
626 } else if (val && !this.mShowCommentColumn) {
627 // reset the flex on the value column and add the comment column
628 document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 2);
629 this.addColumn({id: "treecolAutoCompleteComment", flex: 1});
630 }
631 this.mShowCommentColumn = val;
632 return val;
633 ]]>
634 </setter>
635 </property>
637 <property name="showImageColumn"
638 onget="return this.mShowImageColumn;">
639 <setter>
640 <![CDATA[
641 if (!val && this.mShowImageColumn) {
642 // remove the image column
643 this.removeColumn("treecolAutoCompleteImage");
644 } else if (val && !this.mShowImageColumn) {
645 // add the image column
646 this.addColumn({id: "treecolAutoCompleteImage", flex: 1});
647 }
648 this.mShowImageColumn = val;
649 return val;
650 ]]>
651 </setter>
652 </property>
655 <method name="addColumn">
656 <parameter name="aAttrs"/>
657 <body>
658 <![CDATA[
659 var col = document.createElement("treecol");
660 col.setAttribute("class", "autocomplete-treecol");
661 for (var name in aAttrs)
662 col.setAttribute(name, aAttrs[name]);
663 this.treecols.appendChild(col);
664 return col;
665 ]]>
666 </body>
667 </method>
669 <method name="removeColumn">
670 <parameter name="aColId"/>
671 <body>
672 <![CDATA[
673 return this.treecols.removeChild(document.getElementById(aColId));
674 ]]>
675 </body>
676 </method>
678 <property name="selectedIndex"
679 onget="return this.tree.currentIndex;">
680 <setter>
681 <![CDATA[
682 this.tree.view.selection.select(val);
683 if (this.tree.treeBoxObject.height > 0)
684 this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val);
685 // Fire select event on xul:tree so that accessibility API
686 // support layer can fire appropriate accessibility events.
687 var event = document.createEvent('Events');
688 event.initEvent("select", true, true);
689 this.tree.dispatchEvent(event);
690 return val;
691 ]]></setter>
692 </property>
694 <method name="adjustHeight">
695 <body>
696 <![CDATA[
697 // detect the desired height of the tree
698 var bx = this.tree.treeBoxObject;
699 var view = this.tree.view;
700 if (!view)
701 return;
702 var rows = this.maxRows;
703 if (!view.rowCount || (rows && view.rowCount < rows))
704 rows = view.rowCount;
706 var height = rows * bx.rowHeight;
708 if (height == 0)
709 this.tree.setAttribute("collapsed", "true");
710 else {
711 if (this.tree.hasAttribute("collapsed"))
712 this.tree.removeAttribute("collapsed");
714 this.tree.setAttribute("height", height);
715 }
716 this.tree.setAttribute("hidescrollbar", view.rowCount <= rows);
717 ]]>
718 </body>
719 </method>
721 <method name="openAutocompletePopup">
722 <parameter name="aInput"/>
723 <parameter name="aElement"/>
724 <body><![CDATA[
725 // until we have "baseBinding", (see bug #373652) this allows
726 // us to override openAutocompletePopup(), but still call
727 // the method on the base class
728 this._openAutocompletePopup(aInput, aElement);
729 ]]></body>
730 </method>
732 <method name="_openAutocompletePopup">
733 <parameter name="aInput"/>
734 <parameter name="aElement"/>
735 <body><![CDATA[
736 if (!this.mPopupOpen) {
737 this.mInput = aInput;
738 this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView);
739 this.invalidate();
741 this.showCommentColumn = this.mInput.showCommentColumn;
742 this.showImageColumn = this.mInput.showImageColumn;
744 var rect = aElement.getBoundingClientRect();
745 var nav = aElement.ownerDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
746 .getInterface(Components.interfaces.nsIWebNavigation);
747 var docShell = nav.QueryInterface(Components.interfaces.nsIDocShell);
748 var docViewer = docShell.contentViewer.QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
749 var width = (rect.right - rect.left) * docViewer.fullZoom;
750 this.setAttribute("width", width > 100 ? width : 100);
752 // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840
753 var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction;
754 this.style.direction = popupDirection;
756 this.openPopup(aElement, "after_start", 0, 0, false, false);
757 }
758 ]]></body>
759 </method>
761 <method name="invalidate">
762 <body><![CDATA[
763 this.adjustHeight();
764 this.tree.treeBoxObject.invalidate();
765 ]]></body>
766 </method>
768 <method name="selectBy">
769 <parameter name="aReverse"/>
770 <parameter name="aPage"/>
771 <body><![CDATA[
772 try {
773 var amount = aPage ? 5 : 1;
774 this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this.tree.view.rowCount-1);
775 if (this.selectedIndex == -1) {
776 this.input._focus();
777 }
778 } catch (ex) {
779 // do nothing - occasionally timer-related js errors happen here
780 // e.g. "this.selectedIndex has no properties", when you type fast and hit a
781 // navigation key before this popup has opened
782 }
783 ]]></body>
784 </method>
786 <!-- =================== PUBLIC MEMBERS =================== -->
788 <field name="tree">
789 document.getAnonymousElementByAttribute(this, "anonid", "tree");
790 </field>
792 <field name="treecols">
793 document.getAnonymousElementByAttribute(this, "anonid", "treecols");
794 </field>
796 <property name="view"
797 onget="return this.mView;">
798 <setter><![CDATA[
799 // We must do this by hand because the tree binding may not be ready yet
800 this.mView = val;
801 var bx = this.tree.boxObject;
802 bx = bx.QueryInterface(Components.interfaces.nsITreeBoxObject);
803 bx.view = val;
804 ]]></setter>
805 </property>
807 </implementation>
808 </binding>
810 <binding id="autocomplete-base-popup" role="none"
811 extends="chrome://global/content/bindings/popup.xml#popup">
812 <implementation implements="nsIAutoCompletePopup">
813 <field name="mInput">null</field>
814 <field name="mPopupOpen">false</field>
816 <!-- =================== nsIAutoCompletePopup =================== -->
818 <property name="input" readonly="true"
819 onget="return this.mInput"/>
821 <property name="overrideValue" readonly="true"
822 onget="return null;"/>
824 <property name="popupOpen" readonly="true"
825 onget="return this.mPopupOpen;"/>
827 <method name="closePopup">
828 <body>
829 <![CDATA[
830 if (this.mPopupOpen) {
831 this.hidePopup();
832 this.removeAttribute("width");
833 }
834 ]]>
835 </body>
836 </method>
838 <!-- This is the default number of rows that we give the autocomplete
839 popup when the textbox doesn't have a "maxrows" attribute
840 for us to use. -->
841 <field name="defaultMaxRows" readonly="true">6</field>
843 <!-- In some cases (e.g. when the input's dropmarker button is clicked),
844 the input wants to display a popup with more rows. In that case, it
845 should increase its maxRows property and store the "normal" maxRows
846 in this field. When the popup is hidden, we restore the input's
847 maxRows to the value stored in this field.
849 This field is set to -1 between uses so that we can tell when it's
850 been set by the input and when we need to set it in the popupshowing
851 handler. -->
852 <field name="_normalMaxRows">-1</field>
854 <property name="maxRows" readonly="true">
855 <getter>
856 <![CDATA[
857 return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows;
858 ]]>
859 </getter>
860 </property>
862 <method name="getNextIndex">
863 <parameter name="aReverse"/>
864 <parameter name="aAmount"/>
865 <parameter name="aIndex"/>
866 <parameter name="aMaxRow"/>
867 <body><![CDATA[
868 if (aMaxRow < 0)
869 return -1;
871 var newIdx = aIndex + (aReverse?-1:1)*aAmount;
872 if (aReverse && aIndex == -1 || newIdx > aMaxRow && aIndex != aMaxRow)
873 newIdx = aMaxRow;
874 else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0)
875 newIdx = 0;
877 if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow)
878 aIndex = -1;
879 else
880 aIndex = newIdx;
882 return aIndex;
883 ]]></body>
884 </method>
886 <method name="onPopupClick">
887 <parameter name="aEvent"/>
888 <body><![CDATA[
889 var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
890 controller.handleEnter(true);
891 ]]></body>
892 </method>
893 </implementation>
895 <handlers>
896 <handler event="popupshowing"><![CDATA[
897 // If normalMaxRows wasn't already set by the input, then set it here
898 // so that we restore the correct number when the popup is hidden.
899 if (this._normalMaxRows < 0)
900 this._normalMaxRows = this.mInput.maxRows;
902 this.mPopupOpen = true;
903 ]]></handler>
905 <handler event="popuphiding"><![CDATA[
906 var isListActive = true;
907 if (this.selectedIndex == -1)
908 isListActive = false;
909 var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
910 controller.stopSearch();
912 this.mPopupOpen = false;
914 // Reset the maxRows property to the cached "normal" value, and reset
915 // _normalMaxRows so that we can detect whether it was set by the input
916 // when the popupshowing handler runs.
917 this.mInput.maxRows = this._normalMaxRows;
918 this._normalMaxRows = -1;
919 // If the list was being navigated and then closed, make sure
920 // we fire accessible focus event back to textbox
921 if (isListActive) {
922 this.mInput.mIgnoreFocus = true;
923 this.mInput._focus();
924 this.mInput.mIgnoreFocus = false;
925 }
926 ]]></handler>
927 </handlers>
928 </binding>
930 <binding id="autocomplete-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
931 <resources>
932 <stylesheet src="chrome://global/skin/autocomplete.css"/>
933 </resources>
935 <content ignorekeys="true" level="top" consumeoutsideclicks="false">
936 <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox" flex="1"/>
937 <xul:hbox>
938 <children/>
939 </xul:hbox>
940 </content>
942 <implementation implements="nsIAutoCompletePopup">
943 <field name="_currentIndex">0</field>
944 <field name="_rowHeight">0</field>
946 <!-- =================== nsIAutoCompletePopup =================== -->
948 <property name="selectedIndex"
949 onget="return this.richlistbox.selectedIndex;">
950 <setter>
951 <![CDATA[
952 this.richlistbox.selectedIndex = val;
954 // when clearing the selection (val == -1, so selectedItem will be
955 // null), we want to scroll back to the top. see bug #406194
956 this.richlistbox.ensureElementIsVisible(
957 this.richlistbox.selectedItem || this.richlistbox.firstChild);
959 return val;
960 ]]>
961 </setter>
962 </property>
964 <method name="openAutocompletePopup">
965 <parameter name="aInput"/>
966 <parameter name="aElement"/>
967 <body>
968 <![CDATA[
969 // until we have "baseBinding", (see bug #373652) this allows
970 // us to override openAutocompletePopup(), but still call
971 // the method on the base class
972 this._openAutocompletePopup(aInput, aElement);
973 ]]>
974 </body>
975 </method>
977 <method name="_openAutocompletePopup">
978 <parameter name="aInput"/>
979 <parameter name="aElement"/>
980 <body>
981 <![CDATA[
982 if (!this.mPopupOpen) {
983 this.mInput = aInput;
984 // clear any previous selection, see bugs 400671 and 488357
985 this.selectedIndex = -1;
987 var width = aElement.getBoundingClientRect().width;
988 this.setAttribute("width", width > 100 ? width : 100);
989 // invalidate() depends on the width attribute
990 this._invalidate();
992 this.openPopup(aElement, "after_start", 0, 0, false, false);
993 }
994 ]]>
995 </body>
996 </method>
998 <method name="invalidate">
999 <body>
1000 <![CDATA[
1001 // Don't bother doing work if we're not even showing
1002 if (!this.mPopupOpen)
1003 return;
1005 this._invalidate();
1006 ]]>
1007 </body>
1008 </method>
1010 <method name="_invalidate">
1011 <body>
1012 <![CDATA[
1013 if (!this.hasAttribute("height")) {
1014 // collapsed if no matches
1015 this.richlistbox.collapsed = (this._matchCount == 0);
1017 // Dynamically update height until richlistbox.rows works (bug 401939)
1018 // Adjust the height immediately and after the row contents update
1019 this.adjustHeight();
1020 setTimeout(function(self) self.adjustHeight(), 0, this);
1021 }
1023 // make sure to collapse any existing richlistitems
1024 // that aren't going to be used
1025 var existingItemsCount = this.richlistbox.childNodes.length;
1026 for (var i = this._matchCount; i < existingItemsCount; i++)
1027 this.richlistbox.childNodes[i].collapsed = true;
1029 this._currentIndex = 0;
1030 this._appendCurrentResult();
1031 ]]>
1032 </body>
1033 </method>
1035 <property name="maxResults" readonly="true">
1036 <getter>
1037 <![CDATA[
1038 // this is how many richlistitems will be kept around
1039 // (note, this getter may be overridden)
1040 return 20;
1041 ]]>
1042 </getter>
1043 </property>
1045 <property name="_matchCount" readonly="true">
1046 <getter>
1047 <![CDATA[
1048 return Math.min(this.mInput.controller.matchCount, this.maxResults);
1049 ]]>
1050 </getter>
1051 </property>
1053 <method name="adjustHeight">
1054 <body>
1055 <![CDATA[
1056 // Figure out how many rows to show
1057 let rows = this.richlistbox.childNodes;
1058 let numRows = Math.min(this._matchCount, this.maxRows, rows.length);
1060 // Default the height to 0 if we have no rows to show
1061 let height = 0;
1062 if (numRows) {
1063 if (!this._rowHeight) {
1064 let firstRowRect = rows[0].getBoundingClientRect();
1065 this._rowHeight = firstRowRect.height;
1066 }
1068 // Calculate the height to have the first row to last row shown
1069 height = this._rowHeight * numRows;
1070 }
1072 // Only update the height if we have a non-zero height and if it
1073 // changed (the richlistbox is collapsed if there are no results)
1074 if (height && height != this.richlistbox.height)
1075 this.richlistbox.height = height;
1076 ]]>
1077 </body>
1078 </method>
1080 <method name="_appendCurrentResult">
1081 <body>
1082 <![CDATA[
1083 var controller = this.mInput.controller;
1084 var matchCount = this._matchCount;
1085 var existingItemsCount = this.richlistbox.childNodes.length;
1087 // Process maxRows per chunk to improve performance and user experience
1088 for (let i = 0; i < this.maxRows; i++) {
1089 if (this._currentIndex >= matchCount)
1090 return;
1092 var item;
1094 // trim the leading/trailing whitespace
1095 var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");
1097 // Unescape the URI spec for showing as an entry in the popup
1098 let url = Components.classes["@mozilla.org/intl/texttosuburi;1"].
1099 getService(Components.interfaces.nsITextToSubURI).
1100 unEscapeURIForUI("UTF-8", controller.getValueAt(this._currentIndex));
1102 if (typeof this.input.trimValue == "function")
1103 url = this.input.trimValue(url);
1105 if (this._currentIndex < existingItemsCount) {
1106 // re-use the existing item
1107 item = this.richlistbox.childNodes[this._currentIndex];
1109 // Completely re-use the existing richlistitem if it's the same
1110 if (item.getAttribute("text") == trimmedSearchString &&
1111 item.getAttribute("url") == url) {
1112 item.collapsed = false;
1113 this._currentIndex++;
1114 continue;
1115 }
1116 }
1117 else {
1118 // need to create a new item
1119 item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
1120 }
1122 // set these attributes before we set the class
1123 // so that we can use them from the constructor
1124 item.setAttribute("image", controller.getImageAt(this._currentIndex));
1125 item.setAttribute("url", url);
1126 item.setAttribute("title", controller.getCommentAt(this._currentIndex));
1127 item.setAttribute("type", controller.getStyleAt(this._currentIndex));
1128 item.setAttribute("text", trimmedSearchString);
1130 if (this._currentIndex < existingItemsCount) {
1131 // re-use the existing item
1132 item._adjustAcItem();
1133 item.collapsed = false;
1134 }
1135 else {
1136 // set the class at the end so we can use the attributes
1137 // in the xbl constructor
1138 item.className = "autocomplete-richlistitem";
1139 this.richlistbox.appendChild(item);
1140 }
1142 this._currentIndex++;
1143 }
1145 // yield after each batch of items so that typing the url bar is responsive
1146 setTimeout(function (self) { self._appendCurrentResult(); }, 0, this);
1147 ]]>
1148 </body>
1149 </method>
1151 <method name="selectBy">
1152 <parameter name="aReverse"/>
1153 <parameter name="aPage"/>
1154 <body>
1155 <![CDATA[
1156 try {
1157 var amount = aPage ? 5 : 1;
1159 // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount
1160 this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this._matchCount - 1);
1161 if (this.selectedIndex == -1) {
1162 this.input._focus();
1163 }
1164 } catch (ex) {
1165 // do nothing - occasionally timer-related js errors happen here
1166 // e.g. "this.selectedIndex has no properties", when you type fast and hit a
1167 // navigation key before this popup has opened
1168 }
1169 ]]>
1170 </body>
1171 </method>
1173 <field name="richlistbox">
1174 document.getAnonymousElementByAttribute(this, "anonid", "richlistbox");
1175 </field>
1177 <property name="view"
1178 onget="return this.mInput.controller;"
1179 onset="return val;"/>
1181 </implementation>
1182 </binding>
1184 <binding id="autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
1185 <content>
1186 <xul:hbox align="center" class="ac-title-box">
1187 <xul:image xbl:inherits="src=image" class="ac-site-icon"/>
1188 <xul:hbox anonid="title-box" class="ac-title" flex="1"
1189 onunderflow="_doUnderflow('_title');"
1190 onoverflow="_doOverflow('_title');">
1191 <xul:description anonid="title" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
1192 </xul:hbox>
1193 <xul:label anonid="title-overflow-ellipsis" xbl:inherits="selected"
1194 class="ac-ellipsis-after ac-comment"/>
1195 <xul:hbox anonid="extra-box" class="ac-extra" align="center" hidden="true">
1196 <xul:image class="ac-result-type-tag"/>
1197 <xul:label class="ac-normal-text ac-comment" xbl:inherits="selected" value=":"/>
1198 <xul:description anonid="extra" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
1199 </xul:hbox>
1200 <xul:image anonid="type-image" class="ac-type-icon"/>
1201 </xul:hbox>
1202 <xul:hbox align="center" class="ac-url-box">
1203 <xul:spacer class="ac-site-icon"/>
1204 <xul:image class="ac-action-icon"/>
1205 <xul:hbox anonid="url-box" class="ac-url" flex="1"
1206 onunderflow="_doUnderflow('_url');"
1207 onoverflow="_doOverflow('_url');">
1208 <xul:description anonid="url" class="ac-normal-text ac-url-text"
1209 xbl:inherits="selected type"/>
1210 <xul:description anonid="action" class="ac-normal-text ac-action-text"
1211 xbl:inherits="selected type"/>
1212 </xul:hbox>
1213 <xul:label anonid="url-overflow-ellipsis" xbl:inherits="selected"
1214 class="ac-ellipsis-after ac-url-text"/>
1215 <xul:spacer class="ac-type-icon"/>
1216 </xul:hbox>
1217 </content>
1218 <implementation implements="nsIDOMXULSelectControlItemElement">
1219 <constructor>
1220 <![CDATA[
1221 let ellipsis = "\u2026";
1222 try {
1223 ellipsis = Components.classes["@mozilla.org/preferences-service;1"].
1224 getService(Components.interfaces.nsIPrefBranch).
1225 getComplexValue("intl.ellipsis",
1226 Components.interfaces.nsIPrefLocalizedString).data;
1227 } catch (ex) {
1228 // Do nothing.. we already have a default
1229 }
1231 this._urlOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "url-overflow-ellipsis");
1232 this._titleOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "title-overflow-ellipsis");
1234 this._urlOverflowEllipsis.value = ellipsis;
1235 this._titleOverflowEllipsis.value = ellipsis;
1237 this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
1239 this._urlBox = document.getAnonymousElementByAttribute(this, "anonid", "url-box");
1240 this._url = document.getAnonymousElementByAttribute(this, "anonid", "url");
1241 this._action = document.getAnonymousElementByAttribute(this, "anonid", "action");
1243 this._titleBox = document.getAnonymousElementByAttribute(this, "anonid", "title-box");
1244 this._title = document.getAnonymousElementByAttribute(this, "anonid", "title");
1246 this._extraBox = document.getAnonymousElementByAttribute(this, "anonid", "extra-box");
1247 this._extra = document.getAnonymousElementByAttribute(this, "anonid", "extra");
1249 this._adjustAcItem();
1250 ]]>
1251 </constructor>
1253 <property name="label" readonly="true">
1254 <getter>
1255 <![CDATA[
1256 var title = this.getAttribute("title");
1257 var url = this.getAttribute("url");
1258 var panel = this.parentNode.parentNode;
1260 // allow consumers that have extended popups to override
1261 // the label values for the richlistitems
1262 if (panel.createResultLabel)
1263 return panel.createResultLabel(title, url, this.getAttribute("type"));
1265 // aType (ex: "ac-result-type-<aType>") is related to the class of the image,
1266 // and is not "visible" text so don't use it for the label (for accessibility).
1267 return title + " " + url;
1268 ]]>
1269 </getter>
1270 </property>
1272 <field name="_boundaryCutoff">null</field>
1274 <property name="boundaryCutoff" readonly="true">
1275 <getter>
1276 <![CDATA[
1277 if (!this._boundaryCutoff) {
1278 this._boundaryCutoff =
1279 Components.classes["@mozilla.org/preferences-service;1"].
1280 getService(Components.interfaces.nsIPrefBranch).
1281 getIntPref("toolkit.autocomplete.richBoundaryCutoff");
1282 }
1283 return this._boundaryCutoff;
1284 ]]>
1285 </getter>
1286 </property>
1288 <method name="_getBoundaryIndices">
1289 <parameter name="aText"/>
1290 <parameter name="aSearchTokens"/>
1291 <body>
1292 <![CDATA[
1293 // Short circuit for empty search ([""] == "")
1294 if (aSearchTokens == "")
1295 return [0, aText.length];
1297 // Find which regions of text match the search terms
1298 let regions = [];
1299 for each (let search in aSearchTokens) {
1300 let matchIndex;
1301 let startIndex = 0;
1302 let searchLen = search.length;
1304 // Find all matches of the search terms, but stop early for perf
1305 let lowerText = aText.toLowerCase().substr(0, this.boundaryCutoff);
1306 while ((matchIndex = lowerText.indexOf(search, startIndex)) >= 0) {
1307 // Start the next search from where this one finished
1308 startIndex = matchIndex + searchLen;
1309 regions.push([matchIndex, startIndex]);
1310 }
1311 }
1313 // Sort the regions by start position then end position
1314 regions = regions.sort(function(a, b) let (start = a[0] - b[0])
1315 start == 0 ? a[1] - b[1] : start);
1317 // Generate the boundary indices from each region
1318 let start = 0;
1319 let end = 0;
1320 let boundaries = [];
1321 let len = regions.length;
1322 for (let i = 0; i < len; i++) {
1323 // We have a new boundary if the start of the next is past the end
1324 let region = regions[i];
1325 if (region[0] > end) {
1326 // First index is the beginning of match
1327 boundaries.push(start);
1328 // Second index is the beginning of non-match
1329 boundaries.push(end);
1331 // Track the new region now that we've stored the previous one
1332 start = region[0];
1333 }
1335 // Push back the end index for the current or new region
1336 end = Math.max(end, region[1]);
1337 }
1339 // Add the last region
1340 boundaries.push(start);
1341 boundaries.push(end);
1343 // Put on the end boundary if necessary
1344 if (end < aText.length)
1345 boundaries.push(aText.length);
1347 // Skip the first item because it's always 0
1348 return boundaries.slice(1);
1349 ]]>
1350 </body>
1351 </method>
1353 <method name="_getSearchTokens">
1354 <parameter name="aSearch"/>
1355 <body>
1356 <![CDATA[
1357 let search = aSearch.toLowerCase();
1358 return search.split(/\s+/);
1359 ]]>
1360 </body>
1361 </method>
1363 <method name="_setUpDescription">
1364 <parameter name="aDescriptionElement"/>
1365 <parameter name="aText"/>
1366 <parameter name="aNoEmphasis"/>
1367 <body>
1368 <![CDATA[
1369 // Get rid of all previous text
1370 while (aDescriptionElement.hasChildNodes())
1371 aDescriptionElement.removeChild(aDescriptionElement.firstChild);
1373 // If aNoEmphasis is specified, don't add any emphasis
1374 if (aNoEmphasis) {
1375 aDescriptionElement.appendChild(document.createTextNode(aText));
1376 return;
1377 }
1379 // Get the indices that separate match and non-match text
1380 let search = this.getAttribute("text");
1381 let tokens = this._getSearchTokens(search);
1382 let indices = this._getBoundaryIndices(aText, tokens);
1384 let next;
1385 let start = 0;
1386 let len = indices.length;
1387 // Even indexed boundaries are matches, so skip the 0th if it's empty
1388 for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
1389 next = indices[i];
1390 let text = aText.substr(start, next - start);
1391 start = next;
1393 if (i % 2 == 0) {
1394 // Emphasize the text for even indices
1395 let span = aDescriptionElement.appendChild(
1396 document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
1397 span.className = "ac-emphasize-text";
1398 span.textContent = text;
1399 } else {
1400 // Otherwise, it's plain text
1401 aDescriptionElement.appendChild(document.createTextNode(text));
1402 }
1403 }
1404 ]]>
1405 </body>
1406 </method>
1408 <method name="_adjustAcItem">
1409 <body>
1410 <![CDATA[
1411 var url = this.getAttribute("url");
1412 var title = this.getAttribute("title");
1413 var type = this.getAttribute("type");
1415 this.removeAttribute("actiontype");
1417 // The ellipses are hidden via their visibility so that they always
1418 // take up space and don't pop in on top of text when shown. For
1419 // keyword searches, however, the title ellipsis should not take up
1420 // space when hidden. Setting the hidden property accomplishes that.
1421 this._titleOverflowEllipsis.hidden = false;
1423 // If the type includes an action, set up the item appropriately.
1424 var types = type.split(/\s+/);
1425 var actionIndex = types.indexOf("action");
1426 if (actionIndex >= 0) {
1427 let [,action, param] = url.match(/^moz-action:([^,]+),(.*)$/);
1428 this.setAttribute("actiontype", action);
1429 url = param;
1430 let desc = "]]>&action.switchToTab.label;<![CDATA[";
1431 this._setUpDescription(this._action, desc, true);
1433 // Remove the "action" substring so that the correct style, if any,
1434 // is applied below.
1435 types.splice(actionIndex, 1);
1436 type = types.join(" ");
1437 }
1439 // If we have a tag match, show the tags and icon
1440 if (type == "tag") {
1441 // Configure the extra box for tags display
1442 this._extraBox.hidden = false;
1443 this._extraBox.childNodes[0].hidden = false;
1444 this._extraBox.childNodes[1].hidden = true;
1445 this._extraBox.pack = "end";
1446 this._titleBox.flex = 1;
1448 // The title is separated from the tags by an endash
1449 let tags;
1450 [, title, tags] = title.match(/^(.+) \u2013 (.+)$/);
1452 // Each tag is split by a comma in an undefined order, so sort it
1453 let sortedTags = tags.split(",").sort().join(", ");
1455 // Emphasize the matching text in the tags
1456 this._setUpDescription(this._extra, sortedTags);
1458 // Treat tagged matches as bookmarks for the star
1459 type = "bookmark";
1460 } else if (type == "keyword") {
1461 // Configure the extra box for keyword display
1462 this._extraBox.hidden = false;
1463 this._extraBox.childNodes[0].hidden = true;
1464 this._extraBox.childNodes[1].hidden = false;
1465 this._extraBox.pack = "start";
1466 this._titleBox.flex = 0;
1468 // Hide the ellipsis so it doesn't take up space.
1469 this._titleOverflowEllipsis.hidden = true;
1471 // Put the parameters next to the title if we have any
1472 let search = this.getAttribute("text");
1473 let params = "";
1474 let paramsIndex = search.indexOf(' ');
1475 if (paramsIndex != -1)
1476 params = search.substr(paramsIndex + 1);
1478 // Emphasize the keyword parameters
1479 this._setUpDescription(this._extra, params);
1481 // Don't emphasize keyword searches in the title or url
1482 this.setAttribute("text", "");
1483 } else {
1484 // Hide the title's extra box if we don't need extra stuff
1485 this._extraBox.hidden = true;
1486 this._titleBox.flex = 1;
1487 }
1489 // Give the image the icon style and a special one for the type
1490 this._typeImage.className = "ac-type-icon" +
1491 (type ? " ac-result-type-" + type : "");
1493 // Show the url as the title if we don't have a title
1494 if (title == "")
1495 title = url;
1497 // Emphasize the matching search terms for the description
1498 this._setUpDescription(this._title, title);
1499 this._setUpDescription(this._url, url);
1501 // Set up overflow on a timeout because the contents of the box
1502 // might not have a width yet even though we just changed them
1503 setTimeout(this._setUpOverflow, 0, this._titleBox, this._titleOverflowEllipsis);
1504 setTimeout(this._setUpOverflow, 0, this._urlBox, this._urlOverflowEllipsis);
1505 ]]>
1506 </body>
1507 </method>
1509 <method name="_setUpOverflow">
1510 <parameter name="aParentBox"/>
1511 <parameter name="aEllipsis"/>
1512 <body>
1513 <![CDATA[
1514 // Hide the ellipsis incase there's just enough to not underflow
1515 aEllipsis.style.visibility = "hidden";
1517 // Start with the parent's width and subtract off its children
1518 let tooltip = [];
1519 let children = aParentBox.childNodes;
1520 let widthDiff = aParentBox.boxObject.width;
1522 for (let i = 0; i < children.length; i++) {
1523 // Only consider a child if it actually takes up space
1524 let childWidth = children[i].boxObject.width;
1525 if (childWidth > 0) {
1526 // Subtract a little less to account for subpixel rounding
1527 widthDiff -= childWidth - .5;
1529 // Add to the tooltip if it's not hidden and has text
1530 let childText = children[i].textContent;
1531 if (childText)
1532 tooltip.push(childText);
1533 }
1534 }
1536 // If the children take up more space than the parent.. overflow!
1537 if (widthDiff < 0) {
1538 // Re-show the ellipsis now that we know it's needed
1539 aEllipsis.style.visibility = "visible";
1541 // Separate text components with a ndash --
1542 aParentBox.tooltipText = tooltip.join(" \u2013 ");
1543 }
1544 ]]>
1545 </body>
1546 </method>
1548 <method name="_doUnderflow">
1549 <parameter name="aName"/>
1550 <body>
1551 <![CDATA[
1552 // Hide the ellipsis right when we know we're underflowing instead of
1553 // waiting for the timeout to trigger the _setUpOverflow calculations
1554 this[aName + "Box"].tooltipText = "";
1555 this[aName + "OverflowEllipsis"].style.visibility = "hidden";
1556 ]]>
1557 </body>
1558 </method>
1560 <method name="_doOverflow">
1561 <parameter name="aName"/>
1562 <body>
1563 <![CDATA[
1564 this._setUpOverflow(this[aName + "Box"],
1565 this[aName + "OverflowEllipsis"]);
1566 ]]>
1567 </body>
1568 </method>
1570 </implementation>
1571 </binding>
1573 <binding id="autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree">
1574 <content>
1575 <children includes="treecols"/>
1576 <xul:treerows class="autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1">
1577 <children/>
1578 </xul:treerows>
1579 </content>
1580 </binding>
1582 <binding id="autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
1583 <implementation>
1584 <field name="mLastMoveTime">Date.now()</field>
1585 </implementation>
1586 <handlers>
1587 <handler event="mouseup">
1588 <![CDATA[
1589 // don't call onPopupClick for the scrollbar buttons, thumb, slider, etc.
1590 var item = event.originalTarget;
1592 while (item && item.localName != "richlistitem")
1593 item = item.parentNode;
1595 if (!item)
1596 return;
1598 this.parentNode.onPopupClick(event);
1599 ]]>
1600 </handler>
1602 <handler event="mousemove">
1603 <![CDATA[
1604 if (Date.now() - this.mLastMoveTime > 30) {
1605 var item = event.target;
1607 while (item && item.localName != "richlistitem")
1608 item = item.parentNode;
1610 if (!item)
1611 return;
1613 var rc = this.getIndexOfItem(item);
1614 if (rc != this.selectedIndex)
1615 this.selectedIndex = rc;
1617 this.mLastMoveTime = Date.now();
1618 }
1619 ]]>
1620 </handler>
1621 </handlers>
1622 </binding>
1624 <binding id="autocomplete-treebody">
1625 <implementation>
1626 <field name="mLastMoveTime">Date.now()</field>
1627 </implementation>
1629 <handlers>
1630 <handler event="mouseup" action="this.parentNode.parentNode.onPopupClick(event);"/>
1632 <handler event="mousedown"><![CDATA[
1633 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
1634 if (rc != this.parentNode.currentIndex)
1635 this.parentNode.view.selection.select(rc);
1636 ]]></handler>
1638 <handler event="mousemove"><![CDATA[
1639 if (Date.now() - this.mLastMoveTime > 30) {
1640 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
1641 if (rc != this.parentNode.currentIndex)
1642 this.parentNode.view.selection.select(rc);
1643 this.mLastMoveTime = Date.now();
1644 }
1645 ]]></handler>
1646 </handlers>
1647 </binding>
1649 <binding id="autocomplete-treerows">
1650 <content>
1651 <xul:hbox flex="1" class="tree-bodybox">
1652 <children/>
1653 </xul:hbox>
1654 <xul:scrollbar xbl:inherits="collapsed=hidescrollbar" orient="vertical" class="tree-scrollbar"/>
1655 </content>
1656 </binding>
1658 <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
1659 <implementation>
1660 <method name="showPopup">
1661 <body><![CDATA[
1662 var textbox = document.getBindingParent(this);
1663 textbox.showHistoryPopup();
1664 ]]></body>
1665 </method>
1666 </implementation>
1668 <handlers>
1669 <handler event="mousedown" button="0"><![CDATA[
1670 this.showPopup();
1671 ]]></handler>
1672 </handlers>
1673 </binding>
1675 </bindings>