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"
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"
27 #include "nsGkAtoms.h"
29 #include "nsLayoutUtils.h"
30 #include "nsStyleUtil.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
,
42 {nsGkAtoms::y
, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
,
44 {nsGkAtoms::width
, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE
,
46 {nsGkAtoms::height
, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE
,
50 //----------------------------------------------------------------------
53 SVGViewportElement::SVGViewportElement(
54 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
55 : SVGGraphicsElement(std::move(aNodeInfo
)),
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 //----------------------------------------------------------------------
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
86 if (!IsInner() && (name
== nsGkAtoms::width
|| name
== nsGkAtoms::height
)) {
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 //----------------------------------------------------------------------
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
;
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
;
159 SVGElementMetrics
metrics(this);
160 viewportWidth
= mLengthAttributes
[ATTR_WIDTH
].GetAnimValue(metrics
);
161 viewportHeight
= mLengthAttributes
[ATTR_HEIGHT
].GetAnimValue(metrics
);
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()
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
);
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);
225 w
= std::max(w
, 0.0f
);
226 h
= std::max(h
, 0.0f
);
229 case SVGContentUtils::X
:
231 case SVGContentUtils::Y
:
233 case SVGContentUtils::XY
:
234 return float(SVGContentUtils::ComputeNormalizedHypotenuse(w
, h
));
239 //----------------------------------------------------------------------
240 // SVGElement methods
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
;
260 const_cast<SVGViewportElement
*>(this)->GetAnimatedLengthValues(&x
, &y
,
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();
268 ThebesMatrix(GetViewBoxTransform()
269 .PostScale(scale
, scale
)
270 .PostTranslate(translate
.GetX(), translate
.GetY()));
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
;
293 bool SVGViewportElement::HasValidDimensions() const {
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() {
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>.)
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