Bug 573041 - Clicker code should happen on content side [r=mfinkle]
authorVivien Nicolas <21@vingtetun.org>
Mon, 21 Jun 2010 22:16:37 +0200
changeset 66301 a00c3a60c136dd56dca773f316f20f1e561a0023
parent 66300 21a1e676bc9d3265e1a4859764d9d93f08955a36
child 66302 13684cca16292dbeadf6b101c541209abc66f866
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs573041
Bug 573041 - Clicker code should happen on content side [r=mfinkle]
mobile/chrome/content/Util.js
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.js
mobile/chrome/content/browser.xul
mobile/chrome/content/content.js
--- a/mobile/chrome/content/Util.js
+++ b/mobile/chrome/content/Util.js
@@ -312,18 +312,18 @@ Point.prototype = {
  *
  * NOTE: Since its operations are closed, rectangles may be empty and will report
  * non-positive widths and heights in that case.
  */
 
 function Rect(x, y, w, h) {
   this.left = x;
   this.top = y;
-  this.right = x+w;
-  this.bottom = y+h;
+  this.right = x + w;
+  this.bottom = y + h;
 };
 
 Rect.fromRect = function fromRect(r) {
   return new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
 }
 
 Rect.prototype = {
   get x() { return this.left; },
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -402,32 +402,33 @@ var BrowserUI = {
     // listen content messages
     messageManager.addMessageListener("DOMLinkAdded", this);
     messageManager.addMessageListener("DOMTitleChanged", this);
     messageManager.addMessageListener("DOMWillOpenModalDialog", this);
     messageManager.addMessageListener("DOMWindowClose", this);
 
     // listen returns messages from content
     messageManager.addMessageListener("Browser:SaveAs:Return", this);
+    messageManager.addMessageListener("Browser:Highlight", this);
 
     // listening mousedown for automatically dismiss some popups (e.g. larry)
     window.addEventListener("mousedown", this, true);
 
     // listening escape to dismiss dialog on VK_ESCAPE
     window.addEventListener("keypress", this, true);
 
     // listening AppCommand to handle special keys
     window.addEventListener("AppCommand", this, true);
 
     // Push the panel initialization out of the startup path
     // (Using a message because we have no good way to delay-init [Bug 535366])
     messageManager.addMessageListener("DOMContentLoaded", function() {
       // We only want to delay one time
       messageManager.removeMessageListener("DOMContentLoaded", arguments.callee, true);
-      
+
       // We unhide the panelUI so the XBL and settings can initialize
       Elements.panelUI.hidden = false;
 
       // Init the views
       ExtensionsView.init();
       DownloadsView.init();
       PreferencesView.init();
       ConsoleView.init();
@@ -775,32 +776,32 @@ var BrowserUI = {
       case "error":
         this._favicon.src = "";
         break;
     }
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     let browser = aMessage.target;
+    let json = aMessage.json;
     switch (aMessage.name) {
       case "DOMTitleChanged":
         this._titleChanged(browser);
         break;
       case "DOMWillOpenModalDialog":
         return this._domWillOpenModalDialog(browser);
         break;
       case "DOMWindowClose":
         return this._domWindowClose(browser);
         break;
       case "DOMLinkAdded":
         if (Browser.selectedBrowser == browser)
           this._updateIcon(Browser.selectedBrowser.mIconURL);
         break;
       case "Browser:SaveAs:Return":
-        let json = aMessage.json;
         if (json.type != Ci.nsIPrintSettings.kOutputFormatPDF)
           return;
 
         let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
         let db = dm.DBConnection;
         let stmt = db.createStatement("UPDATE moz_downloads SET endTime = :endTime, state = :state WHERE id = :id");
         stmt.params.endTime = Date.now() * 1000;
         stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
@@ -816,16 +817,25 @@ var BrowserUI = {
           element.setAttribute("endTime", Date.now());
           element.setAttribute("referrer", json.referrer);
           DownloadsView._updateTime(element);
           DownloadsView._updateStatus(element);
         }
         catch(e) {}
         gObserverService.notifyObservers(download, "dl-done", null);
         break;
+
+      case "Browser:Highlight":
+        let rects = [];
+        for (let i = 0; i < json.rects.length; i++) {
+          let rect = json.rects[i];
+          rects.push(new Rect(rect.left, rect.top, rect.width, rect.height));
+        }
+        TapHighlightHelper.show(rects);
+        break;
     }
 
     return {};
   },
 
   supportsCommand : function(cmd) {
     var isSupported = false;
     switch (cmd) {
@@ -977,16 +987,58 @@ var BrowserUI = {
         alerts.showAlertNotification(null, strings.getString("alertLockScreen"),
                                      strings.getString("alertLockScreen." + (!locked ? "locked" : "unlocked")), false, "", null);
         break;
       }
     }
   }
 };
 
+var TapHighlightHelper = {
+  get _overlay() {
+    delete this._overlay;
+    return this._overlay = document.getElementById("content-overlay");
+  },
+
+  show: function show(aRects) {
+    let bv = Browser._browserView;
+    let union = aRects.reduce(function(a, b) {
+      return a.expandToContain(b);
+    }, new Rect(0, 0, 0, 0)).map(bv.browserToViewport);
+
+    let vis = Browser.getVisibleRect();
+    let canvasArea = vis.intersect(union);
+
+    let overlay = this._overlay;
+    overlay.width = canvasArea.width;
+    overlay.style.width = canvasArea.width + "px";
+    overlay.height = canvasArea.height;
+    overlay.style.height = canvasArea.height + "px";
+
+    let ctx = overlay.getContext("2d");
+    ctx.save();
+    ctx.translate(-canvasArea.left, -canvasArea.top);
+    bv.browserToViewportCanvasContext(ctx);
+
+    overlay.style.left = canvasArea.left + "px";
+    overlay.style.top = canvasArea.top + "px";
+    ctx.fillStyle = "rgba(0, 145, 255, .5)";
+    for (let i = aRects.length - 1; i >= 0; i--) {
+      let rect = aRects[i];
+      ctx.fillRect(rect.left, rect.top, rect.width, rect.height);
+    }
+    ctx.restore();
+    overlay.style.display = "block";
+  },
+
+  hide: function hide() {
+    this._overlay.style.display = "none";
+  }
+}
+
 var PageActions = {
   get _permissionManager() {
     delete this._permissionManager;
     return this._permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
   },
 
   get _loginManager() {
     delete this._loginManager;
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -1227,46 +1227,16 @@ var Browser = {
     if (!bv.isDefaultZoom()) {
       let zoomLevel = bv.getDefaultZoomLevel();
       let [elementX, elementY] = this.transformClientToBrowser(cX, cY);
       let zoomRect = this._getZoomRectForPoint(elementX, elementY, zoomLevel);
       this.setVisibleRect(zoomRect);
     }
   },
 
-  getContentClientRects: function getContentClientRects(contentElem) {
-    // XXX don't copy getBoundingContentRect
-    let browser = Browser._browserView.getBrowser();
-
-    if (!browser)
-      return null;
-
-    let offset = BrowserView.Util.getContentScrollOffset(browser);
-    let nativeRects = contentElem.getClientRects();
-
-    // step out of iframes and frames, offsetting scroll values
-    let rect;
-    let cw = browser.contentWindow;
-    for (let frame = contentElem.ownerDocument.defaultView; frame != cw; frame = frame.parent) {
-      // adjust client coordinates' origin to be top left of iframe viewport
-      rect = frame.frameElement.getBoundingClientRect();
-      let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
-      let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
-      offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
-    }
-
-    let result = [];
-    let r;
-    for (let i = nativeRects.length - 1; i >= 0; i--) {
-      r = nativeRects[i];
-      result.push(new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height));
-    }
-    return result;
-  },
-
   getBoundingContentRect: function getBoundingContentRect(contentElem) {
     let document = contentElem.ownerDocument;
     while(document.defaultView.frameElement)
       document = document.defaultView.frameElement.ownerDocument;
 
     let tab = Browser.getTabForDocument(document);
     if (!tab || !tab.browser)
       return null;
@@ -1812,176 +1782,72 @@ const BrowserSearch = {
       button.engine = engine;
     }
   }
 }
 
 /** Watches for mouse events in chrome and sends them to content. */
 function ContentCustomClicker(browserView) {
   this._browserView = browserView;
-  this._overlay = document.getElementById("content-overlay");
-  this._overlayTimeout = 0;
-  this._width = 0;
-  this._height = 0;
 }
 
 ContentCustomClicker.prototype = {
-    /** Dispatch a mouse event with chrome client coordinates. */
-    _dispatchMouseEvent: function _dispatchMouseEvent(element, name, cX, cY) {
-      let browser = this._browserView.getBrowser();
-      if (browser) {
-        let [x, y] = Browser.transformClientToBrowser(cX, cY);
-        let cwu = BrowserView.Util.getBrowserDOMWindowUtils(browser);
-        let scrollX = {}, scrollY = {};
-        cwu.getScrollXY(false, scrollX, scrollY);
-
-        // the element can be out of the cX/cY point because of the touch radius
-        // ignore the redirection if the element is a HTMLHtmlElement (bug 562981)
-        let rect = Browser.getBoundingContentRect(element);
-        if (!rect.isEmpty() && !(element instanceof HTMLHtmlElement) &&
-            ((x < rect.left || (x > rect.left + rect.width)) || (y < rect.top || (y > rect.top + rect.height)))) {
-
-          let point = rect.center();
-          x = point.x;
-          y = point.y;
-        }
-
-        x = x - scrollX.value;
-        y = y - scrollY.value;
-        cwu.sendMouseEvent(name, x, y, 0, 1, 0, true);
-      }
-    },
-
-    /** Returns a node if selecting this node causes a focus. */
-    _getFocusable: function _getFocusable(node) {
-      if (node && node.mozMatchesSelector("*:link,*:visited,*:link *,*:visited *,*[role=button],button,input,option,select,textarea,label"))
-        return node;
-      return null;
-    },
-
-    _showCanvas: function _showCanvas(cX, cY) {
-      // This code is sensitive to performance. Please profile changes you make to
-      // keep this running fast.
-      let bv = this._browserView;
-      let overlay = this._overlay;
-      let ctx = overlay.getContext("2d");
-      let [elementX, elementY] = Browser.transformClientToBrowser(cX, cY);
-      let element = this._getFocusable(Browser.elementFromPoint(elementX, elementY));
-      if (!element)
-        return;
-
-      let rects = Browser.getContentClientRects(element);
-      let union = rects.reduce(function(a, b) {
-        return a.expandToContain(b);
-      }, new Rect(0, 0, 0, 0)).map(bv.browserToViewport);
-
-      let vis = Browser.getVisibleRect();
-      let canvasArea = vis.intersect(union);
-      this._ensureSize(canvasArea.width, canvasArea.height);
-
-      ctx.save();
-      ctx.translate(-canvasArea.left, -canvasArea.top);
-      bv.browserToViewportCanvasContext(ctx);
-
-      overlay.style.left = canvasArea.left + "px";
-      overlay.style.top = canvasArea.top + "px";
-      ctx.fillStyle = "rgba(0, 145, 255, .5)";
-      let rect;
-      let i;
-      for (i = rects.length - 1; i >= 0; i--) {
-        rect = rects[i];
-        ctx.fillRect(rect.left, rect.top, rect.width, rect.height);
-      }
-      ctx.restore();
-      overlay.style.display = "block";
-    },
-
-    /** Stop highlighting current element. */
-    _hideCanvas: function _hideCanvas() {
-      let overlay = this._overlay;
-      overlay.style.display = "none";
-      overlay.getContext("2d").clearRect(0, 0, this._width, this._height);
-
-      if (this._overlayTimeout) {
-        clearTimeout(this._overlayTimeout);
-        this._overlayTimeout = 0;
-      }
-    },
-
-    /** Make sure canvas is at least width x height. */
-    _ensureSize: function _ensureSize(width, height) {
-      if (this._width <= width) {
-        this._width = width;
-        this._overlay.width = width;
-      }
-      if (this._height <= height) {
-        this._height = height;
-        this._overlay.height = height;
-      }
-    },
-
-    mouseDown: function mouseDown(cX, cY) {
-      if (!this._overlayTimeout)
-        this._overlayTimeout = setTimeout(function(self) { self._showCanvas(cX, cY); }, kTapOverlayTimeout, this);
-    },
-
-    mouseUp: function mouseUp(cX, cY) {
-    },
-
-    panBegin: function panBegin() {
-      this._hideCanvas();
-    },
-
-    singleClick: function singleClick(cX, cY, modifiers) {
-      this._hideCanvas();
-
-      let [elementX, elementY] = Browser.transformClientToBrowser(cX, cY);
-      let element = Browser.elementFromPoint(elementX, elementY);
-      if (modifiers == 0) {
-        if (element instanceof HTMLOptionElement)
-          element = element.parentNode;
-
-        if (gPrefService.getBoolPref("formhelper.enabled")) {
-          if (FormHelper.canShowUIFor(element) && FormHelper.open(element))
-            return;
-        }
-        else if (SelectHelper.canShowUIFor(element)) {
-          SelectHelper.show(element);
-          return;
-        }
-
-        gFocusManager.setFocus(element, Ci.nsIFocusManager.FLAG_NOSCROLL);
-
-        let self = this;
-        Util.executeSoon(function() {
-          self._dispatchMouseEvent(element, "mousedown", cX, cY);
-          self._dispatchMouseEvent(element, "mouseup", cX, cY);
-        });
-      }
-      else if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
-        let uri = Util.getHrefForElement(element);
-        if (uri)
-          Browser.addTab(uri, false, Browser.selectedTab);
-      }
-    },
-
-    doubleClick: function doubleClick(cX1, cY1, cX2, cY2) {
-      this._hideCanvas();
-
-      const kDoubleClickRadius = 32;
-
-      let maxRadius = kDoubleClickRadius * Browser._browserView.getZoomLevel();
-      let isClickInRadius = (Math.abs(cX1 - cX2) < maxRadius && Math.abs(cY1 - cY2) < maxRadius);
-      if (isClickInRadius && !Browser.zoomToPoint(cX1, cY1))
-        Browser.zoomFromPoint(cX1, cY1);
-    },
-
-    toString: function toString() {
-      return "[ContentCustomClicker] { }";
-    }
+  _dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY) {
+    let browser = this._browserView.getBrowser();
+    let [x, y] = Browser.transformClientToBrowser(aX, aY);
+    browser.messageManager.sendAsyncMessage(aName, { x: x, y: y });
+  },
+
+  mouseDown: function mouseDown(aX, aY) {
+    // Ensure that the content process has gets an activate event
+    let browser = this._browserView.getBrowser();
+    let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+    try {
+      fl.activateRemoteFrame();
+    } catch (e) {}
+    this._dispatchMouseEvent("Browser:MouseDown", aX, aY);
+  },
+
+  mouseUp: function mouseUp(aX, aY) {
+  },
+
+  panBegin: function panBegin() {
+    TapHighlightHelper.hide();
+    let browser = this._browserView.getBrowser();
+    browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});
+  },
+
+  singleClick: function singleClick(aX, aY, aModifiers) {
+    TapHighlightHelper.hide();
+    this._dispatchMouseEvent("Browser:MouseUp", aX, aY);
+    // TODO e10s: handle modifiers for clicks
+    //
+    //  if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
+    //  let uri = Util.getHrefForElement(element);
+    //  if (uri)
+    //    Browser.addTab(uri, false);
+    //  }
+  },
+
+  doubleClick: function doubleClick(aX1, aY1, aX2, aY2) {
+    TapHighlightHelper.hide();
+    let browser = this._browserView.getBrowser();
+    browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});
+
+    const kDoubleClickRadius = 32;
+
+    let maxRadius = kDoubleClickRadius * Browser._browserView.getZoomLevel();
+    let isClickInRadius = (Math.abs(aX1 - aX2) < maxRadius && Math.abs(aY1 - aY2) < maxRadius);
+    if (isClickInRadius && !Browser.zoomToPoint(aX1, aY1))
+      Browser.zoomFromPoint(aX1, aY1);
+  },
+
+  toString: function toString() {
+    return "[ContentCustomClicker] { }";
+  }
 };
 
 /** Watches for mouse events in chrome and sends them to content. */
 function ContentCustomKeySender(browserView) {
   this._browserView = browserView;
 }
 
 ContentCustomKeySender.prototype = {
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -254,17 +254,17 @@
           <notificationbox id="notifications" class="window-width"/>
 
           <!-- Content viewport -->
           <vbox class="window-width window-height">
             <stack id="tile-stack" class="window-width" flex="1">
               <scrollbox id="content-scrollbox" style="overflow: hidden;" class="window-width" flex="1">
                 <!-- Content viewport -->
                 <html:div id="tile-container" style="overflow: hidden;">
-                  <html:canvas id="content-overlay" style="display: none; position: absolute; z-index: 1000;">
+                  <html:canvas id="content-overlay" style="display: none; position: absolute; z-index: 1000; left: 0; top: 0;">
                   </html:canvas>
                 </html:div>
               </scrollbox>
               <html:canvas id="view-buffer" style="display: none;" moz-opaque="true">
               </html:canvas>
             </stack>
             <box id="form-helper-spacer" hidden="true"/>
           </vbox>
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -1,18 +1,16 @@
 // This stays here because otherwise it's hard to tell if there's a parsing error
 dump("###################################### content loaded\n");
 
 // how many milliseconds before the mousedown and the overlay of an element
 const kTapOverlayTimeout = 200;
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
-let gIOService = Cc["@mozilla.org/network/io-service;1"]
-  .getService(Ci.nsIIOService);
 let gFocusManager = Cc["@mozilla.org/focus-manager;1"]
   .getService(Ci.nsIFocusManager);
 let gPrefService = Cc["@mozilla.org/preferences-service;1"]
   .getService(Ci.nsIPrefBranch2);
 
 let XULDocument = Ci.nsIDOMXULDocument;
 let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
 let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
@@ -140,16 +138,17 @@ const ElementTouchHelper = {
     for (let i = 0; i < listeners.length; i++) {
       if (this._clickableEvents.indexOf(listeners[i].type) != -1)
         return true;
     }
     return false;
   }
 };
 
