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, std::move(aFrameList
));
54 void SVGContainerFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
55 const nsLineList::iterator
* aPrevFrameLine
,
56 nsFrameList
&& aFrameList
) {
57 NS_ASSERTION(aListID
== FrameChildListID::Principal
, "unexpected child list");
58 NS_ASSERTION(!aPrevFrame
|| aPrevFrame
->GetParent() == this,
59 "inserting after sibling frame with different parent");
61 mFrames
.InsertFrames(this, aPrevFrame
, std::move(aFrameList
));
64 void SVGContainerFrame::RemoveFrame(DestroyContext
& aContext
,
65 ChildListID aListID
, nsIFrame
* aOldFrame
) {
66 NS_ASSERTION(aListID
== FrameChildListID::Principal
, "unexpected child list");
67 mFrames
.DestroyFrame(aContext
, aOldFrame
);
70 bool SVGContainerFrame::ComputeCustomOverflow(OverflowAreas
& aOverflowAreas
) {
71 if (HasAnyStateBits(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
->IsSVGFrame(),
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();
115 } else if (kid
->IsSVGContainerFrame() ||
116 type
== LayoutFrameType::SVGForeignObject
||
117 !kid
->IsSVGFrame()) {
118 ReflowSVGNonDisplayText(kid
);
123 void SVGDisplayContainerFrame::Init(nsIContent
* aContent
,
124 nsContainerFrame
* aParent
,
125 nsIFrame
* aPrevInFlow
) {
126 if (!IsSVGOuterSVGFrame()) {
127 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
129 SVGContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
132 void SVGDisplayContainerFrame::BuildDisplayList(
133 nsDisplayListBuilder
* aBuilder
, const nsDisplayListSet
& aLists
) {
134 // content could be a XUL element so check for an SVG element
135 if (auto* svg
= SVGElement::FromNode(GetContent())) {
136 if (!svg
->HasValidDimensions()) {
140 DisplayOutline(aBuilder
, aLists
);
141 return BuildDisplayListForNonBlockChildren(aBuilder
, aLists
);
144 void SVGDisplayContainerFrame::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 SVGContainerFrame::InsertFrames(aListID
, aPrevFrame
, aPrevFrameLine
,
156 std::move(aFrameList
));
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 (!HasAnyStateBits(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 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
166 MOZ_ASSERT(!kid
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
167 "Check for this explicitly in the |if|, then");
168 bool isFirstReflow
= kid
->HasAnyStateBits(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 // SVGUtils::ScheduleBoundsUpdate.
174 SVGUtils::ScheduleReflowSVG(kid
);
176 // Add back the NS_FRAME_FIRST_REFLOW bit:
177 kid
->AddStateBits(NS_FRAME_FIRST_REFLOW
);
184 void SVGDisplayContainerFrame::RemoveFrame(DestroyContext
& aContext
,
186 nsIFrame
* aOldFrame
) {
187 SVGObserverUtils::InvalidateRenderingObservers(aOldFrame
);
189 // SVGContainerFrame::RemoveFrame doesn't call down into
190 // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
191 // need to schedule a repaint and schedule an update to our overflow rects.
193 PresContext()->RestyleManager()->PostRestyleEvent(
194 mContent
->AsElement(), RestyleHint
{0}, nsChangeHint_UpdateOverflow
);
196 SVGContainerFrame::RemoveFrame(aContext
, aListID
, aOldFrame
);
199 bool SVGDisplayContainerFrame::IsSVGTransformed(
200 gfx::Matrix
* aOwnTransform
, gfx::Matrix
* aFromParentTransform
) const {
201 return SVGUtils::IsSVGTransformed(this, aOwnTransform
, aFromParentTransform
);
204 //----------------------------------------------------------------------
205 // ISVGDisplayableFrame methods
207 void SVGDisplayContainerFrame::PaintSVG(gfxContext
& aContext
,
208 const gfxMatrix
& aTransform
,
209 imgDrawingParams
& aImgParams
) {
210 NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
) ||
211 PresContext()->Document()->IsSVGGlyphsDocument(),
212 "Only painting of non-display SVG should take this code path");
214 if (StyleEffects()->IsTransparent()) {
218 gfxMatrix matrix
= aTransform
;
219 if (auto* svg
= SVGElement::FromNode(GetContent())) {
220 matrix
= svg
->PrependLocalTransformsTo(matrix
, eChildToUserSpace
);
221 if (matrix
.IsSingular()) {
226 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
227 gfxMatrix m
= matrix
;
228 // PaintFrameWithEffects() expects the transform that is passed to it to
229 // include the transform to the passed frame's user space, so add it:
230 const nsIContent
* content
= kid
->GetContent();
231 if (const SVGElement
* element
= SVGElement::FromNode(content
)) {
232 if (!element
->HasValidDimensions()) {
233 continue; // nothing to paint for kid
236 m
= SVGUtils::GetTransformMatrixInUserSpace(kid
) * m
;
237 if (m
.IsSingular()) {
241 SVGUtils::PaintFrameWithEffects(kid
, aContext
, m
, aImgParams
);
245 nsIFrame
* SVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
246 NS_ASSERTION(HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
),
247 "Only hit-testing of a clipPath's contents should take this "
249 // First we transform aPoint into the coordinate space established by aFrame
250 // for its children (e.g. take account of any 'viewBox' attribute):
251 gfxPoint point
= aPoint
;
252 if (const auto* svg
= SVGElement::FromNode(GetContent())) {
253 gfxMatrix m
= svg
->PrependLocalTransformsTo({}, eChildToUserSpace
);
254 if (!m
.IsIdentity()) {
258 point
= m
.TransformPoint(point
);
262 // Traverse the list in reverse order, so that if we get a hit we know that's
263 // the topmost frame that intersects the point; then we can just return it.
264 nsIFrame
* result
= nullptr;
265 for (nsIFrame
* current
= PrincipalChildList().LastChild(); current
;
266 current
= current
->GetPrevSibling()) {
267 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(current
);
271 const nsIContent
* content
= current
->GetContent();
272 if (const auto* svg
= SVGElement::FromNode(content
)) {
273 if (!svg
->HasValidDimensions()) {
277 // GetFrameForPoint() expects a point in its frame's SVG user space, so
278 // we need to convert to that space:
280 if (const auto* svg
= SVGElement::FromNode(content
)) {
281 gfxMatrix m
= svg
->PrependLocalTransformsTo({}, eUserSpaceToParent
);
282 if (!m
.IsIdentity()) {
286 p
= m
.TransformPoint(p
);
289 result
= SVGFrame
->GetFrameForPoint(p
);
295 if (result
&& !SVGUtils::HitTestClip(this, aPoint
)) {
302 void SVGDisplayContainerFrame::ReflowSVG() {
303 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
304 "This call is probably a wasteful mistake");
306 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
307 "ReflowSVG mechanism not designed for this");
309 MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>");
311 if (!SVGUtils::NeedsReflowSVG(this)) {
315 // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
316 // then our outer-<svg> has previously had its initial reflow. In that case
317 // we need to make sure that that bit has been removed from ourself _before_
318 // recursing over our children to ensure that they know too. Otherwise, we
319 // need to remove it _after_ recursing over our children so that they know
320 // the initial reflow is currently underway.
322 bool isFirstReflow
= HasAnyStateBits(NS_FRAME_FIRST_REFLOW
);
324 bool outerSVGHasHadFirstReflow
=
325 !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
);
327 if (outerSVGHasHadFirstReflow
) {
328 RemoveStateBits(NS_FRAME_FIRST_REFLOW
); // tell our children
331 OverflowAreas overflowRects
;
333 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
334 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
336 MOZ_ASSERT(!kid
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
337 "Check for this explicitly in the |if|, then");
338 SVGFrame
->ReflowSVG();
340 // We build up our child frame overflows here instead of using
341 // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
342 // frame list, and we're iterating over that list now anyway.
343 ConsiderChildOverflow(overflowRects
, kid
);
345 // Inside a non-display container frame, we might have some
346 // SVGTextFrames. We need to cause those to get reflowed in
347 // case they are the target of a rendering observer.
349 kid
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
) || !kid
->IsSVGFrame(),
350 "expected kid to be a NS_FRAME_IS_NONDISPLAY frame or not SVG");
351 if (kid
->HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
352 SVGContainerFrame
* container
= do_QueryFrame(kid
);
353 if (container
&& container
->GetContent()->IsSVGElement()) {
354 ReflowSVGNonDisplayText(container
);
360 // <svg> can create an SVG viewport with an offset due to its
361 // x/y/width/height attributes, and <use> can introduce an offset with an
362 // empty mRect (any width/height is copied to an anonymous <svg> child).
363 // Other than that containers should not set mRect since all other offsets
364 // come from transforms, which are accounted for by nsDisplayTransform.
365 // Note that we rely on |overflow:visible| to allow display list items to be
366 // created for our children.
367 MOZ_ASSERT(mContent
->IsAnyOfSVGElements(nsGkAtoms::svg
, nsGkAtoms::symbol
) ||
368 (mContent
->IsSVGElement(nsGkAtoms::use
) &&
369 mRect
.Size() == nsSize(0, 0)) ||
370 mRect
.IsEqualEdges(nsRect()),
371 "Only inner-<svg>/<use> is expected to have mRect set");
374 // Make sure we have our filter property (if any) before calling
375 // FinishAndStoreOverflow (subsequent filter changes are handled off
376 // nsChangeHint_UpdateEffects):
377 SVGObserverUtils::UpdateEffects(this);
380 FinishAndStoreOverflow(overflowRects
, mRect
.Size());
382 // Remove state bits after FinishAndStoreOverflow so that it doesn't
383 // invalidate on first reflow:
384 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
385 NS_FRAME_HAS_DIRTY_CHILDREN
);
388 void SVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags
) {
389 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
390 "Invalidation logic may need adjusting");
392 if (aFlags
& TRANSFORM_CHANGED
) {
393 // make sure our cached transform matrix gets (lazily) updated
397 SVGUtils::NotifyChildrenOfSVGChange(this, aFlags
);
400 SVGBBox
SVGDisplayContainerFrame::GetBBoxContribution(
401 const Matrix
& aToBBoxUserspace
, uint32_t aFlags
) {
404 for (nsIFrame
* kid
: mFrames
) {
405 ISVGDisplayableFrame
* svgKid
= do_QueryFrame(kid
);
409 // content could be a XUL element
410 auto* svg
= SVGElement::FromNode(kid
->GetContent());
411 if (svg
&& !svg
->HasValidDimensions()) {
414 gfxMatrix transform
= gfx::ThebesMatrix(aToBBoxUserspace
);
416 transform
= svg
->PrependLocalTransformsTo({}, eChildToUserSpace
) *
417 SVGUtils::GetTransformMatrixInUserSpace(kid
) * transform
;
419 // We need to include zero width/height vertical/horizontal lines, so we
420 // have to use UnionEdges.
421 bboxUnion
.UnionEdges(
422 svgKid
->GetBBoxContribution(gfx::ToMatrix(transform
), aFlags
));
428 gfxMatrix
SVGDisplayContainerFrame::GetCanvasTM() {
430 NS_ASSERTION(GetParent(), "null parent");
432 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
433 auto* content
= static_cast<SVGElement
*>(GetContent());
435 gfxMatrix tm
= content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
437 mCanvasTM
= MakeUnique
<gfxMatrix
>(tm
);
443 } // namespace mozilla