Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/svg/SVGAnimationElement.cpp
Line
Count
Source (jump to first uncovered line)
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 "mozilla/dom/SVGAnimationElement.h"
8
#include "mozilla/dom/SVGSVGElement.h"
9
#include "mozilla/dom/ElementInlines.h"
10
#include "nsSMILTimeContainer.h"
11
#include "nsSMILAnimationController.h"
12
#include "nsSMILAnimationFunction.h"
13
#include "nsContentUtils.h"
14
#include "nsIContentInlines.h"
15
#include "nsIURI.h"
16
#include "prtime.h"
17
18
namespace mozilla {
19
namespace dom {
20
21
//----------------------------------------------------------------------
22
// nsISupports methods
23
24
NS_IMPL_ADDREF_INHERITED(SVGAnimationElement, SVGAnimationElementBase)
25
NS_IMPL_RELEASE_INHERITED(SVGAnimationElement, SVGAnimationElementBase)
26
27
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAnimationElement)
28
0
  NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests)
29
0
NS_INTERFACE_MAP_END_INHERITING(SVGAnimationElementBase)
30
31
NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAnimationElement,
32
                                   SVGAnimationElementBase,
33
                                   mHrefTarget, mTimedElement)
34
35
//----------------------------------------------------------------------
36
// Implementation
37
38
SVGAnimationElement::SVGAnimationElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
39
  : SVGAnimationElementBase(std::move(aNodeInfo)),
40
    mHrefTarget(this)
41
0
{
42
0
}
43
44
SVGAnimationElement::~SVGAnimationElement()
45
0
{
46
0
}
47
48
nsresult
49
SVGAnimationElement::Init()
50
0
{
51
0
  nsresult rv = SVGAnimationElementBase::Init();
52
0
  NS_ENSURE_SUCCESS(rv, rv);
53
0
54
0
  mTimedElement.SetAnimationElement(this);
55
0
  AnimationFunction().SetAnimationElement(this);
56
0
  mTimedElement.SetTimeClient(&AnimationFunction());
57
0
58
0
  return NS_OK;
59
0
}
60
61
//----------------------------------------------------------------------
62
63
Element*
64
SVGAnimationElement::GetTargetElementContent()
65
0
{
66
0
  if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) ||
67
0
      HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
68
0
    return mHrefTarget.get();
69
0
  }
70
0
  MOZ_ASSERT(!mHrefTarget.get(),
71
0
             "We shouldn't have a href target "
72
0
             "if we don't have an xlink:href or href attribute");
73
0
74
0
  // No "href" or "xlink:href" attribute --> I should target my parent.
75
0
  //
76
0
  // Note that we want to use GetParentElement instead of the flattened tree to
77
0
  // allow <use><animate>, for example.
78
0
  return GetParentElement();
79
0
}
80
81
bool
82
SVGAnimationElement::GetTargetAttributeName(int32_t *aNamespaceID,
83
                                            nsAtom **aLocalName) const