+
 /**
  * @param x,y Browser coordinates
  * @return Element at position, null if no active browser or no element found
  */
 function elementFromPoint(x, y) {
   // browser's elementFromPoint expect browser-relative client coordinates.
   // subtract browser's scroll values to adjust
   let cwu = Util.getWindowUtils(content);
@@ -157,49 +156,71 @@ function elementFromPoint(x, y) {
   x = x - scroll.x
   y = y - scroll.y;
   let elem = ElementTouchHelper.getClosest(cwu, x, y);
 
   // step through layers of IFRAMEs and FRAMES to find innermost element
   while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
     // adjust client coordinates' origin to be top left of iframe viewport
     let rect = elem.getBoundingClientRect();
-    x = x - rect.left;
-    y = y - rect.top;
+    x -= rect.left;
+    y -= rect.top;
     let windowUtils = elem.contentDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     elem = ElementTouchHelper.getClosest(windowUtils, x, y);
   }
 
   return elem;
 }
 
-
-function getBoundingContentRect(contentElem) {
-  if (!contentElem)
+function getBoundingContentRect(aElement) {
+  if (!aElement)
     return new Rect(0, 0, 0, 0);
 
-  let document = contentElem.ownerDocument;
+  let document = aElement.ownerDocument;
   while(document.defaultView.frameElement)
     document = document.defaultView.frameElement.ownerDocument;
 
   let offset = Util.getScrollOffset(content);
-  let r = contentElem.getBoundingClientRect();
+  let r = aElement.getBoundingClientRect();
 
   // step out of iframes and frames, offsetting scroll values
-  for (let frame = contentElem.ownerDocument.defaultView; frame != content; frame = frame.parent) {
+  for (let frame = aElement.ownerDocument.defaultView; frame != content; frame = frame.parent) {
     // adjust client coordinates' origin to be top left of iframe viewport
     let rect = frame.frameElement.getBoundingClientRect();
     let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
     let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
     offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
   }
 
   return new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height);
 }
 
+function getContentClientRects(aElement) {
+  let offset = Util.getScrollOffset(content);
+  let nativeRects = aElement.getClientRects();
+  // step out of iframes and frames, offsetting scroll values
+  for (let frame = aElement.ownerDocument.defaultView; frame != content; frame = frame.parent) {
+    // adjust client coordinates' origin to be top left of iframe viewport
+    let rect = frame.frameElement.getBoundingClientRect();
+    let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
+    let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
+    offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
+  }
+
+  let result = [];
+  for (let i = nativeRects.length - 1; i >= 0; i--) {
+    let r = nativeRects[i];
+    result.push({ left: r.left + offset.x,
+                  top: r.top + offset.y,
+                  width: r.width,
+                  height: r.height
+                });
+  }
+  return result;
+};
 
 /** Reponsible for sending messages about viewport size changes and painting. */
 function Coalescer() {
   this._pendingDirtyRect = new Rect(0, 0, 0, 0);
   this._pendingSizeChange = null;
   this._timer = null;
   // XXX When moving back and forward in docShell history, MozAfterPaint does not get called properly and
   // some dirty rectangles are never flagged properly.  To fix this, coalescer will fake a paint event that
@@ -531,18 +552,17 @@ ContentFormManager.prototype = {
       sendMessage("FennecCaretRect", caretRect);
     }
   },
 
   _getRectForCaret: function _getRectForCaret() {
     let currentElement = this.getCurrent();
     let rect = currentElement.getCaretRect();
     return null;
-  },
-  
+  }
 };
 
 
 /**
  * Responsible for iterating through form elements.
  * The navigable list is generated on instantiation, so construct new FormNavigators when
  * the current document is changed or the list of form elements needs to be regenerated.
  */
