Bug 832957 - Fix selection overlay issues with context menus and touch input, and clean up Content.js elementFromPoint. r=mbrubeck
authorJim Mathies <jmathies@mozilla.com>
Wed, 27 Feb 2013 10:27:47 -0600
changeset 123179 a2c6dda9543e8f693629c6bbc32f203169d75336
parent 123178 026f901ab4177471f7c37f6f71552dc58ecd0e64
child 123180 33dd2de9984f47821ff359ad1a9159d33409cd35
push id24373
push userryanvm@gmail.com
push dateThu, 28 Feb 2013 01:36:21 +0000
treeherdermozilla-central@8cb9d6981978 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs832957
milestone22.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 832957 - Fix selection overlay issues with context menus and touch input, and clean up Content.js elementFromPoint. r=mbrubeck
browser/metro/base/content/bindings/selectionoverlay.xml
browser/metro/base/content/contenthandlers/Content.js
browser/metro/base/content/contenthandlers/ContextMenuHandler.js
--- a/browser/metro/base/content/bindings/selectionoverlay.xml
+++ b/browser/metro/base/content/bindings/selectionoverlay.xml
@@ -14,21 +14,23 @@
           <xul:toolbarbutton id="selectionhandle-end" label="^" left="100" top="10" hidden="false"/>
         </xul:stack>
       </html:div>
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <constructor>
         <![CDATA[
+          this._selectionOverlay.addEventListener('contextmenu', this);
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
+          this._selectionOverlay.removeEventListener('contextmenu', this);
         ]]>
       </destructor>
 
       <field name="_selectionOverlay" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "selection-overlay-inner").parentNode;</field>
       <field name="_selectionDebugOverlay" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "selection-overlay-debug");</field>
 
       <property name="enabled">
         <setter>
@@ -82,16 +84,41 @@
       <method name="shutdown">
         <body>
           <![CDATA[
           this.enabled = false;
           ]]>
         </body>
       </method>
 
+      <method name="_onContextMenu">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            // forward this over. frame script will treat this like
+            // a bubbling contextmenu event.
+            Browser.selectedTab.browser.messageManager.sendAsyncMessage("Browser:InvokeContextAtPoint", {
+             xPos: aEvent.clientX, yPos: aEvent.clientY });
+          ]]>
+        </body>
+      </method>
+
+      <method name="handleEvent">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            switch (aEvent.type) {
+              case 'contextmenu':
+                this._onContextMenu(aEvent);
+                break;
+            }
+          ]]>
+        </body>
+      </method>
+
       <method name="addDebugRect">
         <parameter name="aLeft"/>
         <parameter name="aTop"/>
         <parameter name="aRight"/>
         <parameter name="aBottom"/>
         <parameter name="aColor"/>
         <parameter name="aFill"/>
         <parameter name="aId"/>
--- a/browser/metro/base/content/contenthandlers/Content.js
+++ b/browser/metro/base/content/contenthandlers/Content.js
@@ -162,42 +162,48 @@ const ElementTouchHelper = {
 };
 
 
 /*
  * Global functions
  */
 
 /*
- * elementFromPoint
+ * elementFromPoint - find the closes element at a point. searches
+ * sub-frames.
  *
- * @param x,y Browser coordinates
- * @return Element at position, null if no active browser or no element found
+ * @param aX, aY browser coordinates
+ * @return
+ *  element - element at the position, or null if no active browser or
+ *            element was found.
+ *  frameX - x position within the subframe element was found. aX if no
+ *           sub-frame was found.
+ *  frameY - y position within the subframe element was found. aY if no
+ *           sub-frame was found.
  */
-function elementFromPoint(x, y) {
+function elementFromPoint(aX, aY) {
   // browser's elementFromPoint expect browser-relative client coordinates.
   // subtract browser's scroll values to adjust
   let cwu = Util.getWindowUtils(content);
-  let elem = ElementTouchHelper.getClosest(cwu, x, y);
+  let elem = ElementTouchHelper.getClosest(cwu, aX, aY);
 
   // 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 -= rect.left;
-    y -= rect.top;
+    aX -= rect.left;
+    aY -= rect.top;
     let windowUtils = elem.contentDocument
                           .defaultView
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
-    elem = ElementTouchHelper.getClosest(windowUtils, x, y);
+    elem = ElementTouchHelper.getClosest(windowUtils, aX, aY);
   }
-
-  return elem;
+  return { element: elem, frameX: aX, frameY: aY };
 }
 
 /*
  * getBoundingContentRect
  *
  * @param aElement
  * @return Bounding content rect adjusted for scroll and frame offsets.
  */
