Bug 1895153 - Implement "Find in page..." menu functionality r=android-reviewers...
[gecko.git] / layout / svg / SVGClipPathFrame.cpp
blob458c7d4889a03a04125350f2c32d862a03d2a91e
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/. */
7 // Main header first:
8 #include "SVGClipPathFrame.h"
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/SVGGeometryFrame.h"
16 #include "mozilla/SVGObserverUtils.h"
17 #include "mozilla/SVGUtils.h"
18 #include "mozilla/dom/SVGClipPathElement.h"
19 #include "mozilla/dom/SVGGeometryElement.h"
20 #include "nsGkAtoms.h"
22 using namespace mozilla::dom;
23 using namespace mozilla::gfx;
24 using namespace mozilla::image;
26 //----------------------------------------------------------------------
27 // Implementation
29 nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell,
30 mozilla::ComputedStyle* aStyle) {
31 return new (aPresShell)
32 mozilla::SVGClipPathFrame(aStyle, aPresShell->GetPresContext());
35 namespace mozilla {
37 NS_IMPL_FRAMEARENA_HELPERS(SVGClipPathFrame)
39 void SVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
40 nsIFrame* aClippedFrame,
41 const gfxMatrix& aMatrix) {
42 nsIFrame* singleClipPathChild = nullptr;
43 DebugOnly<bool> trivial = IsTrivial(&singleClipPathChild);
44 MOZ_ASSERT(trivial, "Caller needs to use GetClipMask");
46 const DrawTarget* drawTarget = aContext.GetDrawTarget();
48 // No need for AutoReferenceChainGuard since simple clip paths by definition
49 // don't reference another clip path.
51 // Restore current transform after applying clip path:
52 gfxContextMatrixAutoSaveRestore autoRestoreTransform(&aContext);
54 RefPtr<Path> clipPath;
56 if (singleClipPathChild) {
57 SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
58 if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) {
59 SVGGeometryElement* pathElement =
60 static_cast<SVGGeometryElement*>(pathFrame->GetContent());
62 gfxMatrix toChildsUserSpace =
63 SVGUtils::GetTransformMatrixInUserSpace(pathFrame) *
64 (GetClipPathTransform(aClippedFrame) * aMatrix);
66 gfxMatrix newMatrix = aContext.CurrentMatrixDouble()
67 .PreMultiply(toChildsUserSpace)
68 .NudgeToIntegers();
69 if (!newMatrix.IsSingular()) {
70 aContext.SetMatrixDouble(newMatrix);
71 FillRule clipRule =
72 SVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
73 clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule);
78 if (clipPath) {
79 aContext.Clip(clipPath);
80 } else {
81 // The spec says clip away everything if we have no children or the
82 // clipping path otherwise can't be resolved:
83 aContext.Clip(Rect());
87 static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask) {
88 MOZ_ASSERT(aExtraMask);
90 Matrix origin = aTarget->GetTransform();
91 aTarget->SetTransform(Matrix());
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);
98 void SVGClipPathFrame::PaintChildren(gfxContext& aMaskContext,
99 nsIFrame* aClippedFrame,
100 const gfxMatrix& aMatrix) {
101 // Check if this clipPath is itself clipped by another clipPath:
102 SVGClipPathFrame* clipPathThatClipsClipPath;
103 // XXX check return value?
104 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath);
105 SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(this, true);
107 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aMaskContext);
108 if (maskUsage.ShouldApplyClipPath()) {
109 clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
110 aMatrix);
111 } else if (maskUsage.ShouldGenerateClipMaskLayer()) {
112 RefPtr<SourceSurface> maskSurface = clipPathThatClipsClipPath->GetClipMask(
113 aMaskContext, aClippedFrame, aMatrix);
114 // We want the mask to be untransformed so use the inverse of the current
115 // transform as the maskTransform to compensate.
116 Matrix maskTransform = aMaskContext.CurrentMatrix();
117 maskTransform.Invert();
118 autoGroupForBlend.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0f,
119 maskSurface, maskTransform);
122 // Paint our children into the mask:
123 for (auto* kid : mFrames) {
124 PaintFrameIntoMask(kid, aClippedFrame, aMaskContext);
127 if (maskUsage.ShouldApplyClipPath()) {
128 aMaskContext.PopClip();
132 void SVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext,
133 nsIFrame* aClippedFrame,
134 const gfxMatrix& aMatrix,
135 SourceSurface* aExtraMask) {
136 static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
138 // A clipPath can reference another clipPath, creating a chain of clipPaths
139 // that must all be applied. We re-enter this method for each clipPath in a
140 // chain, so we need to protect against reference chain related crashes etc.:
141 AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
142 &sRefChainLengthCounter);
143 if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
144 return; // Break reference chain
146 if (!IsValid()) {
147 return;
150 DrawTarget* maskDT = aMaskContext.GetDrawTarget();
151 MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
153 // Paint this clipPath's contents into aMaskDT:
154 // We need to set mMatrixForChildren here so that under the PaintSVG calls
155 // on our children (below) our GetCanvasTM() method will return the correct
156 // transform.
157 mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
159 PaintChildren(aMaskContext, aClippedFrame, aMatrix);
161 if (aExtraMask) {
162 ComposeExtraMask(maskDT, aExtraMask);
166 void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame,
167 nsIFrame* aClippedFrame,
168 gfxContext& aTarget) {
169 ISVGDisplayableFrame* frame = do_QueryFrame(aFrame);
170 if (!frame) {
171 return;
174 // The CTM of each frame referencing us can be different.
175 frame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED);
177 // Children of this clipPath may themselves be clipped.
178 SVGClipPathFrame* clipPathThatClipsChild;
179 // XXX check return value?
180 if (SVGObserverUtils::GetAndObserveClipPath(aFrame,
181 &clipPathThatClipsChild) ==
182 SVGObserverUtils::eHasRefsSomeInvalid) {
183 return;
186 SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(aFrame, true);
187 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aTarget);
188 if (maskUsage.ShouldApplyClipPath()) {
189 clipPathThatClipsChild->ApplyClipPath(
190 aTarget, aClippedFrame,
191 SVGUtils::GetTransformMatrixInUserSpace(aFrame) * mMatrixForChildren);
192 } else if (maskUsage.ShouldGenerateClipMaskLayer()) {
193 RefPtr<SourceSurface> maskSurface = clipPathThatClipsChild->GetClipMask(
194 aTarget, aClippedFrame,
195 SVGUtils::GetTransformMatrixInUserSpace(aFrame) * mMatrixForChildren);
197 // We want the mask to be untransformed so use the inverse of the current
198 // transform as the maskTransform to compensate.
199 Matrix maskTransform = aTarget.CurrentMatrix();
200 maskTransform.Invert();
201 autoGroupForBlend.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0f,
202 maskSurface, maskTransform);
205 gfxMatrix toChildsUserSpace = mMatrixForChildren;
206 nsIFrame* child = do_QueryFrame(frame);
207 nsIContent* childContent = child->GetContent();
208 if (childContent->IsSVGElement()) {
209 toChildsUserSpace =
210 SVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren;
213 // clipPath does not result in any image rendering, so we just use a dummy
214 // imgDrawingParams instead of requiring our caller to pass one.
215 image::imgDrawingParams imgParams;
217 // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
218 // SVGGeometryFrame::Render checks for that state bit and paints
219 // only the geometry (opaque black) if set.
220 frame->PaintSVG(aTarget, toChildsUserSpace, imgParams);
222 if (maskUsage.ShouldApplyClipPath()) {
223 aTarget.PopClip();
227 already_AddRefed<SourceSurface> SVGClipPathFrame::GetClipMask(
228 gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
229 const gfxMatrix& aMatrix, SourceSurface* aExtraMask) {
230 RefPtr<DrawTarget> maskDT =
231 aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget(
232 Rect(), SurfaceFormat::A8);
233 if (!maskDT) {
234 return nullptr;
237 gfxContext maskContext(maskDT, /* aPreserveTransform */ true);
238 PaintClipMask(maskContext, aClippedFrame, aMatrix, aExtraMask);
240 RefPtr<SourceSurface> surface = maskDT->Snapshot();
241 return surface.forget();
244 bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
245 const gfxPoint& aPoint) {
246 static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
248 // A clipPath can reference another clipPath, creating a chain of clipPaths
249 // that must all be applied. We re-enter this method for each clipPath in a
250 // chain, so we need to protect against reference chain related crashes etc.:
251 AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
252 &sRefChainLengthCounter);
253 if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
254 return false; // Break reference chain
256 if (!IsValid()) {
257 return false;
260 gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
261 if (!matrix.Invert()) {
262 return false;
264 gfxPoint point = matrix.TransformPoint(aPoint);
266 // clipPath elements can themselves be clipped by a different clip path. In
267 // that case the other clip path further clips away the element that is being
268 // clipped by the original clipPath. If this clipPath is being clipped by a
269 // different clip path we need to check if it prevents the original element
270 // from receiving events at aPoint:
271 SVGClipPathFrame* clipPathFrame;
272 // XXX check return value?
273 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame);
274 if (clipPathFrame &&
275 !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
276 return false;
279 for (auto* kid : mFrames) {
280 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
281 if (SVGFrame) {
282 gfxPoint pointForChild = point;
284 gfxMatrix m = SVGUtils::GetTransformMatrixInUserSpace(kid);
285 if (!m.IsIdentity()) {
286 if (!m.Invert()) {
287 return false;
289 pointForChild = m.TransformPoint(point);
291 if (SVGFrame->GetFrameForPoint(pointForChild)) {
292 return true;
297 return false;
300 bool SVGClipPathFrame::IsTrivial(nsIFrame** aSingleChild) {
301 // If the clip path is clipped then it's non-trivial
302 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
303 SVGObserverUtils::eHasRefsAllValid) {
304 return false;
307 if (aSingleChild) {
308 *aSingleChild = nullptr;
311 nsIFrame* foundChild = nullptr;
312 for (auto* kid : mFrames) {
313 ISVGDisplayableFrame* svgChild = do_QueryFrame(kid);
314 if (!svgChild) {
315 continue;
317 // We consider a non-trivial clipPath to be one containing
318 // either more than one svg child and/or a svg container
319 if (foundChild || svgChild->IsDisplayContainer()) {
320 return false;
323 // or where the child is itself clipped
324 if (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) ==
325 SVGObserverUtils::eHasRefsAllValid) {
326 return false;
329 foundChild = kid;
331 if (aSingleChild) {
332 *aSingleChild = foundChild;
334 return true;
337 bool SVGClipPathFrame::IsValid() {
338 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
339 SVGObserverUtils::eHasRefsSomeInvalid) {
340 return false;
343 for (auto* kid : mFrames) {
344 LayoutFrameType kidType = kid->Type();
346 if (kidType == LayoutFrameType::SVGUse) {
347 for (nsIFrame* grandKid : kid->PrincipalChildList()) {
348 LayoutFrameType grandKidType = grandKid->Type();
350 if (grandKidType != LayoutFrameType::SVGGeometry &&
351 grandKidType != LayoutFrameType::SVGText) {
352 return false;
355 continue;
358 if (kidType != LayoutFrameType::SVGGeometry &&
359 kidType != LayoutFrameType::SVGText) {
360 return false;
364 return true;
367 nsresult SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
368 nsAtom* aAttribute,
369 int32_t aModType) {
370 if (aNameSpaceID == kNameSpaceID_None) {
371 if (aAttribute == nsGkAtoms::transform) {
372 SVGObserverUtils::InvalidateRenderingObservers(this);
373 SVGUtils::NotifyChildrenOfSVGChange(
374 this, ISVGDisplayableFrame::TRANSFORM_CHANGED);
376 if (aAttribute == nsGkAtoms::clipPathUnits) {
377 SVGObserverUtils::InvalidateRenderingObservers(this);
381 return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
382 aModType);
385 #ifdef DEBUG
386 void SVGClipPathFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
387 nsIFrame* aPrevInFlow) {
388 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
389 "Content is not an SVG clipPath!");
391 SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
393 #endif
395 gfxMatrix SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; }
397 gfxMatrix SVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) {
398 SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent());
400 gfxMatrix tm = content->PrependLocalTransformsTo({}, eChildToUserSpace) *
401 SVGUtils::GetTransformMatrixInUserSpace(this);
403 SVGAnimatedEnumeration* clipPathUnits =
404 &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
406 uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry |
407 (aClippedFrame->StyleBorder()->mBoxDecorationBreak ==
408 StyleBoxDecorationBreak::Clone
409 ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
410 : 0);
412 return SVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame,
413 flags);
416 SVGBBox SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox,
417 const gfxMatrix& aMatrix,
418 uint32_t aFlags) {
419 SVGClipPathFrame* clipPathThatClipsClipPath;
420 if (SVGObserverUtils::GetAndObserveClipPath(this,
421 &clipPathThatClipsClipPath) ==
422 SVGObserverUtils::eHasRefsSomeInvalid) {
423 return SVGBBox();
426 nsIContent* node = GetContent()->GetFirstChild();
427 SVGBBox unionBBox;
428 for (; node; node = node->GetNextSibling()) {
429 if (nsIFrame* frame = node->GetPrimaryFrame()) {
430 ISVGDisplayableFrame* svg = do_QueryFrame(frame);
431 if (svg) {
432 gfxMatrix matrix =
433 SVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix;
434 SVGBBox tmpBBox = svg->GetBBoxContribution(
435 gfx::ToMatrix(matrix), SVGUtils::eBBoxIncludeFillGeometry);
436 SVGClipPathFrame* clipPathFrame;
437 if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) !=
438 SVGObserverUtils::eHasRefsSomeInvalid &&
439 clipPathFrame) {
440 tmpBBox =
441 clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags);
443 if (!(aFlags & SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) {
444 tmpBBox.Intersect(aBBox);
446 unionBBox.UnionEdges(tmpBBox);
451 if (clipPathThatClipsClipPath) {
452 unionBBox.Intersect(clipPathThatClipsClipPath->GetBBoxForClipPathFrame(
453 aBBox, aMatrix, aFlags));
455 return unionBBox;
458 bool SVGClipPathFrame::IsSVGTransformed(Matrix* aOwnTransforms,
459 Matrix* aFromParentTransforms) const {
460 const auto* e = static_cast<SVGElement const*>(GetContent());
461 Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent));
463 if (m.IsIdentity()) {
464 return false;
467 if (aOwnTransforms) {
468 *aOwnTransforms = m;
471 return true;
474 } // namespace mozilla