@@ -673,75 +693,88 @@ FormNavigator.prototype = {
   }
 };
 
 
 /** Can't think of a good description of this class.  It probably does too much? */
 function Content() {
   addMessageListener("Browser:Blur", this);
   addMessageListener("Browser:Focus", this);
-  addMessageListener("Browser:Mousedown", this);
-  addMessageListener("Browser:Mouseup", this);
-  addMessageListener("Browser:CancelMouse", this);
+  addMessageListener("Browser:MouseDown", this);
+  addMessageListener("Browser:MouseUp", this);
+  addMessageListener("Browser:MouseCancel", this);
   addMessageListener("Browser:SaveAs", this);
 
   this._coalescer = new Coalescer();
   addEventListener("MozAfterPaint", this._coalescer, false);
   addEventListener("MozScrolledAreaChanged", this._coalescer, false);
   addEventListener("MozApplicationManifest", this._coalescer, false);
   addEventListener("scroll", this._coalescer, false);
 
   this._progressController = new ProgressController(this);
   this._progressController.start();
 
   this._contentFormManager = new ContentFormManager();
-
-  this._mousedownTimeout = new Util.Timeout();
 }
 
 Content.prototype = {
   receiveMessage: function receiveMessage(aMessage) {
     let json = aMessage.json;
+    let x = json.x;
+    let y = json.y;
 
     switch (aMessage.name) {
-      case "Browser:Blur": 
+      case "Browser:Blur":
         docShell.isOffScreenBrowser = false;
         this._selected = false;
         break;
 
       case "Browser:Focus":
         docShell.isOffScreenBrowser = true;
         this._selected = true;
         break;
 
-      case "Browser:Mousedown":
-        this._mousedownTimeout.once(kTapOverlayTimeout, function() {
+      case "Browser:MouseDown":
+        if (this._overlayTimeout)
+          return;
+
+        this._overlayTimeout = content.document.defaultView.setTimeout(function() {
           let element = elementFromPoint(x, y);
-          gFocusManager.setFocus(element, Ci.nsIFocusManager.FLAG_NOSCROLL);
-        });
+          if (!element || !element.mozMatchesSelector("*:link,*:visited,*:link *,*:visited *,*[role=button],button,input,option,select,textarea,label"))
+            return;
+
+          let rects = getContentClientRects(element);
+          sendSyncMessage("Browser:Highlight", { rects: rects });
+        }, kTapOverlayTimeout);
         break;
 
-      case "Browser:Mouseup":
-        this._mousedownTimeout.flush();
+      case "Browser:MouseUp":
+        let element = elementFromPoint(x, y);
+        // the element can be out of the cX/cY point because of the touch radius
+        // ignore the redirection if the element is a HTMLHtmlElement (bug 562981)
+        let rect = getBoundingContentRect(element);
+        if (!rect.isEmpty() && !(element instanceof HTMLHtmlElement) &&
+           ((x < rect.left || (x > rect.left + rect.width)) || (y < rect.top || (y > rect.top + rect.height)))) {
 
-        let element = elementFromPoint(x, y);
-        if (!this._contentFormManager.formAssist(element)) {
+          let point = rect.center();
+          x = point.x;
+          y = point.y;
+        }
+
+        // XXX e10s bug 566288
+        // if (!this._contentFormManager.formAssist(element)) {
           this._sendMouseEvent("mousedown", element, x, y);
           this._sendMouseEvent("mouseup", element, x, y);
-        }
-        break;
+        // }
 
-      case "Browser:CancelMouse":
-        this._mousedownTimeout.clear();
-        // XXX there must be a better way than this to cancel the mouseover/focus?
-        this._sendMouseEvent("mouseup", null, -1000, -1000);
-        try {
-          content.document.activeElement.blur();
+      case "Browser:MouseCancel":
+        if (this._overlayTimeout) {
+          content.document.defaultView.clearTimeout(this._overlayTimeout);
+          this._overlayTimeout = 0;
         }
-        catch(e) {}
         break;
 
       case "Browser:SaveAs":
         if (json.type != Ci.nsIPrintSettings.kOutputFormatPDF)
           return;
 
         let printSettings = Cc["@mozilla.org/gfx/printsettings-service;1"]
                               .getService(Ci.nsIPrintSettingsService)