# -*- Mode: HTML -*-
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org viewsource frontend.
# 
# The Initial Developer of the Original Code is
# Netscape Communications Corporation.
# Portions created by the Initial Developer are Copyright (C) 2003
# the Initial Developer.  All Rights Reserved.
# 
# Contributor(s):
#     Blake Ross <blake@cs.stanford.edu> (Original Author)
#     Masayuki Nakano <masayuki@d-toybox.com>
#     Ben Basson <contact@cusser.net>
#     Jason Barnabe <jason_barnabe@fastmail.fm>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the LGPL or the GPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
#***** END LICENSE BLOCK ***** -->

const FIND_NORMAL = 0;
const FIND_TYPEAHEAD = 1;
const FIND_LINKS = 2;

const CHAR_CODE_SPACE = " ".charCodeAt(0);
const CHAR_CODE_SLASH = "/".charCodeAt(0);
const CHAR_CODE_APOSTROPHE = "'".charCodeAt(0);

/*
 * WARNING:
 * Don't define global variables and functions in this file. Because this file
 * is included by some files. So, the global name space is not safety.
 *
 * But we need global functions for the Timer Event Handlers or for the Event
 * Listener Handlers. In this case, we MUST define global functions with
 * "findBar_" prefix.
 */

var gFindBar = {
  SELECTION_CONTROLLER: Components.interfaces.nsISelectionController,

  mFindEnabled: true,
  mFindMode: FIND_NORMAL,
  mFoundLink: null,
  mFoundEditable: null,
  mCurrentWindow: null,
  mTmpOutline: null,
  mTmpOutlineOffset: "0",
  mDrawOutline: false,
  mQuickFindTimeout: null,
  mQuickFindTimeoutLength: 0,
  mHighlightTimeout: null,
  mUseTypeAheadFind: false,
  mWrappedToTopStr: "",
  mWrappedToBottomStr: "",
  mNotFoundStr: "",
  mCaseSensitiveStr: "",
  mFlashFindBar: 0,
  mFlashFindBarCount: 6,
  mFlashFindBarTimeout: null,
  mLastHighlightString: "",
  mTypeAheadLinksOnly: false,
  mTypeAheadCaseSensitive: -1,
  mIsIMEComposing: false,
  mTextToSubURIService: null,

  // DOMRange used during highlighting
  mSearchRange: null,
  mStartPt: null,
  mEndPt: null,

  mTypeAheadFind: {
    useTAFPref: "accessibility.typeaheadfind",
    searchLinksPref: "accessibility.typeaheadfind.linksonly",
    caseSensePref: "accessibility.typeaheadfind.casesensitive",

    observe: function(aSubject, aTopic, aPrefName)
    {
      if (aTopic != "nsPref:changed" ||
          (aPrefName != this.useTAFPref && aPrefName != this.searchLinksPref &&
           aPrefName != this.caseSensePref))
        return;

      var prefService =
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefBranch);

      gFindBar.mUseTypeAheadFind =
        prefService.getBoolPref("accessibility.typeaheadfind");
      gFindBar.mTypeAheadLinksOnly =
        prefService.getBoolPref("accessibility.typeaheadfind.linksonly");
      gFindBar.mTypeAheadCaseSensitive =
        prefService.getIntPref("accessibility.typeaheadfind.casesensitive");
      gFindBar.setCaseSensitivity();
    }
  },

  initFindBar: function ()
  {
    getBrowser().addEventListener("keypress",
                                  findBar_OnBrowserKeyPress, false);
    getBrowser().addEventListener("mouseup",
                                  findBar_OnBrowserMouseUp, false);

    var prefService =
      Components.classes["@mozilla.org/preferences-service;1"]
                .getService(Components.interfaces.nsIPrefBranch);

    var pbi = prefService.QueryInterface(Components.interfaces.nsIPrefBranch2);

    this.mQuickFindTimeoutLength =
      prefService.getIntPref("accessibility.typeaheadfind.timeout");
    this.mFlashFindBar =
      prefService.getIntPref("accessibility.typeaheadfind.flashBar");

    pbi.addObserver(this.mTypeAheadFind.useTAFPref,
                    this.mTypeAheadFind, false);
    pbi.addObserver(this.mTypeAheadFind.searchLinksPref,
                    this.mTypeAheadFind, false);
    pbi.addObserver(this.mTypeAheadFind.caseSensePref,
                    this.mTypeAheadFind, false);
    this.mUseTypeAheadFind =
      prefService.getBoolPref("accessibility.typeaheadfind");
    this.mTypeAheadLinksOnly =
      prefService.getBoolPref("accessibility.typeaheadfind.linksonly");
    this.mTypeAheadCaseSensitive =
      prefService.getIntPref("accessibility.typeaheadfind.casesensitive");

    var findField = document.getElementById("find-field");
    findField.addEventListener("dragdrop", findBar_OnDrop, true);

    this.mTextToSubURIService =
      Components.classes["@mozilla.org/intl/texttosuburi;1"]
                .getService(Components.interfaces.nsITextToSubURI);
  },

  mFindbarObserver: {
    onDrop: function (aEvent, aXferData, aDragSession)
    {
        var findField = document.getElementById("find-field");
        findField.value = aXferData.data;
        gFindBar.find(aXferData.data);
    },
    getSupportedFlavours: function ()
    {
      var flavourSet = new FlavourSet();
      flavourSet.appendFlavour("text/unicode");
      return flavourSet;
    }
  },

  uninitFindBar: function ()
  {
    var prefService =
      Components.classes["@mozilla.org/preferences-service;1"]
                .getService(Components.interfaces.nsIPrefBranch);

    var pbi = prefService.QueryInterface(Components.interfaces.nsIPrefBranch2);
    pbi.removeObserver(this.mTypeAheadFind.useTAFPref,
                       this.mTypeAheadFind);
    pbi.removeObserver(this.mTypeAheadFind.searchLinksPref,
                       this.mTypeAheadFind);
    pbi.removeObserver(this.mTypeAheadFind.caseSensePref,
                       this.mTypeAheadFind);

    getBrowser().removeEventListener("keypress",
                                     findBar_OnBrowserKeyPress, false);
    getBrowser().removeEventListener("mouseup",
                                     findBar_OnBrowserMouseUp, false);
  },

 /**
  * Turns highlighting on or off.
  *
  * @param aHighlight true to turn highlighting on
  */
  toggleHighlight: function (aHighlight)
  {
    var word = document.getElementById("find-field").value;
    if (aHighlight) {
      // We have to update the status because we might still have the status
      // of another tab
      if (this.highlightDoc('yellow', 'black', word))
        this.updateStatus(Components.interfaces.nsITypeAheadFind.FIND_FOUND, false);
      else
        this.updateStatus(Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND, false);
    } else {
      this.highlightDoc(null, null, null);
      this.mLastHighlightString = null;
    }
  },

 /**
  * (Un)highlights each instance of the searched word in the passed window's content.
  *
  * @param highBackColor the background color for the highlight 
  * @param highTextColor the text color for the highlight, or null to make it unhighlight
  * @param word the word to search for
  * @param win the window to search in. Passing undefined will search the current window.
  * @return true if the text was found
  */
  highlightDoc: function (highBackColor, highTextColor, word, win)
  {
    if (!win)
      win = window.content; 

    var textFound = false;
    for (var i = 0; win.frames && i < win.frames.length; i++) {
      if (this.highlightDoc(highBackColor, highTextColor, word, win.frames[i]))
        textFound = true;
    }

    var doc = win.document;
    if (!doc || !("body" in doc))
      return textFound;

    var body = doc.body;

    var count = body.childNodes.length;
    this.mSearchRange = doc.createRange();
    this.mStartPt = doc.createRange();
    this.mEndPt = doc.createRange();

    this.mSearchRange.setStart(body, 0);
    this.mSearchRange.setEnd(body, count);

    this.mStartPt.setStart(body, 0);
    this.mStartPt.setEnd(body, 0);
    this.mEndPt.setStart(body, count);
    this.mEndPt.setEnd(body, count);

    if (!highBackColor) {
      // Remove highlighting.  We use the find API again rather than
      // searching for our span elements by id so that we gain access to the
      // anonymous content that nsIFind searches.

      if (!this.mLastHighlightString)
        return textFound;

      var retRange = null;
      var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
                             .createInstance()
                             .QueryInterface(Components.interfaces.nsIFind);

      while ((retRange = finder.Find(this.mLastHighlightString,
                                     this.mSearchRange, this.mStartPt,
                                     this.mEndPt))) {
        var startContainer = retRange.startContainer;
        var elem = null;
        try {
          elem = startContainer.parentNode;
        }
        catch (e) { }

        if (elem && elem.getAttribute("id") == "__firefox-findbar-search-id") {
          var child = null;
          var docfrag = doc.createDocumentFragment();
          var next = elem.nextSibling;
          var parent = elem.parentNode;

          while ((child = elem.firstChild)) {
            docfrag.appendChild(child);
          }

          this.mStartPt = doc.createRange();
          this.mStartPt.setStartAfter(elem);

          parent.removeChild(elem);
          parent.insertBefore(docfrag, next);
          parent.normalize();
        }
        else {
          // Somehow we didn't highlight this instance; just skip it.
          this.mStartPt = doc.createRange();
          this.mStartPt.setStart(retRange.endContainer, retRange.endOffset);
        }

        this.mStartPt.collapse(true);

        textFound = true;
      }
      return textFound;
    }

    var baseNode = doc.createElement("span");
    baseNode.setAttribute("style", "background-color: " + highBackColor + "; " +
                                   "color: " + highTextColor + "; " +
                                   "display: inline; font-size: inherit;" +
                                   " padding: 0;");
    baseNode.setAttribute("id", "__firefox-findbar-search-id");

    return this.highlightText(word, baseNode) || textFound;
  },

 /**
  * Highlights each instance of the searched word in the current range.
  *
  * @param word the word to search for
  * @param baseNode a node to use as a template for what will replace the
  *                 searched word
  * @return true if the text was found
  */
  highlightText: function (word, baseNode)
  {
    var retRange = null;
    var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
                           .createInstance()
                           .QueryInterface(Components.interfaces.nsIFind);

    finder.caseSensitive = this.shouldBeCaseSensitive(word);

    var textFound = false;
    while((retRange = finder.Find(word, this.mSearchRange,
                                  this.mStartPt, this.mEndPt))) {
      // Highlight
      var nodeSurround = baseNode.cloneNode(true);
      var node = this.highlight(retRange, nodeSurround);
      this.mStartPt = node.ownerDocument.createRange();
      this.mStartPt.setStart(node, node.childNodes.length);
      this.mStartPt.setEnd(node, node.childNodes.length);

      textFound = true;
    }

    this.mLastHighlightString = word;

    return textFound;
  },

 /**
  * Highlights the word in the passed range.
  *
  * @param range the range that contains the word to highlight
  * @param node the node replace the searched word with
  * @return the node that replaced the searched word
  */
  highlight: function (range, node)
  {
    var startContainer = range.startContainer;
    var startOffset = range.startOffset;
    var endOffset = range.endOffset;
    var docfrag = range.extractContents();
    var before = startContainer.splitText(startOffset);
    var parent = before.parentNode;
    node.appendChild(docfrag);
    parent.insertBefore(node, before);
    return node;
  },

  setCaseSensitivity: function (val)
  {
    var findToolbar = document.getElementById("FindToolbar");
    if (findToolbar.hidden)
      return;

    if (!val)
      val = document.getElementById("find-field").value;

    var matchCaseCheckbox = document.getElementById("find-case-sensitive");
    matchCaseCheckbox.checked = this.shouldBeCaseSensitive(val);

    var matchCaseText = document.getElementById("match-case-status");
    matchCaseText.value = matchCaseCheckbox.checked ?
      this.mCaseSensitiveStr : "";

    // Show the checkbox on the full Find bar in non-auto mode.  Show the label
    // in all other cases.
    matchCaseCheckbox.hidden = this.mUsingMinimalUI ||
      (this.mTypeAheadCaseSensitive != 0 && this.mTypeAheadCaseSensitive != 1);
    matchCaseText.hidden = !matchCaseCheckbox.hidden;

    var fastFind = getBrowser().fastFind;
    fastFind.caseSensitive = matchCaseCheckbox.checked;
  },

  toggleCaseSensitiveCheckbox: function (aCaseSensitive)
  {
    var prefService =
      Components.classes["@mozilla.org/preferences-service;1"]
                .getService(Components.interfaces.nsIPrefBranch);

    // Just set the pref; our observer will change the find bar behavior
    prefService.setIntPref("accessibility.typeaheadfind.casesensitive",
                           aCaseSensitive ? 1 : 0);
  },

  /**
   * Opens and displays the find bar.
   *
   * @param   showMinimalUI
   *          true if the find bar is to contain minimalist UI, false (or
   *          undefined, as happens if no arguments are provided) if the find
   *          bar is to display more generalized search UI
   * @returns bool
   *          true if the find bar wasn't previously open, false otherwise
   */
  openFindBar: function (showMinimalUI)
  {
    // Notify anyone else that might want to handle this event
    var findActivatedEvent = document.createEvent("Events");
    findActivatedEvent.initEvent("find-activated", false, true);
    window.dispatchEvent(findActivatedEvent);
    if (!this.mFindEnabled)
      throw Components.results.NS_OK;
 
    if (!this.mNotFoundStr || !this.mWrappedToTopStr ||
        !this.mWrappedToBottomStr || !this.mNormalFindStr ||
        !this.mFastFindStr || !this.mCaseSensitiveStr) {
      var bundle = document.getElementById("bundle_findBar");
      this.mNotFoundStr = bundle.getString("NotFound");
      this.mWrappedToTopStr = bundle.getString("WrappedToTop");
      this.mWrappedToBottomStr = bundle.getString("WrappedToBottom");
      this.mNormalFindStr = bundle.getString("NormalFindLabel");
      this.mFastFindStr = bundle.getString("FastFindLabel");
      this.mCaseSensitiveStr = bundle.getString("CaseSensitive");
    }

    this.updateFindUI(showMinimalUI);
    var findToolbar = document.getElementById("FindToolbar");
    if (findToolbar.hidden) {
      findToolbar.hidden = false;

      var findField = document.getElementById("find-field");
      this.setCaseSensitivity(findField.value);
      this.updateStatus(Components.interfaces.nsITypeAheadFind.FIND_FOUND, false);

      return true;
    }
    return false;
  },

  focusFindBar: function ()
  {
    var findField = document.getElementById("find-field");
    findField.focus();    
  },

  selectFindBar: function ()
  {
    var findField = document.getElementById("find-field");
    findField.select();    
  },

  closeFindBar: function ()
  {
    // ensure the dom is ready...
    setTimeout(findBar_DelayedCloseFindBar, 0);
  },

  fireKeypressEvent: function (target, evt)
  {
    if (!target)
      return;
    var event = document.createEvent("KeyEvents");
    event.initKeyEvent(evt.type, evt.canBubble, evt.cancelable,
                       evt.view, evt.ctrlKey, evt.altKey, evt.shiftKey,
                       evt.metaKey, evt.keyCode, evt.charCode);
    target.dispatchEvent(event);
  },

  updateStatusBar: function ()
  {
    var xulBrowserWindow = window.XULBrowserWindow;
    if (!xulBrowserWindow)
      return false;

    if (!this.mFoundLink || !this.mFoundLink.href ||
        this.mFoundLink.href == "") {
      xulBrowserWindow.setOverLink("");
      return true;
    }

    var docCharset = "";
    var ownerDoc = this.mFoundLink.ownerDocument;
    if (ownerDoc)
      docCharset = ownerDoc.characterSet;

    var url =
      this.mTextToSubURIService.unEscapeURIForUI(docCharset,
                                                 this.mFoundLink.href);
    xulBrowserWindow.setOverLink(url);

    return true;
  },

  setFoundLink: function (foundLink)
  {
    if (this.mFoundLink == foundLink)
      return;

    if (this.mFoundLink && this.mDrawOutline) {
      // restore original outline
      this.mFoundLink.style.outline = this.mTmpOutline;
      this.mFoundLink.style.outlineOffset = this.mTmpOutlineOffset;
    }
    this.mDrawOutline = (foundLink && this.mFindMode != FIND_NORMAL);
    if (this.mDrawOutline) {
      // backup original outline
      this.mTmpOutline = foundLink.style.outline;
      this.mTmpOutlineOffset = foundLink.style.outlineOffset;
      // draw pseudo focus rect
      // XXX Should we change the following style for FAYT pseudo focus?
      // XXX Shouldn't we change default design if outline is visible already?
      foundLink.style.outline = "1px dotted invert";
      foundLink.style.outlineOffset = "0";
    }

    this.mFoundLink = foundLink;

    // We should update status bar. But we need delay. If the mouse cursor is
    // on the document, the status bar text is changed by mouse event that is
    // fired by scroll event. So, we need to change the status bar text after
    // mouse event.
    if (this.mFindMode != FIND_NORMAL)
      setTimeout(findBar_UpdateStatusBar, 0);
  },

  finishFAYT: function (aKeypressEvent)
  {
    try {
      if (this.mFoundLink)
        this.mFoundLink.focus();
      else if (this.mFoundEditable) {
        this.mFoundEditable.focus();
        var fastFind = getBrowser().fastFind;
        fastFind.collapseSelection();
      } else if (this.mCurrentWindow)
        this.mCurrentWindow.focus();
      else
        return false;
    }
    catch(e) {
      return false;
    }

    if (aKeypressEvent)
      aKeypressEvent.preventDefault();

    this.closeFindBar();
    return true;
  },

  delayedCloseFindBar: function ()
  {
    var findField = document.getElementById("find-field");
    var findToolbar = document.getElementById("FindToolbar");
    var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                       .getService(Components.interfaces.nsIWindowWatcher);

    if (window == ww.activeWindow) { 
      var focusedElement = document.commandDispatcher.focusedElement;
      if (focusedElement && focusedElement.parentNode &&
            (focusedElement.parentNode == findToolbar ||
             focusedElement.parentNode.parentNode == findField)) {
        // block scrolling on focus since find already scrolls, further
        // scrolling is due to user action, so don't override this
        var suppressedScroll = document.commandDispatcher.suppressFocusScroll;
        document.commandDispatcher.suppressFocusScroll = true;
        // We MUST reset suppressFocusScroll.
        try {
          if (this.mFoundLink)
            this.mFoundLink.focus();
          else if (this.mFoundEditable)
            this.mFoundEditable.focus();
          else if (this.mCurrentWindow)
            this.mCurrentWindow.focus();
          else
            window.content.focus();
        }
        catch(e) {
          // Retry to set focus.
          try {
            window.content.focus();
          }
          catch(e) { /* We lose focused element! */ }
        }
        document.commandDispatcher.suppressFocusScroll = suppressedScroll;
      }
    }

    findToolbar.hidden = true;
    var fastFind = getBrowser().fastFind;
    fastFind.setSelectionModeAndRepaint(this.SELECTION_CONTROLLER.SELECTION_ON);
    this.setFoundLink(null);
    this.mFoundEditable = null;
    this.mCurrentWindow = null;
    if (this.mQuickFindTimeout) {
      clearTimeout(this.mQuickFindTimeout);
      this.mQuickFindTimeout = null;    
    }
  },

  shouldFastFind: function (evt)
  {
    if (evt.ctrlKey || evt.altKey || evt.metaKey || evt.getPreventDefault())
      return false;

    var elt = document.commandDispatcher.focusedElement;
    if (elt) {
      if (elt instanceof HTMLInputElement) {
        // block FAYT when an <input> textfield element is focused
        var inputType = elt.type;
        switch (inputType) {
          case "text":
          case "password":
          case "file":
            return false;
        }
      }
      else if (elt instanceof HTMLTextAreaElement ||
               elt instanceof HTMLSelectElement ||
               elt instanceof HTMLIsIndexElement ||
               elt instanceof HTMLObjectElement ||
               elt instanceof HTMLEmbedElement)
        return false;
    }

    // disable FAYT in about:config and about:blank to prevent FAYT opening
    // unexpectedly - to fix bugs 264562, 267150, 269712
    var url = getBrowser().currentURI.spec;
    if (url == "about:blank" || url == "about:config")
      return false;

    var win = document.commandDispatcher.focusedWindow;
    if (win && win.document.designMode == "on")
      return false;

    return true;
  },

  shouldBeCaseSensitive: function (str)
  {
    if (this.mTypeAheadCaseSensitive == 0)
      return false;
    if (this.mTypeAheadCaseSensitive == 1)
      return true;

    return (str != str.toLowerCase());
  },

  onFindBarBlur: function ()
  {
    var fastFind = getBrowser().fastFind;
    if (this.mFoundEditable) {
      fastFind.collapseSelection();
    } else {
      fastFind.setSelectionModeAndRepaint(this.SELECTION_CONTROLLER.SELECTION_ON);
    }
    this.setFoundLink(null);
    this.mFoundEditable = null;
    this.mCurrentWindow = null;
  },

  onBrowserMouseUp: function (evt)
  {
    var findToolbar = document.getElementById("FindToolbar");
    if (!findToolbar.hidden && this.mFindMode != FIND_NORMAL)
      this.closeFindBar();
  },

  onBrowserKeyPress: function (evt)
  {
    // Check focused elt
    if (!this.shouldFastFind(evt))
      return;

    var findField = document.getElementById("find-field");
    if (this.mFindMode != FIND_NORMAL && this.mQuickFindTimeout) {
      if (!evt.charCode)
        return;
      this.selectFindBar();
      this.focusFindBar();
      this.fireKeypressEvent(findField.inputField, evt);
      evt.preventDefault();
      return;
    }

    if (evt.charCode == CHAR_CODE_APOSTROPHE ||
        evt.charCode == CHAR_CODE_SLASH ||
        (this.mUseTypeAheadFind &&
         evt.charCode && evt.charCode != CHAR_CODE_SPACE)) {
      var findMode =
        (evt.charCode == CHAR_CODE_APOSTROPHE ||
        (this.mTypeAheadLinksOnly && evt.charCode != CHAR_CODE_SLASH)) ?
          FIND_LINKS : FIND_TYPEAHEAD;
      this.setFindMode(findMode);

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

      try {
        var opened = this.openFindBar(true);
      }
      catch (e) {
        return;
      }    
      if (opened) {
        this.setFindCloseTimeout();
        this.selectFindBar();
        this.focusFindBar();
        if (this.mUseTypeAheadFind &&
            evt.charCode != CHAR_CODE_APOSTROPHE &&
            evt.charCode != CHAR_CODE_SLASH)
          this.fireKeypressEvent(findField.inputField, evt);
        else
          this.updateStatus(Components.interfaces.nsITypeAheadFind.FIND_FOUND, false);
        evt.preventDefault();
      }
      else {
        this.selectFindBar();
        this.focusFindBar();
        if (this.mFindMode != FIND_NORMAL) {
          if (evt.charCode != CHAR_CODE_APOSTROPHE &&
              evt.charCode != CHAR_CODE_SLASH)
            this.fireKeypressEvent(findField.inputField, evt);
          else
            this.updateStatus(Components.interfaces.nsITypeAheadFind.FIND_FOUND, false);
          evt.preventDefault();
        }
      }
    }
  },

  onFindBarKeyPress: function (evt)
  {
    if (evt.keyCode == KeyEvent.DOM_VK_RETURN) {
      if (this.mFindMode == FIND_NORMAL) {
        var findString = document.getElementById("find-field");
        if (!findString.value)
          return;

#ifdef XP_MACOSX
        if (evt.metaKey) {
#else
        if (evt.ctrlKey) {
#endif
          document.getElementById("highlight").click();
          return;
        }

        if (evt.shiftKey)
          this.findPrevious();
        else
          this.findNext();
      }
      else {
        if (this.mFoundLink) {
          var tmpLink = this.mFoundLink;
          if (this.finishFAYT(evt))
            this.fireKeypressEvent(tmpLink, evt);
        }
      }
    }
    else if (evt.keyCode == KeyEvent.DOM_VK_TAB) {
      var shouldHandle = !evt.altKey && !evt.ctrlKey && !evt.metaKey;
      if (shouldHandle && this.mFindMode != FIND_NORMAL &&
          this.finishFAYT(evt)) {
        if (evt.shiftKey)
          document.commandDispatcher.rewindFocus();
        else
          document.commandDispatcher.advanceFocus();
      }
    }
    else if (evt.keyCode == KeyEvent.DOM_VK_ESCAPE) {
      this.closeFindBar();
      evt.preventDefault();
    } 
    else if (evt.keyCode == KeyEvent.DOM_VK_PAGE_UP) {
      window.top._content.scrollByPages(-1);
      evt.preventDefault();
    }
    else if (evt.keyCode == KeyEvent.DOM_VK_PAGE_DOWN) {
      window.top._content.scrollByPages(1);
      evt.preventDefault();
    }
    else if (evt.keyCode == KeyEvent.DOM_VK_UP) {
      window.top._content.scrollByLines(-1);
      evt.preventDefault();
    }
    else if (evt.keyCode == KeyEvent.DOM_VK_DOWN) {
      window.top._content.scrollByLines(1);
      evt.preventDefault();
    }
  },

  enableFindButtons: function (aEnable)
  {
    var findNext = document.getElementById("find-next");
    var findPrev = document.getElementById("find-previous");
    var highlight = document.getElementById("highlight");
    findNext.disabled = findPrev.disabled = highlight.disabled = !aEnable;
  },

  /**
   * Determines whether minimalist or general-purpose search UI is to be
   * displayed when the find bar is activated.
   *
   * @param showMinimalUI
   *        true if minimalist UI should be used, false if general-purpose UI
   *        should be used
   */
  updateFindUI: function (showMinimalUI)
  {
    this.mUsingMinimalUI = showMinimalUI;
    
    var findBar = document.getElementById("FindToolbar");
    for (var i = 0; i < findBar.childNodes.length; i++) {
      var node = findBar.childNodes[i];
      if (node.className == "find-fast")
        continue;
      
      node.hidden = showMinimalUI;
    }

    var findLabel = document.getElementById("find-label");
    if (showMinimalUI)
      findLabel.value = this.mFastFindStr;
    else
      findLabel.value = this.mNormalFindStr;
  },
  
  updateFoundLink: function (res)
  {
    var val = document.getElementById("find-field").value;
    if (res == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND || !val) {
      this.setFoundLink(null);
      this.mFoundEditable = null;
      this.mCurrentWindow = null;
    } else {
      this.setFoundLink(getBrowser().fastFind.foundLink);
      this.mFoundEditable = getBrowser().fastFind.foundEditable;
      this.mCurrentWindow = getBrowser().fastFind.currentWindow;
    }
  },

  find: function (val)
  {
    if (!val)
      val = document.getElementById("find-field").value;

    this.enableFindButtons(val);

    var highlightBtn = document.getElementById("highlight");
    if (highlightBtn.checked)
      this.setHighlightTimeout();

    this.setCaseSensitivity(val);

    var fastFind = getBrowser().fastFind;
    var res = fastFind.find(val, this.mFindMode == FIND_LINKS);
    this.updateFoundLink(res);
    this.updateStatus(res, true);

    if (this.mFindMode != FIND_NORMAL)
      this.setFindCloseTimeout();
  },

  flashFindBar: function ()
  {
    var findToolbar = document.getElementById("FindToolbar");
    if (this.mFlashFindBarCount-- == 0) {
      clearInterval(this.mFlashFindBarTimeout);
      findToolbar.removeAttribute("flash");
      this.mFlashFindBarCount = 6;
      return;
    }
   
    findToolbar.setAttribute("flash",
                             (this.mFlashFindBarCount % 2 == 0) ?
                             "false" : "true");
  },

  onFindCmd: function ()
  {
    try {
      this.openFindBar();
    }
    catch (e) {
    }
    this.setFindMode(FIND_NORMAL);
    if (this.mFlashFindBar) {
      this.mFlashFindBarTimeout = setInterval(findBar_FlashFindBar, 500);
      var prefService =
            Components.classes["@mozilla.org/preferences-service;1"]
                      .getService(Components.interfaces.nsIPrefBranch);

      prefService.setIntPref("accessibility.typeaheadfind.flashBar",
                             --this.mFlashFindBar);
    }
    this.selectFindBar();
    this.focusFindBar();
  },

  onFindAgainCmd: function ()
  {
    var findString = getBrowser().findString;
    if (!findString) {
      this.onFindCmd();
      return;
    }

    var res = this.findNext();
    if (res == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND) {
      try {
        var opened = this.openFindBar(this.mUsingMinimalUI);
      }
      catch(e) {
      }
      if (opened) {
        this.focusFindBar();
        this.selectFindBar();
        if (this.mFindMode != FIND_NORMAL)
          this.setFindCloseTimeout();

        this.updateStatus(res, true);
      }
    }
  },

  onFindPreviousCmd: function ()
  {
    var findString = getBrowser().findString;
    if (!findString) {
      this.onFindCmd();
      return;
    }

    var res = this.findPrevious();
    if (res == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND) {
      try {
        var opened = this.openFindBar(this.mUsingMinimalUI);
      }
      catch (e) {
      }
      if (opened) {
        this.focusFindBar();
        this.selectFindBar();
        if (this.mFindMode != FIND_NORMAL)
          this.setFindCloseTimeout();

        this.updateStatus(res, false);
      }
    }
  },

  setHighlightTimeout: function ()
  {
    if (this.mHighlightTimeout)
      clearTimeout(this.mHighlightTimeout);
    this.mHighlightTimeout =
      setTimeout(function() {
                   gFindBar.toggleHighlight(false);
                   gFindBar.toggleHighlight(true);
                 }, 500);  
  },

  isFindBarVisible: function ()
  {
    var findBar = document.getElementById("FindToolbar");
    return !findBar.hidden;
  },

  findNext: function ()
  {
    var fastFind = getBrowser().fastFind; 
    var res = fastFind.findNext();  
    this.updateFoundLink(res);
    this.updateStatus(res, true);

    if (this.mFindMode != FIND_NORMAL && this.isFindBarVisible())
      this.setFindCloseTimeout();

    return res;
  },

  findPrevious: function ()
  {
    var fastFind = getBrowser().fastFind;
    var res = fastFind.findPrevious();
    this.updateFoundLink(res);
    this.updateStatus(res, false);

    if (this.mFindMode != FIND_NORMAL && this.isFindBarVisible())
      this.setFindCloseTimeout();

    return res;
  },

  updateStatus: function (res, findNext)
  {
    var findBar = document.getElementById("FindToolbar");
    var field = document.getElementById("find-field");
    var statusIcon = document.getElementById("find-status-icon");
    var statusText = document.getElementById("find-status");
    switch(res) {
      case Components.interfaces.nsITypeAheadFind.FIND_WRAPPED:
        statusIcon.setAttribute("status", "wrapped");      
        statusText.value =
          findNext ? this.mWrappedToTopStr : this.mWrappedToBottomStr;
        break;
      case Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND:
        statusIcon.setAttribute("status", "notfound");
        statusText.value = this.mNotFoundStr;
        field.setAttribute("status", "notfound");      
        break;
      case Components.interfaces.nsITypeAheadFind.FIND_FOUND:
      default:
        statusIcon.removeAttribute("status");      
        statusText.value = "";
        field.removeAttribute("status");
        break;
    }
  },

  setFindCloseTimeout: function ()
  {
    if (this.mQuickFindTimeout)
      clearTimeout(this.mQuickFindTimeout);

    // Don't close the find toolbar while IME is composing.
    if (this.mIsIMEComposing) {
      this.mQuickFindTimeout = null;
      return;
    }

    this.mQuickFindTimeout =
      setTimeout(function() {
                   if (gFindBar.mFindMode != FIND_NORMAL)
                     gFindBar.closeFindBar();
                 },
                 this.mQuickFindTimeoutLength);
  },

  findBarOnDrop: function (evt)
  {
    nsDragAndDrop.drop(evt, this.mFindbarObserver);
  },

  onFindBarCompositionStart: function (evt)
  {
    this.mIsIMEComposing = true;
    // Don't close the find toolbar while IME is composing.
    if (this.mQuickFindTimeout) {
      clearTimeout(this.mQuickFindTimeout);
      this.mQuickFindTimeout = null;
    }
  },

  onFindBarCompositionEnd: function (evt)
  {
    this.mIsIMEComposing = false;
    if (this.mFindMode != FIND_NORMAL && this.isFindBarVisible())
      this.setFindCloseTimeout();
  },

  setFindMode: function (mode)
  {
    this.mFindMode = mode;
  }
};

// ================
//  Event Handlers
// ================
function findBar_OnDrop(aEvt)
{
  gFindBar.findBarOnDrop(aEvt);
}

function findBar_OnBrowserKeyPress(aEvt)
{
  gFindBar.onBrowserKeyPress(aEvt);
}

function findBar_OnBrowserMouseUp(aEvt)
{
  gFindBar.onBrowserMouseUp(aEvt);
}

// ======================
//  Timer Event Handlers
// ======================
function findBar_DelayedCloseFindBar()
{
  gFindBar.delayedCloseFindBar();
}

function findBar_UpdateStatusBar()
{
  gFindBar.updateStatusBar();
}

function findBar_FlashFindBar()
{
  gFindBar.flashFindBar();
}
