Bug 1362000 - When the position of masked frame changed, always regenerate mask layer if any of maskUnits/maskContentUnits/clipPathUnits is userSpaceOnUse.
For SVG mask/clipPath, comparison at [1] is not enough to detect whether cached
mask layer is reusable duriang animation. Since aMaskItem->IsInvalid() may keep
return fasle during OMTA. Bring in new condition check in
CSSMaskLayerUserData::IsMaskReusable..
I do not create a reftest for this bug, the reason is almost the same with
bug
1350663.
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
[1] https://hg.mozilla.org/mozilla-central/file/2bc714f86791/layout/painting/FrameLayerBuilder.cpp#l3887
MozReview-Commit-ID: 7lUKTMYPFXo
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -45,16 +45,17 @@
#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 <algorithm>
#include <functional>
using namespace mozilla::layers;
using namespace mozilla::gfx;
namespace mozilla {
@@ -1613,45 +1614,61 @@ 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& aMaskBounds)
+ : mMaskBounds(aMaskBounds),
mMaskStyle(aFrame->StyleSVGReset()->mMask)
{
}
void operator=(CSSMaskLayerUserData&& aOther)
{
- mMaskSize = aOther.mMaskSize;
+ mMaskBounds = aOther.mMaskBounds;
mMaskStyle = Move(aOther.mMaskStyle);
}
bool
- operator==(const CSSMaskLayerUserData& aOther) const
+ IsEqual(const CSSMaskLayerUserData& aOther, nsIFrame* aMaskedFrame) 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 (mMaskBounds.Size() != aOther.mMaskBounds.Size()) {
+ return false;
+ }
+
+ // For SVG mask(or clipPath):
+ // The coordinate space of the mask layer that we created is in
+ // objectBoundingBox. If any of maskUnits or
+ // maskContentUnits is userSpaceOnUse, then mask/maskContent and the mask
+ // layer, where the mask was drawn, stay in different coordinate space.
+ // Since they are in different coordinate space, change the position of any
+ // one of them makes mask layer not reusable, and modifying the position of
+ // the masked frame does change the position of mask layer.
+ //
+ // CSS image mask's coordinate space is always objectBoudingBox, we do not
+ // have to worry about it.
+ if (mMaskBounds.TopLeft() != aOther.mMaskBounds.TopLeft() &&
+ nsSVGEffects::HasUserSpaceOnUseUnitsMaskOrClipPath(aMaskedFrame)) {
return false;
}
return mMaskStyle == aOther.mMaskStyle;
}
private:
- nsIntSize mMaskSize;
+ nsIntRect mMaskBounds;
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.
*/
@@ -3881,19 +3898,20 @@ 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.Size());
+ CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect);
nsRect dirtyRect;
- if (!aMaskItem->IsInvalid(dirtyRect) && *oldUserData == newUserData) {
+ if (!aMaskItem->IsInvalid(dirtyRect) &&
+ oldUserData->IsEqual(newUserData, aMaskItem->Frame())) {
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,48 @@ 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);
}
+
+bool
+nsSVGEffects::HasUserSpaceOnUseUnitsMaskOrClipPath(nsIFrame* aFrame)
+{
+ 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();
+ if (maskUnits->AnimVal() == dom::SVG_UNIT_TYPE_USERSPACEONUSE ||
+ contentUnits->AnimVal() == dom::SVG_UNIT_TYPE_USERSPACEONUSE){
+ return true;
+ }
+ }
+ }
+
+ 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();
+ if (clipPathUnits->AnimVal() == dom::SVG_UNIT_TYPE_USERSPACEONUSE) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
--- a/layout/svg/nsSVGEffects.h
+++ b/layout/svg/nsSVGEffects.h
@@ -678,11 +678,18 @@ 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 true if any of the value of
+ * maskUnits/maskContentUnits/clipPathUnits is userSpaceOnUse.
+ **/
+ static bool
+ HasUserSpaceOnUseUnitsMaskOrClipPath(nsIFrame* aFrame);
};
#endif /*NSSVGEFFECTS_H_*/