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/Likely.h"
15 #include "mozilla/SMILTypes.h"
16 #include "mozilla/SVGContentUtils.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/dom/SVGLengthBinding.h"
19 #include "mozilla/dom/SVGViewElement.h"
21 #include "DOMSVGLength.h"
22 #include "DOMSVGPoint.h"
23 #include "nsContentUtils.h"
24 #include "nsFrameSelection.h"
26 #include "nsGkAtoms.h"
28 #include "nsLayoutUtils.h"
29 #include "nsStyleUtil.h"
34 using namespace mozilla::gfx
;
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 bool SVGViewportElement::IsNodeOfType(uint32_t aFlags
) const {
72 return !(aFlags
& ~eUSE_TARGET
);
75 //----------------------------------------------------------------------
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 SVGElement::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 SVGElement::
86 // UpdateContentStyleRule() to prevent us mapping the attributes when they're
87 // given a <length> value that is not currently recognized by the SVG
90 if (!IsInner() && (name
== nsGkAtoms::width
|| name
== nsGkAtoms::height
)) {
94 static const MappedAttributeEntry
* const map
[] = {sColorMap
,
98 sFontSpecificationMap
,
103 sTextContentElementsMap
,
106 return FindAttributeDependence(name
, map
) ||
107 SVGGraphicsElement::IsAttributeMapped(name
);
110 //----------------------------------------------------------------------
111 // SVGElement overrides
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).
120 inline float ComputeSynthesizedViewBoxDimension(
121 const SVGAnimatedLength
& aLength
, float aViewportLength
,
122 const SVGViewportElement
* aSelf
) {
123 if (aLength
.IsPercentage()) {
124 return aViewportLength
* aLength
.GetAnimValInSpecifiedUnits() / 100.0f
;
127 return aLength
.GetAnimValue(const_cast<SVGViewportElement
*>(aSelf
));
130 //----------------------------------------------------------------------
133 void SVGViewportElement::UpdateHasChildrenOnlyTransform() {
134 bool hasChildrenOnlyTransform
=
135 HasViewBoxOrSyntheticViewBox() ||
136 (IsRootSVGSVGElement() &&
137 static_cast<SVGSVGElement
*>(this)->IsScaledOrTranslated());
138 mHasChildrenOnlyTransform
= hasChildrenOnlyTransform
;
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");
146 nsChangeHint changeHint
;
148 bool hadChildrenOnlyTransform
= mHasChildrenOnlyTransform
;
150 UpdateHasChildrenOnlyTransform();
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
;
157 // We just assume the old and new transforms are different.
158 changeHint
= nsChangeHint(nsChangeHint_UpdateOverflow
|
159 nsChangeHint_ChildrenOnlyTransform
);
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 SVGOuterSVGFrame
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, RestyleHint
{0}, changeHint
);
173 gfx::Matrix
SVGViewportElement::GetViewBoxTransform() const {
174 float viewportWidth
, viewportHeight
;
176 SVGElement
* self
= const_cast<SVGViewportElement
*>(this);
177 viewportWidth
= mLengthAttributes
[ATTR_WIDTH
].GetAnimValue(self
);
178 viewportHeight
= mLengthAttributes
[ATTR_HEIGHT
].GetAnimValue(self
);
180 viewportWidth
= mViewportWidth
;
181 viewportHeight
= mViewportHeight
;
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
188 SVGViewBox viewBox
= GetViewBoxWithSynthesis(viewportWidth
, viewportHeight
);
190 if (viewBox
.width
<= 0.0f
|| viewBox
.height
<= 0.0f
) {
191 return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
194 return SVGContentUtils::GetViewBoxTransform(
195 viewportWidth
, viewportHeight
, viewBox
.x
, viewBox
.y
, viewBox
.width
,
196 viewBox
.height
, GetPreserveAspectRatioWithOverride());
198 //----------------------------------------------------------------------
199 // SVGViewportElement
201 float SVGViewportElement::GetLength(uint8_t aCtxType
) {
202 const SVGViewBox
* viewbox
= GetViewBoxInternal().HasRect()
203 ? &GetViewBoxInternal().GetAnimValue()
206 float h
= 0.0f
, w
= 0.0f
;
207 bool shouldComputeWidth
=
208 (aCtxType
== SVGContentUtils::X
|| aCtxType
== SVGContentUtils::XY
),
209 shouldComputeHeight
=
210 (aCtxType
== SVGContentUtils::Y
|| aCtxType
== SVGContentUtils::XY
);
215 } else if (IsInner()) {
216 // Resolving length for inner <svg> is exactly the same as other
217 // ordinary element. We shouldn't use the SVGViewportElement overload
218 // of GetAnimValue().
219 SVGElement
* self
= this;
220 if (shouldComputeWidth
) {
221 w
= mLengthAttributes
[ATTR_WIDTH
].GetAnimValue(self
);
223 if (shouldComputeHeight
) {
224 h
= mLengthAttributes
[ATTR_HEIGHT
].GetAnimValue(self
);
226 } else if (ShouldSynthesizeViewBox()) {
227 if (shouldComputeWidth
) {
228 w
= ComputeSynthesizedViewBoxDimension(mLengthAttributes
[ATTR_WIDTH
],
229 mViewportWidth
, this);
231 if (shouldComputeHeight
) {
232 h
= ComputeSynthesizedViewBoxDimension(mLengthAttributes
[ATTR_HEIGHT
],
233 mViewportHeight
, this);
240 w
= std::max(w
, 0.0f
);
241 h
= std::max(h
, 0.0f
);
244 case SVGContentUtils::X
:
246 case SVGContentUtils::Y
:
248 case SVGContentUtils::XY
:
249 return float(SVGContentUtils::ComputeNormalizedHypotenuse(w
, h
));
254 //----------------------------------------------------------------------
255 // SVGElement methods
258 gfxMatrix
SVGViewportElement::PrependLocalTransformsTo(
259 const gfxMatrix
& aMatrix
, SVGTransformTypes aWhich
) const {
260 // 'transform' attribute (or an override from a fragment identifier):
261 gfxMatrix userToParent
;
263 if (aWhich
== eUserSpaceToParent
|| aWhich
== eAllTransforms
) {
264 userToParent
= GetUserToParentTransform(mAnimateMotionTransform
.get(),
265 GetTransformInternal());
266 if (aWhich
== eUserSpaceToParent
) {
267 return userToParent
* aMatrix
;
271 gfxMatrix childToUser
;
275 const_cast<SVGViewportElement
*>(this)->GetAnimatedLengthValues(&x
, &y
,
277 childToUser
= ThebesMatrix(GetViewBoxTransform().PostTranslate(x
, y
));
278 } else if (IsRootSVGSVGElement()) {
279 const SVGSVGElement
* svg
= static_cast<const SVGSVGElement
*>(this);
280 const SVGPoint
& translate
= svg
->GetCurrentTranslate();
281 float scale
= svg
->CurrentScale();
283 ThebesMatrix(GetViewBoxTransform()
284 .PostScale(scale
, scale
)
285 .PostTranslate(translate
.GetX(), translate
.GetY()));
287 // outer-<svg>, but inline in some other content:
288 childToUser
= ThebesMatrix(GetViewBoxTransform());
291 if (aWhich
== eAllTransforms
) {
292 return childToUser
* userToParent
* aMatrix
;
295 MOZ_ASSERT(aWhich
== eChildToUserSpace
, "Unknown TransformTypes");
297 // The following may look broken because pre-multiplying our eChildToUserSpace
298 // transform with another matrix without including our eUserSpaceToParent
299 // transform between the two wouldn't make sense. We don't expect that to
300 // ever happen though. We get here either when the identity matrix has been
301 // passed because our caller just wants our eChildToUserSpace transform, or
302 // when our eUserSpaceToParent transform has already been multiplied into the
303 // matrix that our caller passes (such as when we're called from PaintSVG).
304 return childToUser
* aMatrix
;
308 bool SVGViewportElement::HasValidDimensions() const {
310 ((!mLengthAttributes
[ATTR_WIDTH
].IsExplicitlySet() ||
311 mLengthAttributes
[ATTR_WIDTH
].GetAnimValInSpecifiedUnits() > 0) &&
312 (!mLengthAttributes
[ATTR_HEIGHT
].IsExplicitlySet() ||
313 mLengthAttributes
[ATTR_HEIGHT
].GetAnimValInSpecifiedUnits() > 0));
316 SVGAnimatedViewBox
* SVGViewportElement::GetAnimatedViewBox() {
320 SVGAnimatedPreserveAspectRatio
*
321 SVGViewportElement::GetAnimatedPreserveAspectRatio() {
322 return &mPreserveAspectRatio
;
325 bool SVGViewportElement::ShouldSynthesizeViewBox() const {
326 MOZ_ASSERT(!HasViewBox(), "Should only be called if we lack a viewBox");
328 return IsRootSVGSVGElement() && OwnerDoc()->IsBeingUsedAsImage();
331 //----------------------------------------------------------------------
332 // implementation helpers
334 SVGViewBox
SVGViewportElement::GetViewBoxWithSynthesis(
335 float aViewportWidth
, float aViewportHeight
) const {
336 if (GetViewBoxInternal().HasRect()) {
337 return GetViewBoxInternal().GetAnimValue();
340 if (ShouldSynthesizeViewBox()) {
341 // Special case -- fake a viewBox, using height & width attrs.
342 // (Use |this| as context, since if we get here, we're outermost <svg>.)
345 ComputeSynthesizedViewBoxDimension(mLengthAttributes
[ATTR_WIDTH
],
346 mViewportWidth
, this),
347 ComputeSynthesizedViewBoxDimension(mLengthAttributes
[ATTR_HEIGHT
],
348 mViewportHeight
, this));
351 // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
352 // to having a viewBox that exactly matches our viewport size.
353 return SVGViewBox(0, 0, aViewportWidth
, aViewportHeight
);
356 SVGElement::LengthAttributesInfo
SVGViewportElement::GetLengthInfo() {
357 return LengthAttributesInfo(mLengthAttributes
, sLengthInfo
,
358 ArrayLength(sLengthInfo
));
362 } // namespace mozilla