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/. */
7 #include "nsSVGForeignObjectFrame.h"
9 // Keep others in (case-insensitive) order:
10 #include "gfxContext.h"
11 #include "nsGkAtoms.h"
12 #include "nsNameSpaceManager.h"
13 #include "nsLayoutUtils.h"
15 #include "nsRenderingContext.h"
16 #include "nsSVGContainerFrame.h"
17 #include "nsSVGEffects.h"
18 #include "mozilla/dom/SVGForeignObjectElement.h"
19 #include "nsSVGIntegrationUtils.h"
20 #include "nsSVGOuterSVGFrame.h"
21 #include "nsSVGUtils.h"
22 #include "mozilla/AutoRestore.h"
24 using namespace mozilla
;
25 using namespace mozilla::dom
;
27 //----------------------------------------------------------------------
31 NS_NewSVGForeignObjectFrame(nsIPresShell
*aPresShell
,
32 nsStyleContext
*aContext
)
34 return new (aPresShell
) nsSVGForeignObjectFrame(aContext
);
37 NS_IMPL_FRAMEARENA_HELPERS(nsSVGForeignObjectFrame
)
39 nsSVGForeignObjectFrame::nsSVGForeignObjectFrame(nsStyleContext
* aContext
)
40 : nsSVGForeignObjectFrameBase(aContext
),
43 AddStateBits(NS_FRAME_REFLOW_ROOT
| NS_FRAME_MAY_BE_TRANSFORMED
|
47 //----------------------------------------------------------------------
50 NS_QUERYFRAME_HEAD(nsSVGForeignObjectFrame
)
51 NS_QUERYFRAME_ENTRY(nsISVGChildFrame
)
52 NS_QUERYFRAME_TAIL_INHERITING(nsSVGForeignObjectFrameBase
)
55 nsSVGForeignObjectFrame::Init(nsIContent
* aContent
,
56 nsContainerFrame
* aParent
,
57 nsIFrame
* aPrevInFlow
)
59 NS_ASSERTION(aContent
->IsSVG(nsGkAtoms::foreignObject
),
60 "Content is not an SVG foreignObject!");
62 nsSVGForeignObjectFrameBase::Init(aContent
, aParent
, aPrevInFlow
);
63 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
64 AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER
|
65 NS_FRAME_FONT_INFLATION_FLOW_ROOT
);
66 if (!(mState
& NS_FRAME_IS_NONDISPLAY
)) {
67 nsSVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this);
71 void nsSVGForeignObjectFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
73 // Only unregister if we registered in the first place:
74 if (!(mState
& NS_FRAME_IS_NONDISPLAY
)) {
75 nsSVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this);
77 nsSVGForeignObjectFrameBase::DestroyFrom(aDestructRoot
);
81 nsSVGForeignObjectFrame::GetType() const
83 return nsGkAtoms::svgForeignObjectFrame
;
87 nsSVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID
,
91 if (aNameSpaceID
== kNameSpaceID_None
) {
92 if (aAttribute
== nsGkAtoms::width
||
93 aAttribute
== nsGkAtoms::height
) {
94 nsSVGEffects::InvalidateRenderingObservers(this);
95 nsSVGUtils::ScheduleReflowSVG(this);
96 // XXXjwatt: why mark intrinsic widths dirty? can't we just use eResize?
97 RequestReflow(nsIPresShell::eStyleChange
);
98 } else if (aAttribute
== nsGkAtoms::x
||
99 aAttribute
== nsGkAtoms::y
) {
100 // make sure our cached transform matrix gets (lazily) updated
102 nsSVGEffects::InvalidateRenderingObservers(this);
103 nsSVGUtils::ScheduleReflowSVG(this);
104 } else if (aAttribute
== nsGkAtoms::transform
) {
105 // We don't invalidate for transform changes (the layers code does that).
106 // Also note that SVGTransformableElement::GetAttributeChangeHint will
107 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
108 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
110 } else if (aAttribute
== nsGkAtoms::viewBox
||
111 aAttribute
== nsGkAtoms::preserveAspectRatio
) {
112 nsSVGEffects::InvalidateRenderingObservers(this);
120 nsSVGForeignObjectFrame::Reflow(nsPresContext
* aPresContext
,
121 nsHTMLReflowMetrics
& aDesiredSize
,
122 const nsHTMLReflowState
& aReflowState
,
123 nsReflowStatus
& aStatus
)
125 NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
126 "Should not have been called");
128 // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
129 // so if that bit is still set we still have a resize pending. If we hit
130 // this assertion, then we should get the presShell to skip reflow roots
131 // that have a dirty parent since a reflow is going to come via the
132 // reflow root's parent anyway.
133 NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_DIRTY
),
134 "Reflowing while a resize is pending is wasteful");
136 // ReflowSVG makes sure mRect is up to date before we're called.
138 NS_ASSERTION(!aReflowState
.parentReflowState
,
139 "should only get reflow from being reflow root");
140 NS_ASSERTION(aReflowState
.ComputedWidth() == GetSize().width
&&
141 aReflowState
.ComputedHeight() == GetSize().height
,
142 "reflow roots should be reflowed at existing size and "
143 "svg.css should ensure we have no padding/border/margin");
147 WritingMode wm
= aReflowState
.GetWritingMode();
148 LogicalSize
finalSize(wm
, aReflowState
.ComputedISize(),
149 aReflowState
.ComputedBSize());
150 aDesiredSize
.SetSize(wm
, finalSize
);
151 aDesiredSize
.SetOverflowAreasToDesiredBounds();
152 aStatus
= NS_FRAME_COMPLETE
;
156 nsSVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
157 const nsRect
& aDirtyRect
,
158 const nsDisplayListSet
& aLists
)
160 if (!static_cast<const nsSVGElement
*>(mContent
)->HasValidDimensions()) {
163 BuildDisplayListForNonBlockChildren(aBuilder
, aDirtyRect
, aLists
);
167 nsSVGForeignObjectFrame::IsSVGTransformed(Matrix
*aOwnTransform
,
168 Matrix
*aFromParentTransform
) const
170 bool foundTransform
= false;
172 // Check if our parent has children-only transforms:
173 nsIFrame
*parent
= GetParent();
175 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
176 foundTransform
= static_cast<nsSVGContainerFrame
*>(parent
)->
177 HasChildrenOnlyTransform(aFromParentTransform
);
180 nsSVGElement
*content
= static_cast<nsSVGElement
*>(mContent
);
181 nsSVGAnimatedTransformList
* transformList
=
182 content
->GetAnimatedTransformList();
183 if ((transformList
&& transformList
->HasTransform()) ||
184 content
->GetAnimateMotionTransform()) {
186 *aOwnTransform
= gfx::ToMatrix(content
->PrependLocalTransformsTo(gfxMatrix(),
187 nsSVGElement::eUserSpaceToParent
));
189 foundTransform
= true;
191 return foundTransform
;
195 nsSVGForeignObjectFrame::PaintSVG(nsRenderingContext
*aContext
,
196 const nsIntRect
*aDirtyRect
,
197 nsIFrame
* aTransformRoot
)
199 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
200 (mState
& NS_FRAME_IS_NONDISPLAY
),
201 "If display lists are enabled, only painting of non-display "
202 "SVG should take this code path");
207 nsIFrame
* kid
= GetFirstPrincipalChild();
211 gfxMatrix canvasTM
= GetCanvasTM(FOR_PAINTING
, aTransformRoot
);
213 if (canvasTM
.IsSingular()) {
214 NS_WARNING("Can't render foreignObject element!");
215 return NS_ERROR_FAILURE
;
218 nsRect kidDirtyRect
= kid
->GetVisualOverflowRect();
220 /* Check if we need to draw anything. */
222 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
223 (mState
& NS_FRAME_IS_NONDISPLAY
),
224 "Display lists handle dirty rect intersection test");
225 // Transform the dirty rect into app units in our userspace.
226 gfxMatrix invmatrix
= canvasTM
;
227 DebugOnly
<bool> ok
= invmatrix
.Invert();
228 NS_ASSERTION(ok
, "inverse of non-singular matrix should be non-singular");
230 gfxRect transDirtyRect
= gfxRect(aDirtyRect
->x
, aDirtyRect
->y
,
231 aDirtyRect
->width
, aDirtyRect
->height
);
232 transDirtyRect
= invmatrix
.TransformBounds(transDirtyRect
);
234 kidDirtyRect
.IntersectRect(kidDirtyRect
,
235 nsLayoutUtils::RoundGfxRectToAppRect(transDirtyRect
,
236 PresContext()->AppUnitsPerCSSPixel()));
238 // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect,
239 // not with kidDirtyRect. I.e.
240 // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
241 // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect)
242 if (kidDirtyRect
.IsEmpty())
246 gfxContext
*gfx
= aContext
->ThebesContext();
250 if (StyleDisplay()->IsScrollableOverflow()) {
251 float x
, y
, width
, height
;
252 static_cast<nsSVGElement
*>(mContent
)->
253 GetAnimatedLengthValues(&x
, &y
, &width
, &height
, nullptr);
256 nsSVGUtils::GetClipRectForFrame(this, 0.0f
, 0.0f
, width
, height
);
257 nsSVGUtils::SetClipRect(gfx
, canvasTM
, clipRect
);
260 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
261 // multiply a CSS-px-to-dev-pixel factor onto canvasTM so our children paint
263 float cssPxPerDevPx
= PresContext()->
264 AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel());
265 gfxMatrix canvasTMForChildren
= canvasTM
;
266 canvasTMForChildren
.Scale(cssPxPerDevPx
, cssPxPerDevPx
);
268 gfx
->Multiply(canvasTMForChildren
);
270 uint32_t flags
= nsLayoutUtils::PAINT_IN_TRANSFORM
;
271 if (SVGAutoRenderState::IsPaintingToWindow(aContext
)) {
272 flags
|= nsLayoutUtils::PAINT_TO_WINDOW
;
274 nsresult rv
= nsLayoutUtils::PaintFrame(aContext
, kid
, nsRegion(kidDirtyRect
),
275 NS_RGBA(0,0,0,0), flags
);
283 nsSVGForeignObjectFrame::GetFrameForPoint(const gfxPoint
& aPoint
)
285 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
286 (mState
& NS_FRAME_IS_NONDISPLAY
),
287 "If display lists are enabled, only hit-testing of a "
288 "clipPath's contents should take this code path");
290 if (IsDisabled() || (GetStateBits() & NS_FRAME_IS_NONDISPLAY
))
293 nsIFrame
* kid
= GetFirstPrincipalChild();
297 float x
, y
, width
, height
;
298 static_cast<nsSVGElement
*>(mContent
)->
299 GetAnimatedLengthValues(&x
, &y
, &width
, &height
, nullptr);
301 if (!gfxRect(x
, y
, width
, height
).Contains(aPoint
) ||
302 !nsSVGUtils::HitTestClip(this, aPoint
)) {
306 // Convert the point to app units relative to the top-left corner of the
307 // viewport that's established by the foreignObject element:
309 gfxPoint pt
= (aPoint
+ gfxPoint(x
, y
)) * nsPresContext::AppUnitsPerCSSPixel();
310 nsPoint point
= nsPoint(NSToIntRound(pt
.x
), NSToIntRound(pt
.y
));
312 return nsLayoutUtils::GetFrameForPoint(kid
, point
);
316 nsSVGForeignObjectFrame::GetCoveredRegion()
319 static_cast<SVGForeignObjectElement
*>(mContent
)->
320 GetAnimatedLengthValues(&x
, &y
, &w
, &h
, nullptr);
321 if (w
< 0.0f
) w
= 0.0f
;
322 if (h
< 0.0f
) h
= 0.0f
;
323 // GetCanvasTM includes the x,y translation
324 return nsSVGUtils::ToCanvasBounds(gfxRect(0.0, 0.0, w
, h
),
325 GetCanvasTM(FOR_OUTERSVG_TM
),
330 nsSVGForeignObjectFrame::ReflowSVG()
332 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
333 "This call is probably a wasteful mistake");
335 NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
336 "ReflowSVG mechanism not designed for this");
338 if (!nsSVGUtils::NeedsReflowSVG(this)) {
342 // We update mRect before the DoReflow call so that DoReflow uses the
343 // correct dimensions:
346 static_cast<SVGForeignObjectElement
*>(mContent
)->
347 GetAnimatedLengthValues(&x
, &y
, &w
, &h
, nullptr);
349 // If mRect's width or height are negative, reflow blows up! We must clamp!
350 if (w
< 0.0f
) w
= 0.0f
;
351 if (h
< 0.0f
) h
= 0.0f
;
353 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(
355 PresContext()->AppUnitsPerCSSPixel());
357 // Fully mark our kid dirty so that it gets resized if necessary
358 // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
359 nsIFrame
* kid
= GetFirstPrincipalChild();
360 kid
->AddStateBits(NS_FRAME_IS_DIRTY
);
362 // Make sure to not allow interrupts if we're not being reflown as a root:
363 nsPresContext::InterruptPreventer
noInterrupts(PresContext());
367 if (mState
& NS_FRAME_FIRST_REFLOW
) {
368 // Make sure we have our filter property (if any) before calling
369 // FinishAndStoreOverflow (subsequent filter changes are handled off
370 // nsChangeHint_UpdateEffects):
371 nsSVGEffects::UpdateEffects(this);
374 // If we have a filter, we need to invalidate ourselves because filter
375 // output can change even if none of our descendants need repainting.
376 if (StyleSVGReset()->HasFilters()) {
380 // TODO: once we support |overflow:visible| on foreignObject, then we will
381 // need to take account of our descendants here.
382 nsRect overflow
= nsRect(nsPoint(0,0), mRect
.Size());
383 nsOverflowAreas
overflowAreas(overflow
, overflow
);
384 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
386 // Now unset the various reflow bits:
387 mState
&= ~(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
388 NS_FRAME_HAS_DIRTY_CHILDREN
);
392 nsSVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags
)
394 NS_ABORT_IF_FALSE(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
395 "Invalidation logic may need adjusting");
397 bool needNewBounds
= false; // i.e. mRect or visual overflow rect
398 bool needReflow
= false;
399 bool needNewCanvasTM
= false;
401 if (aFlags
& COORD_CONTEXT_CHANGED
) {
402 SVGForeignObjectElement
*fO
=
403 static_cast<SVGForeignObjectElement
*>(mContent
);
404 // Coordinate context changes affect mCanvasTM if we have a
405 // percentage 'x' or 'y'
406 if (fO
->mLengthAttributes
[SVGForeignObjectElement::ATTR_X
].IsPercentage() ||
407 fO
->mLengthAttributes
[SVGForeignObjectElement::ATTR_Y
].IsPercentage()) {
408 needNewBounds
= true;
409 needNewCanvasTM
= true;
411 // Our coordinate context's width/height has changed. If we have a
412 // percentage width/height our dimensions will change so we must reflow.
413 if (fO
->mLengthAttributes
[SVGForeignObjectElement::ATTR_WIDTH
].IsPercentage() ||
414 fO
->mLengthAttributes
[SVGForeignObjectElement::ATTR_HEIGHT
].IsPercentage()) {
415 needNewBounds
= true;
420 if (aFlags
& TRANSFORM_CHANGED
) {
421 if (mCanvasTM
&& mCanvasTM
->IsSingular()) {
422 needNewBounds
= true; // old bounds are bogus
424 needNewCanvasTM
= true;
425 // In an ideal world we would reflow when our CTM changes. This is because
426 // glyph metrics do not necessarily scale uniformly with change in scale
427 // and, as a result, CTM changes may require text to break at different
428 // points. The problem would be how to keep performance acceptable when
429 // e.g. the transform of an ancestor is animated.
430 // We also seem to get some sort of infinite loop post bug 421584 if we
435 // Ancestor changes can't affect how we render from the perspective of
436 // any rendering observers that we may have, so we don't need to
437 // invalidate them. We also don't need to invalidate ourself, since our
438 // changed ancestor will have invalidated its entire area, which includes
440 nsSVGUtils::ScheduleReflowSVG(this);
443 // If we're called while the PresShell is handling reflow events then we
444 // must have been called as a result of the NotifyViewportChange() call in
445 // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
446 // at this point (i.e. during reflow) because it could confuse the
447 // PresShell and prevent it from reflowing us properly in future. Besides
448 // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us
449 // synchronously, so there's no need.
450 if (needReflow
&& !PresContext()->PresShell()->IsReflowLocked()) {
451 RequestReflow(nsIPresShell::eResize
);
454 if (needNewCanvasTM
) {
455 // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
456 // change the code and it needs to use it.
462 nsSVGForeignObjectFrame::GetBBoxContribution(const Matrix
&aToBBoxUserspace
,
465 SVGForeignObjectElement
*content
=
466 static_cast<SVGForeignObjectElement
*>(mContent
);
469 content
->GetAnimatedLengthValues(&x
, &y
, &w
, &h
, nullptr);
471 if (w
< 0.0f
) w
= 0.0f
;
472 if (h
< 0.0f
) h
= 0.0f
;
474 if (aToBBoxUserspace
.IsSingular()) {
475 // XXX ReportToConsole
478 return aToBBoxUserspace
.TransformBounds(gfx::Rect(0.0, 0.0, w
, h
));
481 //----------------------------------------------------------------------
484 nsSVGForeignObjectFrame::GetCanvasTM(uint32_t aFor
, nsIFrame
* aTransformRoot
)
486 if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY
) && !aTransformRoot
) {
487 if (aFor
== FOR_PAINTING
&& NS_SVGDisplayListPaintingEnabled()) {
488 return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
492 NS_ASSERTION(GetParent(), "null parent");
494 nsSVGContainerFrame
*parent
= static_cast<nsSVGContainerFrame
*>(GetParent());
495 SVGForeignObjectElement
*content
=
496 static_cast<SVGForeignObjectElement
*>(mContent
);
498 gfxMatrix tm
= content
->PrependLocalTransformsTo(
499 this == aTransformRoot
? gfxMatrix() :
500 parent
->GetCanvasTM(aFor
, aTransformRoot
));
502 mCanvasTM
= new gfxMatrix(tm
);
507 //----------------------------------------------------------------------
508 // Implementation helpers
510 void nsSVGForeignObjectFrame::RequestReflow(nsIPresShell::IntrinsicDirty aType
)
512 if (GetStateBits() & NS_FRAME_FIRST_REFLOW
)
513 // If we haven't had a ReflowSVG() yet, nothing to do.
516 nsIFrame
* kid
= GetFirstPrincipalChild();
520 PresContext()->PresShell()->FrameNeedsReflow(kid
, aType
, NS_FRAME_IS_DIRTY
);
524 nsSVGForeignObjectFrame::DoReflow()
526 // Skip reflow if we're zero-sized, unless this is our first reflow.
528 !(GetStateBits() & NS_FRAME_FIRST_REFLOW
))
531 nsPresContext
*presContext
= PresContext();
532 nsIFrame
* kid
= GetFirstPrincipalChild();
536 // initiate a synchronous reflow here and now:
537 nsRefPtr
<nsRenderingContext
> renderingContext
=
538 presContext
->PresShell()->CreateReferenceRenderingContext();
542 WritingMode wm
= kid
->GetWritingMode();
543 nsHTMLReflowState
reflowState(presContext
, kid
,
545 LogicalSize(wm
, GetLogicalSize(wm
).ISize(wm
),
546 NS_UNCONSTRAINEDSIZE
));
547 nsHTMLReflowMetrics
desiredSize(reflowState
);
548 nsReflowStatus status
;
550 // We don't use mRect.height above because that tells the child to do
551 // page/column breaking at that height.
552 NS_ASSERTION(reflowState
.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
553 reflowState
.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
554 "style system should ensure that :-moz-svg-foreign-content "
555 "does not get styled");
556 NS_ASSERTION(reflowState
.ComputedWidth() == mRect
.width
,
557 "reflow state made child wrong size");
558 reflowState
.SetComputedHeight(mRect
.height
);
560 ReflowChild(kid
, presContext
, desiredSize
, reflowState
, 0, 0,
561 NS_FRAME_NO_MOVE_FRAME
, status
);
562 NS_ASSERTION(mRect
.width
== desiredSize
.Width() &&
563 mRect
.height
== desiredSize
.Height(), "unexpected size");
564 FinishReflowChild(kid
, presContext
, desiredSize
, &reflowState
, 0, 0,
565 NS_FRAME_NO_MOVE_FRAME
);
571 nsSVGForeignObjectFrame::GetInvalidRegion()
573 MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(),
574 "Only called by nsDisplayOuterSVG code");
576 nsIFrame
* kid
= GetFirstPrincipalChild();
577 if (kid
->HasInvalidFrameInSubtree()) {
578 gfxRect
r(mRect
.x
, mRect
.y
, mRect
.width
, mRect
.height
);
579 r
.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel());
580 nsRect rect
= nsSVGUtils::ToCanvasBounds(r
, GetCanvasTM(FOR_OUTERSVG_TM
), PresContext());
581 rect
= nsSVGUtils::GetPostFilterVisualOverflowRect(this, rect
);