|
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 // Main header first: |
|
7 #include "nsSVGInnerSVGFrame.h" |
|
8 |
|
9 // Keep others in (case-insensitive) order: |
|
10 #include "gfxContext.h" |
|
11 #include "nsIFrame.h" |
|
12 #include "nsISVGChildFrame.h" |
|
13 #include "nsRenderingContext.h" |
|
14 #include "nsSVGContainerFrame.h" |
|
15 #include "nsSVGEffects.h" |
|
16 #include "nsSVGIntegrationUtils.h" |
|
17 #include "mozilla/dom/SVGSVGElement.h" |
|
18 |
|
19 using namespace mozilla; |
|
20 using namespace mozilla::dom; |
|
21 |
|
22 nsIFrame* |
|
23 NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
24 { |
|
25 return new (aPresShell) nsSVGInnerSVGFrame(aContext); |
|
26 } |
|
27 |
|
28 NS_IMPL_FRAMEARENA_HELPERS(nsSVGInnerSVGFrame) |
|
29 |
|
30 //---------------------------------------------------------------------- |
|
31 // nsIFrame methods |
|
32 |
|
33 NS_QUERYFRAME_HEAD(nsSVGInnerSVGFrame) |
|
34 NS_QUERYFRAME_ENTRY(nsSVGInnerSVGFrame) |
|
35 NS_QUERYFRAME_ENTRY(nsISVGSVGFrame) |
|
36 NS_QUERYFRAME_TAIL_INHERITING(nsSVGInnerSVGFrameBase) |
|
37 |
|
38 #ifdef DEBUG |
|
39 void |
|
40 nsSVGInnerSVGFrame::Init(nsIContent* aContent, |
|
41 nsIFrame* aParent, |
|
42 nsIFrame* aPrevInFlow) |
|
43 { |
|
44 NS_ASSERTION(aContent->IsSVG(nsGkAtoms::svg), |
|
45 "Content is not an SVG 'svg' element!"); |
|
46 |
|
47 nsSVGInnerSVGFrameBase::Init(aContent, aParent, aPrevInFlow); |
|
48 } |
|
49 #endif /* DEBUG */ |
|
50 |
|
51 nsIAtom * |
|
52 nsSVGInnerSVGFrame::GetType() const |
|
53 { |
|
54 return nsGkAtoms::svgInnerSVGFrame; |
|
55 } |
|
56 |
|
57 //---------------------------------------------------------------------- |
|
58 // nsISVGChildFrame methods |
|
59 |
|
60 nsresult |
|
61 nsSVGInnerSVGFrame::PaintSVG(nsRenderingContext *aContext, |
|
62 const nsIntRect *aDirtyRect, |
|
63 nsIFrame* aTransformRoot) |
|
64 { |
|
65 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || |
|
66 (mState & NS_FRAME_IS_NONDISPLAY), |
|
67 "If display lists are enabled, only painting of non-display " |
|
68 "SVG should take this code path"); |
|
69 |
|
70 gfxContextAutoSaveRestore autoSR; |
|
71 |
|
72 if (StyleDisplay()->IsScrollableOverflow()) { |
|
73 float x, y, width, height; |
|
74 static_cast<SVGSVGElement*>(mContent)-> |
|
75 GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); |
|
76 |
|
77 if (width <= 0 || height <= 0) { |
|
78 return NS_OK; |
|
79 } |
|
80 |
|
81 nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent); |
|
82 gfxMatrix clipTransform = parent->GetCanvasTM(FOR_PAINTING, aTransformRoot); |
|
83 |
|
84 gfxContext *gfx = aContext->ThebesContext(); |
|
85 autoSR.SetContext(gfx); |
|
86 gfxRect clipRect = |
|
87 nsSVGUtils::GetClipRectForFrame(this, x, y, width, height); |
|
88 nsSVGUtils::SetClipRect(gfx, clipTransform, clipRect); |
|
89 } |
|
90 |
|
91 return nsSVGInnerSVGFrameBase::PaintSVG(aContext, aDirtyRect); |
|
92 } |
|
93 |
|
94 void |
|
95 nsSVGInnerSVGFrame::ReflowSVG() |
|
96 { |
|
97 // mRect must be set before FinishAndStoreOverflow is called in order |
|
98 // for our overflow areas to be clipped correctly. |
|
99 float x, y, width, height; |
|
100 static_cast<SVGSVGElement*>(mContent)-> |
|
101 GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); |
|
102 mRect = nsLayoutUtils::RoundGfxRectToAppRect( |
|
103 gfxRect(x, y, width, height), |
|
104 PresContext()->AppUnitsPerCSSPixel()); |
|
105 |
|
106 // If we have a filter, we need to invalidate ourselves because filter |
|
107 // output can change even if none of our descendants need repainting. |
|
108 if (StyleSVGReset()->HasFilters()) { |
|
109 InvalidateFrame(); |
|
110 } |
|
111 |
|
112 nsSVGInnerSVGFrameBase::ReflowSVG(); |
|
113 } |
|
114 |
|
115 void |
|
116 nsSVGInnerSVGFrame::NotifySVGChanged(uint32_t aFlags) |
|
117 { |
|
118 NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), |
|
119 "Invalidation logic may need adjusting"); |
|
120 |
|
121 if (aFlags & COORD_CONTEXT_CHANGED) { |
|
122 |
|
123 SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent); |
|
124 |
|
125 bool xOrYIsPercentage = |
|
126 svg->mLengthAttributes[SVGSVGElement::ATTR_X].IsPercentage() || |
|
127 svg->mLengthAttributes[SVGSVGElement::ATTR_Y].IsPercentage(); |
|
128 bool widthOrHeightIsPercentage = |
|
129 svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH].IsPercentage() || |
|
130 svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT].IsPercentage(); |
|
131 |
|
132 if (xOrYIsPercentage || widthOrHeightIsPercentage) { |
|
133 // Ancestor changes can't affect how we render from the perspective of |
|
134 // any rendering observers that we may have, so we don't need to |
|
135 // invalidate them. We also don't need to invalidate ourself, since our |
|
136 // changed ancestor will have invalidated its entire area, which includes |
|
137 // our area. |
|
138 // For perf reasons we call this before calling NotifySVGChanged() below. |
|
139 nsSVGUtils::ScheduleReflowSVG(this); |
|
140 } |
|
141 |
|
142 // Coordinate context changes affect mCanvasTM if we have a |
|
143 // percentage 'x' or 'y', or if we have a percentage 'width' or 'height' AND |
|
144 // a 'viewBox'. |
|
145 |
|
146 if (!(aFlags & TRANSFORM_CHANGED) && |
|
147 (xOrYIsPercentage || |
|
148 (widthOrHeightIsPercentage && svg->HasViewBoxRect()))) { |
|
149 aFlags |= TRANSFORM_CHANGED; |
|
150 } |
|
151 |
|
152 if (svg->HasViewBoxRect() || !widthOrHeightIsPercentage) { |
|
153 // Remove COORD_CONTEXT_CHANGED, since we establish the coordinate |
|
154 // context for our descendants and this notification won't change its |
|
155 // dimensions: |
|
156 aFlags &= ~COORD_CONTEXT_CHANGED; |
|
157 |
|
158 if (!aFlags) { |
|
159 return; // No notification flags left |
|
160 } |
|
161 } |
|
162 } |
|
163 |
|
164 if (aFlags & TRANSFORM_CHANGED) { |
|
165 // make sure our cached transform matrix gets (lazily) updated |
|
166 mCanvasTM = nullptr; |
|
167 } |
|
168 |
|
169 nsSVGInnerSVGFrameBase::NotifySVGChanged(aFlags); |
|
170 } |
|
171 |
|
172 nsresult |
|
173 nsSVGInnerSVGFrame::AttributeChanged(int32_t aNameSpaceID, |
|
174 nsIAtom* aAttribute, |
|
175 int32_t aModType) |
|
176 { |
|
177 if (aNameSpaceID == kNameSpaceID_None && |
|
178 !(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { |
|
179 |
|
180 SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent); |
|
181 |
|
182 if (aAttribute == nsGkAtoms::width || |
|
183 aAttribute == nsGkAtoms::height) { |
|
184 nsSVGEffects::InvalidateRenderingObservers(this); |
|
185 nsSVGUtils::ScheduleReflowSVG(this); |
|
186 |
|
187 if (content->HasViewBoxOrSyntheticViewBox()) { |
|
188 // make sure our cached transform matrix gets (lazily) updated |
|
189 mCanvasTM = nullptr; |
|
190 content->ChildrenOnlyTransformChanged(); |
|
191 nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED); |
|
192 } else { |
|
193 uint32_t flags = COORD_CONTEXT_CHANGED; |
|
194 if (mCanvasTM && mCanvasTM->IsSingular()) { |
|
195 mCanvasTM = nullptr; |
|
196 flags |= TRANSFORM_CHANGED; |
|
197 } |
|
198 nsSVGUtils::NotifyChildrenOfSVGChange(this, flags); |
|
199 } |
|
200 |
|
201 } else if (aAttribute == nsGkAtoms::transform || |
|
202 aAttribute == nsGkAtoms::preserveAspectRatio || |
|
203 aAttribute == nsGkAtoms::viewBox || |
|
204 aAttribute == nsGkAtoms::x || |
|
205 aAttribute == nsGkAtoms::y) { |
|
206 // make sure our cached transform matrix gets (lazily) updated |
|
207 mCanvasTM = nullptr; |
|
208 |
|
209 nsSVGUtils::NotifyChildrenOfSVGChange( |
|
210 this, aAttribute == nsGkAtoms::viewBox ? |
|
211 TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED); |
|
212 |
|
213 // We don't invalidate for transform changes (the layers code does that). |
|
214 // Also note that SVGTransformableElement::GetAttributeChangeHint will |
|
215 // return nsChangeHint_UpdateOverflow for "transform" attribute changes |
|
216 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. |
|
217 |
|
218 if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) { |
|
219 nsSVGEffects::InvalidateRenderingObservers(this); |
|
220 nsSVGUtils::ScheduleReflowSVG(this); |
|
221 } else if (aAttribute == nsGkAtoms::viewBox || |
|
222 (aAttribute == nsGkAtoms::preserveAspectRatio && |
|
223 content->HasViewBoxOrSyntheticViewBox())) { |
|
224 content->ChildrenOnlyTransformChanged(); |
|
225 // SchedulePaint sets a global state flag so we only need to call it once |
|
226 // (on ourself is fine), not once on each child (despite bug 828240). |
|
227 SchedulePaint(); |
|
228 } |
|
229 } |
|
230 } |
|
231 |
|
232 return NS_OK; |
|
233 } |
|
234 |
|
235 nsIFrame* |
|
236 nsSVGInnerSVGFrame::GetFrameForPoint(const nsPoint &aPoint) |
|
237 { |
|
238 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || |
|
239 (mState & NS_FRAME_IS_NONDISPLAY), |
|
240 "If display lists are enabled, only hit-testing of non-display " |
|
241 "SVG should take this code path"); |
|
242 |
|
243 if (StyleDisplay()->IsScrollableOverflow()) { |
|
244 nsSVGElement *content = static_cast<nsSVGElement*>(mContent); |
|
245 nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent); |
|
246 |
|
247 float clipX, clipY, clipWidth, clipHeight; |
|
248 content->GetAnimatedLengthValues(&clipX, &clipY, &clipWidth, &clipHeight, nullptr); |
|
249 |
|
250 if (!nsSVGUtils::HitTestRect(gfx::ToMatrix(parent->GetCanvasTM(FOR_HIT_TESTING)), |
|
251 clipX, clipY, clipWidth, clipHeight, |
|
252 PresContext()->AppUnitsToDevPixels(aPoint.x), |
|
253 PresContext()->AppUnitsToDevPixels(aPoint.y))) { |
|
254 return nullptr; |
|
255 } |
|
256 } |
|
257 |
|
258 return nsSVGInnerSVGFrameBase::GetFrameForPoint(aPoint); |
|
259 } |
|
260 |
|
261 //---------------------------------------------------------------------- |
|
262 // nsISVGSVGFrame methods: |
|
263 |
|
264 void |
|
265 nsSVGInnerSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) |
|
266 { |
|
267 // The dimensions of inner-<svg> frames are purely defined by their "width" |
|
268 // and "height" attributes, and transform changes can only occur as a result |
|
269 // of changes to their "width", "height", "viewBox" or "preserveAspectRatio" |
|
270 // attributes. Changes to all of these attributes are handled in |
|
271 // AttributeChanged(), so we should never be called. |
|
272 NS_ERROR("Not called for nsSVGInnerSVGFrame"); |
|
273 } |
|
274 |
|
275 //---------------------------------------------------------------------- |
|
276 // nsSVGContainerFrame methods: |
|
277 |
|
278 gfxMatrix |
|
279 nsSVGInnerSVGFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) |
|
280 { |
|
281 if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) { |
|
282 if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) || |
|
283 (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) { |
|
284 return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this); |
|
285 } |
|
286 } |
|
287 if (!mCanvasTM) { |
|
288 NS_ASSERTION(mParent, "null parent"); |
|
289 |
|
290 nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent); |
|
291 SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); |
|
292 |
|
293 gfxMatrix tm = content->PrependLocalTransformsTo( |
|
294 this == aTransformRoot ? gfxMatrix() : |
|
295 parent->GetCanvasTM(aFor, aTransformRoot)); |
|
296 |
|
297 mCanvasTM = new gfxMatrix(tm); |
|
298 } |
|
299 return *mCanvasTM; |
|
300 } |
|
301 |
|
302 bool |
|
303 nsSVGInnerSVGFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const |
|
304 { |
|
305 SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); |
|
306 |
|
307 if (content->HasViewBoxOrSyntheticViewBox()) { |
|
308 // XXX Maybe return false if the transform is the identity transform? |
|
309 if (aTransform) { |
|
310 *aTransform = content->GetViewBoxTransform(); |
|
311 } |
|
312 return true; |
|
313 } |
|
314 return false; |
|
315 } |