1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsSVGForeignObjectFrame.h"
10 // Keep others in (case-insensitive) order:
11 #include "ImgDrawResult.h"
12 #include "gfxContext.h"
13 #include "mozilla/AutoRestore.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/dom/SVGForeignObjectElement.h"
16 #include "nsDisplayList.h"
17 #include "nsGkAtoms.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsLayoutUtils.h"
21 #include "nsSVGContainerFrame.h"
22 #include "SVGGeometryProperty.h"
23 #include "SVGObserverUtils.h"
24 #include "nsSVGIntegrationUtils.h"
25 #include "nsSVGOuterSVGFrame.h"
26 #include "nsSVGUtils.h"
28 using namespace mozilla
;
29 using namespace mozilla::dom
;
30 using namespace mozilla::image
;
31 namespace SVGT
= SVGGeometryProperty::Tags
;
33 //----------------------------------------------------------------------
36 nsContainerFrame
* NS_NewSVGForeignObjectFrame(PresShell
* aPresShell
,
37 ComputedStyle
* aStyle
) {
38 return new (aPresShell
)
39 nsSVGForeignObjectFrame(aStyle
, aPresShell
->GetPresContext());
42 NS_IMPL_FRAMEARENA_HELPERS(nsSVGForeignObjectFrame
)
44 nsSVGForeignObjectFrame::nsSVGForeignObjectFrame(ComputedStyle
* aStyle
,
45 nsPresContext
* aPresContext
)
46 : nsContainerFrame(aStyle
, aPresContext
, kClassID
), mInReflow(false) {
47 AddStateBits(NS_FRAME_REFLOW_ROOT
| NS_FRAME_MAY_BE_TRANSFORMED
|
51 //----------------------------------------------------------------------
54 NS_QUERYFRAME_HEAD(nsSVGForeignObjectFrame
)
55 NS_QUERYFRAME_ENTRY(nsSVGDisplayableFrame
)
56 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
58 void nsSVGForeignObjectFrame::Init(nsIContent
* aContent
,
59 nsContainerFrame
* aParent
,
60 nsIFrame
* aPrevInFlow
) {
61 NS_ASSERTION(aContent
->IsSVGElement(nsGkAtoms::foreignObject
),
62 "Content is not an SVG foreignObject!");
64 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
65 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
66 AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER
|
67 NS_FRAME_FONT_INFLATION_FLOW_ROOT
);
68 if (!(mState
& NS_FRAME_IS_NONDISPLAY
)) {
69 nsSVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this);
73 void nsSVGForeignObjectFrame::DestroyFrom(nsIFrame
* aDestructRoot
,
74 PostDestroyData
& aPostDestroyData
) {
75 // Only unregister if we registered in the first place:
76 if (!(mState
& NS_FRAME_IS_NONDISPLAY
)) {
77 nsSVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this);
79 nsContainerFrame::DestroyFrom(aDestructRoot
, aPostDestroyData
);
82 nsresult
nsSVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID
,
85 if (aNameSpaceID
== kNameSpaceID_None
) {
86 if (aAttribute
== nsGkAtoms::transform
) {
87 // We don't invalidate for transform changes (the layers code does that).
88 // Also note that SVGTransformableElement::GetAttributeChangeHint will
89 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
90 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
92 } else if (aAttribute
== nsGkAtoms::viewBox
||
93 aAttribute
== nsGkAtoms::preserveAspectRatio
) {
94 nsLayoutUtils::PostRestyleEvent(
95 mContent
->AsElement(), RestyleHint
{0},
96 nsChangeHint_InvalidateRenderingObservers
);
103 void nsSVGForeignObjectFrame::DidSetComputedStyle(
104 ComputedStyle
* aOldComputedStyle
) {
105 if (aOldComputedStyle
) {
106 if (StyleSVGReset()->mX
!= aOldComputedStyle
->StyleSVGReset()->mX
||
107 StyleSVGReset()->mY
!= aOldComputedStyle
->StyleSVGReset()->mY
) {
108 // Invalidate cached transform matrix.
110 nsSVGUtils::ScheduleReflowSVG(this);
115 void nsSVGForeignObjectFrame::Reflow(nsPresContext
* aPresContext
,
116 ReflowOutput
& aDesiredSize
,
117 const ReflowInput
& aReflowInput
,
118 nsReflowStatus
& aStatus
) {
119 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
120 MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
121 "Should not have been called");
123 // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
124 // so if that bit is still set we still have a resize pending. If we hit
125 // this assertion, then we should get the presShell to skip reflow roots
126 // that have a dirty parent since a reflow is going to come via the
127 // reflow root's parent anyway.
128 NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_DIRTY
),
129 "Reflowing while a resize is pending is wasteful");
131 // ReflowSVG makes sure mRect is up to date before we're called.
133 NS_ASSERTION(!aReflowInput
.mParentReflowInput
,
134 "should only get reflow from being reflow root");
135 NS_ASSERTION(aReflowInput
.ComputedWidth() == GetSize().width
&&
136 aReflowInput
.ComputedHeight() == GetSize().height
,
137 "reflow roots should be reflowed at existing size and "
138 "svg.css should ensure we have no padding/border/margin");
142 WritingMode wm
= aReflowInput
.GetWritingMode();
143 LogicalSize
finalSize(wm
, aReflowInput
.ComputedISize(),
144 aReflowInput
.ComputedBSize());
145 aDesiredSize
.SetSize(wm
, finalSize
);
146 aDesiredSize
.SetOverflowAreasToDesiredBounds();
149 void nsSVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
150 const nsDisplayListSet
& aLists
) {
151 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
154 nsDisplayList newList
;
155 nsDisplayListSet
set(&newList
, &newList
, &newList
, &newList
, &newList
,
157 DisplayOutline(aBuilder
, set
);
158 BuildDisplayListForNonBlockChildren(aBuilder
, set
);
159 aLists
.Content()->AppendNewToTop
<nsDisplayForeignObject
>(aBuilder
, this,
163 bool nsSVGForeignObjectFrame::IsSVGTransformed(
164 Matrix
* aOwnTransform
, Matrix
* aFromParentTransform
) const {
165 bool foundTransform
= false;
167 // Check if our parent has children-only transforms:
168 nsIFrame
* parent
= GetParent();
170 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
172 static_cast<nsSVGContainerFrame
*>(parent
)->HasChildrenOnlyTransform(
173 aFromParentTransform
);
176 SVGElement
* content
= static_cast<SVGElement
*>(GetContent());
177 SVGAnimatedTransformList
* transformList
= content
->GetAnimatedTransformList();
178 if ((transformList
&& transformList
->HasTransform()) ||
179 content
->GetAnimateMotionTransform()) {
181 *aOwnTransform
= gfx::ToMatrix(
182 content
->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent
));
184 foundTransform
= true;
186 return foundTransform
;
189 void nsSVGForeignObjectFrame::PaintSVG(gfxContext
& aContext
,
190 const gfxMatrix
& aTransform
,
191 imgDrawingParams
& aImgParams
,
192 const nsIntRect
* aDirtyRect
) {
194 !NS_SVGDisplayListPaintingEnabled() || (mState
& NS_FRAME_IS_NONDISPLAY
),
195 "If display lists are enabled, only painting of non-display "
196 "SVG should take this code path");
202 nsIFrame
* kid
= PrincipalChildList().FirstChild();
207 if (aTransform
.IsSingular()) {
208 NS_WARNING("Can't render foreignObject element!");
212 nsRect kidDirtyRect
= kid
->GetVisualOverflowRect();
214 /* Check if we need to draw anything. */
216 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
217 (mState
& NS_FRAME_IS_NONDISPLAY
),
218 "Display lists handle dirty rect intersection test");
219 // Transform the dirty rect into app units in our userspace.
220 gfxMatrix invmatrix
= aTransform
;
221 DebugOnly
<bool> ok
= invmatrix
.Invert();
222 NS_ASSERTION(ok
, "inverse of non-singular matrix should be non-singular");
224 gfxRect transDirtyRect
= gfxRect(aDirtyRect
->x
, aDirtyRect
->y
,
225 aDirtyRect
->width
, aDirtyRect
->height
);
226 transDirtyRect
= invmatrix
.TransformBounds(transDirtyRect
);
228 kidDirtyRect
.IntersectRect(kidDirtyRect
,
229 nsLayoutUtils::RoundGfxRectToAppRect(
230 transDirtyRect
, AppUnitsPerCSSPixel()));
232 // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect,
233 // not with kidDirtyRect. I.e.
234 // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
235 // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect)
236 if (kidDirtyRect
.IsEmpty()) {
243 if (StyleDisplay()->IsScrollableOverflow()) {
244 float x
, y
, width
, height
;
245 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
,
247 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &width
, &height
);
250 nsSVGUtils::GetClipRectForFrame(this, 0.0f
, 0.0f
, width
, height
);
251 nsSVGUtils::SetClipRect(&aContext
, aTransform
, clipRect
);
254 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
255 // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
257 float cssPxPerDevPx
= nsPresContext::AppUnitsToFloatCSSPixels(
258 PresContext()->AppUnitsPerDevPixel());
259 gfxMatrix canvasTMForChildren
= aTransform
;
260 canvasTMForChildren
.PreScale(cssPxPerDevPx
, cssPxPerDevPx
);
262 aContext
.Multiply(canvasTMForChildren
);
264 using PaintFrameFlags
= nsLayoutUtils::PaintFrameFlags
;
265 PaintFrameFlags flags
= PaintFrameFlags::InTransform
;
266 if (SVGAutoRenderState::IsPaintingToWindow(aContext
.GetDrawTarget())) {
267 flags
|= PaintFrameFlags::ToWindow
;
269 if (aImgParams
.imageFlags
& imgIContainer::FLAG_SYNC_DECODE
) {
270 flags
|= PaintFrameFlags::SyncDecodeImages
;
272 Unused
<< nsLayoutUtils::PaintFrame(
273 &aContext
, kid
, nsRegion(kidDirtyRect
), NS_RGBA(0, 0, 0, 0),
274 nsDisplayListBuilderMode::Painting
, flags
);
279 nsIFrame
* nsSVGForeignObjectFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
280 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
281 (mState
& NS_FRAME_IS_NONDISPLAY
),
282 "If display lists are enabled, only hit-testing of a "
283 "clipPath's contents should take this code path");
285 if (IsDisabled() || (GetStateBits() & NS_FRAME_IS_NONDISPLAY
)) {
289 nsIFrame
* kid
= PrincipalChildList().FirstChild();
294 float x
, y
, width
, height
;
295 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
296 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &width
, &height
);
298 if (!gfxRect(x
, y
, width
, height
).Contains(aPoint
) ||
299 !nsSVGUtils::HitTestClip(this, aPoint
)) {
303 // Convert the point to app units relative to the top-left corner of the
304 // viewport that's established by the foreignObject element:
306 gfxPoint pt
= (aPoint
+ gfxPoint(x
, y
)) * AppUnitsPerCSSPixel();
307 nsPoint point
= nsPoint(NSToIntRound(pt
.x
), NSToIntRound(pt
.y
));
309 return nsLayoutUtils::GetFrameForPoint(kid
, point
);
312 void nsSVGForeignObjectFrame::ReflowSVG() {
313 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
314 "This call is probably a wasteful mistake");
316 MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
317 "ReflowSVG mechanism not designed for this");
319 if (!nsSVGUtils::NeedsReflowSVG(this)) {
323 // We update mRect before the DoReflow call so that DoReflow uses the
324 // correct dimensions:
327 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
328 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &w
, &h
);
330 // If mRect's width or height are negative, reflow blows up! We must clamp!
331 if (w
< 0.0f
) w
= 0.0f
;
332 if (h
< 0.0f
) h
= 0.0f
;
334 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x
, y
, w
, h
),
335 AppUnitsPerCSSPixel());
337 // Fully mark our kid dirty so that it gets resized if necessary
338 // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
339 nsIFrame
* kid
= PrincipalChildList().FirstChild();
340 kid
->MarkSubtreeDirty();
342 // Make sure to not allow interrupts if we're not being reflown as a root:
343 nsPresContext::InterruptPreventer
noInterrupts(PresContext());
347 if (mState
& NS_FRAME_FIRST_REFLOW
) {
348 // Make sure we have our filter property (if any) before calling
349 // FinishAndStoreOverflow (subsequent filter changes are handled off
350 // nsChangeHint_UpdateEffects):
351 SVGObserverUtils::UpdateEffects(this);
354 // If we have a filter, we need to invalidate ourselves because filter
355 // output can change even if none of our descendants need repainting.
356 if (StyleEffects()->HasFilters()) {
360 auto* anonKid
= PrincipalChildList().FirstChild();
361 nsRect overflow
= anonKid
->GetVisualOverflowRect();
363 nsOverflowAreas
overflowAreas(overflow
, overflow
);
364 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
366 // Now unset the various reflow bits:
367 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
368 NS_FRAME_HAS_DIRTY_CHILDREN
);
371 void nsSVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags
) {
372 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
373 "Invalidation logic may need adjusting");
375 bool needNewBounds
= false; // i.e. mRect or visual overflow rect
376 bool needReflow
= false;
377 bool needNewCanvasTM
= false;
379 if (aFlags
& COORD_CONTEXT_CHANGED
) {
380 // Coordinate context changes affect mCanvasTM if we have a
381 // percentage 'x' or 'y'
382 if (StyleSVGReset()->mX
.HasPercent() || StyleSVGReset()->mY
.HasPercent()) {
383 needNewBounds
= true;
384 needNewCanvasTM
= true;
387 // Our coordinate context's width/height has changed. If we have a
388 // percentage width/height our dimensions will change so we must reflow.
389 if (StylePosition()->mWidth
.HasPercent() ||
390 StylePosition()->mHeight
.HasPercent()) {
391 needNewBounds
= true;
396 if (aFlags
& TRANSFORM_CHANGED
) {
397 if (mCanvasTM
&& mCanvasTM
->IsSingular()) {
398 needNewBounds
= true; // old bounds are bogus
400 needNewCanvasTM
= true;
401 // In an ideal world we would reflow when our CTM changes. This is because
402 // glyph metrics do not necessarily scale uniformly with change in scale
403 // and, as a result, CTM changes may require text to break at different
404 // points. The problem would be how to keep performance acceptable when
405 // e.g. the transform of an ancestor is animated.
406 // We also seem to get some sort of infinite loop post bug 421584 if we
411 // Ancestor changes can't affect how we render from the perspective of
412 // any rendering observers that we may have, so we don't need to
413 // invalidate them. We also don't need to invalidate ourself, since our
414 // changed ancestor will have invalidated its entire area, which includes
416 nsSVGUtils::ScheduleReflowSVG(this);
419 // If we're called while the PresShell is handling reflow events then we
420 // must have been called as a result of the NotifyViewportChange() call in
421 // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
422 // at this point (i.e. during reflow) because it could confuse the
423 // PresShell and prevent it from reflowing us properly in future. Besides
424 // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us
425 // synchronously, so there's no need.
426 if (needReflow
&& !PresShell()->IsReflowLocked()) {
427 RequestReflow(IntrinsicDirty::Resize
);
430 if (needNewCanvasTM
) {
431 // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
432 // change the code and it needs to use it.
437 SVGBBox
nsSVGForeignObjectFrame::GetBBoxContribution(
438 const Matrix
& aToBBoxUserspace
, uint32_t aFlags
) {
439 SVGForeignObjectElement
* content
=
440 static_cast<SVGForeignObjectElement
*>(GetContent());
443 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
444 content
, &x
, &y
, &w
, &h
);
446 if (w
< 0.0f
) w
= 0.0f
;
447 if (h
< 0.0f
) h
= 0.0f
;
449 if (aToBBoxUserspace
.IsSingular()) {
450 // XXX ReportToConsole
453 return aToBBoxUserspace
.TransformBounds(gfx::Rect(0.0, 0.0, w
, h
));
456 //----------------------------------------------------------------------
458 gfxMatrix
nsSVGForeignObjectFrame::GetCanvasTM() {
460 NS_ASSERTION(GetParent(), "null parent");
462 nsSVGContainerFrame
* parent
=
463 static_cast<nsSVGContainerFrame
*>(GetParent());
464 SVGForeignObjectElement
* content
=
465 static_cast<SVGForeignObjectElement
*>(GetContent());
467 gfxMatrix tm
= content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
469 mCanvasTM
= new gfxMatrix(tm
);
474 //----------------------------------------------------------------------
475 // Implementation helpers
477 void nsSVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType
) {
478 if (GetStateBits() & NS_FRAME_FIRST_REFLOW
)
479 // If we haven't had a ReflowSVG() yet, nothing to do.
482 nsIFrame
* kid
= PrincipalChildList().FirstChild();
487 PresShell()->FrameNeedsReflow(kid
, aType
, NS_FRAME_IS_DIRTY
);
490 void nsSVGForeignObjectFrame::DoReflow() {
492 // Skip reflow if we're zero-sized, unless this is our first reflow.
493 if (IsDisabled() && !(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
497 nsPresContext
* presContext
= PresContext();
498 nsIFrame
* kid
= PrincipalChildList().FirstChild();
503 // initiate a synchronous reflow here and now:
504 RefPtr
<gfxContext
> renderingContext
=
505 presContext
->PresShell()->CreateReferenceRenderingContext();
509 WritingMode wm
= kid
->GetWritingMode();
510 ReflowInput
reflowInput(presContext
, kid
, renderingContext
,
511 LogicalSize(wm
, ISize(wm
), NS_UNCONSTRAINEDSIZE
));
512 ReflowOutput
desiredSize(reflowInput
);
513 nsReflowStatus status
;
515 // We don't use mRect.height above because that tells the child to do
516 // page/column breaking at that height.
518 reflowInput
.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
519 reflowInput
.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
520 "style system should ensure that :-moz-svg-foreign-content "
521 "does not get styled");
522 NS_ASSERTION(reflowInput
.ComputedISize() == ISize(wm
),
523 "reflow input made child wrong size");
524 reflowInput
.SetComputedBSize(BSize(wm
));
526 ReflowChild(kid
, presContext
, desiredSize
, reflowInput
, 0, 0,
527 ReflowChildFlags::NoMoveFrame
, status
);
528 NS_ASSERTION(mRect
.width
== desiredSize
.Width() &&
529 mRect
.height
== desiredSize
.Height(),
531 FinishReflowChild(kid
, presContext
, desiredSize
, &reflowInput
, 0, 0,
532 ReflowChildFlags::NoMoveFrame
);
537 nsRect
nsSVGForeignObjectFrame::GetInvalidRegion() {
538 MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(),
539 "Only called by nsDisplayOuterSVG code");
541 nsIFrame
* kid
= PrincipalChildList().FirstChild();
542 if (kid
->HasInvalidFrameInSubtree()) {
543 gfxRect
r(mRect
.x
, mRect
.y
, mRect
.width
, mRect
.height
);
544 r
.Scale(1.0 / AppUnitsPerCSSPixel());
545 nsRect rect
= nsSVGUtils::ToCanvasBounds(r
, GetCanvasTM(), PresContext());
546 rect
= nsSVGUtils::GetPostFilterVisualOverflowRect(this, rect
);
552 void nsSVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(
553 nsTArray
<OwnedAnonBox
>& aResult
) {
554 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
555 aResult
.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));