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 110007 2af93a18b6b166b49c024e67845924f27b924409
parent 110006 50ca3a4230c8f1cf9b4dc60e061ef9c4356cbf26
child 110008 e513d3aa6a1e5491e3ef47c50df1747d76092009
push id16326
push userryanvm@gmail.com
push dateFri, 12 Oct 2012 01:31:45 +0000
treeherdermozilla-inbound@e05e9c4666e1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdtownsend
bugs793275
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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");
+}