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