Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/smil/nsSMILTimeValueSpec.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/EventListenerManager.h"
8
#include "mozilla/dom/Event.h"
9
#include "mozilla/dom/SVGAnimationElement.h"
10
#include "mozilla/dom/TimeEvent.h"
11
#include "nsSMILTimeValueSpec.h"
12
#include "nsSMILInterval.h"
13
#include "nsSMILTimeContainer.h"
14
#include "nsSMILTimeValue.h"
15
#include "nsSMILTimedElement.h"
16
#include "nsSMILInstanceTime.h"
17
#include "nsSMILParserUtils.h"
18
#include "nsString.h"
19
#include <limits>
20
21
using namespace mozilla;
22
using namespace mozilla::dom;
23
24
//----------------------------------------------------------------------
25
// Nested class: EventListener
26
27
NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
28
29
NS_IMETHODIMP
30
nsSMILTimeValueSpec::EventListener::HandleEvent(Event* aEvent)
31
0
{
32
0
  if (mSpec) {
33
0
    mSpec->HandleEvent(aEvent);
34
0
  }
35
0
  return NS_OK;
36
0
}
37
38
//----------------------------------------------------------------------
39
// Implementation
40
41
nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner,
42
                                         bool aIsBegin)
43
  : mOwner(&aOwner),
44
    mIsBegin(aIsBegin),
45
    mReferencedElement(this)
46
0
{
47
0
}
48
49
nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
50
0
{
51
0
  UnregisterFromReferencedElement(mReferencedElement.get());
52
0
  if (mEventListener) {
53
0
    mEventListener->Disconnect();
54
0
    mEventListener = nullptr;
55
0
  }
56
0
}
57
58
nsresult
59
nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
60
                             Element& aContextElement)
61
0
{
62
0
  nsSMILTimeValueSpecParams params;
63
0
64
0
  if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params))
65
0
    return NS_ERROR_FAILURE;
66
0
67
0
  mParams = params;
68
0
69
0
  // According to SMIL 3.0:
70
0
  //   The special value "indefinite" does not yield an instance time in the
71
0
  //   begin list. It will, however yield a single instance with the value
72
0
  //   "indefinite" in an end list. This value is not removed by a reset.
73
0
  if (mParams.mType == nsSMILTimeValueSpecParams::OFFSET ||
74
0
      (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) {
75
0
    mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin);
76
0
  }
77
0
78
0
  // Fill in the event symbol to simplify handling later
79
0
  if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
80
0
    mParams.mEventSymbol = nsGkAtoms::repeatEvent;
81
0
  }
82
0
83
0
  ResolveReferences(aContextElement);
84
0
85
0
  return NS_OK;
86
0
}
87
88
void
89
nsSMILTimeValueSpec::ResolveReferences(Element& aContextElement)
90
0
{
91
0
  if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) {
92
0
    return;
93
0
  }
94
0
95
0
  // If we're not bound to the document yet, don't worry, we'll get called again
96
0
  // when that happens
97
0
  if (!aContextElement.IsInComposedDoc())
98
0
    return;
99
0
100
0
  // Hold ref to the old element so that it isn't destroyed in between resetting
101
0
  // the referenced element and using the pointer to update the referenced
102
0
  // element.
103
0
  RefPtr<Element> oldReferencedElement = mReferencedElement.get();
104
0
105
0
  if (mParams.mDependentElemID) {
106
0
    mReferencedElement.ResetWithID(aContextElement, mParams.mDependentElemID);
107
0
  } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
108
0
    Element* target = mOwner->GetTargetElement();
109
0
    mReferencedElement.ResetWithElement(target);
110
0
  } else {
111
0
    MOZ_ASSERT(false, "Syncbase or repeat spec without ID");
112
0
  }
113
0
  UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
114
0
}
115
116
bool
117
nsSMILTimeValueSpec::IsEventBased() const
118
0
{
119
0
  return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
120
0
         mParams.mType == nsSMILTimeValueSpecParams::REPEAT;
121
0
}
122
123
void
124
nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval,
125
                                       const nsSMILTimeContainer* aSrcContainer)
126
0
{
127
0
  const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin
128
0
    ? *aInterval.Begin() : *aInterval.End();
129
0
  nsSMILTimeValue newTime =
130
0
    ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer);
131
0
132
0
  // Apply offset
133
0
  if (!ApplyOffset(newTime)) {
134
0
    NS_WARNING("New time overflows nsSMILTime, ignoring");
135
0
    return;
136
0
  }
137
0
138
0
  // Create the instance time and register it with the interval
139
0
  RefPtr<nsSMILInstanceTime> newInstance =
140
0
    new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this,
141
0
                           &aInterval);
142
0
  mOwner->AddInstanceTime(newInstance, mIsBegin);
