Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/smil/nsSMILTimeContainer.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 "nsSMILTimeContainer.h"
8
#include "nsSMILTimeValue.h"
9
#include "nsSMILTimedElement.h"
10
#include <algorithm>
11
12
#include "mozilla/AutoRestore.h"
13
14
using namespace mozilla;
15
16
nsSMILTimeContainer::nsSMILTimeContainer()
17
:
18
  mParent(nullptr),
19
  mCurrentTime(0L),
20
  mParentOffset(0L),
21
  mPauseStart(0L),
22
  mNeedsPauseSample(false),
23
  mNeedsRewind(false),
24
  mIsSeeking(false),
25
#ifdef DEBUG
26
  mHoldingEntries(false),
27
#endif
28
  mPauseState(PAUSE_BEGIN)
29
0
{
30
0
}
31
32
nsSMILTimeContainer::~nsSMILTimeContainer()
33
0
{
34
0
  if (mParent) {
35
0
    mParent->RemoveChild(*this);
36
0
  }
37
0
}
38
39
nsSMILTimeValue
40
nsSMILTimeContainer::ContainerToParentTime(nsSMILTime aContainerTime) const
41
0
{
42
0
  // If we're paused, then future times are indefinite
43
0
  if (IsPaused() && aContainerTime > mCurrentTime)
44
0
    return nsSMILTimeValue::Indefinite();
45
0
46
0
  return nsSMILTimeValue(aContainerTime + mParentOffset);
47
0
}
48
49
nsSMILTimeValue
50
nsSMILTimeContainer::ParentToContainerTime(nsSMILTime aParentTime) const
51
0
{
52
0
  // If we're paused, then any time after when we paused is indefinite
53
0
  if (IsPaused() && aParentTime > mPauseStart)
54
0
    return nsSMILTimeValue::Indefinite();
55
0
56
0
  return nsSMILTimeValue(aParentTime - mParentOffset);
57
0
}
58
59
void
60
nsSMILTimeContainer::Begin()
61
0
{
62
0
  Resume(PAUSE_BEGIN);
63
0
  if (mPauseState) {
64
0
    mNeedsPauseSample = true;
65
0
  }
66
0
67
0
  // This is a little bit complicated here. Ideally we'd just like to call
68
0
  // Sample() and force an initial sample but this turns out to be a bad idea
69
0
  // because this may mean that NeedsSample() no longer reports true and so when
70
0
  // we come to the first real sample our parent will skip us over altogether.
71
0
  // So we force the time to be updated and adopt the policy to never call
72
0
  // Sample() ourselves but to always leave that to our parent or client.
73
0
74
0
  UpdateCurrentTime();
75
0
}
76
77
void
78
nsSMILTimeContainer::Pause(uint32_t aType)
79
0
{
80
0
  bool didStartPause = false;
81
0
82
0
  if (!mPauseState && aType) {
83
0
    mPauseStart = GetParentTime();
84
0
    mNeedsPauseSample = true;
85
0
    didStartPause = true;
86
0
  }
87
0
88
0
  mPauseState |= aType;
89
0
90
0
  if (didStartPause) {
91
0
    NotifyTimeChange();
92
0
  }
93
0
}
94
95
void
96
nsSMILTimeContainer::Resume(uint32_t aType)
97
0
{
98
0
  if (!mPauseState)
99
0
    return;
100
0
101
0
  mPauseState &= ~aType;
102
0
103
0
  if (!mPauseState) {
104
0
    nsSMILTime extraOffset = GetParentTime() - mPauseStart;
105
0
    mParentOffset += extraOffset;
106
0
    NotifyTimeChange();
107
0
  }
108
0
}
109
110
nsSMILTime
111
nsSMILTimeContainer::GetCurrentTime() const
112
0
{
113
0
  // The following behaviour is consistent with:
114
0
  // http://www.w3.org/2003/01/REC-SVG11-20030114-errata
115
0
  //  #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin
116
0
  // which says that if GetCurrentTime is called before the document timeline
117
0
  // has begun we should just return 0.
118
0
  if (IsPausedByType(PAUSE_BEGIN))
119
0
    return 0L;
120
0
121
0
  return mCurrentTime;
122
0
}
123
124
void
125
nsSMILTimeContainer::SetCurrentTime(nsSMILTime aSeekTo)
126
0
{
127
0
  // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's
128
0
  // behaviour of clamping negative times to 0.
129
0
  aSeekTo = std::max<nsSMILTime>(0, aSeekTo);
130
0
131
0
  // The following behaviour is consistent with:
132
0
  // http://www.w3.org/2003/01/REC-SVG11-20030114-errata
133
0
  //  #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin
134
0
  // which says that if SetCurrentTime is called before the document timeline
135
0
  // has begun we should still adjust the offset.
136
0
  nsSMILTime parentTime = GetParentTime();
137
0
  mParentOffset = parentTime - aSeekTo;
138
0
  mIsSeeking = true;
139
0
140
0
  if (IsPaused()) {
141
0
    mNeedsPauseSample = true;
142
0
    mPauseStart = parentTime;
143
0
  }
144
0
145
0
  if (aSeekTo < mCurrentTime) {
146
0
    // Backwards seek
147
0
    mNeedsRewind = true;
148
0
    ClearMilestones();
149
0
  }
150
0
151
0
  // Force an update to the current time in case we get a call to GetCurrentTime
152
0
  // before another call to Sample().
153
0
  UpdateCurrentTime();
154
0
155
0
  NotifyTimeChange();
156
0
}
157
158
nsSMILTime
159
nsSMILTimeContainer::GetParentTime() const
160
0
{
161
0
  if (mParent)
162
0
    return mParent->GetCurrentTime();
163
0
164
0
  return 0L;
165
0
}
166
167
void
168
nsSMILTimeContainer::SyncPauseTime()
169
0
{
170
0
  if (IsPaused()) {
171
0
    nsSMILTime parentTime = GetParentTime();
172
0
    nsSMILTime extraOffset = parentTime - mPauseStart;
173
0
    mParentOffset += extraOffset;
174
0
    mPauseStart = parentTime;
175
0
  }
176
0
}
177
178
void
179
nsSMILTimeContainer::Sample()
180
0
{
181
0
  if (!NeedsSample())
182
0
    return;
183
0
184
0
  UpdateCurrentTime();
185
0
  DoSample();
186
0
187
0
  mNeedsPauseSample = false;
188
0
}
189
190
nsresult
191
nsSMILTimeContainer::SetParent(nsSMILTimeContainer* aParent)
192
0
{
193
0
  if (mParent) {
194
0
    mParent->RemoveChild(*this);
195
0
    // When we're not attached to a parent time container, GetParentTime() will
196
0
    // return 0. We need to adjust our pause state information to be relative to
197
0
    // this new time base.
198
0
    // Note that since "current time = parent time - parent offset" setting the
199
0
    // parent offset and pause start as follows preserves our current time even
200
0
    // while parent time = 0.
201
0
    mParentOffset = -mCurrentTime;
202
0
    mPauseStart = 0L;
203
0
  }
204
0
205
0
  mParent = aParent;
206
0
207
0
  nsresult rv = NS_OK;
208
0
  if (mParent) {
209
0
    rv = mParent->AddChild(*this);
210
0
  }
211
0
212
0
  return rv;
213
0
}
214
215
bool
216
nsSMILTimeContainer::AddMilestone(const nsSMILMilestone& aMilestone,
217
                                  mozilla::dom::SVGAnimationElement& aElement)
