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/SVGUtils.h"
18 #include "mozilla/dom/SVGForeignObjectElement.h"
19 #include "nsDisplayList.h"
20 #include "nsGkAtoms.h"
21 #include "nsNameSpaceManager.h"
22 #include "nsLayoutUtils.h"
24 #include "SVGGeometryProperty.h"
26 using namespace mozilla::dom
;
27 using namespace mozilla::image
;
28 namespace SVGT
= SVGGeometryProperty::Tags
;
30 //----------------------------------------------------------------------
33 nsContainerFrame
* NS_NewSVGForeignObjectFrame(mozilla::PresShell
* aPresShell
,
34 mozilla::ComputedStyle
* aStyle
) {
35 return new (aPresShell
)
36 mozilla::SVGForeignObjectFrame(aStyle
, aPresShell
->GetPresContext());
41 NS_IMPL_FRAMEARENA_HELPERS(SVGForeignObjectFrame
)
43 SVGForeignObjectFrame::SVGForeignObjectFrame(ComputedStyle
* aStyle
,
44 nsPresContext
* aPresContext
)
45 : nsContainerFrame(aStyle
, aPresContext
, kClassID
), mInReflow(false) {
46 AddStateBits(NS_FRAME_REFLOW_ROOT
| NS_FRAME_MAY_BE_TRANSFORMED
|
47 NS_FRAME_SVG_LAYOUT
| NS_FRAME_FONT_INFLATION_CONTAINER
|
48 NS_FRAME_FONT_INFLATION_FLOW_ROOT
);
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
);
68 nsresult
SVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID
,
71 if (aNameSpaceID
== kNameSpaceID_None
) {
72 if (aAttribute
== nsGkAtoms::transform
) {
73 // We don't invalidate for transform changes (the layers code does that).
74 // Also note that SVGTransformableElement::GetAttributeChangeHint will
75 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
76 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
78 } else if (aAttribute
== nsGkAtoms::viewBox
||
79 aAttribute
== nsGkAtoms::preserveAspectRatio
) {
80 nsLayoutUtils::PostRestyleEvent(
81 mContent
->AsElement(), RestyleHint
{0},
82 nsChangeHint_InvalidateRenderingObservers
);
89 void SVGForeignObjectFrame::DidSetComputedStyle(
90 ComputedStyle
* aOldComputedStyle
) {
91 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle
);
93 if (aOldComputedStyle
) {
94 if (StyleSVGReset()->mX
!= aOldComputedStyle
->StyleSVGReset()->mX
||
95 StyleSVGReset()->mY
!= aOldComputedStyle
->StyleSVGReset()->mY
) {
96 // Invalidate cached transform matrix.
98 SVGUtils::ScheduleReflowSVG(this);
103 void SVGForeignObjectFrame::Reflow(nsPresContext
* aPresContext
,
104 ReflowOutput
& aDesiredSize
,
105 const ReflowInput
& aReflowInput
,
106 nsReflowStatus
& aStatus
) {
107 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
108 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
109 "Should not have been called");
111 // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
112 // so if that bit is still set we still have a resize pending. If we hit
113 // this assertion, then we should get the presShell to skip reflow roots
114 // that have a dirty parent since a reflow is going to come via the
115 // reflow root's parent anyway.
116 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IS_DIRTY
),
117 "Reflowing while a resize is pending is wasteful");
119 // ReflowSVG makes sure mRect is up to date before we're called.
121 NS_ASSERTION(!aReflowInput
.mParentReflowInput
,
122 "should only get reflow from being reflow root");
123 NS_ASSERTION(aReflowInput
.ComputedWidth() == GetSize().width
&&
124 aReflowInput
.ComputedHeight() == GetSize().height
,
125 "reflow roots should be reflowed at existing size and "
126 "svg.css should ensure we have no padding/border/margin");
130 WritingMode wm
= aReflowInput
.GetWritingMode();
131 LogicalSize
finalSize(wm
, aReflowInput
.ComputedISize(),
132 aReflowInput
.ComputedBSize());
133 aDesiredSize
.SetSize(wm
, finalSize
);
134 aDesiredSize
.SetOverflowAreasToDesiredBounds();
137 void SVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
138 const nsDisplayListSet
& aLists
) {
139 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
142 nsDisplayList
newList(aBuilder
);
143 nsDisplayListSet
set(&newList
, &newList
, &newList
, &newList
, &newList
,
145 DisplayOutline(aBuilder
, set
);
146 BuildDisplayListForNonBlockChildren(aBuilder
, set
);
147 aLists
.Content()->AppendNewToTop
<nsDisplayForeignObject
>(aBuilder
, this,
151 bool SVGForeignObjectFrame::IsSVGTransformed(
152 Matrix
* aOwnTransform
, Matrix
* aFromParentTransform
) const {
153 return SVGUtils::IsSVGTransformed(this, aOwnTransform
, aFromParentTransform
);
156 void SVGForeignObjectFrame::PaintSVG(gfxContext
& aContext
,
157 const gfxMatrix
& aTransform
,
158 imgDrawingParams
& aImgParams
) {
159 NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
160 "Only painting of non-display SVG should take this code path");
166 nsIFrame
* kid
= PrincipalChildList().FirstChild();
171 if (aTransform
.IsSingular()) {
172 NS_WARNING("Can't render foreignObject element!");
176 gfxClipAutoSaveRestore
autoSaveClip(&aContext
);
178 if (StyleDisplay()->IsScrollableOverflow()) {
179 float x
, y
, width
, height
;
180 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
,
182 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &width
, &height
);
185 SVGUtils::GetClipRectForFrame(this, 0.0f
, 0.0f
, width
, height
);
186 autoSaveClip
.TransformedClip(aTransform
, clipRect
);
189 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
190 // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
192 float cssPxPerDevPx
= nsPresContext::AppUnitsToFloatCSSPixels(
193 PresContext()->AppUnitsPerDevPixel());
194 gfxMatrix canvasTMForChildren
= aTransform
;
195 canvasTMForChildren
.PreScale(cssPxPerDevPx
, cssPxPerDevPx
);
197 aContext
.Multiply(canvasTMForChildren
);
199 using PaintFrameFlags
= nsLayoutUtils::PaintFrameFlags
;
200 PaintFrameFlags flags
= PaintFrameFlags::InTransform
;
201 if (SVGAutoRenderState::IsPaintingToWindow(aContext
.GetDrawTarget())) {
202 flags
|= PaintFrameFlags::ToWindow
;
204 if (aImgParams
.imageFlags
& imgIContainer::FLAG_SYNC_DECODE
) {
205 flags
|= PaintFrameFlags::SyncDecodeImages
;
207 if (aImgParams
.imageFlags
& imgIContainer::FLAG_HIGH_QUALITY_SCALING
) {
208 flags
|= PaintFrameFlags::UseHighQualityScaling
;
210 nsLayoutUtils::PaintFrame(&aContext
, kid
, nsRegion(kid
->InkOverflowRect()),
212 nsDisplayListBuilderMode::Painting
, flags
);
215 nsIFrame
* SVGForeignObjectFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
216 MOZ_ASSERT_UNREACHABLE(
217 "A clipPath cannot contain an SVGForeignObject element");
221 void SVGForeignObjectFrame::ReflowSVG() {
222 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
223 "This call is probably a wasteful mistake");
225 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
226 "ReflowSVG mechanism not designed for this");
228 if (!SVGUtils::NeedsReflowSVG(this)) {
232 // We update mRect before the DoReflow call so that DoReflow uses the
233 // correct dimensions:
236 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
237 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &w
, &h
);
239 // If mRect's width or height are negative, reflow blows up! We must clamp!
240 if (w
< 0.0f
) w
= 0.0f
;
241 if (h
< 0.0f
) h
= 0.0f
;
243 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x
, y
, w
, h
),
244 AppUnitsPerCSSPixel());
246 // Fully mark our kid dirty so that it gets resized if necessary
247 // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
248 nsIFrame
* kid
= PrincipalChildList().FirstChild();
249 kid
->MarkSubtreeDirty();
251 // Make sure to not allow interrupts if we're not being reflown as a root:
252 nsPresContext::InterruptPreventer
noInterrupts(PresContext());
256 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
257 // Make sure we have our filter property (if any) before calling
258 // FinishAndStoreOverflow (subsequent filter changes are handled off
259 // nsChangeHint_UpdateEffects):
260 SVGObserverUtils::UpdateEffects(this);
263 // If we have a filter, we need to invalidate ourselves because filter
264 // output can change even if none of our descendants need repainting.
265 if (StyleEffects()->HasFilters()) {
269 auto* anonKid
= PrincipalChildList().FirstChild();
270 nsRect overflow
= anonKid
->InkOverflowRect();
272 OverflowAreas
overflowAreas(overflow
, overflow
);
273 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
275 // Now unset the various reflow bits:
276 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
277 NS_FRAME_HAS_DIRTY_CHILDREN
);
280 void SVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags
) {
281 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
282 "Invalidation logic may need adjusting");
284 bool needNewBounds
= false; // i.e. mRect or ink overflow rect
285 bool needReflow
= false;
286 bool needNewCanvasTM
= false;
288 if (aFlags
& COORD_CONTEXT_CHANGED
) {
289 // Coordinate context changes affect mCanvasTM if we have a
290 // percentage 'x' or 'y'
291 if (StyleSVGReset()->mX
.HasPercent() || StyleSVGReset()->mY
.HasPercent()) {
292 needNewBounds
= true;
293 needNewCanvasTM
= true;
296 // Our coordinate context's width/height has changed. If we have a
297 // percentage width/height our dimensions will change so we must reflow.
298 if (StylePosition()->mWidth
.HasPercent() ||
299 StylePosition()->mHeight
.HasPercent()) {
300 needNewBounds
= true;
305 if (aFlags
& TRANSFORM_CHANGED
) {
306 if (mCanvasTM
&& mCanvasTM
->IsSingular()) {
307 needNewBounds
= true; // old bounds are bogus
309 needNewCanvasTM
= true;
310 // In an ideal world we would reflow when our CTM changes. This is because
311 // glyph metrics do not necessarily scale uniformly with change in scale
312 // and, as a result, CTM changes may require text to break at different
313 // points. The problem would be how to keep performance acceptable when
314 // e.g. the transform of an ancestor is animated.
315 // We also seem to get some sort of infinite loop post bug 421584 if we
320 // Ancestor changes can't affect how we render from the perspective of
321 // any rendering observers that we may have, so we don't need to
322 // invalidate them. We also don't need to invalidate ourself, since our
323 // changed ancestor will have invalidated its entire area, which includes
325 SVGUtils::ScheduleReflowSVG(this);
328 // If we're called while the PresShell is handling reflow events then we
329 // must have been called as a result of the NotifyViewportChange() call in
330 // our SVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
331 // at this point (i.e. during reflow) because it could confuse the
332 // PresShell and prevent it from reflowing us properly in future. Besides
333 // that, SVGOuterSVGFrame::DidReflow will take care of reflowing us
334 // synchronously, so there's no need.
335 if (needReflow
&& !PresShell()->IsReflowLocked()) {
336 RequestReflow(IntrinsicDirty::None
);
339 if (needNewCanvasTM
) {
340 // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
341 // change the code and it needs to use it.
346 SVGBBox
SVGForeignObjectFrame::GetBBoxContribution(
347 const Matrix
& aToBBoxUserspace
, uint32_t aFlags
) {
348 SVGForeignObjectElement
* content
=
349 static_cast<SVGForeignObjectElement
*>(GetContent());
352 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
353 content
, &x
, &y
, &w
, &h
);
355 if (w
< 0.0f
) w
= 0.0f
;
356 if (h
< 0.0f
) h
= 0.0f
;
358 if (aToBBoxUserspace
.IsSingular()) {
359 // XXX ReportToConsole
362 return aToBBoxUserspace
.TransformBounds(gfx::Rect(0.0, 0.0, w
, h
));
365 //----------------------------------------------------------------------
367 gfxMatrix
SVGForeignObjectFrame::GetCanvasTM() {
369 NS_ASSERTION(GetParent(), "null parent");
371 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
372 auto* content
= static_cast<SVGForeignObjectElement
*>(GetContent());
374 gfxMatrix tm
= content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
376 mCanvasTM
= MakeUnique
<gfxMatrix
>(tm
);
381 //----------------------------------------------------------------------
382 // Implementation helpers
384 void SVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType
) {
385 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
386 // If we haven't had a ReflowSVG() yet, nothing to do.
390 nsIFrame
* kid
= PrincipalChildList().FirstChild();
395 PresShell()->FrameNeedsReflow(kid
, aType
, NS_FRAME_IS_DIRTY
);
398 void SVGForeignObjectFrame::DoReflow() {
400 // Skip reflow if we're zero-sized, unless this is our first reflow.
401 if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
405 nsPresContext
* presContext
= PresContext();
406 nsIFrame
* kid
= PrincipalChildList().FirstChild();
411 // initiate a synchronous reflow here and now:
412 UniquePtr
<gfxContext
> renderingContext
=
413 presContext
->PresShell()->CreateReferenceRenderingContext();
417 WritingMode wm
= kid
->GetWritingMode();
418 ReflowInput
reflowInput(presContext
, kid
, renderingContext
.get(),
419 LogicalSize(wm
, ISize(wm
), NS_UNCONSTRAINEDSIZE
));
420 ReflowOutput
desiredSize(reflowInput
);
421 nsReflowStatus status
;
423 // We don't use mRect.height above because that tells the child to do
424 // page/column breaking at that height.
426 reflowInput
.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
427 reflowInput
.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
428 "style system should ensure that :-moz-svg-foreign-content "
429 "does not get styled");
430 NS_ASSERTION(reflowInput
.ComputedISize() == ISize(wm
),
431 "reflow input made child wrong size");
432 reflowInput
.SetComputedBSize(BSize(wm
));
434 ReflowChild(kid
, presContext
, desiredSize
, reflowInput
, 0, 0,
435 ReflowChildFlags::NoMoveFrame
, status
);
436 NS_ASSERTION(mRect
.width
== desiredSize
.Width() &&
437 mRect
.height
== desiredSize
.Height(),
439 FinishReflowChild(kid
, presContext
, desiredSize
, &reflowInput
, 0, 0,
440 ReflowChildFlags::NoMoveFrame
);
445 void SVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(
446 nsTArray
<OwnedAnonBox
>& aResult
) {
447 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
448 aResult
.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
451 } // namespace mozilla