143
0
}
144
145
void
146
nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget)
147
0
{
148
0
  if (!IsEventBased() || mParams.mDependentElemID)
149
0
    return;
150
0
151
0
  mReferencedElement.ResetWithElement(aNewTarget);
152
0
}
153
154
void
155
nsSMILTimeValueSpec::HandleChangedInstanceTime(
156
    const nsSMILInstanceTime& aBaseTime,
157
    const nsSMILTimeContainer* aSrcContainer,
158
    nsSMILInstanceTime& aInstanceTimeToUpdate,
159
    bool aObjectChanged)
160
0
{
161
0
  // If the instance time is fixed (e.g. because it's being used as the begin
162
0
  // time of an active or postactive interval) we just ignore the change.
163
0
  if (aInstanceTimeToUpdate.IsFixedTime())
164
0
    return;
165
0
166
0
  nsSMILTimeValue updatedTime =
167
0
    ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
168
0
169
0
  // Apply offset
170
0
  if (!ApplyOffset(updatedTime)) {
171
0
    NS_WARNING("Updated time overflows nsSMILTime, ignoring");
172
0
    return;
173
0
  }
174
0
175
0
  // The timed element that owns the instance time does the updating so it can
176
0
  // re-sort its array of instance times more efficiently
177
0
  if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) {
178
0
    mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin);
179
0
  }
180
0
}
181
182
void
183
nsSMILTimeValueSpec::HandleDeletedInstanceTime(
184
    nsSMILInstanceTime &aInstanceTime)
185
0
{
186
0
  mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
187
0
}
188
189
bool
190
nsSMILTimeValueSpec::DependsOnBegin() const
191
0
{
192
0
  return mParams.mSyncBegin;
193
0
}
194
195
void
196
nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback)
197
0
{
198
0
  mReferencedElement.Traverse(aCallback);
199
0
}
200
201
void
202
nsSMILTimeValueSpec::Unlink()
203
0
{
204
0
  UnregisterFromReferencedElement(mReferencedElement.get());
205
0
  mReferencedElement.Unlink();
206
0
}
207
208
//----------------------------------------------------------------------
209
// Implementation helpers
210
211
void
212
nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo)
213
0
{
214
0
  if (aFrom == aTo)
215
0
    return;
216
0
217
0
  UnregisterFromReferencedElement(aFrom);
218
0
219
0
  switch (mParams.mType)
220
0
  {
221
0
  case nsSMILTimeValueSpecParams::SYNCBASE:
222
0
    {
223
0
      nsSMILTimedElement* to = GetTimedElement(aTo);
224
0
      if (to) {
225
0
        to->AddDependent(*this);
226
0
      }
227
0
    }
228
0
    break;
229
0
230
0
  case nsSMILTimeValueSpecParams::EVENT:
231
0
  case nsSMILTimeValueSpecParams::REPEAT:
232
0
    RegisterEventListener(aTo);
233
0
    break;
234
0
235
0
  default:
236
0
    // not a referencing-type
237
0
    break;
238
0
  }
239
0
}
240
241
void
242
nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement)
243
0
{
244
0
  if (!aElement)
245
0
    return;
246
0
247
0
  if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
248
0
    nsSMILTimedElement* timedElement = GetTimedElement(aElement);
249
0
    if (timedElement) {
250
0
      timedElement->RemoveDependent(*this);
251
0
    }
252
0
    mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
253
0
  } else if (IsEventBased()) {
254
0
    UnregisterEventListener(aElement);
255
0
  }
256
0
}
257
258
nsSMILTimedElement*
259
nsSMILTimeValueSpec::GetTimedElement(Element* aElement)
260
0
{
261
0
  return aElement && aElement->IsNodeOfType(nsINode::eANIMATION) ?
262
0
    &static_cast<SVGAnimationElement*>(aElement)->TimedElement() : nullptr;
263
0
}
264
265
// Indicates whether we're allowed to register an event-listener
266
// when scripting is disabled.
267
bool
268
nsSMILTimeValueSpec::IsWhitelistedEvent()
269
0
{
270
0
  // The category of (SMIL-specific) "repeat(n)" events are allowed.
271
0
  if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
272
0
    return true;
273
0
  }
274
0
275
0
  // A specific list of other SMIL-related events are allowed, too.
276
0
  if (mParams.mType == nsSMILTimeValueSpecParams::EVENT &&
277
0
      (mParams.mEventSymbol == nsGkAtoms::repeat ||
278
0
       mParams.mEventSymbol == nsGkAtoms::repeatEvent ||
279
0
       mParams.mEventSymbol == nsGkAtoms::beginEvent ||
280
0
       mParams.mEventSymbol == nsGkAtoms::endEvent)) {
281
0
    return true;
282
0
  }
283
0
284
0
  return false;
285
0
}
286
287
void
288
nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget)
289
0
{
290
0
  MOZ_ASSERT(IsEventBased(),
291
0
             "Attempting to register event-listener for unexpected "
292
0
             "nsSMILTimeValueSpec type");
293
0
  MOZ_ASSERT(mParams.mEventSymbol,
294
0
             "Attempting to register event-listener but there is no event "
295
0
             "name");
296
0
297
0
  if (!aTarget)
298
0
    return;
299
0
300
0
  // When script is disabled, only allow registration for whitelisted events.
301
0
  if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
302
0
      !IsWhitelistedEvent()) {
303
0
    return;
304
0
  }
