Bug 793275 - Add findbar events that can be intercepted. r=dtownsend
authorBrendan Dahl <bdahl@mozilla.com>
Thu, 04 Oct 2012 16:53:49 -0700
changeset 110141 2af93a18b6b166b49c024e67845924f27b924409
parent 110140 50ca3a4230c8f1cf9b4dc60e061ef9c4356cbf26
child 110142 e513d3aa6a1e5491e3ef47c50df1747d76092009
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersdtownsend
bugs793275
milestone19.0a1
Bug 793275 - Add findbar events that can be intercepted. r=dtownsend
toolkit/components/typeaheadfind/nsITypeAheadFind.idl
toolkit/content/tests/chrome/Makefile.in
toolkit/content/tests/chrome/findbar_events_window.xul
toolkit/content/tests/chrome/test_findbar_events.xul
toolkit/content/widgets/findbar.xml
toolkit/themes/gnomestripe/global/findBar.css
toolkit/themes/pinstripe/global/findBar.css
toolkit/themes/winstripe/global/findBar.css
--- a/toolkit/components/typeaheadfind/nsITypeAheadFind.idl
+++ b/toolkit/components/typeaheadfind/nsITypeAheadFind.idl
@@ -71,16 +71,18 @@ interface nsITypeAheadFind : nsISupports
 
   /* Find return codes */
   const unsigned short FIND_FOUND    = 0;
                                         // Successful find
   const unsigned short FIND_NOTFOUND = 1;
                                         // Unsuccessful find
   const unsigned short FIND_WRAPPED  = 2;
                                         // Successful find, but wrapped around
+  const unsigned short FIND_PENDING  = 3;
+                                        // Unknown status, find has not finished
 
 
   /*************************************************************************/
 
 };
 
 
 /*****************************************************************************/
--- a/toolkit/content/tests/chrome/Makefile.in
+++ b/toolkit/content/tests/chrome/Makefile.in
@@ -11,16 +11,18 @@ relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 DIRS = rtltest rtlchrome
 
 
 MOCHITEST_CHROME_FILES = 	findbar_window.xul \
 		test_findbar.xul \
