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 162719 18acca8fc68166f566149926a58fe6db951d1ec3
parent 162718 faa87475ab44087f7a5e92ef3e365da57aa6ad6d
child 162720 f3e7fa7d9748627cc89644b378a0e4d04d88d1ef
push id4259
push usermbrubeck@mozilla.com
push dateThu, 09 Jan 2014 15:59:43 +0000
treeherderfx-team@18acca8fc681 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie, emtwo
bugs916481
milestone29.0a1
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;
   },