Bug 1204084 - Double tap gesture fails when tapping on a large element contained in an iframe or scrollable div. r=botond
authorRandall Barker <rbarker@mozilla.com>
Wed, 16 Sep 2015 11:17:00 +0200
changeset 295650 a404231c514af1c61920055f7a004bd09e52b392
parent 295649 2237e1a53be9d08f8d8cd3b651fd6c16533ffab0
child 295651 d9337a944faeeb6ba8ca9b22c72452972f5ddbe6
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1204084
milestone43.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 1204084 - Double tap gesture fails when tapping on a large element contained in an iframe or scrollable div. r=botond
gfx/layers/apz/util/DoubleTapToZoom.cpp
--- a/gfx/layers/apz/util/DoubleTapToZoom.cpp
+++ b/gfx/layers/apz/util/DoubleTapToZoom.cpp
@@ -66,33 +66,54 @@ ShouldZoomToElement(const nsCOMPtr<dom::
   }
   if (aElement->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::q)) {
     return false;
   }
   return true;
 }
 
 // Calculate the bounding rect of |aElement|, relative to the origin
-// of the document associated with |aShell|.
-// |aRootScrollFrame| should be the root scroll frame of the document in
-// question.
-// The implementation is adapted from Element::GetBoundingClientRect().
+// of the scrolled content of |aRootScrollFrame|.
+// The implementation of this calculation is adapted from
+// Element::GetBoundingClientRect().
+//
+// Where the element is contained inside a scrollable subframe, the
+// bounding rect is clipped to the bounds of the subframe.
 static CSSRect
-GetBoundingContentRect(const nsCOMPtr<nsIPresShell>& aShell,
-                       const nsCOMPtr<dom::Element>& aElement,
+GetBoundingContentRect(const nsCOMPtr<dom::Element>& aElement,
                        const nsIScrollableFrame* aRootScrollFrame) {
+
+  CSSRect result;
   if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
-    return CSSRect::FromAppUnits(
+    nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame();
+    result = CSSRect::FromAppUnits(
         nsLayoutUtils::GetAllInFlowRectsUnion(
             frame,
-            aShell->GetRootFrame(),
-            nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS)
-      + aRootScrollFrame->GetScrollPosition());
+            relativeTo,
+            nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
+
+    // If the element is contained in a scrollable frame that is not
+    // the root scroll frame, make sure to clip the result so that it is
+    // not larger than the containing scrollable frame's bounds.
+    nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(frame);
+    if (scrollFrame && scrollFrame != aRootScrollFrame) {
+      nsIFrame* subFrame = do_QueryFrame(scrollFrame);
+      MOZ_ASSERT(subFrame);
+      // Get the bounds of the scroll frame in the same coordinate space
+      // as |result|.
+      CSSRect subFrameRect = CSSRect::FromAppUnits(
+          nsLayoutUtils::TransformFrameRectToAncestor(
+              subFrame,
+              subFrame->GetRectRelativeToSelf(),
+              relativeTo));
+
+      result = subFrameRect.Intersect(result);
+    }
   }
-  return CSSRect();
+  return result;
 }
 
 static bool
 IsRectZoomedIn(const CSSRect& aRect, const CSSRect& aCompositedArea)
 {
   // This functions checks to see if the area of the rect visible in the
   // composition bounds (i.e. the overlapArea variable below) is approximately
   // the max area of the rect we can show.
@@ -138,17 +159,37 @@ CalculateRectToZoomTo(const nsCOMPtr<nsI
 
   if (!element) {
     return zoomOut;
   }
 
   FrameMetrics metrics = nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame);
   CSSRect compositedArea(metrics.GetScrollOffset(), metrics.CalculateCompositedSizeInCssPixels());
   const CSSCoord margin = 15;
-  CSSRect rect = GetBoundingContentRect(shell, element, rootScrollFrame);
+  CSSRect rect = GetBoundingContentRect(element, rootScrollFrame);
+
+  // If the element is taller than the visible area of the page scale
+  // the height of the |rect| so that it has the same aspect ratio as
+  // the root frame.  The clipped |rect| is centered on the y value of
+  // the touch point. This allows tall narrow elements to be zoomed.
+  if (!rect.IsEmpty() && compositedArea.width > 0.0f) {
+    const float widthRatio = rect.width / compositedArea.width;
+    float targetHeight = compositedArea.height * widthRatio;
+    if (widthRatio < 0.9 && targetHeight < rect.height) {
+      const CSSPoint scrollPoint = CSSPoint::FromAppUnits(rootScrollFrame->GetScrollPosition());
+      float newY = aPoint.y + scrollPoint.y - (targetHeight * 0.5f);
+      if ((newY + targetHeight) > (rect.y + rect.height)) {
+        rect.y += rect.height - targetHeight;
+      } else if (newY > rect.y) {
+        rect.y = newY;
+      }
+      rect.height = targetHeight;
+    }
+  }
+
   rect = CSSRect(std::max(metrics.GetScrollableRect().x, rect.x - margin),
                  rect.y,
                  rect.width + 2 * margin,
                  rect.height);
   // Constrict the rect to the screen's right edge
   rect.width = std::min(rect.width, metrics.GetScrollableRect().XMost() - rect.x);
 
   // If the rect is already taking up most of the visible area and is