Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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, ¶ms); |
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 | } |