|
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/. --> |
|
5 |
|
6 |
|
7 <!DOCTYPE bindings [ |
|
8 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd"> |
|
9 %browserDTD; |
|
10 ]> |
|
11 |
|
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"> |
|
16 |
|
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"); |
|
24 |
|
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); |
|
28 |
|
29 this.inputField.controllers.insertControllerAt(0, this._copyCutValueController); |
|
30 |
|
31 this.minResultsForPopup = 0; |
|
32 this.popup._input = this; |
|
33 ]]> |
|
34 </constructor> |
|
35 |
|
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> |
|
43 |
|
44 <field name="_mayFormat"/> |
|
45 <field name="_mayTrimURLs"/> |
|
46 <field name="_maySelectAll"/> |
|
47 <field name="_lastKnownGoodURL"/> |
|
48 |
|
49 <method name="openPopup"> |
|
50 <body> |
|
51 <![CDATA[ |
|
52 this.popup.openAutocompletePopup(this, null); |
|
53 ]]> |
|
54 </body> |
|
55 </method> |
|
56 |
|
57 <method name="closePopup"> |
|
58 <body> |
|
59 <![CDATA[ |
|
60 this.popup.closePopup(this, null); |
|
61 ]]> |
|
62 </body> |
|
63 </method> |
|
64 |
|
65 <!-- URI Display: Domain Highlighting --> |
|
66 |
|
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> |
|
76 |
|
77 <method name="formatValue"> |
|
78 <body> |
|
79 <![CDATA[ |
|
80 if (!this._mayFormat || this.isEditing) |
|
81 return; |
|
82 |
|
83 let controller = this.editor.selectionController; |
|
84 let selection = controller.getSelection(controller.SELECTION_URLSECONDARY); |
|
85 selection.removeAllRanges(); |
|
86 |
|
87 let textNode = this.editor.rootElement.firstChild; |
|
88 let value = textNode.textContent; |
|
89 |
|
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; |
|
97 |
|
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 } |
|
116 |
|
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 } |
|
124 |
|
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> |
|
135 |
|
136 <!-- URI Display: Scheme and Trailing Slash Triming --> |
|
137 |
|
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> |
|
151 |
|
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); |
|
158 |
|
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; |
|
163 |
|
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 } |
|
171 |
|
172 let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup); |
|
173 |
|
174 let uri; |
|
175 try { |
|
176 uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE); |
|
177 } catch (e) {} |
|
178 if (!uri) |
|
179 return selectedVal; |
|
180 |
|
181 // Only copy exposable URIs |
|
182 try { |
|
183 uri = uriFixup.createExposableURI(uri); |
|
184 } catch (ex) {} |
|
185 |
|
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 } |
|
194 |
|
195 return selectedVal; |
|
196 } |
|
197 |
|
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 } |
|
208 |
|
209 return selectedVal; |
|
210 ]]> |
|
211 </body> |
|
212 </method> |
|
213 |
|
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; |
|
223 |
|
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 } |
|
231 |
|
232 Cc["@mozilla.org/widget/clipboardhelper;1"] |
|
233 .getService(Ci.nsIClipboardHelper) |
|
234 .copyString(val, document); |
|
235 }, |
|
236 |
|
237 supportsCommand: function(aCommand) { |
|
238 switch (aCommand) { |
|
239 case "cmd_copy": |
|
240 case "cmd_cut": |
|
241 return true; |
|
242 } |
|
243 return false; |
|
244 }, |
|
245 |
|
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 }, |
|
252 |
|
253 onEvent: function(aEventName) {} |
|
254 }) |
|
255 ]]> |
|
256 </field> |
|
257 |
|
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> |
|
266 |
|
267 <!-- URI Editing --> |
|
268 |
|
269 <property name="isEditing" readonly="true"> |
|
270 <getter> |
|
271 <![CDATA[ |
|
272 return Elements.urlbarState.hasAttribute("editing"); |
|
273 ]]> |
|
274 </getter> |
|
275 </property> |
|
276 |
|
277 <method name="beginEditing"> |
|
278 <parameter name="aShouldDismiss"/> |
|
279 <body> |
|
280 <![CDATA[ |
|
281 if (this.isEditing) |
|
282 return; |
|
283 |
|
284 Elements.urlbarState.setAttribute("editing", true); |
|
285 this._lastKnownGoodURL = this.value; |
|
286 |
|
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 } |
|
296 |
|
297 this._clearFormatting(); |
|
298 this.select(); |
|
299 |
|
300 if (aShouldDismiss) |
|
301 ContextUI.dismissTabs(); |
|
302 |
|
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> |
|
311 |
|
312 <method name="endEditing"> |
|
313 <parameter name="aShouldRevert"/> |
|
314 <body> |
|
315 <![CDATA[ |
|
316 if (!this.isEditing) |
|
317 return; |
|
318 |
|
319 Elements.urlbarState.removeAttribute("editing"); |
|
320 this.closePopup(); |
|
321 this.formatValue(); |
|
322 |
|
323 if (this.focused) |
|
324 this.blur(); |
|
325 |
|
326 if (aShouldRevert) |
|
327 this.value = this._lastKnownGoodURL; |
|
328 ]]> |
|
329 </body> |
|
330 </method> |
|
331 |
|
332 <!-- URI Submission --> |
|
333 |
|
334 <method name="_canonizeURL"> |
|
335 <parameter name="aURL"/> |
|
336 <parameter name="aTriggeringEvent"/> |
|
337 <body> |
|
338 <![CDATA[ |
|
339 if (!aURL) |
|
340 return ""; |
|
341 |
|
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 = ""; |
|
348 |
|
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 } |
|
366 |
|
367 if (suffix) { |
|
368 // trim leading/trailing spaces (bug 233205) |
|
369 aURL = aURL.trim(); |
|
370 |
|
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 } |
|
382 |
|
383 return aURL; |
|
384 ]]> |
|
385 </body> |
|
386 </method> |
|
387 |
|
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); |
|
395 |
|
396 this.endEditing(); |
|
397 BrowserUI.goToURI(this.value); |
|
398 |
|
399 return true; |
|
400 ]]> |
|
401 </body> |
|
402 </method> |
|
403 |
|
404 <method name="submitSearch"> |
|
405 <parameter name="anEngineName"/> |
|
406 <body> |
|
407 <![CDATA[ |
|
408 this.endEditing(); |
|
409 BrowserUI.doOpenSearch(anEngineName); |
|
410 |
|
411 return true; |
|
412 ]]> |
|
413 </body> |
|
414 </method> |
|
415 |
|
416 <!-- nsIObserver --> |
|
417 |
|
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; |
|
426 |
|
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> |
|
443 |
|
444 <handlers> |
|
445 <!-- Entering editing mode --> |
|
446 |
|
447 <handler event="focus" phase="capturing"> |
|
448 <![CDATA[ |
|
449 this.beginEditing(); |
|
450 ]]> |
|
451 </handler> |
|
452 |
|
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> |
|
461 |
|
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> |
|
476 |
|
477 <!-- Editing mode behaviors --> |
|
478 |
|
479 <handler event="dblclick" phase="capturing"> |
|
480 <![CDATA[ |
|
481 if (this._maySelectAll) this.select(); |
|
482 ]]> |
|
483 </handler> |
|
484 |
|
485 <handler event="contextmenu" phase="capturing"> |
|
486 <![CDATA[ |
|
487 let box = this.inputField.parentNode; |
|
488 box.showContextMenu(this, event, true); |
|
489 ]]> |
|
490 </handler> |
|
491 |
|
492 <!-- Leaving editing mode --> |
|
493 |
|
494 <handler event="blur" phase="capturing"> |
|
495 <![CDATA[ |
|
496 this.endEditing(); |
|
497 ]]> |
|
498 </handler> |
|
499 |
|
500 <handler event="keypress" phase="capturing" keycode="VK_RETURN" |
|
501 modifiers="accel shift any"> |
|
502 <![CDATA[ |
|
503 if (this.popup.submitSelected()) |
|
504 return; |
|
505 |
|
506 if (this.submitURL(event)) |
|
507 return; |
|
508 ]]> |
|
509 </handler> |
|
510 |
|
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> |
|
523 |
|
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> |
|
532 |
|
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> |
|
539 |
|
540 <implementation implements="nsIAutoCompletePopup, nsIObserver"> |
|
541 <constructor> |
|
542 <![CDATA[ |
|
543 this.hidden = true; |
|
544 Services.obs.addObserver(this, "browser-search-engine-modified", false); |
|
545 |
|
546 this._results.controller = this; |
|
547 this._searches.controller = this; |
|
548 ]]> |
|
549 </constructor> |
|
550 |
|
551 <destructor> |
|
552 <![CDATA[ |
|
553 Services.obs.removeObserver(this, "browser-search-engine-modified"); |
|
554 ]]> |
|
555 </destructor> |
|
556 |
|
557 <!-- nsIAutocompleteInput --> |
|
558 |
|
559 <field name="_input">null</field> |
|
560 |
|
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;"/> |
|
565 |
|
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> |
|
580 |
|
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> |
|
595 |
|
596 <property name="hasSelection"> |
|
597 <getter> |
|
598 <![CDATA[ |
|
599 if (!this._isGridBound(this._results) || |
|
600 !this._isGridBound(this._searches)) |
|
601 return false; |
|
602 |
|
603 return (this._results.selectedIndex >= 0 || |
|
604 this._searches.selectedIndex >= 0); |
|
605 ]]> |
|
606 </getter> |
|
607 </property> |
|
608 |
|
609 <method name="openAutocompletePopup"> |
|
610 <parameter name="aInput"/> |
|
611 <parameter name="aElement"/> |
|
612 <body> |
|
613 <![CDATA[ |
|
614 if (this.popupOpen) |
|
615 return; |
|
616 |
|
617 ContextUI.dismissContextAppbar(); |
|
618 |
|
619 this._input = aInput; |
|
620 this._grid = this._results; |
|
621 |
|
622 this.clearSelection(); |
|
623 this.invalidate(); |
|
624 |
|
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 } |
|
635 |
|
636 this._results.arrangeItemsNow(); |
|
637 this._searches.arrangeItemsNow(); |
|
638 |
|
639 this.hidden = false; |
|
640 Elements.urlbarState.setAttribute("autocomplete", "true"); |
|
641 ]]> |
|
642 </body> |
|
643 </method> |
|
644 |
|
645 <method name="closePopup"> |
|
646 <body> |
|
647 <![CDATA[ |
|
648 if (!this.popupOpen) |
|
649 return; |
|
650 |
|
651 this.input.controller.stopSearch(); |
|
652 this.hidden = true; |
|
653 Elements.urlbarState.removeAttribute("autocomplete"); |
|
654 ]]> |
|
655 </body> |
|
656 </method> |
|
657 |
|
658 <!-- Updating grid content --> |
|
659 |
|
660 <field name="_grid">null</field> |
|
661 |
|
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> |
|
664 |
|
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> |
|
667 |
|
668 <property name="_otherGrid" readonly="true"> |
|
669 <getter> |
|
670 <![CDATA[ |
|
671 if (this._grid == null) |
|
672 return null; |
|
673 |
|
674 return (this._grid == this._results) ? this._searches : this._results; |
|
675 ]]> |
|
676 </getter> |
|
677 </property> |
|
678 |
|
679 <method name="_isGridBound"> |
|
680 <parameter name="aGrid"/> |
|
681 <body> |
|
682 <![CDATA[ |
|
683 return aGrid && aGrid.isBound; |
|
684 ]]> |
|
685 </body> |
|
686 </method> |
|
687 |
|
688 <method name="invalidate"> |
|
689 <body> |
|
690 <![CDATA[ |
|
691 if (!this.popupOpen) |
|
692 return; |
|
693 |
|
694 this.updateSearchEngineHeader(); |
|
695 this.updateResults(); |
|
696 ]]> |
|
697 </body> |
|
698 </method> |
|
699 |
|
700 <!-- Updating grid content: results --> |
|
701 |
|
702 <method name="updateResults"> |
|
703 <body> |
|
704 <![CDATA[ |
|
705 if (!this._isGridBound(this._results)) |
|
706 return; |
|
707 |
|
708 if (!this.input) |
|
709 return; |
|
710 |
|
711 let haveNoResults = (this.matchCount == 0); |
|
712 |
|
713 if (haveNoResults) { |
|
714 this._results.clearAll(); |
|
715 this.setAttribute("nomatch", true); |
|
716 this._resultsContainer.removeAttribute("flex"); |
|
717 return; |
|
718 } |
|
719 |
|
720 this.removeAttribute("nomatch"); |
|
721 this._resultsContainer.setAttribute("flex", 1); |
|
722 |
|
723 let controller = this.input.controller; |
|
724 let lastMatch = this.matchCount - 1; |
|
725 let iterCount = Math.max(this._results.itemCount, this.matchCount); |
|
726 |
|
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 } |
|
735 |
|
736 let value = controller.getValueAt(i); |
|
737 let label = controller.getCommentAt(i) || value; |
|
738 let iconURI = controller.getImageAt(i); |
|
739 |
|
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 } |
|
760 |
|
761 this._results.arrangeItems(); |
|
762 ]]> |
|
763 </body> |
|
764 </method> |
|
765 |
|
766 <!-- Updating grid content: search engines --> |
|
767 |
|
768 <field name="_engines">[]</field> |
|
769 |
|
770 <method name="_initSearchEngines"> |
|
771 <body> |
|
772 <![CDATA[ |
|
773 Services.search.init(this.updateSearchEngineGrid.bind(this)); |
|
774 ]]> |
|
775 </body> |
|
776 </method> |
|
777 |
|
778 <method name="updateSearchEngineGrid"> |
|
779 <body> |
|
780 <![CDATA[ |
|
781 if (!this._isGridBound(this._searches)) |
|
782 return; |
|
783 |
|
784 this._engines = Services.search.getVisibleEngines(); |
|
785 |
|
786 while (this._searches.itemCount > 0) |
|
787 this._searches.removeItemAt(0, true); |
|
788 |
|
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"); |
|
793 |
|
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)); |
|
803 |
|
804 this._searches.arrangeItems(); |
|
805 ]]> |
|
806 </body> |
|
807 </method> |
|
808 |
|
809 <method name="updateSearchEngineHeader"> |
|
810 <body> |
|
811 <![CDATA[ |
|
812 if (!this._isGridBound(this._searches)) |
|
813 return; |
|
814 |
|
815 let searchString = this.input.controller.searchString; |
|
816 let label = Strings.browser.formatStringFromName( |
|
817 "opensearch.search.header", [searchString], 1); |
|
818 |
|
819 this._searchesHeader.value = label; |
|
820 ]]> |
|
821 </body> |
|
822 </method> |
|
823 |
|
824 <!-- Selecting results --> |
|
825 |
|
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; |
|
834 |
|
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> |
|
846 |
|
847 <method name="clearSelection"> |
|
848 <body> |
|
849 <![CDATA[ |
|
850 if (this._isGridBound(this._results)) |
|
851 this._results.clearSelection(); |
|
852 |
|
853 if (this._isGridBound(this._searches)) |
|
854 this._searches.clearSelection(); |
|
855 ]]> |
|
856 </body> |
|
857 </method> |
|
858 |
|
859 <!-- Submitting selected results --> |
|
860 |
|
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 } |
|
870 |
|
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 } |
|
876 |
|
877 return false; |
|
878 ]]> |
|
879 </body> |
|
880 </method> |
|
881 |
|
882 <method name="handleItemClick"> |
|
883 <parameter name="aItem"/> |
|
884 <parameter name="aEvent"/> |
|
885 <body> |
|
886 <![CDATA[ |
|
887 this.submitSelected(); |
|
888 ]]> |
|
889 </body> |
|
890 </method> |
|
891 |
|
892 <!-- nsIObserver --> |
|
893 |
|
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> |
|
909 |
|
910 <handlers> |
|
911 <handler event="contentgenerated"> |
|
912 <![CDATA[ |
|
913 let grid = event.originalTarget; |
|
914 |
|
915 if (grid == this._searches) |
|
916 this._initSearchEngines(); |
|
917 |
|
918 if (grid == this._results) |
|
919 this.updateResults(); |
|
920 ]]> |
|
921 </handler> |
|
922 |
|
923 <handler event="select"> |
|
924 <![CDATA[ |
|
925 let grid = event.originalTarget; |
|
926 |
|
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> |