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
) {
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
.ComputedSize() == GetLogicalSize(),
124 "reflow roots should be reflowed at existing size and "
125 "svg.css should ensure we have no padding/border/margin");
129 WritingMode wm
= aReflowInput
.GetWritingMode();
130 LogicalSize
finalSize(wm
, aReflowInput
.ComputedISize(),
131 aReflowInput
.ComputedBSize());
132 aDesiredSize
.SetSize(wm
, finalSize
);
133 aDesiredSize
.SetOverflowAreasToDesiredBounds();
136 void SVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
137 const nsDisplayListSet
& aLists
) {
138 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
141 nsDisplayList
newList(aBuilder
);
142 nsDisplayListSet
set(&newList
, &newList
, &newList
, &newList
, &newList
,
144 DisplayOutline(aBuilder
, set
);
145 BuildDisplayListForNonBlockChildren(aBuilder
, set
);
146 aLists
.Content()->AppendNewToTop
<nsDisplayForeignObject
>(aBuilder
, this,
150 bool SVGForeignObjectFrame::IsSVGTransformed(
151 Matrix
* aOwnTransform
, Matrix
* aFromParentTransform
) const {
152 return SVGUtils::IsSVGTransformed(this, aOwnTransform
, aFromParentTransform
);
155 void SVGForeignObjectFrame::PaintSVG(gfxContext
& aContext
,
156 const gfxMatrix
& aTransform
,
157 imgDrawingParams
& aImgParams
) {
158 NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
159 "Only painting of non-display SVG should take this code path");
165 nsIFrame
* kid
= PrincipalChildList().FirstChild();
170 if (aTransform
.IsSingular()) {
171 NS_WARNING("Can't render foreignObject element!");
175 gfxClipAutoSaveRestore
autoSaveClip(&aContext
);
177 if (StyleDisplay()->IsScrollableOverflow()) {
178 float x
, y
, width
, height
;
179 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
,
181 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &width
, &height
);
184 SVGUtils::GetClipRectForFrame(this, 0.0f
, 0.0f
, width
, height
);
185 autoSaveClip
.TransformedClip(aTransform
, clipRect
);
188 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
189 // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
191 float cssPxPerDevPx
= nsPresContext::AppUnitsToFloatCSSPixels(
192 PresContext()->AppUnitsPerDevPixel());
193 gfxMatrix canvasTMForChildren
= aTransform
;
194 canvasTMForChildren
.PreScale(cssPxPerDevPx
, cssPxPerDevPx
);
196 aContext
.Multiply(canvasTMForChildren
);
198 using PaintFrameFlags
= nsLayoutUtils::PaintFrameFlags
;
199 PaintFrameFlags flags
= PaintFrameFlags::InTransform
;
200 if (SVGAutoRenderState::IsPaintingToWindow(aContext
.GetDrawTarget())) {
201 flags
|= PaintFrameFlags::ToWindow
;
203 if (aImgParams
.imageFlags
& imgIContainer::FLAG_SYNC_DECODE
) {
204 flags
|= PaintFrameFlags::SyncDecodeImages
;
206 if (aImgParams
.imageFlags
& imgIContainer::FLAG_HIGH_QUALITY_SCALING
) {
207 flags
|= PaintFrameFlags::UseHighQualityScaling
;
209 nsLayoutUtils::PaintFrame(&aContext
, kid
, nsRegion(kid
->InkOverflowRect()),
211 nsDisplayListBuilderMode::Painting
, flags
);
214 nsIFrame
* SVGForeignObjectFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
215 MOZ_ASSERT_UNREACHABLE(
216 "A clipPath cannot contain an SVGForeignObject element");
220 void SVGForeignObjectFrame::ReflowSVG() {
221 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
222 "This call is probably a wasteful mistake");
224 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
225 "ReflowSVG mechanism not designed for this");
227 if (!SVGUtils::NeedsReflowSVG(this)) {
231 // We update mRect before the DoReflow call so that DoReflow uses the
232 // correct dimensions:
235 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
236 static_cast<SVGElement
*>(GetContent()), &x
, &y
, &w
, &h
);
238 // If mRect's width or height are negative, reflow blows up! We must clamp!
239 if (w
< 0.0f
) w
= 0.0f
;
240 if (h
< 0.0f
) h
= 0.0f
;
242 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x
, y
, w
, h
),
243 AppUnitsPerCSSPixel());
245 // Fully mark our kid dirty so that it gets resized if necessary
246 // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
247 nsIFrame
* kid
= PrincipalChildList().FirstChild();
248 kid
->MarkSubtreeDirty();
250 // Make sure to not allow interrupts if we're not being reflown as a root:
251 nsPresContext::InterruptPreventer
noInterrupts(PresContext());
255 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
256 // Make sure we have our filter property (if any) before calling
257 // FinishAndStoreOverflow (subsequent filter changes are handled off
258 // nsChangeHint_UpdateEffects):
259 SVGObserverUtils::UpdateEffects(this);
262 // If we have a filter, we need to invalidate ourselves because filter
263 // output can change even if none of our descendants need repainting.
264 if (StyleEffects()->HasFilters()) {
268 auto* anonKid
= PrincipalChildList().FirstChild();
269 nsRect overflow
= anonKid
->InkOverflowRect();
271 OverflowAreas
overflowAreas(overflow
, overflow
);
272 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
274 // Now unset the various reflow bits:
275 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
276 NS_FRAME_HAS_DIRTY_CHILDREN
);
279 void SVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags
) {
280 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
281 "Invalidation logic may need adjusting");
283 bool needNewBounds
= false; // i.e. mRect or ink overflow rect
284 bool needReflow
= false;
285 bool needNewCanvasTM
= false;
287 if (aFlags
& COORD_CONTEXT_CHANGED
) {
288 // Coordinate context changes affect mCanvasTM if we have a
289 // percentage 'x' or 'y'
290 if (StyleSVGReset()->mX
.HasPercent() || StyleSVGReset()->mY
.HasPercent()) {
291 needNewBounds
= true;
292 needNewCanvasTM
= true;
295 // Our coordinate context's width/height has changed. If we have a
296 // percentage width/height our dimensions will change so we must reflow.
297 if (StylePosition()->mWidth
.HasPercent() ||
298 StylePosition()->mHeight
.HasPercent()) {
299 needNewBounds
= true;
304 if (aFlags
& TRANSFORM_CHANGED
) {
305 if (mCanvasTM
&& mCanvasTM
->IsSingular()) {
306 needNewBounds
= true; // old bounds are bogus
308 needNewCanvasTM
= true;
309 // In an ideal world we would reflow when our CTM changes. This is because
310 // glyph metrics do not necessarily scale uniformly with change in scale
311 // and, as a result, CTM changes may require text to break at different
312 // points. The problem would be how to keep performance acceptable when
313 // e.g. the transform of an ancestor is animated.
314 // We also seem to get some sort of infinite loop post bug 421584 if we
319 // Ancestor changes can't affect how we render from the perspective of
320 // any rendering observers that we may have, so we don't need to
321 // invalidate them. We also don't need to invalidate ourself, since our
322 // changed ancestor will have invalidated its entire area, which includes
324 SVGUtils::ScheduleReflowSVG(this);
327 // If we're called while the PresShell is handling reflow events then we
328 // must have been called as a result of the NotifyViewportChange() call in
329 // our SVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
330 // at this point (i.e. during reflow) because it could confuse the
331 // PresShell and prevent it from reflowing us properly in future. Besides
332 // that, SVGOuterSVGFrame::DidReflow will take care of reflowing us
333 // synchronously, so there's no need.
334 if (needReflow
&& !PresShell()->IsReflowLocked()) {
335 RequestReflow(IntrinsicDirty::None
);
338 if (needNewCanvasTM
) {
339 // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
340 // change the code and it needs to use it.
345 SVGBBox
SVGForeignObjectFrame::GetBBoxContribution(
346 const Matrix
& aToBBoxUserspace
, uint32_t aFlags
) {
347 SVGForeignObjectElement
* content
=
348 static_cast<SVGForeignObjectElement
*>(GetContent());
351 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
352 content
, &x
, &y
, &w
, &h
);
354 if (w
< 0.0f
) w
= 0.0f
;
355 if (h
< 0.0f
) h
= 0.0f
;
357 if (aToBBoxUserspace
.IsSingular()) {
358 // XXX ReportToConsole
361 return aToBBoxUserspace
.TransformBounds(gfx::Rect(0.0, 0.0, w
, h
));
364 //----------------------------------------------------------------------
366 gfxMatrix
SVGForeignObjectFrame::GetCanvasTM() {
368 NS_ASSERTION(GetParent(), "null parent");
370 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
371 auto* content
= static_cast<SVGForeignObjectElement
*>(GetContent());
373 gfxMatrix tm
= content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
375 mCanvasTM
= MakeUnique
<gfxMatrix
>(tm
);
380 //----------------------------------------------------------------------
381 // Implementation helpers
383 void SVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType
) {
384 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
385 // If we haven't had a ReflowSVG() yet, nothing to do.
389 nsIFrame
* kid
= PrincipalChildList().FirstChild();
394 PresShell()->FrameNeedsReflow(kid
, aType
, NS_FRAME_IS_DIRTY
);
397 void SVGForeignObjectFrame::DoReflow() {
399 // Skip reflow if we're zero-sized, unless this is our first reflow.
400 if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
404 nsPresContext
* presContext
= PresContext();
405 nsIFrame
* kid
= PrincipalChildList().FirstChild();
410 // initiate a synchronous reflow here and now:
411 UniquePtr
<gfxContext
> renderingContext
=
412 presContext
->PresShell()->CreateReferenceRenderingContext();
414 WritingMode wm
= kid
->GetWritingMode();
415 ReflowInput
reflowInput(presContext
, kid
, renderingContext
.get(),
416 LogicalSize(wm
, ISize(wm
), NS_UNCONSTRAINEDSIZE
));
417 ReflowOutput
desiredSize(reflowInput
);
418 nsReflowStatus status
;
420 // We don't use mRect.height above because that tells the child to do
421 // page/column breaking at that height.
423 reflowInput
.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
424 reflowInput
.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
425 "style system should ensure that :-moz-svg-foreign-content "
426 "does not get styled");
427 NS_ASSERTION(reflowInput
.ComputedISize() == ISize(wm
),
428 "reflow input made child wrong size");
429 reflowInput
.SetComputedBSize(BSize(wm
));
431 ReflowChild(kid
, presContext
, desiredSize
, reflowInput
, 0, 0,
432 ReflowChildFlags::NoMoveFrame
, status
);
433 NS_ASSERTION(mRect
.width
== desiredSize
.Width() &&
434 mRect
.height
== desiredSize
.Height(),
436 FinishReflowChild(kid
, presContext
, desiredSize
, &reflowInput
, 0, 0,
437 ReflowChildFlags::NoMoveFrame
);
440 void SVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(
441 nsTArray
<OwnedAnonBox
>& aResult
) {
442 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
443 aResult
.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
446 } // namespace mozilla