Bug 1362000 - Part 1. Regenerate mask layer if the position of masked frame changed. draft
authorcku <cku@mozilla.com>
Fri, 12 May 2017 20:17:14 +0800
changeset 577040 4717af028474a700a2b7e81eecae142f2ab82ff1
parent 576745 8a7d0b15595f9916123848ca906f29c62d4914c9
child 628408 e8376119cd36c8401a8410de03f7655bcecd3a09
push id58587
push usercku@mozilla.com
push dateFri, 12 May 2017 18:39:11 +0000
bugs1362000
milestone55.0a1
Bug 1362000 - Part 1. Regenerate mask layer if the position of masked frame changed. Extract from the comment in CSSMaskLayerUserData::EqualTo: For CSS image mask, the position of masked frame is regardless. The content of CSS image mask has nothing to do with the position of masked frame. In another hand, the content of SVG mask or clip-path is depended on target frame's geometry. Take SVG mask as an example, if the maskContentUnits is not equal to maskUnits(e.g one is objectBoundingBox, another one is userSpaceOnUse), the content of SVG mask need to be re-created according to new frame position. That's said, with svg mask or clip-path, even if the frame is valid, we still need to check the position of the display item to ensure we can reuse cache mask layer. I did not create a reftest for this since we can capture this glith only during animation, and glitch appears and then disappear occasionally. The masked frame reports invalid, nsIFrame::IsInvalid, randomly, glitch disappears when it does so. So I manually verified paiting result by visit this three pages: https://codepen.io/tadeuszwojcik/pen/ZKJwRE https://codepen.io/tadeuszwojcik/pen/oWexWW http://codepen.io/iiroullin/pen/oWEOOM MozReview-Commit-ID: 7lUKTMYPFXo
layout/painting/FrameLayerBuilder.cpp
layout/svg/nsSVGEffects.cpp
layout/svg/nsSVGEffects.h
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -45,16 +45,18 @@
 #include "mozilla/gfx/Tools.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/layers/TextureClient.h"
 #include "mozilla/layers/TextureWrapperImage.h"
 #include "mozilla/Unused.h"
 #include "GeckoProfiler.h"
 #include "LayersLogging.h"
 #include "gfxPrefs.h"
+#include "nsSVGEffects.h"
+#include "nsSVGElement.h"
 
 #include <algorithm>
 #include <functional>
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 namespace mozilla {
@@ -1610,45 +1612,86 @@ 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 nsIntSize& aMaskSize)
-    : mMaskSize(aMaskSize),
+  CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aMaskRect)
+    : mMaskRect(aMaskRect),
       mMaskStyle(aFrame->StyleSVGReset()->mMask)
   {
   }
 
   void operator=(CSSMaskLayerUserData&& aOther)
   {
-    mMaskSize = aOther.mMaskSize;
+    mMaskRect = aOther.mMaskRect;
     mMaskStyle = Move(aOther.mMaskStyle);
   }
 
   bool
-  operator==(const CSSMaskLayerUserData& aOther) const
+  EqualTo(const CSSMaskLayerUserData& aOther, const nsDisplayItem* aItem) const
   {
     // Even if the frame is valid, check the size of the display item's
     // boundary is still necessary. For example, if we scale the masked frame
     // by adding a transform property on it, the masked frame is valid itself
     // but we have to regenerate mask according to the new size in device
     // space.
-    if (mMaskSize != aOther.mMaskSize) {
+    if (mMaskRect.Size() != aOther.mMaskRect.Size()) {
+      return false;
+    }
+
+    // For CSS image mask, the position of masked frame is regardless. The
+    // content of CSS image mask has nothing to do with the position of masked
+    // frame.
+    //
+    // In another hand, the content of SVG mask or SVG clip-path is depended on
+    // target frame's geometry. Take SVG mask as an example, if
+    // maskContentUnits and maskUnits are in different units(e.g one is
+    // objectBoundingBox, another one is userSpaceOnUse), changing the position
+    // of masked frame is essentially change the position of mask content
+    // element in mask region's coordinate space.
+    //
+    // That's said, with SVG mask or clip-path on, even if the masked frame is
+    // valid, we still need to check the position of the display item to ensure
+    // cached mask layer can be reused. (_Normally_, position change will make
+    // masked frame invalid, we don't bother to compare all the things here,
+    // but CSS animation is one exception. When you animate an object, it report
+    // invalid only after animation end.)
+    bool shouldComparePosition = false;
+    nsSVGEffects::IterateEffectUnits(aItem->Frame(),
+      [&shouldComparePosition](unsigned short aMaskUnits, unsigned short aMaskContentUnits)
+      {
+        // If the coordinate system of mask and mask content is matched, the
+        // masked frame's position change means we need to repaint mask layer.
+        if (aMaskUnits != aMaskContentUnits) {
+          shouldComparePosition = true;
+        }
+      },
+      [&shouldComparePosition](unsigned short aClipPathUnits)
+      {
+        // We generate mask surface for clip-path according to bbox of the
+        // clipped frame. As a result, mask layer can be reusable only if
+        // clipPathUnits is objectBoundingBox.
+        if (aClipPathUnits == dom::SVG_UNIT_TYPE_USERSPACEONUSE) {
+          shouldComparePosition = true;
+        }
+      });
+
+    if (shouldComparePosition && (mMaskRect.TopLeft() != aOther.mMaskRect.TopLeft())){
       return false;
     }
 
     return mMaskStyle == aOther.mMaskStyle;
   }
 
 private:
-  nsIntSize mMaskSize;
+  nsIntRect mMaskRect;
   nsStyleImageLayers mMaskStyle;
 };
 
 /*
  * 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.
  */
