1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 // Main header first:
8 #include "nsSVGClipPathFrame.h"
9
10 // Keep others in (case-insensitive) order:
11 #include "AutoReferenceChainGuard.h"
12 #include "ImgDrawResult.h"
13 #include "gfxContext.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/dom/SVGClipPathElement.h"
16 #include "nsGkAtoms.h"
17 #include "SVGObserverUtils.h"
18 #include "SVGGeometryElement.h"
19 #include "SVGGeometryFrame.h"
20 #include "nsSVGUtils.h"
21
22 using namespace mozilla;
23 using namespace mozilla::dom;
24 using namespace mozilla::gfx;
25 using namespace mozilla::image;
26
27 //----------------------------------------------------------------------
28 // Implementation
29
NS_NewSVGClipPathFrame(PresShell * aPresShell,ComputedStyle * aStyle)30 nsIFrame* NS_NewSVGClipPathFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
31 return new (aPresShell)
32 nsSVGClipPathFrame(aStyle, aPresShell->GetPresContext());
33 }
34
NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)35 NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)
36
37 void nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
38 nsIFrame* aClippedFrame,
39 const gfxMatrix& aMatrix) {
40 MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");
41
42 const DrawTarget* drawTarget = aContext.GetDrawTarget();
43
44 // No need for AutoReferenceChainGuard since simple clip paths by definition
45 // don't reference another clip path.
46
47 // Restore current transform after applying clip path:
48 gfxContextMatrixAutoSaveRestore autoRestore(&aContext);
49
50 RefPtr<Path> clipPath;
51
52 nsSVGDisplayableFrame* singleClipPathChild = nullptr;
53 IsTrivial(&singleClipPathChild);
54
55 if (singleClipPathChild) {
56 SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
57 if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) {
58 SVGGeometryElement* pathElement =
59 static_cast<SVGGeometryElement*>(pathFrame->GetContent());
60
61 gfxMatrix toChildsUserSpace =
62 nsSVGUtils::GetTransformMatrixInUserSpace(pathFrame) *
63 (GetClipPathTransform(aClippedFrame) * aMatrix);
64
65 gfxMatrix newMatrix = aContext.CurrentMatrixDouble()
66 .PreMultiply(toChildsUserSpace)
67 .NudgeToIntegers();
68 if (!newMatrix.IsSingular()) {
69 aContext.SetMatrixDouble(newMatrix);
70 FillRule clipRule =
71 nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
72 clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule);
73 }
74 }
75 }
76
77 if (clipPath) {
78 aContext.Clip(clipPath);
79 } else {
80 // The spec says clip away everything if we have no children or the
81 // clipping path otherwise can't be resolved:
82 aContext.Clip(Rect());
83 }
84 }
85
ComposeExtraMask(DrawTarget * aTarget,SourceSurface * aExtraMask,const Matrix & aExtraMasksTransform)86 static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask,
87 const Matrix& aExtraMasksTransform) {
88 MOZ_ASSERT(aExtraMask);
89
90 Matrix origin = aTarget->GetTransform();
91 aTarget->SetTransform(aExtraMasksTransform * aTarget->GetTransform());
92 aTarget->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
93 aExtraMask, Point(0, 0),
94 DrawOptions(1.0, CompositionOp::OP_IN));
95 aTarget->SetTransform(origin);
96 }
97
PaintClipMask(gfxContext & aMaskContext,nsIFrame * aClippedFrame,const gfxMatrix & aMatrix,SourceSurface * aExtraMask,const Matrix & aExtraMasksTransform)98 void nsSVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext,
99 nsIFrame* aClippedFrame,
100 const gfxMatrix& aMatrix,
101 SourceSurface* aExtraMask,
102 const Matrix& aExtraMasksTransform) {
103 static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
104
105 // A clipPath can reference another clipPath, creating a chain of clipPaths
106 // that must all be applied. We re-enter this method for each clipPath in a
107 // chain, so we need to protect against reference chain related crashes etc.:
108 AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
109 &sRefChainLengthCounter);
110 if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
111 return; // Break reference chain
112 }
113
114 DrawTarget* maskDT = aMaskContext.GetDrawTarget();
115 MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
116
117 // Paint this clipPath's contents into aMaskDT:
118 // We need to set mMatrixForChildren here so that under the PaintSVG calls
119 // on our children (below) our GetCanvasTM() method will return the correct
120 // transform.
121 mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
122
123 // Check if this clipPath is itself clipped by another clipPath:
124 nsSVGClipPathFrame* clipPathThatClipsClipPath;
125 // XXX check return value?
126 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath);
127 nsSVGUtils::MaskUsage maskUsage;
128 nsSVGUtils::DetermineMaskUsage(this, true, maskUsage);
129
130 if (maskUsage.shouldApplyClipPath) {
131 clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
132 aMatrix);
133 } else if (maskUsage.shouldGenerateClipMaskLayer) {
134 RefPtr<SourceSurface> maskSurface = clipPathThatClipsClipPath->GetClipMask(
135 aMaskContext, aClippedFrame, aMatrix);
136 // We want the mask to be untransformed so use the inverse of the current
137 // transform as the maskTransform to compensate.
138 Matrix maskTransform = aMaskContext.CurrentMatrix();
139 maskTransform.Invert();
140 aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface,
141 maskTransform);
142 // The corresponding PopGroupAndBlend call below will mask the
143 // blend using |maskSurface|.
144 }
145
146 // Paint our children into the mask:
147 for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
148 PaintFrameIntoMask(kid, aClippedFrame, aMaskContext);
149 }
150
151 if (maskUsage.shouldGenerateClipMaskLayer) {
152 aMaskContext.PopGroupAndBlend();
153 } else if (maskUsage.shouldApplyClipPath) {
154 aMaskContext.PopClip();
155 }
156
157 if (aExtraMask) {
158 ComposeExtraMask(maskDT, aExtraMask, aExtraMasksTransform);
159 }
160 }
161
PaintFrameIntoMask(nsIFrame * aFrame,nsIFrame * aClippedFrame,gfxContext & aTarget)162 void nsSVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame,
163 nsIFrame* aClippedFrame,
164 gfxContext& aTarget) {
165 nsSVGDisplayableFrame* frame = do_QueryFrame(aFrame);
166 if (!frame) {
167 return;
168 }
169
170 // The CTM of each frame referencing us can be different.
171 frame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
172
173 // Children of this clipPath may themselves be clipped.
174 nsSVGClipPathFrame* clipPathThatClipsChild;
175 // XXX check return value?
176 if (SVGObserverUtils::GetAndObserveClipPath(aFrame,
177 &clipPathThatClipsChild) ==
178 SVGObserverUtils::eHasRefsSomeInvalid) {
179 return;
180 }
181
182 nsSVGUtils::MaskUsage maskUsage;
183 nsSVGUtils::DetermineMaskUsage(aFrame, true, maskUsage);
184 if (maskUsage.shouldApplyClipPath) {
185 clipPathThatClipsChild->ApplyClipPath(aTarget, aClippedFrame,
186 mMatrixForChildren);
187 } else if (maskUsage.shouldGenerateClipMaskLayer) {
188 RefPtr<SourceSurface> maskSurface = clipPathThatClipsChild->GetClipMask(
189 aTarget, aClippedFrame, mMatrixForChildren);
190
191 // We want the mask to be untransformed so use the inverse of the current
192 // transform as the maskTransform to compensate.
193 Matrix maskTransform = aTarget.CurrentMatrix();
194 maskTransform.Invert();
195 aTarget.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface,
196 maskTransform);
197 // The corresponding PopGroupAndBlend call below will mask the
198 // blend using |maskSurface|.
199 }
200
201 gfxMatrix toChildsUserSpace = mMatrixForChildren;
202 nsIFrame* child = do_QueryFrame(frame);
203 nsIContent* childContent = child->GetContent();
204 if (childContent->IsSVGElement()) {
205 toChildsUserSpace =
206 nsSVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren;
207 }
208
209 // clipPath does not result in any image rendering, so we just use a dummy
210 // imgDrawingParams instead of requiring our caller to pass one.
211 image::imgDrawingParams imgParams;
212
213 // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
214 // SVGGeometryFrame::Render checks for that state bit and paints
215 // only the geometry (opaque black) if set.
216 frame->PaintSVG(aTarget, toChildsUserSpace, imgParams);
217
218 if (maskUsage.shouldGenerateClipMaskLayer) {
219 aTarget.PopGroupAndBlend();
220 } else if (maskUsage.shouldApplyClipPath) {
221 aTarget.PopClip();
222 }
223 }
224
GetClipMask(gfxContext & aReferenceContext,nsIFrame * aClippedFrame,const gfxMatrix & aMatrix,SourceSurface * aExtraMask,const Matrix & aExtraMasksTransform)225 already_AddRefed<SourceSurface> nsSVGClipPathFrame::GetClipMask(
226 gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
227 const gfxMatrix& aMatrix, SourceSurface* aExtraMask,
228 const Matrix& aExtraMasksTransform) {
229 RefPtr<DrawTarget> maskDT =
230 aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget(
231 Rect(), SurfaceFormat::A8);
232 if (!maskDT) {
233 return nullptr;
234 }
235
236 RefPtr<gfxContext> maskContext =
237 gfxContext::CreatePreservingTransformOrNull(maskDT);
238 if (!maskContext) {
239 gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
240 return nullptr;
241 }
242
243 PaintClipMask(*maskContext, aClippedFrame, aMatrix, aExtraMask,
244 aExtraMasksTransform);
245
246 RefPtr<SourceSurface> surface = maskDT->Snapshot();
247 return surface.forget();
248 }
249
PointIsInsideClipPath(nsIFrame * aClippedFrame,const gfxPoint & aPoint)250 bool nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
251 const gfxPoint& aPoint) {
252 static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
253
254 // A clipPath can reference another clipPath, creating a chain of clipPaths
255 // that must all be applied. We re-enter this method for each clipPath in a
256 // chain, so we need to protect against reference chain related crashes etc.:
257 AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
258 &sRefChainLengthCounter);
259 if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
260 return false; // Break reference chain
261 }
262
263 gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
264 if (!matrix.Invert()) {
265 return false;
266 }
267 gfxPoint point = matrix.TransformPoint(aPoint);
268
269 // clipPath elements can themselves be clipped by a different clip path. In
270 // that case the other clip path further clips away the element that is being
271 // clipped by the original clipPath. If this clipPath is being clipped by a
272 // different clip path we need to check if it prevents the original element
273 // from receiving events at aPoint:
274 nsSVGClipPathFrame* clipPathFrame;
275 // XXX check return value?
276 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame);
277 if (clipPathFrame &&
278 !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
279 return false;
280 }
281
282 for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
283 nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
284 if (SVGFrame) {
285 gfxPoint pointForChild = point;
286
287 gfxMatrix m = nsSVGUtils::GetTransformMatrixInUserSpace(kid);
288 if (!m.IsIdentity()) {
289 if (!m.Invert()) {
290 return false;
291 }
292 pointForChild = m.TransformPoint(point);
293 }
294 if (SVGFrame->GetFrameForPoint(pointForChild)) {
295 return true;
296 }
297 }
298 }
299
300 return false;
301 }
302
IsTrivial(nsSVGDisplayableFrame ** aSingleChild)303 bool nsSVGClipPathFrame::IsTrivial(nsSVGDisplayableFrame** aSingleChild) {
304 // If the clip path is clipped then it's non-trivial
305 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
306 SVGObserverUtils::eHasRefsAllValid) {
307 return false;
308 }
309
310 if (aSingleChild) {
311 *aSingleChild = nullptr;
312 }
313
314 nsSVGDisplayableFrame* foundChild = nullptr;
315
316 for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
317 nsSVGDisplayableFrame* svgChild = do_QueryFrame(kid);
318 if (svgChild) {
319 // We consider a non-trivial clipPath to be one containing
320 // either more than one svg child and/or a svg container
321 if (foundChild || svgChild->IsDisplayContainer()) {
322 return false;
323 }
324
325 // or where the child is itself clipped
326 if (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) ==
327 SVGObserverUtils::eHasRefsAllValid) {
328 return false;
329 }
330
331 foundChild = svgChild;
332 }
333 }
334 if (aSingleChild) {
335 *aSingleChild = foundChild;
336 }
337 return true;
338 }
339
IsValid()340 bool nsSVGClipPathFrame::IsValid() {
341 static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
342
343 // A clipPath can reference another clipPath, creating a chain of clipPaths
344 // that must all be applied. We re-enter this method for each clipPath in a
345 // chain, so we need to protect against reference chain related crashes etc.:
346 AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
347 &sRefChainLengthCounter);
348 if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
349 return false; // Break reference chain
350 }
351
352 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
353 SVGObserverUtils::eHasRefsSomeInvalid) {
354 return false;
355 }
356
357 for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
358 LayoutFrameType kidType = kid->Type();
359
360 if (kidType == LayoutFrameType::SVGUse) {
361 for (nsIFrame* grandKid : kid->PrincipalChildList()) {
362 LayoutFrameType grandKidType = grandKid->Type();
363
364 if (grandKidType != LayoutFrameType::SVGGeometry &&
365 grandKidType != LayoutFrameType::SVGText) {
366 return false;
367 }
368 }
369 continue;
370 }
371
372 if (kidType != LayoutFrameType::SVGGeometry &&
373 kidType != LayoutFrameType::SVGText) {
374 return false;
375 }
376 }
377
378 return true;
379 }
380
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)381 nsresult nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
382 nsAtom* aAttribute,
383 int32_t aModType) {
384 if (aNameSpaceID == kNameSpaceID_None) {
385 if (aAttribute == nsGkAtoms::transform) {
386 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
387 nsSVGUtils::NotifyChildrenOfSVGChange(
388 this, nsSVGDisplayableFrame::TRANSFORM_CHANGED);
389 }
390 if (aAttribute == nsGkAtoms::clipPathUnits) {
391 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
392 }
393 }
394
395 return nsSVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
396 aModType);
397 }
398
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)399 void nsSVGClipPathFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
400 nsIFrame* aPrevInFlow) {
401 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
402 "Content is not an SVG clipPath!");
403
404 AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
405 nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
406 }
407
GetCanvasTM()408 gfxMatrix nsSVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; }
409
GetClipPathTransform(nsIFrame * aClippedFrame)410 gfxMatrix nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) {
411 SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent());
412
413 gfxMatrix tm = content->PrependLocalTransformsTo({}, eChildToUserSpace) *
414 nsSVGUtils::GetTransformMatrixInUserSpace(this);
415
416 SVGAnimatedEnumeration* clipPathUnits =
417 &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
418
419 uint32_t flags = nsSVGUtils::eBBoxIncludeFillGeometry |
420 (aClippedFrame->StyleBorder()->mBoxDecorationBreak ==
421 StyleBoxDecorationBreak::Clone
422 ? nsSVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
423 : 0);
424
425 return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame,
426 flags);
427 }
428
GetBBoxForClipPathFrame(const SVGBBox & aBBox,const gfxMatrix & aMatrix,uint32_t aFlags)429 SVGBBox nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox,
430 const gfxMatrix& aMatrix,
431 uint32_t aFlags) {
432 nsSVGClipPathFrame* clipPathThatClipsClipPath;
433 if (SVGObserverUtils::GetAndObserveClipPath(this,
434 &clipPathThatClipsClipPath) ==
435 SVGObserverUtils::eHasRefsSomeInvalid) {
436 return SVGBBox();
437 }
438
439 nsIContent* node = GetContent()->GetFirstChild();
440 SVGBBox unionBBox, tmpBBox;
441 for (; node; node = node->GetNextSibling()) {
442 SVGElement* svgNode = static_cast<SVGElement*>(node);
443 nsIFrame* frame = svgNode->GetPrimaryFrame();
444 if (frame) {
445 nsSVGDisplayableFrame* svg = do_QueryFrame(frame);
446 if (svg) {
447 gfxMatrix matrix =
448 nsSVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix;
449 tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(matrix),
450 nsSVGUtils::eBBoxIncludeFill);
451 nsSVGClipPathFrame* clipPathFrame;
452 if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) !=
453 SVGObserverUtils::eHasRefsSomeInvalid &&
454 clipPathFrame) {
455 tmpBBox =
456 clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags);
457 }
458 if (!(aFlags & nsSVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) {
459 tmpBBox.Intersect(aBBox);
460 }
461 unionBBox.UnionEdges(tmpBBox);
462 }
463 }
464 }
465
466 if (clipPathThatClipsClipPath) {
467 tmpBBox = clipPathThatClipsClipPath->GetBBoxForClipPathFrame(aBBox, aMatrix,
468 aFlags);
469 unionBBox.Intersect(tmpBBox);
470 }
471 return unionBBox;
472 }
473
IsSVGTransformed(Matrix * aOwnTransforms,Matrix * aFromParentTransforms) const474 bool nsSVGClipPathFrame::IsSVGTransformed(Matrix* aOwnTransforms,
475 Matrix* aFromParentTransforms) const {
476 auto e = static_cast<SVGElement const*>(GetContent());
477 Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent));
478
479 if (m.IsIdentity()) {
480 return false;
481 }
482
483 if (aOwnTransforms) {
484 *aOwnTransforms = m;
485 }
486
487 return true;
488 }
489