1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsSVGContainerFrame.h"
9 // Keep others in (case-insensitive) order:
10 #include "nsCSSFrameConstructor.h"
11 #include "nsSVGEffects.h"
12 #include "nsSVGElement.h"
13 #include "nsSVGUtils.h"
14 #include "nsSVGAnimatedTransformList.h"
16 using namespace mozilla
;
18 NS_QUERYFRAME_HEAD(nsSVGContainerFrame
)
19 NS_QUERYFRAME_ENTRY(nsSVGContainerFrame
)
20 NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrameBase
)
22 NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame
)
23 NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame
)
24 NS_QUERYFRAME_ENTRY(nsISVGChildFrame
)
25 NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame
)
28 NS_NewSVGContainerFrame(nsIPresShell
* aPresShell
,
29 nsStyleContext
* aContext
)
31 nsIFrame
*frame
= new (aPresShell
) nsSVGContainerFrame(aContext
);
32 // If we were called directly, then the frame is for a <defs> or
33 // an unknown element type. In both cases we prevent the content
34 // from displaying directly.
35 frame
->AddStateBits(NS_STATE_SVG_NONDISPLAY_CHILD
);
39 NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame
)
40 NS_IMPL_FRAMEARENA_HELPERS(nsSVGDisplayContainerFrame
)
43 nsSVGContainerFrame::AppendFrames(ChildListID aListID
,
44 nsFrameList
& aFrameList
)
46 return InsertFrames(aListID
, mFrames
.LastChild(), aFrameList
);
50 nsSVGContainerFrame::InsertFrames(ChildListID aListID
,
52 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
);
64 nsSVGContainerFrame::RemoveFrame(ChildListID aListID
,
67 NS_ASSERTION(aListID
== kPrincipalList
, "unexpected child list");
69 mFrames
.DestroyFrame(aOldFrame
);
74 nsSVGContainerFrame::UpdateOverflow()
76 if (mState
& NS_STATE_SVG_NONDISPLAY_CHILD
) {
77 // We don't maintain overflow rects.
78 // XXX It would have be better if the restyle request hadn't even happened.
81 return nsSVGContainerFrameBase::UpdateOverflow();
85 nsSVGDisplayContainerFrame::Init(nsIContent
* aContent
,
87 nsIFrame
* aPrevInFlow
)
89 if (!(GetStateBits() & NS_STATE_IS_OUTER_SVG
)) {
90 AddStateBits(aParent
->GetStateBits() &
91 (NS_STATE_SVG_NONDISPLAY_CHILD
| NS_STATE_SVG_CLIPPATH_CHILD
));
93 nsSVGContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
97 nsSVGDisplayContainerFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
98 const nsRect
& aDirtyRect
,
99 const nsDisplayListSet
& aLists
)
101 if (mContent
->IsSVG() &&
102 !static_cast<const nsSVGElement
*>(mContent
)->HasValidDimensions()) {
105 return BuildDisplayListForNonBlockChildren(aBuilder
, aDirtyRect
, aLists
);
109 nsSVGDisplayContainerFrame::InsertFrames(ChildListID aListID
,
110 nsIFrame
* aPrevFrame
,
111 nsFrameList
& aFrameList
)
113 // memorize first old frame after insertion point
114 // XXXbz once again, this would work a lot better if the nsIFrame
115 // methods returned framelist iterators....
116 nsIFrame
* firstOldFrame
= aPrevFrame
?
117 aPrevFrame
->GetNextSibling() : GetChildList(aListID
).FirstChild();
118 nsIFrame
* firstNewFrame
= aFrameList
.FirstChild();
120 // Insert the new frames
121 nsSVGContainerFrame::InsertFrames(aListID
, aPrevFrame
, aFrameList
);
123 // If we are not a non-display SVG frame and we do not have a bounds update
124 // pending, then we need to schedule one for our new children:
125 if (!(GetStateBits() &
126 (NS_FRAME_IS_DIRTY
| NS_FRAME_HAS_DIRTY_CHILDREN
|
127 NS_STATE_SVG_NONDISPLAY_CHILD
))) {
128 for (nsIFrame
* kid
= firstNewFrame
; kid
!= firstOldFrame
;
129 kid
= kid
->GetNextSibling()) {
130 nsISVGChildFrame
* SVGFrame
= do_QueryFrame(kid
);
132 NS_ABORT_IF_FALSE(!(kid
->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
),
133 "Check for this explicitly in the |if|, then");
134 bool isFirstReflow
= (kid
->GetStateBits() & NS_FRAME_FIRST_REFLOW
);
135 // Remove bits so that ScheduleBoundsUpdate will work:
136 kid
->RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
137 NS_FRAME_HAS_DIRTY_CHILDREN
);
138 // No need to invalidate the new kid's old bounds, so we just use
139 // nsSVGUtils::ScheduleBoundsUpdate.
140 nsSVGUtils::ScheduleReflowSVG(kid
);
142 // Add back the NS_FRAME_FIRST_REFLOW bit:
143 kid
->AddStateBits(NS_FRAME_FIRST_REFLOW
);
153 nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID
,
156 nsSVGEffects::InvalidateRenderingObservers(aOldFrame
);
158 // nsSVGContainerFrame::RemoveFrame doesn't call down into
159 // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
160 // need to schedule a repaint and schedule an update to our overflow rects.
162 PresContext()->PresShell()->FrameConstructor()->PostRestyleEvent(
163 mContent
->AsElement(), nsRestyleHint(0), nsChangeHint_UpdateOverflow
);
165 nsresult rv
= nsSVGContainerFrame::RemoveFrame(aListID
, aOldFrame
);
167 if (!(GetStateBits() & (NS_STATE_SVG_NONDISPLAY_CHILD
| NS_STATE_IS_OUTER_SVG
))) {
168 nsSVGUtils::NotifyAncestorsOfFilterRegionChange(this);
175 nsSVGDisplayContainerFrame::IsSVGTransformed(gfxMatrix
*aOwnTransform
,
176 gfxMatrix
*aFromParentTransform
) const
178 bool foundTransform
= false;
180 // Check if our parent has children-only transforms:
181 nsIFrame
*parent
= GetParent();
183 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
184 foundTransform
= static_cast<nsSVGContainerFrame
*>(parent
)->
185 HasChildrenOnlyTransform(aFromParentTransform
);
188 if (mContent
->IsSVG()) {
189 nsSVGElement
*content
= static_cast<nsSVGElement
*>(mContent
);
190 nsSVGAnimatedTransformList
* transformList
=
191 content
->GetAnimatedTransformList();
192 if ((transformList
&& transformList
->HasTransform()) ||
193 content
->GetAnimateMotionTransform()) {
195 *aOwnTransform
= content
->PrependLocalTransformsTo(gfxMatrix(),
196 nsSVGElement::eUserSpaceToParent
);
198 foundTransform
= true;
201 return foundTransform
;
204 //----------------------------------------------------------------------
205 // nsISVGChildFrame methods
208 nsSVGDisplayContainerFrame::PaintSVG(nsRenderingContext
* aContext
,
209 const nsIntRect
*aDirtyRect
)
211 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
212 (mState
& NS_STATE_SVG_NONDISPLAY_CHILD
) ||
213 PresContext()->IsGlyph(),
214 "If display lists are enabled, only painting of non-display "
215 "SVG should take this code path");
217 const nsStyleDisplay
*display
= StyleDisplay();
218 if (display
->mOpacity
== 0.0)
221 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
;
222 kid
= kid
->GetNextSibling()) {
223 nsSVGUtils::PaintFrameWithEffects(aContext
, aDirtyRect
, kid
);
229 NS_IMETHODIMP_(nsIFrame
*)
230 nsSVGDisplayContainerFrame::GetFrameForPoint(const nsPoint
&aPoint
)
232 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
233 (mState
& NS_STATE_SVG_NONDISPLAY_CHILD
),
234 "If display lists are enabled, only hit-testing of a "
235 "clipPath's contents should take this code path");
236 return nsSVGUtils::HitTestChildren(this, aPoint
);
239 NS_IMETHODIMP_(nsRect
)
240 nsSVGDisplayContainerFrame::GetCoveredRegion()
242 return nsSVGUtils::GetCoveredRegion(mFrames
);
246 nsSVGDisplayContainerFrame::ReflowSVG()
248 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
249 "This call is probably a wasteful mistake");
251 NS_ABORT_IF_FALSE(!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
),
252 "ReflowSVG mechanism not designed for this");
254 NS_ABORT_IF_FALSE(GetType() != nsGkAtoms::svgOuterSVGFrame
,
255 "Do not call on outer-<svg>");
257 if (!nsSVGUtils::NeedsReflowSVG(this)) {
261 // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
262 // then our outer-<svg> has previously had its initial reflow. In that case
263 // we need to make sure that that bit has been removed from ourself _before_
264 // recursing over our children to ensure that they know too. Otherwise, we
265 // need to remove it _after_ recursing over our children so that they know
266 // the initial reflow is currently underway.
268 bool outerSVGHasHadFirstReflow
=
269 (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW
) == 0;
271 if (outerSVGHasHadFirstReflow
) {
272 mState
&= ~NS_FRAME_FIRST_REFLOW
; // tell our children
275 nsOverflowAreas overflowRects
;
277 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
;
278 kid
= kid
->GetNextSibling()) {
279 nsISVGChildFrame
* SVGFrame
= do_QueryFrame(kid
);
281 NS_ABORT_IF_FALSE(!(kid
->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
),
282 "Check for this explicitly in the |if|, then");
283 kid
->AddStateBits(mState
& NS_FRAME_IS_DIRTY
);
284 SVGFrame
->ReflowSVG();
286 // We build up our child frame overflows here instead of using
287 // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
288 // frame list, and we're iterating over that list now anyway.
289 ConsiderChildOverflow(overflowRects
, kid
);
293 // <svg> can create an SVG viewport with an offset due to its
294 // x/y/width/height attributes, and <use> can introduce an offset with an
295 // empty mRect (any width/height is copied to an anonymous <svg> child).
296 // Other than that containers should not set mRect since all other offsets
297 // come from transforms, which are accounted for by nsDisplayTransform.
298 // Note that we rely on |overflow:visible| to allow display list items to be
299 // created for our children.
300 NS_ABORT_IF_FALSE(mContent
->Tag() == nsGkAtoms::svg
||
301 (mContent
->Tag() == nsGkAtoms::use
&&
302 mRect
.Size() == nsSize(0,0)) ||
303 mRect
.IsEqualEdges(nsRect()),
304 "Only inner-<svg>/<use> is expected to have mRect set");
306 if (mState
& NS_FRAME_FIRST_REFLOW
) {
307 // Make sure we have our filter property (if any) before calling
308 // FinishAndStoreOverflow (subsequent filter changes are handled off
309 // nsChangeHint_UpdateEffects):
310 nsSVGEffects::UpdateEffects(this);
313 FinishAndStoreOverflow(overflowRects
, mRect
.Size());
315 // Remove state bits after FinishAndStoreOverflow so that it doesn't
316 // invalidate on first reflow:
317 mState
&= ~(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
318 NS_FRAME_HAS_DIRTY_CHILDREN
);
322 nsSVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags
)
324 NS_ABORT_IF_FALSE(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
325 "Invalidation logic may need adjusting");
327 nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags
);
331 nsSVGDisplayContainerFrame::GetBBoxContribution(
332 const gfxMatrix
&aToBBoxUserspace
,
337 nsIFrame
* kid
= mFrames
.FirstChild();
339 nsISVGChildFrame
* svgKid
= do_QueryFrame(kid
);
341 gfxMatrix transform
= aToBBoxUserspace
;
342 nsIContent
*content
= kid
->GetContent();
343 if (content
->IsSVG()) {
344 transform
= static_cast<nsSVGElement
*>(content
)->
345 PrependLocalTransformsTo(aToBBoxUserspace
);
347 // We need to include zero width/height vertical/horizontal lines, so we have
348 // to use UnionEdges.
349 bboxUnion
.UnionEdges(svgKid
->GetBBoxContribution(transform
, aFlags
));
351 kid
= kid
->GetNextSibling();