218
0
{
219
0
  // We record the milestone time and store it along with the element but this
220
0
  // time may change (e.g. if attributes are changed on the timed element in
221
0
  // between samples). If this happens, then we may do an unecessary sample
222
0
  // but that's pretty cheap.
223
0
  MOZ_ASSERT(!mHoldingEntries);
224
0
  return mMilestoneEntries.Push(MilestoneEntry(aMilestone, aElement));
225
0
}
226
227
void
228
nsSMILTimeContainer::ClearMilestones()
229
0
{
230
0
  MOZ_ASSERT(!mHoldingEntries);
231
0
  mMilestoneEntries.Clear();
232
0
}
233
234
bool
235
nsSMILTimeContainer::GetNextMilestoneInParentTime(
236
    nsSMILMilestone& aNextMilestone) const
237
0
{
238
0
  if (mMilestoneEntries.IsEmpty())
239
0
    return false;
240
0
241
0
  nsSMILTimeValue parentTime =
242
0
    ContainerToParentTime(mMilestoneEntries.Top().mMilestone.mTime);
243
0
  if (!parentTime.IsDefinite())
244
0
    return false;
245
0
246
0
  aNextMilestone = nsSMILMilestone(parentTime.GetMillis(),
247
0
                                   mMilestoneEntries.Top().mMilestone.mIsEnd);
248
0
249
0
  return true;
250
0
}
251
252
bool
253
nsSMILTimeContainer::PopMilestoneElementsAtMilestone(
254
      const nsSMILMilestone& aMilestone,
255
      AnimElemArray& aMatchedElements)
