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/. */
8 #include "SVGContainerFrame.h"
10 // Keep others in (case-insensitive) order:
11 #include "ImgDrawResult.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/RestyleManager.h"
14 #include "mozilla/SVGObserverUtils.h"
15 #include "mozilla/SVGTextFrame.h"
16 #include "mozilla/SVGUtils.h"
17 #include "mozilla/dom/SVGElement.h"
18 #include "nsCSSFrameConstructor.h"
19 #include "SVGAnimatedTransformList.h"
21 using namespace mozilla::dom
;
22 using namespace mozilla::image
;
24 nsIFrame
* NS_NewSVGContainerFrame(mozilla::PresShell
* aPresShell
,
25 mozilla::ComputedStyle
* aStyle
) {
26 nsIFrame
* frame
= new (aPresShell
)
27 mozilla::SVGContainerFrame(aStyle
, aPresShell
->GetPresContext(),
28 mozilla::SVGContainerFrame::kClassID
);
29 // If we were called directly, then the frame is for a <defs> or
30 // an unknown element type. In both cases we prevent the content
31 // from displaying directly.
32 frame
->AddStateBits(NS_FRAME_IS_NONDISPLAY
);
38 NS_QUERYFRAME_HEAD(SVGContainerFrame
)
39 NS_QUERYFRAME_ENTRY(SVGContainerFrame
)
40 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
42 NS_QUERYFRAME_HEAD(SVGDisplayContainerFrame
)
43 NS_QUERYFRAME_ENTRY(SVGDisplayContainerFrame
)
44 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame
)
45 NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame
)
47 NS_IMPL_FRAMEARENA_HELPERS(SVGContainerFrame
)
49 void SVGContainerFrame::AppendFrames(ChildListID aListID
,
50 nsFrameList
& aFrameList
) {
51 InsertFrames(aListID
, mFrames
.LastChild(), nullptr, aFrameList
);
54 void SVGContainerFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
55 const nsLineList::iterator
* aPrevFrameLine
,
56 nsFrameList
& aFrameList
) {
57 NS_ASSERTION(aListID
== kPrincipalList
, "unexpected child list");
58 NS_ASSERTION(!aPrevFrame
|| aPrevFrame
->GetParent() == this,
59 "inserting after sibling frame with different parent");
61 mFrames
.InsertFrames(this, aPrevFrame
, aFrameList
);
64 void SVGContainerFrame::RemoveFrame(ChildListID aListID
, nsIFrame
* aOldFrame
) {
65 NS_ASSERTION(aListID
== kPrincipalList
, "unexpected child list");
67 mFrames
.DestroyFrame(aOldFrame
);
70 bool SVGContainerFrame::ComputeCustomOverflow(OverflowAreas
& aOverflowAreas
) {
71 if (mState
& NS_FRAME_IS_NONDISPLAY
) {
72 // We don't maintain overflow rects.
73 // XXX It would have be better if the restyle request hadn't even happened.
76 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas
);
80 * Traverses a frame tree, marking any SVGTextFrame frames as dirty
81 * and calling InvalidateRenderingObservers() on it.
83 * The reason that this helper exists is because SVGTextFrame is special.
84 * None of the other SVG frames ever need to be reflowed when they have the
85 * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods
86 * (and those of any containers that they can validly be contained within) do
87 * not make use of mRect or overflow rects. "em" lengths, etc., are resolved
88 * as those elements are painted.
90 * SVGTextFrame is different because its anonymous block and inline frames
91 * need to be reflowed in order to get the correct metrics when things like
92 * inherited font-size of an ancestor changes, or a delayed webfont loads and
95 * However, we only need to do this work if we were reflowed with
96 * NS_FRAME_IS_DIRTY, which implies that all descendants are dirty. When
97 * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally
98 * stop, but this helper looks for any SVGTextFrame descendants of such
99 * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they
100 * are painted their anonymous kid will first get the necessary reflow.
103 void SVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame
* aContainer
) {
104 if (!aContainer
->HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
107 MOZ_ASSERT(aContainer
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
) ||
108 !aContainer
->IsFrameOfType(nsIFrame::eSVG
),
109 "it is wasteful to call ReflowSVGNonDisplayText on a container "
110 "frame that is not NS_FRAME_IS_NONDISPLAY or not SVG");
111 for (nsIFrame
* kid
: aContainer
->PrincipalChildList()) {
112 LayoutFrameType type
= kid
->Type();
113 if (type
== LayoutFrameType::SVGText
) {
114 static_cast<SVGTextFrame
*>(kid
)->ReflowSVGNonDisplayText();
116 if (kid
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
) ||
117 type
== LayoutFrameType::SVGForeignObject
||
118 !kid
->IsFrameOfType(nsIFrame::eSVG
)) {
119 ReflowSVGNonDisplayText(kid
);
125 void SVGDisplayContainerFrame::Init(nsIContent
* aContent
,
126 nsContainerFrame
* aParent
,
127 nsIFrame
* aPrevInFlow
) {
128 if (!IsSVGOuterSVGFrame()) {
129 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
131 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED
);
132 SVGContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
135 void SVGDisplayContainerFrame::BuildDisplayList(
136 nsDisplayListBuilder
* aBuilder
, const nsDisplayListSet
& aLists
) {
137 // mContent could be a XUL element so check for an SVG element before casting
138 if (mContent
->IsSVGElement() &&
139 !static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
142 DisplayOutline(aBuilder
, aLists
);
143 return BuildDisplayListForNonBlockChildren(aBuilder
, aLists
);
146 void SVGDisplayContainerFrame::InsertFrames(
147 ChildListID aListID
, nsIFrame
* aPrevFrame
,
148 const nsLineList::iterator
* aPrevFrameLine
, nsFrameList
& aFrameList
) {
149 // memorize first old frame after insertion point
150 // XXXbz once again, this would work a lot better if the nsIFrame
151 // methods returned framelist iterators....
152 nsIFrame
* nextFrame
= aPrevFrame
? aPrevFrame
->GetNextSibling()
153 : GetChildList(aListID
).FirstChild();
154 nsIFrame
* firstNewFrame
= aFrameList
.FirstChild();
156 // Insert the new frames
157 SVGContainerFrame::InsertFrames(aListID
, aPrevFrame
, aPrevFrameLine
,
160 // If we are not a non-display SVG frame and we do not have a bounds update
161 // pending, then we need to schedule one for our new children:
162 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY
| NS_FRAME_HAS_DIRTY_CHILDREN
|
163 NS_FRAME_IS_NONDISPLAY
)) {
164 for (nsIFrame
* kid
= firstNewFrame
; kid
!= nextFrame
;
165 kid
= kid
->GetNextSibling()) {
166 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
168 MOZ_ASSERT(!kid
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
169 "Check for this explicitly in the |if|, then");
170 bool isFirstReflow
= kid
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
);
171 // Remove bits so that ScheduleBoundsUpdate will work:
172 kid
->RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
173 NS_FRAME_HAS_DIRTY_CHILDREN
);
174 // No need to invalidate the new kid's old bounds, so we just use
175 // SVGUtils::ScheduleBoundsUpdate.
176 SVGUtils::ScheduleReflowSVG(kid
);
178 // Add back the NS_FRAME_FIRST_REFLOW bit:
179 kid
->AddStateBits(NS_FRAME_FIRST_REFLOW
);
186 void SVGDisplayContainerFrame::RemoveFrame(ChildListID aListID
,
187 nsIFrame
* aOldFrame
) {
188 SVGObserverUtils::InvalidateRenderingObservers(aOldFrame
);
190 // SVGContainerFrame::RemoveFrame doesn't call down into
191 // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
192 // need to schedule a repaint and schedule an update to our overflow rects.
194 PresContext()->RestyleManager()->PostRestyleEvent(
195 mContent
->AsElement(), RestyleHint
{0}, nsChangeHint_UpdateOverflow
);
197 SVGContainerFrame::RemoveFrame(aListID
, aOldFrame
);
200 bool SVGDisplayContainerFrame::IsSVGTransformed(
201 gfx::Matrix
* aOwnTransform
, gfx::Matrix
* aFromParentTransform
) const {
202 bool foundTransform
= false;
204 // Check if our parent has children-only transforms:
205 nsIFrame
* parent
= GetParent();
207 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
209 static_cast<SVGContainerFrame
*>(parent
)->HasChildrenOnlyTransform(
210 aFromParentTransform
);
213 // mContent could be a XUL element so check for an SVG element before casting
214 if (mContent
->IsSVGElement()) {
215 SVGElement
* content
= static_cast<SVGElement
*>(GetContent());
216 SVGAnimatedTransformList
* transformList
=
217 content
->GetAnimatedTransformList();
218 if ((transformList
&& transformList
->HasTransform()) ||
219 content
->GetAnimateMotionTransform()) {
221 *aOwnTransform
= gfx::ToMatrix(
222 content
->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent
));
224 foundTransform
= true;
227 return foundTransform
;
230 //----------------------------------------------------------------------
231 // ISVGDisplayableFrame methods
233 void SVGDisplayContainerFrame::PaintSVG(gfxContext
& aContext
,
234 const gfxMatrix
& aTransform
,
235 imgDrawingParams
& aImgParams
,
236 const nsIntRect
* aDirtyRect
) {
237 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
238 (mState
& NS_FRAME_IS_NONDISPLAY
) ||
239 PresContext()->Document()->IsSVGGlyphsDocument(),
240 "If display lists are enabled, only painting of non-display "
241 "SVG should take this code path");
243 if (StyleEffects()->mOpacity
== 0.0) {
247 gfxMatrix matrix
= aTransform
;
248 if (GetContent()->IsSVGElement()) { // must check before cast
249 matrix
= static_cast<const SVGElement
*>(GetContent())
250 ->PrependLocalTransformsTo(matrix
, eChildToUserSpace
);
251 if (matrix
.IsSingular()) {
256 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
257 gfxMatrix m
= matrix
;
258 // PaintFrameWithEffects() expects the transform that is passed to it to
259 // include the transform to the passed frame's user space, so add it:
260 const nsIContent
* content
= kid
->GetContent();
261 if (content
->IsSVGElement()) { // must check before cast
262 const SVGElement
* element
= static_cast<const SVGElement
*>(content
);
263 if (!element
->HasValidDimensions()) {
264 continue; // nothing to paint for kid
267 m
= SVGUtils::GetTransformMatrixInUserSpace(kid
) * m
;
268 if (m
.IsSingular()) {
272 SVGUtils::PaintFrameWithEffects(kid
, aContext
, m
, aImgParams
, aDirtyRect
);
276 nsIFrame
* SVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
277 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
278 (mState
& NS_FRAME_IS_NONDISPLAY
),
279 "If display lists are enabled, only hit-testing of a "
280 "clipPath's contents should take this code path");
281 return SVGUtils::HitTestChildren(this, aPoint
);
284 void SVGDisplayContainerFrame::ReflowSVG() {
285 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
286 "This call is probably a wasteful mistake");
288 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
289 "ReflowSVG mechanism not designed for this");
291 MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>");
293 if (!SVGUtils::NeedsReflowSVG(this)) {
297 // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
298 // then our outer-<svg> has previously had its initial reflow. In that case
299 // we need to make sure that that bit has been removed from ourself _before_
300 // recursing over our children to ensure that they know too. Otherwise, we
301 // need to remove it _after_ recursing over our children so that they know
302 // the initial reflow is currently underway.
304 bool isFirstReflow
= (mState
& NS_FRAME_FIRST_REFLOW
);
306 bool outerSVGHasHadFirstReflow
=
307 !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
);
309 if (outerSVGHasHadFirstReflow
) {
310 RemoveStateBits(NS_FRAME_FIRST_REFLOW
); // tell our children
313 OverflowAreas overflowRects
;
315 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
316 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
318 MOZ_ASSERT(!kid
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
319 "Check for this explicitly in the |if|, then");
320 SVGFrame
->ReflowSVG();
322 // We build up our child frame overflows here instead of using
323 // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
324 // frame list, and we're iterating over that list now anyway.
325 ConsiderChildOverflow(overflowRects
, kid
);
327 // Inside a non-display container frame, we might have some
328 // SVGTextFrames. We need to cause those to get reflowed in
329 // case they are the target of a rendering observer.
331 kid
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
) ||
332 !kid
->IsFrameOfType(nsIFrame::eSVG
),
333 "expected kid to be a NS_FRAME_IS_NONDISPLAY frame or not SVG");
334 if (kid
->HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
335 SVGContainerFrame
* container
= do_QueryFrame(kid
);
336 if (container
&& container
->GetContent()->IsSVGElement()) {
337 ReflowSVGNonDisplayText(container
);
343 // <svg> can create an SVG viewport with an offset due to its
344 // x/y/width/height attributes, and <use> can introduce an offset with an
345 // empty mRect (any width/height is copied to an anonymous <svg> child).
346 // Other than that containers should not set mRect since all other offsets
347 // come from transforms, which are accounted for by nsDisplayTransform.
348 // Note that we rely on |overflow:visible| to allow display list items to be
349 // created for our children.
350 MOZ_ASSERT(mContent
->IsAnyOfSVGElements(nsGkAtoms::svg
, nsGkAtoms::symbol
) ||
351 (mContent
->IsSVGElement(nsGkAtoms::use
) &&
352 mRect
.Size() == nsSize(0, 0)) ||
353 mRect
.IsEqualEdges(nsRect()),
354 "Only inner-<svg>/<use> is expected to have mRect set");
357 // Make sure we have our filter property (if any) before calling
358 // FinishAndStoreOverflow (subsequent filter changes are handled off
359 // nsChangeHint_UpdateEffects):
360 SVGObserverUtils::UpdateEffects(this);
363 FinishAndStoreOverflow(overflowRects
, mRect
.Size());
365 // Remove state bits after FinishAndStoreOverflow so that it doesn't
366 // invalidate on first reflow:
367 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
368 NS_FRAME_HAS_DIRTY_CHILDREN
);
371 void SVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags
) {
372 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
373 "Invalidation logic may need adjusting");
375 if (aFlags
& TRANSFORM_CHANGED
) {
376 // make sure our cached transform matrix gets (lazily) updated
380 SVGUtils::NotifyChildrenOfSVGChange(this, aFlags
);
383 SVGBBox
SVGDisplayContainerFrame::GetBBoxContribution(
384 const Matrix
& aToBBoxUserspace
, uint32_t aFlags
) {
387 nsIFrame
* kid
= mFrames
.FirstChild();
389 nsIContent
* content
= kid
->GetContent();
390 ISVGDisplayableFrame
* svgKid
= do_QueryFrame(kid
);
391 // content could be a XUL element so check for an SVG element before casting
393 (!content
->IsSVGElement() ||
394 static_cast<const SVGElement
*>(content
)->HasValidDimensions())) {
395 gfxMatrix transform
= gfx::ThebesMatrix(aToBBoxUserspace
);
396 if (content
->IsSVGElement()) {
397 transform
= static_cast<SVGElement
*>(content
)->PrependLocalTransformsTo(
398 {}, eChildToUserSpace
) *
399 SVGUtils::GetTransformMatrixInUserSpace(kid
) * transform
;
401 // We need to include zero width/height vertical/horizontal lines, so we
402 // have to use UnionEdges.
403 bboxUnion
.UnionEdges(
404 svgKid
->GetBBoxContribution(gfx::ToMatrix(transform
), aFlags
));
406 kid
= kid
->GetNextSibling();
412 gfxMatrix
SVGDisplayContainerFrame::GetCanvasTM() {
414 NS_ASSERTION(GetParent(), "null parent");
416 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
417 auto* content
= static_cast<SVGElement
*>(GetContent());
419 gfxMatrix tm
= content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
421 mCanvasTM
= MakeUnique
<gfxMatrix
>(tm
);
427 } // namespace mozilla