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" encoding="Windows-1252" ?>
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 <!DOCTYPE bindings [
8 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
9 %browserDTD;
10 ]>
12 <bindings
13 xmlns="http://www.mozilla.org/xbl"
14 xmlns:xbl="http://www.mozilla.org/xbl"
15 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
17 <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
18 <implementation implements="nsIObserver">
19 <constructor>
20 <![CDATA[
21 this._mayFormat = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
22 this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
23 this._maySelectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
25 Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
26 Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
27 Services.prefs.addObserver("browser.urlbar.doubleClickSelectsAll", this, false);
29 this.inputField.controllers.insertControllerAt(0, this._copyCutValueController);
31 this.minResultsForPopup = 0;
32 this.popup._input = this;
33 ]]>
34 </constructor>
36 <destructor>
37 <![CDATA[
38 Services.prefs.removeObserver("browser.urlbar.formatting.enabled", this);
39 Services.prefs.removeObserver("browser.urlbar.trimURLs", this);
40 Services.prefs.removeObserver("browser.urlbar.doubleClickSelectsAll", this);
41 ]]>
42 </destructor>
44 <field name="_mayFormat"/>
45 <field name="_mayTrimURLs"/>
46 <field name="_maySelectAll"/>
47 <field name="_lastKnownGoodURL"/>
49 <method name="openPopup">
50 <body>
51 <![CDATA[
52 this.popup.openAutocompletePopup(this, null);
53 ]]>
54 </body>
55 </method>
57 <method name="closePopup">
58 <body>
59 <![CDATA[
60 this.popup.closePopup(this, null);
61 ]]>
62 </body>
63 </method>
65 <!-- URI Display: Domain Highlighting -->
67 <method name="_clearFormatting">
68 <body>
69 <![CDATA[
70 let controller = this.editor.selectionController;
71 let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
72 selection.removeAllRanges();
73 ]]>
74 </body>
75 </method>
77 <method name="formatValue">
78 <body>
79 <![CDATA[
80 if (!this._mayFormat || this.isEditing)
81 return;
83 let controller = this.editor.selectionController;
84 let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
85 selection.removeAllRanges();
87 let textNode = this.editor.rootElement.firstChild;
88 let value = textNode.textContent;
90 let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
91 if (protocol &&
92 ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
93 return;
94 let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
95 if (!matchedURL)
96 return;
98 let [, preDomain, domain] = matchedURL;
99 let baseDomain = domain;
100 let subDomain = "";
101 // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
102 if (domain[0] != "[") {
103 try {
104 baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
105 if (!domain.endsWith(baseDomain)) {
106 // getBaseDomainFromHost converts its resultant to ACE.
107 let IDNService = Cc["@mozilla.org/network/idn-service;1"]
108 .getService(Ci.nsIIDNService);
109 baseDomain = IDNService.convertACEtoUTF8(baseDomain);
110 }
111 } catch (e) {}
112 }
113 if (baseDomain != domain) {
114 subDomain = domain.slice(0, -baseDomain.length);
115 }
117 let rangeLength = preDomain.length + subDomain.length;
118 if (rangeLength) {
119 let range = document.createRange();
120 range.setStart(textNode, 0);
121 range.setEnd(textNode, rangeLength);
122 selection.addRange(range);
123 }
125 let startRest = preDomain.length + domain.length;
126 if (startRest < value.length) {
127 let range = document.createRange();
128 range.setStart(textNode, startRest);
129 range.setEnd(textNode, value.length);
130 selection.addRange(range);
131 }
132 ]]>
133 </body>
134 </method>
136 <!-- URI Display: Scheme and Trailing Slash Triming -->
138 <method name="_trimURL">
139 <parameter name="aURL"/>
140 <body>
141 <![CDATA[
142 // This function must not modify the given URL such that calling
143 // nsIURIFixup::createFixupURI with the rfdesult will produce a different URI.
144 return aURL /* remove single trailing slash for http/https/ftp URLs */
145 .replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
146 /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
147 .replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
148 ]]>
149 </body>
150 </method>
152 <method name="_getSelectedValueForClipboard">
153 <body>
154 <![CDATA[
155 // Grab the actual input field's value, not our value, which could include moz-action:
156 let inputVal = this.inputField.value;
157 let selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
159 // If the selection doesn't start at the beginning or doesn't span the full domain or
160 // the URL bar is modified, nothing else to do here.
161 if (this.selectionStart > 0 || this.valueIsTyped)
162 return selectedVal;
164 // The selection doesn't span the full domain if it doesn't contain a slash and is
165 // followed by some character other than a slash.
166 if (!selectedVal.contains("/")) {
167 let remainder = inputVal.replace(selectedVal, "");
168 if (remainder != "" && remainder[0] != "/")
169 return selectedVal;
170 }
172 let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
174 let uri;
175 try {
176 uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
177 } catch (e) {}
178 if (!uri)
179 return selectedVal;
181 // Only copy exposable URIs
182 try {
183 uri = uriFixup.createExposableURI(uri);
184 } catch (ex) {}
186 // If the entire URL is selected, just use the actual loaded URI.
187 if (inputVal == selectedVal) {
188 // ... but only if isn't a javascript: or data: URI, since those
189 // are hard to read when encoded
190 if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
191 // Parentheses are known to confuse third-party applications (bug 458565).
192 selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
193 }
195 return selectedVal;
196 }
198 // Just the beginning of the URL is selected, check for a trimmed value
199 let spec = uri.spec;
200 let trimmedSpec = this._trimURL(spec);
201 if (spec != trimmedSpec) {
202 // Prepend the portion that trimURL removed from the beginning.
203 // This assumes trimURL will only truncate the URL at
204 // the beginning or end (or both).
205 let trimmedSegments = spec.split(trimmedSpec);
206 selectedVal = trimmedSegments[0] + selectedVal;
207 }
209 return selectedVal;
210 ]]>
211 </body>
212 </method>
214 <field name="_copyCutValueController">
215 <![CDATA[
216 ({
217 urlbar: this,
218 doCommand: function(aCommand) {
219 let urlbar = this.urlbar;
220 let val = urlbar._getSelectedValueForClipboard();
221 if (!val)
222 return;
224 if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
225 let start = urlbar.selectionStart;
226 let end = urlbar.selectionEnd;
227 urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
228 urlbar.inputField.value.substring(end);
229 urlbar.selectionStart = urlbar.selectionEnd = start;
230 }
232 Cc["@mozilla.org/widget/clipboardhelper;1"]
233 .getService(Ci.nsIClipboardHelper)
234 .copyString(val, document);
235 },
237 supportsCommand: function(aCommand) {
238 switch (aCommand) {
239 case "cmd_copy":
240 case "cmd_cut":
241 return true;
242 }
243 return false;
244 },
246 isCommandEnabled: function(aCommand) {
247 let urlbar = this.urlbar;
248 return this.supportsCommand(aCommand) &&
249 (aCommand != "cmd_cut" || !urlbar.readOnly) &&
250 urlbar.selectionStart < urlbar.selectionEnd;
251 },
253 onEvent: function(aEventName) {}
254 })
255 ]]>
256 </field>
258 <method name="trimValue">
259 <parameter name="aURL"/>
260 <body>
261 <![CDATA[
262 return (this._mayTrimURLs) ? this._trimURL(aURL) : aURL;
263 ]]>
264 </body>
265 </method>
267 <!-- URI Editing -->
269 <property name="isEditing" readonly="true">
270 <getter>
271 <![CDATA[
272 return Elements.urlbarState.hasAttribute("editing");
273 ]]>
274 </getter>
275 </property>
277 <method name="beginEditing">
278 <parameter name="aShouldDismiss"/>
279 <body>
280 <![CDATA[
281 if (this.isEditing)
282 return;
284 Elements.urlbarState.setAttribute("editing", true);
285 this._lastKnownGoodURL = this.value;
287 if (!this.focused) {
288 this.focus();
289 // If we force focus, ensure we're visible.
290 if (ContextUI.displayNavbar()) {
291 // If we forced visibility, ensure we're positioned above keyboard.
292 // (Previous "blur" may have forced us down behind it.)
293 ContentAreaObserver.updateAppBarPosition();
294 }
295 }
297 this._clearFormatting();
298 this.select();
300 if (aShouldDismiss)
301 ContextUI.dismissTabs();
303 if (!InputSourceHelper.isPrecise) {
304 let inputRectangle = this.inputField.getBoundingClientRect();
305 SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
306 inputRectangle.left, inputRectangle.top, this);
307 }
308 ]]>
309 </body>
310 </method>
312 <method name="endEditing">
313 <parameter name="aShouldRevert"/>
314 <body>
315 <![CDATA[
316 if (!this.isEditing)
317 return;
319 Elements.urlbarState.removeAttribute("editing");
320 this.closePopup();
321 this.formatValue();
323 if (this.focused)
324 this.blur();
326 if (aShouldRevert)
327 this.value = this._lastKnownGoodURL;
328 ]]>
329 </body>
330 </method>
332 <!-- URI Submission -->
334 <method name="_canonizeURL">
335 <parameter name="aURL"/>
336 <parameter name="aTriggeringEvent"/>
337 <body>
338 <![CDATA[
339 if (!aURL)
340 return "";
342 // Only add the suffix when the URL bar value isn't already "URL-like",
343 // and only if we get a keyboard event, to match user expectations.
344 if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aURL)) {
345 let accel = aTriggeringEvent.ctrlKey;
346 let shift = aTriggeringEvent.shiftKey;
347 let suffix = "";
349 switch (true) {
350 case (accel && shift):
351 suffix = ".org/";
352 break;
353 case (shift):
354 suffix = ".net/";
355 break;
356 case (accel):
357 try {
358 suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
359 if (suffix.charAt(suffix.length - 1) != "/")
360 suffix += "/";
361 } catch(e) {
362 suffix = ".com/";
363 }
364 break;
365 }
367 if (suffix) {
368 // trim leading/trailing spaces (bug 233205)
369 aURL = aURL.trim();
371 // Tack www. and suffix on. If user has appended directories, insert
372 // suffix before them (bug 279035). Be careful not to get two slashes.
373 let firstSlash = aURL.indexOf("/");
374 if (firstSlash >= 0) {
375 aURL = aURL.substring(0, firstSlash) + suffix + aURL.substring(firstSlash + 1);
376 } else {
377 aURL = aURL + suffix;
378 }
379 aURL = "http://www." + aURL;
380 }
381 }
383 return aURL;
384 ]]>
385 </body>
386 </method>
388 <method name="submitURL">
389 <parameter name="aEvent"/>
390 <body>
391 <![CDATA[
392 // If the address was typed in by a user, tidy it up
393 if (aEvent instanceof KeyEvent)
394 this.value = this._canonizeURL(this.value, aEvent);
396 this.endEditing();
397 BrowserUI.goToURI(this.value);
399 return true;
400 ]]>
401 </body>
402 </method>
404 <method name="submitSearch">
405 <parameter name="anEngineName"/>
406 <body>
407 <![CDATA[
408 this.endEditing();
409 BrowserUI.doOpenSearch(anEngineName);
411 return true;
412 ]]>
413 </body>
414 </method>
416 <!-- nsIObserver -->
418 <method name="observe">
419 <parameter name="aSubject"/>
420 <parameter name="aTopic"/>
421 <parameter name="aData"/>
422 <body>
423 <![CDATA[
424 if (aTopic != "nsPref:changed")
425 return;
427 switch (aData) {
428 case "browser.urlbar.formatting.enabled":
429 this._mayFormat = Services.prefs.getBoolPref(aData);
430 if (!this._mayFormat) this._clearFormatting();
431 break;
432 case "browser.urlbar.trimURLs":
433 this._mayTrimURLs = Services.prefs.getBoolPref(aData);
434 break;
435 case "browser.urlbar.doubleClickSelectsAll":
436 this._maySelectAll = Services.prefs.getBoolPref(aData);
437 break;
438 }
439 ]]>
440 </body>
441 </method>
442 </implementation>
444 <handlers>
445 <!-- Entering editing mode -->
447 <handler event="focus" phase="capturing">
448 <![CDATA[
449 this.beginEditing();
450 ]]>
451 </handler>
453 <handler event="input" phase="capturing">
454 <![CDATA[
455 // Ensures that paste-and-go actually brings the URL bar into editing mode
456 // and displays the half-height autocomplete popup.
457 this.beginEditing();
458 this.openPopup();
459 ]]>
460 </handler>
462 <handler event="click" phase="capturing">
463 <![CDATA[
464 // workaround for bug 925457: taping browser chrome resets last tap
465 // co-ordinates to 'undefined' so that we know not to shift the
466 // browser when the keyboard is up in SelectionHandler's
467 // _calcNewContentPosition().
468 Browser.selectedTab.browser.messageManager.sendAsyncMessage(
469 "Browser:ResetLastPos", {
470 xPos: null,
471 yPos: null
472 });
473 this.beginEditing(true);
474 ]]>
475 </handler>
477 <!-- Editing mode behaviors -->
479 <handler event="dblclick" phase="capturing">
480 <![CDATA[
481 if (this._maySelectAll) this.select();
482 ]]>
483 </handler>
485 <handler event="contextmenu" phase="capturing">
486 <![CDATA[
487 let box = this.inputField.parentNode;
488 box.showContextMenu(this, event, true);
489 ]]>
490 </handler>
492 <!-- Leaving editing mode -->
494 <handler event="blur" phase="capturing">
495 <![CDATA[
496 this.endEditing();
497 ]]>
498 </handler>
500 <handler event="keypress" phase="capturing" keycode="VK_RETURN"
501 modifiers="accel shift any">
502 <![CDATA[
503 if (this.popup.submitSelected())
504 return;
506 if (this.submitURL(event))
507 return;
508 ]]>
509 </handler>
511 <handler event="keypress" phase="capturing" keycode="VK_UP">
512 <![CDATA[
513 if (!this.popup.hasSelection) {
514 // Treat the first up as a down key to trick handleKeyNavigation() to start
515 // keyboard navigation on autocomplete popup.
516 this.mController.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
517 event.preventDefault();
518 }
519 ]]>
520 </handler>
521 </handlers>
522 </binding>
524 <binding id="urlbar-autocomplete">
525 <content class="meta-section-container" pack="end">
526 <xul:vbox class="meta-section" anonid="results-container" flex="2">
527 <xul:label class="meta-section-title"
528 value="&autocompleteResultsHeader.label;"/>
529 <richgrid anonid="results" rows="3" minSlots="8"
530 seltype="single" nocontext="true" deferlayout="true"/>
531 </xul:vbox>
533 <xul:vbox class="meta-section" flex="1">
534 <xul:label anonid="searches-header" class="meta-section-title"/>
535 <richgrid anonid="searches" rows="3" flex="1" search="true"
536 seltype="single" nocontext="true" deferlayout="true"/>
537 </xul:vbox>
538 </content>
540 <implementation implements="nsIAutoCompletePopup, nsIObserver">
541 <constructor>
542 <![CDATA[
543 this.hidden = true;
544 Services.obs.addObserver(this, "browser-search-engine-modified", false);
546 this._results.controller = this;
547 this._searches.controller = this;
548 ]]>
549 </constructor>
551 <destructor>
552 <![CDATA[
553 Services.obs.removeObserver(this, "browser-search-engine-modified");
554 ]]>
555 </destructor>
557 <!-- nsIAutocompleteInput -->
559 <field name="_input">null</field>
561 <property name="input" readonly="true" onget="return this._input;"/>
562 <property name="matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
563 <property name="popupOpen" readonly="true" onget="return !this.hidden"/>
564 <property name="overrideValue" readonly="true" onget="return null;"/>
566 <property name="selectedItem">
567 <getter>
568 <![CDATA[
569 return this._isGridBound(this._results) ? this._results.selectedItem : null;
570 ]]>
571 </getter>
572 <setter>
573 <![CDATA[
574 if (this._isGridBound(this._results)) {
575 this._results.selectedItem = val;
576 }
577 ]]>
578 </setter>
579 </property>
581 <property name="selectedIndex">
582 <getter>
583 <![CDATA[
584 return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
585 ]]>
586 </getter>
587 <setter>
588 <![CDATA[
589 if (this._isGridBound(this._results)) {
590 this._results.selectedIndex = val;
591 }
592 ]]>
593 </setter>
594 </property>
596 <property name="hasSelection">
597 <getter>
598 <![CDATA[
599 if (!this._isGridBound(this._results) ||
600 !this._isGridBound(this._searches))
601 return false;
603 return (this._results.selectedIndex >= 0 ||
604 this._searches.selectedIndex >= 0);
605 ]]>
606 </getter>
607 </property>
609 <method name="openAutocompletePopup">
610 <parameter name="aInput"/>
611 <parameter name="aElement"/>
612 <body>
613 <![CDATA[
614 if (this.popupOpen)
615 return;
617 ContextUI.dismissContextAppbar();
619 this._input = aInput;
620 this._grid = this._results;
622 this.clearSelection();
623 this.invalidate();
625 let viewstate = this.getAttribute("viewstate");
626 switch (viewstate) {
627 case "portrait":
628 case "snapped":
629 this._results.setAttribute("vertical", true);
630 break;
631 default:
632 this._results.removeAttribute("vertical");
633 break;
634 }
636 this._results.arrangeItemsNow();
637 this._searches.arrangeItemsNow();
639 this.hidden = false;
640 Elements.urlbarState.setAttribute("autocomplete", "true");
641 ]]>
642 </body>
643 </method>
645 <method name="closePopup">
646 <body>
647 <![CDATA[
648 if (!this.popupOpen)
649 return;
651 this.input.controller.stopSearch();
652 this.hidden = true;
653 Elements.urlbarState.removeAttribute("autocomplete");
654 ]]>
655 </body>
656 </method>
658 <!-- Updating grid content -->
660 <field name="_grid">null</field>
662 <field name="_results" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results');</field>
663 <field name="_resultsContainer" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results-container');</field>
665 <field name="_searchesHeader" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches-header');</field>
666 <field name="_searches" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches');</field>
668 <property name="_otherGrid" readonly="true">
669 <getter>
670 <![CDATA[
671 if (this._grid == null)
672 return null;
674 return (this._grid == this._results) ? this._searches : this._results;
675 ]]>
676 </getter>
677 </property>
679 <method name="_isGridBound">
680 <parameter name="aGrid"/>
681 <body>
682 <![CDATA[
683 return aGrid && aGrid.isBound;
684 ]]>
685 </body>
686 </method>
688 <method name="invalidate">
689 <body>
690 <![CDATA[
691 if (!this.popupOpen)
692 return;
694 this.updateSearchEngineHeader();
695 this.updateResults();
696 ]]>
697 </body>
698 </method>
700 <!-- Updating grid content: results -->
702 <method name="updateResults">
703 <body>
704 <![CDATA[
705 if (!this._isGridBound(this._results))
706 return;
708 if (!this.input)
709 return;
711 let haveNoResults = (this.matchCount == 0);
713 if (haveNoResults) {
714 this._results.clearAll();
715 this.setAttribute("nomatch", true);
716 this._resultsContainer.removeAttribute("flex");
717 return;
718 }
720 this.removeAttribute("nomatch");
721 this._resultsContainer.setAttribute("flex", 1);
723 let controller = this.input.controller;
724 let lastMatch = this.matchCount - 1;
725 let iterCount = Math.max(this._results.itemCount, this.matchCount);
727 // Swap out existing items for new search hit results
728 for (let i = 0; i < iterCount; i++) {
729 let item = this._results._slotAt(i);
730 if (i > lastMatch) {
731 item.removeAttribute("value");
732 item.removeAttribute("autocomplete");
733 continue;
734 }
736 let value = controller.getValueAt(i);
737 let label = controller.getCommentAt(i) || value;
738 let iconURI = controller.getImageAt(i);
740 item.setAttribute("autocomplete", true);
741 item.setAttribute("label", label);
742 item.setAttribute("value", value);
743 item.setAttribute("iconURI", iconURI);
744 let xpFaviconURI = Services.io.newURI(iconURI.replace("moz-anno:favicon:",""), null, null);
745 Task.spawn(function() {
746 let colorInfo = yield ColorUtils.getForegroundAndBackgroundIconColors(xpFaviconURI);
747 if ( !(colorInfo && colorInfo.background && colorInfo.foreground)
748 || (item.getAttribute("iconURI") != iconURI) ) {
749 return;
750 }
751 let { background, foreground } = colorInfo;
752 item.style.color = foreground; //color text
753 item.setAttribute("customColor", background);
754 // when bound, use the setter to propogate the color change through the tile
755 if ('color' in item) {
756 item.color = background;
757 }
758 }).then(null, err => Components.utils.reportError(err));
759 }
761 this._results.arrangeItems();
762 ]]>
763 </body>
764 </method>
766 <!-- Updating grid content: search engines -->
768 <field name="_engines">[]</field>
770 <method name="_initSearchEngines">
771 <body>
772 <![CDATA[
773 Services.search.init(this.updateSearchEngineGrid.bind(this));
774 ]]>
775 </body>
776 </method>
778 <method name="updateSearchEngineGrid">
779 <body>
780 <![CDATA[
781 if (!this._isGridBound(this._searches))
782 return;
784 this._engines = Services.search.getVisibleEngines();
786 while (this._searches.itemCount > 0)
787 this._searches.removeItemAt(0, true);
789 this._engines.forEach(function (anEngine) {
790 let item = this._searches.appendItem("", anEngine.name, true);
791 item.setAttribute("autocomplete", "true");
792 item.setAttribute("search", "true");
794 let largeImage = anEngine.getIconURLBySize(74,74);
795 if (largeImage) {
796 item.setAttribute("iconsize", "large");
797 item.setAttribute("iconURI", largeImage);
798 } else if (anEngine.iconURI && anEngine.iconURI.spec) {
799 item.setAttribute("iconURI", anEngine.iconURI.spec);
800 item.setAttribute("customColor", "#fff");
801 }
802 }.bind(this));
804 this._searches.arrangeItems();
805 ]]>
806 </body>
807 </method>
809 <method name="updateSearchEngineHeader">
810 <body>
811 <![CDATA[
812 if (!this._isGridBound(this._searches))
813 return;
815 let searchString = this.input.controller.searchString;
816 let label = Strings.browser.formatStringFromName(
817 "opensearch.search.header", [searchString], 1);
819 this._searchesHeader.value = label;
820 ]]>
821 </body>
822 </method>
824 <!-- Selecting results -->
826 <method name="selectBy">
827 <parameter name="aReverse"/>
828 <parameter name="aPage"/>
829 <body>
830 <![CDATA[
831 if (!this._isGridBound(this._results) ||
832 !this._isGridBound(this._searches))
833 return;
835 // Move between grids if we're at the edge of one.
836 if ((this._grid.isSelectionAtEnd && !aReverse) ||
837 (this._grid.isSelectionAtStart && aReverse)) {
838 let index = !aReverse ? 0 : this._otherGrid.itemCount - 1;
839 this._otherGrid.selectedIndex = index;
840 } else {
841 this._grid.offsetSelection(aReverse ? -1 : 1);
842 }
843 ]]>
844 </body>
845 </method>
847 <method name="clearSelection">
848 <body>
849 <![CDATA[
850 if (this._isGridBound(this._results))
851 this._results.clearSelection();
853 if (this._isGridBound(this._searches))
854 this._searches.clearSelection();
855 ]]>
856 </body>
857 </method>
859 <!-- Submitting selected results -->
861 <method name="submitSelected">
862 <body>
863 <![CDATA[
864 if (this._isGridBound(this._results) &&
865 this._results.selectedIndex >= 0) {
866 let url = this.input.controller.getValueAt(this._results.selectedIndex);
867 this.input.value = url;
868 return this.input.submitURL();
869 }
871 if (this._isGridBound(this._searches) &&
872 this._searches.selectedIndex >= 0) {
873 let engine = this._engines[this._searches.selectedIndex];
874 return this.input.submitSearch(engine.name);
875 }
877 return false;
878 ]]>
879 </body>
880 </method>
882 <method name="handleItemClick">
883 <parameter name="aItem"/>
884 <parameter name="aEvent"/>
885 <body>
886 <![CDATA[
887 this.submitSelected();
888 ]]>
889 </body>
890 </method>
892 <!-- nsIObserver -->
894 <method name="observe">
895 <parameter name="aSubject"/>
896 <parameter name="aTopic"/>
897 <parameter name="aData"/>
898 <body>
899 <![CDATA[
900 switch (aTopic) {
901 case "browser-search-engine-modified":
902 this.updateSearchEngineGrid();
903 break;
904 }
905 ]]>
906 </body>
907 </method>
908 </implementation>
910 <handlers>
911 <handler event="contentgenerated">
912 <![CDATA[
913 let grid = event.originalTarget;
915 if (grid == this._searches)
916 this._initSearchEngines();
918 if (grid == this._results)
919 this.updateResults();
920 ]]>
921 </handler>
923 <handler event="select">
924 <![CDATA[
925 let grid = event.originalTarget;
927 // If a selection was made on a different grid,
928 // remove selection from the current grid.
929 if (grid != this._grid) {
930 this._grid.clearSelection();
931 this._grid = this._otherGrid;
932 }
933 ]]>
934 </handler>
935 </handlers>
936 </binding>
937 </bindings>