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/. */
6 
7 // Main header first:
8 #include "SVGContainerFrame.h"
9 
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"
20 
21 using namespace mozilla::dom;
22 using namespace mozilla::image;
23 
NS_NewSVGContainerFrame(mozilla::PresShell * aPresShell,mozilla::ComputedStyle * aStyle)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);
33   return frame;
34 }
35 
36 namespace mozilla {
37 
38 NS_QUERYFRAME_HEAD(SVGContainerFrame)
NS_QUERYFRAME_ENTRY(SVGContainerFrame)39   NS_QUERYFRAME_ENTRY(SVGContainerFrame)
40 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
41 
42 NS_QUERYFRAME_HEAD(SVGDisplayContainerFrame)
43   NS_QUERYFRAME_ENTRY(SVGDisplayContainerFrame)
44   NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
45 NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame)
46 
47 NS_IMPL_FRAMEARENA_HELPERS(SVGContainerFrame)
48 
49 void SVGContainerFrame::AppendFrames(ChildListID aListID,
50                                      nsFrameList& aFrameList) {
51   InsertFrames(aListID, mFrames.LastChild(), nullptr, aFrameList);
52 }
53 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & 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");
60 
61   mFrames.InsertFrames(this, aPrevFrame, aFrameList);
62 }
63 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)64 void SVGContainerFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
65   NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
66 
67   mFrames.DestroyFrame(aOldFrame);
68 }
69 
ComputeCustomOverflow(OverflowAreas & aOverflowAreas)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.
74     return false;
75   }
76   return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
77 }
78 
79 /**
80  * Traverses a frame tree, marking any SVGTextFrame frames as dirty
81  * and calling InvalidateRenderingObservers() on it.
82  *
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.
89  *
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
93  * applies.
94  *
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.
101  */
102 /* static */
ReflowSVGNonDisplayText(nsIFrame * aContainer)103 void SVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) {
104   if (!aContainer->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
105     return;
106   }
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();
115     } else {
116       if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) ||
117           type == LayoutFrameType::SVGForeignObject ||
118           !kid->IsFrameOfType(nsIFrame::eSVG)) {
119         ReflowSVGNonDisplayText(kid);
120       }
121     }
122   }
123 }
124 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)125 void SVGDisplayContainerFrame::Init(nsIContent* aContent,
126                                     nsContainerFrame* aParent,
127                                     nsIFrame* aPrevInFlow) {
128   if (!IsSVGOuterSVGFrame()) {
129     AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
130   }
131   AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
132   SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
133 }
134 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)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()) {
140     return;
141   }
142   DisplayOutline(aBuilder, aLists);
143   return BuildDisplayListForNonBlockChildren(aBuilder, aLists);
144 }
145 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)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();
155 
156   // Insert the new frames
157   SVGContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
158                                   aFrameList);
159 
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);
167       if (SVGFrame) {
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);
177         if (isFirstReflow) {
178           // Add back the NS_FRAME_FIRST_REFLOW bit:
179           kid->AddStateBits(NS_FRAME_FIRST_REFLOW);
180         }
181       }
182     }
183   }
184 }
185 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)186 void SVGDisplayContainerFrame::RemoveFrame(ChildListID aListID,
187                                            nsIFrame* aOldFrame) {
188   SVGObserverUtils::InvalidateRenderingObservers(aOldFrame);
189 
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.
193   SchedulePaint();
194   PresContext()->RestyleManager()->PostRestyleEvent(
195       mContent->AsElement(), RestyleHint{0}, nsChangeHint_UpdateOverflow);
196 
197   SVGContainerFrame::RemoveFrame(aListID, aOldFrame);
198 }
199 
IsSVGTransformed(gfx::Matrix * aOwnTransform,gfx::Matrix * aFromParentTransform) const200 bool SVGDisplayContainerFrame::IsSVGTransformed(
201     gfx::Matrix* aOwnTransform, gfx::Matrix* aFromParentTransform) const {
202   bool foundTransform = false;
203 
204   // Check if our parent has children-only transforms:
205   nsIFrame* parent = GetParent();
206   if (parent &&
207       parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
208     foundTransform =
209         static_cast<SVGContainerFrame*>(parent)->HasChildrenOnlyTransform(
210             aFromParentTransform);
211   }
212 
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()) {
220       if (aOwnTransform) {
221         *aOwnTransform = gfx::ToMatrix(
222             content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
223       }
224       foundTransform = true;
225     }
226   }
227   return foundTransform;
228 }
229 
230 //----------------------------------------------------------------------
231 // ISVGDisplayableFrame methods
232 
PaintSVG(gfxContext & aContext,const gfxMatrix & aTransform,imgDrawingParams & aImgParams,const nsIntRect * aDirtyRect)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");
242 
243   if (StyleEffects()->mOpacity == 0.0) {
244     return;
245   }
246 
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()) {
252       return;
253     }
254   }
255 
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
265       }
266 
267       m = SVGUtils::GetTransformMatrixInUserSpace(kid) * m;
268       if (m.IsSingular()) {
269         continue;
270       }
271     }
272     SVGUtils::PaintFrameWithEffects(kid, aContext, m, aImgParams, aDirtyRect);
273   }
274 }
275 
GetFrameForPoint(const gfxPoint & aPoint)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);
282 }
283 
ReflowSVG()284 void SVGDisplayContainerFrame::ReflowSVG() {
285   MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
286              "This call is probably a wasteful mistake");
287 
288   MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
289              "ReflowSVG mechanism not designed for this");
290 
291   MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>");
292 
293   if (!SVGUtils::NeedsReflowSVG(this)) {
294     return;
295   }
296 
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.
303 
304   bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW);
305 
306   bool outerSVGHasHadFirstReflow =
307       !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
308 
309   if (outerSVGHasHadFirstReflow) {
310     RemoveStateBits(NS_FRAME_FIRST_REFLOW);  // tell our children
311   }
312 
313   OverflowAreas overflowRects;
314 
315   for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
316     ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
317     if (SVGFrame) {
318       MOZ_ASSERT(!kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
319                  "Check for this explicitly in the |if|, then");
320       SVGFrame->ReflowSVG();
321 
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);
326     } else {
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.
330       MOZ_ASSERT(
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);
338         }
339       }
340     }
341   }
342 
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");
355 
356   if (isFirstReflow) {
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);
361   }
362 
363   FinishAndStoreOverflow(overflowRects, mRect.Size());
364 
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);
369 }
370 
NotifySVGChanged(uint32_t aFlags)371 void SVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags) {
372   MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
373              "Invalidation logic may need adjusting");
374 
375   if (aFlags & TRANSFORM_CHANGED) {
376     // make sure our cached transform matrix gets (lazily) updated
377     mCanvasTM = nullptr;
378   }
379 
380   SVGUtils::NotifyChildrenOfSVGChange(this, aFlags);
381 }
382 
GetBBoxContribution(const Matrix & aToBBoxUserspace,uint32_t aFlags)383 SVGBBox SVGDisplayContainerFrame::GetBBoxContribution(
384     const Matrix& aToBBoxUserspace, uint32_t aFlags) {
385   SVGBBox bboxUnion;
386 
387   nsIFrame* kid = mFrames.FirstChild();
388   while (kid) {
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
392     if (svgKid &&
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;
400       }
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));
405     }
406     kid = kid->GetNextSibling();
407   }
408 
409   return bboxUnion;
410 }
411 
GetCanvasTM()412 gfxMatrix SVGDisplayContainerFrame::GetCanvasTM() {
413   if (!mCanvasTM) {
414     NS_ASSERTION(GetParent(), "null parent");
415 
416     auto* parent = static_cast<SVGContainerFrame*>(GetParent());
417     auto* content = static_cast<SVGElement*>(GetContent());
418 
419     gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
420 
421     mCanvasTM = MakeUnique<gfxMatrix>(tm);
422   }
423 
424   return *mCanvasTM;
425 }
426 
427 }  // namespace mozilla
428