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 // Keep in (case-insensitive) order:
8 #include "gfxRect.h"
9 #include "SVGGFrame.h"
10 #include "mozilla/PresShell.h"
11 #include "mozilla/SVGContainerFrame.h"
12 #include "mozilla/SVGObserverUtils.h"
13 #include "mozilla/SVGTextFrame.h"
14 #include "mozilla/SVGUtils.h"
15 #include "mozilla/dom/SVGSwitchElement.h"
16 
17 using namespace mozilla::dom;
18 using namespace mozilla::gfx;
19 using namespace mozilla::image;
20 
21 nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell,
22                                mozilla::ComputedStyle* aStyle);
23 
24 namespace mozilla {
25 
26 class SVGSwitchFrame final : public SVGGFrame {
27   friend nsIFrame* ::NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell,
28                                           ComputedStyle* aStyle);
29 
30  protected:
SVGSwitchFrame(ComputedStyle * aStyle,nsPresContext * aPresContext)31   explicit SVGSwitchFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
32       : SVGGFrame(aStyle, aPresContext, kClassID) {}
33 
34  public:
35   NS_DECL_FRAMEARENA_HELPERS(SVGSwitchFrame)
36 
37 #ifdef DEBUG
38   virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
39                     nsIFrame* aPrevInFlow) override;
40 #endif
41 
42 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const43   virtual nsresult GetFrameName(nsAString& aResult) const override {
44     return MakeFrameName(u"SVGSwitch"_ns, aResult);
45   }
46 #endif
47 
48   virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
49                                 const nsDisplayListSet& aLists) override;
50 
51   // ISVGDisplayableFrame interface:
52   virtual void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
53                         imgDrawingParams& aImgParams,
54                         const nsIntRect* aDirtyRect = nullptr) override;
55   nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
56   virtual void ReflowSVG() override;
57   virtual SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
58                                       uint32_t aFlags) override;
59 
60  private:
61   nsIFrame* GetActiveChildFrame();
62   void ReflowAllSVGTextFramesInsideNonActiveChildren(nsIFrame* aActiveChild);
63   static void AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid);
64 };
65 
66 }  // namespace mozilla
67 
68 //----------------------------------------------------------------------
69 // Implementation
70 
NS_NewSVGSwitchFrame(mozilla::PresShell * aPresShell,mozilla::ComputedStyle * aStyle)71 nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell,
72                                mozilla::ComputedStyle* aStyle) {
73   return new (aPresShell)
74       mozilla::SVGSwitchFrame(aStyle, aPresShell->GetPresContext());
75 }
76 
77 namespace mozilla {
78 
NS_IMPL_FRAMEARENA_HELPERS(SVGSwitchFrame)79 NS_IMPL_FRAMEARENA_HELPERS(SVGSwitchFrame)
80 
81 #ifdef DEBUG
82 void SVGSwitchFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
83                           nsIFrame* aPrevInFlow) {
84   NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svgSwitch),
85                "Content is not an SVG switch");
86 
87   SVGGFrame::Init(aContent, aParent, aPrevInFlow);
88 }
89 #endif /* DEBUG */
90 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)91 void SVGSwitchFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
92                                       const nsDisplayListSet& aLists) {
93   nsIFrame* kid = GetActiveChildFrame();
94   if (kid) {
95     BuildDisplayListForChild(aBuilder, kid, aLists);
96   }
97 }
98 
PaintSVG(gfxContext & aContext,const gfxMatrix & aTransform,imgDrawingParams & aImgParams,const nsIntRect * aDirtyRect)99 void SVGSwitchFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
100                               imgDrawingParams& aImgParams,
101                               const nsIntRect* aDirtyRect) {
102   NS_ASSERTION(
103       !NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY),
104       "If display lists are enabled, only painting of non-display "
105       "SVG should take this code path");
106 
107   if (StyleEffects()->mOpacity == 0.0) {
108     return;
109   }
110 
111   nsIFrame* kid = GetActiveChildFrame();
112   if (kid) {
113     gfxMatrix tm = aTransform;
114     if (kid->GetContent()->IsSVGElement()) {
115       tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm;
116     }
117     SVGUtils::PaintFrameWithEffects(kid, aContext, tm, aImgParams, aDirtyRect);
118   }
119 }
120 
GetFrameForPoint(const gfxPoint & aPoint)121 nsIFrame* SVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint) {
122   NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
123                    (mState & NS_FRAME_IS_NONDISPLAY),
124                "If display lists are enabled, only hit-testing of non-display "
125                "SVG should take this code path");
126 
127   nsIFrame* kid = GetActiveChildFrame();
128   ISVGDisplayableFrame* svgFrame = do_QueryFrame(kid);
129   if (svgFrame) {
130     // Transform the point from our SVG user space to our child's.
131     gfxPoint point = aPoint;
132     gfxMatrix m =
133         static_cast<const SVGElement*>(GetContent())
134             ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
135     m = static_cast<const SVGElement*>(kid->GetContent())
136             ->PrependLocalTransformsTo(m, eUserSpaceToParent);
137     if (!m.IsIdentity()) {
138       if (!m.Invert()) {
139         return nullptr;
140       }
141       point = m.TransformPoint(point);
142     }
143     return svgFrame->GetFrameForPoint(point);
144   }
145 
146   return nullptr;
147 }
148 
shouldReflowSVGTextFrameInside(nsIFrame * aFrame)149 static bool shouldReflowSVGTextFrameInside(nsIFrame* aFrame) {
150   return aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) ||
151          aFrame->IsSVGForeignObjectFrame() ||
152          !aFrame->IsFrameOfType(nsIFrame::eSVG);
153 }
154 
AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame * aKid)155 void SVGSwitchFrame::AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid) {
156   if (!aKid->IsSubtreeDirty()) {
157     return;
158   }
159 
160   if (aKid->IsSVGTextFrame()) {
161     MOZ_ASSERT(!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
162                "A non-display SVGTextFrame directly contained in a display "
163                "container?");
164     static_cast<SVGTextFrame*>(aKid)->ReflowSVG();
165   } else if (shouldReflowSVGTextFrameInside(aKid)) {
166     if (!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
167       for (nsIFrame* kid : aKid->PrincipalChildList()) {
168         AlwaysReflowSVGTextFrameDoForOneKid(kid);
169       }
170     } else {
171       // This child is in a nondisplay context, something like:
172       // <switch>
173       //   ...
174       //   <g><mask><text></text></mask></g>
175       // </switch>
176       // We should not call ReflowSVG on it.
177       SVGContainerFrame::ReflowSVGNonDisplayText(aKid);
178     }
179   }
180 }
181 
ReflowAllSVGTextFramesInsideNonActiveChildren(nsIFrame * aActiveChild)182 void SVGSwitchFrame::ReflowAllSVGTextFramesInsideNonActiveChildren(
183     nsIFrame* aActiveChild) {
184   for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
185     if (aActiveChild == kid) {
186       continue;
187     }
188 
189     AlwaysReflowSVGTextFrameDoForOneKid(kid);
190   }
191 }
192 
ReflowSVG()193 void SVGSwitchFrame::ReflowSVG() {
194   NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
195                "This call is probably a wasteful mistake");
196 
197   MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
198              "ReflowSVG mechanism not designed for this");
199 
200   if (!SVGUtils::NeedsReflowSVG(this)) {
201     return;
202   }
203 
204   // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
205   // then our outer-<svg> has previously had its initial reflow. In that case
206   // we need to make sure that that bit has been removed from ourself _before_
207   // recursing over our children to ensure that they know too. Otherwise, we
208   // need to remove it _after_ recursing over our children so that they know
209   // the initial reflow is currently underway.
210 
211   bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW);
212 
213   bool outerSVGHasHadFirstReflow =
214       !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
215 
216   if (outerSVGHasHadFirstReflow) {
217     RemoveStateBits(NS_FRAME_FIRST_REFLOW);  // tell our children
218   }
219 
220   OverflowAreas overflowRects;
221 
222   nsIFrame* child = GetActiveChildFrame();
223   ReflowAllSVGTextFramesInsideNonActiveChildren(child);
224 
225   ISVGDisplayableFrame* svgChild = do_QueryFrame(child);
226   if (svgChild) {
227     MOZ_ASSERT(!child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
228                "Check for this explicitly in the |if|, then");
229     svgChild->ReflowSVG();
230 
231     // We build up our child frame overflows here instead of using
232     // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
233     // frame list, and we're iterating over that list now anyway.
234     ConsiderChildOverflow(overflowRects, child);
235   } else if (child && shouldReflowSVGTextFrameInside(child)) {
236     MOZ_ASSERT(child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
237                    !child->IsFrameOfType(nsIFrame::eSVG),
238                "Check for this explicitly in the |if|, then");
239     ReflowSVGNonDisplayText(child);
240   }
241 
242   if (isFirstReflow) {
243     // Make sure we have our filter property (if any) before calling
244     // FinishAndStoreOverflow (subsequent filter changes are handled off
245     // nsChangeHint_UpdateEffects):
246     SVGObserverUtils::UpdateEffects(this);
247   }
248 
249   FinishAndStoreOverflow(overflowRects, mRect.Size());
250 
251   // Remove state bits after FinishAndStoreOverflow so that it doesn't
252   // invalidate on first reflow:
253   RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
254                   NS_FRAME_HAS_DIRTY_CHILDREN);
255 }
256 
GetBBoxContribution(const Matrix & aToBBoxUserspace,uint32_t aFlags)257 SVGBBox SVGSwitchFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
258                                             uint32_t aFlags) {
259   nsIFrame* kid = GetActiveChildFrame();
260   ISVGDisplayableFrame* svgKid = do_QueryFrame(kid);
261   if (svgKid) {
262     nsIContent* content = kid->GetContent();
263     gfxMatrix transform = ThebesMatrix(aToBBoxUserspace);
264     if (content->IsSVGElement()) {
265       transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo(
266                       {}, eChildToUserSpace) *
267                   SVGUtils::GetTransformMatrixInUserSpace(kid) * transform;
268     }
269     return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags);
270   }
271   return SVGBBox();
272 }
273 
GetActiveChildFrame()274 nsIFrame* SVGSwitchFrame::GetActiveChildFrame() {
275   nsIContent* activeChild =
276       static_cast<dom::SVGSwitchElement*>(GetContent())->GetActiveChild();
277 
278   if (activeChild) {
279     for (nsIFrame* kid = mFrames.FirstChild(); kid;
280          kid = kid->GetNextSibling()) {
281       if (activeChild == kid->GetContent()) {
282         return kid;
283       }
284     }
285   }
286   return nullptr;
287 }
288 
289 }  // namespace mozilla
290