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 <stdint.h>
8 #include "mozilla/ArrayUtils.h"
9 #include "mozilla/ContentEvents.h"
10 #include "mozilla/EventDispatcher.h"
11 #include "mozilla/Likely.h"
12 #include "mozilla/dom/SVGLengthBinding.h"
13 #include "mozilla/dom/SVGMatrix.h"
14 #include "mozilla/dom/SVGViewportElement.h"
15 #include "mozilla/dom/SVGViewElement.h"
16
17 #include "DOMSVGLength.h"
18 #include "DOMSVGPoint.h"
19 #include "nsCOMPtr.h"
20 #include "nsContentUtils.h"
21 #include "nsFrameSelection.h"
22 #include "nsError.h"
23 #include "nsGkAtoms.h"
24 #include "nsIDocument.h"
25 #include "nsIFrame.h"
26 #include "nsIPresShell.h"
27 #include "nsISVGSVGFrame.h" //XXX
28 #include "nsLayoutUtils.h"
29 #include "nsStyleUtil.h"
30 #include "nsSMILTypes.h"
31 #include "SVGContentUtils.h"
32
33 #include <algorithm>
34 #include "prtime.h"
35
36 using namespace mozilla::gfx;
37
38 namespace mozilla {
39 namespace dom {
40
41 nsSVGElement::LengthInfo SVGViewportElement::sLengthInfo[4] = {
42 {&nsGkAtoms::x, 0, SVGLengthBinding::SVG_LENGTHTYPE_NUMBER,
43 SVGContentUtils::X},
44 {&nsGkAtoms::y, 0, SVGLengthBinding::SVG_LENGTHTYPE_NUMBER,
45 SVGContentUtils::Y},
46 {&nsGkAtoms::width, 100, SVGLengthBinding::SVG_LENGTHTYPE_PERCENTAGE,
47 SVGContentUtils::X},
48 {&nsGkAtoms::height, 100, SVGLengthBinding::SVG_LENGTHTYPE_PERCENTAGE,
49 SVGContentUtils::Y},
50 };
51
52 //----------------------------------------------------------------------
53 // Implementation
54
SVGViewportElement(already_AddRefed<mozilla::dom::NodeInfo> & aNodeInfo)55 SVGViewportElement::SVGViewportElement(
56 already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
57 : SVGGraphicsElement(aNodeInfo),
58 mViewportWidth(0),
59 mViewportHeight(0),
60 mHasChildrenOnlyTransform(false) {}
61
~SVGViewportElement()62 SVGViewportElement::~SVGViewportElement() {}
63
64 //----------------------------------------------------------------------
65
ViewBox()66 already_AddRefed<SVGAnimatedRect> SVGViewportElement::ViewBox() {
67 return mViewBox.ToSVGAnimatedRect(this);
68 }
69
70 already_AddRefed<DOMSVGAnimatedPreserveAspectRatio>
PreserveAspectRatio()71 SVGViewportElement::PreserveAspectRatio() {
72 return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this);
73 }
74
75 //----------------------------------------------------------------------
76 // nsIContent methods
77
NS_IMETHODIMP_(bool)78 NS_IMETHODIMP_(bool)
79 SVGViewportElement::IsAttributeMapped(const nsAtom* name) const {
80 // We want to map the 'width' and 'height' attributes into style for
81 // outer-<svg>, except when the attributes aren't set (since their default
82 // values of '100%' can cause unexpected and undesirable behaviour for SVG
83 // inline in HTML). We rely on nsSVGElement::UpdateContentStyleRule() to
84 // prevent mapping of the default values into style (it only maps attributes
85 // that are set). We also rely on a check in nsSVGElement::
86 // UpdateContentStyleRule() to prevent us mapping the attributes when they're
87 // given a <length> value that is not currently recognized by the SVG
88 // specification.
89
90 if (!IsInner() && (name == nsGkAtoms::width || name == nsGkAtoms::height)) {
91 return true;
92 }
93
94 static const MappedAttributeEntry* const map[] = {sColorMap,
95 sFEFloodMap,
96 sFillStrokeMap,
97 sFiltersMap,
98 sFontSpecificationMap,
99 sGradientStopMap,
100 sGraphicsMap,
101 sLightingEffectsMap,
102 sMarkersMap,
103 sTextContentElementsMap,
104 sViewportsMap};
105
106 return FindAttributeDependence(name, map) ||
107 SVGGraphicsElement::IsAttributeMapped(name);
108 }
109
110 //----------------------------------------------------------------------
111 // nsSVGElement overrides
112
113 // Helper for GetViewBoxTransform on root <svg> node
114 // * aLength: internal value for our <svg> width or height attribute.
115 // * aViewportLength: length of the corresponding dimension of the viewport.
116 // * aSelf: the outermost <svg> node itself.
117 // NOTE: aSelf is not an ancestor viewport element, so it can't be used to
118 // resolve percentage lengths. (It can only be used to resolve
119 // 'em'/'ex'-valued units).
ComputeSynthesizedViewBoxDimension(const nsSVGLength2 & aLength,float aViewportLength,const SVGViewportElement * aSelf)120 inline float ComputeSynthesizedViewBoxDimension(
121 const nsSVGLength2& aLength, float aViewportLength,
122 const SVGViewportElement* aSelf) {
123 if (aLength.IsPercentage()) {
124 return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f;
125 }
126
127 return aLength.GetAnimValue(const_cast<SVGViewportElement*>(aSelf));
128 }
129
130 //----------------------------------------------------------------------
131 // public helpers:
132
UpdateHasChildrenOnlyTransform()133 void SVGViewportElement::UpdateHasChildrenOnlyTransform() {
134 bool hasChildrenOnlyTransform =
135 HasViewBoxOrSyntheticViewBox() ||
136 (IsRoot() && (GetCurrentTranslate() != SVGPoint(0.0f, 0.0f) ||
137 GetCurrentScale() != 1.0f));
138 mHasChildrenOnlyTransform = hasChildrenOnlyTransform;
139 }
140
ChildrenOnlyTransformChanged(uint32_t aFlags)141 void SVGViewportElement::ChildrenOnlyTransformChanged(uint32_t aFlags) {
142 // Avoid wasteful calls:
143 MOZ_ASSERT(!(GetPrimaryFrame()->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
144 "Non-display SVG frames don't maintain overflow rects");
145
146 nsChangeHint changeHint;
147
148 bool hadChildrenOnlyTransform = mHasChildrenOnlyTransform;
149
150 UpdateHasChildrenOnlyTransform();
151
152 if (hadChildrenOnlyTransform != mHasChildrenOnlyTransform) {
153 // Reconstruct the frame tree to handle stacking context changes:
154 // XXXjwatt don't do this for root-<svg> or even outer-<svg>?
155 changeHint = nsChangeHint_ReconstructFrame;
156 } else {
157 // We just assume the old and new transforms are different.
158 changeHint = nsChangeHint(nsChangeHint_UpdateOverflow |
159 nsChangeHint_ChildrenOnlyTransform);
160 }
161
162 // If we're not reconstructing the frame tree, then we only call
163 // PostRestyleEvent if we're not being called under reflow to avoid recursing
164 // to death. See bug 767056 comments 10 and 12. Since our nsSVGOuterSVGFrame
165 // is being reflowed we're going to invalidate and repaint its entire area
166 // anyway (which will include our children).
167 if ((changeHint & nsChangeHint_ReconstructFrame) ||
168 !(aFlags & eDuringReflow)) {
169 nsLayoutUtils::PostRestyleEvent(this, nsRestyleHint(0), changeHint);
170 }
171 }
172
GetViewBoxTransform() const173 gfx::Matrix SVGViewportElement::GetViewBoxTransform() const {
174 float viewportWidth, viewportHeight;
175 if (IsInner()) {
176 SVGViewportElement* ctx = GetCtx();
177 viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
178 viewportHeight = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
179 } else {
180 viewportWidth = mViewportWidth;
181 viewportHeight = mViewportHeight;
182 }
183
184 if (viewportWidth <= 0.0f || viewportHeight <= 0.0f) {
185 return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
186 }
187
188 nsSVGViewBoxRect viewBox =
189 GetViewBoxWithSynthesis(viewportWidth, viewportHeight);
190
191 if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
192 return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
193 }
194
195 return SVGContentUtils::GetViewBoxTransform(
196 viewportWidth, viewportHeight, viewBox.x, viewBox.y, viewBox.width,
197 viewBox.height, GetPreserveAspectRatioWithOverride());
198 }
199 //----------------------------------------------------------------------
200 // SVGViewportElement
201
GetLength(uint8_t aCtxType)202 float SVGViewportElement::GetLength(uint8_t aCtxType) {
203 const nsSVGViewBoxRect* viewbox = GetViewBoxInternal().HasRect()
204 ? &GetViewBoxInternal().GetAnimValue()
205 : nullptr;
206
207 float h, w;
208 if (viewbox) {
209 w = viewbox->width;
210 h = viewbox->height;
211 } else if (IsInner()) {
212 SVGViewportElement* ctx = GetCtx();
213 w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
214 h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
215 } else if (ShouldSynthesizeViewBox()) {
216 w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
217 mViewportWidth, this);
218 h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
219 mViewportHeight, this);
220 } else {
221 w = mViewportWidth;
222 h = mViewportHeight;
223 }
224
225 w = std::max(w, 0.0f);
226 h = std::max(h, 0.0f);
227
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));
235 }
236 return 0;
237 }
238
239 //----------------------------------------------------------------------
240 // nsSVGElement methods
241
PrependLocalTransformsTo(const gfxMatrix & aMatrix,SVGTransformTypes aWhich) const242 /* virtual */ gfxMatrix SVGViewportElement::PrependLocalTransformsTo(
243 const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const {
244 // 'transform' attribute (or an override from a fragment identifier):
245 gfxMatrix userToParent;
246
247 if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) {
248 userToParent = GetUserToParentTransform(mAnimateMotionTransform,
249 GetTransformInternal());
250 if (aWhich == eUserSpaceToParent) {
251 return userToParent * aMatrix;
252 }
253 }
254
255 gfxMatrix childToUser;
256
257 if (IsInner()) {
258 float x, y;
259 const_cast<SVGViewportElement*>(this)->GetAnimatedLengthValues(&x, &y,
260 nullptr);
261 childToUser = ThebesMatrix(GetViewBoxTransform().PostTranslate(x, y));
262 } else if (IsRoot()) {
263 SVGPoint translate = GetCurrentTranslate();
264 float scale = GetCurrentScale();
265 childToUser =
266 ThebesMatrix(GetViewBoxTransform()
267 .PostScale(scale, scale)
268 .PostTranslate(translate.GetX(), translate.GetY()));
269 } else {
270 // outer-<svg>, but inline in some other content:
271 childToUser = ThebesMatrix(GetViewBoxTransform());
272 }
273
274 if (aWhich == eAllTransforms) {
275 return childToUser * userToParent * aMatrix;
276 }
277
278 MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes");
279
280 // The following may look broken because pre-multiplying our eChildToUserSpace
281 // transform with another matrix without including our eUserSpaceToParent
282 // transform between the two wouldn't make sense. We don't expect that to
283 // ever happen though. We get here either when the identity matrix has been
284 // passed because our caller just wants our eChildToUserSpace transform, or
285 // when our eUserSpaceToParent transform has already been multiplied into the
286 // matrix that our caller passes (such as when we're called from PaintSVG).
287 return childToUser * aMatrix;
288 }
289
HasValidDimensions() const290 /* virtual */ bool SVGViewportElement::HasValidDimensions() const {
291 return !IsInner() ||
292 ((!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
293 mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
294 (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
295 mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0));
296 }
297
GetViewBox()298 nsSVGViewBox* SVGViewportElement::GetViewBox() { return &mViewBox; }
299
GetPreserveAspectRatio()300 SVGAnimatedPreserveAspectRatio* SVGViewportElement::GetPreserveAspectRatio() {
301 return &mPreserveAspectRatio;
302 }
303
ShouldSynthesizeViewBox() const304 bool SVGViewportElement::ShouldSynthesizeViewBox() const {
305 MOZ_ASSERT(!HasViewBoxRect(), "Should only be called if we lack a viewBox");
306
307 return IsRoot() && OwnerDoc()->IsBeingUsedAsImage();
308 }
309
310 //----------------------------------------------------------------------
311 // implementation helpers
312
GetViewBoxWithSynthesis(float aViewportWidth,float aViewportHeight) const313 nsSVGViewBoxRect SVGViewportElement::GetViewBoxWithSynthesis(
314 float aViewportWidth, float aViewportHeight) const {
315 if (GetViewBoxInternal().HasRect()) {
316 return GetViewBoxInternal().GetAnimValue();
317 }
318
319 if (ShouldSynthesizeViewBox()) {
320 // Special case -- fake a viewBox, using height & width attrs.
321 // (Use |this| as context, since if we get here, we're outermost <svg>.)
322 return nsSVGViewBoxRect(
323 0, 0,
324 ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
325 mViewportWidth, this),
326 ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
327 mViewportHeight, this));
328 }
329
330 // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
331 // to having a viewBox that exactly matches our viewport size.
332 return nsSVGViewBoxRect(0, 0, aViewportWidth, aViewportHeight);
333 }
334
GetLengthInfo()335 nsSVGElement::LengthAttributesInfo SVGViewportElement::GetLengthInfo() {
336 return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
337 ArrayLength(sLengthInfo));
338 }
339
340 } // namespace dom
341 } // namespace mozilla
342