no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / layout / svg / SVGForeignObjectFrame.cpp
blob576d6a136725bbba75c5d568f881ceae73c70777
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/. */
7 // Main header first:
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"
23 #include "nsRegion.h"
24 #include "SVGGeometryProperty.h"
26 using namespace mozilla::dom;
27 using namespace mozilla::image;
28 namespace SVGT = SVGGeometryProperty::Tags;
30 //----------------------------------------------------------------------
31 // Implementation
33 nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell,
34 mozilla::ComputedStyle* aStyle) {
35 return new (aPresShell)
36 mozilla::SVGForeignObjectFrame(aStyle, aPresShell->GetPresContext());
39 namespace mozilla {
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 //----------------------------------------------------------------------
52 // nsIFrame methods
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,
69 nsAtom* aAttribute,
70 int32_t aModType) {
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.
77 mCanvasTM = nullptr;
78 } else if (aAttribute == nsGkAtoms::viewBox ||
79 aAttribute == nsGkAtoms::preserveAspectRatio) {
80 nsLayoutUtils::PostRestyleEvent(
81 mContent->AsElement(), RestyleHint{0},
82 nsChangeHint_InvalidateRenderingObservers);
86 return NS_OK;
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.
97 mCanvasTM = nullptr;
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");
128 DoReflow();
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()) {
140 return;
142 nsDisplayList newList(aBuilder);
143 nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList,
144 &newList);
145 DisplayOutline(aBuilder, set);
146 BuildDisplayListForNonBlockChildren(aBuilder, set);
147 aLists.Content()->AppendNewToTop<nsDisplayForeignObject>(aBuilder, this,
148 &newList);
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");
162 if (IsDisabled()) {
163 return;
166 nsIFrame* kid = PrincipalChildList().FirstChild();
167 if (!kid) {
168 return;
171 if (aTransform.IsSingular()) {
172 NS_WARNING("Can't render foreignObject element!");
173 return;
176 gfxClipAutoSaveRestore autoSaveClip(&aContext);
178 if (StyleDisplay()->IsScrollableOverflow()) {
179 float x, y, width, height;
180 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
181 SVGT::Height>(
182 static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
184 gfxRect clipRect =
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
191 // paint correctly.
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()),
211 NS_RGBA(0, 0, 0, 0),
212 nsDisplayListBuilderMode::Painting, flags);
215 nsIFrame* SVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) {
216 MOZ_ASSERT_UNREACHABLE(
217 "A clipPath cannot contain an SVGForeignObject element");
218 return nullptr;
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)) {
229 return;
232 // We update mRect before the DoReflow call so that DoReflow uses the
233 // correct dimensions:
235 float x, y, w, h;
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());
254 DoReflow();
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()) {
266 InvalidateFrame();
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;
301 needReflow = 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
316 // reflow.
319 if (needNewBounds) {
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
324 // our area.
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.
342 mCanvasTM = nullptr;
346 SVGBBox SVGForeignObjectFrame::GetBBoxContribution(
347 const Matrix& aToBBoxUserspace, uint32_t aFlags) {
348 SVGForeignObjectElement* content =
349 static_cast<SVGForeignObjectElement*>(GetContent());
351 float x, y, w, h;
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
360 return SVGBBox();
362 return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h));
365 //----------------------------------------------------------------------
367 gfxMatrix SVGForeignObjectFrame::GetCanvasTM() {
368 if (!mCanvasTM) {
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);
378 return *mCanvasTM;
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.
387 return;
390 nsIFrame* kid = PrincipalChildList().FirstChild();
391 if (!kid) {
392 return;
395 PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY);
398 void SVGForeignObjectFrame::DoReflow() {
399 MarkInReflow();
400 // Skip reflow if we're zero-sized, unless this is our first reflow.
401 if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
402 return;
405 nsPresContext* presContext = PresContext();
406 nsIFrame* kid = PrincipalChildList().FirstChild();
407 if (!kid) {
408 return;
411 // initiate a synchronous reflow here and now:
412 UniquePtr<gfxContext> renderingContext =
413 presContext->PresShell()->CreateReferenceRenderingContext();
415 mInReflow = true;
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.
425 NS_ASSERTION(
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(),
438 "unexpected size");
439 FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0,
440 ReflowChildFlags::NoMoveFrame);
442 mInReflow = false;
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