browser/components/search/content/search.xml

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/search/content/search.xml	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,841 @@
     1.4 +<?xml version="1.0"?>
     1.5 +# -*- Mode: HTML -*-
     1.6 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.9 +
    1.10 +<!DOCTYPE bindings [
    1.11 +<!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd" >
    1.12 +%searchBarDTD;
    1.13 +<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
    1.14 +%browserDTD;
    1.15 +]>
    1.16 +
    1.17 +<bindings id="SearchBindings"
    1.18 +          xmlns="http://www.mozilla.org/xbl"
    1.19 +          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    1.20 +          xmlns:xbl="http://www.mozilla.org/xbl">
    1.21 +
    1.22 +  <binding id="searchbar">
    1.23 +    <resources>
    1.24 +      <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
    1.25 +      <stylesheet src="chrome://browser/skin/searchbar.css"/>
    1.26 +    </resources>
    1.27 +    <content>
    1.28 +      <xul:stringbundle src="chrome://browser/locale/search.properties"
    1.29 +                        anonid="searchbar-stringbundle"/>
    1.30 +      <!--
    1.31 +      There is a dependency between "maxrows" attribute and
    1.32 +      "SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing
    1.33 +      one of them requires changing the other one.
    1.34 +      -->
    1.35 +      <xul:textbox class="searchbar-textbox"
    1.36 +                   anonid="searchbar-textbox"
    1.37 +                   type="autocomplete"
    1.38 +                   flex="1"
    1.39 +                   autocompletepopup="PopupAutoComplete"
    1.40 +                   autocompletesearch="search-autocomplete"
    1.41 +                   autocompletesearchparam="searchbar-history"
    1.42 +                   timeout="250"
    1.43 +                   maxrows="10"
    1.44 +                   completeselectedindex="true"
    1.45 +                   showcommentcolumn="true"
    1.46 +                   tabscrolling="true"
    1.47 +                   xbl:inherits="disabled,disableautocomplete,searchengine,src,newlines">
    1.48 +        <!--
    1.49 +        Empty <box> to properly position the icon within the autocomplete
    1.50 +        binding's anonymous children (the autocomplete binding positions <box>
    1.51 +        children differently)
    1.52 +        -->
    1.53 +        <xul:box>
    1.54 +          <xul:button class="searchbar-engine-button"
    1.55 +                      type="menu"
    1.56 +                      anonid="searchbar-engine-button">
    1.57 +            <xul:image class="searchbar-engine-image" xbl:inherits="src"/>
    1.58 +            <xul:image class="searchbar-dropmarker-image"/>
    1.59 +            <xul:menupopup class="searchbar-popup"
    1.60 +                           anonid="searchbar-popup">
    1.61 +              <xul:menuseparator/>
    1.62 +              <xul:menuitem class="open-engine-manager"
    1.63 +                            anonid="open-engine-manager"
    1.64 +                            label="&cmd_engineManager.label;"
    1.65 +                            oncommand="openManager(event);"/>
    1.66 +            </xul:menupopup>
    1.67 +          </xul:button>
    1.68 +        </xul:box>
    1.69 +        <xul:hbox class="search-go-container">
    1.70 +          <xul:image class="search-go-button"
    1.71 +                     anonid="search-go-button"
    1.72 +                     onclick="handleSearchCommand(event);"
    1.73 +                     tooltiptext="&searchEndCap.label;"/>
    1.74 +        </xul:hbox>
    1.75 +      </xul:textbox>
    1.76 +    </content>
    1.77 +
    1.78 +    <implementation implements="nsIObserver">
    1.79 +      <constructor><![CDATA[
    1.80 +        if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
    1.81 +          return;
    1.82 +        // Make sure we rebuild the popup in onpopupshowing
    1.83 +        this._needToBuildPopup = true;
    1.84 +
    1.85 +        var os =
    1.86 +               Components.classes["@mozilla.org/observer-service;1"]
    1.87 +                         .getService(Components.interfaces.nsIObserverService);
    1.88 +        os.addObserver(this, "browser-search-engine-modified", false);
    1.89 +
    1.90 +        this._initialized = true;
    1.91 +
    1.92 +        this.searchService.init((function search_init_cb(aStatus) {
    1.93 +          // Bail out if the binding's been destroyed
    1.94 +          if (!this._initialized)
    1.95 +            return;
    1.96 +
    1.97 +          if (Components.isSuccessCode(aStatus)) {
    1.98 +            // Refresh the display (updating icon, etc)
    1.99 +            this.updateDisplay();
   1.100 +          } else {
   1.101 +            Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
   1.102 +          }
   1.103 +        }).bind(this));
   1.104 +      ]]></constructor>
   1.105 +
   1.106 +      <destructor><![CDATA[
   1.107 +        if (this._initialized) {
   1.108 +          this._initialized = false;
   1.109 +
   1.110 +          var os = Components.classes["@mozilla.org/observer-service;1"]
   1.111 +                             .getService(Components.interfaces.nsIObserverService);
   1.112 +          os.removeObserver(this, "browser-search-engine-modified");
   1.113 +        }
   1.114 +
   1.115 +        // Make sure to break the cycle from _textbox to us. Otherwise we leak
   1.116 +        // the world. But make sure it's actually pointing to us.
   1.117 +        if (this._textbox.mController.input == this)
   1.118 +          this._textbox.mController.input = null;
   1.119 +      ]]></destructor>
   1.120 +
   1.121 +      <field name="_stringBundle">document.getAnonymousElementByAttribute(this,
   1.122 +          "anonid", "searchbar-stringbundle");</field>
   1.123 +      <field name="_textbox">document.getAnonymousElementByAttribute(this,
   1.124 +          "anonid", "searchbar-textbox");</field>
   1.125 +      <field name="_popup">document.getAnonymousElementByAttribute(this,
   1.126 +          "anonid", "searchbar-popup");</field>
   1.127 +      <field name="_ss">null</field>
   1.128 +      <field name="_engines">null</field>
   1.129 +      <field name="FormHistory" readonly="true">
   1.130 +        (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
   1.131 +      </field>
   1.132 +
   1.133 +      <property name="engines" readonly="true">
   1.134 +        <getter><![CDATA[
   1.135 +          if (!this._engines)
   1.136 +            this._engines = this.searchService.getVisibleEngines();
   1.137 +          return this._engines;
   1.138 +        ]]></getter>
   1.139 +      </property>
   1.140 +
   1.141 +      <field name="searchButton">document.getAnonymousElementByAttribute(this,
   1.142 +          "anonid", "searchbar-engine-button");</field>
   1.143 +
   1.144 +      <property name="currentEngine">
   1.145 +        <setter><![CDATA[
   1.146 +          let ss = this.searchService;
   1.147 +          ss.defaultEngine = ss.currentEngine = val;
   1.148 +          return val;
   1.149 +        ]]></setter>
   1.150 +        <getter><![CDATA[
   1.151 +          var currentEngine = this.searchService.currentEngine;
   1.152 +          // Return a dummy engine if there is no currentEngine
   1.153 +          return currentEngine || {name: "", uri: null};
   1.154 +        ]]></getter>
   1.155 +      </property>
   1.156 +
   1.157 +      <!-- textbox is used by sanitize.js to clear the undo history when
   1.158 +           clearing form information. -->
   1.159 +      <property name="textbox" readonly="true"
   1.160 +                onget="return this._textbox;"/>
   1.161 +
   1.162 +      <property name="searchService" readonly="true">
   1.163 +        <getter><![CDATA[
   1.164 +          if (!this._ss) {
   1.165 +            const nsIBSS = Components.interfaces.nsIBrowserSearchService;
   1.166 +            this._ss =
   1.167 +                 Components.classes["@mozilla.org/browser/search-service;1"]
   1.168 +                           .getService(nsIBSS);
   1.169 +          }
   1.170 +          return this._ss;
   1.171 +        ]]></getter>
   1.172 +      </property>
   1.173 +
   1.174 +      <property name="value" onget="return this._textbox.value;"
   1.175 +                             onset="return this._textbox.value = val;"/>
   1.176 +
   1.177 +      <method name="focus">
   1.178 +        <body><![CDATA[
   1.179 +          this._textbox.focus();
   1.180 +        ]]></body>
   1.181 +      </method>
   1.182 +
   1.183 +      <method name="select">
   1.184 +        <body><![CDATA[
   1.185 +          this._textbox.select();
   1.186 +        ]]></body>
   1.187 +      </method>
   1.188 +
   1.189 +      <method name="observe">
   1.190 +        <parameter name="aEngine"/>
   1.191 +        <parameter name="aTopic"/>
   1.192 +        <parameter name="aVerb"/>
   1.193 +        <body><![CDATA[
   1.194 +          if (aTopic == "browser-search-engine-modified") {
   1.195 +            switch (aVerb) {
   1.196 +            case "engine-removed":
   1.197 +              this.offerNewEngine(aEngine);
   1.198 +              break;
   1.199 +            case "engine-added":
   1.200 +              this.hideNewEngine(aEngine);
   1.201 +              break;
   1.202 +            case "engine-current":
   1.203 +              // The current engine was changed.  Rebuilding the menu appears to
   1.204 +              // confuse its idea of whether it should be open when it's just
   1.205 +              // been clicked, so we force it to close now.
   1.206 +              this._popup.hidePopup();
   1.207 +              break;
   1.208 +            case "engine-changed":
   1.209 +              // An engine was removed (or hidden) or added, or an icon was
   1.210 +              // changed.  Do nothing special.
   1.211 +            }
   1.212 +
   1.213 +            // Make sure the engine list is refetched next time it's needed
   1.214 +            this._engines = null;
   1.215 +
   1.216 +            // Rebuild the popup and update the display after any modification.
   1.217 +            this.rebuildPopup();
   1.218 +            this.updateDisplay();
   1.219 +          }
   1.220 +        ]]></body>
   1.221 +      </method>
   1.222 +
   1.223 +      <!-- There are two seaprate lists of search engines, whose uses intersect
   1.224 +      in this file.  The search service (nsIBrowserSearchService and
   1.225 +      nsSearchService.js) maintains a list of Engine objects which is used to
   1.226 +      populate the searchbox list of available engines and to perform queries.
   1.227 +      That list is accessed here via this.SearchService, and it's that sort of
   1.228 +      Engine that is passed to this binding's observer as aEngine.
   1.229 +
   1.230 +      In addition, browser.js fills two lists of autodetected search engines
   1.231 +      (browser.engines and browser.hiddenEngines) as properties of
   1.232 +      mCurrentBrowser.  Those lists contain unnamed JS objects of the form
   1.233 +      { uri:, title:, icon: }, and that's what the searchbar uses to determine
   1.234 +      whether to show any "Add <EngineName>" menu items in the drop-down.
   1.235 +
   1.236 +      The two types of engines are currently related by their identifying
   1.237 +      titles (the Engine object's 'name'), although that may change; see bug
   1.238 +      335102.  -->
   1.239 +
   1.240 +      <!-- If the engine that was just removed from the searchbox list was
   1.241 +      autodetected on this page, move it to each browser's active list so it
   1.242 +      will be offered to be added again. -->
   1.243 +      <method name="offerNewEngine">
   1.244 +        <parameter name="aEngine"/>
   1.245 +        <body><![CDATA[
   1.246 +          var allbrowsers = getBrowser().mPanelContainer.childNodes;
   1.247 +          for (var tab = 0; tab < allbrowsers.length; tab++) {
   1.248 +            var browser = getBrowser().getBrowserAtIndex(tab);
   1.249 +            if (browser.hiddenEngines) {
   1.250 +              // XXX This will need to be changed when engines are identified by
   1.251 +              // URL rather than title; see bug 335102.
   1.252 +              var removeTitle = aEngine.wrappedJSObject.name;
   1.253 +              for (var i = 0; i < browser.hiddenEngines.length; i++) {
   1.254 +                if (browser.hiddenEngines[i].title == removeTitle) {
   1.255 +                  if (!browser.engines)
   1.256 +                    browser.engines = [];
   1.257 +                  browser.engines.push(browser.hiddenEngines[i]);
   1.258 +                  browser.hiddenEngines.splice(i, 1);
   1.259 +                  break;
   1.260 +                }
   1.261 +              }
   1.262 +            }
   1.263 +          }
   1.264 +        ]]></body>
   1.265 +      </method>
   1.266 +
   1.267 +      <!-- If the engine that was just added to the searchbox list was
   1.268 +      autodetected on this page, move it to each browser's hidden list so it is
   1.269 +      no longer offered to be added. -->
   1.270 +      <method name="hideNewEngine">
   1.271 +        <parameter name="aEngine"/>
   1.272 +        <body><![CDATA[
   1.273 +          var allbrowsers = getBrowser().mPanelContainer.childNodes;
   1.274 +          for (var tab = 0; tab < allbrowsers.length; tab++) {
   1.275 +            var browser = getBrowser().getBrowserAtIndex(tab);
   1.276 +            if (browser.engines) {
   1.277 +              // XXX This will need to be changed when engines are identified by
   1.278 +              // URL rather than title; see bug 335102.
   1.279 +              var removeTitle = aEngine.wrappedJSObject.name;
   1.280 +              for (var i = 0; i < browser.engines.length; i++) {
   1.281 +                if (browser.engines[i].title == removeTitle) {
   1.282 +                  if (!browser.hiddenEngines)
   1.283 +                    browser.hiddenEngines = [];
   1.284 +                  browser.hiddenEngines.push(browser.engines[i]);
   1.285 +                  browser.engines.splice(i, 1);
   1.286 +                  break;
   1.287 +                }
   1.288 +              }
   1.289 +            }
   1.290 +          }
   1.291 +        ]]></body>
   1.292 +      </method>
   1.293 +
   1.294 +      <method name="setIcon">
   1.295 +        <parameter name="element"/>
   1.296 +        <parameter name="uri"/>
   1.297 +        <body><![CDATA[
   1.298 +          if (uri) {
   1.299 +            let size = Math.round(16 * window.devicePixelRatio);
   1.300 +            if (!uri.contains("#"))
   1.301 +              uri += "#-moz-resolution=" + size + "," + size;
   1.302 +          }
   1.303 +          element.setAttribute("src", uri);
   1.304 +        ]]></body>
   1.305 +      </method>
   1.306 +
   1.307 +      <method name="updateDisplay">
   1.308 +        <body><![CDATA[
   1.309 +          var uri = this.currentEngine.iconURI;
   1.310 +          this.setIcon(this, uri ? uri.spec : "");
   1.311 +
   1.312 +          var name = this.currentEngine.name;
   1.313 +          var text = this._stringBundle.getFormattedString("searchtip", [name]);
   1.314 +          this._textbox.placeholder = name;
   1.315 +          this._textbox.label = text;
   1.316 +          this._textbox.tooltipText = text;
   1.317 +        ]]></body>
   1.318 +      </method>
   1.319 +
   1.320 +      <!-- Rebuilds the dynamic portion of the popup menu (i.e., the menu items
   1.321 +           for new search engines that can be added to the available list).  This
   1.322 +           is called each time the popup is shown.
   1.323 +      -->
   1.324 +      <method name="rebuildPopupDynamic">
   1.325 +        <body><![CDATA[
   1.326 +          // We might not have added the main popup items yet, do that first
   1.327 +          // if needed.
   1.328 +          if (this._needToBuildPopup)
   1.329 +            this.rebuildPopup();
   1.330 +
   1.331 +          var popup = this._popup;
   1.332 +          // Clear any addengine menuitems, including addengine-item entries and
   1.333 +          // the addengine-separator.  Work backward to avoid invalidating the
   1.334 +          // indexes as items are removed.
   1.335 +          var items = popup.childNodes;
   1.336 +          for (var i = items.length - 1; i >= 0; i--) {
   1.337 +            if (items[i].classList.contains("addengine-item") ||
   1.338 +                items[i].classList.contains("addengine-separator"))
   1.339 +              popup.removeChild(items[i]);
   1.340 +          }
   1.341 +
   1.342 +          var addengines = getBrowser().mCurrentBrowser.engines;
   1.343 +          if (addengines && addengines.length > 0) {
   1.344 +            const kXULNS =
   1.345 +               "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   1.346 +
   1.347 +            // Find the (first) separator in the remaining menu, or the first item
   1.348 +            // if no separators are present.
   1.349 +            var insertLocation = popup.firstChild;
   1.350 +            while (insertLocation.nextSibling &&
   1.351 +                   insertLocation.localName != "menuseparator") {
   1.352 +              insertLocation = insertLocation.nextSibling;
   1.353 +            }
   1.354 +            if (insertLocation.localName != "menuseparator")
   1.355 +              insertLocation = popup.firstChild;
   1.356 +
   1.357 +            var separator = document.createElementNS(kXULNS, "menuseparator");
   1.358 +            separator.setAttribute("class", "addengine-separator");
   1.359 +            popup.insertBefore(separator, insertLocation);
   1.360 +
   1.361 +            // Insert the "add this engine" items.
   1.362 +            for (var i = 0; i < addengines.length; i++) {
   1.363 +              var menuitem = document.createElement("menuitem");
   1.364 +              var engineInfo = addengines[i];
   1.365 +              var labelStr =
   1.366 +                  this._stringBundle.getFormattedString("cmd_addFoundEngine",
   1.367 +                                                        [engineInfo.title]);
   1.368 +              menuitem = document.createElementNS(kXULNS, "menuitem");
   1.369 +              menuitem.setAttribute("class", "menuitem-iconic addengine-item");
   1.370 +              menuitem.setAttribute("label", labelStr);
   1.371 +              menuitem.setAttribute("tooltiptext", engineInfo.uri);
   1.372 +              menuitem.setAttribute("uri", engineInfo.uri);
   1.373 +              if (engineInfo.icon)
   1.374 +                this.setIcon(menuitem, engineInfo.icon);
   1.375 +              menuitem.setAttribute("title", engineInfo.title);
   1.376 +              popup.insertBefore(menuitem, insertLocation);
   1.377 +            }
   1.378 +          }
   1.379 +        ]]></body>
   1.380 +      </method>
   1.381 +
   1.382 +      <!-- Rebuilds the list of visible search engines in the menu.  Does not remove
   1.383 +           or update any dynamic entries (i.e., "Add this engine" items) nor the
   1.384 +           Manage Engines item.  This is called by the observer when the list of
   1.385 +           visible engines, or the currently selected engine, has changed.
   1.386 +      -->
   1.387 +      <method name="rebuildPopup">
   1.388 +        <body><![CDATA[
   1.389 +          var popup = this._popup;
   1.390 +
   1.391 +          // Clear the popup, down to the first separator
   1.392 +          while (popup.firstChild && popup.firstChild.localName != "menuseparator")
   1.393 +            popup.removeChild(popup.firstChild);
   1.394 +
   1.395 +          const kXULNS =
   1.396 +               "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   1.397 +
   1.398 +          var engines = this.engines;
   1.399 +          for (var i = engines.length - 1; i >= 0; --i) {
   1.400 +            var menuitem = document.createElementNS(kXULNS, "menuitem");
   1.401 +            var name = engines[i].name;
   1.402 +            menuitem.setAttribute("label", name);
   1.403 +            menuitem.setAttribute("id", name);
   1.404 +            menuitem.setAttribute("class", "menuitem-iconic searchbar-engine-menuitem menuitem-with-favicon");
   1.405 +            // Since this menu is rebuilt by the observer method whenever a new
   1.406 +            // engine is selected, the "selected" attribute does not need to be
   1.407 +            // explicitly cleared anywhere.
   1.408 +            if (engines[i] == this.currentEngine)
   1.409 +              menuitem.setAttribute("selected", "true");
   1.410 +            var tooltip = this._stringBundle.getFormattedString("searchtip", [name]);
   1.411 +            menuitem.setAttribute("tooltiptext", tooltip);
   1.412 +            if (engines[i].iconURI)
   1.413 +              this.setIcon(menuitem, engines[i].iconURI.spec);
   1.414 +            popup.insertBefore(menuitem, popup.firstChild);
   1.415 +            menuitem.engine = engines[i];
   1.416 +          }
   1.417 +
   1.418 +          this._needToBuildPopup = false;
   1.419 +        ]]></body>
   1.420 +      </method>
   1.421 +
   1.422 +      <method name="openManager">
   1.423 +        <parameter name="aEvent"/>
   1.424 +        <body><![CDATA[
   1.425 +          var wm =
   1.426 +                Components.classes["@mozilla.org/appshell/window-mediator;1"]
   1.427 +                          .getService(Components.interfaces.nsIWindowMediator);
   1.428 +
   1.429 +          var window = wm.getMostRecentWindow("Browser:SearchManager");
   1.430 +          if (window)
   1.431 +            window.focus()
   1.432 +          else {
   1.433 +            setTimeout(function () {
   1.434 +              openDialog("chrome://browser/content/search/engineManager.xul",
   1.435 +                         "_blank", "chrome,dialog,modal,centerscreen,resizable");
   1.436 +            }, 0);
   1.437 +          }
   1.438 +        ]]></body>
   1.439 +      </method>
   1.440 +
   1.441 +      <method name="selectEngine">
   1.442 +        <parameter name="aEvent"/>
   1.443 +        <parameter name="isNextEngine"/>
   1.444 +        <body><![CDATA[
   1.445 +          // Find the new index
   1.446 +          var newIndex = this.engines.indexOf(this.currentEngine);
   1.447 +          newIndex += isNextEngine ? 1 : -1;
   1.448 +
   1.449 +          if (newIndex >= 0 && newIndex < this.engines.length) {
   1.450 +            this.currentEngine = this.engines[newIndex];
   1.451 +          }
   1.452 +
   1.453 +          aEvent.preventDefault();
   1.454 +          aEvent.stopPropagation();
   1.455 +        ]]></body>
   1.456 +      </method>
   1.457 +
   1.458 +      <method name="handleSearchCommand">
   1.459 +        <parameter name="aEvent"/>
   1.460 +        <body><![CDATA[
   1.461 +          var textBox = this._textbox;
   1.462 +          var textValue = textBox.value;
   1.463 +
   1.464 +          var where = "current";
   1.465 +          if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") {
   1.466 +            if (aEvent.button == 2)
   1.467 +              return;
   1.468 +            where = whereToOpenLink(aEvent, false, true);
   1.469 +          }
   1.470 +          else {
   1.471 +            var newTabPref = textBox._prefBranch.getBoolPref("browser.search.openintab");
   1.472 +            if ((aEvent && aEvent.altKey) ^ newTabPref)
   1.473 +              where = "tab";
   1.474 +          }
   1.475 +
   1.476 +          this.doSearch(textValue, where);
   1.477 +        ]]></body>
   1.478 +      </method>
   1.479 +
   1.480 +      <method name="doSearch">
   1.481 +        <parameter name="aData"/>
   1.482 +        <parameter name="aWhere"/>
   1.483 +        <body><![CDATA[
   1.484 +          var textBox = this._textbox;
   1.485 +        
   1.486 +          // Save the current value in the form history
   1.487 +          if (aData && !PrivateBrowsingUtils.isWindowPrivate(window)) {
   1.488 +            this.FormHistory.update(
   1.489 +              { op : "bump",
   1.490 +                fieldname : textBox.getAttribute("autocompletesearchparam"),
   1.491 +                value : aData },
   1.492 +              { handleError : function(aError) {
   1.493 +                  Components.utils.reportError("Saving search to form history failed: " + aError.message);
   1.494 +              }});
   1.495 +          }
   1.496 +          
   1.497 +          // null parameter below specifies HTML response for search
   1.498 +          var submission = this.currentEngine.getSubmission(aData, null, "searchbar");
   1.499 +          BrowserSearch.recordSearchInHealthReport(this.currentEngine, "searchbar");
   1.500 +          openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
   1.501 +        ]]></body>
   1.502 +      </method>
   1.503 +    </implementation>
   1.504 +
   1.505 +    <handlers>
   1.506 +      <handler event="command"><![CDATA[
   1.507 +        const target = event.originalTarget;
   1.508 +        if (target.engine) {
   1.509 +          this.currentEngine = target.engine;
   1.510 +        } else if (target.classList.contains("addengine-item")) {
   1.511 +          var searchService =
   1.512 +            Components.classes["@mozilla.org/browser/search-service;1"]
   1.513 +                      .getService(Components.interfaces.nsIBrowserSearchService);
   1.514 +          // We only detect OpenSearch files
   1.515 +          var type = Components.interfaces.nsISearchEngine.DATA_XML;
   1.516 +          // Select the installed engine if the installation succeeds
   1.517 +          var installCallback = {
   1.518 +            onSuccess: engine => this.currentEngine = engine
   1.519 +          }
   1.520 +          searchService.addEngine(target.getAttribute("uri"), type,
   1.521 +                                  target.getAttribute("src"), false,
   1.522 +                                  installCallback);
   1.523 +        }
   1.524 +        else
   1.525 +          return;
   1.526 +
   1.527 +        this.focus();
   1.528 +        this.select();
   1.529 +      ]]></handler>
   1.530 +
   1.531 +      <handler event="popupshowing" action="this.rebuildPopupDynamic();"/>
   1.532 +
   1.533 +      <handler event="DOMMouseScroll"
   1.534 +               phase="capturing"
   1.535 +               modifiers="accel"
   1.536 +               action="this.selectEngine(event, (event.detail > 0));"/>
   1.537 +
   1.538 +      <handler event="focus">
   1.539 +      <![CDATA[
   1.540 +        // Speculatively connect to the current engine's search URI (and
   1.541 +        // suggest URI, if different) to reduce request latency
   1.542 +
   1.543 +        const SUGGEST_TYPE = "application/x-suggestions+json";
   1.544 +        var engine = this.currentEngine;
   1.545 +        var connector =
   1.546 +            Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect);
   1.547 +        var searchURI = engine.getSubmission("dummy", null, "searchbar").uri;
   1.548 +        let callbacks = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
   1.549 +                              .getInterface(Components.interfaces.nsIWebNavigation)
   1.550 +                              .QueryInterface(Components.interfaces.nsILoadContext);
   1.551 +        connector.speculativeConnect(searchURI, callbacks);
   1.552 +
   1.553 +        if (engine.supportsResponseType(SUGGEST_TYPE)) {
   1.554 +          var suggestURI = engine.getSubmission("dummy", SUGGEST_TYPE, "searchbar").uri;
   1.555 +          if (suggestURI.prePath != searchURI.prePath)
   1.556 +            connector.speculativeConnect(suggestURI, callbacks);
   1.557 +        }
   1.558 +      ]]></handler>
   1.559 +    </handlers>
   1.560 +  </binding>
   1.561 +
   1.562 +  <binding id="searchbar-textbox"
   1.563 +      extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
   1.564 +    <implementation implements="nsIObserver">
   1.565 +      <constructor><![CDATA[
   1.566 +        const kXULNS =
   1.567 +          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   1.568 +
   1.569 +        if (document.getBindingParent(this).parentNode.parentNode.localName ==
   1.570 +            "toolbarpaletteitem")
   1.571 +          return;
   1.572 +
   1.573 +        // Initialize fields
   1.574 +        this._stringBundle = document.getBindingParent(this)._stringBundle;
   1.575 +        this._prefBranch =
   1.576 +                  Components.classes["@mozilla.org/preferences-service;1"]
   1.577 +                            .getService(Components.interfaces.nsIPrefBranch);
   1.578 +        this._suggestEnabled =
   1.579 +            this._prefBranch.getBoolPref("browser.search.suggest.enabled");
   1.580 +
   1.581 +        if (this._prefBranch.getBoolPref("browser.urlbar.clickSelectsAll"))
   1.582 +          this.setAttribute("clickSelectsAll", true);
   1.583 +
   1.584 +        // Add items to context menu and attach controller to handle them
   1.585 +        var textBox = document.getAnonymousElementByAttribute(this,
   1.586 +                                              "anonid", "textbox-input-box");
   1.587 +        var cxmenu = document.getAnonymousElementByAttribute(textBox,
   1.588 +                                          "anonid", "input-box-contextmenu");
   1.589 +        var pasteAndSearch;
   1.590 +        cxmenu.addEventListener("popupshowing", function() {
   1.591 +          if (!pasteAndSearch)
   1.592 +            return;
   1.593 +          var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
   1.594 +          var enabled = controller.isCommandEnabled("cmd_paste");
   1.595 +          if (enabled)
   1.596 +            pasteAndSearch.removeAttribute("disabled");
   1.597 +          else
   1.598 +            pasteAndSearch.setAttribute("disabled", "true");
   1.599 +        }, false);
   1.600 +
   1.601 +        var element, label, akey;
   1.602 +
   1.603 +        element = document.createElementNS(kXULNS, "menuseparator");
   1.604 +        cxmenu.appendChild(element);
   1.605 +
   1.606 +        var insertLocation = cxmenu.firstChild;
   1.607 +        while (insertLocation.nextSibling &&
   1.608 +               insertLocation.getAttribute("cmd") != "cmd_paste")
   1.609 +          insertLocation = insertLocation.nextSibling;
   1.610 +        if (insertLocation) {
   1.611 +          element = document.createElementNS(kXULNS, "menuitem");
   1.612 +          label = this._stringBundle.getString("cmd_pasteAndSearch");
   1.613 +          element.setAttribute("label", label);
   1.614 +          element.setAttribute("anonid", "paste-and-search");
   1.615 +          element.setAttribute("oncommand",
   1.616 +              "BrowserSearch.searchBar.select(); goDoCommand('cmd_paste'); BrowserSearch.searchBar.handleSearchCommand();");
   1.617 +          cxmenu.insertBefore(element, insertLocation.nextSibling);
   1.618 +          pasteAndSearch = element;
   1.619 +        }
   1.620 +
   1.621 +        element = document.createElementNS(kXULNS, "menuitem");
   1.622 +        label = this._stringBundle.getString("cmd_clearHistory");
   1.623 +        akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
   1.624 +        element.setAttribute("label", label);
   1.625 +        element.setAttribute("accesskey", akey);
   1.626 +        element.setAttribute("cmd", "cmd_clearhistory");
   1.627 +        cxmenu.appendChild(element);
   1.628 +
   1.629 +        element = document.createElementNS(kXULNS, "menuitem");
   1.630 +        label = this._stringBundle.getString("cmd_showSuggestions");
   1.631 +        akey = this._stringBundle.getString("cmd_showSuggestions_accesskey");
   1.632 +        element.setAttribute("anonid", "toggle-suggest-item");
   1.633 +        element.setAttribute("label", label);
   1.634 +        element.setAttribute("accesskey", akey);
   1.635 +        element.setAttribute("cmd", "cmd_togglesuggest");
   1.636 +        element.setAttribute("type", "checkbox");
   1.637 +        element.setAttribute("checked", this._suggestEnabled);
   1.638 +        element.setAttribute("autocheck", "false");
   1.639 +        this._suggestMenuItem = element;
   1.640 +        cxmenu.appendChild(element);
   1.641 +
   1.642 +        this.controllers.appendController(this.searchbarController);
   1.643 +
   1.644 +        // Add observer for suggest preference
   1.645 +        var prefs = Components.classes["@mozilla.org/preferences-service;1"]
   1.646 +                            .getService(Components.interfaces.nsIPrefBranch);
   1.647 +        prefs.addObserver("browser.search.suggest.enabled", this, false);
   1.648 +      ]]></constructor>
   1.649 +
   1.650 +      <destructor><![CDATA[
   1.651 +          var prefs = Components.classes["@mozilla.org/preferences-service;1"]
   1.652 +                              .getService(Components.interfaces.nsIPrefBranch);
   1.653 +          prefs.removeObserver("browser.search.suggest.enabled", this);
   1.654 +
   1.655 +        // Because XBL and the customize toolbar code interacts poorly,
   1.656 +        // there may not be anything to remove here
   1.657 +        try {
   1.658 +          this.controllers.removeController(this.searchbarController);
   1.659 +        } catch (ex) { }
   1.660 +      ]]></destructor>
   1.661 +
   1.662 +      <field name="_stringBundle"/>
   1.663 +      <field name="_prefBranch"/>
   1.664 +      <field name="_suggestMenuItem"/>
   1.665 +      <field name="_suggestEnabled"/>
   1.666 +
   1.667 +      <!--
   1.668 +        This overrides the searchParam property in autocomplete.xml.  We're
   1.669 +        hijacking this property as a vehicle for delivering the privacy
   1.670 +        information about the window into the guts of nsSearchSuggestions.
   1.671 +
   1.672 +        Note that the setter is the same as the parent.  We were not sure whether
   1.673 +        we can override just the getter.  If that proves to be the case, the setter
   1.674 +        can be removed.
   1.675 +      -->
   1.676 +      <property name="searchParam"
   1.677 +                onget="return this.getAttribute('autocompletesearchparam') +
   1.678 +                       (PrivateBrowsingUtils.isWindowPrivate(window) ? '|private' : '');"
   1.679 +                onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
   1.680 +
   1.681 +      <!--
   1.682 +        This method overrides the autocomplete binding's openPopup (essentially
   1.683 +        duplicating the logic from the autocomplete popup binding's
   1.684 +        openAutocompletePopup method), modifying it so that the popup is aligned with
   1.685 +        the inner textbox, but sized to not extend beyond the search bar border.
   1.686 +      -->
   1.687 +      <method name="openPopup">
   1.688 +        <body><![CDATA[
   1.689 +          var popup = this.popup;
   1.690 +          if (!popup.mPopupOpen) {
   1.691 +            // Initially the panel used for the searchbar (PopupAutoComplete
   1.692 +            // in browser.xul) is hidden to avoid impacting startup / new
   1.693 +            // window performance. The base binding's openPopup would normally
   1.694 +            // call the overriden openAutocompletePopup in urlbarBindings.xml's
   1.695 +            // browser-autocomplete-result-popup binding to unhide the popup,
   1.696 +            // but since we're overriding openPopup we need to unhide the panel
   1.697 +            // ourselves.
   1.698 +            popup.hidden = false;
   1.699 +
   1.700 +            popup.mInput = this;
   1.701 +            popup.view = this.controller.QueryInterface(Components.interfaces.nsITreeView);
   1.702 +            popup.invalidate();
   1.703 +
   1.704 +            popup.showCommentColumn = this.showCommentColumn;
   1.705 +            popup.showImageColumn = this.showImageColumn;
   1.706 +
   1.707 +            document.popupNode = null;
   1.708 +
   1.709 +            const isRTL = getComputedStyle(this, "").direction == "rtl";
   1.710 +
   1.711 +            var outerRect = this.getBoundingClientRect();
   1.712 +            var innerRect = this.inputField.getBoundingClientRect();
   1.713 +            if (isRTL) {
   1.714 +              var width = innerRect.right - outerRect.left;
   1.715 +            } else {
   1.716 +              var width = outerRect.right - innerRect.left;
   1.717 +            }
   1.718 +            popup.setAttribute("width", width > 100 ? width : 100);
   1.719 +
   1.720 +            var yOffset = outerRect.bottom - innerRect.bottom;
   1.721 +            popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false);
   1.722 +          }
   1.723 +        ]]></body>
   1.724 +      </method>
   1.725 +
   1.726 +      <method name="observe">
   1.727 +        <parameter name="aSubject"/>
   1.728 +        <parameter name="aTopic"/>
   1.729 +        <parameter name="aData"/>
   1.730 +        <body><![CDATA[
   1.731 +          if (aTopic == "nsPref:changed") {
   1.732 +            this._suggestEnabled =
   1.733 +              this._prefBranch.getBoolPref("browser.search.suggest.enabled");
   1.734 +            this._suggestMenuItem.setAttribute("checked", this._suggestEnabled);
   1.735 +          }
   1.736 +        ]]></body>
   1.737 +      </method>
   1.738 +
   1.739 +      <method name="openSearch">
   1.740 +        <body>
   1.741 +          <![CDATA[
   1.742 +            // Don't open search popup if history popup is open
   1.743 +            if (!this.popupOpen) {
   1.744 +              document.getBindingParent(this).searchButton.open = true;
   1.745 +              return false;
   1.746 +            }
   1.747 +            return true;
   1.748 +          ]]>
   1.749 +        </body>
   1.750 +      </method>
   1.751 +
   1.752 +      <!-- override |onTextEntered| in autocomplete.xml -->
   1.753 +      <method name="onTextEntered">
   1.754 +        <parameter name="aEvent"/>
   1.755 +        <body><![CDATA[
   1.756 +          var evt = aEvent || this.mEnterEvent;
   1.757 +          document.getBindingParent(this).handleSearchCommand(evt);
   1.758 +          this.mEnterEvent = null;
   1.759 +        ]]></body>
   1.760 +      </method>
   1.761 +
   1.762 +      <!-- nsIController -->
   1.763 +      <field name="searchbarController" readonly="true"><![CDATA[({
   1.764 +        _self: this,
   1.765 +        supportsCommand: function(aCommand) {
   1.766 +          return aCommand == "cmd_clearhistory" ||
   1.767 +                 aCommand == "cmd_togglesuggest";
   1.768 +        },
   1.769 +
   1.770 +        isCommandEnabled: function(aCommand) {
   1.771 +          return true;
   1.772 +        },
   1.773 +
   1.774 +        doCommand: function (aCommand) {
   1.775 +          switch (aCommand) {
   1.776 +            case "cmd_clearhistory":
   1.777 +              var param = this._self.getAttribute("autocompletesearchparam");
   1.778 +
   1.779 +              let searchBar = this._self.parentNode;
   1.780 +
   1.781 +              BrowserSearch.searchBar.FormHistory.update({ op : "remove", fieldname : param }, null);
   1.782 +              this._self.value = "";
   1.783 +              break;
   1.784 +            case "cmd_togglesuggest":
   1.785 +              // The pref observer will update _suggestEnabled and the menu
   1.786 +              // checkmark.
   1.787 +              this._self._prefBranch.setBoolPref("browser.search.suggest.enabled",
   1.788 +                                                 !this._self._suggestEnabled);
   1.789 +              break;
   1.790 +            default:
   1.791 +              // do nothing with unrecognized command
   1.792 +          }
   1.793 +        }
   1.794 +      })]]></field>
   1.795 +    </implementation>
   1.796 +
   1.797 +    <handlers>
   1.798 +      <handler event="keypress" keycode="VK_UP" modifiers="accel"
   1.799 +               phase="capturing"
   1.800 +               action="document.getBindingParent(this).selectEngine(event, false);"/>
   1.801 +
   1.802 +      <handler event="keypress" keycode="VK_DOWN" modifiers="accel"
   1.803 +               phase="capturing"
   1.804 +               action="document.getBindingParent(this).selectEngine(event, true);"/>
   1.805 +
   1.806 +      <handler event="keypress" keycode="VK_DOWN" modifiers="alt"
   1.807 +               phase="capturing"
   1.808 +               action="return this.openSearch();"/>
   1.809 +
   1.810 +      <handler event="keypress" keycode="VK_UP" modifiers="alt"
   1.811 +               phase="capturing"
   1.812 +               action="return this.openSearch();"/>
   1.813 +
   1.814 +#ifndef XP_MACOSX
   1.815 +      <handler event="keypress" keycode="VK_F4"
   1.816 +               phase="capturing"
   1.817 +               action="return this.openSearch();"/>
   1.818 +#endif
   1.819 +
   1.820 +      <handler event="dragover">
   1.821 +      <![CDATA[
   1.822 +        var types = event.dataTransfer.types;
   1.823 +        if (types.contains("text/plain") || types.contains("text/x-moz-text-internal"))
   1.824 +          event.preventDefault();
   1.825 +      ]]>
   1.826 +      </handler>
   1.827 +
   1.828 +      <handler event="drop">
   1.829 +      <![CDATA[
   1.830 +        var dataTransfer = event.dataTransfer;
   1.831 +        var data = dataTransfer.getData("text/plain");
   1.832 +        if (!data)
   1.833 +          data = dataTransfer.getData("text/x-moz-text-internal");
   1.834 +        if (data) {
   1.835 +          event.preventDefault();
   1.836 +          this.value = data;
   1.837 +          this.onTextEntered(event);
   1.838 +        }
   1.839 +      ]]>
   1.840 +      </handler>
   1.841 +
   1.842 +    </handlers>
   1.843 +  </binding>
   1.844 +</bindings>

mercurial