dom/smil/nsSMILAnimationController.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/smil/nsSMILAnimationController.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,870 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "nsSMILAnimationController.h"
    1.10 +#include "nsSMILCompositor.h"
    1.11 +#include "nsSMILCSSProperty.h"
    1.12 +#include "nsCSSProps.h"
    1.13 +#include "nsITimer.h"
    1.14 +#include "mozilla/dom/Element.h"
    1.15 +#include "nsIDocument.h"
    1.16 +#include "mozilla/dom/SVGAnimationElement.h"
    1.17 +#include "nsSMILTimedElement.h"
    1.18 +#include <algorithm>
    1.19 +#include "mozilla/AutoRestore.h"
    1.20 +
    1.21 +using namespace mozilla;
    1.22 +using namespace mozilla::dom;
    1.23 +
    1.24 +//----------------------------------------------------------------------
    1.25 +// nsSMILAnimationController implementation
    1.26 +
    1.27 +//----------------------------------------------------------------------
    1.28 +// ctors, dtors, factory methods
    1.29 +
    1.30 +nsSMILAnimationController::nsSMILAnimationController(nsIDocument* aDoc)
    1.31 +  : mAvgTimeBetweenSamples(0),
    1.32 +    mResampleNeeded(false),
    1.33 +    mDeferredStartSampling(false),
    1.34 +    mRunningSample(false),
    1.35 +    mRegisteredWithRefreshDriver(false),
    1.36 +    mDocument(aDoc)
    1.37 +{
    1.38 +  NS_ABORT_IF_FALSE(aDoc, "need a non-null document");
    1.39 +
    1.40 +  nsRefreshDriver* refreshDriver = GetRefreshDriver();
    1.41 +  if (refreshDriver) {
    1.42 +    mStartTime = refreshDriver->MostRecentRefresh();
    1.43 +  } else {
    1.44 +    mStartTime = mozilla::TimeStamp::Now();
    1.45 +  }
    1.46 +  mCurrentSampleTime = mStartTime;
    1.47 +
    1.48 +  Begin();
    1.49 +}
    1.50 +
    1.51 +nsSMILAnimationController::~nsSMILAnimationController()
    1.52 +{
    1.53 +  NS_ASSERTION(mAnimationElementTable.Count() == 0,
    1.54 +               "Animation controller shouldn't be tracking any animation"
    1.55 +               " elements when it dies");
    1.56 +  NS_ASSERTION(!mRegisteredWithRefreshDriver,
    1.57 +               "Leaving stale entry in refresh driver's observer list");
    1.58 +}
    1.59 +
    1.60 +void
    1.61 +nsSMILAnimationController::Disconnect()
    1.62 +{
    1.63 +  NS_ABORT_IF_FALSE(mDocument, "disconnecting when we weren't connected...?");
    1.64 +  NS_ABORT_IF_FALSE(mRefCnt.get() == 1,
    1.65 +                    "Expecting to disconnect when doc is sole remaining owner");
    1.66 +  NS_ASSERTION(mPauseState & nsSMILTimeContainer::PAUSE_PAGEHIDE,
    1.67 +               "Expecting to be paused for pagehide before disconnect");
    1.68 +
    1.69 +  StopSampling(GetRefreshDriver());
    1.70 +
    1.71 +  mDocument = nullptr; // (raw pointer)
    1.72 +}
    1.73 +
    1.74 +//----------------------------------------------------------------------
    1.75 +// nsSMILTimeContainer methods:
    1.76 +
    1.77 +void
    1.78 +nsSMILAnimationController::Pause(uint32_t aType)
    1.79 +{
    1.80 +  nsSMILTimeContainer::Pause(aType);
    1.81 +
    1.82 +  if (mPauseState) {
    1.83 +    mDeferredStartSampling = false;
    1.84 +    StopSampling(GetRefreshDriver());
    1.85 +  }
    1.86 +}
    1.87 +
    1.88 +void
    1.89 +nsSMILAnimationController::Resume(uint32_t aType)
    1.90 +{
    1.91 +  bool wasPaused = (mPauseState != 0);
    1.92 +  // Update mCurrentSampleTime so that calls to GetParentTime--used for
    1.93 +  // calculating parent offsets--are accurate
    1.94 +  mCurrentSampleTime = mozilla::TimeStamp::Now();
    1.95 +
    1.96 +  nsSMILTimeContainer::Resume(aType);
    1.97 +
    1.98 +  if (wasPaused && !mPauseState && mChildContainerTable.Count()) {
    1.99 +    MaybeStartSampling(GetRefreshDriver());
   1.100 +    Sample(); // Run the first sample manually
   1.101 +  }
   1.102 +}
   1.103 +
   1.104 +nsSMILTime
   1.105 +nsSMILAnimationController::GetParentTime() const
   1.106 +{
   1.107 +  return (nsSMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
   1.108 +}
   1.109 +
   1.110 +//----------------------------------------------------------------------
   1.111 +// nsARefreshObserver methods:
   1.112 +NS_IMPL_ADDREF(nsSMILAnimationController)
   1.113 +NS_IMPL_RELEASE(nsSMILAnimationController)
   1.114 +
   1.115 +// nsRefreshDriver Callback function
   1.116 +void
   1.117 +nsSMILAnimationController::WillRefresh(mozilla::TimeStamp aTime)
   1.118 +{
   1.119 +  // Although we never expect aTime to go backwards, when we initialise the
   1.120 +  // animation controller, if we can't get hold of a refresh driver we
   1.121 +  // initialise mCurrentSampleTime to Now(). It may be possible that after
   1.122 +  // doing so we get sampled by a refresh driver whose most recent refresh time
   1.123 +  // predates when we were initialised, so to be safe we make sure to take the
   1.124 +  // most recent time here.
   1.125 +  aTime = std::max(mCurrentSampleTime, aTime);
   1.126 +
   1.127 +  // Sleep detection: If the time between samples is a whole lot greater than we
   1.128 +  // were expecting then we assume the computer went to sleep or someone's
   1.129 +  // messing with the clock. In that case, fiddle our parent offset and use our
   1.130 +  // average time between samples to calculate the new sample time. This
   1.131 +  // prevents us from hanging while trying to catch up on all the missed time.
   1.132 +
   1.133 +  // Smoothing of coefficient for the average function. 0.2 should let us track
   1.134 +  // the sample rate reasonably tightly without being overly affected by
   1.135 +  // occasional delays.
   1.136 +  static const double SAMPLE_DUR_WEIGHTING = 0.2;
   1.137 +  // If the elapsed time exceeds our expectation by this number of times we'll
   1.138 +  // initiate special behaviour to basically ignore the intervening time.
   1.139 +  static const double SAMPLE_DEV_THRESHOLD = 200.0;
   1.140 +
   1.141 +  nsSMILTime elapsedTime =
   1.142 +    (nsSMILTime)(aTime - mCurrentSampleTime).ToMilliseconds();
   1.143 +  if (mAvgTimeBetweenSamples == 0) {
   1.144 +    // First sample.
   1.145 +    mAvgTimeBetweenSamples = elapsedTime;
   1.146 +  } else {
   1.147 +    if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
   1.148 +      // Unexpectedly long delay between samples.
   1.149 +      NS_WARNING("Detected really long delay between samples, continuing from "
   1.150 +                 "previous sample");
   1.151 +      mParentOffset += elapsedTime - mAvgTimeBetweenSamples;
   1.152 +    }
   1.153 +    // Update the moving average. Due to truncation here the average will
   1.154 +    // normally be a little less than it should be but that's probably ok.
   1.155 +    mAvgTimeBetweenSamples =
   1.156 +      (nsSMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
   1.157 +      mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
   1.158 +  }
   1.159 +  mCurrentSampleTime = aTime;
   1.160 +
   1.161 +  Sample();
   1.162 +}
   1.163 +
   1.164 +//----------------------------------------------------------------------
   1.165 +// Animation element registration methods:
   1.166 +
   1.167 +void
   1.168 +nsSMILAnimationController::RegisterAnimationElement(
   1.169 +                                  SVGAnimationElement* aAnimationElement)
   1.170 +{
   1.171 +  mAnimationElementTable.PutEntry(aAnimationElement);
   1.172 +  if (mDeferredStartSampling) {
   1.173 +    mDeferredStartSampling = false;
   1.174 +    if (mChildContainerTable.Count()) {
   1.175 +      // mAnimationElementTable was empty, but now we've added its 1st element
   1.176 +      NS_ABORT_IF_FALSE(mAnimationElementTable.Count() == 1,
   1.177 +                        "we shouldn't have deferred sampling if we already had "
   1.178 +                        "animations registered");
   1.179 +      StartSampling(GetRefreshDriver());
   1.180 +      Sample(); // Run the first sample manually
   1.181 +    } // else, don't sample until a time container is registered (via AddChild)
   1.182 +  }
   1.183 +}
   1.184 +
   1.185 +void
   1.186 +nsSMILAnimationController::UnregisterAnimationElement(
   1.187 +                                  SVGAnimationElement* aAnimationElement)
   1.188 +{
   1.189 +  mAnimationElementTable.RemoveEntry(aAnimationElement);
   1.190 +}
   1.191 +
   1.192 +//----------------------------------------------------------------------
   1.193 +// Page show/hide
   1.194 +
   1.195 +void
   1.196 +nsSMILAnimationController::OnPageShow()
   1.197 +{
   1.198 +  Resume(nsSMILTimeContainer::PAUSE_PAGEHIDE);
   1.199 +}
   1.200 +
   1.201 +void
   1.202 +nsSMILAnimationController::OnPageHide()
   1.203 +{
   1.204 +  Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE);
   1.205 +}
   1.206 +
   1.207 +//----------------------------------------------------------------------
   1.208 +// Cycle-collection support
   1.209 +
   1.210 +void
   1.211 +nsSMILAnimationController::Traverse(
   1.212 +    nsCycleCollectionTraversalCallback* aCallback)
   1.213 +{
   1.214 +  // Traverse last compositor table
   1.215 +  if (mLastCompositorTable) {
   1.216 +    mLastCompositorTable->EnumerateEntries(CompositorTableEntryTraverse,
   1.217 +                                           aCallback);
   1.218 +  }
   1.219 +}
   1.220 +
   1.221 +/*static*/ PLDHashOperator
   1.222 +nsSMILAnimationController::CompositorTableEntryTraverse(
   1.223 +                                      nsSMILCompositor* aCompositor,
   1.224 +                                      void* aArg)
   1.225 +{
   1.226 +  nsCycleCollectionTraversalCallback* cb =
   1.227 +    static_cast<nsCycleCollectionTraversalCallback*>(aArg);
   1.228 +  aCompositor->Traverse(cb);
   1.229 +  return PL_DHASH_NEXT;
   1.230 +}
   1.231 +
   1.232 +void
   1.233 +nsSMILAnimationController::Unlink()
   1.234 +{
   1.235 +  mLastCompositorTable = nullptr;
   1.236 +}
   1.237 +
   1.238 +//----------------------------------------------------------------------
   1.239 +// Refresh driver lifecycle related methods
   1.240 +
   1.241 +void
   1.242 +nsSMILAnimationController::NotifyRefreshDriverCreated(
   1.243 +    nsRefreshDriver* aRefreshDriver)
   1.244 +{
   1.245 +  if (!mPauseState) {
   1.246 +    MaybeStartSampling(aRefreshDriver);
   1.247 +  }
   1.248 +}
   1.249 +
   1.250 +void
   1.251 +nsSMILAnimationController::NotifyRefreshDriverDestroying(
   1.252 +    nsRefreshDriver* aRefreshDriver)
   1.253 +{
   1.254 +  if (!mPauseState && !mDeferredStartSampling) {
   1.255 +    StopSampling(aRefreshDriver);
   1.256 +  }
   1.257 +}
   1.258 +
   1.259 +//----------------------------------------------------------------------
   1.260 +// Timer-related implementation helpers
   1.261 +
   1.262 +void
   1.263 +nsSMILAnimationController::StartSampling(nsRefreshDriver* aRefreshDriver)
   1.264 +{
   1.265 +  NS_ASSERTION(mPauseState == 0, "Starting sampling but controller is paused");
   1.266 +  NS_ASSERTION(!mDeferredStartSampling,
   1.267 +               "Started sampling but the deferred start flag is still set");
   1.268 +  if (aRefreshDriver) {
   1.269 +    MOZ_ASSERT(!mRegisteredWithRefreshDriver,
   1.270 +               "Redundantly registering with refresh driver");
   1.271 +    NS_ABORT_IF_FALSE(!GetRefreshDriver() ||
   1.272 +                      aRefreshDriver == GetRefreshDriver(),
   1.273 +                      "Starting sampling with wrong refresh driver");
   1.274 +    // We're effectively resuming from a pause so update our current sample time
   1.275 +    // or else it will confuse our "average time between samples" calculations.
   1.276 +    mCurrentSampleTime = mozilla::TimeStamp::Now();
   1.277 +    aRefreshDriver->AddRefreshObserver(this, Flush_Style);
   1.278 +    mRegisteredWithRefreshDriver = true;
   1.279 +  }
   1.280 +}
   1.281 +
   1.282 +void
   1.283 +nsSMILAnimationController::StopSampling(nsRefreshDriver* aRefreshDriver)
   1.284 +{
   1.285 +  if (aRefreshDriver && mRegisteredWithRefreshDriver) {
   1.286 +    // NOTE: The document might already have been detached from its PresContext
   1.287 +    // (and RefreshDriver), which would make GetRefreshDriver() return null.
   1.288 +    NS_ABORT_IF_FALSE(!GetRefreshDriver() ||
   1.289 +                      aRefreshDriver == GetRefreshDriver(),
   1.290 +                      "Stopping sampling with wrong refresh driver");
   1.291 +    aRefreshDriver->RemoveRefreshObserver(this, Flush_Style);
   1.292 +    mRegisteredWithRefreshDriver = false;
   1.293 +  }
   1.294 +}
   1.295 +
   1.296 +void
   1.297 +nsSMILAnimationController::MaybeStartSampling(nsRefreshDriver* aRefreshDriver)
   1.298 +{
   1.299 +  if (mDeferredStartSampling) {
   1.300 +    // We've received earlier 'MaybeStartSampling' calls, and we're
   1.301 +    // deferring until we get a registered animation.
   1.302 +    return;
   1.303 +  }
   1.304 +
   1.305 +  if (mAnimationElementTable.Count()) {
   1.306 +    StartSampling(aRefreshDriver);
   1.307 +  } else {
   1.308 +    mDeferredStartSampling = true;
   1.309 +  }
   1.310 +}
   1.311 +
   1.312 +//----------------------------------------------------------------------
   1.313 +// Sample-related methods and callbacks
   1.314 +
   1.315 +PLDHashOperator
   1.316 +TransferCachedBaseValue(nsSMILCompositor* aCompositor,
   1.317 +                        void* aData)
   1.318 +{
   1.319 +  nsSMILCompositorTable* lastCompositorTable =
   1.320 +    static_cast<nsSMILCompositorTable*>(aData);
   1.321 +  nsSMILCompositor* lastCompositor =
   1.322 +    lastCompositorTable->GetEntry(aCompositor->GetKey());
   1.323 +
   1.324 +  if (lastCompositor) {
   1.325 +    aCompositor->StealCachedBaseValue(lastCompositor);
   1.326 +  }
   1.327 +
   1.328 +  return PL_DHASH_NEXT;  
   1.329 +}
   1.330 +
   1.331 +PLDHashOperator
   1.332 +RemoveCompositorFromTable(nsSMILCompositor* aCompositor,
   1.333 +                          void* aData)
   1.334 +{
   1.335 +  nsSMILCompositorTable* lastCompositorTable =
   1.336 +    static_cast<nsSMILCompositorTable*>(aData);
   1.337 +  lastCompositorTable->RemoveEntry(aCompositor->GetKey());
   1.338 +  return PL_DHASH_NEXT;
   1.339 +}
   1.340 +
   1.341 +PLDHashOperator
   1.342 +DoClearAnimationEffects(nsSMILCompositor* aCompositor,
   1.343 +                        void* /*aData*/)
   1.344 +{
   1.345 +  aCompositor->ClearAnimationEffects();
   1.346 +  return PL_DHASH_NEXT;
   1.347 +}
   1.348 +
   1.349 +PLDHashOperator
   1.350 +DoComposeAttribute(nsSMILCompositor* aCompositor,
   1.351 +                   void* /*aData*/)
   1.352 +{
   1.353 +  aCompositor->ComposeAttribute();
   1.354 +  return PL_DHASH_NEXT;
   1.355 +}
   1.356 +
   1.357 +void
   1.358 +nsSMILAnimationController::DoSample()
   1.359 +{
   1.360 +  DoSample(true); // Skip unchanged time containers
   1.361 +}
   1.362 +
   1.363 +void
   1.364 +nsSMILAnimationController::DoSample(bool aSkipUnchangedContainers)
   1.365 +{
   1.366 +  if (!mDocument) {
   1.367 +    NS_ERROR("Shouldn't be sampling after document has disconnected");
   1.368 +    return;
   1.369 +  }
   1.370 +  if (mRunningSample) {
   1.371 +    NS_ERROR("Shouldn't be recursively sampling");
   1.372 +    return;
   1.373 +  }
   1.374 +
   1.375 +  mResampleNeeded = false;
   1.376 +  // Set running sample flag -- do this before flushing styles so that when we
   1.377 +  // flush styles we don't end up requesting extra samples
   1.378 +  AutoRestore<bool> autoRestoreRunningSample(mRunningSample);
   1.379 +  mRunningSample = true;
   1.380 +  
   1.381 +  // STEP 1: Bring model up to date
   1.382 +  // (i)  Rewind elements where necessary
   1.383 +  // (ii) Run milestone samples
   1.384 +  RewindElements();
   1.385 +  DoMilestoneSamples();
   1.386 +
   1.387 +  // STEP 2: Sample the child time containers
   1.388 +  //
   1.389 +  // When we sample the child time containers they will simply record the sample
   1.390 +  // time in document time.
   1.391 +  TimeContainerHashtable activeContainers(mChildContainerTable.Count());
   1.392 +  SampleTimeContainerParams tcParams = { &activeContainers,
   1.393 +                                         aSkipUnchangedContainers };
   1.394 +  mChildContainerTable.EnumerateEntries(SampleTimeContainer, &tcParams);
   1.395 +
   1.396 +  // STEP 3: (i)  Sample the timed elements AND
   1.397 +  //         (ii) Create a table of compositors
   1.398 +  //
   1.399 +  // (i) Here we sample the timed elements (fetched from the
   1.400 +  // SVGAnimationElements) which determine from the active time if the
   1.401 +  // element is active and what its simple time etc. is. This information is
   1.402 +  // then passed to its time client (nsSMILAnimationFunction).
   1.403 +  //
   1.404 +  // (ii) During the same loop we also build up a table that contains one
   1.405 +  // compositor for each animated attribute and which maps animated elements to
   1.406 +  // the corresponding compositor for their target attribute.
   1.407 +  //
   1.408 +  // Note that this compositor table needs to be allocated on the heap so we can
   1.409 +  // store it until the next sample. This lets us find out which elements were
   1.410 +  // animated in sample 'n-1' but not in sample 'n' (and hence need to have
   1.411 +  // their animation effects removed in sample 'n').
   1.412 +  //
   1.413 +  // Parts (i) and (ii) are not functionally related but we combine them here to
   1.414 +  // save iterating over the animation elements twice.
   1.415 +
   1.416 +  // Create the compositor table
   1.417 +  nsAutoPtr<nsSMILCompositorTable>
   1.418 +    currentCompositorTable(new nsSMILCompositorTable(0));
   1.419 +
   1.420 +  SampleAnimationParams saParams = { &activeContainers,
   1.421 +                                     currentCompositorTable };
   1.422 +  mAnimationElementTable.EnumerateEntries(SampleAnimation,
   1.423 +                                          &saParams);
   1.424 +  activeContainers.Clear();
   1.425 +
   1.426 +  // STEP 4: Compare previous sample's compositors against this sample's.
   1.427 +  // (Transfer cached base values across, & remove animation effects from 
   1.428 +  // no-longer-animated targets.)
   1.429 +  if (mLastCompositorTable) {
   1.430 +    // * Transfer over cached base values, from last sample's compositors
   1.431 +    currentCompositorTable->EnumerateEntries(TransferCachedBaseValue,
   1.432 +                                             mLastCompositorTable);
   1.433 +
   1.434 +    // * For each compositor in current sample's hash table, remove entry from
   1.435 +    // prev sample's hash table -- we don't need to clear animation
   1.436 +    // effects of those compositors, since they're still being animated.
   1.437 +    currentCompositorTable->EnumerateEntries(RemoveCompositorFromTable,
   1.438 +                                             mLastCompositorTable);
   1.439 +
   1.440 +    // * For each entry that remains in prev sample's hash table (i.e. for
   1.441 +    // every target that's no longer animated), clear animation effects.
   1.442 +    mLastCompositorTable->EnumerateEntries(DoClearAnimationEffects, nullptr);
   1.443 +  }
   1.444 +
   1.445 +  // return early if there are no active animations to avoid a style flush
   1.446 +  if (currentCompositorTable->Count() == 0) {
   1.447 +    mLastCompositorTable = nullptr;
   1.448 +    return;
   1.449 +  }
   1.450 +
   1.451 +  nsCOMPtr<nsIDocument> kungFuDeathGrip(mDocument);  // keeps 'this' alive too
   1.452 +  mDocument->FlushPendingNotifications(Flush_Style);
   1.453 +
   1.454 +  // WARNING: 
   1.455 +  // WARNING: the above flush may have destroyed the pres shell and/or
   1.456 +  // WARNING: frames and other layout related objects.
   1.457 +  // WARNING:
   1.458 +
   1.459 +  // STEP 5: Compose currently-animated attributes.
   1.460 +  // XXXdholbert: This step traverses our animation targets in an effectively
   1.461 +  // random order. For animation from/to 'inherit' values to work correctly
   1.462 +  // when the inherited value is *also* being animated, we really should be
   1.463 +  // traversing our animated nodes in an ancestors-first order (bug 501183)
   1.464 +  currentCompositorTable->EnumerateEntries(DoComposeAttribute, nullptr);
   1.465 +
   1.466 +  // Update last compositor table
   1.467 +  mLastCompositorTable = currentCompositorTable.forget();
   1.468 +
   1.469 +  NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
   1.470 +}
   1.471 +
   1.472 +void
   1.473 +nsSMILAnimationController::RewindElements()
   1.474 +{
   1.475 +  bool rewindNeeded = false;
   1.476 +  mChildContainerTable.EnumerateEntries(RewindNeeded, &rewindNeeded);
   1.477 +  if (!rewindNeeded)
   1.478 +    return;
   1.479 +
   1.480 +  mAnimationElementTable.EnumerateEntries(RewindAnimation, nullptr);
   1.481 +  mChildContainerTable.EnumerateEntries(ClearRewindNeeded, nullptr);
   1.482 +}
   1.483 +
   1.484 +/*static*/ PLDHashOperator
   1.485 +nsSMILAnimationController::RewindNeeded(TimeContainerPtrKey* aKey,
   1.486 +                                        void* aData)
   1.487 +{
   1.488 +  NS_ABORT_IF_FALSE(aData,
   1.489 +      "Null data pointer during time container enumeration");
   1.490 +  bool* rewindNeeded = static_cast<bool*>(aData);
   1.491 +
   1.492 +  nsSMILTimeContainer* container = aKey->GetKey();
   1.493 +  if (container->NeedsRewind()) {
   1.494 +    *rewindNeeded = true;
   1.495 +    return PL_DHASH_STOP;
   1.496 +  }
   1.497 +
   1.498 +  return PL_DHASH_NEXT;
   1.499 +}
   1.500 +
   1.501 +/*static*/ PLDHashOperator
   1.502 +nsSMILAnimationController::RewindAnimation(AnimationElementPtrKey* aKey,
   1.503 +                                           void* aData)
   1.504 +{
   1.505 +  SVGAnimationElement* animElem = aKey->GetKey();
   1.506 +  nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer();
   1.507 +  if (timeContainer && timeContainer->NeedsRewind()) {
   1.508 +    animElem->TimedElement().Rewind();
   1.509 +  }
   1.510 +
   1.511 +  return PL_DHASH_NEXT;
   1.512 +}
   1.513 +
   1.514 +/*static*/ PLDHashOperator
   1.515 +nsSMILAnimationController::ClearRewindNeeded(TimeContainerPtrKey* aKey,
   1.516 +                                             void* aData)
   1.517 +{
   1.518 +  aKey->GetKey()->ClearNeedsRewind();
   1.519 +  return PL_DHASH_NEXT;
   1.520 +}
   1.521 +
   1.522 +void
   1.523 +nsSMILAnimationController::DoMilestoneSamples()
   1.524 +{
   1.525 +  // We need to sample the timing model but because SMIL operates independently
   1.526 +  // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
   1.527 +  //
   1.528 +  // In between those two sample times a whole string of significant events
   1.529 +  // might be expected to take place: events firing, new interdependencies
   1.530 +  // between animations resolved and dissolved, etc.
   1.531 +  //
   1.532 +  // Furthermore, at any given time, we want to sample all the intervals that
   1.533 +  // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
   1.534 +  // endpoint-exclusive timing model.
   1.535 +  //
   1.536 +  // So we have the animations (specifically the timed elements) register the
   1.537 +  // next significant moment (called a milestone) in their lifetime and then we
   1.538 +  // step through the model at each of these moments and sample those animations
   1.539 +  // registered for those times. This way events can fire in the correct order,
   1.540 +  // dependencies can be resolved etc.
   1.541 +
   1.542 +  nsSMILTime sampleTime = INT64_MIN;
   1.543 +
   1.544 +  while (true) {
   1.545 +    // We want to find any milestones AT OR BEFORE the current sample time so we
   1.546 +    // initialise the next milestone to the moment after (1ms after, to be
   1.547 +    // precise) the current sample time and see if there are any milestones
   1.548 +    // before that. Any other milestones will be dealt with in a subsequent
   1.549 +    // sample.
   1.550 +    nsSMILMilestone nextMilestone(GetCurrentTime() + 1, true);
   1.551 +    mChildContainerTable.EnumerateEntries(GetNextMilestone, &nextMilestone);
   1.552 +
   1.553 +    if (nextMilestone.mTime > GetCurrentTime()) {
   1.554 +      break;
   1.555 +    }
   1.556 +
   1.557 +    GetMilestoneElementsParams params;
   1.558 +    params.mMilestone = nextMilestone;
   1.559 +    mChildContainerTable.EnumerateEntries(GetMilestoneElements, &params);
   1.560 +    uint32_t length = params.mElements.Length();
   1.561 +
   1.562 +    // During the course of a sampling we don't want to actually go backwards.
   1.563 +    // Due to negative offsets, early ends and the like, a timed element might
   1.564 +    // register a milestone that is actually in the past. That's fine, but it's
   1.565 +    // still only going to get *sampled* with whatever time we're up to and no
   1.566 +    // earlier.
   1.567 +    //
   1.568 +    // Because we're only performing this clamping at the last moment, the
   1.569 +    // animations will still all get sampled in the correct order and
   1.570 +    // dependencies will be appropriately resolved.
   1.571 +    sampleTime = std::max(nextMilestone.mTime, sampleTime);
   1.572 +
   1.573 +    for (uint32_t i = 0; i < length; ++i) {
   1.574 +      SVGAnimationElement* elem = params.mElements[i].get();
   1.575 +      NS_ABORT_IF_FALSE(elem, "nullptr animation element in list");
   1.576 +      nsSMILTimeContainer* container = elem->GetTimeContainer();
   1.577 +      if (!container)
   1.578 +        // The container may be nullptr if the element has been detached from its
   1.579 +        // parent since registering a milestone.
   1.580 +        continue;
   1.581 +
   1.582 +      nsSMILTimeValue containerTimeValue =
   1.583 +        container->ParentToContainerTime(sampleTime);
   1.584 +      if (!containerTimeValue.IsDefinite())
   1.585 +        continue;
   1.586 +
   1.587 +      // Clamp the converted container time to non-negative values.
   1.588 +      nsSMILTime containerTime = std::max<nsSMILTime>(0, containerTimeValue.GetMillis());
   1.589 +
   1.590 +      if (nextMilestone.mIsEnd) {
   1.591 +        elem->TimedElement().SampleEndAt(containerTime);
   1.592 +      } else {
   1.593 +        elem->TimedElement().SampleAt(containerTime);
   1.594 +      }
   1.595 +    }
   1.596 +  }
   1.597 +}
   1.598 +
   1.599 +/*static*/ PLDHashOperator
   1.600 +nsSMILAnimationController::GetNextMilestone(TimeContainerPtrKey* aKey,
   1.601 +                                            void* aData)
   1.602 +{
   1.603 +  NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
   1.604 +  NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table");
   1.605 +  NS_ABORT_IF_FALSE(aData,
   1.606 +      "Null data pointer during time container enumeration");
   1.607 +
   1.608 +  nsSMILMilestone* nextMilestone = static_cast<nsSMILMilestone*>(aData);
   1.609 +
   1.610 +  nsSMILTimeContainer* container = aKey->GetKey();
   1.611 +  if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
   1.612 +    return PL_DHASH_NEXT;
   1.613 +
   1.614 +  nsSMILMilestone thisMilestone;
   1.615 +  bool didGetMilestone =
   1.616 +    container->GetNextMilestoneInParentTime(thisMilestone);
   1.617 +  if (didGetMilestone && thisMilestone < *nextMilestone) {
   1.618 +    *nextMilestone = thisMilestone;
   1.619 +  }
   1.620 +
   1.621 +  return PL_DHASH_NEXT;
   1.622 +}
   1.623 +
   1.624 +/*static*/ PLDHashOperator
   1.625 +nsSMILAnimationController::GetMilestoneElements(TimeContainerPtrKey* aKey,
   1.626 +                                                void* aData)
   1.627 +{
   1.628 +  NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
   1.629 +  NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table");
   1.630 +  NS_ABORT_IF_FALSE(aData,
   1.631 +      "Null data pointer during time container enumeration");
   1.632 +
   1.633 +  GetMilestoneElementsParams* params =
   1.634 +    static_cast<GetMilestoneElementsParams*>(aData);
   1.635 +
   1.636 +  nsSMILTimeContainer* container = aKey->GetKey();
   1.637 +  if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
   1.638 +    return PL_DHASH_NEXT;
   1.639 +
   1.640 +  container->PopMilestoneElementsAtMilestone(params->mMilestone,
   1.641 +                                             params->mElements);
   1.642 +
   1.643 +  return PL_DHASH_NEXT;
   1.644 +}
   1.645 +
   1.646 +/*static*/ PLDHashOperator
   1.647 +nsSMILAnimationController::SampleTimeContainer(TimeContainerPtrKey* aKey,
   1.648 +                                               void* aData)
   1.649 +{
   1.650 +  NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT);
   1.651 +  NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT);
   1.652 +  NS_ENSURE_TRUE(aData, PL_DHASH_NEXT);
   1.653 +
   1.654 +  SampleTimeContainerParams* params =
   1.655 +    static_cast<SampleTimeContainerParams*>(aData);
   1.656 +
   1.657 +  nsSMILTimeContainer* container = aKey->GetKey();
   1.658 +  if (!container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN) &&
   1.659 +      (container->NeedsSample() || !params->mSkipUnchangedContainers)) {
   1.660 +    container->ClearMilestones();
   1.661 +    container->Sample();
   1.662 +    container->MarkSeekFinished();
   1.663 +    params->mActiveContainers->PutEntry(container);
   1.664 +  }
   1.665 +
   1.666 +  return PL_DHASH_NEXT;
   1.667 +}
   1.668 +
   1.669 +/*static*/ PLDHashOperator
   1.670 +nsSMILAnimationController::SampleAnimation(AnimationElementPtrKey* aKey,
   1.671 +                                           void* aData)
   1.672 +{
   1.673 +  NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT);
   1.674 +  NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT);
   1.675 +  NS_ENSURE_TRUE(aData, PL_DHASH_NEXT);
   1.676 +
   1.677 +  SVGAnimationElement* animElem = aKey->GetKey();
   1.678 +  if (animElem->PassesConditionalProcessingTests()) {
   1.679 +    SampleAnimationParams* params = static_cast<SampleAnimationParams*>(aData);
   1.680 +
   1.681 +    SampleTimedElement(animElem, params->mActiveContainers);
   1.682 +    AddAnimationToCompositorTable(animElem, params->mCompositorTable);
   1.683 +  }
   1.684 +
   1.685 +  return PL_DHASH_NEXT;
   1.686 +}
   1.687 +
   1.688 +/*static*/ void
   1.689 +nsSMILAnimationController::SampleTimedElement(
   1.690 +  SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers)
   1.691 +{
   1.692 +  nsSMILTimeContainer* timeContainer = aElement->GetTimeContainer();
   1.693 +  if (!timeContainer)
   1.694 +    return;
   1.695 +
   1.696 +  // We'd like to call timeContainer->NeedsSample() here and skip all timed
   1.697 +  // elements that belong to paused time containers that don't need a sample,
   1.698 +  // but that doesn't work because we've already called Sample() on all the time
   1.699 +  // containers so the paused ones don't need a sample any more and they'll
   1.700 +  // return false.
   1.701 +  //
   1.702 +  // Instead we build up a hashmap of active time containers during the previous
   1.703 +  // step (SampleTimeContainer) and then test here if the container for this
   1.704 +  // timed element is in the list.
   1.705 +  if (!aActiveContainers->GetEntry(timeContainer))
   1.706 +    return;
   1.707 +
   1.708 +  nsSMILTime containerTime = timeContainer->GetCurrentTime();
   1.709 +
   1.710 +  NS_ABORT_IF_FALSE(!timeContainer->IsSeeking(),
   1.711 +      "Doing a regular sample but the time container is still seeking");
   1.712 +  aElement->TimedElement().SampleAt(containerTime);
   1.713 +}
   1.714 +
   1.715 +/*static*/ void
   1.716 +nsSMILAnimationController::AddAnimationToCompositorTable(
   1.717 +  SVGAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable)
   1.718 +{
   1.719 +  // Add a compositor to the hash table if there's not already one there
   1.720 +  nsSMILTargetIdentifier key;
   1.721 +  if (!GetTargetIdentifierForAnimation(aElement, key))
   1.722 +    // Something's wrong/missing about animation's target; skip this animation
   1.723 +    return;
   1.724 +
   1.725 +  nsSMILAnimationFunction& func = aElement->AnimationFunction();
   1.726 +
   1.727 +  // Only add active animation functions. If there are no active animations
   1.728 +  // targeting an attribute, no compositor will be created and any previously
   1.729 +  // applied animations will be cleared.
   1.730 +  if (func.IsActiveOrFrozen()) {
   1.731 +    // Look up the compositor for our target, & add our animation function
   1.732 +    // to its list of animation functions.
   1.733 +    nsSMILCompositor* result = aCompositorTable->PutEntry(key);
   1.734 +    result->AddAnimationFunction(&func);
   1.735 +
   1.736 +  } else if (func.HasChanged()) {
   1.737 +    // Look up the compositor for our target, and force it to skip the
   1.738 +    // "nothing's changed so don't bother compositing" optimization for this
   1.739 +    // sample. |func| is inactive, but it's probably *newly* inactive (since
   1.740 +    // it's got HasChanged() == true), so we need to make sure to recompose
   1.741 +    // its target.
   1.742 +    nsSMILCompositor* result = aCompositorTable->PutEntry(key);
   1.743 +    result->ToggleForceCompositing();
   1.744 +
   1.745 +    // We've now made sure that |func|'s inactivity will be reflected as of
   1.746 +    // this sample. We need to clear its HasChanged() flag so that it won't
   1.747 +    // trigger this same clause in future samples (until it changes again).
   1.748 +    func.ClearHasChanged();
   1.749 +  }
   1.750 +}
   1.751 +
   1.752 +static inline bool
   1.753 +IsTransformAttribute(int32_t aNamespaceID, nsIAtom *aAttributeName)
   1.754 +{
   1.755 +  return aNamespaceID == kNameSpaceID_None &&
   1.756 +         (aAttributeName == nsGkAtoms::transform ||
   1.757 +          aAttributeName == nsGkAtoms::patternTransform ||
   1.758 +          aAttributeName == nsGkAtoms::gradientTransform);
   1.759 +}
   1.760 +
   1.761 +// Helper function that, given a SVGAnimationElement, looks up its target
   1.762 +// element & target attribute and populates a nsSMILTargetIdentifier
   1.763 +// for this target.
   1.764 +/*static*/ bool
   1.765 +nsSMILAnimationController::GetTargetIdentifierForAnimation(
   1.766 +    SVGAnimationElement* aAnimElem, nsSMILTargetIdentifier& aResult)
   1.767 +{
   1.768 +  // Look up target (animated) element
   1.769 +  Element* targetElem = aAnimElem->GetTargetElementContent();
   1.770 +  if (!targetElem)
   1.771 +    // Animation has no target elem -- skip it.
   1.772 +    return false;
   1.773 +
   1.774 +  // Look up target (animated) attribute
   1.775 +  // SMILANIM section 3.1, attributeName may
   1.776 +  // have an XMLNS prefix to indicate the XML namespace.
   1.777 +  nsCOMPtr<nsIAtom> attributeName;
   1.778 +  int32_t attributeNamespaceID;
   1.779 +  if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
   1.780 +                                         getter_AddRefs(attributeName)))
   1.781 +    // Animation has no target attr -- skip it.
   1.782 +    return false;
   1.783 +
   1.784 +  // animateTransform can only animate transforms, conversely transforms
   1.785 +  // can only be animated by animateTransform
   1.786 +  if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
   1.787 +      (aAnimElem->Tag() == nsGkAtoms::animateTransform))
   1.788 +    return false;
   1.789 +
   1.790 +  // Look up target (animated) attribute-type
   1.791 +  nsSMILTargetAttrType attributeType = aAnimElem->GetTargetAttributeType();
   1.792 +
   1.793 +  // Check if an 'auto' attributeType refers to a CSS property or XML attribute.
   1.794 +  // Note that SMIL requires we search for CSS properties first. So if they
   1.795 +  // overlap, 'auto' = 'CSS'. (SMILANIM 3.1)
   1.796 +  bool isCSS = false;
   1.797 +  if (attributeType == eSMILTargetAttrType_auto) {
   1.798 +    if (attributeNamespaceID == kNameSpaceID_None) {
   1.799 +      // width/height are special as they may be attributes or for
   1.800 +      // outer-<svg> elements, mapped into style.
   1.801 +      if (attributeName == nsGkAtoms::width ||
   1.802 +          attributeName == nsGkAtoms::height) {
   1.803 +        isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG;
   1.804 +      } else {
   1.805 +        nsCSSProperty prop =
   1.806 +          nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
   1.807 +                                     nsCSSProps::eEnabledForAllContent);
   1.808 +        isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
   1.809 +      }
   1.810 +    }
   1.811 +  } else {
   1.812 +    isCSS = (attributeType == eSMILTargetAttrType_CSS);
   1.813 +  }
   1.814 +
   1.815 +  // Construct the key
   1.816 +  aResult.mElement = targetElem;
   1.817 +  aResult.mAttributeName = attributeName;
   1.818 +  aResult.mAttributeNamespaceID = attributeNamespaceID;
   1.819 +  aResult.mIsCSS = isCSS;
   1.820 +
   1.821 +  return true;
   1.822 +}
   1.823 +
   1.824 +//----------------------------------------------------------------------
   1.825 +// Add/remove child time containers
   1.826 +
   1.827 +nsresult
   1.828 +nsSMILAnimationController::AddChild(nsSMILTimeContainer& aChild)
   1.829 +{
   1.830 +  TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
   1.831 +  NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
   1.832 +
   1.833 +  if (!mPauseState && mChildContainerTable.Count() == 1) {
   1.834 +    MaybeStartSampling(GetRefreshDriver());
   1.835 +    Sample(); // Run the first sample manually
   1.836 +  }
   1.837 +
   1.838 +  return NS_OK;
   1.839 +}
   1.840 +
   1.841 +void
   1.842 +nsSMILAnimationController::RemoveChild(nsSMILTimeContainer& aChild)
   1.843 +{
   1.844 +  mChildContainerTable.RemoveEntry(&aChild);
   1.845 +
   1.846 +  if (!mPauseState && mChildContainerTable.Count() == 0) {
   1.847 +    StopSampling(GetRefreshDriver());
   1.848 +  }
   1.849 +}
   1.850 +
   1.851 +// Helper method
   1.852 +nsRefreshDriver*
   1.853 +nsSMILAnimationController::GetRefreshDriver()
   1.854 +{
   1.855 +  if (!mDocument) {
   1.856 +    NS_ERROR("Requesting refresh driver after document has disconnected!");
   1.857 +    return nullptr;
   1.858 +  }
   1.859 +
   1.860 +  nsIPresShell* shell = mDocument->GetShell();
   1.861 +  if (!shell) {
   1.862 +    return nullptr;
   1.863 +  }
   1.864 +
   1.865 +  nsPresContext* context = shell->GetPresContext();
   1.866 +  return context ? context->RefreshDriver() : nullptr;
   1.867 +}
   1.868 +
   1.869 +void
   1.870 +nsSMILAnimationController::FlagDocumentNeedsFlush()
   1.871 +{
   1.872 +  mDocument->SetNeedStyleFlush();
   1.873 +}

mercurial