@@ -395,34 +401,34 @@ let Content = {
   /******************************************************
    * generic input handlers
    *
    * regardless of whether the input was received via
    * message manager or sent directly via dispatch.
    */
 
   _genericMouseDown: function _genericMouseDown(x, y) {
-    let element = elementFromPoint(x, y);
+    let { element } = elementFromPoint(x, y);
     if (!element)
       return;
 
     // There is no need to have a feedback for disabled element
     let isDisabled = element instanceof HTMLOptionElement ?
       (element.disabled || element.parentNode.disabled) : element.disabled;
     if (isDisabled)
       return;
 
     // Set the target element to active
     this._doTapHighlight(element);
   },
 
   _genericMouseClick: function _genericMouseClick(aEvent) {
     ContextMenuHandler.reset();
 
-    let element = elementFromPoint(aEvent.clientX, aEvent.clientY);
+    let { element: element } = elementFromPoint(aEvent.clientX, aEvent.clientY);
     if (!element)
       return;
 
     // Only show autocomplete after the item is clicked
     if (!this.lastClickElement || this.lastClickElement != element) {
       this.lastClickElement = element;
       if (aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE &&
           !(element instanceof HTMLSelectElement)) {
--- a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
+++ b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
@@ -86,18 +86,19 @@ var ContextMenuHandler = {
   },
 
   /*
    * Handler for selection overlay context menu events.
    */
   _onContextAtPoint: function _onContextCommand(aMessage) {
     // we need to find popupNode as if the context menu were
     // invoked on underlying content.
-    let elem = elementFromPoint(aMessage.json.xPos, aMessage.json.yPos);
-    this._processPopupNode(elem, aMessage.json.xPos, aMessage.json.yPos,
+    let { element, frameX, frameY } =
+      elementFromPoint(aMessage.json.xPos, aMessage.json.yPos);
+    this._processPopupNode(element, frameX, frameY,
                            Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
   },
 
   /******************************************************
    * Event handlers
    */
 
   reset: function ch_reset() {
@@ -166,30 +167,39 @@ var ContextMenuHandler = {
     while (element &&
            element.ownerDocument &&
            element.ownerDocument.defaultView != content) {
       element = element.ownerDocument.defaultView.frameElement;
       let rect = element.getBoundingClientRect();
       offsetX += rect.left;
       offsetY += rect.top;
     }
-    return { offsetX: offsetX, offsetY: offsetY };
+    let win = null;
+    if (element == aPopupNode)
+      win = content;
+    else
+      win = element.contentDocument.defaultView;
+    return { targetWindow: win, offsetX: offsetX, offsetY: offsetY };
   },
 
   /*
    * _processPopupNode - Generate and send a Content:ContextMenu message
    * to browser detailing the underlying content types at this.popupNode.
    * Note the event we receive targets the sub frame (if there is one) of
    * the page.
    */
   _processPopupNode: function _processPopupNode(aPopupNode, aX, aY, aInputSrc) {
     if (!aPopupNode)
       return;
-    let { offsetX: offsetX, offsetY: offsetY } =
+
+    let { targetWindow: targetWindow,
+          offsetX: offsetX,
+          offsetY: offsetY } =
       this._translateToTopLevelWindow(aPopupNode);
+
     let popupNode = this.popupNode = aPopupNode;
     let imageUrl = "";
 
     let state = {
       types: [],
       label: "",
       linkURL: "",
       linkTitle: "",
@@ -293,19 +303,19 @@ var ContextMenuHandler = {
 
       elem = elem.parentNode;
     }
 
     // Over arching text tests
     if (isText) {
       // If this is text and has a selection, we want to bring
       // up the copy option on the context menu.
-      if (content && content.getSelection() &&
-          content.getSelection().toString().length > 0) {
-        state.string = content.getSelection().toString();
+      let selection = targetWindow.getSelection();
+      if (selection && selection.toString().length > 0) {
+        state.string = targetWindow.getSelection().toString();
         state.types.push("copy");
         state.types.push("selected-text");
       } else {
         // Add general content text if this isn't anything specific
         if (state.types.indexOf("image") == -1 &&
             state.types.indexOf("media") == -1 &&
             state.types.indexOf("video") == -1 &&
             state.types.indexOf("link") == -1 &&