SVGViewportFrame.cpp (9764B)
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 // Main header first: 8 #include "SVGViewportFrame.h" 9 10 // Keep others in (case-insensitive) order: 11 #include "gfx2DGlue.h" 12 #include "gfxContext.h" 13 #include "mozilla/ISVGDisplayableFrame.h" 14 #include "mozilla/SVGContainerFrame.h" 15 #include "mozilla/SVGUtils.h" 16 #include "mozilla/dom/SVGViewportElement.h" 17 #include "nsLayoutUtils.h" 18 19 using namespace mozilla::dom; 20 using namespace mozilla::gfx; 21 using namespace mozilla::image; 22 23 namespace mozilla { 24 25 //---------------------------------------------------------------------- 26 // ISVGDisplayableFrame methods 27 28 void SVGViewportFrame::PaintSVG(gfxContext& aContext, 29 const gfxMatrix& aTransform, 30 imgDrawingParams& aImgParams) { 31 NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 32 "Only painting of non-display SVG should take this code path"); 33 34 gfxClipAutoSaveRestore autoSaveClip(&aContext); 35 36 if (StyleDisplay()->IsScrollableOverflow()) { 37 float x, y, width, height; 38 static_cast<SVGViewportElement*>(GetContent()) 39 ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); 40 41 if (width <= 0 || height <= 0) { 42 return; 43 } 44 45 gfxRect clipRect = SVGUtils::GetClipRectForFrame(this, x, y, width, height); 46 autoSaveClip.TransformedClip(aTransform, clipRect); 47 } 48 49 SVGDisplayContainerFrame::PaintSVG(aContext, aTransform, aImgParams); 50 } 51 52 void SVGViewportFrame::ReflowSVG() { 53 // mRect must be set before FinishAndStoreOverflow is called in order 54 // for our overflow areas to be clipped correctly. 55 float x, y, width, height; 56 static_cast<SVGViewportElement*>(GetContent()) 57 ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); 58 if (width < 0.0f) { 59 width = 0.0f; 60 } 61 if (height < 0.0f) { 62 height = 0.0f; 63 } 64 mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, width, height), 65 AppUnitsPerCSSPixel()); 66 67 // If we have a filter, we need to invalidate ourselves because filter 68 // output can change even if none of our descendants need repainting. 69 if (StyleEffects()->HasFilters()) { 70 InvalidateFrame(); 71 } 72 73 SVGDisplayContainerFrame::ReflowSVG(); 74 } 75 76 void SVGViewportFrame::NotifySVGChanged(ChangeFlags aFlags) { 77 MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) || 78 aFlags.contains(ChangeFlag::CoordContextChanged), 79 "Invalidation logic may need adjusting"); 80 81 if (aFlags.contains(ChangeFlag::CoordContextChanged)) { 82 SVGViewportElement* svg = static_cast<SVGViewportElement*>(GetContent()); 83 84 bool xOrYIsPercentage = 85 svg->mLengthAttributes[SVGViewportElement::ATTR_X].IsPercentage() || 86 svg->mLengthAttributes[SVGViewportElement::ATTR_Y].IsPercentage(); 87 bool widthOrHeightIsPercentage = 88 svg->mLengthAttributes[SVGViewportElement::ATTR_WIDTH].IsPercentage() || 89 svg->mLengthAttributes[SVGViewportElement::ATTR_HEIGHT].IsPercentage(); 90 91 if (xOrYIsPercentage || widthOrHeightIsPercentage) { 92 // Ancestor changes can't affect how we render from the perspective of 93 // any rendering observers that we may have, so we don't need to 94 // invalidate them. We also don't need to invalidate ourself, since our 95 // changed ancestor will have invalidated its entire area, which includes 96 // our area. 97 // For perf reasons we call this before calling NotifySVGChanged() below. 98 SVGUtils::ScheduleReflowSVG(this); 99 } 100 101 // Coordinate context changes affect mCanvasTM if we have a 102 // percentage 'x' or 'y', or if we have a percentage 'width' or 'height' AND 103 // a 'viewBox'. 104 105 if (!aFlags.contains(ChangeFlag::TransformChanged) && 106 (xOrYIsPercentage || 107 (widthOrHeightIsPercentage && svg->HasViewBox()))) { 108 aFlags += ChangeFlag::TransformChanged; 109 } 110 111 if (svg->HasViewBox() || !widthOrHeightIsPercentage) { 112 // Remove COORD_CONTEXT_CHANGED, since we establish the coordinate 113 // context for our descendants and this notification won't change its 114 // dimensions: 115 aFlags -= ChangeFlag::CoordContextChanged; 116 117 if (aFlags.isEmpty()) { 118 return; // No notification flags left 119 } 120 } 121 } 122 123 SVGDisplayContainerFrame::NotifySVGChanged(aFlags); 124 } 125 126 SVGBBox SVGViewportFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, 127 uint32_t aFlags) { 128 // XXXjwatt It seems like authors would want the result to be clipped by the 129 // viewport we establish if IsScrollableOverflow() is true. We should 130 // consider doing that. See bug 1350755. 131 132 SVGBBox bbox; 133 134 if (aFlags & SVGUtils::eForGetClientRects) { 135 // XXXjwatt For consistency with the old code this code includes the 136 // viewport we establish in the result, but only includes the bounds of our 137 // descendants if they are not clipped to that viewport. However, this is 138 // both inconsistent with Chrome and with the specs. See bug 1350755. 139 // Ideally getClientRects/getBoundingClientRect should be consistent with 140 // getBBox. 141 float x, y, w, h; 142 static_cast<SVGViewportElement*>(GetContent()) 143 ->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); 144 if (w < 0.0f) { 145 w = 0.0f; 146 } 147 if (h < 0.0f) { 148 h = 0.0f; 149 } 150 Rect viewport(x, y, w, h); 151 bbox = aToBBoxUserspace.TransformBounds(viewport); 152 if (StyleDisplay()->IsScrollableOverflow()) { 153 return bbox; 154 } 155 // Else we're not clipping to our viewport so we fall through and include 156 // the bounds of our children. 157 } 158 159 SVGBBox descendantsBbox = 160 SVGDisplayContainerFrame::GetBBoxContribution(aToBBoxUserspace, aFlags); 161 162 bbox.UnionEdges(descendantsBbox); 163 164 return bbox; 165 } 166 167 nsresult SVGViewportFrame::AttributeChanged(int32_t aNameSpaceID, 168 nsAtom* aAttribute, AttrModType) { 169 if (aNameSpaceID == kNameSpaceID_None && 170 !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 171 SVGViewportElement* content = 172 static_cast<SVGViewportElement*>(GetContent()); 173 174 if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) { 175 nsLayoutUtils::PostRestyleEvent( 176 mContent->AsElement(), RestyleHint{0}, 177 nsChangeHint_InvalidateRenderingObservers); 178 SVGUtils::ScheduleReflowSVG(this); 179 180 if (content->HasViewBoxOrSyntheticViewBox()) { 181 // make sure our cached transform matrix gets (lazily) updated 182 mCanvasTM = nullptr; 183 content->ChildrenOnlyTransformChanged(); 184 SVGUtils::NotifyChildrenOfSVGChange(this, ChangeFlag::TransformChanged); 185 } else { 186 ChangeFlags flags(ChangeFlag::CoordContextChanged); 187 if (mCanvasTM && mCanvasTM->IsSingular()) { 188 mCanvasTM = nullptr; 189 flags += ChangeFlag::TransformChanged; 190 } 191 SVGUtils::NotifyChildrenOfSVGChange(this, flags); 192 } 193 194 } else if (aAttribute == nsGkAtoms::preserveAspectRatio || 195 aAttribute == nsGkAtoms::viewBox || aAttribute == nsGkAtoms::x || 196 aAttribute == nsGkAtoms::y) { 197 // make sure our cached transform matrix gets (lazily) updated 198 mCanvasTM = nullptr; 199 200 SVGUtils::NotifyChildrenOfSVGChange( 201 this, aAttribute == nsGkAtoms::viewBox 202 ? ChangeFlags(ChangeFlag::TransformChanged, 203 ChangeFlag::CoordContextChanged) 204 : ChangeFlag::TransformChanged); 205 206 if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) { 207 nsLayoutUtils::PostRestyleEvent( 208 mContent->AsElement(), RestyleHint{0}, 209 nsChangeHint_InvalidateRenderingObservers); 210 SVGUtils::ScheduleReflowSVG(this); 211 } else if (aAttribute == nsGkAtoms::viewBox || 212 (aAttribute == nsGkAtoms::preserveAspectRatio && 213 content->HasViewBoxOrSyntheticViewBox())) { 214 content->ChildrenOnlyTransformChanged(); 215 // SchedulePaint sets a global state flag so we only need to call it 216 // once (on ourself is fine), not once on each child (despite bug 217 // 828240). 218 SchedulePaint(); 219 } 220 } 221 } 222 223 return NS_OK; 224 } 225 226 nsIFrame* SVGViewportFrame::GetFrameForPoint(const gfxPoint& aPoint) { 227 MOZ_ASSERT_UNREACHABLE("A clipPath cannot contain svg or symbol elements"); 228 return nullptr; 229 } 230 231 //---------------------------------------------------------------------- 232 // ISVGSVGFrame methods: 233 234 void SVGViewportFrame::NotifyViewportOrTransformChanged(ChangeFlags aFlags) { 235 // The dimensions of inner-<svg> frames are purely defined by their "width" 236 // and "height" attributes, and transform changes can only occur as a result 237 // of changes to their "width", "height", "viewBox" or "preserveAspectRatio" 238 // attributes. Changes to all of these attributes are handled in 239 // AttributeChanged(), so we should never be called. 240 NS_ERROR("Not called for SVGViewportFrame"); 241 } 242 243 //---------------------------------------------------------------------- 244 // SVGContainerFrame methods: 245 246 bool SVGViewportFrame::HasChildrenOnlyTransform(gfx::Matrix* aTransform) const { 247 auto* content = static_cast<SVGViewportElement*>(GetContent()); 248 if (!content->HasViewBoxOrSyntheticViewBox()) { 249 return false; 250 } 251 // XXX Maybe return false if the transform is the identity transform? 252 if (aTransform) { 253 *aTransform = content->GetViewBoxTransform(); 254 } 255 return true; 256 } 257 258 } // namespace mozilla