SVGViewportElement.cpp (11700B)
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 #include "mozilla/dom/SVGViewportElement.h" 8 9 #include <stdint.h> 10 11 #include <algorithm> 12 13 #include "DOMSVGLength.h" 14 #include "DOMSVGPoint.h" 15 #include "mozilla/AlreadyAddRefed.h" 16 #include "mozilla/ContentEvents.h" 17 #include "mozilla/EventDispatcher.h" 18 #include "mozilla/SMILTypes.h" 19 #include "mozilla/SVGContentUtils.h" 20 #include "mozilla/dom/Document.h" 21 #include "mozilla/dom/SVGLengthBinding.h" 22 #include "mozilla/dom/SVGViewElement.h" 23 #include "nsContentUtils.h" 24 #include "nsError.h" 25 #include "nsFrameSelection.h" 26 #include "nsGkAtoms.h" 27 #include "nsIFrame.h" 28 #include "nsLayoutUtils.h" 29 #include "nsStyleUtil.h" 30 #include "prtime.h" 31 32 using namespace mozilla::gfx; 33 34 namespace mozilla::dom { 35 36 SVGElement::LengthInfo SVGViewportElement::sLengthInfo[4] = { 37 {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, 38 SVGContentUtils::X}, 39 {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, 40 SVGContentUtils::Y}, 41 {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, 42 SVGContentUtils::X}, 43 {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, 44 SVGContentUtils::Y}, 45 }; 46 47 //---------------------------------------------------------------------- 48 // Implementation 49 50 SVGViewportElement::SVGViewportElement( 51 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 52 : SVGGraphicsElement(std::move(aNodeInfo)), 53 mHasChildrenOnlyTransform(false) {} 54 55 //---------------------------------------------------------------------- 56 57 already_AddRefed<SVGAnimatedRect> SVGViewportElement::ViewBox() { 58 return mViewBox.ToSVGAnimatedRect(this); 59 } 60 61 already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> 62 SVGViewportElement::PreserveAspectRatio() { 63 return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); 64 } 65 66 //---------------------------------------------------------------------- 67 // nsIContent methods 68 69 NS_IMETHODIMP_(bool) 70 SVGViewportElement::IsAttributeMapped(const nsAtom* name) const { 71 // We want to map the 'width' and 'height' attributes into style for 72 // outer-<svg>, except when the attributes aren't set (since their default 73 // values of '100%' can cause unexpected and undesirable behaviour for SVG 74 // inline in HTML). We rely on SVGElement::UpdateContentStyleRule() to 75 // prevent mapping of the default values into style (it only maps attributes 76 // that are set). We also rely on a check in SVGElement:: 77 // UpdateContentStyleRule() to prevent us mapping the attributes when they're 78 // given a <length> value that is not currently recognized by the SVG 79 // specification. 80 81 if (!IsInner() && (name == nsGkAtoms::width || name == nsGkAtoms::height)) { 82 return true; 83 } 84 85 return SVGGraphicsElement::IsAttributeMapped(name); 86 } 87 88 //---------------------------------------------------------------------- 89 // SVGElement overrides 90 91 // Helper for GetViewBoxTransform on root <svg> node 92 // * aLength: internal value for our <svg> width or height attribute. 93 // * aViewportLength: length of the corresponding dimension of the viewport. 94 // * aSelf: the outermost <svg> node itself. 95 // NOTE: aSelf is not an ancestor viewport element, so it can't be used to 96 // resolve percentage lengths. (It can only be used to resolve 97 // 'em'/'ex'-valued units). 98 inline float ComputeSynthesizedViewBoxDimension( 99 const SVGAnimatedLength& aLength, float aViewportLength, 100 const SVGViewportElement* aSelf) { 101 if (aLength.IsPercentage()) { 102 return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f; 103 } 104 105 return aLength.GetAnimValueWithZoom(aSelf); 106 } 107 108 //---------------------------------------------------------------------- 109 // public helpers: 110 111 void SVGViewportElement::UpdateHasChildrenOnlyTransform() { 112 mHasChildrenOnlyTransform = 113 HasViewBoxOrSyntheticViewBox() || 114 (IsRootSVGSVGElement() && 115 static_cast<SVGSVGElement*>(this)->IsScaledOrTranslated()); 116 } 117 118 void SVGViewportElement::ChildrenOnlyTransformChanged( 119 ChildrenOnlyTransformChangedFlags aFlags) { 120 // Avoid wasteful calls: 121 MOZ_ASSERT(!GetPrimaryFrame()->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 122 "Non-display SVG frames don't maintain overflow rects"); 123 124 nsChangeHint changeHint; 125 126 bool hadChildrenOnlyTransform = mHasChildrenOnlyTransform; 127 128 UpdateHasChildrenOnlyTransform(); 129 130 if (hadChildrenOnlyTransform != mHasChildrenOnlyTransform) { 131 // Reconstruct the frame tree to handle stacking context changes: 132 // XXXjwatt don't do this for root-<svg> or even outer-<svg>? 133 changeHint = nsChangeHint_ReconstructFrame; 134 } else { 135 // We just assume the old and new transforms are different. 136 changeHint = nsChangeHint(nsChangeHint_UpdateOverflow | 137 nsChangeHint_ChildrenOnlyTransform); 138 } 139 140 // If we're not reconstructing the frame tree, then we only call 141 // PostRestyleEvent if we're not being called under reflow to avoid recursing 142 // to death. See bug 767056 comments 10 and 12. Since our SVGOuterSVGFrame 143 // is being reflowed we're going to invalidate and repaint its entire area 144 // anyway (which will include our children). 145 if ((changeHint & nsChangeHint_ReconstructFrame) || 146 !aFlags.contains(ChildrenOnlyTransformChangedFlag::DuringReflow)) { 147 nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, changeHint); 148 } 149 } 150 151 gfx::Matrix SVGViewportElement::GetViewBoxTransform() const { 152 float viewportWidth, viewportHeight; 153 if (IsInner()) { 154 SVGElementMetrics metrics(this); 155 viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValueWithZoom(metrics); 156 viewportHeight = 157 mLengthAttributes[ATTR_HEIGHT].GetAnimValueWithZoom(metrics); 158 } else { 159 viewportWidth = mViewportSize.width; 160 viewportHeight = mViewportSize.height; 161 } 162 163 if (!std::isfinite(viewportWidth) || viewportWidth <= 0.0f || 164 !std::isfinite(viewportHeight) || viewportHeight <= 0.0f) { 165 return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular 166 } 167 168 SVGViewBox viewBox = GetViewBoxWithSynthesis(viewportWidth, viewportHeight); 169 170 if (!viewBox.IsValid()) { 171 return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular 172 } 173 174 return SVGContentUtils::GetViewBoxTransform( 175 viewportWidth, viewportHeight, viewBox.x, viewBox.y, viewBox.width, 176 viewBox.height, GetPreserveAspectRatioWithOverride()); 177 } 178 //---------------------------------------------------------------------- 179 // SVGViewportElement 180 181 float SVGViewportElement::GetLength(uint8_t aCtxType) const { 182 const auto& animatedViewBox = GetViewBoxInternal(); 183 float h = 0.0f, w = 0.0f; 184 bool shouldComputeWidth = 185 (aCtxType == SVGContentUtils::X || aCtxType == SVGContentUtils::XY), 186 shouldComputeHeight = 187 (aCtxType == SVGContentUtils::Y || aCtxType == SVGContentUtils::XY); 188 189 if (animatedViewBox.HasRect()) { 190 float zoom = UserSpaceMetrics::GetZoom(this); 191 const auto& viewbox = animatedViewBox.GetAnimValue() * zoom; 192 w = viewbox.width; 193 h = viewbox.height; 194 } else if (IsInner()) { 195 // Resolving length for inner <svg> is exactly the same as other 196 // ordinary element. We shouldn't use the SVGViewportElement overload 197 // of GetAnimValue(). 198 SVGElementMetrics metrics(this); 199 if (shouldComputeWidth) { 200 w = mLengthAttributes[ATTR_WIDTH].GetAnimValueWithZoom(metrics); 201 } 202 if (shouldComputeHeight) { 203 h = mLengthAttributes[ATTR_HEIGHT].GetAnimValueWithZoom(metrics); 204 } 205 } else if (ShouldSynthesizeViewBox()) { 206 if (shouldComputeWidth) { 207 w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH], 208 mViewportSize.width, this); 209 } 210 if (shouldComputeHeight) { 211 h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT], 212 mViewportSize.height, this); 213 } 214 } else { 215 w = mViewportSize.width; 216 h = mViewportSize.height; 217 } 218 219 w = std::max(w, 0.0f); 220 h = std::max(h, 0.0f); 221 222 switch (aCtxType) { 223 case SVGContentUtils::X: 224 return w; 225 case SVGContentUtils::Y: 226 return h; 227 case SVGContentUtils::XY: 228 return float(SVGContentUtils::ComputeNormalizedHypotenuse(w, h)); 229 } 230 return 0; 231 } 232 233 //---------------------------------------------------------------------- 234 // SVGElement methods 235 236 /* virtual */ 237 gfxMatrix SVGViewportElement::ChildToUserSpaceTransform() const { 238 auto viewBox = GetViewBoxTransform(); 239 if (IsInner()) { 240 float x, y; 241 const_cast<SVGViewportElement*>(this)->GetAnimatedLengthValues(&x, &y, 242 nullptr); 243 return ThebesMatrix(viewBox.PostTranslate(x, y)); 244 } 245 if (IsRootSVGSVGElement()) { 246 const auto* svg = static_cast<const SVGSVGElement*>(this); 247 const SVGPoint& translate = svg->GetCurrentTranslate(); 248 float scale = svg->CurrentScale(); 249 return ThebesMatrix(viewBox.PostScale(scale, scale) 250 .PostTranslate(translate.GetX(), translate.GetY())); 251 } 252 // outer-<svg>, but inline in some other content: 253 return ThebesMatrix(viewBox); 254 } 255 256 /* virtual */ 257 bool SVGViewportElement::HasValidDimensions() const { 258 return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || 259 mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && 260 (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || 261 mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); 262 } 263 264 SVGAnimatedViewBox* SVGViewportElement::GetAnimatedViewBox() { 265 return &mViewBox; 266 } 267 268 SVGAnimatedPreserveAspectRatio* 269 SVGViewportElement::GetAnimatedPreserveAspectRatio() { 270 return &mPreserveAspectRatio; 271 } 272 273 bool SVGViewportElement::ShouldSynthesizeViewBox() const { 274 MOZ_ASSERT(!HasViewBox(), "Should only be called if we lack a viewBox"); 275 276 // We synthesize a viewbox if we're the root node of an SVG image 277 // document (and lack an explicit viewBox), as long as our width & 278 // height attributes wouldn't yield an empty synthesized viewbox. 279 return IsRootSVGSVGElement() && OwnerDoc()->IsBeingUsedAsImage() && 280 HasValidDimensions(); 281 } 282 283 //---------------------------------------------------------------------- 284 // implementation helpers 285 286 SVGViewBox SVGViewportElement::GetViewBoxWithSynthesis( 287 float aViewportWidth, float aViewportHeight) const { 288 const auto& animatedViewBox = GetViewBoxInternal(); 289 if (animatedViewBox.HasRect()) { 290 float zoom = UserSpaceMetrics::GetZoom(this); 291 return animatedViewBox.GetAnimValue() * zoom; 292 } 293 294 if (ShouldSynthesizeViewBox()) { 295 // Special case -- fake a viewBox, using height & width attrs. 296 // (Use |this| as context, since if we get here, we're outermost <svg>.) 297 return SVGViewBox( 298 0, 0, 299 ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH], 300 mViewportSize.width, this), 301 ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT], 302 mViewportSize.height, this)); 303 } 304 305 // No viewBox attribute, so we shouldn't auto-scale. This is equivalent 306 // to having a viewBox that exactly matches our viewport size. 307 return SVGViewBox(0, 0, aViewportWidth, aViewportHeight); 308 } 309 310 SVGElement::LengthAttributesInfo SVGViewportElement::GetLengthInfo() { 311 return LengthAttributesInfo(mLengthAttributes, sLengthInfo, 312 std::size(sLengthInfo)); 313 } 314 315 } // namespace mozilla::dom