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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/metro/base/content/bindings/urlbar.xml	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,937 @@
     1.4 +<?xml version="1.0" encoding="Windows-1252" ?>
     1.5 +<!-- This Source Code Form is subject to the terms of the Mozilla Public
     1.6 +   - License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 +   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
     1.8 +
     1.9 +
    1.10 +<!DOCTYPE bindings [
    1.11 +<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
    1.12 +%browserDTD;
    1.13 +]>
    1.14 +
    1.15 +<bindings
    1.16 +    xmlns="http://www.mozilla.org/xbl"
    1.17 +    xmlns:xbl="http://www.mozilla.org/xbl"
    1.18 +    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    1.19 +
    1.20 +  <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
    1.21 +    <implementation implements="nsIObserver">
    1.22 +      <constructor>
    1.23 +        <![CDATA[
    1.24 +          this._mayFormat = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
    1.25 +          this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
    1.26 +          this._maySelectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
    1.27 +
    1.28 +          Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
    1.29 +          Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
    1.30 +          Services.prefs.addObserver("browser.urlbar.doubleClickSelectsAll", this, false);
    1.31 +
    1.32 +          this.inputField.controllers.insertControllerAt(0, this._copyCutValueController);
    1.33 +
    1.34 +          this.minResultsForPopup = 0;
    1.35 +          this.popup._input = this;
    1.36 +        ]]>
    1.37 +      </constructor>
    1.38 +
    1.39 +      <destructor>
    1.40 +        <![CDATA[
    1.41 +          Services.prefs.removeObserver("browser.urlbar.formatting.enabled", this);
    1.42 +          Services.prefs.removeObserver("browser.urlbar.trimURLs", this);
    1.43 +          Services.prefs.removeObserver("browser.urlbar.doubleClickSelectsAll", this);
    1.44 +        ]]>
    1.45 +      </destructor>
    1.46 +
    1.47 +      <field name="_mayFormat"/>
    1.48 +      <field name="_mayTrimURLs"/>
    1.49 +      <field name="_maySelectAll"/>
    1.50 +      <field name="_lastKnownGoodURL"/>
    1.51 +
    1.52 +      <method name="openPopup">
    1.53 +        <body>
    1.54 +          <![CDATA[
    1.55 +            this.popup.openAutocompletePopup(this, null);
    1.56 +          ]]>
    1.57 +        </body>
    1.58 +      </method>
    1.59 +
    1.60 +      <method name="closePopup">
    1.61 +        <body>
    1.62 +          <![CDATA[
    1.63 +            this.popup.closePopup(this, null);
    1.64 +          ]]>
    1.65 +        </body>
    1.66 +      </method>
    1.67 +
    1.68 +      <!-- URI Display: Domain Highlighting -->
    1.69 +
    1.70 +      <method name="_clearFormatting">
    1.71 +        <body>
    1.72 +          <![CDATA[
    1.73 +            let controller = this.editor.selectionController;
    1.74 +            let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
    1.75 +            selection.removeAllRanges();
    1.76 +          ]]>
    1.77 +        </body>
    1.78 +      </method>
    1.79 +
    1.80 +      <method name="formatValue">
    1.81 +        <body>
    1.82 +          <![CDATA[
    1.83 +            if (!this._mayFormat || this.isEditing)
    1.84 +              return;
    1.85 +
    1.86 +            let controller = this.editor.selectionController;
    1.87 +            let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
    1.88 +            selection.removeAllRanges();
    1.89 +
    1.90 +            let textNode = this.editor.rootElement.firstChild;
    1.91 +            let value = textNode.textContent;
    1.92 +
    1.93 +            let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
    1.94 +            if (protocol &&
    1.95 +                ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
    1.96 +              return;
    1.97 +            let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
    1.98 +            if (!matchedURL)
    1.99 +              return;
   1.100 +
   1.101 +            let [, preDomain, domain] = matchedURL;
   1.102 +            let baseDomain = domain;
   1.103 +            let subDomain = "";
   1.104 +            // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
   1.105 +            if (domain[0] != "[") {
   1.106 +              try {
   1.107 +                baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
   1.108 +                if (!domain.endsWith(baseDomain)) {
   1.109 +                  // getBaseDomainFromHost converts its resultant to ACE.
   1.110 +                  let IDNService = Cc["@mozilla.org/network/idn-service;1"]
   1.111 +                                   .getService(Ci.nsIIDNService);
   1.112 +                  baseDomain = IDNService.convertACEtoUTF8(baseDomain);
   1.113 +                }
   1.114 +              } catch (e) {}
   1.115 +            }
   1.116 +            if (baseDomain != domain) {
   1.117 +              subDomain = domain.slice(0, -baseDomain.length);
   1.118 +            }
   1.119 +
   1.120 +            let rangeLength = preDomain.length + subDomain.length;
   1.121 +            if (rangeLength) {
   1.122 +              let range = document.createRange();
   1.123 +              range.setStart(textNode, 0);
   1.124 +              range.setEnd(textNode, rangeLength);
   1.125 +              selection.addRange(range);
   1.126 +            }
   1.127 +
   1.128 +            let startRest = preDomain.length + domain.length;
   1.129 +            if (startRest < value.length) {
   1.130 +              let range = document.createRange();
   1.131 +              range.setStart(textNode, startRest);
   1.132 +              range.setEnd(textNode, value.length);
   1.133 +              selection.addRange(range);
   1.134 +            }
   1.135 +          ]]>
   1.136 +        </body>
   1.137 +      </method>
   1.138 +
   1.139 +      <!-- URI Display: Scheme and Trailing Slash Triming -->
   1.140 +
   1.141 +      <method name="_trimURL">
   1.142 +        <parameter name="aURL"/>
   1.143 +        <body>
   1.144 +          <![CDATA[
   1.145 +            // This function must not modify the given URL such that calling
   1.146 +            // nsIURIFixup::createFixupURI with the rfdesult will produce a different URI.
   1.147 +            return aURL /* remove single trailing slash for http/https/ftp URLs */
   1.148 +               .replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
   1.149 +                /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
   1.150 +               .replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
   1.151 +          ]]>
   1.152 +        </body>
   1.153 +      </method>
   1.154 +
   1.155 +      <method name="_getSelectedValueForClipboard">
   1.156 +        <body>
   1.157 +          <![CDATA[
   1.158 +            // Grab the actual input field's value, not our value, which could include moz-action:
   1.159 +            let inputVal = this.inputField.value;
   1.160 +            let selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
   1.161 +
   1.162 +            // If the selection doesn't start at the beginning or doesn't span the full domain or
   1.163 +            // the URL bar is modified, nothing else to do here.
   1.164 +            if (this.selectionStart > 0 || this.valueIsTyped)
   1.165 +              return selectedVal;
   1.166 +
   1.167 +            // The selection doesn't span the full domain if it doesn't contain a slash and is
   1.168 +            // followed by some character other than a slash.
   1.169 +            if (!selectedVal.contains("/")) {
   1.170 +              let remainder = inputVal.replace(selectedVal, "");
   1.171 +              if (remainder != "" && remainder[0] != "/")
   1.172 +                return selectedVal;
   1.173 +            }
   1.174 +
   1.175 +            let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
   1.176 +
   1.177 +            let uri;
   1.178 +            try {
   1.179 +              uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
   1.180 +            } catch (e) {}
   1.181 +            if (!uri)
   1.182 +              return selectedVal;
   1.183 +
   1.184 +            // Only copy exposable URIs
   1.185 +            try {
   1.186 +              uri = uriFixup.createExposableURI(uri);
   1.187 +            } catch (ex) {}
   1.188 +
   1.189 +            // If the entire URL is selected, just use the actual loaded URI.
   1.190 +            if (inputVal == selectedVal) {
   1.191 +              // ... but only if  isn't a javascript: or data: URI, since those
   1.192 +              // are hard to read when encoded
   1.193 +              if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
   1.194 +                // Parentheses are known to confuse third-party applications (bug 458565).
   1.195 +                selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
   1.196 +              }
   1.197 +
   1.198 +              return selectedVal;
   1.199 +            }
   1.200 +
   1.201 +            // Just the beginning of the URL is selected, check for a trimmed value
   1.202 +            let spec = uri.spec;
   1.203 +            let trimmedSpec = this._trimURL(spec);
   1.204 +            if (spec != trimmedSpec) {
   1.205 +              // Prepend the portion that trimURL removed from the beginning.
   1.206 +              // This assumes trimURL will only truncate the URL at
   1.207 +              // the beginning or end (or both).
   1.208 +              let trimmedSegments = spec.split(trimmedSpec);
   1.209 +              selectedVal = trimmedSegments[0] + selectedVal;
   1.210 +            }
   1.211 +
   1.212 +            return selectedVal;
   1.213 +          ]]>
   1.214 +        </body>
   1.215 +      </method>
   1.216 +
   1.217 +      <field name="_copyCutValueController">
   1.218 +        <![CDATA[
   1.219 +          ({
   1.220 +            urlbar: this,
   1.221 +            doCommand: function(aCommand) {
   1.222 +              let urlbar = this.urlbar;
   1.223 +              let val = urlbar._getSelectedValueForClipboard();
   1.224 +              if (!val)
   1.225 +                return;
   1.226 +
   1.227 +              if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
   1.228 +                let start = urlbar.selectionStart;
   1.229 +                let end = urlbar.selectionEnd;
   1.230 +                urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
   1.231 +                                          urlbar.inputField.value.substring(end);
   1.232 +                urlbar.selectionStart = urlbar.selectionEnd = start;
   1.233 +              }
   1.234 +
   1.235 +              Cc["@mozilla.org/widget/clipboardhelper;1"]
   1.236 +                .getService(Ci.nsIClipboardHelper)
   1.237 +                .copyString(val, document);
   1.238 +            },
   1.239 +
   1.240 +            supportsCommand: function(aCommand) {
   1.241 +              switch (aCommand) {
   1.242 +                case "cmd_copy":
   1.243 +                case "cmd_cut":
   1.244 +                  return true;
   1.245 +              }
   1.246 +              return false;
   1.247 +            },
   1.248 +
   1.249 +            isCommandEnabled: function(aCommand) {
   1.250 +              let urlbar = this.urlbar;
   1.251 +              return this.supportsCommand(aCommand) &&
   1.252 +                     (aCommand != "cmd_cut" || !urlbar.readOnly) &&
   1.253 +                     urlbar.selectionStart < urlbar.selectionEnd;
   1.254 +            },
   1.255 +
   1.256 +            onEvent: function(aEventName) {}
   1.257 +          })
   1.258 +        ]]>
   1.259 +      </field>
   1.260 +
   1.261 +      <method name="trimValue">
   1.262 +        <parameter name="aURL"/>
   1.263 +        <body>
   1.264 +          <![CDATA[
   1.265 +            return (this._mayTrimURLs) ? this._trimURL(aURL) : aURL;
   1.266 +          ]]>
   1.267 +        </body>
   1.268 +      </method>
   1.269 +
   1.270 +      <!-- URI Editing -->
   1.271 +
   1.272 +      <property name="isEditing" readonly="true">
   1.273 +        <getter>
   1.274 +          <![CDATA[
   1.275 +            return Elements.urlbarState.hasAttribute("editing");
   1.276 +          ]]>
   1.277 +        </getter>
   1.278 +      </property>
   1.279 +
   1.280 +      <method name="beginEditing">
   1.281 +        <parameter name="aShouldDismiss"/>
   1.282 +        <body>
   1.283 +          <![CDATA[
   1.284 +            if (this.isEditing)
   1.285 +              return;
   1.286 +
   1.287 +            Elements.urlbarState.setAttribute("editing", true);
   1.288 +            this._lastKnownGoodURL = this.value;
   1.289 +
   1.290 +            if (!this.focused) {
   1.291 +              this.focus();
   1.292 +              // If we force focus, ensure we're visible.
   1.293 +              if (ContextUI.displayNavbar()) {
   1.294 +                // If we forced visibility, ensure we're positioned above keyboard.
   1.295 +                // (Previous "blur" may have forced us down behind it.)
   1.296 +                ContentAreaObserver.updateAppBarPosition();
   1.297 +              }
   1.298 +            }
   1.299 +
   1.300 +            this._clearFormatting();
   1.301 +            this.select();
   1.302 +
   1.303 +            if (aShouldDismiss)
   1.304 +              ContextUI.dismissTabs();
   1.305 +
   1.306 +            if (!InputSourceHelper.isPrecise) {
   1.307 +              let inputRectangle = this.inputField.getBoundingClientRect();
   1.308 +              SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
   1.309 +                  inputRectangle.left, inputRectangle.top, this);
   1.310 +            }
   1.311 +          ]]>
   1.312 +        </body>
   1.313 +      </method>
   1.314 +
   1.315 +      <method name="endEditing">
   1.316 +        <parameter name="aShouldRevert"/>
   1.317 +        <body>
   1.318 +          <![CDATA[
   1.319 +            if (!this.isEditing)
   1.320 +              return;
   1.321 +
   1.322 +            Elements.urlbarState.removeAttribute("editing");
   1.323 +            this.closePopup();
   1.324 +            this.formatValue();
   1.325 +
   1.326 +            if (this.focused)
   1.327 +              this.blur();
   1.328 +
   1.329 +            if (aShouldRevert)
   1.330 +              this.value = this._lastKnownGoodURL;
   1.331 +          ]]>
   1.332 +        </body>
   1.333 +      </method>
   1.334 +
   1.335 +      <!-- URI Submission -->
   1.336 +
   1.337 +      <method name="_canonizeURL">
   1.338 +        <parameter name="aURL"/>
   1.339 +        <parameter name="aTriggeringEvent"/>
   1.340 +        <body>
   1.341 +          <![CDATA[
   1.342 +            if (!aURL)
   1.343 +              return "";
   1.344 +
   1.345 +            // Only add the suffix when the URL bar value isn't already "URL-like",
   1.346 +            // and only if we get a keyboard event, to match user expectations.
   1.347 +            if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aURL)) {
   1.348 +              let accel = aTriggeringEvent.ctrlKey;
   1.349 +              let shift = aTriggeringEvent.shiftKey;
   1.350 +              let suffix = "";
   1.351 +
   1.352 +              switch (true) {
   1.353 +                case (accel && shift):
   1.354 +                  suffix = ".org/";
   1.355 +                  break;
   1.356 +                case (shift):
   1.357 +                  suffix = ".net/";
   1.358 +                  break;
   1.359 +                case (accel):
   1.360 +                  try {
   1.361 +                    suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
   1.362 +                    if (suffix.charAt(suffix.length - 1) != "/")
   1.363 +                      suffix += "/";
   1.364 +                  } catch(e) {
   1.365 +                    suffix = ".com/";
   1.366 +                  }
   1.367 +                  break;
   1.368 +              }
   1.369 +
   1.370 +              if (suffix) {
   1.371 +                // trim leading/trailing spaces (bug 233205)
   1.372 +                aURL = aURL.trim();
   1.373 +
   1.374 +                // Tack www. and suffix on.  If user has appended directories, insert
   1.375 +                // suffix before them (bug 279035).  Be careful not to get two slashes.
   1.376 +                let firstSlash = aURL.indexOf("/");
   1.377 +                if (firstSlash >= 0) {
   1.378 +                  aURL = aURL.substring(0, firstSlash) + suffix + aURL.substring(firstSlash + 1);
   1.379 +                } else {
   1.380 +                  aURL = aURL + suffix;
   1.381 +                }
   1.382 +                aURL = "http://www." + aURL;
   1.383 +              }
   1.384 +            }
   1.385 +
   1.386 +            return aURL;
   1.387 +          ]]>
   1.388 +        </body>
   1.389 +      </method>
   1.390 +
   1.391 +      <method name="submitURL">
   1.392 +        <parameter name="aEvent"/>
   1.393 +        <body>
   1.394 +          <![CDATA[
   1.395 +            // If the address was typed in by a user, tidy it up
   1.396 +            if (aEvent instanceof KeyEvent)
   1.397 +              this.value = this._canonizeURL(this.value, aEvent);
   1.398 +
   1.399 +            this.endEditing();
   1.400 +            BrowserUI.goToURI(this.value);
   1.401 +
   1.402 +            return true;
   1.403 +          ]]>
   1.404 +        </body>
   1.405 +      </method>
   1.406 +
   1.407 +      <method name="submitSearch">
   1.408 +        <parameter name="anEngineName"/>
   1.409 +        <body>
   1.410 +          <![CDATA[
   1.411 +            this.endEditing();
   1.412 +            BrowserUI.doOpenSearch(anEngineName);
   1.413 +
   1.414 +            return true;
   1.415 +          ]]>
   1.416 +        </body>
   1.417 +      </method>
   1.418 +
   1.419 +      <!-- nsIObserver -->
   1.420 +
   1.421 +      <method name="observe">
   1.422 +        <parameter name="aSubject"/>
   1.423 +        <parameter name="aTopic"/>
   1.424 +        <parameter name="aData"/>
   1.425 +        <body>
   1.426 +          <![CDATA[
   1.427 +            if (aTopic != "nsPref:changed")
   1.428 +              return;
   1.429 +
   1.430 +            switch (aData) {
   1.431 +              case "browser.urlbar.formatting.enabled":
   1.432 +                this._mayFormat = Services.prefs.getBoolPref(aData);
   1.433 +                if (!this._mayFormat) this._clearFormatting();
   1.434 +                break;
   1.435 +              case "browser.urlbar.trimURLs":
   1.436 +                this._mayTrimURLs = Services.prefs.getBoolPref(aData);
   1.437 +                break;
   1.438 +              case "browser.urlbar.doubleClickSelectsAll":
   1.439 +                this._maySelectAll = Services.prefs.getBoolPref(aData);
   1.440 +                break;
   1.441 +            }
   1.442 +          ]]>
   1.443 +        </body>
   1.444 +      </method>
   1.445 +    </implementation>
   1.446 +
   1.447 +    <handlers>
   1.448 +      <!-- Entering editing mode -->
   1.449 +
   1.450 +      <handler event="focus" phase="capturing">
   1.451 +        <![CDATA[
   1.452 +          this.beginEditing();
   1.453 +        ]]>
   1.454 +      </handler>
   1.455 +
   1.456 +      <handler event="input" phase="capturing">
   1.457 +        <![CDATA[
   1.458 +          // Ensures that paste-and-go actually brings the URL bar into editing mode
   1.459 +          // and displays the half-height autocomplete popup.
   1.460 +          this.beginEditing();
   1.461 +          this.openPopup();
   1.462 +        ]]>
   1.463 +      </handler>
   1.464 +
   1.465 +      <handler event="click" phase="capturing">
   1.466 +        <![CDATA[
   1.467 +          // workaround for bug 925457: taping browser chrome resets last tap
   1.468 +          // co-ordinates to 'undefined' so that we know not to shift the
   1.469 +          // browser when the keyboard is up in SelectionHandler's
   1.470 +          // _calcNewContentPosition().
   1.471 +          Browser.selectedTab.browser.messageManager.sendAsyncMessage(
   1.472 +              "Browser:ResetLastPos", {
   1.473 +                xPos: null,
   1.474 +                yPos: null
   1.475 +              });
   1.476 +          this.beginEditing(true);
   1.477 +        ]]>
   1.478 +      </handler>
   1.479 +
   1.480 +      <!-- Editing mode behaviors -->
   1.481 +
   1.482 +      <handler event="dblclick" phase="capturing">
   1.483 +        <![CDATA[
   1.484 +            if (this._maySelectAll) this.select();
   1.485 +        ]]>
   1.486 +      </handler>
   1.487 +
   1.488 +      <handler event="contextmenu" phase="capturing">
   1.489 +        <![CDATA[
   1.490 +          let box = this.inputField.parentNode;
   1.491 +          box.showContextMenu(this, event, true);
   1.492 +        ]]>
   1.493 +      </handler>
   1.494 +
   1.495 +      <!-- Leaving editing mode -->
   1.496 +
   1.497 +      <handler event="blur" phase="capturing">
   1.498 +        <![CDATA[
   1.499 +          this.endEditing();
   1.500 +        ]]>
   1.501 +      </handler>
   1.502 +
   1.503 +      <handler event="keypress" phase="capturing" keycode="VK_RETURN"
   1.504 +               modifiers="accel shift any">
   1.505 +        <![CDATA[
   1.506 +          if (this.popup.submitSelected())
   1.507 +            return;
   1.508 +
   1.509 +          if (this.submitURL(event))
   1.510 +            return;
   1.511 +        ]]>
   1.512 +      </handler>
   1.513 +
   1.514 +      <handler event="keypress" phase="capturing" keycode="VK_UP">
   1.515 +        <![CDATA[
   1.516 +          if (!this.popup.hasSelection) {
   1.517 +            // Treat the first up as a down key to trick handleKeyNavigation() to start
   1.518 +            // keyboard navigation on autocomplete popup.
   1.519 +            this.mController.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
   1.520 +            event.preventDefault();
   1.521 +          }
   1.522 +        ]]>
   1.523 +      </handler>
   1.524 +    </handlers>
   1.525 +  </binding>
   1.526 +
   1.527 +  <binding id="urlbar-autocomplete">
   1.528 +    <content class="meta-section-container" pack="end">
   1.529 +      <xul:vbox class="meta-section" anonid="results-container" flex="2">
   1.530 +        <xul:label class="meta-section-title"
   1.531 +                   value="&autocompleteResultsHeader.label;"/>
   1.532 +        <richgrid anonid="results" rows="3" minSlots="8"
   1.533 +                  seltype="single" nocontext="true" deferlayout="true"/>
   1.534 +      </xul:vbox>
   1.535 +
   1.536 +      <xul:vbox class="meta-section" flex="1">
   1.537 +        <xul:label anonid="searches-header" class="meta-section-title"/>
   1.538 +        <richgrid anonid="searches" rows="3" flex="1" search="true"
   1.539 +                  seltype="single" nocontext="true" deferlayout="true"/>
   1.540 +      </xul:vbox>
   1.541 +    </content>
   1.542 +
   1.543 +    <implementation implements="nsIAutoCompletePopup, nsIObserver">
   1.544 +      <constructor>
   1.545 +        <![CDATA[
   1.546 +          this.hidden = true;
   1.547 +          Services.obs.addObserver(this, "browser-search-engine-modified", false);
   1.548 +
   1.549 +          this._results.controller = this;
   1.550 +          this._searches.controller = this;
   1.551 +        ]]>
   1.552 +      </constructor>
   1.553 +
   1.554 +      <destructor>
   1.555 +        <![CDATA[
   1.556 +          Services.obs.removeObserver(this, "browser-search-engine-modified");
   1.557 +        ]]>
   1.558 +      </destructor>
   1.559 +
   1.560 +      <!-- nsIAutocompleteInput -->
   1.561 +
   1.562 +      <field name="_input">null</field>
   1.563 +
   1.564 +      <property name="input" readonly="true" onget="return this._input;"/>
   1.565 +      <property name="matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
   1.566 +      <property name="popupOpen" readonly="true" onget="return !this.hidden"/>
   1.567 +      <property name="overrideValue" readonly="true" onget="return null;"/>
   1.568 +
   1.569 +      <property name="selectedItem">
   1.570 +        <getter>
   1.571 +          <![CDATA[
   1.572 +            return this._isGridBound(this._results) ? this._results.selectedItem : null;
   1.573 +          ]]>
   1.574 +        </getter>
   1.575 +        <setter>
   1.576 +          <![CDATA[
   1.577 +            if (this._isGridBound(this._results)) {
   1.578 +              this._results.selectedItem = val;
   1.579 +            }
   1.580 +          ]]>
   1.581 +        </setter>
   1.582 +      </property>
   1.583 +
   1.584 +      <property name="selectedIndex">
   1.585 +        <getter>
   1.586 +          <![CDATA[
   1.587 +            return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
   1.588 +          ]]>
   1.589 +        </getter>
   1.590 +        <setter>
   1.591 +          <![CDATA[
   1.592 +            if (this._isGridBound(this._results)) {
   1.593 +              this._results.selectedIndex = val;
   1.594 +            }
   1.595 +          ]]>
   1.596 +        </setter>
   1.597 +      </property>
   1.598 +
   1.599 +      <property name="hasSelection">
   1.600 +        <getter>
   1.601 +          <![CDATA[
   1.602 +            if (!this._isGridBound(this._results) ||
   1.603 +              !this._isGridBound(this._searches))
   1.604 +              return false;
   1.605 +
   1.606 +            return (this._results.selectedIndex >= 0 ||
   1.607 +              this._searches.selectedIndex >= 0);
   1.608 +          ]]>
   1.609 +        </getter>
   1.610 +      </property>
   1.611 +
   1.612 +      <method name="openAutocompletePopup">
   1.613 +        <parameter name="aInput"/>
   1.614 +        <parameter name="aElement"/>
   1.615 +        <body>
   1.616 +          <![CDATA[
   1.617 +            if (this.popupOpen)
   1.618 +              return;
   1.619 +
   1.620 +            ContextUI.dismissContextAppbar();
   1.621 +
   1.622 +            this._input = aInput;
   1.623 +            this._grid = this._results;
   1.624 +
   1.625 +            this.clearSelection();
   1.626 +            this.invalidate();
   1.627 +
   1.628 +            let viewstate = this.getAttribute("viewstate");
   1.629 +            switch (viewstate) {
   1.630 +              case "portrait":
   1.631 +              case "snapped":
   1.632 +                this._results.setAttribute("vertical", true);
   1.633 +                break;
   1.634 +              default:
   1.635 +                this._results.removeAttribute("vertical");
   1.636 +                break;
   1.637 +            }
   1.638 +
   1.639 +            this._results.arrangeItemsNow();
   1.640 +            this._searches.arrangeItemsNow();
   1.641 +
   1.642 +            this.hidden = false;
   1.643 +            Elements.urlbarState.setAttribute("autocomplete", "true");
   1.644 +          ]]>
   1.645 +        </body>
   1.646 +      </method>
   1.647 +
   1.648 +      <method name="closePopup">
   1.649 +        <body>
   1.650 +          <![CDATA[
   1.651 +            if (!this.popupOpen)
   1.652 +              return;
   1.653 +
   1.654 +            this.input.controller.stopSearch();
   1.655 +            this.hidden = true;
   1.656 +            Elements.urlbarState.removeAttribute("autocomplete");
   1.657 +          ]]>
   1.658 +        </body>
   1.659 +      </method>
   1.660 +
   1.661 +      <!-- Updating grid content -->
   1.662 +
   1.663 +      <field name="_grid">null</field>
   1.664 +
   1.665 +      <field name="_results" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results');</field>
   1.666 +      <field name="_resultsContainer" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results-container');</field>
   1.667 +
   1.668 +      <field name="_searchesHeader" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches-header');</field>
   1.669 +      <field name="_searches" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches');</field>
   1.670 +
   1.671 +      <property name="_otherGrid" readonly="true">
   1.672 +        <getter>
   1.673 +          <![CDATA[
   1.674 +            if (this._grid == null)
   1.675 +              return null;
   1.676 +
   1.677 +            return (this._grid == this._results) ? this._searches : this._results;
   1.678 +          ]]>
   1.679 +        </getter>
   1.680 +      </property>
   1.681 +
   1.682 +      <method name="_isGridBound">
   1.683 +        <parameter name="aGrid"/>
   1.684 +        <body>
   1.685 +          <![CDATA[
   1.686 +            return aGrid && aGrid.isBound;
   1.687 +          ]]>
   1.688 +        </body>
   1.689 +      </method>
   1.690 +
   1.691 +      <method name="invalidate">
   1.692 +        <body>
   1.693 +          <![CDATA[
   1.694 +            if (!this.popupOpen)
   1.695 +              return;
   1.696 +
   1.697 +            this.updateSearchEngineHeader();
   1.698 +            this.updateResults();
   1.699 +          ]]>
   1.700 +        </body>
   1.701 +      </method>
   1.702 +
   1.703 +      <!-- Updating grid content: results -->
   1.704 +
   1.705 +      <method name="updateResults">
   1.706 +        <body>
   1.707 +          <![CDATA[
   1.708 +            if (!this._isGridBound(this._results))
   1.709 +              return;
   1.710 +
   1.711 +            if (!this.input)
   1.712 +              return;
   1.713 +
   1.714 +            let haveNoResults = (this.matchCount == 0);
   1.715 +
   1.716 +            if (haveNoResults) {
   1.717 +              this._results.clearAll();
   1.718 +              this.setAttribute("nomatch", true);
   1.719 +              this._resultsContainer.removeAttribute("flex");
   1.720 +              return;
   1.721 +            }
   1.722 +
   1.723 +            this.removeAttribute("nomatch");
   1.724 +            this._resultsContainer.setAttribute("flex", 1);
   1.725 +
   1.726 +            let controller = this.input.controller;
   1.727 +            let lastMatch = this.matchCount - 1;
   1.728 +            let iterCount = Math.max(this._results.itemCount, this.matchCount);
   1.729 +
   1.730 +            // Swap out existing items for new search hit results
   1.731 +            for (let i = 0; i < iterCount; i++) {
   1.732 +              let item = this._results._slotAt(i);
   1.733 +              if (i > lastMatch) {
   1.734 +                item.removeAttribute("value");
   1.735 +                item.removeAttribute("autocomplete");
   1.736 +                continue;
   1.737 +              }
   1.738 +
   1.739 +              let value = controller.getValueAt(i);
   1.740 +              let label = controller.getCommentAt(i) || value;
   1.741 +              let iconURI = controller.getImageAt(i);
   1.742 +
   1.743 +              item.setAttribute("autocomplete", true);
   1.744 +              item.setAttribute("label", label);
   1.745 +              item.setAttribute("value", value);
   1.746 +              item.setAttribute("iconURI", iconURI);
   1.747 +              let xpFaviconURI = Services.io.newURI(iconURI.replace("moz-anno:favicon:",""), null, null);
   1.748 +              Task.spawn(function() {
   1.749 +                let colorInfo = yield ColorUtils.getForegroundAndBackgroundIconColors(xpFaviconURI);
   1.750 +                if ( !(colorInfo && colorInfo.background && colorInfo.foreground) 
   1.751 +                     || (item.getAttribute("iconURI") != iconURI) ) {
   1.752 +                  return;
   1.753 +                }
   1.754 +                let { background, foreground } = colorInfo;
   1.755 +                item.style.color = foreground; //color text
   1.756 +                item.setAttribute("customColor", background);
   1.757 +                // when bound, use the setter to propogate the color change through the tile
   1.758 +                if ('color' in item) {
   1.759 +                  item.color = background;
   1.760 +                }
   1.761 +              }).then(null, err => Components.utils.reportError(err));
   1.762 +            }
   1.763 +
   1.764 +            this._results.arrangeItems();
   1.765 +          ]]>
   1.766 +        </body>
   1.767 +      </method>
   1.768 +
   1.769 +      <!-- Updating grid content: search engines -->
   1.770 +
   1.771 +      <field name="_engines">[]</field>
   1.772 +
   1.773 +      <method name="_initSearchEngines">
   1.774 +        <body>
   1.775 +          <![CDATA[
   1.776 +            Services.search.init(this.updateSearchEngineGrid.bind(this));
   1.777 +          ]]>
   1.778 +        </body>
   1.779 +      </method>
   1.780 +
   1.781 +      <method name="updateSearchEngineGrid">
   1.782 +        <body>
   1.783 +          <![CDATA[
   1.784 +            if (!this._isGridBound(this._searches))
   1.785 +              return;
   1.786 +
   1.787 +            this._engines = Services.search.getVisibleEngines();
   1.788 +
   1.789 +            while (this._searches.itemCount > 0)
   1.790 +              this._searches.removeItemAt(0, true);
   1.791 +
   1.792 +            this._engines.forEach(function (anEngine) {
   1.793 +              let item = this._searches.appendItem("", anEngine.name, true);
   1.794 +              item.setAttribute("autocomplete", "true");
   1.795 +              item.setAttribute("search", "true");
   1.796 +
   1.797 +              let largeImage = anEngine.getIconURLBySize(74,74);
   1.798 +              if (largeImage) {
   1.799 +                item.setAttribute("iconsize", "large");
   1.800 +                item.setAttribute("iconURI", largeImage);
   1.801 +              } else if (anEngine.iconURI && anEngine.iconURI.spec) {
   1.802 +                item.setAttribute("iconURI", anEngine.iconURI.spec);
   1.803 +                item.setAttribute("customColor", "#fff");
   1.804 +              }
   1.805 +            }.bind(this));
   1.806 +
   1.807 +            this._searches.arrangeItems();
   1.808 +          ]]>
   1.809 +        </body>
   1.810 +      </method>
   1.811 +
   1.812 +      <method name="updateSearchEngineHeader">
   1.813 +        <body>
   1.814 +          <![CDATA[
   1.815 +            if (!this._isGridBound(this._searches))
   1.816 +              return;
   1.817 +
   1.818 +            let searchString = this.input.controller.searchString;
   1.819 +            let label = Strings.browser.formatStringFromName(
   1.820 +                              "opensearch.search.header", [searchString], 1);
   1.821 +
   1.822 +            this._searchesHeader.value = label;
   1.823 +          ]]>
   1.824 +        </body>
   1.825 +      </method>
   1.826 +
   1.827 +      <!-- Selecting results -->
   1.828 +
   1.829 +      <method name="selectBy">
   1.830 +        <parameter name="aReverse"/>
   1.831 +        <parameter name="aPage"/>
   1.832 +        <body>
   1.833 +          <![CDATA[
   1.834 +            if (!this._isGridBound(this._results) ||
   1.835 +                !this._isGridBound(this._searches))
   1.836 +              return;
   1.837 +
   1.838 +            // Move between grids if we're at the edge of one.
   1.839 +            if ((this._grid.isSelectionAtEnd && !aReverse) ||
   1.840 +                (this._grid.isSelectionAtStart && aReverse)) {
   1.841 +              let index = !aReverse ? 0 : this._otherGrid.itemCount - 1;
   1.842 +              this._otherGrid.selectedIndex = index;
   1.843 +            } else {
   1.844 +              this._grid.offsetSelection(aReverse ? -1 : 1);
   1.845 +            }
   1.846 +          ]]>
   1.847 +        </body>
   1.848 +      </method>
   1.849 +
   1.850 +      <method name="clearSelection">
   1.851 +        <body>
   1.852 +          <![CDATA[
   1.853 +            if (this._isGridBound(this._results))
   1.854 +              this._results.clearSelection();
   1.855 +
   1.856 +            if (this._isGridBound(this._searches))
   1.857 +              this._searches.clearSelection();
   1.858 +          ]]>
   1.859 +        </body>
   1.860 +      </method>
   1.861 +
   1.862 +      <!-- Submitting selected results -->
   1.863 +
   1.864 +      <method name="submitSelected">
   1.865 +        <body>
   1.866 +          <![CDATA[
   1.867 +            if (this._isGridBound(this._results) &&
   1.868 +                this._results.selectedIndex >= 0) {
   1.869 +              let url = this.input.controller.getValueAt(this._results.selectedIndex);
   1.870 +              this.input.value = url;
   1.871 +              return this.input.submitURL();
   1.872 +            }
   1.873 +
   1.874 +            if (this._isGridBound(this._searches) &&
   1.875 +                this._searches.selectedIndex >= 0) {
   1.876 +              let engine = this._engines[this._searches.selectedIndex];
   1.877 +              return this.input.submitSearch(engine.name);
   1.878 +            }
   1.879 +
   1.880 +            return false;
   1.881 +          ]]>
   1.882 +        </body>
   1.883 +      </method>
   1.884 +
   1.885 +      <method name="handleItemClick">
   1.886 +        <parameter name="aItem"/>
   1.887 +        <parameter name="aEvent"/>
   1.888 +        <body>
   1.889 +          <![CDATA[
   1.890 +            this.submitSelected();
   1.891 +          ]]>
   1.892 +        </body>
   1.893 +      </method>
   1.894 +
   1.895 +      <!-- nsIObserver -->
   1.896 +
   1.897 +      <method name="observe">
   1.898 +        <parameter name="aSubject"/>
   1.899 +        <parameter name="aTopic"/>
   1.900 +        <parameter name="aData"/>
   1.901 +        <body>
   1.902 +          <![CDATA[
   1.903 +            switch (aTopic) {
   1.904 +              case "browser-search-engine-modified":
   1.905 +                this.updateSearchEngineGrid();
   1.906 +                break;
   1.907 +            }
   1.908 +          ]]>
   1.909 +        </body>
   1.910 +      </method>
   1.911 +    </implementation>
   1.912 +
   1.913 +    <handlers>
   1.914 +      <handler event="contentgenerated">
   1.915 +        <![CDATA[
   1.916 +          let grid = event.originalTarget;
   1.917 +
   1.918 +          if (grid == this._searches)
   1.919 +            this._initSearchEngines();
   1.920 +
   1.921 +          if (grid == this._results)
   1.922 +            this.updateResults();
   1.923 +        ]]>
   1.924 +      </handler>
   1.925 +
   1.926 +      <handler event="select">
   1.927 +        <![CDATA[
   1.928 +          let grid = event.originalTarget;
   1.929 +
   1.930 +          // If a selection was made on a different grid,
   1.931 +          // remove selection from the current grid.
   1.932 +          if (grid != this._grid) {
   1.933 +            this._grid.clearSelection();
   1.934 +            this._grid = this._otherGrid;
   1.935 +          }
   1.936 +        ]]>
   1.937 +      </handler>
   1.938 +    </handlers>
   1.939 +  </binding>
   1.940 +</bindings>

mercurial