84
0
{
85
0
  const nsAttrValue* nameAttr = mAttrs.GetAttr(nsGkAtoms::attributeName);
86
0
87
0
  if (!nameAttr)
88
0
    return false;
89
0
90
0
  NS_ASSERTION(nameAttr->Type() == nsAttrValue::eAtom,
91
0
    "attributeName should have been parsed as an atom");
92
0
93
0
  return NS_SUCCEEDED(nsContentUtils::SplitQName(
94
0
                        this, nsDependentAtomString(nameAttr->GetAtomValue()),
95
0
                        aNamespaceID, aLocalName));
96
0
}
97
98
nsSMILTimedElement&
99
SVGAnimationElement::TimedElement()
100
0
{
101
0
  return mTimedElement;
102
0
}
103
104
nsSVGElement*
105
SVGAnimationElement::GetTargetElement()
106
0
{
107
0
  FlushAnimations();
108
0
109
0
  // We'll just call the other GetTargetElement method, and QI to the right type
110
0
  nsIContent* target = GetTargetElementContent();
111
0
112
0
  return (target && target->IsSVGElement())
113
0
           ? static_cast<nsSVGElement*>(target) : nullptr;
114
0
}
115
116
float
117
SVGAnimationElement::GetStartTime(ErrorResult& rv)
118
0
{
119
0
  FlushAnimations();
120
0
121
0
  nsSMILTimeValue startTime = mTimedElement.GetStartTime();
122
0
  if (!startTime.IsDefinite()) {
123
0
    rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
124
0
    return 0.f;
125
0
  }
126
0
127
0
  return float(double(startTime.GetMillis()) / PR_MSEC_PER_SEC);
128
0
}
129
130
float
131
SVGAnimationElement::GetCurrentTime()
132
0
{
133
0
  // Not necessary to call FlushAnimations() for this
134
0
135
0
  nsSMILTimeContainer* root = GetTimeContainer();
136
0
  if (root) {
137
0
    return float(double(root->GetCurrentTime()) / PR_MSEC_PER_SEC);
138
0
  }
139
0
140
0
  return 0.0f;
141
0
}
142
143
float
144
SVGAnimationElement::GetSimpleDuration(ErrorResult& rv)
145
0
{
146
0
  // Not necessary to call FlushAnimations() for this
147
0
148
0
  nsSMILTimeValue simpleDur = mTimedElement.GetSimpleDuration();
149
0
  if (!simpleDur.IsDefinite()) {
150
0
    rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
151
0
    return 0.f;
152
0
  }
153
0
154
0
  return float(double(simpleDur.GetMillis()) / PR_MSEC_PER_SEC);
155
0
}
156
157
//----------------------------------------------------------------------
158
// nsIContent methods
159
160
nsresult
161
SVGAnimationElement::BindToTree(nsIDocument* aDocument,
162
                                nsIContent* aParent,
163
                                nsIContent* aBindingParent)
164
0
{
165
0
  MOZ_ASSERT(!mHrefTarget.get(),
166
0
             "Shouldn't have href-target yet (or it should've been cleared)");
167
0
  nsresult rv = SVGAnimationElementBase::BindToTree(aDocument, aParent,
168
0
                                                    aBindingParent);
169
0
  NS_ENSURE_SUCCESS(rv,rv);
170
0
171
0
  // Add myself to the animation controller's master set of animation elements.
172
0
  if (nsIDocument* doc = GetComposedDoc()) {
173
0
    nsSMILAnimationController* controller = doc->GetAnimationController();
174
0
    if (controller) {
175
0
      controller->RegisterAnimationElement(this);
176
0
    }
177
0
    const nsAttrValue* href =
178
0
      HasAttr(kNameSpaceID_None, nsGkAtoms::href)
179
0
        ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None)
180
0
        : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
181
0
    if (href) {
182
0
      nsAutoString hrefStr;
183
0
      href->ToString(hrefStr);
184
0
185
0
      UpdateHrefTarget(hrefStr);
186
0
    }
187
0
188
0
    mTimedElement.BindToTree(*this);
189
0
  }
190
0
191
0
  AnimationNeedsResample();
192
0
193
0
  return NS_OK;
194
0
}
195
196
void
197
SVGAnimationElement::UnbindFromTree(bool aDeep, bool aNullParent)
198
0
{
199
0
  nsSMILAnimationController* controller = OwnerDoc()->GetAnimationController();
200
0
  if (controller) {
201
0
    controller->UnregisterAnimationElement(this);
202
0
  }
203
0
204
0
  mHrefTarget.Unlink();
205
0
  mTimedElement.DissolveReferences();
206
0
207
0
  AnimationNeedsResample();
208
0
209
0
  SVGAnimationElementBase::UnbindFromTree(aDeep, aNullParent);
210
0
}
211
212
bool
213
SVGAnimationElement::ParseAttribute(int32_t aNamespaceID,
214
                                    nsAtom* aAttribute,
215
                                    const nsAString& aValue,
216
                                    nsIPrincipal* aMaybeScriptedPrincipal,
217
                                    nsAttrValue& aResult)
