Bug 1800263 - Part 1: Tidup, make MarkingState a private enum inside GCMarker r=sfink
[gecko.git] / layout / svg / SVGForeignObjectFrame.cpp
blob3e9fadfff875dd7828a5e4a3e9da83aba9787cf1
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/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"
24 #include "nsRegion.h"
25 #include "SVGGeometryProperty.h"
27 using namespace mozilla::dom;
28 using namespace mozilla::image;
29 namespace SVGT = SVGGeometryProperty::Tags;
31 //----------------------------------------------------------------------
32 // Implementation
34 nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell,
35 mozilla::ComputedStyle* aStyle) {
36 return new (aPresShell)
37 mozilla::SVGForeignObjectFrame(aStyle, aPresShell->GetPresContext());
40 namespace mozilla {
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 |
48 NS_FRAME_SVG_LAYOUT);
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);
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,
84 nsAtom* aAttribute,
85 int32_t aModType) {
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.
92 mCanvasTM = nullptr;
93 } else if (aAttribute == nsGkAtoms::viewBox ||
94 aAttribute == nsGkAtoms::preserveAspectRatio) {
95 nsLayoutUtils::PostRestyleEvent(
96 mContent->AsElement(), RestyleHint{0},
97 nsChangeHint_InvalidateRenderingObservers);
101 return NS_OK;
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.
112 mCanvasTM = nullptr;
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");
143 DoReflow();
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()) {
155 return;
157 nsDisplayList newList(aBuilder);
158 nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList,
159 &newList);
160 DisplayOutline(aBuilder, set);
161 BuildDisplayListForNonBlockChildren(aBuilder, set);
162 aLists.Content()->AppendNewToTop<nsDisplayForeignObject>(aBuilder, this,
163 &newList);
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();
172 if (parent &&
173 parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
174 foundTransform =
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()) {
183 if (aOwnTransform) {
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) {
196 NS_ASSERTION(
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");
201 if (IsDisabled()) {
202 return;
205 nsIFrame* kid = PrincipalChildList().FirstChild();
206 if (!kid) {
207 return;
210 if (aTransform.IsSingular()) {
211 NS_WARNING("Can't render foreignObject element!");
212 return;
215 nsRect kidDirtyRect = kid->InkOverflowRect();
217 /* Check if we need to draw anything. */
218 if (aDirtyRect) {
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()) {
240 return;
244 aContext.Save();
246 if (StyleDisplay()->IsScrollableOverflow()) {
247 float x, y, width, height;
248 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
249 SVGT::Height>(
250 static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
252 gfxRect clipRect =
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
259 // paint correctly.
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),
279 NS_RGBA(0, 0, 0, 0),
280 nsDisplayListBuilderMode::Painting, flags);
282 aContext.Restore();
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)) {
292 return nullptr;
295 nsIFrame* kid = PrincipalChildList().FirstChild();
296 if (!kid) {
297 return nullptr;
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)) {
306 return nullptr;
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)) {
326 return;
329 // We update mRect before the DoReflow call so that DoReflow uses the
330 // correct dimensions:
332 float x, y, w, h;
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());
351 DoReflow();
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()) {
363 InvalidateFrame();
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;
398 needReflow = 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
413 // reflow.
416 if (needNewBounds) {
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
421 // our area.
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.
439 mCanvasTM = nullptr;
443 SVGBBox SVGForeignObjectFrame::GetBBoxContribution(
444 const Matrix& aToBBoxUserspace, uint32_t aFlags) {
445 SVGForeignObjectElement* content =
446 static_cast<SVGForeignObjectElement*>(GetContent());
448 float x, y, w, h;
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
457 return SVGBBox();
459 return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h));
462 //----------------------------------------------------------------------
464 gfxMatrix SVGForeignObjectFrame::GetCanvasTM() {
465 if (!mCanvasTM) {
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);
475 return *mCanvasTM;
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.
484 return;
487 nsIFrame* kid = PrincipalChildList().FirstChild();
488 if (!kid) {
489 return;
492 PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY);
495 void SVGForeignObjectFrame::DoReflow() {
496 MarkInReflow();
497 // Skip reflow if we're zero-sized, unless this is our first reflow.
498 if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
499 return;
502 nsPresContext* presContext = PresContext();
503 nsIFrame* kid = PrincipalChildList().FirstChild();
504 if (!kid) {
505 return;
508 // initiate a synchronous reflow here and now:
509 RefPtr<gfxContext> renderingContext =
510 presContext->PresShell()->CreateReferenceRenderingContext();
512 mInReflow = true;
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.
522 NS_ASSERTION(
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(),
535 "unexpected size");
536 FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0,
537 ReflowChildFlags::NoMoveFrame);
539 mInReflow = false;
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);
552 return rect;
554 return nsRect();
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