Bug 1941128 - Turn off network.dns.native_https_query on Mac again
[gecko.git] / dom / svg / SVGMotionSMILAnimationFunction.cpp
blob1f9abeb84cac7896daa002b09f7a2b780de2def6
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 "SVGMotionSMILAnimationFunction.h"
9 #include "mozilla/dom/SVGAnimationElement.h"
10 #include "mozilla/dom/SVGPathElement.h"
11 #include "mozilla/dom/SVGMPathElement.h"
12 #include "mozilla/gfx/2D.h"
13 #include "mozilla/SMILParserUtils.h"
14 #include "nsAttrValue.h"
15 #include "nsAttrValueInlines.h"
16 #include "SVGAnimatedOrient.h"
17 #include "SVGMotionSMILPathUtils.h"
18 #include "SVGMotionSMILType.h"
20 using namespace mozilla::dom;
21 using namespace mozilla::dom::SVGAngle_Binding;
22 using namespace mozilla::gfx;
24 namespace mozilla {
26 SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction()
27 : mRotateType(eRotateType_Explicit),
28 mRotateAngle(0.0f),
29 mPathSourceType(ePathSourceType_None),
30 mIsPathStale(true) // Try to initialize path on first GetValues call
33 void SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath(
34 nsAtom* aAttribute) {
35 bool isAffected;
36 if (aAttribute == nsGkAtoms::path) {
37 isAffected = (mPathSourceType <= ePathSourceType_PathAttr);
38 } else if (aAttribute == nsGkAtoms::values) {
39 isAffected = (mPathSourceType <= ePathSourceType_ValuesAttr);
40 } else if (aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to) {
41 isAffected = (mPathSourceType <= ePathSourceType_ToAttr);
42 } else if (aAttribute == nsGkAtoms::by) {
43 isAffected = (mPathSourceType <= ePathSourceType_ByAttr);
44 } else {
45 MOZ_ASSERT_UNREACHABLE(
46 "Should only call this method for path-describing "
47 "attrs");
48 isAffected = false;
51 if (isAffected) {
52 mIsPathStale = true;
53 mHasChanged = true;
57 bool SVGMotionSMILAnimationFunction::SetAttr(nsAtom* aAttribute,
58 const nsAString& aValue,
59 nsAttrValue& aResult,
60 nsresult* aParseResult) {
61 // Handle motion-specific attrs
62 if (aAttribute == nsGkAtoms::keyPoints) {
63 nsresult rv = SetKeyPoints(aValue, aResult);
64 if (aParseResult) {
65 *aParseResult = rv;
67 } else if (aAttribute == nsGkAtoms::rotate) {
68 nsresult rv = SetRotate(aValue, aResult);
69 if (aParseResult) {
70 *aParseResult = rv;
72 } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by ||
73 aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to ||
74 aAttribute == nsGkAtoms::values) {
75 aResult.SetTo(aValue);
76 MarkStaleIfAttributeAffectsPath(aAttribute);
77 if (aParseResult) {
78 *aParseResult = NS_OK;
80 } else {
81 // Defer to superclass method
82 return SMILAnimationFunction::SetAttr(aAttribute, aValue, aResult,
83 aParseResult);
86 return true;
89 bool SVGMotionSMILAnimationFunction::UnsetAttr(nsAtom* aAttribute) {
90 if (aAttribute == nsGkAtoms::keyPoints) {
91 UnsetKeyPoints();
92 } else if (aAttribute == nsGkAtoms::rotate) {
93 UnsetRotate();
94 } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by ||
95 aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to ||
96 aAttribute == nsGkAtoms::values) {
97 MarkStaleIfAttributeAffectsPath(aAttribute);
98 } else {
99 // Defer to superclass method
100 return SMILAnimationFunction::UnsetAttr(aAttribute);
103 return true;
106 SMILAnimationFunction::SMILCalcMode
107 SVGMotionSMILAnimationFunction::GetCalcMode() const {
108 const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
109 if (!value) {
110 return CALC_PACED; // animateMotion defaults to calcMode="paced"
113 return SMILCalcMode(value->GetEnumValue());
116 //----------------------------------------------------------------------
117 // Helpers for GetValues
120 * Returns the first <mpath> child of the given element
123 static SVGMPathElement* GetFirstMPathChild(nsIContent* aElem) {
124 for (nsIContent* child = aElem->GetFirstChild(); child;
125 child = child->GetNextSibling()) {
126 if (child->IsSVGElement(nsGkAtoms::mpath)) {
127 return static_cast<SVGMPathElement*>(child);
131 return nullptr;
134 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromBasicAttrs(
135 const nsIContent* aContextElem) {
136 MOZ_ASSERT(!HasAttr(nsGkAtoms::path),
137 "Should be using |path| attr if we have it");
138 MOZ_ASSERT(!mPath, "regenerating when we already have path");
139 MOZ_ASSERT(mPathVertices.IsEmpty(),
140 "regenerating when we already have vertices");
142 const auto* context = SVGElement::FromNode(aContextElem);
143 if (!context) {
144 NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node");
145 return;
147 SVGMotionSMILPathUtils::PathGenerator pathGenerator(context);
149 bool success = false;
150 if (HasAttr(nsGkAtoms::values)) {
151 // Generate path based on our values array
152 mPathSourceType = ePathSourceType_ValuesAttr;
153 const nsAString& valuesStr = GetAttr(nsGkAtoms::values)->GetStringValue();
154 SVGMotionSMILPathUtils::MotionValueParser parser(&pathGenerator,
155 &mPathVertices);
156 success = SMILParserUtils::ParseValuesGeneric(valuesStr, parser);
157 } else if (HasAttr(nsGkAtoms::to) || HasAttr(nsGkAtoms::by)) {
158 // Apply 'from' value (or a dummy 0,0 'from' value)
159 if (HasAttr(nsGkAtoms::from)) {
160 const nsAString& fromStr = GetAttr(nsGkAtoms::from)->GetStringValue();
161 success = pathGenerator.MoveToAbsolute(fromStr);
162 if (!mPathVertices.AppendElement(0.0, fallible)) {
163 success = false;
165 } else {
166 // Create dummy 'from' value at 0,0, if we're doing by-animation.
167 // (NOTE: We don't add the dummy 0-point to our list for *to-animation*,
168 // because the SMILAnimationFunction logic for to-animation doesn't
169 // expect a dummy value. It only expects one value: the final 'to' value.)
170 pathGenerator.MoveToOrigin();
171 success = true;
172 if (!HasAttr(nsGkAtoms::to)) {
173 if (!mPathVertices.AppendElement(0.0, fallible)) {
174 success = false;
179 // Apply 'to' or 'by' value
180 if (success) {
181 double dist;
182 if (HasAttr(nsGkAtoms::to)) {
183 mPathSourceType = ePathSourceType_ToAttr;
184 const nsAString& toStr = GetAttr(nsGkAtoms::to)->GetStringValue();
185 success = pathGenerator.LineToAbsolute(toStr, dist);
186 } else { // HasAttr(nsGkAtoms::by)
187 mPathSourceType = ePathSourceType_ByAttr;
188 const nsAString& byStr = GetAttr(nsGkAtoms::by)->GetStringValue();
189 success = pathGenerator.LineToRelative(byStr, dist);
191 if (success) {
192 if (!mPathVertices.AppendElement(dist, fallible)) {
193 success = false;
198 if (success) {
199 mPath = pathGenerator.GetResultingPath();
200 } else {
201 // Parse failure. Leave path as null, and clear path-related member data.
202 mPathVertices.Clear();
206 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromMpathElem(
207 SVGMPathElement* aMpathElem) {
208 mPathSourceType = ePathSourceType_Mpath;
210 // Use the shape that's the target of our chosen <mpath> child.
211 SVGGeometryElement* shapeElem = aMpathElem->GetReferencedPath();
212 if (shapeElem && shapeElem->HasValidDimensions()) {
213 bool ok = shapeElem->GetDistancesFromOriginToEndsOfVisibleSegments(
214 &mPathVertices);
215 if (!ok) {
216 mPathVertices.Clear();
217 return;
219 if (mPathVertices.Length()) {
220 mPath = shapeElem->GetOrBuildPathForMeasuring();
225 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() {
226 const nsAString& pathSpec = GetAttr(nsGkAtoms::path)->GetStringValue();
227 mPathSourceType = ePathSourceType_PathAttr;
229 // Generate Path from |path| attr
230 SVGPathData path{NS_ConvertUTF16toUTF8(pathSpec)};
232 // We must explicitly check that the parse produces at least one path segment
233 // (if the path data doesn't begin with a valid "M", then it's invalid).
234 if (path.IsEmpty()) {
235 return;
238 mPath = path.BuildPathForMeasuring(1.0f);
239 bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
240 if (!ok || !mPathVertices.Length()) {
241 mPath = nullptr;
242 mPathVertices.Clear();
246 // Helper to regenerate our path representation & its list of vertices
247 void SVGMotionSMILAnimationFunction::RebuildPathAndVertices(
248 const nsIContent* aTargetElement) {
249 MOZ_ASSERT(mIsPathStale, "rebuilding path when it isn't stale");
251 // Clear stale data
252 mPath = nullptr;
253 mPathVertices.Clear();
254 mPathSourceType = ePathSourceType_None;
256 // Do we have a mpath child? if so, it trumps everything. Otherwise, we look
257 // through our list of path-defining attributes, in order of priority.
258 SVGMPathElement* firstMpathChild = GetFirstMPathChild(mAnimationElement);
260 if (firstMpathChild) {
261 RebuildPathAndVerticesFromMpathElem(firstMpathChild);
262 mValueNeedsReparsingEverySample = false;
263 } else if (HasAttr(nsGkAtoms::path)) {
264 RebuildPathAndVerticesFromPathAttr();
265 mValueNeedsReparsingEverySample = false;
266 } else {
267 // Get path & vertices from basic SMIL attrs: from/by/to/values
269 RebuildPathAndVerticesFromBasicAttrs(aTargetElement);
270 mValueNeedsReparsingEverySample = true;
272 mIsPathStale = false;
275 bool SVGMotionSMILAnimationFunction::GenerateValuesForPathAndPoints(
276 Path* aPath, bool aIsKeyPoints, FallibleTArray<double>& aPointDistances,
277 SMILValueArray& aResult) {
278 MOZ_ASSERT(aResult.IsEmpty(), "outparam is non-empty");
280 // If we're using "keyPoints" as our list of input distances, then we need
281 // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale.
282 double distanceMultiplier = aIsKeyPoints ? aPath->ComputeLength() : 1.0;
283 const uint32_t numPoints = aPointDistances.Length();
284 for (uint32_t i = 0; i < numPoints; ++i) {
285 double curDist = aPointDistances[i] * distanceMultiplier;
286 if (!aResult.AppendElement(SVGMotionSMILType::ConstructSMILValue(
287 aPath, curDist, mRotateType, mRotateAngle),
288 fallible)) {
289 return false;
292 return true;
295 nsresult SVGMotionSMILAnimationFunction::GetValues(const SMILAttr& aSMILAttr,
296 SMILValueArray& aResult) {
297 if (mIsPathStale) {
298 RebuildPathAndVertices(aSMILAttr.GetTargetNode());
300 MOZ_ASSERT(!mIsPathStale, "Forgot to clear 'is path stale' state");
302 if (!mPath) {
303 // This could be due to e.g. a parse error.
304 MOZ_ASSERT(mPathVertices.IsEmpty(), "have vertices but no path");
305 return NS_ERROR_FAILURE;
307 MOZ_ASSERT(!mPathVertices.IsEmpty(), "have a path but no vertices");
309 // Now: Make the actual list of SMILValues (using keyPoints, if set)
310 bool isUsingKeyPoints = !mKeyPoints.IsEmpty();
311 bool success = GenerateValuesForPathAndPoints(
312 mPath, isUsingKeyPoints, isUsingKeyPoints ? mKeyPoints : mPathVertices,
313 aResult);
314 if (!success) {
315 return NS_ERROR_OUT_OF_MEMORY;
318 return NS_OK;
321 void SVGMotionSMILAnimationFunction::CheckValueListDependentAttrs(
322 uint32_t aNumValues) {
323 // Call superclass method.
324 SMILAnimationFunction::CheckValueListDependentAttrs(aNumValues);
326 // Added behavior: Do checks specific to keyPoints.
327 CheckKeyPoints();
330 bool SVGMotionSMILAnimationFunction::IsToAnimation() const {
331 // Rely on inherited method, but not if we have an <mpath> child or a |path|
332 // attribute, because they'll override any 'to' attr we might have.
333 // NOTE: We can't rely on mPathSourceType, because it might not have been
334 // set to a useful value yet (or it might be stale).
335 return !GetFirstMPathChild(mAnimationElement) && !HasAttr(nsGkAtoms::path) &&
336 SMILAnimationFunction::IsToAnimation();
339 void SVGMotionSMILAnimationFunction::CheckKeyPoints() {
340 if (!HasAttr(nsGkAtoms::keyPoints)) return;
342 // attribute is ignored for calcMode="paced" (even if it's got errors)
343 if (GetCalcMode() == CALC_PACED) {
344 SetKeyPointsErrorFlag(false);
347 if (mKeyPoints.Length() != mKeyTimes.Length()) {
348 // there must be exactly as many keyPoints as keyTimes
349 SetKeyPointsErrorFlag(true);
350 return;
353 // Nothing else to check -- we can catch all keyPoints errors elsewhere.
354 // - Formatting & range issues will be caught in SetKeyPoints, and will
355 // result in an empty mKeyPoints array, which will drop us into the error
356 // case above.
359 nsresult SVGMotionSMILAnimationFunction::SetKeyPoints(
360 const nsAString& aKeyPoints, nsAttrValue& aResult) {
361 mKeyPoints.Clear();
362 aResult.SetTo(aKeyPoints);
364 mHasChanged = true;
366 if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints, false,
367 mKeyPoints)) {
368 mKeyPoints.Clear();
369 return NS_ERROR_FAILURE;
372 return NS_OK;
375 void SVGMotionSMILAnimationFunction::UnsetKeyPoints() {
376 mKeyPoints.Clear();
377 SetKeyPointsErrorFlag(false);
378 mHasChanged = true;
381 nsresult SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate,
382 nsAttrValue& aResult) {
383 mHasChanged = true;
385 aResult.SetTo(aRotate);
386 if (aRotate.EqualsLiteral("auto")) {
387 mRotateType = eRotateType_Auto;
388 } else if (aRotate.EqualsLiteral("auto-reverse")) {
389 mRotateType = eRotateType_AutoReverse;
390 } else {
391 mRotateType = eRotateType_Explicit;
393 uint16_t angleUnit;
394 if (!SVGAnimatedOrient::GetValueFromString(aRotate, mRotateAngle,
395 &angleUnit)) {
396 mRotateAngle = 0.0f; // set default rotate angle
397 // XXX report to console?
398 return NS_ERROR_DOM_SYNTAX_ERR;
401 // Convert to radian units, if we're not already in radians.
402 if (angleUnit != SVG_ANGLETYPE_RAD) {
403 mRotateAngle *= SVGAnimatedOrient::GetDegreesPerUnit(angleUnit) /
404 SVGAnimatedOrient::GetDegreesPerUnit(SVG_ANGLETYPE_RAD);
407 return NS_OK;
410 void SVGMotionSMILAnimationFunction::UnsetRotate() {
411 mRotateAngle = 0.0f; // default value
412 mRotateType = eRotateType_Explicit;
413 mHasChanged = true;
416 } // namespace mozilla