+		findbar_events_window.xul \
+		test_findbar_events.xul \
 		test_bug253481.xul \
 		bug263683_window.xul \
 		test_bug263683.xul \
 		bug304188_window.xul \
 		test_bug304188.xul \
 		bug331215_window.xul \
 		test_bug331215.xul \
 		bug360437_window.xul \
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/findbar_events_window.xul
@@ -0,0 +1,153 @@
+<?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/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="FindbarTest"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        width="600"
+        height="600"
+        onload="SimpleTest.executeSoon(startTest);"
+        title="findbar events test">
+
+  <script type="application/javascript"><![CDATA[
+    const Ci = Components.interfaces;
+    const Cc = Components.classes;
+    const Cr = Components.results;
+
+    var gFindBar = null;
+    var gBrowser;
+
+    var imports = ["SimpleTest", "ok"];
+    for each (var name in imports) {
+      window[name] = window.opener.wrappedJSObject[name];
+    }
+
+    function finish() {
+      window.close();
+      SimpleTest.finish();
+    }
+
+    function startTest() {
+      gFindBar = document.getElementById("FindToolbar");
+      gBrowser = document.getElementById("content");
+      gBrowser.addEventListener("pageshow", onPageShow, false);
+      gBrowser.loadURI('data:text/html,hello there');
+    }
+
+    var tests = [
+      testFind,
+      testFindAgain,
+      testCaseSensitivity,
+      testHighlight,
+      finish
+    ];
+
+    // Iterates through the above tests and takes care of passing the done
+    // callback for any async tests.
+    function nextTest() {
+      if (!tests.length) {
+        return;
+      }
+      var func = tests.shift();
+      if (!func.length) {
+        // Test isn't async advance to the next test here.
+        func();
+        SimpleTest.executeSoon(nextTest);
+      } else {
+        func(nextTest);
+      }
+    }
+
+    function onPageShow() {
+      gFindBar.open();
+      gFindBar.onFindCommand();
+      nextTest();
+    }
+
+    function checkSelection(done) {
+      SimpleTest.executeSoon(function() {
+        var selected = gBrowser.contentWindow.getSelection();
+        ok(selected == "", "No text is selected");
+
+        var controller = gFindBar.browser.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsISelectionDisplay)
+                                 .QueryInterface(Ci.nsISelectionController);
+        var selection = controller.getSelection(controller.SELECTION_FIND);
+        ok(selection.rangeCount == 0, "No text is highlighted");
+        done();
+      });
+    }
+
+    function testFind(done) {
+      var findTriggered = false;
+      var query = "t";
+      gFindBar.addEventListener("find", function(e) {
+        eventTriggered = true;
+        ok(e.detail.query === query, "find event query should match '" + query + "'");
+        e.preventDefault();
+        // Since we're preventing the default make sure nothing was selected.
+        checkSelection(done);
+      });
+
+      // Put some text in the find box.
+      var event = document.createEvent("KeyEvents");
+      event.initKeyEvent("keypress", true, true, null, false, false,
+                         false, false, 0, query.charCodeAt(0));
+      gFindBar._findField.inputField.dispatchEvent(event);
+      ok(eventTriggered, "find event should be triggered");
+    }
+
+    function testFindAgain(done) {
+      var eventTriggered = false;
+      gFindBar.addEventListener("findagain", function(e) {
+        eventTriggered = true;
+        e.preventDefault();
+        // Since we're preventing the default make sure nothing was selected.
+        checkSelection(done);
+      });
+
+      gFindBar.onFindAgainCommand();
+      ok(eventTriggered, "findagain event should be triggered");
+    }
+
+    function testCaseSensitivity() {
+      var eventTriggered = false;
+      gFindBar.addEventListener("findcasesensitivitychange", function(e) {
+        eventTriggered = true;
+        ok(e.detail.caseSensitive, "find should be case sensitive");
+      });
+
+      var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive");
+      matchCaseCheckbox.click();
+      ok(eventTriggered, "findcasesensitivitychange should be triggered");
+    }
+
+    function testHighlight(done) {
+      // Update the find state so the highlight button is clickable.
+      gFindBar.updateControlState(Ci.nsITypeAheadFind.FIND_FOUND, false);
+      var eventTriggered = false;
+      gFindBar.addEventListener("findhighlightallchange", function(e) {
+        eventTriggered = true;
+        ok(e.detail.highlightAll, "find event should have highlight all set");
+        e.preventDefault();
+        // Since we're preventing the default make sure nothing was highlighted.
+        SimpleTest.executeSoon(function() {
+          checkSelection(done);
+        });
+      });
+
+      var highlightButton = gFindBar.getElement("highlight");
+      if (!highlightButton.checked) {
+        highlightButton.click();
+      }
+      ok(eventTriggered, "findhighlightallchange should be triggered");
+    }
+  ]]></script>
+
+  <browser type="content-primary" flex="1" id="content" src="about:blank"/>
+  <findbar id="FindToolbar" browserid="content"/>
+</window>
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/test_findbar_events.xul
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+  href="chrome://mochikit/content/tests/SimpleTest/test.css"
+  type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=793275
+-->
+<window title="Mozilla Bug 793275"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <title>Test for Bug 793275</title>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <body  xmlns="http://www.w3.org/1999/xhtml">
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=793275">
+      Mozilla Bug 793275
+    </a>
+
+    <p id="display"></p>
+    <div id="content" style="display: none">
+    </div>
+    <pre id="test">
+    </pre>
+  </body>
+
+  <script class="testbody" type="application/javascript">
+    <![CDATA[
+
+      /** Test for Bug 793275 **/
+      SimpleTest.waitForExplicitFinish();
+      window.open("findbar_events_window.xul", "793275test",
+                  "chrome,width=600,height=600");
+
+    ]]>
+  </script>
+
+</window>
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -884,16 +884,19 @@
       <!--
         - Turns highlight on or off.
         - @param aHighlight (boolean)
         -        Whether to turn the highlight on or off
         -->
       <method name="toggleHighlight">
         <parameter name="aHighlight"/>
         <body><![CDATA[
+          if (!this._dispatchFindEvent("highlightallchange"))
+            return;
+
           var word = this._findField.value;
 
           // Bug 429723. Don't attempt to highlight ""
           if (aHighlight && !word)
             return;
 
           // We have to update the status because we might still have the status
           // of another tab
@@ -1075,16 +1078,18 @@
         <body><![CDATA[
           var prefsvc =
             Components.classes["@mozilla.org/preferences-service;1"]
                       .getService(Components.interfaces.nsIPrefBranch);
 
           // Just set the pref; our observer will change the find bar behavior
           prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive",
                              aCaseSensitive ? 1 : 0);
+
+          this._dispatchFindEvent("casesensitivitychange");
         ]]></body>
       </method>
 
       <!--
         - Opens and displays the find bar.
         -
         - @param aMode
         -        the find mode to be used, which is either FIND_NORMAL,
@@ -1507,16 +1512,19 @@
             this._currentWindow = this.browser.fastFind.currentWindow;
           }
         ]]></body>
       </method>
 
       <method name="_find">
         <parameter name="aValue"/>
         <body><![CDATA[
+          if (!this._dispatchFindEvent(""))
+            return this.nsITypeAheadFind.FIND_PENDING;
+
           var val = aValue || this._findField.value;
           var res = this.nsITypeAheadFind.FIND_NOTFOUND;
 
           // Only search on input if we don't have a last-failed string,
           // or if the current search string doesn't start with it.
           if (this._findFailedString == null ||
               val.indexOf(this._findFailedString) != 0)
           {
@@ -1613,26 +1621,41 @@
                 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="_getInitialSelection">
         <body><![CDATA[
           var focusedElement = document.commandDispatcher.focusedElement;
           var selText;
 
           if (focusedElement instanceof Components.interfaces.nsIDOMNSEditableElement &&
               focusedElement.editor &&
               focusedElement.ownerDocument.defaultView.top == this._browser.contentWindow)
@@ -1660,16 +1683,32 @@
           }
           return selText.replace(/^\s+/, "")
                         .replace(/\s+$/, "")
                         .replace(/\s+/g, " ")
                         .substr(0, this._selectionMaxLen);
         ]]></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,
+            highlightAll: this.getElement("highlight").checked,
+            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.
         -->
@@ -1728,16 +1767,23 @@
         <parameter name="aFindPrevious"/>
         <body><![CDATA[
           var findString = this._browser.fastFind.searchString || this._findField.value;
           if (!findString) {
             this.startFind();
             return;
           }
 
+          // We dispatch the findAgain event here instead of in _findAgain since
+          // if there is a find event handler that prevents the default then
+          // fastFind.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;
+
           // user explicitly requested another search, so do it even if we think it'll fail
           this._findFailedString = null;
 
           var res;
           // Ensure the stored SearchString is in sync with what we want to find
           if (this._findField.value != this._browser.fastFind.searchString)
             res = this._find(this._findField.value);
           else
--- a/toolkit/themes/gnomestripe/global/findBar.css
+++ b/toolkit/themes/gnomestripe/global/findBar.css
@@ -55,16 +55,20 @@ findbar {
   -moz-margin-end: 0 !important;
   padding: 2px !important;
 }
 
 .find-status-icon[status="notfound"] {
   list-style-image: url("moz-icon://stock/gtk-dialog-error?size=menu");
 }
 
+.find-status-icon[status="pending"] {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
 .findbar-textbox[status="notfound"] {
   box-shadow: 0 0 0 1em #f66 inset;
   color: white;
 }
 
 .findbar-textbox[flash="true"] {
   box-shadow: 0 0 0 1em yellow inset;
   color: black;
--- a/toolkit/themes/pinstripe/global/findBar.css
+++ b/toolkit/themes/pinstripe/global/findBar.css
@@ -229,15 +229,20 @@ label.findbar-find-fast:-moz-lwtheme,
   background-color: #FFFF00;
   border-color: #818100;
 }
 
 .find-status-icon {
   display: none;
 }
 
+.find-status-icon[status="pending"] {
+  display: block;
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
 .findbar-find-status {
   color: #436599;
   font-weight: bold;
   text-shadow: 0 1px rgba(255, 255, 255, .4);
   margin: 1px 1px 0 !important;
   -moz-margin-start: 12px !important;
 }
--- a/toolkit/themes/winstripe/global/findBar.css
+++ b/toolkit/themes/winstripe/global/findBar.css
@@ -109,8 +109,12 @@ findbar {
 .findbar-textbox[flash="true"] {
   box-shadow: 0 0 0 1em yellow inset;
   color: black;
 }
 
 .find-status-icon[status="wrapped"] {
   list-style-image: url("chrome://global/skin/icons/wrap.png");
 }
+
+.find-status-icon[status="pending"] {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}