SVGSwitchFrame.cpp (8532B)
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/. */ 6 7 // Keep in (case-insensitive) order: 8 #include "SVGGFrame.h" 9 #include "gfxRect.h" 10 #include "mozilla/PresShell.h" 11 #include "mozilla/SVGContainerFrame.h" 12 #include "mozilla/SVGObserverUtils.h" 13 #include "mozilla/SVGTextFrame.h" 14 #include "mozilla/SVGUtils.h" 15 #include "mozilla/dom/SVGSwitchElement.h" 16 17 using namespace mozilla::dom; 18 using namespace mozilla::gfx; 19 using namespace mozilla::image; 20 21 nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, 22 mozilla::ComputedStyle* aStyle); 23 24 namespace mozilla { 25 26 class SVGSwitchFrame final : public SVGGFrame { 27 friend nsIFrame* ::NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, 28 ComputedStyle* aStyle); 29 30 protected: 31 explicit SVGSwitchFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) 32 : SVGGFrame(aStyle, aPresContext, kClassID) {} 33 34 public: 35 NS_DECL_FRAMEARENA_HELPERS(SVGSwitchFrame) 36 37 #ifdef DEBUG 38 void Init(nsIContent* aContent, nsContainerFrame* aParent, 39 nsIFrame* aPrevInFlow) override; 40 #endif 41 42 #ifdef DEBUG_FRAME_DUMP 43 nsresult GetFrameName(nsAString& aResult) const override { 44 return MakeFrameName(u"SVGSwitch"_ns, aResult); 45 } 46 #endif 47 48 void BuildDisplayList(nsDisplayListBuilder* aBuilder, 49 const nsDisplayListSet& aLists) override; 50 51 // ISVGDisplayableFrame interface: 52 void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, 53 imgDrawingParams& aImgParams) override; 54 nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; 55 void ReflowSVG() override; 56 SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, 57 uint32_t aFlags) override; 58 59 private: 60 nsIFrame* GetActiveChildFrame(); 61 void ReflowAllSVGTextFramesInsideNonActiveChildren(nsIFrame* aActiveChild); 62 static void AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid); 63 }; 64 65 } // namespace mozilla 66 67 //---------------------------------------------------------------------- 68 // Implementation 69 70 nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, 71 mozilla::ComputedStyle* aStyle) { 72 return new (aPresShell) 73 mozilla::SVGSwitchFrame(aStyle, aPresShell->GetPresContext()); 74 } 75 76 namespace mozilla { 77 78 NS_IMPL_FRAMEARENA_HELPERS(SVGSwitchFrame) 79 80 #ifdef DEBUG 81 void SVGSwitchFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 82 nsIFrame* aPrevInFlow) { 83 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svgSwitch), 84 "Content is not an SVG switch"); 85 86 SVGGFrame::Init(aContent, aParent, aPrevInFlow); 87 } 88 #endif /* DEBUG */ 89 90 void SVGSwitchFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 91 const nsDisplayListSet& aLists) { 92 if (HidesContent()) { 93 return; 94 } 95 if (auto* kid = GetActiveChildFrame()) { 96 BuildDisplayListForChild(aBuilder, kid, aLists); 97 } 98 } 99 100 void SVGSwitchFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, 101 imgDrawingParams& aImgParams) { 102 NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 103 "Only painting of non-display SVG should take this code path"); 104 105 if (StyleEffects()->IsTransparent()) { 106 return; 107 } 108 109 if (auto* kid = GetActiveChildFrame()) { 110 gfxMatrix tm = aTransform; 111 if (kid->GetContent()->IsSVGElement()) { 112 tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm; 113 } 114 SVGUtils::PaintFrameWithEffects(kid, aContext, tm, aImgParams); 115 } 116 } 117 118 nsIFrame* SVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint) { 119 MOZ_ASSERT_UNREACHABLE("A clipPath cannot contain an SVGSwitch element"); 120 return nullptr; 121 } 122 123 static bool ShouldReflowSVGTextFrameInside(const nsIFrame* aFrame) { 124 return aFrame->IsSVGContainerFrame() || aFrame->IsSVGForeignObjectFrame() || 125 !aFrame->IsSVGFrame(); 126 } 127 128 void SVGSwitchFrame::AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid) { 129 if (!aKid->IsSubtreeDirty()) { 130 return; 131 } 132 133 if (aKid->IsSVGTextFrame()) { 134 MOZ_ASSERT(!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 135 "A non-display SVGTextFrame directly contained in a display " 136 "container?"); 137 static_cast<SVGTextFrame*>(aKid)->ReflowSVG(); 138 } else if (ShouldReflowSVGTextFrameInside(aKid)) { 139 if (!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 140 for (nsIFrame* kid : aKid->PrincipalChildList()) { 141 AlwaysReflowSVGTextFrameDoForOneKid(kid); 142 } 143 } else { 144 // This child is in a nondisplay context, something like: 145 // <switch> 146 // ... 147 // <g><mask><text></text></mask></g> 148 // </switch> 149 // We should not call ReflowSVG on it. 150 SVGContainerFrame::ReflowSVGNonDisplayText(aKid); 151 } 152 } 153 } 154 155 void SVGSwitchFrame::ReflowAllSVGTextFramesInsideNonActiveChildren( 156 nsIFrame* aActiveChild) { 157 for (auto* kid : mFrames) { 158 if (aActiveChild == kid) { 159 continue; 160 } 161 162 AlwaysReflowSVGTextFrameDoForOneKid(kid); 163 } 164 } 165 166 void SVGSwitchFrame::ReflowSVG() { 167 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), 168 "This call is probably a wasteful mistake"); 169 170 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 171 "ReflowSVG mechanism not designed for this"); 172 173 if (!SVGUtils::NeedsReflowSVG(this)) { 174 return; 175 } 176 177 // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, 178 // then our outer-<svg> has previously had its initial reflow. In that case 179 // we need to make sure that that bit has been removed from ourself _before_ 180 // recursing over our children to ensure that they know too. Otherwise, we 181 // need to remove it _after_ recursing over our children so that they know 182 // the initial reflow is currently underway. 183 184 bool isFirstReflow = HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 185 186 bool outerSVGHasHadFirstReflow = 187 !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 188 189 if (outerSVGHasHadFirstReflow) { 190 RemoveStateBits(NS_FRAME_FIRST_REFLOW); // tell our children 191 } 192 193 OverflowAreas overflowRects; 194 195 auto* child = GetActiveChildFrame(); 196 ReflowAllSVGTextFramesInsideNonActiveChildren(child); 197 198 ISVGDisplayableFrame* svgChild = do_QueryFrame(child); 199 if (svgChild && !child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 200 svgChild->ReflowSVG(); 201 202 // We build up our child frame overflows here instead of using 203 // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same 204 // frame list, and we're iterating over that list now anyway. 205 ConsiderChildOverflow(overflowRects, child); 206 } else if (child && ShouldReflowSVGTextFrameInside(child)) { 207 MOZ_ASSERT( 208 child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || !child->IsSVGFrame(), 209 "Check for this explicitly in the |if|, then"); 210 ReflowSVGNonDisplayText(child); 211 } 212 213 if (isFirstReflow) { 214 // Make sure we have our filter property (if any) before calling 215 // FinishAndStoreOverflow (subsequent filter changes are handled off 216 // nsChangeHint_UpdateEffects): 217 SVGObserverUtils::UpdateEffects(this); 218 } 219 220 FinishAndStoreOverflow(overflowRects, mRect.Size()); 221 222 // Remove state bits after FinishAndStoreOverflow so that it doesn't 223 // invalidate on first reflow: 224 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 225 NS_FRAME_HAS_DIRTY_CHILDREN); 226 } 227 228 SVGBBox SVGSwitchFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, 229 uint32_t aFlags) { 230 auto* kid = GetActiveChildFrame(); 231 if (ISVGDisplayableFrame* svgKid = do_QueryFrame(kid)) { 232 nsIContent* content = kid->GetContent(); 233 gfxMatrix transform = ThebesMatrix(aToBBoxUserspace); 234 if (content->IsSVGElement()) { 235 transform = 236 static_cast<SVGElement*>(content)->ChildToUserSpaceTransform() * 237 SVGUtils::GetTransformMatrixInUserSpace(kid) * transform; 238 } 239 return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags); 240 } 241 return SVGBBox(); 242 } 243 244 nsIFrame* SVGSwitchFrame::GetActiveChildFrame() { 245 auto* activeChild = 246 static_cast<dom::SVGSwitchElement*>(GetContent())->GetActiveChild(); 247 return activeChild ? activeChild->GetPrimaryFrame() : nullptr; 248 } 249 250 } // namespace mozilla