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