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 "nsSVGContainerFrame.h"
10 // Keep others in (case-insensitive) order:
11 #include "ImgDrawResult.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/RestyleManager.h"
14 #include "nsCSSFrameConstructor.h"
15 #include "SVGObserverUtils.h"
16 #include "SVGElement.h"
17 #include "nsSVGUtils.h"
18 #include "SVGAnimatedTransformList.h"
19 #include "SVGTextFrame.h"
21 using namespace mozilla
;
22 using namespace mozilla::image
;
24 NS_QUERYFRAME_HEAD(nsSVGContainerFrame
)
25 NS_QUERYFRAME_ENTRY(nsSVGContainerFrame
)
26 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
28 NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame
)
29 NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame
)
30 NS_QUERYFRAME_ENTRY(nsSVGDisplayableFrame
)
31 NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame
)
33 nsIFrame
* NS_NewSVGContainerFrame(PresShell
* aPresShell
,
34 ComputedStyle
* aStyle
) {
35 nsIFrame
* frame
= new (aPresShell
) nsSVGContainerFrame(
36 aStyle
, aPresShell
->GetPresContext(), nsSVGContainerFrame::kClassID
);
37 // If we were called directly, then the frame is for a <defs> or
38 // an unknown element type. In both cases we prevent the content
39 // from displaying directly.
40 frame
->AddStateBits(NS_FRAME_IS_NONDISPLAY
);
44 NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame
)
46 void nsSVGContainerFrame::AppendFrames(ChildListID aListID
,
47 nsFrameList
& aFrameList
) {
48 InsertFrames(aListID
, mFrames
.LastChild(), nullptr, aFrameList
);
51 void nsSVGContainerFrame::InsertFrames(
52 ChildListID aListID
, nsIFrame
* aPrevFrame
,
53 const nsLineList::iterator
* aPrevFrameLine
, nsFrameList
& aFrameList
) {
54 NS_ASSERTION(aListID
== kPrincipalList
, "unexpected child list");
55 NS_ASSERTION(!aPrevFrame
|| aPrevFrame
->GetParent() == this,
56 "inserting after sibling frame with different parent");
58 mFrames
.InsertFrames(this, aPrevFrame
, aFrameList
);
61 void nsSVGContainerFrame::RemoveFrame(ChildListID aListID
,
62 nsIFrame
* aOldFrame
) {
63 NS_ASSERTION(aListID
== kPrincipalList
, "unexpected child list");
65 mFrames
.DestroyFrame(aOldFrame
);
68 bool nsSVGContainerFrame::ComputeCustomOverflow(
69 nsOverflowAreas
& aOverflowAreas
) {
70 if (mState
& NS_FRAME_IS_NONDISPLAY
) {
71 // We don't maintain overflow rects.
72 // XXX It would have be better if the restyle request hadn't even happened.
75 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas
);
79 * Traverses a frame tree, marking any SVGTextFrame frames as dirty
80 * and calling InvalidateRenderingObservers() on it.
82 * The reason that this helper exists is because SVGTextFrame is special.
83 * None of the other SVG frames ever need to be reflowed when they have the
84 * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods
85 * (and those of any containers that they can validly be contained within) do
86 * not make use of mRect or overflow rects. "em" lengths, etc., are resolved
87 * as those elements are painted.
89 * SVGTextFrame is different because its anonymous block and inline frames
90 * need to be reflowed in order to get the correct metrics when things like
91 * inherited font-size of an ancestor changes, or a delayed webfont loads and
94 * However, we only need to do this work if we were reflowed with
95 * NS_FRAME_IS_DIRTY, which implies that all descendants are dirty. When
96 * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally
97 * stop, but this helper looks for any SVGTextFrame descendants of such
98 * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they
99 * are painted their anonymous kid will first get the necessary reflow.
102 void nsSVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame
* aContainer
) {
103 if (!(aContainer
->GetStateBits() & NS_FRAME_IS_DIRTY
)) {
106 NS_ASSERTION((aContainer
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
) ||
107 !aContainer
->IsFrameOfType(nsIFrame::eSVG
),
108 "it is wasteful to call ReflowSVGNonDisplayText on a container "
109 "frame that is not NS_FRAME_IS_NONDISPLAY");
110 for (nsIFrame
* kid
: aContainer
->PrincipalChildList()) {
111 LayoutFrameType type
= kid
->Type();
112 if (type
== LayoutFrameType::SVGText
) {
113 static_cast<SVGTextFrame
*>(kid
)->ReflowSVGNonDisplayText();
115 if (kid
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
) ||
116 type
== LayoutFrameType::SVGForeignObject
||
117 !kid
->IsFrameOfType(nsIFrame::eSVG
)) {
118 ReflowSVGNonDisplayText(kid
);
124 void nsSVGDisplayContainerFrame::Init(nsIContent
* aContent
,
125 nsContainerFrame
* aParent
,
126 nsIFrame
* aPrevInFlow
) {
127 if (!IsSVGOuterSVGFrame()) {
128 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
130 nsSVGContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
133 void nsSVGDisplayContainerFrame::BuildDisplayList(
134 nsDisplayListBuilder
* aBuilder
, const nsDisplayListSet
& aLists
) {
135 // mContent could be a XUL element so check for an SVG element before casting
136 if (mContent
->IsSVGElement() &&
137 !static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
140 DisplayOutline(aBuilder
, aLists
);
141 return BuildDisplayListForNonBlockChildren(aBuilder
, aLists
);
144 void nsSVGDisplayContainerFrame::InsertFrames(
145 ChildListID aListID
, nsIFrame
* aPrevFrame
,
146 const nsLineList::iterator
* aPrevFrameLine
, nsFrameList
& aFrameList
) {
147 // memorize first old frame after insertion point
148 // XXXbz once again, this would work a lot better if the nsIFrame
149 // methods returned framelist iterators....
150 nsIFrame
* nextFrame
= aPrevFrame
? aPrevFrame
->GetNextSibling()
151 : GetChildList(aListID
).FirstChild();
152 nsIFrame
* firstNewFrame
= aFrameList
.FirstChild();
154 // Insert the new frames
155 nsSVGContainerFrame::InsertFrames(aListID
, aPrevFrame
, aPrevFrameLine
,
158 // If we are not a non-display SVG frame and we do not have a bounds update
159 // pending, then we need to schedule one for our new children:
160 if (!(GetStateBits() & (NS_FRAME_IS_DIRTY
| NS_FRAME_HAS_DIRTY_CHILDREN
|
161 NS_FRAME_IS_NONDISPLAY
))) {
162 for (nsIFrame
* kid
= firstNewFrame
; kid
!= nextFrame
;
163 kid
= kid
->GetNextSibling()) {
164 nsSVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
166 MOZ_ASSERT(!(kid
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
167 "Check for this explicitly in the |if|, then");
168 bool isFirstReflow
= (kid
->GetStateBits() & NS_FRAME_FIRST_REFLOW
);
169 // Remove bits so that ScheduleBoundsUpdate will work:
170 kid
->RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
171 NS_FRAME_HAS_DIRTY_CHILDREN
);
172 // No need to invalidate the new kid's old bounds, so we just use
173 // nsSVGUtils::ScheduleBoundsUpdate.
174 nsSVGUtils::ScheduleReflowSVG(kid
);
176 // Add back the NS_FRAME_FIRST_REFLOW bit:
177 kid
->AddStateBits(NS_FRAME_FIRST_REFLOW
);
184 void nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID
,
185 nsIFrame
* aOldFrame
) {
186 SVGObserverUtils::InvalidateRenderingObservers(aOldFrame
);
188 // nsSVGContainerFrame::RemoveFrame doesn't call down into
189 // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
190 // need to schedule a repaint and schedule an update to our overflow rects.
192 PresContext()->RestyleManager()->PostRestyleEvent(
193 mContent
->AsElement(), RestyleHint
{0}, nsChangeHint_UpdateOverflow
);
195 nsSVGContainerFrame::RemoveFrame(aListID
, aOldFrame
);
198 bool nsSVGDisplayContainerFrame::IsSVGTransformed(
199 gfx::Matrix
* aOwnTransform
, gfx::Matrix
* aFromParentTransform
) const {
200 bool foundTransform
= false;
202 // Check if our parent has children-only transforms:
203 nsIFrame
* parent
= GetParent();
205 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
207 static_cast<nsSVGContainerFrame
*>(parent
)->HasChildrenOnlyTransform(
208 aFromParentTransform
);
211 // mContent could be a XUL element so check for an SVG element before casting
212 if (mContent
->IsSVGElement()) {
213 SVGElement
* content
= static_cast<SVGElement
*>(GetContent());
214 SVGAnimatedTransformList
* transformList
=
215 content
->GetAnimatedTransformList();
216 if ((transformList
&& transformList
->HasTransform()) ||
217 content
->GetAnimateMotionTransform()) {
219 *aOwnTransform
= gfx::ToMatrix(
220 content
->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent
));
222 foundTransform
= true;
225 return foundTransform
;
228 //----------------------------------------------------------------------
229 // nsSVGDisplayableFrame methods
231 void nsSVGDisplayContainerFrame::PaintSVG(gfxContext
& aContext
,
232 const gfxMatrix
& aTransform
,
233 imgDrawingParams
& aImgParams
,
234 const nsIntRect
* aDirtyRect
) {
235 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
236 (mState
& NS_FRAME_IS_NONDISPLAY
) ||
237 PresContext()->Document()->IsSVGGlyphsDocument(),
238 "If display lists are enabled, only painting of non-display "
239 "SVG should take this code path");
241 if (StyleEffects()->mOpacity
== 0.0) {
245 gfxMatrix matrix
= aTransform
;
246 if (GetContent()->IsSVGElement()) { // must check before cast
247 matrix
= static_cast<const SVGElement
*>(GetContent())
248 ->PrependLocalTransformsTo(matrix
, eChildToUserSpace
);
249 if (matrix
.IsSingular()) {
254 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
255 gfxMatrix m
= matrix
;
256 // PaintFrameWithEffects() expects the transform that is passed to it to
257 // include the transform to the passed frame's user space, so add it:
258 const nsIContent
* content
= kid
->GetContent();
259 if (content
->IsSVGElement()) { // must check before cast
260 const SVGElement
* element
= static_cast<const SVGElement
*>(content
);
261 if (!element
->HasValidDimensions()) {
262 continue; // nothing to paint for kid
265 m
= nsSVGUtils::GetTransformMatrixInUserSpace(kid
) * m
;
266 if (m
.IsSingular()) {
270 nsSVGUtils::PaintFrameWithEffects(kid
, aContext
, m
, aImgParams
, aDirtyRect
);
274 nsIFrame
* nsSVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
275 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
276 (mState
& NS_FRAME_IS_NONDISPLAY
),
277 "If display lists are enabled, only hit-testing of a "
278 "clipPath's contents should take this code path");
279 return nsSVGUtils::HitTestChildren(this, aPoint
);
282 void nsSVGDisplayContainerFrame::ReflowSVG() {
283 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
284 "This call is probably a wasteful mistake");
286 MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
287 "ReflowSVG mechanism not designed for this");
289 MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>");
291 if (!nsSVGUtils::NeedsReflowSVG(this)) {
295 // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
296 // then our outer-<svg> has previously had its initial reflow. In that case
297 // we need to make sure that that bit has been removed from ourself _before_
298 // recursing over our children to ensure that they know too. Otherwise, we
299 // need to remove it _after_ recursing over our children so that they know
300 // the initial reflow is currently underway.
302 bool isFirstReflow
= (mState
& NS_FRAME_FIRST_REFLOW
);
304 bool outerSVGHasHadFirstReflow
=
305 (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW
) == 0;
307 if (outerSVGHasHadFirstReflow
) {
308 RemoveStateBits(NS_FRAME_FIRST_REFLOW
); // tell our children
311 nsOverflowAreas overflowRects
;
313 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
314 nsSVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
316 MOZ_ASSERT(!(kid
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
317 "Check for this explicitly in the |if|, then");
318 SVGFrame
->ReflowSVG();
320 // We build up our child frame overflows here instead of using
321 // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
322 // frame list, and we're iterating over that list now anyway.
323 ConsiderChildOverflow(overflowRects
, kid
);
325 // Inside a non-display container frame, we might have some
326 // SVGTextFrames. We need to cause those to get reflowed in
327 // case they are the target of a rendering observer.
328 NS_ASSERTION(kid
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
,
329 "expected kid to be a NS_FRAME_IS_NONDISPLAY frame");
330 if (kid
->GetStateBits() & NS_FRAME_IS_DIRTY
) {
331 nsSVGContainerFrame
* container
= do_QueryFrame(kid
);
332 if (container
&& container
->GetContent()->IsSVGElement()) {
333 ReflowSVGNonDisplayText(container
);
339 // <svg> can create an SVG viewport with an offset due to its
340 // x/y/width/height attributes, and <use> can introduce an offset with an
341 // empty mRect (any width/height is copied to an anonymous <svg> child).
342 // Other than that containers should not set mRect since all other offsets
343 // come from transforms, which are accounted for by nsDisplayTransform.
344 // Note that we rely on |overflow:visible| to allow display list items to be
345 // created for our children.
346 MOZ_ASSERT(mContent
->IsAnyOfSVGElements(nsGkAtoms::svg
, nsGkAtoms::symbol
) ||
347 (mContent
->IsSVGElement(nsGkAtoms::use
) &&
348 mRect
.Size() == nsSize(0, 0)) ||
349 mRect
.IsEqualEdges(nsRect()),
350 "Only inner-<svg>/<use> is expected to have mRect set");
353 // Make sure we have our filter property (if any) before calling
354 // FinishAndStoreOverflow (subsequent filter changes are handled off
355 // nsChangeHint_UpdateEffects):
356 SVGObserverUtils::UpdateEffects(this);
359 FinishAndStoreOverflow(overflowRects
, mRect
.Size());
361 // Remove state bits after FinishAndStoreOverflow so that it doesn't
362 // invalidate on first reflow:
363 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
364 NS_FRAME_HAS_DIRTY_CHILDREN
);
367 void nsSVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags
) {
368 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
369 "Invalidation logic may need adjusting");
371 if (aFlags
& TRANSFORM_CHANGED
) {
372 // make sure our cached transform matrix gets (lazily) updated
376 nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags
);
379 SVGBBox
nsSVGDisplayContainerFrame::GetBBoxContribution(
380 const Matrix
& aToBBoxUserspace
, uint32_t aFlags
) {
383 nsIFrame
* kid
= mFrames
.FirstChild();
385 nsIContent
* content
= kid
->GetContent();
386 nsSVGDisplayableFrame
* svgKid
= do_QueryFrame(kid
);
387 // content could be a XUL element so check for an SVG element before casting
389 (!content
->IsSVGElement() ||
390 static_cast<const SVGElement
*>(content
)->HasValidDimensions())) {
391 gfxMatrix transform
= gfx::ThebesMatrix(aToBBoxUserspace
);
392 if (content
->IsSVGElement()) {
393 transform
= static_cast<SVGElement
*>(content
)->PrependLocalTransformsTo(
394 {}, eChildToUserSpace
) *
395 nsSVGUtils::GetTransformMatrixInUserSpace(kid
) * transform
;
397 // We need to include zero width/height vertical/horizontal lines, so we
398 // have to use UnionEdges.
399 bboxUnion
.UnionEdges(
400 svgKid
->GetBBoxContribution(gfx::ToMatrix(transform
), aFlags
));
402 kid
= kid
->GetNextSibling();
408 gfxMatrix
nsSVGDisplayContainerFrame::GetCanvasTM() {
410 NS_ASSERTION(GetParent(), "null parent");
412 nsSVGContainerFrame
* parent
=
413 static_cast<nsSVGContainerFrame
*>(GetParent());
414 SVGElement
* content
= static_cast<SVGElement
*>(GetContent());
416 gfxMatrix tm
= content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
418 mCanvasTM
= new gfxMatrix(tm
);