Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / svg / SVGViewportElement.cpp
blobc9fef21c87aa40a79d5d100d0459f141d419c7f9
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 #include "mozilla/dom/SVGViewportElement.h"
9 #include <stdint.h>
10 #include "mozilla/AlreadyAddRefed.h"
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/ContentEvents.h"
13 #include "mozilla/EventDispatcher.h"
14 #include "mozilla/FloatingPoint.h"
15 #include "mozilla/Likely.h"
16 #include "mozilla/SMILTypes.h"
17 #include "mozilla/SVGContentUtils.h"
18 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/SVGLengthBinding.h"
20 #include "mozilla/dom/SVGViewElement.h"
22 #include "DOMSVGLength.h"
23 #include "DOMSVGPoint.h"
24 #include "nsContentUtils.h"
25 #include "nsFrameSelection.h"
26 #include "nsError.h"
27 #include "nsGkAtoms.h"
28 #include "nsIFrame.h"
29 #include "nsLayoutUtils.h"
30 #include "nsStyleUtil.h"
32 #include <algorithm>
33 #include "prtime.h"
35 using namespace mozilla::gfx;
37 namespace mozilla::dom {
39 SVGElement::LengthInfo SVGViewportElement::sLengthInfo[4] = {
40 {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
41 SVGContentUtils::X},
42 {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
43 SVGContentUtils::Y},
44 {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
45 SVGContentUtils::X},
46 {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
47 SVGContentUtils::Y},
50 //----------------------------------------------------------------------
51 // Implementation
53 SVGViewportElement::SVGViewportElement(
54 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
55 : SVGGraphicsElement(std::move(aNodeInfo)),
56 mViewportWidth(0),
57 mViewportHeight(0),
58 mHasChildrenOnlyTransform(false) {}
60 //----------------------------------------------------------------------
62 already_AddRefed<SVGAnimatedRect> SVGViewportElement::ViewBox() {
63 return mViewBox.ToSVGAnimatedRect(this);
66 already_AddRefed<DOMSVGAnimatedPreserveAspectRatio>
67 SVGViewportElement::PreserveAspectRatio() {
68 return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this);
71 //----------------------------------------------------------------------
72 // nsIContent methods
74 NS_IMETHODIMP_(bool)
75 SVGViewportElement::IsAttributeMapped(const nsAtom* name) const {
76 // We want to map the 'width' and 'height' attributes into style for
77 // outer-<svg>, except when the attributes aren't set (since their default
78 // values of '100%' can cause unexpected and undesirable behaviour for SVG
79 // inline in HTML). We rely on SVGElement::UpdateContentStyleRule() to
80 // prevent mapping of the default values into style (it only maps attributes
81 // that are set). We also rely on a check in SVGElement::
82 // UpdateContentStyleRule() to prevent us mapping the attributes when they're
83 // given a <length> value that is not currently recognized by the SVG
84 // specification.
86 if (!IsInner() && (name == nsGkAtoms::width || name == nsGkAtoms::height)) {
87 return true;
90 return SVGGraphicsElement::IsAttributeMapped(name);
93 //----------------------------------------------------------------------
94 // SVGElement overrides
96 // Helper for GetViewBoxTransform on root <svg> node
97 // * aLength: internal value for our <svg> width or height attribute.
98 // * aViewportLength: length of the corresponding dimension of the viewport.
99 // * aSelf: the outermost <svg> node itself.
100 // NOTE: aSelf is not an ancestor viewport element, so it can't be used to
101 // resolve percentage lengths. (It can only be used to resolve
102 // 'em'/'ex'-valued units).
103 inline float ComputeSynthesizedViewBoxDimension(
104 const SVGAnimatedLength& aLength, float aViewportLength,
105 const SVGViewportElement* aSelf) {
106 if (aLength.IsPercentage()) {
107 return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f;
110 return aLength.GetAnimValue(aSelf);
113 //----------------------------------------------------------------------
114 // public helpers:
116 void SVGViewportElement::UpdateHasChildrenOnlyTransform() {
117 bool hasChildrenOnlyTransform =
118 HasViewBoxOrSyntheticViewBox() ||
119 (IsRootSVGSVGElement() &&
120 static_cast<SVGSVGElement*>(this)->IsScaledOrTranslated());
121 mHasChildrenOnlyTransform = hasChildrenOnlyTransform;
124 void SVGViewportElement::ChildrenOnlyTransformChanged(uint32_t aFlags) {
125 // Avoid wasteful calls:
126 MOZ_ASSERT(!GetPrimaryFrame()->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
127 "Non-display SVG frames don't maintain overflow rects");
129 nsChangeHint changeHint;
131 bool hadChildrenOnlyTransform = mHasChildrenOnlyTransform;
133 UpdateHasChildrenOnlyTransform();
135 if (hadChildrenOnlyTransform != mHasChildrenOnlyTransform) {
136 // Reconstruct the frame tree to handle stacking context changes:
137 // XXXjwatt don't do this for root-<svg> or even outer-<svg>?
138 changeHint = nsChangeHint_ReconstructFrame;
139 } else {
140 // We just assume the old and new transforms are different.
141 changeHint = nsChangeHint(nsChangeHint_UpdateOverflow |
142 nsChangeHint_ChildrenOnlyTransform);
145 // If we're not reconstructing the frame tree, then we only call
146 // PostRestyleEvent if we're not being called under reflow to avoid recursing
147 // to death. See bug 767056 comments 10 and 12. Since our SVGOuterSVGFrame
148 // is being reflowed we're going to invalidate and repaint its entire area
149 // anyway (which will include our children).
150 if ((changeHint & nsChangeHint_ReconstructFrame) ||
151 !(aFlags & eDuringReflow)) {
152 nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, changeHint);
156 gfx::Matrix SVGViewportElement::GetViewBoxTransform() const {
157 float viewportWidth, viewportHeight;
158 if (IsInner()) {
159 SVGElementMetrics metrics(this);
160 viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValue(metrics);
161 viewportHeight = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(metrics);
162 } else {
163 viewportWidth = mViewportWidth;
164 viewportHeight = mViewportHeight;
167 if (!std::isfinite(viewportWidth) || viewportWidth <= 0.0f ||
168 !std::isfinite(viewportHeight) || viewportHeight <= 0.0f) {
169 return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
172 SVGViewBox viewBox = GetViewBoxWithSynthesis(viewportWidth, viewportHeight);
174 if (!std::isfinite(viewBox.width) || viewBox.width <= 0.0f ||
175 !std::isfinite(viewBox.height) || viewBox.height <= 0.0f) {
176 return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
179 return SVGContentUtils::GetViewBoxTransform(
180 viewportWidth, viewportHeight, viewBox.x, viewBox.y, viewBox.width,
181 viewBox.height, GetPreserveAspectRatioWithOverride());
183 //----------------------------------------------------------------------
184 // SVGViewportElement
186 float SVGViewportElement::GetLength(uint8_t aCtxType) const {
187 const SVGViewBox* viewbox = GetViewBoxInternal().HasRect()
188 ? &GetViewBoxInternal().GetAnimValue()
189 : nullptr;
191 float h = 0.0f, w = 0.0f;
192 bool shouldComputeWidth =
193 (aCtxType == SVGContentUtils::X || aCtxType == SVGContentUtils::XY),
194 shouldComputeHeight =
195 (aCtxType == SVGContentUtils::Y || aCtxType == SVGContentUtils::XY);
197 if (viewbox) {
198 w = viewbox->width;
199 h = viewbox->height;
200 } else if (IsInner()) {
201 // Resolving length for inner <svg> is exactly the same as other
202 // ordinary element. We shouldn't use the SVGViewportElement overload
203 // of GetAnimValue().
204 SVGElementMetrics metrics(this);
205 if (shouldComputeWidth) {
206 w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(metrics);
208 if (shouldComputeHeight) {
209 h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(metrics);
211 } else if (ShouldSynthesizeViewBox()) {
212 if (shouldComputeWidth) {
213 w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
214 mViewportWidth, this);
216 if (shouldComputeHeight) {
217 h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
218 mViewportHeight, this);
220 } else {
221 w = mViewportWidth;
222 h = mViewportHeight;
225 w = std::max(w, 0.0f);
226 h = std::max(h, 0.0f);
228 switch (aCtxType) {
229 case SVGContentUtils::X:
230 return w;
231 case SVGContentUtils::Y:
232 return h;
233 case SVGContentUtils::XY:
234 return float(SVGContentUtils::ComputeNormalizedHypotenuse(w, h));
236 return 0;
239 //----------------------------------------------------------------------
240 // SVGElement methods
242 /* virtual */
243 gfxMatrix SVGViewportElement::PrependLocalTransformsTo(
244 const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const {
245 // 'transform' attribute (or an override from a fragment identifier):
246 gfxMatrix userToParent;
248 if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) {
249 userToParent = GetUserToParentTransform(mAnimateMotionTransform.get(),
250 GetTransformInternal());
251 if (aWhich == eUserSpaceToParent) {
252 return userToParent * aMatrix;
256 gfxMatrix childToUser;
258 if (IsInner()) {
259 float x, y;
260 const_cast<SVGViewportElement*>(this)->GetAnimatedLengthValues(&x, &y,
261 nullptr);
262 childToUser = ThebesMatrix(GetViewBoxTransform().PostTranslate(x, y));
263 } else if (IsRootSVGSVGElement()) {
264 const SVGSVGElement* svg = static_cast<const SVGSVGElement*>(this);
265 const SVGPoint& translate = svg->GetCurrentTranslate();
266 float scale = svg->CurrentScale();
267 childToUser =
268 ThebesMatrix(GetViewBoxTransform()
269 .PostScale(scale, scale)
270 .PostTranslate(translate.GetX(), translate.GetY()));
271 } else {
272 // outer-<svg>, but inline in some other content:
273 childToUser = ThebesMatrix(GetViewBoxTransform());
276 if (aWhich == eAllTransforms) {
277 return childToUser * userToParent * aMatrix;
280 MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes");
282 // The following may look broken because pre-multiplying our eChildToUserSpace
283 // transform with another matrix without including our eUserSpaceToParent
284 // transform between the two wouldn't make sense. We don't expect that to
285 // ever happen though. We get here either when the identity matrix has been
286 // passed because our caller just wants our eChildToUserSpace transform, or
287 // when our eUserSpaceToParent transform has already been multiplied into the
288 // matrix that our caller passes (such as when we're called from PaintSVG).
289 return childToUser * aMatrix;
292 /* virtual */
293 bool SVGViewportElement::HasValidDimensions() const {
294 return !IsInner() ||
295 ((!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
296 mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
297 (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
298 mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0));
301 SVGAnimatedViewBox* SVGViewportElement::GetAnimatedViewBox() {
302 return &mViewBox;
305 SVGAnimatedPreserveAspectRatio*
306 SVGViewportElement::GetAnimatedPreserveAspectRatio() {
307 return &mPreserveAspectRatio;
310 bool SVGViewportElement::ShouldSynthesizeViewBox() const {
311 MOZ_ASSERT(!HasViewBox(), "Should only be called if we lack a viewBox");
313 return IsRootSVGSVGElement() && OwnerDoc()->IsBeingUsedAsImage();
316 //----------------------------------------------------------------------
317 // implementation helpers
319 SVGViewBox SVGViewportElement::GetViewBoxWithSynthesis(
320 float aViewportWidth, float aViewportHeight) const {
321 if (GetViewBoxInternal().HasRect()) {
322 return GetViewBoxInternal().GetAnimValue();
325 if (ShouldSynthesizeViewBox()) {
326 // Special case -- fake a viewBox, using height & width attrs.
327 // (Use |this| as context, since if we get here, we're outermost <svg>.)
328 return SVGViewBox(
329 0, 0,
330 ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
331 mViewportWidth, this),
332 ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
333 mViewportHeight, this));
336 // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
337 // to having a viewBox that exactly matches our viewport size.
338 return SVGViewBox(0, 0, aViewportWidth, aViewportHeight);
341 SVGElement::LengthAttributesInfo SVGViewportElement::GetLengthInfo() {
342 return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
343 ArrayLength(sLengthInfo));
346 } // namespace mozilla::dom