/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Keep in (case-insensitive) order: #include "gfxRect.h" #include "SVGGFrame.h" #include "mozilla/PresShell.h" #include "mozilla/SVGContainerFrame.h" #include "mozilla/SVGObserverUtils.h" #include "mozilla/SVGTextFrame.h" #include "mozilla/SVGUtils.h" #include "mozilla/dom/SVGSwitchElement.h" using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::image; nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle); namespace mozilla { class SVGSwitchFrame final : public SVGGFrame { friend nsIFrame* ::NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, ComputedStyle* aStyle); protected: explicit SVGSwitchFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) : SVGGFrame(aStyle, aPresContext, kClassID) {} public: NS_DECL_FRAMEARENA_HELPERS(SVGSwitchFrame) #ifdef DEBUG void Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) override; #endif #ifdef DEBUG_FRAME_DUMP nsresult GetFrameName(nsAString& aResult) const override { return MakeFrameName(u"SVGSwitch"_ns, aResult); } #endif void BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) override; // ISVGDisplayableFrame interface: void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, imgDrawingParams& aImgParams) override; nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; void ReflowSVG() override; SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, uint32_t aFlags) override; private: nsIFrame* GetActiveChildFrame(); void ReflowAllSVGTextFramesInsideNonActiveChildren(nsIFrame* aActiveChild); static void AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid); }; } // namespace mozilla //---------------------------------------------------------------------- // Implementation nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) { return new (aPresShell) mozilla::SVGSwitchFrame(aStyle, aPresShell->GetPresContext()); } namespace mozilla { NS_IMPL_FRAMEARENA_HELPERS(SVGSwitchFrame) #ifdef DEBUG void SVGSwitchFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svgSwitch), "Content is not an SVG switch"); SVGGFrame::Init(aContent, aParent, aPrevInFlow); } #endif /* DEBUG */ void SVGSwitchFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { if (auto* kid = GetActiveChildFrame()) { BuildDisplayListForChild(aBuilder, kid, aLists); } } void SVGSwitchFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, imgDrawingParams& aImgParams) { NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), "Only painting of non-display SVG should take this code path"); if (StyleEffects()->IsTransparent()) { return; } if (auto* kid = GetActiveChildFrame()) { gfxMatrix tm = aTransform; if (kid->GetContent()->IsSVGElement()) { tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm; } SVGUtils::PaintFrameWithEffects(kid, aContext, tm, aImgParams); } } nsIFrame* SVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint) { MOZ_ASSERT_UNREACHABLE("A clipPath cannot contain an SVGSwitch element"); return nullptr; } static bool ShouldReflowSVGTextFrameInside(const nsIFrame* aFrame) { return aFrame->IsSVGContainerFrame() || aFrame->IsSVGForeignObjectFrame() || !aFrame->IsSVGFrame(); } void SVGSwitchFrame::AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid) { if (!aKid->IsSubtreeDirty()) { return; } if (aKid->IsSVGTextFrame()) { MOZ_ASSERT(!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), "A non-display SVGTextFrame directly contained in a display " "container?"); static_cast(aKid)->ReflowSVG(); } else if (ShouldReflowSVGTextFrameInside(aKid)) { if (!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { for (nsIFrame* kid : aKid->PrincipalChildList()) { AlwaysReflowSVGTextFrameDoForOneKid(kid); } } else { // This child is in a nondisplay context, something like: // // ... // // // We should not call ReflowSVG on it. SVGContainerFrame::ReflowSVGNonDisplayText(aKid); } } } void SVGSwitchFrame::ReflowAllSVGTextFramesInsideNonActiveChildren( nsIFrame* aActiveChild) { for (auto* kid : mFrames) { if (aActiveChild == kid) { continue; } AlwaysReflowSVGTextFrameDoForOneKid(kid); } } void SVGSwitchFrame::ReflowSVG() { NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), "This call is probably a wasteful mistake"); MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), "ReflowSVG mechanism not designed for this"); if (!SVGUtils::NeedsReflowSVG(this)) { return; } // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, // then our outer- has previously had its initial reflow. In that case // we need to make sure that that bit has been removed from ourself _before_ // recursing over our children to ensure that they know too. Otherwise, we // need to remove it _after_ recursing over our children so that they know // the initial reflow is currently underway. bool isFirstReflow = HasAnyStateBits(NS_FRAME_FIRST_REFLOW); bool outerSVGHasHadFirstReflow = !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); if (outerSVGHasHadFirstReflow) { RemoveStateBits(NS_FRAME_FIRST_REFLOW); // tell our children } OverflowAreas overflowRects; auto* child = GetActiveChildFrame(); ReflowAllSVGTextFramesInsideNonActiveChildren(child); ISVGDisplayableFrame* svgChild = do_QueryFrame(child); if (svgChild && !child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { svgChild->ReflowSVG(); // We build up our child frame overflows here instead of using // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same // frame list, and we're iterating over that list now anyway. ConsiderChildOverflow(overflowRects, child); } else if (child && ShouldReflowSVGTextFrameInside(child)) { MOZ_ASSERT( child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || !child->IsSVGFrame(), "Check for this explicitly in the |if|, then"); ReflowSVGNonDisplayText(child); } if (isFirstReflow) { // Make sure we have our filter property (if any) before calling // FinishAndStoreOverflow (subsequent filter changes are handled off // nsChangeHint_UpdateEffects): SVGObserverUtils::UpdateEffects(this); } FinishAndStoreOverflow(overflowRects, mRect.Size()); // Remove state bits after FinishAndStoreOverflow so that it doesn't // invalidate on first reflow: RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); } SVGBBox SVGSwitchFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, uint32_t aFlags) { auto* kid = GetActiveChildFrame(); if (ISVGDisplayableFrame* svgKid = do_QueryFrame(kid)) { nsIContent* content = kid->GetContent(); gfxMatrix transform = ThebesMatrix(aToBBoxUserspace); if (content->IsSVGElement()) { transform = static_cast(content)->PrependLocalTransformsTo( {}, eChildToUserSpace) * SVGUtils::GetTransformMatrixInUserSpace(kid) * transform; } return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags); } return SVGBBox(); } nsIFrame* SVGSwitchFrame::GetActiveChildFrame() { auto* activeChild = static_cast(GetContent())->GetActiveChild(); return activeChild ? activeChild->GetPrimaryFrame() : nullptr; } } // namespace mozilla