@@ -3867,26 +3910,24 @@ ContainerState::SetupMaskLayerForCSSMask
   CSSMaskLayerUserData* oldUserData =
     static_cast<CSSMaskLayerUserData*>(maskLayer->GetUserData(&gCSSMaskLayerUserData));
 
   bool snap;
   nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap);
   nsIntRect itemRect = ScaleToOutsidePixels(bounds, snap);
 
   // 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.Size());
+  CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect);
   nsRect dirtyRect;
-  if (!aMaskItem->IsInvalid(dirtyRect) && *oldUserData == newUserData) {
+  if (!aMaskItem->IsInvalid(dirtyRect) && oldUserData->EqualTo(newUserData, aMaskItem)) {
     aLayer->SetMaskLayer(maskLayer);
     return;
   }
 
   int32_t maxSize = mManager->GetMaxTextureSize();
   IntSize surfaceSize(std::min(itemRect.width, maxSize),
                       std::min(itemRect.height, maxSize));
 
--- a/layout/svg/nsSVGEffects.cpp
+++ b/layout/svg/nsSVGEffects.cpp
@@ -14,16 +14,17 @@
 #include "nsSVGClipPathFrame.h"
 #include "nsSVGPaintServerFrame.h"
 #include "nsSVGFilterFrame.h"
 #include "nsSVGMaskFrame.h"
 #include "nsIReflowCallback.h"
 #include "nsCycleCollectionParticipant.h"
 #include "SVGGeometryElement.h"
 #include "SVGUseElement.h"
+#include "mozilla/dom/SVGMaskElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 void
 nsSVGRenderingObserver::StartListening()
 {
   Element* target = GetTarget();
@@ -1031,8 +1032,46 @@ nsSVGEffects::GetMaskURI(nsIFrame* aFram
 {
   const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
   MOZ_ASSERT(svgReset->mMask.mLayers.Length() > aIndex);
 
   mozilla::css::URLValueData* data =
     svgReset->mMask.mLayers[aIndex].mImage.GetURLValue();
   return ResolveURLUsingLocalRef(aFrame, data);
 }
+
+void
+nsSVGEffects::IterateEffectUnits(nsIFrame* aFrame,
+  const std::function<void(unsigned short aMaskUnits, unsigned short aMaskContentUnits)>& aMaskCB,
+  const std::function<void(unsigned short aClipPathUnits)>& aClipPathCB)
+{
+  nsIFrame* firstFrame =
+    nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+  nsSVGEffects::EffectProperties effectProperties =
+    nsSVGEffects::GetEffectProperties(firstFrame);
+
+  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+  for (uint32_t i =  0; i < maskFrames.Length() ; i++) {
+    if (maskFrames[i]) {
+      SVGMaskElement *element =
+        static_cast<SVGMaskElement*>(maskFrames[i]->GetContent());
+
+      RefPtr<SVGAnimatedEnumeration> maskUnits = element->MaskUnits();
+      RefPtr<SVGAnimatedEnumeration> contentUnits = element->MaskContentUnits();
+      aMaskCB(maskUnits->AnimVal(), contentUnits->AnimVal());
+    }
+  }
+
+  nsSVGClipPathFrame* clipPath = effectProperties.GetClipPathFrame();
+  if (clipPath) {
+    const nsStyleSVGReset* style = firstFrame->StyleSVGReset();
+    if (style->mClipPath.GetType() == StyleShapeSourceType::URL) {
+      SVGClipPathElement *element =
+        static_cast<SVGClipPathElement*>(clipPath->GetContent());
+
+      RefPtr<SVGAnimatedEnumeration> clipPathUnits = element->ClipPathUnits();
+      aClipPathCB(clipPathUnits->AnimVal());
+    }
+  }
+
+  // XXX: cku we can also add one more callback for reporting filter's coordinate
+  // system when needed.
+}
--- a/layout/svg/nsSVGEffects.h
+++ b/layout/svg/nsSVGEffects.h
@@ -678,11 +678,20 @@ public:
    * @param aContent an element which uses a local-ref property. Here are some
    *                 examples:
    *                   <rect fill=url(#foo)>
    *                   <circle clip-path=url(#foo)>
    *                   <use xlink:href="#foo">
    */
   static already_AddRefed<nsIURI>
   GetBaseURLForLocalRef(nsIContent* aContent, nsIURI* aDocURI);
+
+  /**
+   * Return maskUnits/maskContentUnits of aFrame by aMaskCB.
+   * Return clipPathUnit of aFrame by aClipPathCB.
+   **/
+  static void
+  IterateEffectUnits(nsIFrame* aFrame,
+    const std::function<void(unsigned short aMaskUnits, unsigned short aMaskContentUnits)>& aMaskCB,
+    const std::function<void(unsigned short aClipPathUnits)>& aClipPathCB);
 };
 
 #endif /*NSSVGEFFECTS_H_*/