305
0
306
0
  if (!mEventListener) {
307
0
    mEventListener = new EventListener(this);
308
0
  }
309
0
310
0
  EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
311
0
  if (!elm) {
312
0
    return;
313
0
  }
314
0
315
0
  elm->AddEventListenerByType(mEventListener,
316
0
                              nsDependentAtomString(mParams.mEventSymbol),
317
0
                              AllEventsAtSystemGroupBubble());
318
0
}
319
320
void
321
nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget)
322
0
{
323
0
  if (!aTarget || !mEventListener) {
324
0
    return;
325
0
  }
326
0
327
0
  EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
328
0
  if (!elm) {
329
0
    return;
330
0
  }
331
0
332
0
  elm->RemoveEventListenerByType(mEventListener,
333
0
                                 nsDependentAtomString(mParams.mEventSymbol),
334
0
                                 AllEventsAtSystemGroupBubble());
335
0
}
336
337
void
338
nsSMILTimeValueSpec::HandleEvent(Event* aEvent)
339
0
{
340
0
  MOZ_ASSERT(mEventListener, "Got event without an event listener");
341
0
  MOZ_ASSERT(IsEventBased(),
342
0
             "Got event for non-event nsSMILTimeValueSpec");
343
0
  MOZ_ASSERT(aEvent, "No event supplied");
344
0
345
0
  // XXX In the long run we should get the time from the event itself which will
346
0
  // store the time in global document time which we'll need to convert to our
347
0
  // time container
348
0
  nsSMILTimeContainer* container = mOwner->GetTimeContainer();
349
0
  if (!container)
350
0
    return;
351
0
352
0
  if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT &&
353
0
      !CheckRepeatEventDetail(aEvent)) {
354
0
    return;
355
0
  }
356
0
357
0
  nsSMILTime currentTime = container->GetCurrentTime();
358
0
  nsSMILTimeValue newTime(currentTime);
359
0
  if (!ApplyOffset(newTime)) {
360
0
    NS_WARNING("New time generated from event overflows nsSMILTime, ignoring");
361
0
    return;
362
0
  }
363
0
364
0
  RefPtr<nsSMILInstanceTime> newInstance =
365
0
    new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT);
366
0
  mOwner->AddInstanceTime(newInstance, mIsBegin);
367
0
}
368
369
bool
370
nsSMILTimeValueSpec::CheckRepeatEventDetail(Event *aEvent)
371
0
{
372
0
  TimeEvent* timeEvent = aEvent->AsTimeEvent();
373
0
  if (!timeEvent) {
374
0
    NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
375
0
    return false;
376
0
  }
377
0
378
0
  int32_t detail = timeEvent->Detail();
379
0
  return detail > 0 && (uint32_t)detail == mParams.mRepeatIteration;
380
0
}
381
382
nsSMILTimeValue
383
nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
384
    const nsSMILTimeValue& aSrcTime,
385
    const nsSMILTimeContainer* aSrcContainer)
386
0
{
387
0
  // If the source time is either indefinite or unresolved the result is going
388
0
  // to be the same
389
0
  if (!aSrcTime.IsDefinite())
390
0
    return aSrcTime;
391
0
392
0
  // Convert from source time container to our parent time container
393
0
  const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
394
0
  if (dstContainer == aSrcContainer)
395
0
    return aSrcTime;
396
0
397
0
  // If one of the elements is not attached to a time container then we can't do
398
0
  // any meaningful conversion
399
0
  if (!aSrcContainer || !dstContainer)
400
0
    return nsSMILTimeValue(); // unresolved
401
0
402
0
  nsSMILTimeValue docTime =
403
0
    aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis());
404
0
405
0
  if (docTime.IsIndefinite())
406
0
    // This will happen if the source container is paused and we have a future
407
0
    // time. Just return the indefinite time.
408
0
    return docTime;
409
0
410
0
  MOZ_ASSERT(docTime.IsDefinite(),
411
0
             "ContainerToParentTime gave us an unresolved or indefinite time");
412
0
413
0
  return dstContainer->ParentToContainerTime(docTime.GetMillis());
414
0
}
415
416
bool
417
nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const
418
0
{
419
0
  // indefinite + offset = indefinite. Likewise for unresolved times.
420
0
  if (!aTime.IsDefinite()) {
421
0
    return true;
422
0
  }
423
0
424
0
  double resultAsDouble =
425
0
    (double)aTime.GetMillis() + mParams.mOffset.GetMillis();
426
0
  if (resultAsDouble > std::numeric_limits<nsSMILTime>::max() ||
427
0
      resultAsDouble < std::numeric_limits<nsSMILTime>::min()) {
428
0
    return false;
429
0
  }
430
0
  aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
431
0
  return true;
432
0
}