Bug 1350663 - Repaint mask layer when the offset between mask region and mask layer changed. draft
authorcku <cku@mozilla.com>
Sun, 14 May 2017 19:28:01 +0800
changeset 585244 760763d30524cb5e4e7d373c7e0d5765ed1a2f14
parent 585232 6b08b414648b2c1d8d35ef66552e1f3940adafb7
child 630689 2eb7d04fff0a9824ce5938b124445cc5e0148c1c
push id61072
push userbmo:cku@mozilla.com
push dateFri, 26 May 2017 18:26:03 +0000
bugs1350663
milestone55.0a1
Bug 1350663 - Repaint mask layer when the offset between mask region and mask layer changed. Let's said we have a div, <div id="outer" style="width:200px, height:200px; mask="url(opaque-200X100-transparent-200X100.png)""> <div id="innter" style="position:fixed; left:0px; top:0px; width:200px; height:100px; mask-repeat: no-repeat; background-color: blue;"></div> </div> Some hints: 1. '#inner' is out-of-flow 2. '#outer' itself does not draw any things, no background/ border, etc.... 3. Mask applied on '#outer'. 4. opaque-100X200-transparent-100X200.png is a 200X200 image. The upper side of this image is opaque; the lower side of this image is transparent. After page load, you will see a 200X100 blue rect on left-top corner. This blue rect is contributed by 200X100 blue '#inner' and 200X100 opaque upper part of mask. So far so good. Then you scroll down 100px. '#inner' is not moved, since it's an out-of-flow element, mask move up 100px with '#outter'. Ideally, you should see nothing in the view, since '#inner' is now masked by transparent part of the image mask. On FF, you will still see a 200X100 blue rect in view as if no scrolling at all, which is wrong. Here is the root cause of this bug: 1. At beginning, we create a 200X100 mask layer, which fit the size of '#inner'. Not 200X200 of '#outer', since '#outer' basically draw nothing, we smartly choose a more compact draw target for painting the mask. 2. Things go wrong after scrolling down. By scroll down 100px, the size of the mask layer is still "200X100", so we _think_ cached mask layer is still reusable, but it is not. Before scrolling, we paint (0, 0, 200, 100) portion of the 200X200 mask onto mask layer; after scrolling, we should paint (0, _100_, 200, 100) portion of mask onto mask layer. We did not keep this kind of offset information in CSSMaskLayerUserData, so we don't know that the cached mask layer should be rejected. It's difficult to create a reftest for this bug as well. With scrollTo, we may mimic an environment of this error, but since reftest harness will trigger whole viewport repaint while taking a snapshot, we actually can not repro this issue on it. MozReview-Commit-ID: H5xaUSssMRh
layout/painting/FrameLayerBuilder.cpp
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -1614,41 +1614,51 @@ struct MaskLayerUserData : public LayerU
  * User data for layers which will be used as masks for css positioned mask.
  */
 struct CSSMaskLayerUserData : public LayerUserData
 {
   CSSMaskLayerUserData()
     : mMaskStyle(nsStyleImageLayers::LayerType::Mask)
   { }
 
-  CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aMaskBounds)
+  CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aMaskBounds,
+                       const nsPoint& aMaskLayerOffset)
     : mMaskBounds(aMaskBounds),
-      mMaskStyle(aFrame->StyleSVGReset()->mMask)
+      mMaskStyle(aFrame->StyleSVGReset()->mMask),
+      mMaskLayerOffset(aMaskLayerOffset)
   {
   }
 
   void operator=(CSSMaskLayerUserData&& aOther)
   {
     mMaskBounds = aOther.mMaskBounds;
     mMaskStyle = Move(aOther.mMaskStyle);
+    mMaskLayerOffset = aOther.mMaskLayerOffset;
   }
 
   bool
   operator==(const CSSMaskLayerUserData& aOther) const
   {
     if (!mMaskBounds.IsEqualInterior(aOther.mMaskBounds)) {
       return false;
     }
 
+    // Make sure we draw the same portion of the mask onto mask layer.
+    if (mMaskLayerOffset != aOther.mMaskLayerOffset) {
+      return false;
+    }
+
     return mMaskStyle == aOther.mMaskStyle;
   }
 
 private:
   nsIntRect mMaskBounds;
   nsStyleImageLayers mMaskStyle;
+  nsPoint mMaskLayerOffset; // The offset from the origin of mask bounds to
+                            // the origin of mask layer.
 };
 
 /*
  * A helper object to create a draw target for painting mask and create a
  * image container to hold the drawing result. The caller can then bind this
  * image container with a image mask layer via ImageLayer::SetContainer.
  */
 class MaskImageData
@@ -3877,17 +3887,19 @@ ContainerState::SetupMaskLayerForCSSMask
   // Setup mask layer offset.
   // We do not repaint mask for mask position change, so update base transform
   // each time is required.
   Matrix4x4 matrix;
   matrix.PreTranslate(itemRect.x, itemRect.y, 0);
   matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
   maskLayer->SetBaseTransform(matrix);
 
-  CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect);
+  nsPoint maskLayerOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft();
+    
+  CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect, maskLayerOffset);
   nsRect dirtyRect;
   if (!aMaskItem->IsInvalid(dirtyRect) && *oldUserData == newUserData) {
     aLayer->SetMaskLayer(maskLayer);
     return;
   }
 
   int32_t maxSize = mManager->GetMaxTextureSize();
   IntSize surfaceSize(std::min(itemRect.width, maxSize),