218
0
{
219
0
  if (aNamespaceID == kNameSpaceID_None) {
220
0
    // Deal with target-related attributes here
221
0
    if (aAttribute == nsGkAtoms::attributeName) {
222
0
      aResult.ParseAtom(aValue);
223
0
      AnimationNeedsResample();
224
0
      return true;
225
0
    }
226
0
227
0
    nsresult rv = NS_ERROR_FAILURE;
228
0
229
0
    // First let the animation function try to parse it...
230
0
    bool foundMatch =
231
0
      AnimationFunction().SetAttr(aAttribute, aValue, aResult, &rv);
232
0
233
0
    // ... and if that didn't recognize the attribute, let the timed element
234
0
    // try to parse it.
235
0
    if (!foundMatch) {
236
0
      foundMatch =
237
0
        mTimedElement.SetAttr(aAttribute, aValue, aResult, *this, &rv);
238
0
    }
239
0
240
0
    if (foundMatch) {
241
0
      AnimationNeedsResample();
242
0
      if (NS_FAILED(rv)) {
243
0
        ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue);
244
0
        return false;
245
0
      }
246
0
      return true;
247
0
    }
248
0
  }
249
0
250
0
  return SVGAnimationElementBase::ParseAttribute(aNamespaceID, aAttribute,
251
0
                                                 aValue,
252
0
                                                 aMaybeScriptedPrincipal,
253
0
                                                 aResult);
254
0
}
255
256
nsresult
257
SVGAnimationElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
258
                                  const nsAttrValue* aValue,
259
                                  const nsAttrValue* aOldValue,
260
                                  nsIPrincipal* aSubjectPrincipal,
261
                                  bool aNotify)
262
0
{
263
0
  if (!aValue && aNamespaceID == kNameSpaceID_None) {
264
0
    // Attribute is being removed.
265
0
    if (AnimationFunction().UnsetAttr(aName) ||
266
0
        mTimedElement.UnsetAttr(aName)) {
267
0
      AnimationNeedsResample();
268
0
    }
269
0
  }
270
0
271
0
  nsresult rv =
272
0
    SVGAnimationElementBase::AfterSetAttr(aNamespaceID, aName, aValue,
273
0
                                          aOldValue, aSubjectPrincipal, aNotify);
274
0
275
0
  if (SVGTests::IsConditionalProcessingAttribute(aName)) {
276
0
    bool isDisabled = !SVGTests::PassesConditionalProcessingTests();
277
0
    if (mTimedElement.SetIsDisabled(isDisabled)) {
278
0
      AnimationNeedsResample();
279
0
    }
280
0
  }
281
0
282
0
  if (!IsInComposedDoc()) {
283
0
    return rv;
284
0
  }
285
0
286
0
  if (!((aNamespaceID == kNameSpaceID_None ||
287
0
         aNamespaceID == kNameSpaceID_XLink) &&
288
0
        aName == nsGkAtoms::href)) {
289
0
    return rv;
290
0
  }
291
0
292
0
  if (!aValue) {
293
0
    if (aNamespaceID == kNameSpaceID_None) {
294
0
      mHrefTarget.Unlink();
295
0
      AnimationTargetChanged();
296
0
297
0
      // After unsetting href, we may still have xlink:href, so we
298
0
      // should try to add it back.
299
0
      const nsAttrValue* xlinkHref =
300
0
        mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
301
0
      if (xlinkHref) {
302
0
        UpdateHrefTarget(xlinkHref->GetStringValue());
303
0
      }
304
0
    } else if (!HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
305
0
      mHrefTarget.Unlink();
306
0
      AnimationTargetChanged();
307
0
    } // else: we unset xlink:href, but we still have href attribute, so keep
308
0
      // mHrefTarget linking to href.
309
0
  } else if (!(aNamespaceID == kNameSpaceID_XLink &&
310
0
               HasAttr(kNameSpaceID_None, nsGkAtoms::href))) {
311
0
    // Note: "href" takes priority over xlink:href. So if "xlink:href" is being
312
0
    // set here, we only let that update our target if "href" is *unset*.
313
0
    MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
314
0
               "Expected href attribute to be string type");
315
0
    UpdateHrefTarget(aValue->GetStringValue());
316
0
  } // else: we're not yet in a document -- we'll update the target on
317
0
    // next BindToTree call.
318
0
319
0
  return rv;
