1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla SMIL module.
16 : *
17 : * The Initial Developer of the Original Code is Brian Birtles.
18 : * Portions created by the Initial Developer are Copyright (C) 2005
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Brian Birtles <birtles@gmail.com>
23 : * Daniel Holbert <dholbert@mozilla.com>
24 : * Robert Longson <longsonr@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsSMILAnimationController.h"
41 : #include "nsSMILCompositor.h"
42 : #include "nsSMILCSSProperty.h"
43 : #include "nsCSSProps.h"
44 : #include "nsComponentManagerUtils.h"
45 : #include "nsITimer.h"
46 : #include "nsIContent.h"
47 : #include "mozilla/dom/Element.h"
48 : #include "nsIDocument.h"
49 : #include "nsISMILAnimationElement.h"
50 : #include "nsIDOMSVGAnimationElement.h"
51 : #include "nsSMILTimedElement.h"
52 :
53 : using namespace mozilla::dom;
54 :
55 : //----------------------------------------------------------------------
56 : // nsSMILAnimationController implementation
57 :
58 : //----------------------------------------------------------------------
59 : // ctors, dtors, factory methods
60 :
61 0 : nsSMILAnimationController::nsSMILAnimationController(nsIDocument* aDoc)
62 : : mAvgTimeBetweenSamples(0),
63 : mResampleNeeded(false),
64 : mDeferredStartSampling(false),
65 : mRunningSample(false),
66 0 : mDocument(aDoc)
67 : {
68 0 : NS_ABORT_IF_FALSE(aDoc, "need a non-null document");
69 :
70 0 : mAnimationElementTable.Init();
71 0 : mChildContainerTable.Init();
72 :
73 0 : nsRefreshDriver* refreshDriver = GetRefreshDriver();
74 0 : if (refreshDriver) {
75 0 : mStartTime = refreshDriver->MostRecentRefresh();
76 : } else {
77 0 : mStartTime = mozilla::TimeStamp::Now();
78 : }
79 0 : mCurrentSampleTime = mStartTime;
80 :
81 0 : Begin();
82 0 : }
83 :
84 0 : nsSMILAnimationController::~nsSMILAnimationController()
85 : {
86 0 : NS_ASSERTION(mAnimationElementTable.Count() == 0,
87 : "Animation controller shouldn't be tracking any animation"
88 : " elements when it dies");
89 0 : }
90 :
91 : void
92 0 : nsSMILAnimationController::Disconnect()
93 : {
94 0 : NS_ABORT_IF_FALSE(mDocument, "disconnecting when we weren't connected...?");
95 0 : NS_ABORT_IF_FALSE(mRefCnt.get() == 1,
96 : "Expecting to disconnect when doc is sole remaining owner");
97 0 : NS_ASSERTION(mPauseState & nsSMILTimeContainer::PAUSE_PAGEHIDE,
98 : "Expecting to be paused for pagehide before disconnect");
99 :
100 0 : StopSampling(GetRefreshDriver());
101 :
102 0 : mDocument = nsnull; // (raw pointer)
103 0 : }
104 :
105 : //----------------------------------------------------------------------
106 : // nsSMILTimeContainer methods:
107 :
108 : void
109 0 : nsSMILAnimationController::Pause(PRUint32 aType)
110 : {
111 0 : nsSMILTimeContainer::Pause(aType);
112 :
113 0 : if (mPauseState) {
114 0 : mDeferredStartSampling = false;
115 0 : StopSampling(GetRefreshDriver());
116 : }
117 0 : }
118 :
119 : void
120 0 : nsSMILAnimationController::Resume(PRUint32 aType)
121 : {
122 0 : bool wasPaused = (mPauseState != 0);
123 : // Update mCurrentSampleTime so that calls to GetParentTime--used for
124 : // calculating parent offsets--are accurate
125 0 : mCurrentSampleTime = mozilla::TimeStamp::Now();
126 :
127 0 : nsSMILTimeContainer::Resume(aType);
128 :
129 0 : if (wasPaused && !mPauseState && mChildContainerTable.Count()) {
130 0 : Sample(); // Run the first sample manually
131 0 : MaybeStartSampling(GetRefreshDriver());
132 : }
133 0 : }
134 :
135 : nsSMILTime
136 0 : nsSMILAnimationController::GetParentTime() const
137 : {
138 0 : return (nsSMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
139 : }
140 :
141 : //----------------------------------------------------------------------
142 : // nsARefreshObserver methods:
143 0 : NS_IMPL_ADDREF(nsSMILAnimationController)
144 0 : NS_IMPL_RELEASE(nsSMILAnimationController)
145 :
146 : // nsRefreshDriver Callback function
147 : void
148 0 : nsSMILAnimationController::WillRefresh(mozilla::TimeStamp aTime)
149 : {
150 : // Although we never expect aTime to go backwards, when we initialise the
151 : // animation controller, if we can't get hold of a refresh driver we
152 : // initialise mCurrentSampleTime to Now(). It may be possible that after
153 : // doing so we get sampled by a refresh driver whose most recent refresh time
154 : // predates when we were initialised, so to be safe we make sure to take the
155 : // most recent time here.
156 0 : aTime = NS_MAX(mCurrentSampleTime, aTime);
157 :
158 : // Sleep detection: If the time between samples is a whole lot greater than we
159 : // were expecting then we assume the computer went to sleep or someone's
160 : // messing with the clock. In that case, fiddle our parent offset and use our
161 : // average time between samples to calculate the new sample time. This
162 : // prevents us from hanging while trying to catch up on all the missed time.
163 :
164 : // Smoothing of coefficient for the average function. 0.2 should let us track
165 : // the sample rate reasonably tightly without being overly affected by
166 : // occasional delays.
167 : static const double SAMPLE_DUR_WEIGHTING = 0.2;
168 : // If the elapsed time exceeds our expectation by this number of times we'll
169 : // initiate special behaviour to basically ignore the intervening time.
170 : static const double SAMPLE_DEV_THRESHOLD = 200.0;
171 :
172 : nsSMILTime elapsedTime =
173 0 : (nsSMILTime)(aTime - mCurrentSampleTime).ToMilliseconds();
174 : // First sample:
175 0 : if (mAvgTimeBetweenSamples == 0) {
176 0 : mAvgTimeBetweenSamples = elapsedTime;
177 : // Unexpectedly long delay between samples:
178 0 : } else if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
179 : NS_WARNING("Detected really long delay between samples, continuing from "
180 0 : "previous sample");
181 0 : mParentOffset += elapsedTime - mAvgTimeBetweenSamples;
182 : // Usual case, update moving average:
183 : } else {
184 : // Due to truncation here the average will normally be a little less than
185 : // it should be but that's probably ok
186 : mAvgTimeBetweenSamples =
187 : (nsSMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
188 0 : mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
189 : }
190 0 : mCurrentSampleTime = aTime;
191 :
192 0 : Sample();
193 0 : }
194 :
195 : //----------------------------------------------------------------------
196 : // Animation element registration methods:
197 :
198 : void
199 0 : nsSMILAnimationController::RegisterAnimationElement(
200 : nsISMILAnimationElement* aAnimationElement)
201 : {
202 0 : mAnimationElementTable.PutEntry(aAnimationElement);
203 0 : if (mDeferredStartSampling) {
204 0 : mDeferredStartSampling = false;
205 0 : if (mChildContainerTable.Count()) {
206 : // mAnimationElementTable was empty, but now we've added its 1st element
207 0 : NS_ABORT_IF_FALSE(mAnimationElementTable.Count() == 1,
208 : "we shouldn't have deferred sampling if we already had "
209 : "animations registered");
210 0 : StartSampling(GetRefreshDriver());
211 0 : Sample(); // Run the first sample manually
212 : } // else, don't sample until a time container is registered (via AddChild)
213 : }
214 0 : }
215 :
216 : void
217 0 : nsSMILAnimationController::UnregisterAnimationElement(
218 : nsISMILAnimationElement* aAnimationElement)
219 : {
220 0 : mAnimationElementTable.RemoveEntry(aAnimationElement);
221 0 : }
222 :
223 : //----------------------------------------------------------------------
224 : // Page show/hide
225 :
226 : void
227 0 : nsSMILAnimationController::OnPageShow()
228 : {
229 0 : Resume(nsSMILTimeContainer::PAUSE_PAGEHIDE);
230 0 : }
231 :
232 : void
233 0 : nsSMILAnimationController::OnPageHide()
234 : {
235 0 : Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE);
236 0 : }
237 :
238 : //----------------------------------------------------------------------
239 : // Cycle-collection support
240 :
241 : void
242 0 : nsSMILAnimationController::Traverse(
243 : nsCycleCollectionTraversalCallback* aCallback)
244 : {
245 : // Traverse last compositor table
246 0 : if (mLastCompositorTable) {
247 : mLastCompositorTable->EnumerateEntries(CompositorTableEntryTraverse,
248 0 : aCallback);
249 : }
250 0 : }
251 :
252 : /*static*/ PR_CALLBACK PLDHashOperator
253 0 : nsSMILAnimationController::CompositorTableEntryTraverse(
254 : nsSMILCompositor* aCompositor,
255 : void* aArg)
256 : {
257 : nsCycleCollectionTraversalCallback* cb =
258 0 : static_cast<nsCycleCollectionTraversalCallback*>(aArg);
259 0 : aCompositor->Traverse(cb);
260 0 : return PL_DHASH_NEXT;
261 : }
262 :
263 : void
264 0 : nsSMILAnimationController::Unlink()
265 : {
266 0 : mLastCompositorTable = nsnull;
267 0 : }
268 :
269 : //----------------------------------------------------------------------
270 : // Refresh driver lifecycle related methods
271 :
272 : void
273 0 : nsSMILAnimationController::NotifyRefreshDriverCreated(
274 : nsRefreshDriver* aRefreshDriver)
275 : {
276 0 : if (!mPauseState) {
277 0 : MaybeStartSampling(aRefreshDriver);
278 : }
279 0 : }
280 :
281 : void
282 0 : nsSMILAnimationController::NotifyRefreshDriverDestroying(
283 : nsRefreshDriver* aRefreshDriver)
284 : {
285 0 : if (!mPauseState && !mDeferredStartSampling) {
286 0 : StopSampling(aRefreshDriver);
287 : }
288 0 : }
289 :
290 : //----------------------------------------------------------------------
291 : // Timer-related implementation helpers
292 :
293 : void
294 0 : nsSMILAnimationController::StartSampling(nsRefreshDriver* aRefreshDriver)
295 : {
296 0 : NS_ASSERTION(mPauseState == 0, "Starting sampling but controller is paused");
297 0 : NS_ASSERTION(!mDeferredStartSampling,
298 : "Started sampling but the deferred start flag is still set");
299 0 : if (aRefreshDriver) {
300 0 : NS_ABORT_IF_FALSE(!GetRefreshDriver() ||
301 : aRefreshDriver == GetRefreshDriver(),
302 : "Starting sampling with wrong refresh driver");
303 : // We're effectively resuming from a pause so update our current sample time
304 : // or else it will confuse our "average time between samples" calculations.
305 0 : mCurrentSampleTime = mozilla::TimeStamp::Now();
306 0 : aRefreshDriver->AddRefreshObserver(this, Flush_Style);
307 : }
308 0 : }
309 :
310 : void
311 0 : nsSMILAnimationController::StopSampling(nsRefreshDriver* aRefreshDriver)
312 : {
313 0 : if (aRefreshDriver) {
314 : // NOTE: The document might already have been detached from its PresContext
315 : // (and RefreshDriver), which would make GetRefreshDriverForDoc return null.
316 0 : NS_ABORT_IF_FALSE(!GetRefreshDriver() ||
317 : aRefreshDriver == GetRefreshDriver(),
318 : "Stopping sampling with wrong refresh driver");
319 0 : aRefreshDriver->RemoveRefreshObserver(this, Flush_Style);
320 : }
321 0 : }
322 :
323 : void
324 0 : nsSMILAnimationController::MaybeStartSampling(nsRefreshDriver* aRefreshDriver)
325 : {
326 0 : if (mDeferredStartSampling) {
327 : // We've received earlier 'MaybeStartSampling' calls, and we're
328 : // deferring until we get a registered animation.
329 0 : return;
330 : }
331 :
332 0 : if (mAnimationElementTable.Count()) {
333 0 : StartSampling(aRefreshDriver);
334 : } else {
335 0 : mDeferredStartSampling = true;
336 : }
337 : }
338 :
339 : //----------------------------------------------------------------------
340 : // Sample-related methods and callbacks
341 :
342 : PR_CALLBACK PLDHashOperator
343 0 : TransferCachedBaseValue(nsSMILCompositor* aCompositor,
344 : void* aData)
345 : {
346 : nsSMILCompositorTable* lastCompositorTable =
347 0 : static_cast<nsSMILCompositorTable*>(aData);
348 : nsSMILCompositor* lastCompositor =
349 0 : lastCompositorTable->GetEntry(aCompositor->GetKey());
350 :
351 0 : if (lastCompositor) {
352 0 : aCompositor->StealCachedBaseValue(lastCompositor);
353 : }
354 :
355 0 : return PL_DHASH_NEXT;
356 : }
357 :
358 : PR_CALLBACK PLDHashOperator
359 0 : RemoveCompositorFromTable(nsSMILCompositor* aCompositor,
360 : void* aData)
361 : {
362 : nsSMILCompositorTable* lastCompositorTable =
363 0 : static_cast<nsSMILCompositorTable*>(aData);
364 0 : lastCompositorTable->RemoveEntry(aCompositor->GetKey());
365 0 : return PL_DHASH_NEXT;
366 : }
367 :
368 : PR_CALLBACK PLDHashOperator
369 0 : DoClearAnimationEffects(nsSMILCompositor* aCompositor,
370 : void* /*aData*/)
371 : {
372 0 : aCompositor->ClearAnimationEffects();
373 0 : return PL_DHASH_NEXT;
374 : }
375 :
376 : PR_CALLBACK PLDHashOperator
377 0 : DoComposeAttribute(nsSMILCompositor* aCompositor,
378 : void* /*aData*/)
379 : {
380 0 : aCompositor->ComposeAttribute();
381 0 : return PL_DHASH_NEXT;
382 : }
383 :
384 : void
385 0 : nsSMILAnimationController::DoSample()
386 : {
387 0 : DoSample(true); // Skip unchanged time containers
388 0 : }
389 :
390 : void
391 0 : nsSMILAnimationController::DoSample(bool aSkipUnchangedContainers)
392 : {
393 0 : if (!mDocument) {
394 0 : NS_ERROR("Shouldn't be sampling after document has disconnected");
395 0 : return;
396 : }
397 :
398 0 : mResampleNeeded = false;
399 : // Set running sample flag -- do this before flushing styles so that when we
400 : // flush styles we don't end up requesting extra samples
401 0 : mRunningSample = true;
402 0 : nsCOMPtr<nsIDocument> kungFuDeathGrip(mDocument); // keeps 'this' alive too
403 0 : mDocument->FlushPendingNotifications(Flush_Style);
404 :
405 : // WARNING:
406 : // WARNING: the above flush may have destroyed the pres shell and/or
407 : // WARNING: frames and other layout related objects.
408 : // WARNING:
409 :
410 : // STEP 1: Bring model up to date
411 : // (i) Rewind elements where necessary
412 : // (ii) Run milestone samples
413 0 : RewindElements();
414 0 : DoMilestoneSamples();
415 :
416 : // STEP 2: Sample the child time containers
417 : //
418 : // When we sample the child time containers they will simply record the sample
419 : // time in document time.
420 0 : TimeContainerHashtable activeContainers;
421 0 : activeContainers.Init(mChildContainerTable.Count());
422 : SampleTimeContainerParams tcParams = { &activeContainers,
423 0 : aSkipUnchangedContainers };
424 0 : mChildContainerTable.EnumerateEntries(SampleTimeContainer, &tcParams);
425 :
426 : // STEP 3: (i) Sample the timed elements AND
427 : // (ii) Create a table of compositors
428 : //
429 : // (i) Here we sample the timed elements (fetched from the
430 : // nsISMILAnimationElements) which determine from the active time if the
431 : // element is active and what its simple time etc. is. This information is
432 : // then passed to its time client (nsSMILAnimationFunction).
433 : //
434 : // (ii) During the same loop we also build up a table that contains one
435 : // compositor for each animated attribute and which maps animated elements to
436 : // the corresponding compositor for their target attribute.
437 : //
438 : // Note that this compositor table needs to be allocated on the heap so we can
439 : // store it until the next sample. This lets us find out which elements were
440 : // animated in sample 'n-1' but not in sample 'n' (and hence need to have
441 : // their animation effects removed in sample 'n').
442 : //
443 : // Parts (i) and (ii) are not functionally related but we combine them here to
444 : // save iterating over the animation elements twice.
445 :
446 : // Create the compositor table
447 : nsAutoPtr<nsSMILCompositorTable>
448 0 : currentCompositorTable(new nsSMILCompositorTable());
449 0 : currentCompositorTable->Init(0);
450 :
451 : SampleAnimationParams saParams = { &activeContainers,
452 0 : currentCompositorTable };
453 : mAnimationElementTable.EnumerateEntries(SampleAnimation,
454 0 : &saParams);
455 0 : activeContainers.Clear();
456 :
457 : // STEP 4: Compare previous sample's compositors against this sample's.
458 : // (Transfer cached base values across, & remove animation effects from
459 : // no-longer-animated targets.)
460 0 : if (mLastCompositorTable) {
461 : // * Transfer over cached base values, from last sample's compositors
462 : currentCompositorTable->EnumerateEntries(TransferCachedBaseValue,
463 0 : mLastCompositorTable);
464 :
465 : // * For each compositor in current sample's hash table, remove entry from
466 : // prev sample's hash table -- we don't need to clear animation
467 : // effects of those compositors, since they're still being animated.
468 : currentCompositorTable->EnumerateEntries(RemoveCompositorFromTable,
469 0 : mLastCompositorTable);
470 :
471 : // * For each entry that remains in prev sample's hash table (i.e. for
472 : // every target that's no longer animated), clear animation effects.
473 0 : mLastCompositorTable->EnumerateEntries(DoClearAnimationEffects, nsnull);
474 : }
475 :
476 : // STEP 5: Compose currently-animated attributes.
477 : // XXXdholbert: This step traverses our animation targets in an effectively
478 : // random order. For animation from/to 'inherit' values to work correctly
479 : // when the inherited value is *also* being animated, we really should be
480 : // traversing our animated nodes in an ancestors-first order (bug 501183)
481 0 : currentCompositorTable->EnumerateEntries(DoComposeAttribute, nsnull);
482 0 : mRunningSample = false;
483 :
484 : // Update last compositor table
485 0 : mLastCompositorTable = currentCompositorTable.forget();
486 :
487 0 : NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
488 : }
489 :
490 : void
491 0 : nsSMILAnimationController::RewindElements()
492 : {
493 0 : bool rewindNeeded = false;
494 0 : mChildContainerTable.EnumerateEntries(RewindNeeded, &rewindNeeded);
495 0 : if (!rewindNeeded)
496 0 : return;
497 :
498 0 : mAnimationElementTable.EnumerateEntries(RewindAnimation, nsnull);
499 0 : mChildContainerTable.EnumerateEntries(ClearRewindNeeded, nsnull);
500 : }
501 :
502 : /*static*/ PR_CALLBACK PLDHashOperator
503 0 : nsSMILAnimationController::RewindNeeded(TimeContainerPtrKey* aKey,
504 : void* aData)
505 : {
506 0 : NS_ABORT_IF_FALSE(aData,
507 : "Null data pointer during time container enumeration");
508 0 : bool* rewindNeeded = static_cast<bool*>(aData);
509 :
510 0 : nsSMILTimeContainer* container = aKey->GetKey();
511 0 : if (container->NeedsRewind()) {
512 0 : *rewindNeeded = true;
513 0 : return PL_DHASH_STOP;
514 : }
515 :
516 0 : return PL_DHASH_NEXT;
517 : }
518 :
519 : /*static*/ PR_CALLBACK PLDHashOperator
520 0 : nsSMILAnimationController::RewindAnimation(AnimationElementPtrKey* aKey,
521 : void* aData)
522 : {
523 0 : nsISMILAnimationElement* animElem = aKey->GetKey();
524 0 : nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer();
525 0 : if (timeContainer && timeContainer->NeedsRewind()) {
526 0 : animElem->TimedElement().Rewind();
527 : }
528 :
529 0 : return PL_DHASH_NEXT;
530 : }
531 :
532 : /*static*/ PR_CALLBACK PLDHashOperator
533 0 : nsSMILAnimationController::ClearRewindNeeded(TimeContainerPtrKey* aKey,
534 : void* aData)
535 : {
536 0 : aKey->GetKey()->ClearNeedsRewind();
537 0 : return PL_DHASH_NEXT;
538 : }
539 :
540 : void
541 0 : nsSMILAnimationController::DoMilestoneSamples()
542 : {
543 : // We need to sample the timing model but because SMIL operates independently
544 : // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
545 : //
546 : // In between those two sample times a whole string of significant events
547 : // might be expected to take place: events firing, new interdependencies
548 : // between animations resolved and dissolved, etc.
549 : //
550 : // Furthermore, at any given time, we want to sample all the intervals that
551 : // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
552 : // endpoint-exclusive timing model.
553 : //
554 : // So we have the animations (specifically the timed elements) register the
555 : // next significant moment (called a milestone) in their lifetime and then we
556 : // step through the model at each of these moments and sample those animations
557 : // registered for those times. This way events can fire in the correct order,
558 : // dependencies can be resolved etc.
559 :
560 0 : nsSMILTime sampleTime = LL_MININT;
561 :
562 0 : while (true) {
563 : // We want to find any milestones AT OR BEFORE the current sample time so we
564 : // initialise the next milestone to the moment after (1ms after, to be
565 : // precise) the current sample time and see if there are any milestones
566 : // before that. Any other milestones will be dealt with in a subsequent
567 : // sample.
568 0 : nsSMILMilestone nextMilestone(GetCurrentTime() + 1, true);
569 0 : mChildContainerTable.EnumerateEntries(GetNextMilestone, &nextMilestone);
570 :
571 0 : if (nextMilestone.mTime > GetCurrentTime()) {
572 : break;
573 : }
574 :
575 0 : GetMilestoneElementsParams params;
576 0 : params.mMilestone = nextMilestone;
577 0 : mChildContainerTable.EnumerateEntries(GetMilestoneElements, ¶ms);
578 0 : PRUint32 length = params.mElements.Length();
579 :
580 : // During the course of a sampling we don't want to actually go backwards.
581 : // Due to negative offsets, early ends and the like, a timed element might
582 : // register a milestone that is actually in the past. That's fine, but it's
583 : // still only going to get *sampled* with whatever time we're up to and no
584 : // earlier.
585 : //
586 : // Because we're only performing this clamping at the last moment, the
587 : // animations will still all get sampled in the correct order and
588 : // dependencies will be appropriately resolved.
589 0 : sampleTime = NS_MAX(nextMilestone.mTime, sampleTime);
590 :
591 0 : for (PRUint32 i = 0; i < length; ++i) {
592 0 : nsISMILAnimationElement* elem = params.mElements[i].get();
593 0 : NS_ABORT_IF_FALSE(elem, "NULL animation element in list");
594 0 : nsSMILTimeContainer* container = elem->GetTimeContainer();
595 0 : if (!container)
596 : // The container may be nsnull if the element has been detached from its
597 : // parent since registering a milestone.
598 0 : continue;
599 :
600 : nsSMILTimeValue containerTimeValue =
601 0 : container->ParentToContainerTime(sampleTime);
602 0 : if (!containerTimeValue.IsDefinite())
603 0 : continue;
604 :
605 : // Clamp the converted container time to non-negative values.
606 0 : nsSMILTime containerTime = NS_MAX<nsSMILTime>(0, containerTimeValue.GetMillis());
607 :
608 0 : if (nextMilestone.mIsEnd) {
609 0 : elem->TimedElement().SampleEndAt(containerTime);
610 : } else {
611 0 : elem->TimedElement().SampleAt(containerTime);
612 : }
613 : }
614 : }
615 0 : }
616 :
617 : /*static*/ PR_CALLBACK PLDHashOperator
618 0 : nsSMILAnimationController::GetNextMilestone(TimeContainerPtrKey* aKey,
619 : void* aData)
620 : {
621 0 : NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
622 0 : NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table");
623 0 : NS_ABORT_IF_FALSE(aData,
624 : "Null data pointer during time container enumeration");
625 :
626 0 : nsSMILMilestone* nextMilestone = static_cast<nsSMILMilestone*>(aData);
627 :
628 0 : nsSMILTimeContainer* container = aKey->GetKey();
629 0 : if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
630 0 : return PL_DHASH_NEXT;
631 :
632 0 : nsSMILMilestone thisMilestone;
633 : bool didGetMilestone =
634 0 : container->GetNextMilestoneInParentTime(thisMilestone);
635 0 : if (didGetMilestone && thisMilestone < *nextMilestone) {
636 0 : *nextMilestone = thisMilestone;
637 : }
638 :
639 0 : return PL_DHASH_NEXT;
640 : }
641 :
642 : /*static*/ PR_CALLBACK PLDHashOperator
643 0 : nsSMILAnimationController::GetMilestoneElements(TimeContainerPtrKey* aKey,
644 : void* aData)
645 : {
646 0 : NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
647 0 : NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table");
648 0 : NS_ABORT_IF_FALSE(aData,
649 : "Null data pointer during time container enumeration");
650 :
651 : GetMilestoneElementsParams* params =
652 0 : static_cast<GetMilestoneElementsParams*>(aData);
653 :
654 0 : nsSMILTimeContainer* container = aKey->GetKey();
655 0 : if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
656 0 : return PL_DHASH_NEXT;
657 :
658 : container->PopMilestoneElementsAtMilestone(params->mMilestone,
659 0 : params->mElements);
660 :
661 0 : return PL_DHASH_NEXT;
662 : }
663 :
664 : /*static*/ PR_CALLBACK PLDHashOperator
665 0 : nsSMILAnimationController::SampleTimeContainer(TimeContainerPtrKey* aKey,
666 : void* aData)
667 : {
668 0 : NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT);
669 0 : NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT);
670 0 : NS_ENSURE_TRUE(aData, PL_DHASH_NEXT);
671 :
672 : SampleTimeContainerParams* params =
673 0 : static_cast<SampleTimeContainerParams*>(aData);
674 :
675 0 : nsSMILTimeContainer* container = aKey->GetKey();
676 0 : if (!container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN) &&
677 0 : (container->NeedsSample() || !params->mSkipUnchangedContainers)) {
678 0 : container->ClearMilestones();
679 0 : container->Sample();
680 0 : container->MarkSeekFinished();
681 0 : params->mActiveContainers->PutEntry(container);
682 : }
683 :
684 0 : return PL_DHASH_NEXT;
685 : }
686 :
687 : /*static*/ PR_CALLBACK PLDHashOperator
688 0 : nsSMILAnimationController::SampleAnimation(AnimationElementPtrKey* aKey,
689 : void* aData)
690 : {
691 0 : NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT);
692 0 : NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT);
693 0 : NS_ENSURE_TRUE(aData, PL_DHASH_NEXT);
694 :
695 0 : nsISMILAnimationElement* animElem = aKey->GetKey();
696 0 : if (animElem->PassesConditionalProcessingTests()) {
697 0 : SampleAnimationParams* params = static_cast<SampleAnimationParams*>(aData);
698 :
699 0 : SampleTimedElement(animElem, params->mActiveContainers);
700 0 : AddAnimationToCompositorTable(animElem, params->mCompositorTable);
701 : }
702 :
703 0 : return PL_DHASH_NEXT;
704 : }
705 :
706 : /*static*/ void
707 0 : nsSMILAnimationController::SampleTimedElement(
708 : nsISMILAnimationElement* aElement, TimeContainerHashtable* aActiveContainers)
709 : {
710 0 : nsSMILTimeContainer* timeContainer = aElement->GetTimeContainer();
711 0 : if (!timeContainer)
712 0 : return;
713 :
714 : // We'd like to call timeContainer->NeedsSample() here and skip all timed
715 : // elements that belong to paused time containers that don't need a sample,
716 : // but that doesn't work because we've already called Sample() on all the time
717 : // containers so the paused ones don't need a sample any more and they'll
718 : // return false.
719 : //
720 : // Instead we build up a hashmap of active time containers during the previous
721 : // step (SampleTimeContainer) and then test here if the container for this
722 : // timed element is in the list.
723 0 : if (!aActiveContainers->GetEntry(timeContainer))
724 0 : return;
725 :
726 0 : nsSMILTime containerTime = timeContainer->GetCurrentTime();
727 :
728 0 : NS_ABORT_IF_FALSE(!timeContainer->IsSeeking(),
729 : "Doing a regular sample but the time container is still seeking");
730 0 : aElement->TimedElement().SampleAt(containerTime);
731 : }
732 :
733 : /*static*/ void
734 0 : nsSMILAnimationController::AddAnimationToCompositorTable(
735 : nsISMILAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable)
736 : {
737 : // Add a compositor to the hash table if there's not already one there
738 0 : nsSMILTargetIdentifier key;
739 0 : if (!GetTargetIdentifierForAnimation(aElement, key))
740 : // Something's wrong/missing about animation's target; skip this animation
741 : return;
742 :
743 0 : nsSMILAnimationFunction& func = aElement->AnimationFunction();
744 :
745 : // Only add active animation functions. If there are no active animations
746 : // targeting an attribute, no compositor will be created and any previously
747 : // applied animations will be cleared.
748 0 : if (func.IsActiveOrFrozen()) {
749 : // Look up the compositor for our target, & add our animation function
750 : // to its list of animation functions.
751 0 : nsSMILCompositor* result = aCompositorTable->PutEntry(key);
752 0 : result->AddAnimationFunction(&func);
753 :
754 0 : } else if (func.HasChanged()) {
755 : // Look up the compositor for our target, and force it to skip the
756 : // "nothing's changed so don't bother compositing" optimization for this
757 : // sample. |func| is inactive, but it's probably *newly* inactive (since
758 : // it's got HasChanged() == true), so we need to make sure to recompose
759 : // its target.
760 0 : nsSMILCompositor* result = aCompositorTable->PutEntry(key);
761 0 : result->ToggleForceCompositing();
762 :
763 : // We've now made sure that |func|'s inactivity will be reflected as of
764 : // this sample. We need to clear its HasChanged() flag so that it won't
765 : // trigger this same clause in future samples (until it changes again).
766 0 : func.ClearHasChanged();
767 : }
768 : }
769 :
770 : // Helper function that, given a nsISMILAnimationElement, looks up its target
771 : // element & target attribute and populates a nsSMILTargetIdentifier
772 : // for this target.
773 : /*static*/ bool
774 0 : nsSMILAnimationController::GetTargetIdentifierForAnimation(
775 : nsISMILAnimationElement* aAnimElem, nsSMILTargetIdentifier& aResult)
776 : {
777 : // Look up target (animated) element
778 0 : Element* targetElem = aAnimElem->GetTargetElementContent();
779 0 : if (!targetElem)
780 : // Animation has no target elem -- skip it.
781 0 : return false;
782 :
783 : // Look up target (animated) attribute
784 : // SMILANIM section 3.1, attributeName may
785 : // have an XMLNS prefix to indicate the XML namespace.
786 0 : nsCOMPtr<nsIAtom> attributeName;
787 : PRInt32 attributeNamespaceID;
788 0 : if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
789 0 : getter_AddRefs(attributeName)))
790 : // Animation has no target attr -- skip it.
791 0 : return false;
792 :
793 : // Look up target (animated) attribute-type
794 0 : nsSMILTargetAttrType attributeType = aAnimElem->GetTargetAttributeType();
795 :
796 : // Check if an 'auto' attributeType refers to a CSS property or XML attribute.
797 : // Note that SMIL requires we search for CSS properties first. So if they
798 : // overlap, 'auto' = 'CSS'. (SMILANIM 3.1)
799 0 : bool isCSS = false;
800 0 : if (attributeType == eSMILTargetAttrType_auto) {
801 0 : if (attributeNamespaceID == kNameSpaceID_None) {
802 : nsCSSProperty prop =
803 0 : nsCSSProps::LookupProperty(nsDependentAtomString(attributeName));
804 0 : isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
805 : }
806 : } else {
807 0 : isCSS = (attributeType == eSMILTargetAttrType_CSS);
808 : }
809 :
810 : // Construct the key
811 0 : aResult.mElement = targetElem;
812 0 : aResult.mAttributeName = attributeName;
813 0 : aResult.mAttributeNamespaceID = attributeNamespaceID;
814 0 : aResult.mIsCSS = isCSS;
815 :
816 0 : return true;
817 : }
818 :
819 : //----------------------------------------------------------------------
820 : // Add/remove child time containers
821 :
822 : nsresult
823 0 : nsSMILAnimationController::AddChild(nsSMILTimeContainer& aChild)
824 : {
825 0 : TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
826 0 : NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
827 :
828 0 : if (!mPauseState && mChildContainerTable.Count() == 1) {
829 0 : MaybeStartSampling(GetRefreshDriver());
830 0 : Sample(); // Run the first sample manually
831 : }
832 :
833 0 : return NS_OK;
834 : }
835 :
836 : void
837 0 : nsSMILAnimationController::RemoveChild(nsSMILTimeContainer& aChild)
838 : {
839 0 : mChildContainerTable.RemoveEntry(&aChild);
840 :
841 0 : if (!mPauseState && mChildContainerTable.Count() == 0) {
842 0 : StopSampling(GetRefreshDriver());
843 : }
844 0 : }
845 :
846 : // Helper method
847 : nsRefreshDriver*
848 0 : nsSMILAnimationController::GetRefreshDriver()
849 : {
850 0 : if (!mDocument) {
851 0 : NS_ERROR("Requesting refresh driver after document has disconnected!");
852 0 : return nsnull;
853 : }
854 :
855 0 : nsIPresShell* shell = mDocument->GetShell();
856 0 : if (!shell) {
857 0 : return nsnull;
858 : }
859 :
860 0 : nsPresContext* context = shell->GetPresContext();
861 0 : return context ? context->RefreshDriver() : nsnull;
862 : }
863 :
864 : void
865 0 : nsSMILAnimationController::FlagDocumentNeedsFlush()
866 : {
867 0 : mDocument->SetNeedStyleFlush();
868 0 : }
|