dom/smil/nsSMILAnimationController.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial