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 "SVGForeignObjectFrame.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/SVGContainerFrame.h"
16 #include "mozilla/SVGObserverUtils.h"
17 #include "mozilla/SVGOuterSVGFrame.h"
18 #include "mozilla/SVGUtils.h"
19 #include "mozilla/dom/SVGForeignObjectElement.h"
20 #include "nsDisplayList.h"
21 #include "nsGkAtoms.h"
22 #include "nsNameSpaceManager.h"
23 #include "nsLayoutUtils.h"
25 #include "SVGGeometryProperty.h"
27 using namespace mozilla::dom
;
28 using namespace mozilla::image
;
29 namespace SVGT
= SVGGeometryProperty::Tags
;
31 //----------------------------------------------------------------------
34 nsContainerFrame
* NS_NewSVGForeignObjectFrame(mozilla::PresShell
* aPresShell
,
35 mozilla::ComputedStyle
* aStyle
) {
36 return new (aPresShell
)
37 mozilla::SVGForeignObjectFrame(aStyle
, aPresShell
->GetPresContext());
42 NS_IMPL_FRAMEARENA_HELPERS(SVGForeignObjectFrame
)
44 SVGForeignObjectFrame::SVGForeignObjectFrame(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(SVGForeignObjectFrame
)
55 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame
)
56 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
58 void SVGForeignObjectFrame::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 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED
);
69 if (!(mState
& NS_FRAME_IS_NONDISPLAY
)) {
70 SVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this);
74 void SVGForeignObjectFrame::DestroyFrom(nsIFrame
* aDestructRoot
,
75 PostDestroyData
& aPostDestroyData
) {
76 // Only unregister if we registered in the first place:
77 if (!(mState
& NS_FRAME_IS_NONDISPLAY
)) {
78 SVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this);
80 nsContainerFrame::DestroyFrom(aDestructRoot
, aPostDestroyData
);
83 nsresult
SVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID
,
86 if (aNameSpaceID
== kNameSpaceID_None
) {
87 if (aAttribute
== nsGkAtoms::transform
) {
88 // We don't invalidate for transform changes (the layers code does that).
89 // Also note that SVGTransformableElement::GetAttributeChangeHint will
90 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
91 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
93 } else if (aAttribute
== nsGkAtoms::viewBox
||
94 aAttribute
== nsGkAtoms::preserveAspectRatio
) {
95 nsLayoutUtils::PostRestyleEvent(
96 mContent
->AsElement(), RestyleHint
{0},
97 nsChangeHint_InvalidateRenderingObservers
);
104 void SVGForeignObjectFrame::DidSetComputedStyle(
105 ComputedStyle
* aOldComputedStyle
) {
106 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle
);
108 if (aOldComputedStyle
) {
109 if (StyleSVGReset()->mX
!= aOldComputedStyle
->StyleSVGReset()->mX
||
110 StyleSVGReset()->mY
!= aOldComputedStyle
->StyleSVGReset()->mY
) {
111 // Invalidate cached transform matrix.
113 SVGUtils::ScheduleReflowSVG(this);
118 void SVGForeignObjectFrame::Reflow(nsPresContext
* aPresContext
,
119 ReflowOutput
& aDesiredSize
,
120 const ReflowInput
& aReflowInput
,
121 nsReflowStatus
& aStatus
) {
122 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
123 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
124 "Should not have been called");
126 // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
127 // so if that bit is still set we still have a resize pending. If we hit
128 // this assertion, then we should get the presShell to skip reflow roots
129 // that have a dirty parent since a reflow is going to come via the
130 // reflow root's parent anyway.
131 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IS_DIRTY
),
132 "Reflowing while a resize is pending is wasteful");
134 // ReflowSVG makes sure mRect is up to date before we're called.
136 NS_ASSERTION(!aReflowInput
.mParentReflowInput
,
137 "should only get reflow from being reflow root");
138 NS_ASSERTION(aReflowInput
.ComputedWidth() == GetSize().width
&&
139 aReflowInput
.ComputedHeight() == GetSize().height
,
140 "reflow roots should be reflowed at existing size and "
141 "svg.css should ensure we have no padding/border/margin");
145 WritingMode wm
= aReflowInput
.GetWritingMode();
146 LogicalSize
finalSize(wm
, aReflowInput
.ComputedISize(),
147 aReflowInput
.ComputedBSize());
148 aDesiredSize
.SetSize(wm
, finalSize
);
149 aDesiredSize
.SetOverflowAreasToDesiredBounds();
152 void SVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
153 const nsDisplayListSet
& aLists
) {
154 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
157 nsDisplayList
newList(aBuilder
);
158 nsDisplayListSet
set(&newList
, &newList
, &newList
, &newList
, &newList
,
160 DisplayOutline(aBuilder
, set
);
161 BuildDisplayListForNonBlockChildren(aBuilder
, set
);
162 aLists
.Content()->AppendNewToTop
<nsDisplayForeignObject
>(aBuilder
, this,
166 bool SVGForeignObjectFrame::IsSVGTransformed(
167 Matrix
* aOwnTransform
, Matrix
* aFromParentTransform
) const {
168 bool foundTransform
= false;
170 // Check if our parent has children-only transforms:
171 nsIFrame
* parent
= GetParent();
173 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
175 static_cast<SVGContainerFrame
*>(parent
)->HasChildrenOnlyTransform(
176 aFromParentTransform
);
179 SVGElement
* content
= static_cast<SVGElement
*>(GetContent());
180 SVGAnimatedTransformList
* transformList
= content
->GetAnimatedTransformList();
181 if ((transformList
&& transformList
->HasTransform()) ||
182 content
->GetAnimateMotionTransform()) {
184 *aOwnTransform
= gfx::ToMatrix(
185 content
->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent
));
187 foundTransform
= true;
189 return foundTransform
;
192 void SVGForeignObjectFrame::PaintSVG(gfxContext
& aContext
,
193 const gfxMatrix
& aTransform
,
194 imgDrawingParams
& aImgParams
,
195 const nsIntRect
* aDirtyRect
) {
197 !NS_SVGDisplayListPaintingEnabled() || (mState
& NS_FRAME_IS_NONDISPLAY
),
198 "If display lists are enabled, only painting of non-display "
199 "SVG should take this code path");
205 nsIFrame
* kid
= PrincipalChildList().FirstChild();
210 if (aTransform
.IsSingular()) {
211 NS_WARNING("Can't render foreignObject element!");
215 nsRect kidDirtyRect
= kid
->InkOverflowRect();
217 /* Check if we need to draw anything. */
219 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
220 (mState
& NS_FRAME_IS_NONDISPLAY
),
221 "Display lists handle dirty rect intersection test");
222 // Transform the dirty rect into app units in our userspace.
223 gfxMatrix invmatrix
= aTransform
;
224 DebugOnly
<bool> ok
= invmatrix
.Invert();
225 NS_ASSERTION(ok
, "inverse of non-singular matrix should be non-singular");
227 gfxRect transDirtyRect
= gfxRect(aDirtyRect
->x
, aDirtyRect
->y
,
228 aDirtyRect
->width
, aDirtyRect
->height
);
229 transDirtyRect
= invmatrix
.TransformBounds(transDirtyRect
);
231 kidDirtyRect
.IntersectRect(kidDirtyRect
,
232 nsLayoutUtils::RoundGfxRectToAppRect(
233 transDirtyRect
, AppUnitsPerCSSPixel()));
235 // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect,
236 // not with kidDirtyRect. I.e.
237 // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
238 // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect)
239 if (kidDirtyRect
.IsEmpty()) {
246 if (StyleDisplay()->IsScrollableOverflow()) {
247 float x
, y
, width
, height
;
248 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
,
250 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &width
, &height
);
253 SVGUtils::GetClipRectForFrame(this, 0.0f
, 0.0f
, width
, height
);
254 SVGUtils::SetClipRect(&aContext
, aTransform
, clipRect
);
257 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
258 // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
260 float cssPxPerDevPx
= nsPresContext::AppUnitsToFloatCSSPixels(
261 PresContext()->AppUnitsPerDevPixel());
262 gfxMatrix canvasTMForChildren
= aTransform
;
263 canvasTMForChildren
.PreScale(cssPxPerDevPx
, cssPxPerDevPx
);
265 aContext
.Multiply(canvasTMForChildren
);
267 using PaintFrameFlags
= nsLayoutUtils::PaintFrameFlags
;
268 PaintFrameFlags flags
= PaintFrameFlags::InTransform
;
269 if (SVGAutoRenderState::IsPaintingToWindow(aContext
.GetDrawTarget())) {
270 flags
|= PaintFrameFlags::ToWindow
;
272 if (aImgParams
.imageFlags
& imgIContainer::FLAG_SYNC_DECODE
) {
273 flags
|= PaintFrameFlags::SyncDecodeImages
;
275 if (aImgParams
.imageFlags
& imgIContainer::FLAG_HIGH_QUALITY_SCALING
) {
276 flags
|= PaintFrameFlags::UseHighQualityScaling
;
278 nsLayoutUtils::PaintFrame(&aContext
, kid
, nsRegion(kidDirtyRect
),
280 nsDisplayListBuilderMode::Painting
, flags
);
285 nsIFrame
* SVGForeignObjectFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
286 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
287 (mState
& NS_FRAME_IS_NONDISPLAY
),
288 "If display lists are enabled, only hit-testing of a "
289 "clipPath's contents should take this code path");
291 if (IsDisabled() || HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
295 nsIFrame
* kid
= PrincipalChildList().FirstChild();
300 float x
, y
, width
, height
;
301 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
302 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &width
, &height
);
304 if (!gfxRect(x
, y
, width
, height
).Contains(aPoint
) ||
305 !SVGUtils::HitTestClip(this, aPoint
)) {
309 // Convert the point to app units relative to the top-left corner of the
310 // viewport that's established by the foreignObject element:
312 gfxPoint pt
= (aPoint
+ gfxPoint(x
, y
)) * AppUnitsPerCSSPixel();
313 nsPoint point
= nsPoint(NSToIntRound(pt
.x
), NSToIntRound(pt
.y
));
315 return nsLayoutUtils::GetFrameForPoint(RelativeTo
{kid
}, point
);
318 void SVGForeignObjectFrame::ReflowSVG() {
319 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
320 "This call is probably a wasteful mistake");
322 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
323 "ReflowSVG mechanism not designed for this");
325 if (!SVGUtils::NeedsReflowSVG(this)) {
329 // We update mRect before the DoReflow call so that DoReflow uses the
330 // correct dimensions:
333 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
334 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &w
, &h
);
336 // If mRect's width or height are negative, reflow blows up! We must clamp!
337 if (w
< 0.0f
) w
= 0.0f
;
338 if (h
< 0.0f
) h
= 0.0f
;
340 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x
, y
, w
, h
),
341 AppUnitsPerCSSPixel());
343 // Fully mark our kid dirty so that it gets resized if necessary
344 // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
345 nsIFrame
* kid
= PrincipalChildList().FirstChild();
346 kid
->MarkSubtreeDirty();
348 // Make sure to not allow interrupts if we're not being reflown as a root:
349 nsPresContext::InterruptPreventer
noInterrupts(PresContext());
353 if (mState
& NS_FRAME_FIRST_REFLOW
) {
354 // Make sure we have our filter property (if any) before calling
355 // FinishAndStoreOverflow (subsequent filter changes are handled off
356 // nsChangeHint_UpdateEffects):
357 SVGObserverUtils::UpdateEffects(this);
360 // If we have a filter, we need to invalidate ourselves because filter
361 // output can change even if none of our descendants need repainting.
362 if (StyleEffects()->HasFilters()) {
366 auto* anonKid
= PrincipalChildList().FirstChild();
367 nsRect overflow
= anonKid
->InkOverflowRect();
369 OverflowAreas
overflowAreas(overflow
, overflow
);
370 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
372 // Now unset the various reflow bits:
373 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
374 NS_FRAME_HAS_DIRTY_CHILDREN
);
377 void SVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags
) {
378 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
379 "Invalidation logic may need adjusting");
381 bool needNewBounds
= false; // i.e. mRect or ink overflow rect
382 bool needReflow
= false;
383 bool needNewCanvasTM
= false;
385 if (aFlags
& COORD_CONTEXT_CHANGED
) {
386 // Coordinate context changes affect mCanvasTM if we have a
387 // percentage 'x' or 'y'
388 if (StyleSVGReset()->mX
.HasPercent() || StyleSVGReset()->mY
.HasPercent()) {
389 needNewBounds
= true;
390 needNewCanvasTM
= true;
393 // Our coordinate context's width/height has changed. If we have a
394 // percentage width/height our dimensions will change so we must reflow.
395 if (StylePosition()->mWidth
.HasPercent() ||
396 StylePosition()->mHeight
.HasPercent()) {
397 needNewBounds
= true;
402 if (aFlags
& TRANSFORM_CHANGED
) {
403 if (mCanvasTM
&& mCanvasTM
->IsSingular()) {
404 needNewBounds
= true; // old bounds are bogus
406 needNewCanvasTM
= true;
407 // In an ideal world we would reflow when our CTM changes. This is because
408 // glyph metrics do not necessarily scale uniformly with change in scale
409 // and, as a result, CTM changes may require text to break at different
410 // points. The problem would be how to keep performance acceptable when
411 // e.g. the transform of an ancestor is animated.
412 // We also seem to get some sort of infinite loop post bug 421584 if we
417 // Ancestor changes can't affect how we render from the perspective of
418 // any rendering observers that we may have, so we don't need to
419 // invalidate them. We also don't need to invalidate ourself, since our
420 // changed ancestor will have invalidated its entire area, which includes
422 SVGUtils::ScheduleReflowSVG(this);
425 // If we're called while the PresShell is handling reflow events then we
426 // must have been called as a result of the NotifyViewportChange() call in
427 // our SVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
428 // at this point (i.e. during reflow) because it could confuse the
429 // PresShell and prevent it from reflowing us properly in future. Besides
430 // that, SVGOuterSVGFrame::DidReflow will take care of reflowing us
431 // synchronously, so there's no need.
432 if (needReflow
&& !PresShell()->IsReflowLocked()) {
433 RequestReflow(IntrinsicDirty::Resize
);
436 if (needNewCanvasTM
) {
437 // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
438 // change the code and it needs to use it.
443 SVGBBox
SVGForeignObjectFrame::GetBBoxContribution(
444 const Matrix
& aToBBoxUserspace
, uint32_t aFlags
) {
445 SVGForeignObjectElement
* content
=
446 static_cast<SVGForeignObjectElement
*>(GetContent());
449 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
450 content
, &x
, &y
, &w
, &h
);
452 if (w
< 0.0f
) w
= 0.0f
;
453 if (h
< 0.0f
) h
= 0.0f
;
455 if (aToBBoxUserspace
.IsSingular()) {
456 // XXX ReportToConsole
459 return aToBBoxUserspace
.TransformBounds(gfx::Rect(0.0, 0.0, w
, h
));
462 //----------------------------------------------------------------------
464 gfxMatrix
SVGForeignObjectFrame::GetCanvasTM() {
466 NS_ASSERTION(GetParent(), "null parent");
468 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
469 auto* content
= static_cast<SVGForeignObjectElement
*>(GetContent());
471 gfxMatrix tm
= content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
473 mCanvasTM
= MakeUnique
<gfxMatrix
>(tm
);
478 //----------------------------------------------------------------------
479 // Implementation helpers
481 void SVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType
) {
482 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
483 // If we haven't had a ReflowSVG() yet, nothing to do.
487 nsIFrame
* kid
= PrincipalChildList().FirstChild();
492 PresShell()->FrameNeedsReflow(kid
, aType
, NS_FRAME_IS_DIRTY
);
495 void SVGForeignObjectFrame::DoReflow() {
497 // Skip reflow if we're zero-sized, unless this is our first reflow.
498 if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
502 nsPresContext
* presContext
= PresContext();
503 nsIFrame
* kid
= PrincipalChildList().FirstChild();
508 // initiate a synchronous reflow here and now:
509 RefPtr
<gfxContext
> renderingContext
=
510 presContext
->PresShell()->CreateReferenceRenderingContext();
514 WritingMode wm
= kid
->GetWritingMode();
515 ReflowInput
reflowInput(presContext
, kid
, renderingContext
,
516 LogicalSize(wm
, ISize(wm
), NS_UNCONSTRAINEDSIZE
));
517 ReflowOutput
desiredSize(reflowInput
);
518 nsReflowStatus status
;
520 // We don't use mRect.height above because that tells the child to do
521 // page/column breaking at that height.
523 reflowInput
.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
524 reflowInput
.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
525 "style system should ensure that :-moz-svg-foreign-content "
526 "does not get styled");
527 NS_ASSERTION(reflowInput
.ComputedISize() == ISize(wm
),
528 "reflow input made child wrong size");
529 reflowInput
.SetComputedBSize(BSize(wm
));
531 ReflowChild(kid
, presContext
, desiredSize
, reflowInput
, 0, 0,
532 ReflowChildFlags::NoMoveFrame
, status
);
533 NS_ASSERTION(mRect
.width
== desiredSize
.Width() &&
534 mRect
.height
== desiredSize
.Height(),
536 FinishReflowChild(kid
, presContext
, desiredSize
, &reflowInput
, 0, 0,
537 ReflowChildFlags::NoMoveFrame
);
542 nsRect
SVGForeignObjectFrame::GetInvalidRegion() {
543 MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(),
544 "Only called by nsDisplayOuterSVG code");
546 nsIFrame
* kid
= PrincipalChildList().FirstChild();
547 if (kid
->HasInvalidFrameInSubtree()) {
548 gfxRect
r(mRect
.x
, mRect
.y
, mRect
.width
, mRect
.height
);
549 r
.Scale(1.0 / AppUnitsPerCSSPixel());
550 nsRect rect
= SVGUtils::ToCanvasBounds(r
, GetCanvasTM(), PresContext());
551 rect
= SVGUtils::GetPostFilterInkOverflowRect(this, rect
);
557 void SVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(
558 nsTArray
<OwnedAnonBox
>& aResult
) {
559 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
560 aResult
.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
563 } // namespace mozilla