browser/metro/base/content/bindings/urlbar.xml

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial