Bug 1226904 - Fix boundary checking for leaves collecting. r=roc, a=sledru
authorThinker K.F. Li <thinker@codemud.net>
Thu, 07 Jan 2016 20:22:00 -0500
changeset 310768 742b2f8427cb13f20827ba86406552a262c3f9c2
parent 310767 2674cea8413d7a9fe637c58f704e23cce1f0f684
child 310769 3019eb49d70887f0b3ab8d885cf664734296cd78
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, sledru
bugs1226904
milestone45.0a2
Bug 1226904 - Fix boundary checking for leaves collecting. r=roc, a=sledru
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/tests/bug1226904.html
layout/base/tests/mochitest.ini
layout/base/tests/preserve3d_sorting_hit_testing_iframe.html
layout/base/tests/test_bug1226904.html
layout/base/tests/test_preserve3d_sorting_hit_testing.html
layout/generic/nsFrame.cpp
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1885,21 +1885,19 @@ void FlushFramesArray(nsTArray<FramesWit
 void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                             nsDisplayItem::HitTestState* aState,
                             nsTArray<nsIFrame*> *aOutFrames) const {
   nsDisplayItem* item;
 
   if (aState->mInPreserves3D) {
     // Collect leaves of the current 3D rendering context.
     for (item = GetBottom(); item; item = item->GetAbove()) {
-      MOZ_ASSERT(item->GetType() == nsDisplayTransform::TYPE_TRANSFORM ||
-                 item->GetType() == nsDisplayTransform::TYPE_PERSPECTIVE);
-      if (item->Frame()->Extend3DContext() &&
-          (item->GetType() == nsDisplayTransform::TYPE_PERSPECTIVE ||
-           !static_cast<nsDisplayTransform*>(item)->IsTransformSeparator())) {
+      auto itemType = item->GetType();
+      if (itemType != nsDisplayItem::TYPE_TRANSFORM ||
+          !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
         item->HitTest(aBuilder, aRect, aState, aOutFrames);
       } else {
         // One of leaves in the current 3D rendering context.
         aState->mItemBuffer.AppendElement(item);
       }
     }
     return;
   }
@@ -1913,43 +1911,43 @@ void nsDisplayList::HitTest(nsDisplayLis
   for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart; --i) {
     // Pop element off the end of the buffer. We want to shorten the buffer
     // so that recursive calls to HitTest have more buffer space.
     item = aState->mItemBuffer[i];
     aState->mItemBuffer.SetLength(i);
 
     bool snap;
     nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect);
+    auto itemType = item->GetType();
     bool alwaysIntersect =
-      item->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
-      (item->Frame()->Combines3DTransformWithAncestors() ||
-       item->Frame()->Extend3DContext() ||
-       static_cast<nsDisplayTransform*>(item)->IsTransformSeparator());
+      (itemType == nsDisplayItem::TYPE_TRANSFORM &&
+       static_cast<nsDisplayTransform*>(item)->IsParticipating3DContext()) ||
+      (itemType == nsDisplayItem::TYPE_PERSPECTIVE &&
+       static_cast<nsDisplayPerspective*>(item)->Frame()->Extend3DContext());
+    if (alwaysIntersect &&
+        !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
+      nsAutoTArray<nsIFrame*, 1> neverUsed;
+      // Start gethering leaves of the 3D rendering context, and
+      // append leaves at the end of mItemBuffer.  Leaves are
+      // processed at following iterations.
+      aState->mInPreserves3D = true;
+      item->HitTest(aBuilder, aRect, aState, &neverUsed);
+      aState->mInPreserves3D = false;
+      i = aState->mItemBuffer.Length();
+      continue;
+    }
     if (alwaysIntersect || item->GetClip().MayIntersect(r)) {
       nsAutoTArray<nsIFrame*, 16> outFrames;
-      if (item->Frame()->Extend3DContext() &&
-          item->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
-          !static_cast<nsDisplayTransform*>(item)->IsTransformSeparator()) {
-        // Start gethering leaves of the 3D rendering context, and
-        // append leaves at the end of mItemBuffer.  Leaves are
-        // processed at following iterations.
-        aState->mInPreserves3D = true;
-        item->HitTest(aBuilder, aRect, aState, &outFrames);
-        aState->mInPreserves3D = false;
-        i = aState->mItemBuffer.Length();
-        continue;
-      } else {
-        item->HitTest(aBuilder, aRect, aState, &outFrames);
-      }
+      item->HitTest(aBuilder, aRect, aState, &outFrames);
 
       // For 3d transforms with preserve-3d we add hit frames into the temp list
       // so we can sort them later, otherwise we add them directly to the output list.
       nsTArray<nsIFrame*> *writeFrames = aOutFrames;
       if (item->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
-          item->Frame()->Combines3DTransformWithAncestors()) {
+          static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
         if (outFrames.Length()) {
           nsDisplayTransform *transform = static_cast<nsDisplayTransform*>(item);
           nsPoint point = aRect.TopLeft();
           // A 1x1 rect means a point, otherwise use the center of the rect
           if (aRect.width != 1 || aRect.height != 1) {
             point = aRect.Center();
           }
           temp.AppendElement(FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point)));
@@ -5533,24 +5531,24 @@ nsDisplayTransform::GetTransformForRende
   // Don't include perspective transform, or the offset to origin, since
   // nsDisplayPerspective will handle both of those.
   return GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, 0);
 }
 
 const Matrix4x4&
 nsDisplayTransform::GetAccumulatedPreserved3DTransform(nsDisplayListBuilder* aBuilder)
 {
+  MOZ_ASSERT(!mFrame->Extend3DContext() || IsLeafOf3DContext());
   // XXX: should go back to fix mTransformGetter.
   if (!mTransformPreserves3DInited) {
     mTransformPreserves3DInited = true;
-    if (!mFrame->Combines3DTransformWithAncestors()) {
+    if (!IsLeafOf3DContext()) {
       mTransformPreserves3D = GetTransform();
       return mTransformPreserves3D;
     }
-    MOZ_ASSERT(!mFrame->Extend3DContext() || IsTransformSeparator());
 
     const nsIFrame* establisher; // Establisher of the 3D rendering context.
     for (establisher = nsLayoutUtils::GetCrossDocParentFrame(mFrame);
          establisher && establisher->Combines3DTransformWithAncestors();
          establisher = nsLayoutUtils::GetCrossDocParentFrame(establisher)) {
     }
     establisher = nsLayoutUtils::GetCrossDocParentFrame(establisher);
     const nsIFrame* establisherReference =
@@ -5777,17 +5775,16 @@ nsDisplayTransform::GetHitDepthAtPoint(n
 
   NS_ASSERTION(IsFrameVisible(mFrame, matrix),
                "We can't have hit a frame that isn't visible!");
 
   Matrix4x4 inverse = matrix;
   inverse.Invert();
   Point4D point = inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor),
                                              NSAppUnitsToFloatPixels(aPoint.y, factor)));
-  NS_ASSERTION(point.HasPositiveWCoord(), "Why are we trying to get the depth for a point we didn't hit?");
 
   Point point2d = point.As2DPoint();
 
   Point3D transformed = matrix * Point3D(point2d.x, point2d.y, 0);
   return transformed.z;
 }
 
 /* The bounding rectangle for the object is the overflow rectangle translated
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -4041,16 +4041,24 @@ public:
    * This item is the boundary between parent and child 3D rendering
    * context.
    */
   bool IsLeafOf3DContext() {
     return (IsTransformSeparator() ||
             (!mFrame->Extend3DContext() &&
              mFrame->Combines3DTransformWithAncestors()));
   }
+  /**
+   * The backing frame of this item participates a 3D rendering
+   * context.
+   */
+  bool IsParticipating3DContext() {
+    return mFrame->Extend3DContext() ||
+      mFrame->Combines3DTransformWithAncestors();
+  }
 
 private:
   void ComputeBounds(nsDisplayListBuilder* aBuilder);
   void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder);
   void Init(nsDisplayListBuilder* aBuilder);
 
   static Matrix4x4 GetResultingTransformMatrixInternal(const FrameTransformProperties& aProperties,
                                                        const nsPoint& aOrigin,
@@ -4155,16 +4163,21 @@ public:
   virtual nsDisplayList* GetChildren() override { return mList.GetChildren(); }
   virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
   {
     return mList.GetComponentAlphaBounds(aBuilder);
   }
 
   nsIFrame* TransformFrame() { return mTransformFrame; }
 
+  virtual void
+  DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override {
+    static_cast<nsDisplayTransform*>(mList.GetChildren()->GetTop())->DoUpdateBoundsPreserves3D(aBuilder);
+  }
+
 private:
   nsDisplayWrapList mList;
   nsIFrame* mTransformFrame;
   uint32_t mIndex;
 };
 
 /**
  * This class adds basic support for limiting the rendering (in the inline axis
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1226904.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+  <title>Test for Bug 684759</title>
+</head>
+<style>
+  #container {
+  transform-style: preserve-3d;
+  transform: translate3d(400px, 0px, 10px);
+  background-color: black;
+  }
+  #separator {
+  width: 400px;
+  height: 400px;
+  background-color: green;
+  }
+  #transformed {
+  width: 400px;
+  height: 400px;
+  background-color: blue;
+  transform: translate3d(-400px, 0px, 10px);
+  }
+</style>
+<body onload="run()">
+<div>
+  <div id="container">
+    <div id="separator"></div>
+    <div id="transformed"></div>
+  </div>
+</div>
+</body>
+</html>
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -261,8 +261,10 @@ support-files = bug1093686_inner.html
 [test_bug1120705.html]
 skip-if = buildapp == 'android' || buildapp == 'b2g' || buildapp == 'b2g-debug' || os == 'mac' || toolkit == 'gtk2' || toolkit == 'gtk3' # android and b2g do not have clickable scrollbars, mac does not have scrollbar down and up buttons, gtk may or may not have scrollbar buttons depending on theme
 [test_bug1153130.html]
 support-files = bug1153130_inner.html
 [test_bug1162990.html]
 support-files =
   bug1162990_inner_1.html
   bug1162990_inner_2.html
+[test_bug1226904.html]
+support-files = bug1226904.html
--- a/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html
+++ b/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html
@@ -7,24 +7,24 @@
   #parent {
     -moz-transform-style: preserve-3d;
   }
 
   #big {
     width: 1000px;
     height: 1000px;
     background-color: #995C7F;
-    -moz-transform: rotatey(45deg);
+    -moz-transform: translate3d(0px, 0px, 350px) rotatey(45deg);
   }
 
   #small {
     width: 100px;
     height: 100px;
     background-color: #835A99;
-    -moz-transform: translate3d(600px, 200px, 0px);
+    -moz-transform: translate3d(600px, 200px, 350px);
   }
 
 </style>
 <body>
   <div id="stage">
     <div id="parent">
       <div id="small"></div>
       <div id="big"></div>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_bug1226904.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+  <title>Test for Bug 684759</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1226904">Mozilla Bug 1226904</a>
+<iframe src="bug1226904.html" id="iframe" height="1000" width="1000" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1226904 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+  var iframe = document.getElementById("iframe");
+  var doc = iframe.contentDocument;
+  var container = doc.getElementById("container");
+  var separator = doc.getElementById("separator");
+  var transformed = doc.getElementById("transformed");
+
+  function check(x, y, expected_element, description)
+  {
+	is(doc.elementFromPoint(x, y), expected_element,
+	   "point (" + x + ", " + y + "): " + description);
+  }
+
+  check(600, 200, separator, "Separator object should be at right side");
+  check(900, 200, container, "Check bounds of separator object");
+  check(200, 600, transformed, "Transformed object should be at left side");
+
+  SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/base/tests/test_preserve3d_sorting_hit_testing.html
+++ b/layout/base/tests/test_preserve3d_sorting_hit_testing.html
@@ -24,17 +24,17 @@ function run() {
   
   var doc = iframe.contentDocument;
 
   var big= doc.getElementById("big");
   var small = doc.getElementById("small");
 
   function check(x, y, expected_element, description)
   {
-	is(doc.elementFromPoint(x, y), expected_element,
+	is(doc.elementFromPoint(x, y).id, expected_element.id,
 	   "point (" + x + ", " + y + "): " + description);
   }
 
   check(650, 250, small, "Small object should be infront of big");
   check(650, 308, big, "Check bounds of small object");
   check(650, 207, big, "Check bounds of small object");
   check(607, 250, big, "Check bounds of small object");
   check(708, 250, big, "Check bounds of small object");
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1917,16 +1917,19 @@ ItemParticipatesIn3DContext(nsIFrame* aA
   nsIFrame* transformFrame;
   if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM) {
     transformFrame = aItem->Frame();
   } else if (aItem->GetType() == nsDisplayItem::TYPE_PERSPECTIVE) {
     transformFrame = static_cast<nsDisplayPerspective*>(aItem)->TransformFrame();
   } else {
     return false;
   }
+  if (aAncestor == transformFrame) {
+    return true;
+  }
   return FrameParticipatesIn3DContext(aAncestor, transformFrame);
 }
 
 static void
 WrapSeparatorTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                        nsRect& aDirtyRect,
                        nsDisplayList* aSource, nsDisplayList* aTarget,
                        int aIndex) {