Bug 916481 - Make Finder.jsm send back the selection rect of the find result [r=evilpie,emtwo]
authorMatt Brubeck <mbrubeck@mozilla.com>
Thu, 09 Jan 2014 07:58:26 -0800
changeset 162803 18acca8fc68166f566149926a58fe6db951d1ec3
parent 162802 faa87475ab44087f7a5e92ef3e365da57aa6ad6d
child 162804 f3e7fa7d9748627cc89644b378a0e4d04d88d1ef
push id25971
push userkwierso@gmail.com
push dateFri, 10 Jan 2014 00:50:13 +0000
treeherdermozilla-central@37516445a0b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie, emtwo
bugs916481
milestone29.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 916481 - Make Finder.jsm send back the selection rect of the find result [r=evilpie,emtwo]
browser/metro/base/content/helperui/FindHelperUI.js
browser/metro/base/tests/mochitest/browser_findbar.html
browser/metro/base/tests/mochitest/browser_findbar.js
toolkit/modules/Finder.jsm
toolkit/modules/RemoteFinder.jsm
--- a/browser/metro/base/content/helperui/FindHelperUI.js
+++ b/browser/metro/base/content/helperui/FindHelperUI.js
@@ -166,18 +166,21 @@ var FindHelperUI = {
   goToPrevious: function findHelperGoToPrevious() {
     this.searchAgain(this._searchString, true);
   },
 
   goToNext: function findHelperGoToNext() {
     this.searchAgain(this._searchString, false);
   },
 
-  onFindResult: function(aResult, aFindBackwards, aLinkURL) {
+  onFindResult: function(aResult, aFindBackwards, aLinkURL, aRect) {
     this._status = aResult;
+    if (aRect) {
+      this._zoom(aRect, Browser.selectedBrowser.contentDocumentHeight);
+    }
     this.updateCommands();
   },
 
   updateCommands: function findHelperUpdateCommands() {
     let disabled = (this._status == Ci.nsITypeAheadFind.FIND_NOTFOUND) || (this._searchString == "");
     this._cmdPrevious.setAttribute("disabled", disabled);
     this._cmdNext.setAttribute("disabled", disabled);
   },
--- a/browser/metro/base/tests/mochitest/browser_findbar.html
+++ b/browser/metro/base/tests/mochitest/browser_findbar.html
@@ -1,10 +1,11 @@
 <!DOCTYPE html>
 <html>
   <head>
     <meta charset="UTF-8">
     <title>Find bar tests</title>
   </head>
   <body>
-    <p>Find bar tests</title>
+    <p>Find bar tests</p>
+    <p style="position: absolute; bottom: 0;">bottom</p>
   </body>
 </html>
--- a/browser/metro/base/tests/mochitest/browser_findbar.js
+++ b/browser/metro/base/tests/mochitest/browser_findbar.js
@@ -83,8 +83,50 @@ gTests.push({
 
     EventUtils.synthesizeMouse(document.getElementById("findbar-close-button"), 1, 1, {});
     yield waitForEvent(Elements.findbar, "transitionend");
     is(Elements.findbar.isShowing, false, "Hide find bar with close button");
 
     Browser.closeTab(tab);
   }
 });
+
+gTests.push({
+  desc: "Text at bottom of screen is not obscured by findbar",
+  run: function() {
+    let textbox = document.getElementById("findbar-textbox");
+
+    let tab = yield addTab(chromeRoot + "browser_findbar.html");
+    yield waitForCondition(() => BrowserUI.ready);
+    is(Elements.findbar.isShowing, false, "Find bar is hidden by default");
+
+    FindHelperUI.show();
+    yield waitForCondition(() => FindHelperUI.isActive);
+
+    EventUtils.sendString("bottom");
+    let event = yield waitForEvent(window, "MozDeckOffsetChanged");
+    ok(!(event instanceof Error), "MozDeckOffsetChanged received (1)");
+    ok(event.detail > 0, "Browser deck shifted upward");
+
+    textbox.select();
+    EventUtils.sendString("bar");
+    event = yield waitForEvent(window, "MozDeckOffsetChanged");
+    ok(!(event instanceof Error), "MozDeckOffsetChanged received (2)");
+    is(event.detail, 0, "Browser deck shifted back to normal");
+
+    textbox.select();
+    EventUtils.sendString("bottom");
+    event = yield waitForEvent(window, "MozDeckOffsetChanged");
+    ok(!(event instanceof Error), "MozDeckOffsetChanged received (3)");
+    ok(event.detail > 0, "Browser deck shifted upward again");
+
+    let waitForDeckOffset = waitForEvent(window, "MozDeckOffsetChanged");
+    let waitForTransitionEnd = waitForEvent(Elements.findbar, "transitionend");
+    FindHelperUI.hide();
+    event = yield waitForDeckOffset;
+    ok(!(event instanceof Error), "MozDeckOffsetChanged received (4)");
+    is(event.detail, 0, "Browser deck shifted back to normal when findbar hides");
+
+    // Cleanup.
+    yield waitForTransitionEnd;
+    Browser.closeTab(tab);
+  }
+});
--- a/toolkit/modules/Finder.jsm
+++ b/toolkit/modules/Finder.jsm
@@ -4,17 +4,18 @@
 
 this.EXPORTED_SYMBOLS = ["Finder"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
+Cu.import("resource://gre/modules/Geometry.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 function Finder(docShell) {
   this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
   this._fastFind.init(docShell);
 
   this._docShell = docShell;
   this._listeners = [];
   this._previousLink = null;
@@ -50,18 +51,20 @@ Finder.prototype = {
       if (!this._textToSubURIService) {
         this._textToSubURIService = Cc["@mozilla.org/intl/texttosuburi;1"]
                                       .getService(Ci.nsITextToSubURI);
       }
 
       linkURL = this._textToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
     }
 
+    let rect = this._getResultRect();
+
     for (let l of this._listeners) {
-      l.onFindResult(aResult, aFindBackwards, linkURL);
+      l.onFindResult(aResult, aFindBackwards, linkURL, rect);
     }
   },
 
   get searchString() {
     return this._searchString;
   },
 
   set caseSensitive(aSensitive) {
@@ -181,16 +184,58 @@ Finder.prototype = {
         break;
     }
   },
 
   _getWindow: function () {
     return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
   },
 
+  /**
+   * Get the bounding selection rect in CSS px relative to the origin of the
+   * top-level content document.
+   */
+  _getResultRect: function () {
+    let topWin = this._getWindow();
+    let win = this._fastFind.currentWindow;
+    if (!win)
+      return null;
+
+    let selection = win.getSelection();
+    if (!selection.rangeCount || selection.isCollapsed) {
+      // The selection can be into an input or a textarea element.
+      let nodes = win.document.querySelectorAll("input, textarea");
+      for (let node of nodes) {
+        if (node instanceof Ci.nsIDOMNSEditableElement && node.editor) {
+          let sc = node.editor.selectionController;
+          selection = sc.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
+          if (selection.rangeCount && !selection.isCollapsed) {
+            break;
+          }
+        }
+      }
+    }
+
+    let utils = topWin.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDOMWindowUtils);
+
+    let scrollX = {}, scrollY = {};
+    utils.getScrollXY(false, scrollX, scrollY);
+
+    for (let frame = win; frame != topWin; frame = frame.parent) {
+      let rect = frame.frameElement.getBoundingClientRect();
+      let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
+      let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
+      scrollX.value += rect.left + parseInt(left, 10);
+      scrollY.value += rect.top + parseInt(top, 10);
+    }
+    let rect = Rect.fromRect(selection.getRangeAt(0).getBoundingClientRect());
+    return rect.translate(scrollX.value, scrollY.value);
+  },
+
   _outlineLink: function (aDrawOutline) {
     let foundLink = this._fastFind.foundLink;
 
     // Optimization: We are drawing outlines and we matched
     // the same link before, so don't duplicate work.
     if (foundLink == this._previousLink && aDrawOutline)
       return;
 
--- a/toolkit/modules/RemoteFinder.jsm
+++ b/toolkit/modules/RemoteFinder.jsm
@@ -30,17 +30,17 @@ RemoteFinder.prototype = {
     this._listeners = this._listeners.filter(l => l != aListener);
   },
 
   receiveMessage: function (aMessage) {
     this._searchString = aMessage.data.searchString;
 
     for (let l of this._listeners) {
       l.onFindResult(aMessage.data.result, aMessage.data.findBackwards,
-                     aMessage.data.linkURL);
+                     aMessage.data.linkURL, aMessage.data.rect);
     }
   },
 
   get searchString() {
     return this._searchString;
   },
 
   set caseSensitive(aSensitive) {
@@ -103,20 +103,24 @@ RemoteFinderListener.prototype = {
     "Finder:FindAgain",
     "Finder:Highlight",
     "Finder:EnableSelection",
     "Finder:RemoveSelection",
     "Finder:FocusContent",
     "Finder:KeyPress"
   ],
 
-  onFindResult: function (aResult, aFindBackwards, aLinkURL) {
-    let data = { result: aResult, findBackwards: aFindBackwards,
-                 linkURL: aLinkURL, searchString: this._finder.searchString };
-    this._global.sendAsyncMessage("Finder:Result", data);
+  onFindResult: function (aResult, aFindBackwards, aLinkURL, aRect) {
+    this._global.sendAsyncMessage("Finder:Result", {
+      result: aResult,
+      findBackwards: aFindBackwards,
+      linkURL: aLinkURL,
+      rect: aRect,
+      searchString: this._finder.searchString,
+    });
   },
 
   //XXXmikedeboer-20131016: implement |shouldFocusContent| here to mitigate
   //                        issues like bug 921338 and bug 921308.
   shouldFocusContent: function () {
     return true;
   },