320
0
}
321
322
bool
323
SVGAnimationElement::IsNodeOfType(uint32_t aFlags) const
324
0
{
325
0
  return !(aFlags & ~eANIMATION);
326
0
}
327
328
//----------------------------------------------------------------------
329
// SVG utility methods
330
331
void
332
SVGAnimationElement::ActivateByHyperlink()
333
0
{
334
0
  FlushAnimations();
335
0
336
0
  // The behavior for when the target is an animation element is defined in
337
0
  // SMIL Animation:
338
0
  //   http://www.w3.org/TR/smil-animation/#HyperlinkSemantics
339
0
  nsSMILTimeValue seekTime = mTimedElement.GetHyperlinkTime();
340
0
  if (seekTime.IsDefinite()) {
341
0
    nsSMILTimeContainer* timeContainer = GetTimeContainer();
342
0
    if (timeContainer) {
343
0
      timeContainer->SetCurrentTime(seekTime.GetMillis());
344
0
      AnimationNeedsResample();
345
0
      // As with SVGSVGElement::SetCurrentTime, we need to trigger
346
0
      // a synchronous sample now.
347
0
      FlushAnimations();
348
0
    }
349
0
    // else, silently fail. We mustn't be part of an SVG document fragment that
350
0
    // is attached to the document tree so there's nothing we can do here
351
0
  } else {
352
0
    BeginElement(IgnoreErrors());
353
0
  }
354
0
}
355
356
//----------------------------------------------------------------------
357
// Implementation helpers
358
359
nsSMILTimeContainer*
360
SVGAnimationElement::GetTimeContainer()
361
0
{
362
0
  SVGSVGElement *element = SVGContentUtils::GetOuterSVGElement(this);
363
0
364
0
  if (element) {
365
0
    return element->GetTimedDocumentRoot();
366
0
  }
367
0
368
0
  return nullptr;
369
0
}
370
371
void
372
SVGAnimationElement::BeginElementAt(float offset, ErrorResult& rv)
373
0
{
374
0
  // Make sure the timegraph is up-to-date
375
0
  FlushAnimations();
376
0
377
0
  // This will fail if we're not attached to a time container (SVG document
378
0
  // fragment).
379
0
  rv = mTimedElement.BeginElementAt(offset);
380
0
  if (rv.Failed())
381
0
    return;
382
0
383
0
  AnimationNeedsResample();
384
0
  // Force synchronous sample so that events resulting from this call arrive in
385
0
  // the expected order and we get an up-to-date paint.
386
0
  FlushAnimations();
387
0
}
388
389
void
390
SVGAnimationElement::EndElementAt(float offset, ErrorResult& rv)
391
0
{
392
0
  // Make sure the timegraph is up-to-date
393
0
  FlushAnimations();
394
0
395
0
  rv = mTimedElement.EndElementAt(offset);
396
0
  if (rv.Failed())
397
0
    return;
398
0
399
0
  AnimationNeedsResample();
400
0
  // Force synchronous sample
401
0
  FlushAnimations();
402
0
}
403
404
bool
405
SVGAnimationElement::IsEventAttributeNameInternal(nsAtom* aName)
406
0
{
407
0
  return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL);
408
0
}
409
410
void
411
SVGAnimationElement::UpdateHrefTarget(const nsAString& aHrefStr)
412
0
{
413
0
  nsCOMPtr<nsIURI> targetURI;
414
0
  nsCOMPtr<nsIURI> baseURI = GetBaseURI();
415
0
  nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI),
416
0
                                            aHrefStr, OwnerDoc(), baseURI);
417
0
  // Bug 1415044 to investigate which referrer we should use
418
0
  mHrefTarget.Reset(this,
419
0
                    targetURI,
420
0
                    OwnerDoc()->GetDocumentURI(),
421
0
                    OwnerDoc()->GetReferrerPolicy());
422
0
  AnimationTargetChanged();
423
0
}
424
425
void
426
SVGAnimationElement::AnimationTargetChanged()
427
0
{
428
0
  mTimedElement.HandleTargetElementChange(GetTargetElementContent());
429
0
  AnimationNeedsResample();
430
0
}
431
432
} // namespace dom
433
} // namespace mozilla