<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE bindings [
<!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
%findBarDTD;
]>

<bindings id="findbarBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <!-- Private binding -->
  <binding id="findbar-textbox"
           extends="chrome://global/content/bindings/textbox.xml#textbox">
    <implementation>

      <field name="_findbar">null</field>
      <property name="findbar" readonly="true">
        <getter>
          return this._findbar ?
                 this._findbar : this._findbar = document.getBindingParent(this);
        </getter>
      </property>

      <method name="_handleEnter">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (this.findbar._findMode == this.findbar.FIND_NORMAL) {
            let findString = this.findbar._findField;
            if (!findString.value)
              return;
            if (aEvent.getModifierState("Accel")) {
              this.findbar.getElement("highlight").click();
              return;
            }

            this.findbar.onFindAgainCommand(aEvent.shiftKey);
          } else {
            this.findbar._finishFAYT(aEvent);
          }
        ]]></body>
      </method>

      <method name="_handleTab">
        <parameter name="aEvent"/>
        <body><![CDATA[
          let shouldHandle = !aEvent.altKey && !aEvent.ctrlKey &&
                             !aEvent.metaKey;
          if (shouldHandle &&
              this.findbar._findMode != this.findbar.FIND_NORMAL) {

            this.findbar._finishFAYT(aEvent);
          }
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="input"><![CDATA[
        // We should do nothing during composition.  E.g., composing string
        // before converting may matches a forward word of expected word.
        // After that, even if user converts the composition string to the
        // expected word, it may find second or later searching word in the
        // document.
        if (this.findbar._isIMEComposing) {
          return;
        }

        if (this._hadValue && !this.value) {
          this._willfullyDeleted = true;
          this._hadValue = false;
        } else if (this.value.trim()) {
          this._hadValue = true;
          this._willfullyDeleted = false;
        }
        this.findbar._find(this.value);
      ]]></handler>

      <handler event="keypress"><![CDATA[
        let shouldHandle = !event.altKey && !event.ctrlKey &&
                           !event.metaKey && !event.shiftKey;

        switch (event.keyCode) {
          case KeyEvent.DOM_VK_RETURN:
            this._handleEnter(event);
            break;
          case KeyEvent.DOM_VK_TAB:
            this._handleTab(event);
            break;
          case KeyEvent.DOM_VK_PAGE_UP:
          case KeyEvent.DOM_VK_PAGE_DOWN:
            if (shouldHandle) {
              this.findbar.browser.finder.keyPress(event);
              event.preventDefault();
            }
            break;
          case KeyEvent.DOM_VK_UP:
          case KeyEvent.DOM_VK_DOWN:
            this.findbar.browser.finder.keyPress(event);
            event.preventDefault();
            break;
        }
      ]]></handler>

      <handler event="blur"><![CDATA[
        let findbar = this.findbar;
        // Note: This code used to remove the selection
        // if it matched an editable.
        findbar.browser.finder.enableSelection();
      ]]></handler>

      <handler event="focus"><![CDATA[
        if (/Mac/.test(navigator.platform)) {
          let findbar = this.findbar;
          findbar._onFindFieldFocus();
        }
      ]]></handler>

      <handler event="compositionstart"><![CDATA[
        // Don't close the find toolbar while IME is composing.
        let findbar = this.findbar;
        findbar._isIMEComposing = true;
        if (findbar._quickFindTimeout) {
          clearTimeout(findbar._quickFindTimeout);
          findbar._quickFindTimeout = null;
        }
      ]]></handler>

      <handler event="compositionend"><![CDATA[
        let findbar = this.findbar;
        findbar._isIMEComposing = false;
        if (findbar._findMode != findbar.FIND_NORMAL)
          findbar._setFindCloseTimeout();
      ]]></handler>

      <handler event="dragover"><![CDATA[
        if (event.dataTransfer.types.includes("text/plain"))
          event.preventDefault();
      ]]></handler>

      <handler event="drop"><![CDATA[
        let value = event.dataTransfer.getData("text/plain");
        this.value = value;
        this.findbar._find(value);
        event.stopPropagation();
        event.preventDefault();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="findbar"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
    <resources>
      <stylesheet src="chrome://global/skin/findBar.css"/>
    </resources>

    <content hidden="true">
    <xul:hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
      <xul:hbox anonid="findbar-textbox-wrapper" align="stretch">
        <xul:textbox anonid="findbar-textbox"
                     class="findbar-textbox findbar-find-fast"
                     xbl:inherits="flash"/>
        <xul:toolbarbutton anonid="find-previous"
                           class="findbar-find-previous tabbable"
                           tooltiptext="&previous.tooltip;"
                           oncommand="onFindAgainCommand(true);"
                           disabled="true"
                           xbl:inherits="accesskey=findpreviousaccesskey"/>
        <xul:toolbarbutton anonid="find-next"
                           class="findbar-find-next tabbable"
                           tooltiptext="&next.tooltip;"
                           oncommand="onFindAgainCommand(false);"
                           disabled="true"
                           xbl:inherits="accesskey=findnextaccesskey"/>
      </xul:hbox>
      <xul:toolbarbutton anonid="highlight"
                         class="findbar-highlight findbar-button tabbable"
                         label="&highlightAll.label;"
                         accesskey="&highlightAll.accesskey;"
                         tooltiptext="&highlightAll.tooltiptext;"
                         oncommand="toggleHighlight(this.checked);"
                         type="checkbox"
                         xbl:inherits="accesskey=highlightaccesskey"/>
      <xul:toolbarbutton anonid="find-case-sensitive"
                         class="findbar-case-sensitive findbar-button tabbable"
                         label="&caseSensitive.label;"
                         accesskey="&caseSensitive.accesskey;"
                         tooltiptext="&caseSensitive.tooltiptext;"
                         oncommand="_setCaseSensitivity(this.checked ? 1 : 0);"
                         type="checkbox"
                         xbl:inherits="accesskey=matchcaseaccesskey"/>
      <xul:toolbarbutton anonid="find-entire-word"
                         class="findbar-entire-word findbar-button tabbable"
                         label="&entireWord.label;"
                         accesskey="&entireWord.accesskey;"
                         tooltiptext="&entireWord.tooltiptext;"
                         oncommand="toggleEntireWord(this.checked);"
                         type="checkbox"
                         xbl:inherits="accesskey=entirewordaccesskey"/>
      <xul:label anonid="match-case-status" class="findbar-find-fast"/>
      <xul:label anonid="entire-word-status" class="findbar-find-fast"/>
      <xul:label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true"/>
      <xul:image anonid="find-status-icon" class="findbar-find-fast find-status-icon"/>
      <xul:description anonid="find-status"
                       control="findbar-textbox"
                       class="findbar-find-fast findbar-find-status">
      <!-- Do not use value, first child is used because it provides a11y with text change events -->
      </xul:description>
    </xul:hbox>
    <xul:toolbarbutton anonid="find-closebutton"
                       class="findbar-closebutton close-icon"
                       tooltiptext="&findCloseButton.tooltip;"
                       oncommand="close();"/>
    </content>

    <implementation implements="nsIMessageListener, nsIEditActionListener">
      <!-- Please keep in sync with toolkit/content/browser-content.js -->
      <field name="FIND_NORMAL">0</field>
      <field name="FIND_TYPEAHEAD">1</field>
      <field name="FIND_LINKS">2</field>

      <field name="__findMode">0</field>
      <property name="_findMode" onget="return this.__findMode;"
                onset="this.__findMode = val; this._updateBrowserWithState(); return val;"/>

      <field name="_flashFindBar">0</field>
      <field name="_initialFlashFindBarCount">6</field>

      <!--
        - For tests that need to know when the find bar is finished
        - initializing, we store a promise to notify on.
        -->
      <field name="_startFindDeferred">null</field>

      <property name="prefillWithSelection"
                onget="return this.getAttribute('prefillwithselection') != 'false'"
                onset="this.setAttribute('prefillwithselection', val); return val;"/>

      <method name="getElement">
        <parameter name="aAnonymousID"/>
        <body><![CDATA[
          return document.getAnonymousElementByAttribute(this,
                                                         "anonid",
                                                         aAnonymousID)
        ]]></body>
      </method>

      <property name="findMode"
                readonly="true"
                onget="return this._findMode;"/>

      <property name="hasTransactions" readonly="true">
        <getter><![CDATA[
          if (this._findField.value)
            return true;

          // Watch out for lazy editor init
          if (this._findField.editor) {
            let tm = this._findField.editor.transactionManager;
            return !!(tm.numberOfUndoItems || tm.numberOfRedoItems);
          }
          return false;
        ]]></getter>
      </property>

      <field name="_browser">null</field>
      <property name="browser">
        <getter><![CDATA[
          if (!this._browser) {
            this._browser =
              document.getElementById(this.getAttribute("browserid"));
          }
          return this._browser;
        ]]></getter>
        <setter><![CDATA[
          if (this._browser) {
            if (this._browser.messageManager) {
              this._browser.messageManager.removeMessageListener("Findbar:Keypress", this);
              this._browser.messageManager.removeMessageListener("Findbar:Mouseup", this);
            }
            let finder = this._browser.finder;
            if (finder)
              finder.removeResultListener(this);
          }

          this._browser = val;
          if (this._browser) {
            // Need to do this to ensure the correct initial state.
            this._updateBrowserWithState();
            this._browser.messageManager.addMessageListener("Findbar:Keypress", this);
            this._browser.messageManager.addMessageListener("Findbar:Mouseup", this);
            this._browser.finder.addResultListener(this);

            this._findField.value = this._browser._lastSearchString;
          }
          return val;
        ]]></setter>
      </property>

      <field name="__prefsvc">null</field>
      <property name="_prefsvc">
        <getter><![CDATA[
          if (!this.__prefsvc) {
            this.__prefsvc = Components.classes["@mozilla.org/preferences-service;1"]
              .getService(Components.interfaces.nsIPrefBranch);
          }
          return this.__prefsvc;
        ]]></getter>
      </property>

      <field name="_observer"><![CDATA[({
        _self: this,

        QueryInterface: function(aIID) {
          if (aIID.equals(Components.interfaces.nsIObserver) ||
              aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
              aIID.equals(Components.interfaces.nsISupports))
            return this;

          throw Components.results.NS_ERROR_NO_INTERFACE;
        },

        observe: function(aSubject, aTopic, aPrefName) {
          if (aTopic != "nsPref:changed")
            return;

          let prefsvc = this._self._prefsvc;

          switch (aPrefName) {
            case "accessibility.typeaheadfind":
              this._self._findAsYouType = prefsvc.getBoolPref(aPrefName);
              break;
            case "accessibility.typeaheadfind.linksonly":
              this._self._typeAheadLinksOnly = prefsvc.getBoolPref(aPrefName);
              break;
            case "accessibility.typeaheadfind.casesensitive":
              this._self._setCaseSensitivity(prefsvc.getIntPref(aPrefName));
              break;
            case "findbar.entireword":
              this._self._entireWord = prefsvc.getBoolPref(aPrefName);
              this._self.toggleEntireWord(this._self._entireWord, true);
              break;
            case "findbar.highlightAll":
              this._self.toggleHighlight(prefsvc.getBoolPref(aPrefName), true);
              break;
            case "findbar.modalHighlight":
              this._self._useModalHighlight = prefsvc.getBoolPref(aPrefName);
              if (this._self.browser.finder)
                this._self.browser.finder.onModalHighlightChange(this._self._useModalHighlight);
              break;
          }
        }
      })]]></field>

      <field name="_destroyed">false</field>

      <constructor><![CDATA[
        // These elements are accessed frequently and are therefore cached
        this._findField = this.getElement("findbar-textbox");
        this._foundMatches = this.getElement("found-matches");
        this._findStatusIcon = this.getElement("find-status-icon");
        this._findStatusDesc = this.getElement("find-status");

        this._foundURL = null;

        let prefsvc = this._prefsvc;

        this._quickFindTimeoutLength =
          prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
        this._flashFindBar =
          prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
        this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");

        prefsvc.addObserver("accessibility.typeaheadfind",
                            this._observer, false);
        prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
                            this._observer, false);
        prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
                            this._observer, false);
        prefsvc.addObserver("findbar.entireword", this._observer, false);
        prefsvc.addObserver("findbar.highlightAll", this._observer, false);
        prefsvc.addObserver("findbar.modalHighlight", this._observer, false);

        this._findAsYouType =
          prefsvc.getBoolPref("accessibility.typeaheadfind");
        this._typeAheadLinksOnly =
          prefsvc.getBoolPref("accessibility.typeaheadfind.linksonly");
        this._typeAheadCaseSensitive =
          prefsvc.getIntPref("accessibility.typeaheadfind.casesensitive");
        this._entireWord = prefsvc.getBoolPref("findbar.entireword");
        this._highlightAll = prefsvc.getBoolPref("findbar.highlightAll");

        // Convenience
        this.nsITypeAheadFind = Components.interfaces.nsITypeAheadFind;
        this.nsISelectionController = Components.interfaces.nsISelectionController;
        this._findSelection = this.nsISelectionController.SELECTION_FIND;

        this._findResetTimeout = -1;

        // Make sure the FAYT keypress listener is attached by initializing the
        // browser property
        if (this.getAttribute("browserid"))
          setTimeout(function(aSelf) { aSelf.browser = aSelf.browser; }, 0, this);
      ]]></constructor>

      <destructor><![CDATA[
        this.destroy();
      ]]></destructor>

      <!-- This is necessary because the destructor isn't called when
           we are removed from a document that is not destroyed. This
           needs to be explicitly called in this case -->
      <method name="destroy">
        <body><![CDATA[
          if (this._destroyed)
            return;
          this._destroyed = true;

          if (this.browser.finder)
            this.browser.finder.destroy();

          this.browser = null;

          let prefsvc = this._prefsvc;
          prefsvc.removeObserver("accessibility.typeaheadfind",
                                 this._observer);
          prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
                                 this._observer);
          prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
                                 this._observer);
          prefsvc.removeObserver("findbar.entireword", this._observer);
          prefsvc.removeObserver("findbar.highlightAll", this._observer);
          prefsvc.removeObserver("findbar.modalHighlight", this._observer);

          // Clear all timers that might still be running.
          this._cancelTimers();
        ]]></body>
      </method>

      <method name="_cancelTimers">
        <body><![CDATA[
          if (this._flashFindBarTimeout) {
            clearInterval(this._flashFindBarTimeout);
            this._flashFindBarTimeout = null;
          }
          if (this._quickFindTimeout) {
            clearTimeout(this._quickFindTimeout);
            this._quickFindTimeout = null;
          }
          if (this._findResetTimeout) {
            clearTimeout(this._findResetTimeout);
            this._findResetTimeout = null;
          }
        ]]></body>
      </method>

      <method name="_setFindCloseTimeout">
        <body><![CDATA[
          if (this._quickFindTimeout)
            clearTimeout(this._quickFindTimeout);

          // Don't close the find toolbar while IME is composing OR when the
          // findbar is already hidden.
          if (this._isIMEComposing || this.hidden) {
            this._quickFindTimeout = null;
            return;
          }

          this._quickFindTimeout = setTimeout(() => {
             if (this._findMode != this.FIND_NORMAL)
               this.close();
             this._quickFindTimeout = null;
           }, this._quickFindTimeoutLength);
        ]]></body>
      </method>

      <field name="_pluralForm">null</field>
      <property name="pluralForm">
        <getter><![CDATA[
          if (!this._pluralForm) {
            this._pluralForm = Components.utils.import(
                               "resource://gre/modules/PluralForm.jsm", {}).PluralForm;
          }
          return this._pluralForm;
        ]]></getter>
      </property>

      <!--
        - Updates the search match count after each find operation on a new string.
        - @param aRes
        -        the result of the find operation
        -->
      <method name="_updateMatchesCount">
        <body><![CDATA[
          if (!this._dispatchFindEvent("matchescount"))
            return;

          this.browser.finder.requestMatchesCount(this._findField.value,
            this._findMode == this.FIND_LINKS);
        ]]></body>
      </method>

      <!--
        - Turns highlight on or off.
        - @param aHighlight (boolean)
        -        Whether to turn the highlight on or off
        - @param aFromPrefObserver (boolean)
        -        Whether the callee is the pref observer, which means we should
        -        not set the same pref again.
        -->
      <method name="toggleHighlight">
        <parameter name="aHighlight"/>
        <parameter name="aFromPrefObserver"/>
        <body><![CDATA[
          if (aHighlight === this._highlightAll) {
            return;
          }

          this.browser.finder.onHighlightAllChange(aHighlight);

          this._setHighlightAll(aHighlight, aFromPrefObserver);

          if (!this._dispatchFindEvent("highlightallchange")) {
            return;
          }

          let word = this._findField.value;
          // Bug 429723. Don't attempt to highlight ""
          if (aHighlight && !word)
            return;

          this.browser.finder.highlight(aHighlight, word,
            this._findMode == this.FIND_LINKS);

          // Update the matches count
          this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
        ]]></body>
      </method>

      <!--
        - Updates the highlight-all mode of the findbar and its UI.
        - @param aHighlight (boolean)
        -        Whether to turn the highlight on or off.
        - @param aFromPrefObserver (boolean)
        -        Whether the callee is the pref observer, which means we should
        -        not set the same pref again.
        -->
      <method name="_setHighlightAll">
        <parameter name="aHighlight"/>
        <parameter name="aFromPrefObserver"/>
        <body><![CDATA[
          if (typeof aHighlight != "boolean") {
            aHighlight = this._highlightAll;
          }
          if (aHighlight !== this._highlightAll && !aFromPrefObserver) {
            this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
          }
          this._highlightAll = aHighlight;
          let checkbox = this.getElement("highlight");
          checkbox.checked = this._highlightAll;
        ]]></body>
      </method>

      <method name="_maybeHighlightAll">
        <body><![CDATA[
          let word = this._findField.value;
          // Bug 429723. Don't attempt to highlight ""
          if (!this._highlightAll || !word)
            return;

          this.browser.finder.highlight(true, word,
            this._findMode == this.FIND_LINKS);
        ]]></body>
      </method>

      <!--
        - Updates the case-sensitivity mode of the findbar and its UI.
        - @param [optional] aString
        -        The string for which case sensitivity might be turned on.
        -        This only used when case-sensitivity is in auto mode,
        -        @see _shouldBeCaseSensitive. The default value for this
        -        parameter is the find-field value.
        -->
      <method name="_updateCaseSensitivity">
        <parameter name="aString"/>
        <body><![CDATA[
          let val = aString || this._findField.value;

          let caseSensitive = this._shouldBeCaseSensitive(val);
          let checkbox = this.getElement("find-case-sensitive");
          let statusLabel = this.getElement("match-case-status");
          checkbox.checked = caseSensitive;

          statusLabel.value = caseSensitive ? this._caseSensitiveStr : "";

          // Show the checkbox on the full Find bar in non-auto mode.
          // Show the label in all other cases.
          let hideCheckbox = this._findMode != this.FIND_NORMAL ||
            (this._typeAheadCaseSensitive != 0 &&
             this._typeAheadCaseSensitive != 1);
          checkbox.hidden = hideCheckbox;
          statusLabel.hidden = !hideCheckbox;

          this.browser.finder.caseSensitive = caseSensitive;
        ]]></body>
      </method>

      <!--
        - Sets the findbar case-sensitivity mode
        - @param aCaseSensitivity (int)
        -   0 - case insensitive
        -   1 - case sensitive
        -   2 - auto = case sensitive iff match string contains upper case letters
        -   @see _shouldBeCaseSensitive
        -->
      <method name="_setCaseSensitivity">
        <parameter name="aCaseSensitivity"/>
        <body><![CDATA[
          this._typeAheadCaseSensitive = aCaseSensitivity;
          this._updateCaseSensitivity();
          this._findFailedString = null;
          this._find();

          this._dispatchFindEvent("casesensitivitychange");
        ]]></body>
      </method>

      <!--
        - Updates the entire-word mode of the findbar and its UI.
        -->
      <method name="_setEntireWord">
        <body><![CDATA[
          let entireWord = this._entireWord;
          let checkbox = this.getElement("find-entire-word");
          let statusLabel = this.getElement("entire-word-status");
          checkbox.checked = entireWord;

          statusLabel.value = entireWord ? this._entireWordStr : "";

          // Show the checkbox on the full Find bar in non-auto mode.
          // Show the label in all other cases.
          let hideCheckbox = this._findMode != this.FIND_NORMAL;
          checkbox.hidden = hideCheckbox;
          statusLabel.hidden = !hideCheckbox;

          this.browser.finder.entireWord = entireWord;
        ]]></body>
      </method>

      <!--
        - Sets the findbar entire-word mode
        - @param aEntireWord (boolean)
        - Whether or not entire-word mode should be turned on.
        -->
      <method name="toggleEntireWord">
        <parameter name="aEntireWord"/>
        <parameter name="aFromPrefObserver"/>
        <body><![CDATA[
          if (!aFromPrefObserver) {
            // Just set the pref; our observer will change the find bar behavior.
            this._prefsvc.setBoolPref("findbar.entireword", aEntireWord);
            return;
          }

          this._findFailedString = null;
          this._find();
        ]]></body>
      </method>

      <field name="_strBundle">null</field>
      <property name="strBundle">
        <getter><![CDATA[
          if (!this._strBundle) {
            this._strBundle =
              Components.classes["@mozilla.org/intl/stringbundle;1"]
                        .getService(Components.interfaces.nsIStringBundleService)
                        .createBundle("chrome://global/locale/findbar.properties");
          }
          return this._strBundle;
        ]]></getter>
      </property>

      <!--
        - Opens and displays the find bar.
        -
        - @param aMode
        -        the find mode to be used, which is either FIND_NORMAL,
        -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
        -        find mode if any or FIND_NORMAL.
        - @returns true if the find bar wasn't previously open, false otherwise.
        -->
      <method name="open">
        <parameter name="aMode"/>
        <body><![CDATA[
          if (aMode != undefined)
            this._findMode = aMode;

          if (!this._notFoundStr) {
            var stringsBundle = this.strBundle;
            this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
            this._wrappedToTopStr =
              stringsBundle.GetStringFromName("WrappedToTop");
            this._wrappedToBottomStr =
              stringsBundle.GetStringFromName("WrappedToBottom");
            this._normalFindStr =
              stringsBundle.GetStringFromName("NormalFind");
            this._fastFindStr =
              stringsBundle.GetStringFromName("FastFind");
            this._fastFindLinksStr =
              stringsBundle.GetStringFromName("FastFindLinks");
            this._caseSensitiveStr =
              stringsBundle.GetStringFromName("CaseSensitive");
            this._entireWordStr =
              stringsBundle.GetStringFromName("EntireWord");
          }

          this._findFailedString = null;

          this._updateFindUI();
          if (this.hidden) {
            this.removeAttribute("noanim");
            this.hidden = false;

            this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);

            let event = document.createEvent("Events");
            event.initEvent("findbaropen", true, false);
            this.dispatchEvent(event);

            this.browser.finder.onFindbarOpen();

            return true;
          }
          return false;
        ]]></body>
      </method>

      <!--
        - Closes the findbar.
        -->
      <method name="close">
        <parameter name="aNoAnim"/>
        <body><![CDATA[
          if (this.hidden)
            return;

          if (aNoAnim)
            this.setAttribute("noanim", true);
          this.hidden = true;

          // 'focusContent()' iterates over all listeners in the chrome
          // process, so we need to call it from here.
          this.browser.finder.focusContent();
          this.browser.finder.onFindbarClose();

          this._cancelTimers();

          this._findFailedString = null;
        ]]></body>
      </method>

      <method name="clear">
        <body><![CDATA[
          this.browser.finder.removeSelection();
          this._findField.reset();
          this.toggleHighlight(false);
          this._updateStatusUI();
          this._enableFindButtons(false);
        ]]></body>
      </method>

      <method name="_dispatchKeypressEvent">
        <parameter name="aTarget"/>
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!aTarget)
            return;

          let event = document.createEvent("KeyboardEvent");
          event.initKeyEvent(aEvent.type, aEvent.bubbles, aEvent.cancelable,
                             aEvent.view, aEvent.ctrlKey, aEvent.altKey,
                             aEvent.shiftKey, aEvent.metaKey, aEvent.keyCode,
                             aEvent.charCode);
          aTarget.dispatchEvent(event);
        ]]></body>
      </method>

      <field name="_xulBrowserWindow">null</field>
      <method name="_updateStatusUIBar">
        <parameter name="aFoundURL"/>
        <body><![CDATA[
          if (!this._xulBrowserWindow) {
            try {
              this._xulBrowserWindow =
                window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                      .getInterface(Components.interfaces.nsIWebNavigation)
                      .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                      .treeOwner
                      .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                      .getInterface(Components.interfaces.nsIXULWindow)
                      .XULBrowserWindow;
            }
            catch (ex) { }
            if (!this._xulBrowserWindow)
              return false;
          }

          // Call this has the same effect like hovering over link,
          // the browser shows the URL as a tooltip.
          this._xulBrowserWindow.setOverLink(aFoundURL || "", null);
          return true;
        ]]></body>
      </method>

      <method name="_finishFAYT">
        <parameter name="aKeypressEvent"/>
        <body><![CDATA[
          this.browser.finder.focusContent();

          if (aKeypressEvent)
            aKeypressEvent.preventDefault();

          this.browser.finder.keyPress(aKeypressEvent);

          this.close();
          return true;
        ]]></body>
      </method>

      <method name="_shouldBeCaseSensitive">
        <parameter name="aString"/>
        <body><![CDATA[
          if (this._typeAheadCaseSensitive == 0)
            return false;
          if (this._typeAheadCaseSensitive == 1)
            return true;

          return aString != aString.toLowerCase();
        ]]></body>
      </method>

      <!-- We get a fake event object through an IPC message which contains the
           data we need to make a decision. We then return |true| if and only if
           the page gets to deal with the event itself. Everywhere we return
           false, the message sender will take care of calling event.preventDefault
           on the real event. -->
      <method name="_onBrowserKeypress">
        <parameter name="aFakeEvent"/>
        <parameter name="aShouldFastFind"/>
        <body><![CDATA[
          const FAYT_LINKS_KEY = "'";
          const FAYT_TEXT_KEY = "/";

          // Fast keypresses can stack up when the content process is slow or
          // hangs when in e10s mode. We make sure the findbar isn't 'opened'
          // several times in a row, because then the find query is selected
          // each time, losing characters typed initially.
          let inputField = this._findField.inputField;
          if (!this.hidden && document.activeElement == inputField) {
            this._dispatchKeypressEvent(inputField, aFakeEvent);
            return false;
          }

          if (this._findMode != this.FIND_NORMAL && this._quickFindTimeout) {
            if (!aFakeEvent.charCode)
              return true;

            this._findField.select();
            this._findField.focus();
            this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
            return false;
          }

          if (!aShouldFastFind)
            return true;

          let key = aFakeEvent.charCode ? String.fromCharCode(aFakeEvent.charCode) : null;
          let manualstartFAYT = (key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY);
          let autostartFAYT = !manualstartFAYT && this._findAsYouType &&
                              key && key != " ";
          if (manualstartFAYT || autostartFAYT) {
            let mode = (key == FAYT_LINKS_KEY ||
                        (autostartFAYT && this._typeAheadLinksOnly)) ?
              this.FIND_LINKS : this.FIND_TYPEAHEAD;

            // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
            // it doesn't get confused by a lingering value
            this._findField.value = "";

            this.open(mode);
            this._setFindCloseTimeout();
            this._findField.select();
            this._findField.focus();

            if (autostartFAYT)
              this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
            else
              this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);

            return false;
          }
          return undefined;
        ]]></body>
      </method>

      <!-- See nsIMessageListener -->
      <method name="receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          if (aMessage.target != this._browser) {
            return undefined;
          }
          switch (aMessage.name) {
            case "Findbar:Mouseup":
              if (!this.hidden && this._findMode != this.FIND_NORMAL)
                this.close();
              break;

            case "Findbar:Keypress":
              return this._onBrowserKeypress(aMessage.data.fakeEvent,
                                             aMessage.data.shouldFastFind);
          }
          return undefined;
        ]]></body>
      </method>

      <method name="_updateBrowserWithState">
        <body><![CDATA[
          if (this._browser && this._browser.messageManager) {
            this._browser.messageManager.sendAsyncMessage("Findbar:UpdateState", {
              findMode: this._findMode
            });
          }
        ]]></body>
      </method>

      <method name="_enableFindButtons">
        <parameter name="aEnable"/>
        <body><![CDATA[
          this.getElement("find-next").disabled =
            this.getElement("find-previous").disabled = !aEnable;
        ]]></body>
      </method>

      <!--
        - Determines whether minimalist or general-purpose search UI is to be
        - displayed when the find bar is activated.
        -->
      <method name="_updateFindUI">
        <body><![CDATA[
          let showMinimalUI = this._findMode != this.FIND_NORMAL;

          let nodes = this.getElement("findbar-container").childNodes;
          let wrapper = this.getElement("findbar-textbox-wrapper");
          let foundMatches = this._foundMatches;
          for (let node of nodes) {
            if (node == wrapper || node == foundMatches)
               continue;
            node.hidden = showMinimalUI;
          }
          this.getElement("find-next").hidden =
            this.getElement("find-previous").hidden = showMinimalUI;
          foundMatches.hidden = showMinimalUI || !foundMatches.value;
          this._updateCaseSensitivity();
          this._setEntireWord();
          this._setHighlightAll();

          if (showMinimalUI)
            this._findField.classList.add("minimal");
          else
            this._findField.classList.remove("minimal");

          if (this._findMode == this.FIND_TYPEAHEAD)
            this._findField.placeholder = this._fastFindStr;
          else if (this._findMode == this.FIND_LINKS)
            this._findField.placeholder = this._fastFindLinksStr;
          else
            this._findField.placeholder = this._normalFindStr;
        ]]></body>
      </method>

      <method name="_find">
        <parameter name="aValue"/>
        <body><![CDATA[
          if (!this._dispatchFindEvent(""))
            return;

          let val = aValue || this._findField.value;

          // We have to carry around an explicit version of this,
          // because finder.searchString doesn't update on failed
          // searches.
          this.browser._lastSearchString = val;

          // Only search on input if we don't have a last-failed string,
          // or if the current search string doesn't start with it.
          // In entire-word mode we always attemp a find; since sequential matching
          // is not guaranteed, the first character typed may not be a word (no
          // match), but the with the second character it may well be a word,
          // thus a match.
          if (!this._findFailedString ||
              !val.startsWith(this._findFailedString) ||
              this._entireWord) {
            // Getting here means the user commanded a find op. Make sure any
            // initial prefilling is ignored if it hasn't happened yet.
            if (this._startFindDeferred) {
              this._startFindDeferred.resolve();
              this._startFindDeferred = null;
            }

            this._enableFindButtons(val);
            this._updateCaseSensitivity(val);
            this._setEntireWord();

            this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
                                         this._findMode != this.FIND_NORMAL);
          }

          if (this._findMode != this.FIND_NORMAL)
            this._setFindCloseTimeout();

          if (this._findResetTimeout != -1)
            clearTimeout(this._findResetTimeout);

          // allow a search to happen on input again after a second has
          // expired since the previous input, to allow for dynamic
          // content and/or page loading
          this._findResetTimeout = setTimeout(() => {
            this._findFailedString = null;
            this._findResetTimeout = -1;
          }, 1000);
        ]]></body>
      </method>

      <method name="_flash">
        <body><![CDATA[
          if (this._flashFindBarCount === undefined)
            this._flashFindBarCount = this._initialFlashFindBarCount;

          if (this._flashFindBarCount-- == 0) {
            clearInterval(this._flashFindBarTimeout);
            this.removeAttribute("flash");
            this._flashFindBarCount = 6;
            return;
          }

          this.setAttribute("flash",
                            (this._flashFindBarCount % 2 == 0) ?
                            "false" : "true");
        ]]></body>
      </method>

      <method name="_findAgain">
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          this.browser.finder.findAgain(aFindPrevious,
                                        this._findMode == this.FIND_LINKS,
                                        this._findMode != this.FIND_NORMAL);
        ]]></body>
      </method>

      <method name="_updateStatusUI">
        <parameter name="res"/>
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          switch (res) {
            case this.nsITypeAheadFind.FIND_WRAPPED:
              this._findStatusIcon.setAttribute("status", "wrapped");
              this._findStatusDesc.textContent =
                aFindPrevious ? this._wrappedToBottomStr : this._wrappedToTopStr;
              this._findField.removeAttribute("status");
              break;
            case this.nsITypeAheadFind.FIND_NOTFOUND:
              this._findStatusIcon.setAttribute("status", "notfound");
              this._findStatusDesc.textContent = this._notFoundStr;
              this._findField.setAttribute("status", "notfound");
              break;
            case this.nsITypeAheadFind.FIND_PENDING:
              this._findStatusIcon.setAttribute("status", "pending");
              this._findStatusDesc.textContent = "";
              this._findField.removeAttribute("status");
              break;
            case this.nsITypeAheadFind.FIND_FOUND:
            default:
              this._findStatusIcon.removeAttribute("status");
              this._findStatusDesc.textContent = "";
              this._findField.removeAttribute("status");
              break;
          }
        ]]></body>
      </method>

      <method name="updateControlState">
        <parameter name="aResult"/>
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          this._updateStatusUI(aResult, aFindPrevious);
          this._enableFindButtons(aResult !== this.nsITypeAheadFind.FIND_NOTFOUND);
        ]]></body>
      </method>

      <method name="_dispatchFindEvent">
        <parameter name="aType"/>
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          let event = document.createEvent("CustomEvent");
          event.initCustomEvent("find" + aType, true, true, {
            query: this._findField.value,
            caseSensitive: !!this._typeAheadCaseSensitive,
            entireWord: this._entireWord,
            highlightAll: this._highlightAll,
            findPrevious: aFindPrevious
          });
          return this.dispatchEvent(event);
        ]]></body>
      </method>


      <!--
        - Opens the findbar, focuses the findfield and selects its contents.
        - Also flashes the findbar the first time it's used.
        - @param aMode
        -        the find mode to be used, which is either FIND_NORMAL,
        -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
        -        find mode if any or FIND_NORMAL.
        -->
      <method name="startFind">
        <parameter name="aMode"/>
        <body><![CDATA[
          let prefsvc = this._prefsvc;
          let userWantsPrefill = true;
          this.open(aMode);

          if (this._flashFindBar) {
            this._flashFindBarTimeout = setInterval(() => this._flash(), 500);
            prefsvc.setIntPref("accessibility.typeaheadfind.flashBar",
                               --this._flashFindBar);
          }

          let {PromiseUtils} =
            Components.utils.import("resource://gre/modules/PromiseUtils.jsm", {});
          this._startFindDeferred = PromiseUtils.defer();
          let startFindPromise = this._startFindDeferred.promise;

          if (this.prefillWithSelection)
            userWantsPrefill =
              prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");

          if (this.prefillWithSelection && userWantsPrefill) {
            // NB: We have to focus this._findField here so tests that send
            // key events can open and close the find bar synchronously.
            this._findField.focus();

            // (e10s) since we focus lets also select it, otherwise that would
            // only happen in this.onCurrentSelection and, because it is async,
            // there's a chance keypresses could come inbetween, leading to
            // jumbled up queries.
            this._findField.select();

            this.browser.finder.getInitialSelection();
            return startFindPromise;
          }

          // If userWantsPrefill is false but prefillWithSelection is true,
          // then we might need to check the selection clipboard. Call
          // onCurrentSelection to do so.
          // Note: this.onCurrentSelection clears this._startFindDeferred.
          this.onCurrentSelection("", true);
          return startFindPromise;
        ]]></body>
      </method>

      <!--
        - Convenient alias to startFind(gFindBar.FIND_NORMAL);
        -
        - You should generally map the window's find command to this method.
        -   e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
        -->
      <method name="onFindCommand">
        <body><![CDATA[
          return this.startFind(this.FIND_NORMAL);
        ]]></body>
      </method>

      <!--
        - Stub for find-next and find-previous commands
        - @param aFindPrevious
        -        true for find-previous, false otherwise.
        -->
      <method name="onFindAgainCommand">
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          let findString = this._browser.finder.searchString || this._findField.value;
          if (!findString)
            return this.startFind();

          // We dispatch the findAgain event here instead of in _findAgain since
          // if there is a find event handler that prevents the default then
          // finder.searchString will never get updated which in turn means
          // there would never be findAgain events because of the logic below.
          if (!this._dispatchFindEvent("again", aFindPrevious))
            return undefined;

          // user explicitly requested another search, so do it even if we think it'll fail
          this._findFailedString = null;

          // Ensure the stored SearchString is in sync with what we want to find
          if (this._findField.value != this._browser.finder.searchString) {
            this._find(this._findField.value);
          } else {
            this._findAgain(aFindPrevious);
            if (this._useModalHighlight) {
              this.open();
              this._findField.select();
              this._findField.focus();
            }
          }

          return undefined;
        ]]></body>
      </method>


      <!--
        - This handles all the result changes for both
        - type-ahead-find and highlighting.
        - @param aResult
        -   One of the nsITypeAheadFind.FIND_* constants
        -   indicating the result of a search operation.
        - @param aFindBackwards
        -   If the search was done from the bottom to
        -   the top. This is used for right error messages
        -   when reaching "the end of the page".
        - @param aLinkURL
        -   When a link matched then its URK. Always null
        -   when not in FIND_LINKS mode.
        -->
      <method name="onFindResult">
        <parameter name="aData"/>
        <body><![CDATA[
          if (aData.result == this.nsITypeAheadFind.FIND_NOTFOUND) {
            // If an explicit Find Again command fails, re-open the toolbar.
            if (aData.storeResult && this.open()) {
              this._findField.select();
              this._findField.focus();
            }
            this._findFailedString = aData.searchString;
          } else {
            this._findFailedString = null;
          }

          this._updateStatusUI(aData.result, aData.findBackwards);
          this._updateStatusUIBar(aData.linkURL);

          if (this._findMode != this.FIND_NORMAL)
            this._setFindCloseTimeout();
        ]]></body>
      </method>

      <!--
        - This handles all the result changes for matches counts.
        - @param aResult
        -   Result Object, containing the total amount of matches and a vector
        -   of the current result.
        -->
      <method name="onMatchesCountResult">
        <parameter name="aResult"/>
        <body><![CDATA[
          if (aResult.total !== 0) {
            if (aResult.total == -1) {
              this._foundMatches.value = this.pluralForm.get(
                aResult.limit,
                this.strBundle.GetStringFromName("FoundMatchesCountLimit")
              ).replace("#1", aResult.limit);
            } else {
              this._foundMatches.value = this.pluralForm.get(
                aResult.total,
                this.strBundle.GetStringFromName("FoundMatches")
              ).replace("#1", aResult.current)
               .replace("#2", aResult.total);
            }
            this._foundMatches.hidden = false;
          } else {
            this._foundMatches.hidden = true;
            this._foundMatches.value = "";
          }
        ]]></body>
      </method>

      <method name="onHighlightFinished">
        <parameter name="result"/>
        <body><![CDATA[
          // Noop.
        ]]></body>
      </method>

      <method name="onCurrentSelection">
        <parameter name="aSelectionString" />
        <parameter name="aIsInitialSelection" />
        <body><![CDATA[
          // Ignore the prefill if the user has already typed in the findbar,
          // it would have been overwritten anyway. See bug 1198465.
          if (aIsInitialSelection && !this._startFindDeferred)
            return;

          if (/Mac/.test(navigator.platform) && aIsInitialSelection && !aSelectionString) {
            let clipboardSearchString = this.browser.finder.clipboardSearchString;
            if (clipboardSearchString)
              aSelectionString = clipboardSearchString;
          }

          if (aSelectionString)
            this._findField.value = aSelectionString;

          if (aIsInitialSelection) {
            this._enableFindButtons(!!this._findField.value);
            this._findField.select();
            this._findField.focus();

            this._startFindDeferred.resolve();
            this._startFindDeferred = null;
          }
        ]]></body>
      </method>

      <!--
        - This handler may cancel a request to focus content by returning |false|
        - explicitly.
        -->
      <method name="shouldFocusContent">
        <body><![CDATA[
          const fm = Components.classes["@mozilla.org/focus-manager;1"]
                               .getService(Components.interfaces.nsIFocusManager);
          if (fm.focusedWindow != window)
            return false;

          let focusedElement = fm.focusedElement;
          if (!focusedElement)
            return false;

          let bindingParent = document.getBindingParent(focusedElement);
          if (bindingParent != this && bindingParent != this._findField)
            return false;

          return true;
        ]]></body>
      </method>

    </implementation>

    <handlers>
      <!--
        - We have to guard against `this.close` being |null| due to an unknown
        - issue, which is tracked in bug 957999.
        -->
      <handler event="keypress" keycode="VK_ESCAPE" phase="capturing"
               action="if (this.close) this.close();" preventdefault="true"/>
    </handlers>
  </binding>
</bindings>
