Bug 1568157 - Part 5: Move the NodePicker initialization into a getter. r=yulia
[gecko.git] / layout / svg / nsSVGForeignObjectFrame.cpp
blobdff61f635e681aeca94fa899a7257290f5183f65
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 "nsSVGForeignObjectFrame.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/dom/SVGForeignObjectElement.h"
16 #include "nsDisplayList.h"
17 #include "nsGkAtoms.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsLayoutUtils.h"
20 #include "nsRegion.h"
21 #include "nsSVGContainerFrame.h"
22 #include "SVGGeometryProperty.h"
23 #include "SVGObserverUtils.h"
24 #include "nsSVGIntegrationUtils.h"
25 #include "nsSVGOuterSVGFrame.h"
26 #include "nsSVGUtils.h"
28 using namespace mozilla;
29 using namespace mozilla::dom;
30 using namespace mozilla::image;
31 namespace SVGT = SVGGeometryProperty::Tags;
33 //----------------------------------------------------------------------
34 // Implementation
36 nsContainerFrame* NS_NewSVGForeignObjectFrame(PresShell* aPresShell,
37 ComputedStyle* aStyle) {
38 return new (aPresShell)
39 nsSVGForeignObjectFrame(aStyle, aPresShell->GetPresContext());
42 NS_IMPL_FRAMEARENA_HELPERS(nsSVGForeignObjectFrame)
44 nsSVGForeignObjectFrame::nsSVGForeignObjectFrame(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(nsSVGForeignObjectFrame)
55 NS_QUERYFRAME_ENTRY(nsSVGDisplayableFrame)
56 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
58 void nsSVGForeignObjectFrame::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 if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
69 nsSVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this);
73 void nsSVGForeignObjectFrame::DestroyFrom(nsIFrame* aDestructRoot,
74 PostDestroyData& aPostDestroyData) {
75 // Only unregister if we registered in the first place:
76 if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
77 nsSVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this);
79 nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
82 nsresult nsSVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID,
83 nsAtom* aAttribute,
84 int32_t aModType) {
85 if (aNameSpaceID == kNameSpaceID_None) {
86 if (aAttribute == nsGkAtoms::transform) {
87 // We don't invalidate for transform changes (the layers code does that).
88 // Also note that SVGTransformableElement::GetAttributeChangeHint will
89 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
90 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
91 mCanvasTM = nullptr;
92 } else if (aAttribute == nsGkAtoms::viewBox ||
93 aAttribute == nsGkAtoms::preserveAspectRatio) {
94 nsLayoutUtils::PostRestyleEvent(
95 mContent->AsElement(), RestyleHint{0},
96 nsChangeHint_InvalidateRenderingObservers);
100 return NS_OK;
103 void nsSVGForeignObjectFrame::DidSetComputedStyle(
104 ComputedStyle* aOldComputedStyle) {
105 if (aOldComputedStyle) {
106 if (StyleSVGReset()->mX != aOldComputedStyle->StyleSVGReset()->mX ||
107 StyleSVGReset()->mY != aOldComputedStyle->StyleSVGReset()->mY) {
108 // Invalidate cached transform matrix.
109 mCanvasTM = nullptr;
110 nsSVGUtils::ScheduleReflowSVG(this);
115 void nsSVGForeignObjectFrame::Reflow(nsPresContext* aPresContext,
116 ReflowOutput& aDesiredSize,
117 const ReflowInput& aReflowInput,
118 nsReflowStatus& aStatus) {
119 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
120 MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
121 "Should not have been called");
123 // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
124 // so if that bit is still set we still have a resize pending. If we hit
125 // this assertion, then we should get the presShell to skip reflow roots
126 // that have a dirty parent since a reflow is going to come via the
127 // reflow root's parent anyway.
128 NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_DIRTY),
129 "Reflowing while a resize is pending is wasteful");
131 // ReflowSVG makes sure mRect is up to date before we're called.
133 NS_ASSERTION(!aReflowInput.mParentReflowInput,
134 "should only get reflow from being reflow root");
135 NS_ASSERTION(aReflowInput.ComputedWidth() == GetSize().width &&
136 aReflowInput.ComputedHeight() == GetSize().height,
137 "reflow roots should be reflowed at existing size and "
138 "svg.css should ensure we have no padding/border/margin");
140 DoReflow();
142 WritingMode wm = aReflowInput.GetWritingMode();
143 LogicalSize finalSize(wm, aReflowInput.ComputedISize(),
144 aReflowInput.ComputedBSize());
145 aDesiredSize.SetSize(wm, finalSize);
146 aDesiredSize.SetOverflowAreasToDesiredBounds();
149 void nsSVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
150 const nsDisplayListSet& aLists) {
151 if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) {
152 return;
154 nsDisplayList newList;
155 nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList,
156 &newList);
157 DisplayOutline(aBuilder, set);
158 BuildDisplayListForNonBlockChildren(aBuilder, set);
159 aLists.Content()->AppendNewToTop<nsDisplayForeignObject>(aBuilder, this,
160 &newList);
163 bool nsSVGForeignObjectFrame::IsSVGTransformed(
164 Matrix* aOwnTransform, Matrix* aFromParentTransform) const {
165 bool foundTransform = false;
167 // Check if our parent has children-only transforms:
168 nsIFrame* parent = GetParent();
169 if (parent &&
170 parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
171 foundTransform =
172 static_cast<nsSVGContainerFrame*>(parent)->HasChildrenOnlyTransform(
173 aFromParentTransform);
176 SVGElement* content = static_cast<SVGElement*>(GetContent());
177 SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList();
178 if ((transformList && transformList->HasTransform()) ||
179 content->GetAnimateMotionTransform()) {
180 if (aOwnTransform) {
181 *aOwnTransform = gfx::ToMatrix(
182 content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
184 foundTransform = true;
186 return foundTransform;
189 void nsSVGForeignObjectFrame::PaintSVG(gfxContext& aContext,
190 const gfxMatrix& aTransform,
191 imgDrawingParams& aImgParams,
192 const nsIntRect* aDirtyRect) {
193 NS_ASSERTION(
194 !NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY),
195 "If display lists are enabled, only painting of non-display "
196 "SVG should take this code path");
198 if (IsDisabled()) {
199 return;
202 nsIFrame* kid = PrincipalChildList().FirstChild();
203 if (!kid) {
204 return;
207 if (aTransform.IsSingular()) {
208 NS_WARNING("Can't render foreignObject element!");
209 return;
212 nsRect kidDirtyRect = kid->GetVisualOverflowRect();
214 /* Check if we need to draw anything. */
215 if (aDirtyRect) {
216 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
217 (mState & NS_FRAME_IS_NONDISPLAY),
218 "Display lists handle dirty rect intersection test");
219 // Transform the dirty rect into app units in our userspace.
220 gfxMatrix invmatrix = aTransform;
221 DebugOnly<bool> ok = invmatrix.Invert();
222 NS_ASSERTION(ok, "inverse of non-singular matrix should be non-singular");
224 gfxRect transDirtyRect = gfxRect(aDirtyRect->x, aDirtyRect->y,
225 aDirtyRect->width, aDirtyRect->height);
226 transDirtyRect = invmatrix.TransformBounds(transDirtyRect);
228 kidDirtyRect.IntersectRect(kidDirtyRect,
229 nsLayoutUtils::RoundGfxRectToAppRect(
230 transDirtyRect, AppUnitsPerCSSPixel()));
232 // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect,
233 // not with kidDirtyRect. I.e.
234 // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
235 // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect)
236 if (kidDirtyRect.IsEmpty()) {
237 return;
241 aContext.Save();
243 if (StyleDisplay()->IsScrollableOverflow()) {
244 float x, y, width, height;
245 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
246 SVGT::Height>(
247 static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
249 gfxRect clipRect =
250 nsSVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height);
251 nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect);
254 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
255 // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
256 // paint correctly.
257 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
258 PresContext()->AppUnitsPerDevPixel());
259 gfxMatrix canvasTMForChildren = aTransform;
260 canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx);
262 aContext.Multiply(canvasTMForChildren);
264 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
265 PaintFrameFlags flags = PaintFrameFlags::InTransform;
266 if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) {
267 flags |= PaintFrameFlags::ToWindow;
269 if (aImgParams.imageFlags & imgIContainer::FLAG_SYNC_DECODE) {
270 flags |= PaintFrameFlags::SyncDecodeImages;
272 Unused << nsLayoutUtils::PaintFrame(
273 &aContext, kid, nsRegion(kidDirtyRect), NS_RGBA(0, 0, 0, 0),
274 nsDisplayListBuilderMode::Painting, flags);
276 aContext.Restore();
279 nsIFrame* nsSVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) {
280 NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
281 (mState & NS_FRAME_IS_NONDISPLAY),
282 "If display lists are enabled, only hit-testing of a "
283 "clipPath's contents should take this code path");
285 if (IsDisabled() || (GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
286 return nullptr;
289 nsIFrame* kid = PrincipalChildList().FirstChild();
290 if (!kid) {
291 return nullptr;
294 float x, y, width, height;
295 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
296 static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
298 if (!gfxRect(x, y, width, height).Contains(aPoint) ||
299 !nsSVGUtils::HitTestClip(this, aPoint)) {
300 return nullptr;
303 // Convert the point to app units relative to the top-left corner of the
304 // viewport that's established by the foreignObject element:
306 gfxPoint pt = (aPoint + gfxPoint(x, y)) * AppUnitsPerCSSPixel();
307 nsPoint point = nsPoint(NSToIntRound(pt.x), NSToIntRound(pt.y));
309 return nsLayoutUtils::GetFrameForPoint(kid, point);
312 void nsSVGForeignObjectFrame::ReflowSVG() {
313 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
314 "This call is probably a wasteful mistake");
316 MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
317 "ReflowSVG mechanism not designed for this");
319 if (!nsSVGUtils::NeedsReflowSVG(this)) {
320 return;
323 // We update mRect before the DoReflow call so that DoReflow uses the
324 // correct dimensions:
326 float x, y, w, h;
327 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
328 static_cast<SVGElement*>(GetContent()), &x, &y, &w, &h);
330 // If mRect's width or height are negative, reflow blows up! We must clamp!
331 if (w < 0.0f) w = 0.0f;
332 if (h < 0.0f) h = 0.0f;
334 mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, w, h),
335 AppUnitsPerCSSPixel());
337 // Fully mark our kid dirty so that it gets resized if necessary
338 // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
339 nsIFrame* kid = PrincipalChildList().FirstChild();
340 kid->MarkSubtreeDirty();
342 // Make sure to not allow interrupts if we're not being reflown as a root:
343 nsPresContext::InterruptPreventer noInterrupts(PresContext());
345 DoReflow();
347 if (mState & NS_FRAME_FIRST_REFLOW) {
348 // Make sure we have our filter property (if any) before calling
349 // FinishAndStoreOverflow (subsequent filter changes are handled off
350 // nsChangeHint_UpdateEffects):
351 SVGObserverUtils::UpdateEffects(this);
354 // If we have a filter, we need to invalidate ourselves because filter
355 // output can change even if none of our descendants need repainting.
356 if (StyleEffects()->HasFilters()) {
357 InvalidateFrame();
360 auto* anonKid = PrincipalChildList().FirstChild();
361 nsRect overflow = anonKid->GetVisualOverflowRect();
363 nsOverflowAreas overflowAreas(overflow, overflow);
364 FinishAndStoreOverflow(overflowAreas, mRect.Size());
366 // Now unset the various reflow bits:
367 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
368 NS_FRAME_HAS_DIRTY_CHILDREN);
371 void nsSVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags) {
372 MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
373 "Invalidation logic may need adjusting");
375 bool needNewBounds = false; // i.e. mRect or visual overflow rect
376 bool needReflow = false;
377 bool needNewCanvasTM = false;
379 if (aFlags & COORD_CONTEXT_CHANGED) {
380 // Coordinate context changes affect mCanvasTM if we have a
381 // percentage 'x' or 'y'
382 if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) {
383 needNewBounds = true;
384 needNewCanvasTM = true;
387 // Our coordinate context's width/height has changed. If we have a
388 // percentage width/height our dimensions will change so we must reflow.
389 if (StylePosition()->mWidth.HasPercent() ||
390 StylePosition()->mHeight.HasPercent()) {
391 needNewBounds = true;
392 needReflow = true;
396 if (aFlags & TRANSFORM_CHANGED) {
397 if (mCanvasTM && mCanvasTM->IsSingular()) {
398 needNewBounds = true; // old bounds are bogus
400 needNewCanvasTM = true;
401 // In an ideal world we would reflow when our CTM changes. This is because
402 // glyph metrics do not necessarily scale uniformly with change in scale
403 // and, as a result, CTM changes may require text to break at different
404 // points. The problem would be how to keep performance acceptable when
405 // e.g. the transform of an ancestor is animated.
406 // We also seem to get some sort of infinite loop post bug 421584 if we
407 // reflow.
410 if (needNewBounds) {
411 // Ancestor changes can't affect how we render from the perspective of
412 // any rendering observers that we may have, so we don't need to
413 // invalidate them. We also don't need to invalidate ourself, since our
414 // changed ancestor will have invalidated its entire area, which includes
415 // our area.
416 nsSVGUtils::ScheduleReflowSVG(this);
419 // If we're called while the PresShell is handling reflow events then we
420 // must have been called as a result of the NotifyViewportChange() call in
421 // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
422 // at this point (i.e. during reflow) because it could confuse the
423 // PresShell and prevent it from reflowing us properly in future. Besides
424 // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us
425 // synchronously, so there's no need.
426 if (needReflow && !PresShell()->IsReflowLocked()) {
427 RequestReflow(IntrinsicDirty::Resize);
430 if (needNewCanvasTM) {
431 // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
432 // change the code and it needs to use it.
433 mCanvasTM = nullptr;
437 SVGBBox nsSVGForeignObjectFrame::GetBBoxContribution(
438 const Matrix& aToBBoxUserspace, uint32_t aFlags) {
439 SVGForeignObjectElement* content =
440 static_cast<SVGForeignObjectElement*>(GetContent());
442 float x, y, w, h;
443 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
444 content, &x, &y, &w, &h);
446 if (w < 0.0f) w = 0.0f;
447 if (h < 0.0f) h = 0.0f;
449 if (aToBBoxUserspace.IsSingular()) {
450 // XXX ReportToConsole
451 return SVGBBox();
453 return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h));
456 //----------------------------------------------------------------------
458 gfxMatrix nsSVGForeignObjectFrame::GetCanvasTM() {
459 if (!mCanvasTM) {
460 NS_ASSERTION(GetParent(), "null parent");
462 nsSVGContainerFrame* parent =
463 static_cast<nsSVGContainerFrame*>(GetParent());
464 SVGForeignObjectElement* content =
465 static_cast<SVGForeignObjectElement*>(GetContent());
467 gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
469 mCanvasTM = new gfxMatrix(tm);
471 return *mCanvasTM;
474 //----------------------------------------------------------------------
475 // Implementation helpers
477 void nsSVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType) {
478 if (GetStateBits() & NS_FRAME_FIRST_REFLOW)
479 // If we haven't had a ReflowSVG() yet, nothing to do.
480 return;
482 nsIFrame* kid = PrincipalChildList().FirstChild();
483 if (!kid) {
484 return;
487 PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY);
490 void nsSVGForeignObjectFrame::DoReflow() {
491 MarkInReflow();
492 // Skip reflow if we're zero-sized, unless this is our first reflow.
493 if (IsDisabled() && !(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
494 return;
497 nsPresContext* presContext = PresContext();
498 nsIFrame* kid = PrincipalChildList().FirstChild();
499 if (!kid) {
500 return;
503 // initiate a synchronous reflow here and now:
504 RefPtr<gfxContext> renderingContext =
505 presContext->PresShell()->CreateReferenceRenderingContext();
507 mInReflow = true;
509 WritingMode wm = kid->GetWritingMode();
510 ReflowInput reflowInput(presContext, kid, renderingContext,
511 LogicalSize(wm, ISize(wm), NS_UNCONSTRAINEDSIZE));
512 ReflowOutput desiredSize(reflowInput);
513 nsReflowStatus status;
515 // We don't use mRect.height above because that tells the child to do
516 // page/column breaking at that height.
517 NS_ASSERTION(
518 reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
519 reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
520 "style system should ensure that :-moz-svg-foreign-content "
521 "does not get styled");
522 NS_ASSERTION(reflowInput.ComputedISize() == ISize(wm),
523 "reflow input made child wrong size");
524 reflowInput.SetComputedBSize(BSize(wm));
526 ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0,
527 ReflowChildFlags::NoMoveFrame, status);
528 NS_ASSERTION(mRect.width == desiredSize.Width() &&
529 mRect.height == desiredSize.Height(),
530 "unexpected size");
531 FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0,
532 ReflowChildFlags::NoMoveFrame);
534 mInReflow = false;
537 nsRect nsSVGForeignObjectFrame::GetInvalidRegion() {
538 MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(),
539 "Only called by nsDisplayOuterSVG code");
541 nsIFrame* kid = PrincipalChildList().FirstChild();
542 if (kid->HasInvalidFrameInSubtree()) {
543 gfxRect r(mRect.x, mRect.y, mRect.width, mRect.height);
544 r.Scale(1.0 / AppUnitsPerCSSPixel());
545 nsRect rect = nsSVGUtils::ToCanvasBounds(r, GetCanvasTM(), PresContext());
546 rect = nsSVGUtils::GetPostFilterVisualOverflowRect(this, rect);
547 return rect;
549 return nsRect();
552 void nsSVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(
553 nsTArray<OwnedAnonBox>& aResult) {
554 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
555 aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));