|
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 "nsSVGContainerFrame.h" |
|
8 |
|
9 // Keep others in (case-insensitive) order: |
|
10 #include "nsCSSFrameConstructor.h" |
|
11 #include "nsSVGEffects.h" |
|
12 #include "nsSVGElement.h" |
|
13 #include "nsSVGUtils.h" |
|
14 #include "nsSVGAnimatedTransformList.h" |
|
15 #include "SVGTextFrame.h" |
|
16 #include "RestyleManager.h" |
|
17 |
|
18 using namespace mozilla; |
|
19 |
|
20 NS_QUERYFRAME_HEAD(nsSVGContainerFrame) |
|
21 NS_QUERYFRAME_ENTRY(nsSVGContainerFrame) |
|
22 NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrameBase) |
|
23 |
|
24 NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame) |
|
25 NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame) |
|
26 NS_QUERYFRAME_ENTRY(nsISVGChildFrame) |
|
27 NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame) |
|
28 |
|
29 nsIFrame* |
|
30 NS_NewSVGContainerFrame(nsIPresShell* aPresShell, |
|
31 nsStyleContext* aContext) |
|
32 { |
|
33 nsIFrame *frame = new (aPresShell) nsSVGContainerFrame(aContext); |
|
34 // If we were called directly, then the frame is for a <defs> or |
|
35 // an unknown element type. In both cases we prevent the content |
|
36 // from displaying directly. |
|
37 frame->AddStateBits(NS_FRAME_IS_NONDISPLAY); |
|
38 return frame; |
|
39 } |
|
40 |
|
41 NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame) |
|
42 NS_IMPL_FRAMEARENA_HELPERS(nsSVGDisplayContainerFrame) |
|
43 |
|
44 nsresult |
|
45 nsSVGContainerFrame::AppendFrames(ChildListID aListID, |
|
46 nsFrameList& aFrameList) |
|
47 { |
|
48 return InsertFrames(aListID, mFrames.LastChild(), aFrameList); |
|
49 } |
|
50 |
|
51 nsresult |
|
52 nsSVGContainerFrame::InsertFrames(ChildListID aListID, |
|
53 nsIFrame* aPrevFrame, |
|
54 nsFrameList& aFrameList) |
|
55 { |
|
56 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); |
|
57 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, |
|
58 "inserting after sibling frame with different parent"); |
|
59 |
|
60 mFrames.InsertFrames(this, aPrevFrame, aFrameList); |
|
61 |
|
62 return NS_OK; |
|
63 } |
|
64 |
|
65 nsresult |
|
66 nsSVGContainerFrame::RemoveFrame(ChildListID aListID, |
|
67 nsIFrame* aOldFrame) |
|
68 { |
|
69 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); |
|
70 |
|
71 mFrames.DestroyFrame(aOldFrame); |
|
72 return NS_OK; |
|
73 } |
|
74 |
|
75 bool |
|
76 nsSVGContainerFrame::UpdateOverflow() |
|
77 { |
|
78 if (mState & NS_FRAME_IS_NONDISPLAY) { |
|
79 // We don't maintain overflow rects. |
|
80 // XXX It would have be better if the restyle request hadn't even happened. |
|
81 return false; |
|
82 } |
|
83 return nsSVGContainerFrameBase::UpdateOverflow(); |
|
84 } |
|
85 |
|
86 /** |
|
87 * Traverses a frame tree, marking any SVGTextFrame frames as dirty |
|
88 * and calling InvalidateRenderingObservers() on it. |
|
89 * |
|
90 * The reason that this helper exists is because SVGTextFrame is special. |
|
91 * None of the other SVG frames ever need to be reflowed when they have the |
|
92 * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods |
|
93 * (and those of any containers that they can validly be contained within) do |
|
94 * not make use of mRect or overflow rects. "em" lengths, etc., are resolved |
|
95 * as those elements are painted. |
|
96 * |
|
97 * SVGTextFrame is different because its anonymous block and inline frames |
|
98 * need to be reflowed in order to get the correct metrics when things like |
|
99 * inherited font-size of an ancestor changes, or a delayed webfont loads and |
|
100 * applies. |
|
101 * |
|
102 * We assume that any change that requires the anonymous kid of an |
|
103 * SVGTextFrame to reflow will result in an NS_FRAME_IS_DIRTY reflow. When |
|
104 * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally |
|
105 * stop, but this helper looks for any SVGTextFrame descendants of such |
|
106 * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they are |
|
107 * painted their anonymous kid will first get the necessary reflow. |
|
108 */ |
|
109 /* static */ void |
|
110 nsSVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) |
|
111 { |
|
112 NS_ASSERTION(aContainer->GetStateBits() & NS_FRAME_IS_DIRTY, |
|
113 "expected aContainer to be NS_FRAME_IS_DIRTY"); |
|
114 NS_ASSERTION((aContainer->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || |
|
115 !aContainer->IsFrameOfType(nsIFrame::eSVG), |
|
116 "it is wasteful to call ReflowSVGNonDisplayText on a container " |
|
117 "frame that is not NS_FRAME_IS_NONDISPLAY"); |
|
118 for (nsIFrame* kid = aContainer->GetFirstPrincipalChild(); kid; |
|
119 kid = kid->GetNextSibling()) { |
|
120 nsIAtom* type = kid->GetType(); |
|
121 if (type == nsGkAtoms::svgTextFrame) { |
|
122 static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText(); |
|
123 } else { |
|
124 if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) || |
|
125 type == nsGkAtoms::svgForeignObjectFrame || |
|
126 !kid->IsFrameOfType(nsIFrame::eSVG)) { |
|
127 ReflowSVGNonDisplayText(kid); |
|
128 } |
|
129 } |
|
130 } |
|
131 } |
|
132 |
|
133 void |
|
134 nsSVGDisplayContainerFrame::Init(nsIContent* aContent, |
|
135 nsIFrame* aParent, |
|
136 nsIFrame* aPrevInFlow) |
|
137 { |
|
138 if (!(GetStateBits() & NS_STATE_IS_OUTER_SVG)) { |
|
139 AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); |
|
140 } |
|
141 nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); |
|
142 } |
|
143 |
|
144 void |
|
145 nsSVGDisplayContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
|
146 const nsRect& aDirtyRect, |
|
147 const nsDisplayListSet& aLists) |
|
148 { |
|
149 // mContent could be a XUL element so check for an SVG element before casting |
|
150 if (mContent->IsSVG() && |
|
151 !static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) { |
|
152 return; |
|
153 } |
|
154 return BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists); |
|
155 } |
|
156 |
|
157 nsresult |
|
158 nsSVGDisplayContainerFrame::InsertFrames(ChildListID aListID, |
|
159 nsIFrame* aPrevFrame, |
|
160 nsFrameList& aFrameList) |
|
161 { |
|
162 // memorize first old frame after insertion point |
|
163 // XXXbz once again, this would work a lot better if the nsIFrame |
|
164 // methods returned framelist iterators.... |
|
165 nsIFrame* nextFrame = aPrevFrame ? |
|
166 aPrevFrame->GetNextSibling() : GetChildList(aListID).FirstChild(); |
|
167 nsIFrame* firstNewFrame = aFrameList.FirstChild(); |
|
168 |
|
169 // Insert the new frames |
|
170 nsSVGContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); |
|
171 |
|
172 // If we are not a non-display SVG frame and we do not have a bounds update |
|
173 // pending, then we need to schedule one for our new children: |
|
174 if (!(GetStateBits() & |
|
175 (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN | |
|
176 NS_FRAME_IS_NONDISPLAY))) { |
|
177 for (nsIFrame* kid = firstNewFrame; kid != nextFrame; |
|
178 kid = kid->GetNextSibling()) { |
|
179 nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); |
|
180 if (SVGFrame) { |
|
181 NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), |
|
182 "Check for this explicitly in the |if|, then"); |
|
183 bool isFirstReflow = (kid->GetStateBits() & NS_FRAME_FIRST_REFLOW); |
|
184 // Remove bits so that ScheduleBoundsUpdate will work: |
|
185 kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | |
|
186 NS_FRAME_HAS_DIRTY_CHILDREN); |
|
187 // No need to invalidate the new kid's old bounds, so we just use |
|
188 // nsSVGUtils::ScheduleBoundsUpdate. |
|
189 nsSVGUtils::ScheduleReflowSVG(kid); |
|
190 if (isFirstReflow) { |
|
191 // Add back the NS_FRAME_FIRST_REFLOW bit: |
|
192 kid->AddStateBits(NS_FRAME_FIRST_REFLOW); |
|
193 } |
|
194 } |
|
195 } |
|
196 } |
|
197 |
|
198 return NS_OK; |
|
199 } |
|
200 |
|
201 nsresult |
|
202 nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID, |
|
203 nsIFrame* aOldFrame) |
|
204 { |
|
205 nsSVGEffects::InvalidateRenderingObservers(aOldFrame); |
|
206 |
|
207 // nsSVGContainerFrame::RemoveFrame doesn't call down into |
|
208 // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We |
|
209 // need to schedule a repaint and schedule an update to our overflow rects. |
|
210 SchedulePaint(); |
|
211 PresContext()->RestyleManager()->PostRestyleEvent( |
|
212 mContent->AsElement(), nsRestyleHint(0), nsChangeHint_UpdateOverflow); |
|
213 |
|
214 nsresult rv = nsSVGContainerFrame::RemoveFrame(aListID, aOldFrame); |
|
215 |
|
216 if (!(GetStateBits() & (NS_FRAME_IS_NONDISPLAY | NS_STATE_IS_OUTER_SVG))) { |
|
217 nsSVGUtils::NotifyAncestorsOfFilterRegionChange(this); |
|
218 } |
|
219 |
|
220 return rv; |
|
221 } |
|
222 |
|
223 bool |
|
224 nsSVGDisplayContainerFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, |
|
225 gfx::Matrix *aFromParentTransform) const |
|
226 { |
|
227 bool foundTransform = false; |
|
228 |
|
229 // Check if our parent has children-only transforms: |
|
230 nsIFrame *parent = GetParent(); |
|
231 if (parent && |
|
232 parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { |
|
233 foundTransform = static_cast<nsSVGContainerFrame*>(parent)-> |
|
234 HasChildrenOnlyTransform(aFromParentTransform); |
|
235 } |
|
236 |
|
237 // mContent could be a XUL element so check for an SVG element before casting |
|
238 if (mContent->IsSVG()) { |
|
239 nsSVGElement *content = static_cast<nsSVGElement*>(mContent); |
|
240 nsSVGAnimatedTransformList* transformList = |
|
241 content->GetAnimatedTransformList(); |
|
242 if ((transformList && transformList->HasTransform()) || |
|
243 content->GetAnimateMotionTransform()) { |
|
244 if (aOwnTransform) { |
|
245 *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), |
|
246 nsSVGElement::eUserSpaceToParent)); |
|
247 } |
|
248 foundTransform = true; |
|
249 } |
|
250 } |
|
251 return foundTransform; |
|
252 } |
|
253 |
|
254 //---------------------------------------------------------------------- |
|
255 // nsISVGChildFrame methods |
|
256 |
|
257 nsresult |
|
258 nsSVGDisplayContainerFrame::PaintSVG(nsRenderingContext* aContext, |
|
259 const nsIntRect *aDirtyRect, |
|
260 nsIFrame* aTransformRoot) |
|
261 { |
|
262 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || |
|
263 (mState & NS_FRAME_IS_NONDISPLAY) || |
|
264 PresContext()->IsGlyph(), |
|
265 "If display lists are enabled, only painting of non-display " |
|
266 "SVG should take this code path"); |
|
267 |
|
268 const nsStyleDisplay *display = StyleDisplay(); |
|
269 if (display->mOpacity == 0.0) |
|
270 return NS_OK; |
|
271 |
|
272 for (nsIFrame* kid = mFrames.FirstChild(); kid; |
|
273 kid = kid->GetNextSibling()) { |
|
274 nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid, aTransformRoot); |
|
275 } |
|
276 |
|
277 return NS_OK; |
|
278 } |
|
279 |
|
280 nsIFrame* |
|
281 nsSVGDisplayContainerFrame::GetFrameForPoint(const nsPoint &aPoint) |
|
282 { |
|
283 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || |
|
284 (mState & NS_FRAME_IS_NONDISPLAY), |
|
285 "If display lists are enabled, only hit-testing of a " |
|
286 "clipPath's contents should take this code path"); |
|
287 return nsSVGUtils::HitTestChildren(this, aPoint); |
|
288 } |
|
289 |
|
290 nsRect |
|
291 nsSVGDisplayContainerFrame::GetCoveredRegion() |
|
292 { |
|
293 return nsSVGUtils::GetCoveredRegion(mFrames); |
|
294 } |
|
295 |
|
296 void |
|
297 nsSVGDisplayContainerFrame::ReflowSVG() |
|
298 { |
|
299 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), |
|
300 "This call is probably a wasteful mistake"); |
|
301 |
|
302 NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), |
|
303 "ReflowSVG mechanism not designed for this"); |
|
304 |
|
305 NS_ABORT_IF_FALSE(GetType() != nsGkAtoms::svgOuterSVGFrame, |
|
306 "Do not call on outer-<svg>"); |
|
307 |
|
308 if (!nsSVGUtils::NeedsReflowSVG(this)) { |
|
309 return; |
|
310 } |
|
311 |
|
312 // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, |
|
313 // then our outer-<svg> has previously had its initial reflow. In that case |
|
314 // we need to make sure that that bit has been removed from ourself _before_ |
|
315 // recursing over our children to ensure that they know too. Otherwise, we |
|
316 // need to remove it _after_ recursing over our children so that they know |
|
317 // the initial reflow is currently underway. |
|
318 |
|
319 bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW); |
|
320 |
|
321 bool outerSVGHasHadFirstReflow = |
|
322 (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0; |
|
323 |
|
324 if (outerSVGHasHadFirstReflow) { |
|
325 mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children |
|
326 } |
|
327 |
|
328 nsOverflowAreas overflowRects; |
|
329 |
|
330 for (nsIFrame* kid = mFrames.FirstChild(); kid; |
|
331 kid = kid->GetNextSibling()) { |
|
332 nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); |
|
333 if (SVGFrame) { |
|
334 NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), |
|
335 "Check for this explicitly in the |if|, then"); |
|
336 kid->AddStateBits(mState & NS_FRAME_IS_DIRTY); |
|
337 SVGFrame->ReflowSVG(); |
|
338 |
|
339 // We build up our child frame overflows here instead of using |
|
340 // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same |
|
341 // frame list, and we're iterating over that list now anyway. |
|
342 ConsiderChildOverflow(overflowRects, kid); |
|
343 } else { |
|
344 // Inside a non-display container frame, we might have some |
|
345 // SVGTextFrames. We need to cause those to get reflowed in |
|
346 // case they are the target of a rendering observer. |
|
347 NS_ASSERTION(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY, |
|
348 "expected kid to be a NS_FRAME_IS_NONDISPLAY frame"); |
|
349 if (kid->GetStateBits() & NS_FRAME_IS_DIRTY) { |
|
350 nsSVGContainerFrame* container = do_QueryFrame(kid); |
|
351 if (container && container->GetContent()->IsSVG()) { |
|
352 ReflowSVGNonDisplayText(container); |
|
353 } |
|
354 } |
|
355 } |
|
356 } |
|
357 |
|
358 // <svg> can create an SVG viewport with an offset due to its |
|
359 // x/y/width/height attributes, and <use> can introduce an offset with an |
|
360 // empty mRect (any width/height is copied to an anonymous <svg> child). |
|
361 // Other than that containers should not set mRect since all other offsets |
|
362 // come from transforms, which are accounted for by nsDisplayTransform. |
|
363 // Note that we rely on |overflow:visible| to allow display list items to be |
|
364 // created for our children. |
|
365 NS_ABORT_IF_FALSE(mContent->Tag() == nsGkAtoms::svg || |
|
366 (mContent->Tag() == nsGkAtoms::use && |
|
367 mRect.Size() == nsSize(0,0)) || |
|
368 mRect.IsEqualEdges(nsRect()), |
|
369 "Only inner-<svg>/<use> is expected to have mRect set"); |
|
370 |
|
371 if (isFirstReflow) { |
|
372 // Make sure we have our filter property (if any) before calling |
|
373 // FinishAndStoreOverflow (subsequent filter changes are handled off |
|
374 // nsChangeHint_UpdateEffects): |
|
375 nsSVGEffects::UpdateEffects(this); |
|
376 } |
|
377 |
|
378 FinishAndStoreOverflow(overflowRects, mRect.Size()); |
|
379 |
|
380 // Remove state bits after FinishAndStoreOverflow so that it doesn't |
|
381 // invalidate on first reflow: |
|
382 mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | |
|
383 NS_FRAME_HAS_DIRTY_CHILDREN); |
|
384 } |
|
385 |
|
386 void |
|
387 nsSVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags) |
|
388 { |
|
389 NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), |
|
390 "Invalidation logic may need adjusting"); |
|
391 |
|
392 nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags); |
|
393 } |
|
394 |
|
395 SVGBBox |
|
396 nsSVGDisplayContainerFrame::GetBBoxContribution( |
|
397 const Matrix &aToBBoxUserspace, |
|
398 uint32_t aFlags) |
|
399 { |
|
400 SVGBBox bboxUnion; |
|
401 |
|
402 nsIFrame* kid = mFrames.FirstChild(); |
|
403 while (kid) { |
|
404 nsIContent *content = kid->GetContent(); |
|
405 nsISVGChildFrame* svgKid = do_QueryFrame(kid); |
|
406 // content could be a XUL element so check for an SVG element before casting |
|
407 if (svgKid && (!content->IsSVG() || |
|
408 static_cast<const nsSVGElement*>(content)->HasValidDimensions())) { |
|
409 |
|
410 gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace); |
|
411 if (content->IsSVG()) { |
|
412 transform = static_cast<nsSVGElement*>(content)-> |
|
413 PrependLocalTransformsTo(transform); |
|
414 } |
|
415 // We need to include zero width/height vertical/horizontal lines, so we have |
|
416 // to use UnionEdges. |
|
417 bboxUnion.UnionEdges(svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags)); |
|
418 } |
|
419 kid = kid->GetNextSibling(); |
|
420 } |
|
421 |
|
422 return bboxUnion; |
|
423 } |