256
0
{
257
0
  if (mMilestoneEntries.IsEmpty())
258
0
    return false;
259
0
260
0
  nsSMILTimeValue containerTime = ParentToContainerTime(aMilestone.mTime);
261
0
  if (!containerTime.IsDefinite())
262
0
    return false;
263
0
264
0
  nsSMILMilestone containerMilestone(containerTime.GetMillis(),
265
0
                                     aMilestone.mIsEnd);
266
0
267
0
  MOZ_ASSERT(mMilestoneEntries.Top().mMilestone >= containerMilestone,
268
0
             "Trying to pop off earliest times but we have earlier ones that "
269
0
             "were overlooked");
270
0
271
0
  MOZ_ASSERT(!mHoldingEntries);
272
0
273
0
  bool gotOne = false;
274
0
  while (!mMilestoneEntries.IsEmpty() &&
275
0
      mMilestoneEntries.Top().mMilestone == containerMilestone)
276
0
  {
277
0
    aMatchedElements.AppendElement(mMilestoneEntries.Pop().mTimebase);
278
0
    gotOne = true;
279
0
  }
280
0
281
0
  return gotOne;
282
0
}
283
284
void
285
nsSMILTimeContainer::Traverse(nsCycleCollectionTraversalCallback* aCallback)
286
0
{
287
#ifdef DEBUG
288
  AutoRestore<bool> saveHolding(mHoldingEntries);
289
  mHoldingEntries = true;
290
#endif
291
  const MilestoneEntry* p = mMilestoneEntries.Elements();
292
0
  while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) {
293
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mTimebase");
294
0
    aCallback->NoteXPCOMChild(static_cast<nsIContent*>(p->mTimebase.get()));
295
0
    ++p;
296
0
  }
297
0
}
298
299
void
300
nsSMILTimeContainer::Unlink()
301
0
{
302
0
  MOZ_ASSERT(!mHoldingEntries);
303
0
  mMilestoneEntries.Clear();
304
0
}
305
306
void
307
nsSMILTimeContainer::UpdateCurrentTime()
308
0
{
309
0
  nsSMILTime now = IsPaused() ? mPauseStart : GetParentTime();
310
0
  mCurrentTime = now - mParentOffset;
311
0
  MOZ_ASSERT(mCurrentTime >= 0, "Container has negative time");
312
0
}
313
314
void
315
nsSMILTimeContainer::NotifyTimeChange()
316
0
{
317
0
  // Called when the container time is changed with respect to the document
318
0
  // time. When this happens time dependencies in other time containers need to
319
0
  // re-resolve their times because begin and end times are stored in container
320
0
  // time.
321
0
  //
322
0
  // To get the list of timed elements with dependencies we simply re-use the
323
0
  // milestone elements. This is because any timed element with dependents and
324
0
  // with significant transitions yet to fire should have their next milestone
325
0
  // registered. Other timed elements don't matter.
326
0
327
0
  // Copy the timed elements to a separate array before calling
328
0
  // HandleContainerTimeChange on each of them in case doing so mutates
329
0
  // mMilestoneEntries.
330
0
  nsTArray<RefPtr<mozilla::dom::SVGAnimationElement>> elems;
331
0
332
0
  {
333
#ifdef DEBUG
334
    AutoRestore<bool> saveHolding(mHoldingEntries);
335
    mHoldingEntries = true;
336
#endif
337
    for (const MilestoneEntry* p = mMilestoneEntries.Elements();
338
0
        p < mMilestoneEntries.Elements() + mMilestoneEntries.Length();
339
0
        ++p) {
340
0
      elems.AppendElement(p->mTimebase.get());
341
0
    }
342
0
  }
343
0
344
0
  for (auto& elem : elems) {
345
0
    elem->TimedElement().HandleContainerTimeChange();
346
0
  }
347
0
}