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