|
1 <?xml version="1.0"?> |
|
2 # -*- Mode: HTML -*- |
|
3 # This Source Code Form is subject to the terms of the Mozilla Public |
|
4 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
6 |
|
7 <!DOCTYPE bindings [ |
|
8 <!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd" > |
|
9 %searchBarDTD; |
|
10 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd"> |
|
11 %browserDTD; |
|
12 ]> |
|
13 |
|
14 <bindings id="SearchBindings" |
|
15 xmlns="http://www.mozilla.org/xbl" |
|
16 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" |
|
17 xmlns:xbl="http://www.mozilla.org/xbl"> |
|
18 |
|
19 <binding id="searchbar"> |
|
20 <resources> |
|
21 <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/> |
|
22 <stylesheet src="chrome://browser/skin/searchbar.css"/> |
|
23 </resources> |
|
24 <content> |
|
25 <xul:stringbundle src="chrome://browser/locale/search.properties" |
|
26 anonid="searchbar-stringbundle"/> |
|
27 <!-- |
|
28 There is a dependency between "maxrows" attribute and |
|
29 "SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing |
|
30 one of them requires changing the other one. |
|
31 --> |
|
32 <xul:textbox class="searchbar-textbox" |
|
33 anonid="searchbar-textbox" |
|
34 type="autocomplete" |
|
35 flex="1" |
|
36 autocompletepopup="PopupAutoComplete" |
|
37 autocompletesearch="search-autocomplete" |
|
38 autocompletesearchparam="searchbar-history" |
|
39 timeout="250" |
|
40 maxrows="10" |
|
41 completeselectedindex="true" |
|
42 showcommentcolumn="true" |
|
43 tabscrolling="true" |
|
44 xbl:inherits="disabled,disableautocomplete,searchengine,src,newlines"> |
|
45 <!-- |
|
46 Empty <box> to properly position the icon within the autocomplete |
|
47 binding's anonymous children (the autocomplete binding positions <box> |
|
48 children differently) |
|
49 --> |
|
50 <xul:box> |
|
51 <xul:button class="searchbar-engine-button" |
|
52 type="menu" |
|
53 anonid="searchbar-engine-button"> |
|
54 <xul:image class="searchbar-engine-image" xbl:inherits="src"/> |
|
55 <xul:image class="searchbar-dropmarker-image"/> |
|
56 <xul:menupopup class="searchbar-popup" |
|
57 anonid="searchbar-popup"> |
|
58 <xul:menuseparator/> |
|
59 <xul:menuitem class="open-engine-manager" |
|
60 anonid="open-engine-manager" |
|
61 label="&cmd_engineManager.label;" |
|
62 oncommand="openManager(event);"/> |
|
63 </xul:menupopup> |
|
64 </xul:button> |
|
65 </xul:box> |
|
66 <xul:hbox class="search-go-container"> |
|
67 <xul:image class="search-go-button" |
|
68 anonid="search-go-button" |
|
69 onclick="handleSearchCommand(event);" |
|
70 tooltiptext="&searchEndCap.label;"/> |
|
71 </xul:hbox> |
|
72 </xul:textbox> |
|
73 </content> |
|
74 |
|
75 <implementation implements="nsIObserver"> |
|
76 <constructor><![CDATA[ |
|
77 if (this.parentNode.parentNode.localName == "toolbarpaletteitem") |
|
78 return; |
|
79 // Make sure we rebuild the popup in onpopupshowing |
|
80 this._needToBuildPopup = true; |
|
81 |
|
82 var os = |
|
83 Components.classes["@mozilla.org/observer-service;1"] |
|
84 .getService(Components.interfaces.nsIObserverService); |
|
85 os.addObserver(this, "browser-search-engine-modified", false); |
|
86 |
|
87 this._initialized = true; |
|
88 |
|
89 this.searchService.init((function search_init_cb(aStatus) { |
|
90 // Bail out if the binding's been destroyed |
|
91 if (!this._initialized) |
|
92 return; |
|
93 |
|
94 if (Components.isSuccessCode(aStatus)) { |
|
95 // Refresh the display (updating icon, etc) |
|
96 this.updateDisplay(); |
|
97 } else { |
|
98 Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus); |
|
99 } |
|
100 }).bind(this)); |
|
101 ]]></constructor> |
|
102 |
|
103 <destructor><![CDATA[ |
|
104 if (this._initialized) { |
|
105 this._initialized = false; |
|
106 |
|
107 var os = Components.classes["@mozilla.org/observer-service;1"] |
|
108 .getService(Components.interfaces.nsIObserverService); |
|
109 os.removeObserver(this, "browser-search-engine-modified"); |
|
110 } |
|
111 |
|
112 // Make sure to break the cycle from _textbox to us. Otherwise we leak |
|
113 // the world. But make sure it's actually pointing to us. |
|
114 if (this._textbox.mController.input == this) |
|
115 this._textbox.mController.input = null; |
|
116 ]]></destructor> |
|
117 |
|
118 <field name="_stringBundle">document.getAnonymousElementByAttribute(this, |
|
119 "anonid", "searchbar-stringbundle");</field> |
|
120 <field name="_textbox">document.getAnonymousElementByAttribute(this, |
|
121 "anonid", "searchbar-textbox");</field> |
|
122 <field name="_popup">document.getAnonymousElementByAttribute(this, |
|
123 "anonid", "searchbar-popup");</field> |
|
124 <field name="_ss">null</field> |
|
125 <field name="_engines">null</field> |
|
126 <field name="FormHistory" readonly="true"> |
|
127 (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory; |
|
128 </field> |
|
129 |
|
130 <property name="engines" readonly="true"> |
|
131 <getter><![CDATA[ |
|
132 if (!this._engines) |
|
133 this._engines = this.searchService.getVisibleEngines(); |
|
134 return this._engines; |
|
135 ]]></getter> |
|
136 </property> |
|
137 |
|
138 <field name="searchButton">document.getAnonymousElementByAttribute(this, |
|
139 "anonid", "searchbar-engine-button");</field> |
|
140 |
|
141 <property name="currentEngine"> |
|
142 <setter><![CDATA[ |
|
143 let ss = this.searchService; |
|
144 ss.defaultEngine = ss.currentEngine = val; |
|
145 return val; |
|
146 ]]></setter> |
|
147 <getter><![CDATA[ |
|
148 var currentEngine = this.searchService.currentEngine; |
|
149 // Return a dummy engine if there is no currentEngine |
|
150 return currentEngine || {name: "", uri: null}; |
|
151 ]]></getter> |
|
152 </property> |
|
153 |
|
154 <!-- textbox is used by sanitize.js to clear the undo history when |
|
155 clearing form information. --> |
|
156 <property name="textbox" readonly="true" |
|
157 onget="return this._textbox;"/> |
|
158 |
|
159 <property name="searchService" readonly="true"> |
|
160 <getter><![CDATA[ |
|
161 if (!this._ss) { |
|
162 const nsIBSS = Components.interfaces.nsIBrowserSearchService; |
|
163 this._ss = |
|
164 Components.classes["@mozilla.org/browser/search-service;1"] |
|
165 .getService(nsIBSS); |
|
166 } |
|
167 return this._ss; |
|
168 ]]></getter> |
|
169 </property> |
|
170 |
|
171 <property name="value" onget="return this._textbox.value;" |
|
172 onset="return this._textbox.value = val;"/> |
|
173 |
|
174 <method name="focus"> |
|
175 <body><![CDATA[ |
|
176 this._textbox.focus(); |
|
177 ]]></body> |
|
178 </method> |
|
179 |
|
180 <method name="select"> |
|
181 <body><![CDATA[ |
|
182 this._textbox.select(); |
|
183 ]]></body> |
|
184 </method> |
|
185 |
|
186 <method name="observe"> |
|
187 <parameter name="aEngine"/> |
|
188 <parameter name="aTopic"/> |
|
189 <parameter name="aVerb"/> |
|
190 <body><![CDATA[ |
|
191 if (aTopic == "browser-search-engine-modified") { |
|
192 switch (aVerb) { |
|
193 case "engine-removed": |
|
194 this.offerNewEngine(aEngine); |
|
195 break; |
|
196 case "engine-added": |
|
197 this.hideNewEngine(aEngine); |
|
198 break; |
|
199 case "engine-current": |
|
200 // The current engine was changed. Rebuilding the menu appears to |
|
201 // confuse its idea of whether it should be open when it's just |
|
202 // been clicked, so we force it to close now. |
|
203 this._popup.hidePopup(); |
|
204 break; |
|
205 case "engine-changed": |
|
206 // An engine was removed (or hidden) or added, or an icon was |
|
207 // changed. Do nothing special. |
|
208 } |
|
209 |
|
210 // Make sure the engine list is refetched next time it's needed |
|
211 this._engines = null; |
|
212 |
|
213 // Rebuild the popup and update the display after any modification. |
|
214 this.rebuildPopup(); |
|
215 this.updateDisplay(); |
|
216 } |
|
217 ]]></body> |
|
218 </method> |
|
219 |
|
220 <!-- There are two seaprate lists of search engines, whose uses intersect |
|
221 in this file. The search service (nsIBrowserSearchService and |
|
222 nsSearchService.js) maintains a list of Engine objects which is used to |
|
223 populate the searchbox list of available engines and to perform queries. |
|
224 That list is accessed here via this.SearchService, and it's that sort of |
|
225 Engine that is passed to this binding's observer as aEngine. |
|
226 |
|
227 In addition, browser.js fills two lists of autodetected search engines |
|
228 (browser.engines and browser.hiddenEngines) as properties of |
|
229 mCurrentBrowser. Those lists contain unnamed JS objects of the form |
|
230 { uri:, title:, icon: }, and that's what the searchbar uses to determine |
|
231 whether to show any "Add <EngineName>" menu items in the drop-down. |
|
232 |
|
233 The two types of engines are currently related by their identifying |
|
234 titles (the Engine object's 'name'), although that may change; see bug |
|
235 335102. --> |
|
236 |
|
237 <!-- If the engine that was just removed from the searchbox list was |
|
238 autodetected on this page, move it to each browser's active list so it |
|
239 will be offered to be added again. --> |
|
240 <method name="offerNewEngine"> |
|
241 <parameter name="aEngine"/> |
|
242 <body><![CDATA[ |
|
243 var allbrowsers = getBrowser().mPanelContainer.childNodes; |
|
244 for (var tab = 0; tab < allbrowsers.length; tab++) { |
|
245 var browser = getBrowser().getBrowserAtIndex(tab); |
|
246 if (browser.hiddenEngines) { |
|
247 // XXX This will need to be changed when engines are identified by |
|
248 // URL rather than title; see bug 335102. |
|
249 var removeTitle = aEngine.wrappedJSObject.name; |
|
250 for (var i = 0; i < browser.hiddenEngines.length; i++) { |
|
251 if (browser.hiddenEngines[i].title == removeTitle) { |
|
252 if (!browser.engines) |
|
253 browser.engines = []; |
|
254 browser.engines.push(browser.hiddenEngines[i]); |
|
255 browser.hiddenEngines.splice(i, 1); |
|
256 break; |
|
257 } |
|
258 } |
|
259 } |
|
260 } |
|
261 ]]></body> |
|
262 </method> |
|
263 |
|
264 <!-- If the engine that was just added to the searchbox list was |
|
265 autodetected on this page, move it to each browser's hidden list so it is |
|
266 no longer offered to be added. --> |
|
267 <method name="hideNewEngine"> |
|
268 <parameter name="aEngine"/> |
|
269 <body><![CDATA[ |
|
270 var allbrowsers = getBrowser().mPanelContainer.childNodes; |
|
271 for (var tab = 0; tab < allbrowsers.length; tab++) { |
|
272 var browser = getBrowser().getBrowserAtIndex(tab); |
|
273 if (browser.engines) { |
|
274 // XXX This will need to be changed when engines are identified by |
|
275 // URL rather than title; see bug 335102. |
|
276 var removeTitle = aEngine.wrappedJSObject.name; |
|
277 for (var i = 0; i < browser.engines.length; i++) { |
|
278 if (browser.engines[i].title == removeTitle) { |
|
279 if (!browser.hiddenEngines) |
|
280 browser.hiddenEngines = []; |
|
281 browser.hiddenEngines.push(browser.engines[i]); |
|
282 browser.engines.splice(i, 1); |
|
283 break; |
|
284 } |
|
285 } |
|
286 } |
|
287 } |
|
288 ]]></body> |
|
289 </method> |
|
290 |
|
291 <method name="setIcon"> |
|
292 <parameter name="element"/> |
|
293 <parameter name="uri"/> |
|
294 <body><![CDATA[ |
|
295 if (uri) { |
|
296 let size = Math.round(16 * window.devicePixelRatio); |
|
297 if (!uri.contains("#")) |
|
298 uri += "#-moz-resolution=" + size + "," + size; |
|
299 } |
|
300 element.setAttribute("src", uri); |
|
301 ]]></body> |
|
302 </method> |
|
303 |
|
304 <method name="updateDisplay"> |
|
305 <body><![CDATA[ |
|
306 var uri = this.currentEngine.iconURI; |
|
307 this.setIcon(this, uri ? uri.spec : ""); |
|
308 |
|
309 var name = this.currentEngine.name; |
|
310 var text = this._stringBundle.getFormattedString("searchtip", [name]); |
|
311 this._textbox.placeholder = name; |
|
312 this._textbox.label = text; |
|
313 this._textbox.tooltipText = text; |
|
314 ]]></body> |
|
315 </method> |
|
316 |
|
317 <!-- Rebuilds the dynamic portion of the popup menu (i.e., the menu items |
|
318 for new search engines that can be added to the available list). This |
|
319 is called each time the popup is shown. |
|
320 --> |
|
321 <method name="rebuildPopupDynamic"> |
|
322 <body><![CDATA[ |
|
323 // We might not have added the main popup items yet, do that first |
|
324 // if needed. |
|
325 if (this._needToBuildPopup) |
|
326 this.rebuildPopup(); |
|
327 |
|
328 var popup = this._popup; |
|
329 // Clear any addengine menuitems, including addengine-item entries and |
|
330 // the addengine-separator. Work backward to avoid invalidating the |
|
331 // indexes as items are removed. |
|
332 var items = popup.childNodes; |
|
333 for (var i = items.length - 1; i >= 0; i--) { |
|
334 if (items[i].classList.contains("addengine-item") || |
|
335 items[i].classList.contains("addengine-separator")) |
|
336 popup.removeChild(items[i]); |
|
337 } |
|
338 |
|
339 var addengines = getBrowser().mCurrentBrowser.engines; |
|
340 if (addengines && addengines.length > 0) { |
|
341 const kXULNS = |
|
342 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
343 |
|
344 // Find the (first) separator in the remaining menu, or the first item |
|
345 // if no separators are present. |
|
346 var insertLocation = popup.firstChild; |
|
347 while (insertLocation.nextSibling && |
|
348 insertLocation.localName != "menuseparator") { |
|
349 insertLocation = insertLocation.nextSibling; |
|
350 } |
|
351 if (insertLocation.localName != "menuseparator") |
|
352 insertLocation = popup.firstChild; |
|
353 |
|
354 var separator = document.createElementNS(kXULNS, "menuseparator"); |
|
355 separator.setAttribute("class", "addengine-separator"); |
|
356 popup.insertBefore(separator, insertLocation); |
|
357 |
|
358 // Insert the "add this engine" items. |
|
359 for (var i = 0; i < addengines.length; i++) { |
|
360 var menuitem = document.createElement("menuitem"); |
|
361 var engineInfo = addengines[i]; |
|
362 var labelStr = |
|
363 this._stringBundle.getFormattedString("cmd_addFoundEngine", |
|
364 [engineInfo.title]); |
|
365 menuitem = document.createElementNS(kXULNS, "menuitem"); |
|
366 menuitem.setAttribute("class", "menuitem-iconic addengine-item"); |
|
367 menuitem.setAttribute("label", labelStr); |
|
368 menuitem.setAttribute("tooltiptext", engineInfo.uri); |
|
369 menuitem.setAttribute("uri", engineInfo.uri); |
|
370 if (engineInfo.icon) |
|
371 this.setIcon(menuitem, engineInfo.icon); |
|
372 menuitem.setAttribute("title", engineInfo.title); |
|
373 popup.insertBefore(menuitem, insertLocation); |
|
374 } |
|
375 } |
|
376 ]]></body> |
|
377 </method> |
|
378 |
|
379 <!-- Rebuilds the list of visible search engines in the menu. Does not remove |
|
380 or update any dynamic entries (i.e., "Add this engine" items) nor the |
|
381 Manage Engines item. This is called by the observer when the list of |
|
382 visible engines, or the currently selected engine, has changed. |
|
383 --> |
|
384 <method name="rebuildPopup"> |
|
385 <body><![CDATA[ |
|
386 var popup = this._popup; |
|
387 |
|
388 // Clear the popup, down to the first separator |
|
389 while (popup.firstChild && popup.firstChild.localName != "menuseparator") |
|
390 popup.removeChild(popup.firstChild); |
|
391 |
|
392 const kXULNS = |
|
393 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
394 |
|
395 var engines = this.engines; |
|
396 for (var i = engines.length - 1; i >= 0; --i) { |
|
397 var menuitem = document.createElementNS(kXULNS, "menuitem"); |
|
398 var name = engines[i].name; |
|
399 menuitem.setAttribute("label", name); |
|
400 menuitem.setAttribute("id", name); |
|
401 menuitem.setAttribute("class", "menuitem-iconic searchbar-engine-menuitem menuitem-with-favicon"); |
|
402 // Since this menu is rebuilt by the observer method whenever a new |
|
403 // engine is selected, the "selected" attribute does not need to be |
|
404 // explicitly cleared anywhere. |
|
405 if (engines[i] == this.currentEngine) |
|
406 menuitem.setAttribute("selected", "true"); |
|
407 var tooltip = this._stringBundle.getFormattedString("searchtip", [name]); |
|
408 menuitem.setAttribute("tooltiptext", tooltip); |
|
409 if (engines[i].iconURI) |
|
410 this.setIcon(menuitem, engines[i].iconURI.spec); |
|
411 popup.insertBefore(menuitem, popup.firstChild); |
|
412 menuitem.engine = engines[i]; |
|
413 } |
|
414 |
|
415 this._needToBuildPopup = false; |
|
416 ]]></body> |
|
417 </method> |
|
418 |
|
419 <method name="openManager"> |
|
420 <parameter name="aEvent"/> |
|
421 <body><![CDATA[ |
|
422 var wm = |
|
423 Components.classes["@mozilla.org/appshell/window-mediator;1"] |
|
424 .getService(Components.interfaces.nsIWindowMediator); |
|
425 |
|
426 var window = wm.getMostRecentWindow("Browser:SearchManager"); |
|
427 if (window) |
|
428 window.focus() |
|
429 else { |
|
430 setTimeout(function () { |
|
431 openDialog("chrome://browser/content/search/engineManager.xul", |
|
432 "_blank", "chrome,dialog,modal,centerscreen,resizable"); |
|
433 }, 0); |
|
434 } |
|
435 ]]></body> |
|
436 </method> |
|
437 |
|
438 <method name="selectEngine"> |
|
439 <parameter name="aEvent"/> |
|
440 <parameter name="isNextEngine"/> |
|
441 <body><![CDATA[ |
|
442 // Find the new index |
|
443 var newIndex = this.engines.indexOf(this.currentEngine); |
|
444 newIndex += isNextEngine ? 1 : -1; |
|
445 |
|
446 if (newIndex >= 0 && newIndex < this.engines.length) { |
|
447 this.currentEngine = this.engines[newIndex]; |
|
448 } |
|
449 |
|
450 aEvent.preventDefault(); |
|
451 aEvent.stopPropagation(); |
|
452 ]]></body> |
|
453 </method> |
|
454 |
|
455 <method name="handleSearchCommand"> |
|
456 <parameter name="aEvent"/> |
|
457 <body><![CDATA[ |
|
458 var textBox = this._textbox; |
|
459 var textValue = textBox.value; |
|
460 |
|
461 var where = "current"; |
|
462 if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") { |
|
463 if (aEvent.button == 2) |
|
464 return; |
|
465 where = whereToOpenLink(aEvent, false, true); |
|
466 } |
|
467 else { |
|
468 var newTabPref = textBox._prefBranch.getBoolPref("browser.search.openintab"); |
|
469 if ((aEvent && aEvent.altKey) ^ newTabPref) |
|
470 where = "tab"; |
|
471 } |
|
472 |
|
473 this.doSearch(textValue, where); |
|
474 ]]></body> |
|
475 </method> |
|
476 |
|
477 <method name="doSearch"> |
|
478 <parameter name="aData"/> |
|
479 <parameter name="aWhere"/> |
|
480 <body><![CDATA[ |
|
481 var textBox = this._textbox; |
|
482 |
|
483 // Save the current value in the form history |
|
484 if (aData && !PrivateBrowsingUtils.isWindowPrivate(window)) { |
|
485 this.FormHistory.update( |
|
486 { op : "bump", |
|
487 fieldname : textBox.getAttribute("autocompletesearchparam"), |
|
488 value : aData }, |
|
489 { handleError : function(aError) { |
|
490 Components.utils.reportError("Saving search to form history failed: " + aError.message); |
|
491 }}); |
|
492 } |
|
493 |
|
494 // null parameter below specifies HTML response for search |
|
495 var submission = this.currentEngine.getSubmission(aData, null, "searchbar"); |
|
496 BrowserSearch.recordSearchInHealthReport(this.currentEngine, "searchbar"); |
|
497 openUILinkIn(submission.uri.spec, aWhere, null, submission.postData); |
|
498 ]]></body> |
|
499 </method> |
|
500 </implementation> |
|
501 |
|
502 <handlers> |
|
503 <handler event="command"><![CDATA[ |
|
504 const target = event.originalTarget; |
|
505 if (target.engine) { |
|
506 this.currentEngine = target.engine; |
|
507 } else if (target.classList.contains("addengine-item")) { |
|
508 var searchService = |
|
509 Components.classes["@mozilla.org/browser/search-service;1"] |
|
510 .getService(Components.interfaces.nsIBrowserSearchService); |
|
511 // We only detect OpenSearch files |
|
512 var type = Components.interfaces.nsISearchEngine.DATA_XML; |
|
513 // Select the installed engine if the installation succeeds |
|
514 var installCallback = { |
|
515 onSuccess: engine => this.currentEngine = engine |
|
516 } |
|
517 searchService.addEngine(target.getAttribute("uri"), type, |
|
518 target.getAttribute("src"), false, |
|
519 installCallback); |
|
520 } |
|
521 else |
|
522 return; |
|
523 |
|
524 this.focus(); |
|
525 this.select(); |
|
526 ]]></handler> |
|
527 |
|
528 <handler event="popupshowing" action="this.rebuildPopupDynamic();"/> |
|
529 |
|
530 <handler event="DOMMouseScroll" |
|
531 phase="capturing" |
|
532 modifiers="accel" |
|
533 action="this.selectEngine(event, (event.detail > 0));"/> |
|
534 |
|
535 <handler event="focus"> |
|
536 <![CDATA[ |
|
537 // Speculatively connect to the current engine's search URI (and |
|
538 // suggest URI, if different) to reduce request latency |
|
539 |
|
540 const SUGGEST_TYPE = "application/x-suggestions+json"; |
|
541 var engine = this.currentEngine; |
|
542 var connector = |
|
543 Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect); |
|
544 var searchURI = engine.getSubmission("dummy", null, "searchbar").uri; |
|
545 let callbacks = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
|
546 .getInterface(Components.interfaces.nsIWebNavigation) |
|
547 .QueryInterface(Components.interfaces.nsILoadContext); |
|
548 connector.speculativeConnect(searchURI, callbacks); |
|
549 |
|
550 if (engine.supportsResponseType(SUGGEST_TYPE)) { |
|
551 var suggestURI = engine.getSubmission("dummy", SUGGEST_TYPE, "searchbar").uri; |
|
552 if (suggestURI.prePath != searchURI.prePath) |
|
553 connector.speculativeConnect(suggestURI, callbacks); |
|
554 } |
|
555 ]]></handler> |
|
556 </handlers> |
|
557 </binding> |
|
558 |
|
559 <binding id="searchbar-textbox" |
|
560 extends="chrome://global/content/bindings/autocomplete.xml#autocomplete"> |
|
561 <implementation implements="nsIObserver"> |
|
562 <constructor><![CDATA[ |
|
563 const kXULNS = |
|
564 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
565 |
|
566 if (document.getBindingParent(this).parentNode.parentNode.localName == |
|
567 "toolbarpaletteitem") |
|
568 return; |
|
569 |
|
570 // Initialize fields |
|
571 this._stringBundle = document.getBindingParent(this)._stringBundle; |
|
572 this._prefBranch = |
|
573 Components.classes["@mozilla.org/preferences-service;1"] |
|
574 .getService(Components.interfaces.nsIPrefBranch); |
|
575 this._suggestEnabled = |
|
576 this._prefBranch.getBoolPref("browser.search.suggest.enabled"); |
|
577 |
|
578 if (this._prefBranch.getBoolPref("browser.urlbar.clickSelectsAll")) |
|
579 this.setAttribute("clickSelectsAll", true); |
|
580 |
|
581 // Add items to context menu and attach controller to handle them |
|
582 var textBox = document.getAnonymousElementByAttribute(this, |
|
583 "anonid", "textbox-input-box"); |
|
584 var cxmenu = document.getAnonymousElementByAttribute(textBox, |
|
585 "anonid", "input-box-contextmenu"); |
|
586 var pasteAndSearch; |
|
587 cxmenu.addEventListener("popupshowing", function() { |
|
588 if (!pasteAndSearch) |
|
589 return; |
|
590 var controller = document.commandDispatcher.getControllerForCommand("cmd_paste"); |
|
591 var enabled = controller.isCommandEnabled("cmd_paste"); |
|
592 if (enabled) |
|
593 pasteAndSearch.removeAttribute("disabled"); |
|
594 else |
|
595 pasteAndSearch.setAttribute("disabled", "true"); |
|
596 }, false); |
|
597 |
|
598 var element, label, akey; |
|
599 |
|
600 element = document.createElementNS(kXULNS, "menuseparator"); |
|
601 cxmenu.appendChild(element); |
|
602 |
|
603 var insertLocation = cxmenu.firstChild; |
|
604 while (insertLocation.nextSibling && |
|
605 insertLocation.getAttribute("cmd") != "cmd_paste") |
|
606 insertLocation = insertLocation.nextSibling; |
|
607 if (insertLocation) { |
|
608 element = document.createElementNS(kXULNS, "menuitem"); |
|
609 label = this._stringBundle.getString("cmd_pasteAndSearch"); |
|
610 element.setAttribute("label", label); |
|
611 element.setAttribute("anonid", "paste-and-search"); |
|
612 element.setAttribute("oncommand", |
|
613 "BrowserSearch.searchBar.select(); goDoCommand('cmd_paste'); BrowserSearch.searchBar.handleSearchCommand();"); |
|
614 cxmenu.insertBefore(element, insertLocation.nextSibling); |
|
615 pasteAndSearch = element; |
|
616 } |
|
617 |
|
618 element = document.createElementNS(kXULNS, "menuitem"); |
|
619 label = this._stringBundle.getString("cmd_clearHistory"); |
|
620 akey = this._stringBundle.getString("cmd_clearHistory_accesskey"); |
|
621 element.setAttribute("label", label); |
|
622 element.setAttribute("accesskey", akey); |
|
623 element.setAttribute("cmd", "cmd_clearhistory"); |
|
624 cxmenu.appendChild(element); |
|
625 |
|
626 element = document.createElementNS(kXULNS, "menuitem"); |
|
627 label = this._stringBundle.getString("cmd_showSuggestions"); |
|
628 akey = this._stringBundle.getString("cmd_showSuggestions_accesskey"); |
|
629 element.setAttribute("anonid", "toggle-suggest-item"); |
|
630 element.setAttribute("label", label); |
|
631 element.setAttribute("accesskey", akey); |
|
632 element.setAttribute("cmd", "cmd_togglesuggest"); |
|
633 element.setAttribute("type", "checkbox"); |
|
634 element.setAttribute("checked", this._suggestEnabled); |
|
635 element.setAttribute("autocheck", "false"); |
|
636 this._suggestMenuItem = element; |
|
637 cxmenu.appendChild(element); |
|
638 |
|
639 this.controllers.appendController(this.searchbarController); |
|
640 |
|
641 // Add observer for suggest preference |
|
642 var prefs = Components.classes["@mozilla.org/preferences-service;1"] |
|
643 .getService(Components.interfaces.nsIPrefBranch); |
|
644 prefs.addObserver("browser.search.suggest.enabled", this, false); |
|
645 ]]></constructor> |
|
646 |
|
647 <destructor><![CDATA[ |
|
648 var prefs = Components.classes["@mozilla.org/preferences-service;1"] |
|
649 .getService(Components.interfaces.nsIPrefBranch); |
|
650 prefs.removeObserver("browser.search.suggest.enabled", this); |
|
651 |
|
652 // Because XBL and the customize toolbar code interacts poorly, |
|
653 // there may not be anything to remove here |
|
654 try { |
|
655 this.controllers.removeController(this.searchbarController); |
|
656 } catch (ex) { } |
|
657 ]]></destructor> |
|
658 |
|
659 <field name="_stringBundle"/> |
|
660 <field name="_prefBranch"/> |
|
661 <field name="_suggestMenuItem"/> |
|
662 <field name="_suggestEnabled"/> |
|
663 |
|
664 <!-- |
|
665 This overrides the searchParam property in autocomplete.xml. We're |
|
666 hijacking this property as a vehicle for delivering the privacy |
|
667 information about the window into the guts of nsSearchSuggestions. |
|
668 |
|
669 Note that the setter is the same as the parent. We were not sure whether |
|
670 we can override just the getter. If that proves to be the case, the setter |
|
671 can be removed. |
|
672 --> |
|
673 <property name="searchParam" |
|
674 onget="return this.getAttribute('autocompletesearchparam') + |
|
675 (PrivateBrowsingUtils.isWindowPrivate(window) ? '|private' : '');" |
|
676 onset="this.setAttribute('autocompletesearchparam', val); return val;"/> |
|
677 |
|
678 <!-- |
|
679 This method overrides the autocomplete binding's openPopup (essentially |
|
680 duplicating the logic from the autocomplete popup binding's |
|
681 openAutocompletePopup method), modifying it so that the popup is aligned with |
|
682 the inner textbox, but sized to not extend beyond the search bar border. |
|
683 --> |
|
684 <method name="openPopup"> |
|
685 <body><![CDATA[ |
|
686 var popup = this.popup; |
|
687 if (!popup.mPopupOpen) { |
|
688 // Initially the panel used for the searchbar (PopupAutoComplete |
|
689 // in browser.xul) is hidden to avoid impacting startup / new |
|
690 // window performance. The base binding's openPopup would normally |
|
691 // call the overriden openAutocompletePopup in urlbarBindings.xml's |
|
692 // browser-autocomplete-result-popup binding to unhide the popup, |
|
693 // but since we're overriding openPopup we need to unhide the panel |
|
694 // ourselves. |
|
695 popup.hidden = false; |
|
696 |
|
697 popup.mInput = this; |
|
698 popup.view = this.controller.QueryInterface(Components.interfaces.nsITreeView); |
|
699 popup.invalidate(); |
|
700 |
|
701 popup.showCommentColumn = this.showCommentColumn; |
|
702 popup.showImageColumn = this.showImageColumn; |
|
703 |
|
704 document.popupNode = null; |
|
705 |
|
706 const isRTL = getComputedStyle(this, "").direction == "rtl"; |
|
707 |
|
708 var outerRect = this.getBoundingClientRect(); |
|
709 var innerRect = this.inputField.getBoundingClientRect(); |
|
710 if (isRTL) { |
|
711 var width = innerRect.right - outerRect.left; |
|
712 } else { |
|
713 var width = outerRect.right - innerRect.left; |
|
714 } |
|
715 popup.setAttribute("width", width > 100 ? width : 100); |
|
716 |
|
717 var yOffset = outerRect.bottom - innerRect.bottom; |
|
718 popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false); |
|
719 } |
|
720 ]]></body> |
|
721 </method> |
|
722 |
|
723 <method name="observe"> |
|
724 <parameter name="aSubject"/> |
|
725 <parameter name="aTopic"/> |
|
726 <parameter name="aData"/> |
|
727 <body><![CDATA[ |
|
728 if (aTopic == "nsPref:changed") { |
|
729 this._suggestEnabled = |
|
730 this._prefBranch.getBoolPref("browser.search.suggest.enabled"); |
|
731 this._suggestMenuItem.setAttribute("checked", this._suggestEnabled); |
|
732 } |
|
733 ]]></body> |
|
734 </method> |
|
735 |
|
736 <method name="openSearch"> |
|
737 <body> |
|
738 <![CDATA[ |
|
739 // Don't open search popup if history popup is open |
|
740 if (!this.popupOpen) { |
|
741 document.getBindingParent(this).searchButton.open = true; |
|
742 return false; |
|
743 } |
|
744 return true; |
|
745 ]]> |
|
746 </body> |
|
747 </method> |
|
748 |
|
749 <!-- override |onTextEntered| in autocomplete.xml --> |
|
750 <method name="onTextEntered"> |
|
751 <parameter name="aEvent"/> |
|
752 <body><![CDATA[ |
|
753 var evt = aEvent || this.mEnterEvent; |
|
754 document.getBindingParent(this).handleSearchCommand(evt); |
|
755 this.mEnterEvent = null; |
|
756 ]]></body> |
|
757 </method> |
|
758 |
|
759 <!-- nsIController --> |
|
760 <field name="searchbarController" readonly="true"><![CDATA[({ |
|
761 _self: this, |
|
762 supportsCommand: function(aCommand) { |
|
763 return aCommand == "cmd_clearhistory" || |
|
764 aCommand == "cmd_togglesuggest"; |
|
765 }, |
|
766 |
|
767 isCommandEnabled: function(aCommand) { |
|
768 return true; |
|
769 }, |
|
770 |
|
771 doCommand: function (aCommand) { |
|
772 switch (aCommand) { |
|
773 case "cmd_clearhistory": |
|
774 var param = this._self.getAttribute("autocompletesearchparam"); |
|
775 |
|
776 let searchBar = this._self.parentNode; |
|
777 |
|
778 BrowserSearch.searchBar.FormHistory.update({ op : "remove", fieldname : param }, null); |
|
779 this._self.value = ""; |
|
780 break; |
|
781 case "cmd_togglesuggest": |
|
782 // The pref observer will update _suggestEnabled and the menu |
|
783 // checkmark. |
|
784 this._self._prefBranch.setBoolPref("browser.search.suggest.enabled", |
|
785 !this._self._suggestEnabled); |
|
786 break; |
|
787 default: |
|
788 // do nothing with unrecognized command |
|
789 } |
|
790 } |
|
791 })]]></field> |
|
792 </implementation> |
|
793 |
|
794 <handlers> |
|
795 <handler event="keypress" keycode="VK_UP" modifiers="accel" |
|
796 phase="capturing" |
|
797 action="document.getBindingParent(this).selectEngine(event, false);"/> |
|
798 |
|
799 <handler event="keypress" keycode="VK_DOWN" modifiers="accel" |
|
800 phase="capturing" |
|
801 action="document.getBindingParent(this).selectEngine(event, true);"/> |
|
802 |
|
803 <handler event="keypress" keycode="VK_DOWN" modifiers="alt" |
|
804 phase="capturing" |
|
805 action="return this.openSearch();"/> |
|
806 |
|
807 <handler event="keypress" keycode="VK_UP" modifiers="alt" |
|
808 phase="capturing" |
|
809 action="return this.openSearch();"/> |
|
810 |
|
811 #ifndef XP_MACOSX |
|
812 <handler event="keypress" keycode="VK_F4" |
|
813 phase="capturing" |
|
814 action="return this.openSearch();"/> |
|
815 #endif |
|
816 |
|
817 <handler event="dragover"> |
|
818 <![CDATA[ |
|
819 var types = event.dataTransfer.types; |
|
820 if (types.contains("text/plain") || types.contains("text/x-moz-text-internal")) |
|
821 event.preventDefault(); |
|
822 ]]> |
|
823 </handler> |
|
824 |
|
825 <handler event="drop"> |
|
826 <![CDATA[ |
|
827 var dataTransfer = event.dataTransfer; |
|
828 var data = dataTransfer.getData("text/plain"); |
|
829 if (!data) |
|
830 data = dataTransfer.getData("text/x-moz-text-internal"); |
|
831 if (data) { |
|
832 event.preventDefault(); |
|
833 this.value = data; |
|
834 this.onTextEntered(event); |
|
835 } |
|
836 ]]> |
|
837 </handler> |
|
838 |
|
839 </handlers> |
|
840 </binding> |
|
841 </bindings> |