SMILAnimationController.cpp (24211B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "SMILAnimationController.h" 8 9 #include <algorithm> 10 11 #include "SMILCSSProperty.h" 12 #include "SMILCompositor.h" 13 #include "mozilla/AutoRestore.h" 14 #include "mozilla/PresShell.h" 15 #include "mozilla/PresShellInlines.h" 16 #include "mozilla/RestyleManager.h" 17 #include "mozilla/SMILTimedElement.h" 18 #include "mozilla/dom/Document.h" 19 #include "mozilla/dom/DocumentInlines.h" 20 #include "mozilla/dom/Element.h" 21 #include "mozilla/dom/SVGAnimationElement.h" 22 #include "nsCSSProps.h" 23 #include "nsContentUtils.h" 24 #include "nsRefreshDriver.h" 25 26 using namespace mozilla::dom; 27 28 namespace mozilla { 29 30 //---------------------------------------------------------------------- 31 // SMILAnimationController implementation 32 33 //---------------------------------------------------------------------- 34 // ctors, dtors, factory methods 35 36 SMILAnimationController::SMILAnimationController(Document* aDoc) 37 : mDocument(aDoc) { 38 MOZ_ASSERT(aDoc, "need a non-null document"); 39 40 if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) { 41 mStartTime = refreshDriver->MostRecentRefresh(); 42 } else { 43 mStartTime = mozilla::TimeStamp::Now(); 44 } 45 mCurrentSampleTime = mStartTime; 46 47 Begin(); 48 } 49 50 SMILAnimationController::~SMILAnimationController() { 51 NS_ASSERTION(mAnimationElementTable.IsEmpty(), 52 "Animation controller shouldn't be tracking any animation" 53 " elements when it dies"); 54 } 55 56 void SMILAnimationController::Disconnect() { 57 MOZ_ASSERT(mDocument, "disconnecting when we weren't connected...?"); 58 MOZ_ASSERT(mRefCnt.get() == 1, 59 "Expecting to disconnect when doc is sole remaining owner"); 60 NS_ASSERTION(mPauseState & SMILTimeContainer::PAUSE_PAGEHIDE, 61 "Expecting to be paused for pagehide before disconnect"); 62 mDocument = nullptr; // (raw pointer) 63 } 64 65 //---------------------------------------------------------------------- 66 // SMILTimeContainer methods: 67 68 void SMILAnimationController::Pause(uint32_t aType) { 69 SMILTimeContainer::Pause(aType); 70 UpdateSampling(); 71 } 72 73 void SMILAnimationController::Resume(uint32_t aType) { 74 bool wasPaused = !!mPauseState; 75 // Update mCurrentSampleTime so that calls to GetParentTime--used for 76 // calculating parent offsets--are accurate 77 mCurrentSampleTime = mozilla::TimeStamp::Now(); 78 79 SMILTimeContainer::Resume(aType); 80 81 if (wasPaused && !mPauseState) { 82 UpdateSampling(); 83 } 84 } 85 86 SMILTime SMILAnimationController::GetParentTime() const { 87 return (SMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds(); 88 } 89 90 // nsRefreshDriver Callback function 91 void SMILAnimationController::WillRefresh(mozilla::TimeStamp aTime) { 92 if (!mIsSampling) { 93 return; 94 } 95 // Although we never expect aTime to go backwards, when we initialise the 96 // animation controller, if we can't get hold of a refresh driver we 97 // initialise mCurrentSampleTime to Now(). It may be possible that after 98 // doing so we get sampled by a refresh driver whose most recent refresh time 99 // predates when we were initialised, so to be safe we make sure to take the 100 // most recent time here. 101 aTime = std::max(mCurrentSampleTime, aTime); 102 103 // Sleep detection: If the time between samples is a whole lot greater than we 104 // were expecting then we assume the computer went to sleep or someone's 105 // messing with the clock. In that case, fiddle our parent offset and use our 106 // average time between samples to calculate the new sample time. This 107 // prevents us from hanging while trying to catch up on all the missed time. 108 109 // Smoothing of coefficient for the average function. 0.2 should let us track 110 // the sample rate reasonably tightly without being overly affected by 111 // occasional delays. 112 static const double SAMPLE_DUR_WEIGHTING = 0.2; 113 // If the elapsed time exceeds our expectation by this number of times we'll 114 // initiate special behaviour to basically ignore the intervening time. 115 static const double SAMPLE_DEV_THRESHOLD = 200.0; 116 117 SMILTime elapsedTime = 118 (SMILTime)(aTime - mCurrentSampleTime).ToMilliseconds(); 119 if (mAvgTimeBetweenSamples == 0) { 120 // First sample. 121 mAvgTimeBetweenSamples = elapsedTime; 122 } else { 123 if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) { 124 // Unexpectedly long delay between samples. 125 NS_WARNING( 126 "Detected really long delay between samples, continuing from " 127 "previous sample"); 128 mParentOffset += elapsedTime - mAvgTimeBetweenSamples; 129 } 130 // Update the moving average. Due to truncation here the average will 131 // normally be a little less than it should be but that's probably ok. 132 mAvgTimeBetweenSamples = 133 (SMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING + 134 mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING)); 135 } 136 mCurrentSampleTime = aTime; 137 138 Sample(); 139 UpdateSampling(); 140 } 141 142 //---------------------------------------------------------------------- 143 // Animation element registration methods: 144 145 void SMILAnimationController::RegisterAnimationElement( 146 SVGAnimationElement* aAnimationElement) { 147 const bool wasEmpty = mAnimationElementTable.IsEmpty(); 148 mAnimationElementTable.PutEntry(aAnimationElement); 149 if (wasEmpty) { 150 UpdateSampling(); 151 } 152 } 153 154 void SMILAnimationController::UnregisterAnimationElement( 155 SVGAnimationElement* aAnimationElement) { 156 mAnimationElementTable.RemoveEntry(aAnimationElement); 157 if (mAnimationElementTable.IsEmpty()) { 158 UpdateSampling(); 159 } 160 } 161 162 //---------------------------------------------------------------------- 163 // Page show/hide 164 165 void SMILAnimationController::OnPageShow() { 166 Resume(SMILTimeContainer::PAUSE_PAGEHIDE); 167 } 168 169 void SMILAnimationController::OnPageHide() { 170 Pause(SMILTimeContainer::PAUSE_PAGEHIDE); 171 } 172 173 //---------------------------------------------------------------------- 174 // Cycle-collection support 175 176 void SMILAnimationController::Traverse( 177 nsCycleCollectionTraversalCallback* aCallback) { 178 // Traverse last compositor table 179 if (mLastCompositorTable) { 180 for (SMILCompositor& compositor : *mLastCompositorTable) { 181 compositor.Traverse(aCallback); 182 } 183 } 184 } 185 186 void SMILAnimationController::Unlink() { mLastCompositorTable = nullptr; } 187 188 //---------------------------------------------------------------------- 189 // Timer-related implementation helpers 190 191 bool SMILAnimationController::ShouldSample() const { 192 return !mPauseState && !mAnimationElementTable.IsEmpty() && 193 !mChildContainerTable.IsEmpty(); 194 } 195 196 void SMILAnimationController::UpdateSampling() { 197 const bool shouldSample = ShouldSample(); 198 if (!shouldSample) { 199 mIsSampling = false; 200 return; 201 } 202 mDocument->MaybeScheduleRenderingPhases( 203 {RenderingPhase::UpdateAnimationsAndSendEvents}); 204 if (!mIsSampling) { 205 mIsSampling = true; 206 // We're effectively resuming from a pause so update our current sample time 207 // or else it will confuse our "average time between samples" calculations. 208 mCurrentSampleTime = mozilla::TimeStamp::Now(); 209 // TODO(emilio): Not doing the sync first sample breaks 210 // test_smilDynamicDelayedBeginElement.xhtml which tests for this 211 // explicitly. Maybe we can avoid this? 212 Sample(); // Run the first sample manually. 213 } 214 } 215 216 //---------------------------------------------------------------------- 217 // Sample-related methods and callbacks 218 219 void SMILAnimationController::DoSample() { 220 DoSample(true); // Skip unchanged time containers 221 } 222 223 void SMILAnimationController::DoSample(bool aSkipUnchangedContainers) { 224 if (!mDocument) { 225 NS_ERROR("Shouldn't be sampling after document has disconnected"); 226 return; 227 } 228 if (mRunningSample) { 229 NS_ERROR("Shouldn't be recursively sampling"); 230 return; 231 } 232 233 mResampleNeeded = false; 234 235 // Set running sample flag -- do this before flushing styles so that when we 236 // flush styles we don't end up requesting extra samples 237 AutoRestore<bool> autoRestoreRunningSample(mRunningSample); 238 mRunningSample = true; 239 240 // STEP 1: Bring model up to date 241 // (i) Rewind elements where necessary 242 // (ii) Run milestone samples 243 RewindElements(); 244 DoMilestoneSamples(); 245 246 // STEP 2: Sample the child time containers 247 // 248 // When we sample the child time containers they will simply record the sample 249 // time in document time. 250 TimeContainerHashtable activeContainers(mChildContainerTable.Count()); 251 for (SMILTimeContainer* container : mChildContainerTable.Keys()) { 252 if (!container) { 253 continue; 254 } 255 256 if (!container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN) && 257 (container->NeedsSample() || !aSkipUnchangedContainers)) { 258 container->ClearMilestones(); 259 container->Sample(); 260 container->MarkSeekFinished(); 261 activeContainers.PutEntry(container); 262 } 263 } 264 265 // STEP 3: (i) Sample the timed elements AND 266 // (ii) Create a table of compositors 267 // 268 // (i) Here we sample the timed elements (fetched from the 269 // SVGAnimationElements) which determine from the active time if the 270 // element is active and what its simple time etc. is. This information is 271 // then passed to its time client (SMILAnimationFunction). 272 // 273 // (ii) During the same loop we also build up a table that contains one 274 // compositor for each animated attribute and which maps animated elements to 275 // the corresponding compositor for their target attribute. 276 // 277 // Note that this compositor table needs to be allocated on the heap so we can 278 // store it until the next sample. This lets us find out which elements were 279 // animated in sample 'n-1' but not in sample 'n' (and hence need to have 280 // their animation effects removed in sample 'n'). 281 // 282 // Parts (i) and (ii) are not functionally related but we combine them here to 283 // save iterating over the animation elements twice. 284 285 // Create the compositor table 286 UniquePtr<SMILCompositorTable> currentCompositorTable( 287 new SMILCompositorTable(0)); 288 nsTArray<RefPtr<SVGAnimationElement>> animElems( 289 mAnimationElementTable.Count()); 290 291 for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) { 292 SampleTimedElement(animElem, &activeContainers); 293 AddAnimationToCompositorTable(animElem, currentCompositorTable.get()); 294 animElems.AppendElement(animElem); 295 } 296 activeContainers.Clear(); 297 298 // STEP 4: Compare previous sample's compositors against this sample's. 299 // (Transfer cached base values across, & remove animation effects from 300 // no-longer-animated targets.) 301 if (mLastCompositorTable) { 302 // * Transfer over cached base values, from last sample's compositors 303 for (SMILCompositor& compositor : *currentCompositorTable) { 304 SMILCompositor* lastCompositor = 305 mLastCompositorTable->GetEntry(compositor.GetKey()); 306 307 if (lastCompositor) { 308 compositor.StealCachedBaseValue(lastCompositor); 309 if (!lastCompositor->HasSameNumberOfAnimationFunctionsAs(compositor)) { 310 // If we have multiple animations on the same element, they share a 311 // compositor. If an active animation ends, it will no longer be in 312 // the compositor table. We need to force compositing to ensure we 313 // render the element with any remaining frozen animations even though 314 // they would not normally trigger compositing. 315 compositor.ToggleForceCompositing(); 316 } 317 } 318 } 319 320 // * For each compositor in current sample's hash table, remove entry from 321 // prev sample's hash table -- we don't need to clear animation 322 // effects of those compositors, since they're still being animated. 323 for (const auto& key : currentCompositorTable->Keys()) { 324 mLastCompositorTable->RemoveEntry(key); 325 } 326 327 // * For each entry that remains in prev sample's hash table (i.e. for 328 // every target that's no longer animated), clear animation effects. 329 for (SMILCompositor& compositor : *mLastCompositorTable) { 330 compositor.ClearAnimationEffects(); 331 } 332 } 333 334 // return early if there are no active animations to avoid a style flush 335 if (currentCompositorTable->IsEmpty()) { 336 mLastCompositorTable = nullptr; 337 return; 338 } 339 340 // STEP 5: Compose currently-animated attributes. 341 // XXXdholbert: This step traverses our animation targets in an effectively 342 // random order. For animation from/to 'inherit' values to work correctly 343 // when the inherited value is *also* being animated, we really should be 344 // traversing our animated nodes in an ancestors-first order (bug 501183) 345 bool mightHavePendingStyleUpdates = false; 346 for (auto& compositor : *currentCompositorTable) { 347 compositor.ComposeAttribute(mightHavePendingStyleUpdates); 348 } 349 350 // Update last compositor table 351 mLastCompositorTable = std::move(currentCompositorTable); 352 mMightHavePendingStyleUpdates = mightHavePendingStyleUpdates; 353 354 NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!"); 355 } 356 357 void SMILAnimationController::RewindElements() { 358 const bool rewindNeeded = std::any_of( 359 mChildContainerTable.Keys().cbegin(), mChildContainerTable.Keys().cend(), 360 [](SMILTimeContainer* container) { return container->NeedsRewind(); }); 361 362 if (!rewindNeeded) return; 363 364 for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) { 365 SMILTimeContainer* timeContainer = animElem->GetTimeContainer(); 366 if (timeContainer && timeContainer->NeedsRewind()) { 367 animElem->TimedElement().Rewind(); 368 } 369 } 370 371 for (SMILTimeContainer* container : mChildContainerTable.Keys()) { 372 container->ClearNeedsRewind(); 373 } 374 } 375 376 void SMILAnimationController::DoMilestoneSamples() { 377 // We need to sample the timing model but because SMIL operates independently 378 // of the frame-rate, we can get one sample at t=0s and the next at t=10min. 379 // 380 // In between those two sample times a whole string of significant events 381 // might be expected to take place: events firing, new interdependencies 382 // between animations resolved and dissolved, etc. 383 // 384 // Furthermore, at any given time, we want to sample all the intervals that 385 // end at that time BEFORE any that begin. This behaviour is implied by SMIL's 386 // endpoint-exclusive timing model. 387 // 388 // So we have the animations (specifically the timed elements) register the 389 // next significant moment (called a milestone) in their lifetime and then we 390 // step through the model at each of these moments and sample those animations 391 // registered for those times. This way events can fire in the correct order, 392 // dependencies can be resolved etc. 393 394 SMILTime sampleTime = std::numeric_limits<SMILTime>::min(); 395 396 while (true) { 397 // We want to find any milestones AT OR BEFORE the current sample time so we 398 // initialise the next milestone to the moment after (1ms after, to be 399 // precise) the current sample time and see if there are any milestones 400 // before that. Any other milestones will be dealt with in a subsequent 401 // sample. 402 SMILMilestone nextMilestone(GetCurrentTimeAsSMILTime() + 1, true); 403 for (SMILTimeContainer* container : mChildContainerTable.Keys()) { 404 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) { 405 continue; 406 } 407 SMILMilestone thisMilestone; 408 bool didGetMilestone = 409 container->GetNextMilestoneInParentTime(thisMilestone); 410 if (didGetMilestone && thisMilestone < nextMilestone) { 411 nextMilestone = thisMilestone; 412 } 413 } 414 415 if (nextMilestone.mTime > GetCurrentTimeAsSMILTime()) { 416 break; 417 } 418 419 nsTArray<RefPtr<dom::SVGAnimationElement>> elements; 420 for (SMILTimeContainer* container : mChildContainerTable.Keys()) { 421 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) { 422 continue; 423 } 424 container->PopMilestoneElementsAtMilestone(nextMilestone, elements); 425 } 426 427 // During the course of a sampling we don't want to actually go backwards. 428 // Due to negative offsets, early ends and the like, a timed element might 429 // register a milestone that is actually in the past. That's fine, but it's 430 // still only going to get *sampled* with whatever time we're up to and no 431 // earlier. 432 // 433 // Because we're only performing this clamping at the last moment, the 434 // animations will still all get sampled in the correct order and 435 // dependencies will be appropriately resolved. 436 sampleTime = std::max(nextMilestone.mTime, sampleTime); 437 438 for (RefPtr<dom::SVGAnimationElement>& elem : elements) { 439 MOZ_ASSERT(elem, "nullptr animation element in list"); 440 SMILTimeContainer* container = elem->GetTimeContainer(); 441 if (!container) 442 // The container may be nullptr if the element has been detached from 443 // its parent since registering a milestone. 444 continue; 445 446 SMILTimeValue containerTimeValue = 447 container->ParentToContainerTime(sampleTime); 448 if (!containerTimeValue.IsDefinite()) continue; 449 450 // Clamp the converted container time to non-negative values. 451 SMILTime containerTime = 452 std::max<SMILTime>(0, containerTimeValue.GetMillis()); 453 454 if (nextMilestone.mIsEnd) { 455 elem->TimedElement().SampleEndAt(containerTime); 456 } else { 457 elem->TimedElement().SampleAt(containerTime); 458 } 459 } 460 } 461 } 462 463 /*static*/ 464 void SMILAnimationController::SampleTimedElement( 465 SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers) { 466 SMILTimeContainer* timeContainer = aElement->GetTimeContainer(); 467 if (!timeContainer) return; 468 469 // We'd like to call timeContainer->NeedsSample() here and skip all timed 470 // elements that belong to paused time containers that don't need a sample, 471 // but that doesn't work because we've already called Sample() on all the time 472 // containers so the paused ones don't need a sample any more and they'll 473 // return false. 474 // 475 // Instead we build up a hashmap of active time containers during the previous 476 // step (SampleTimeContainer) and then test here if the container for this 477 // timed element is in the list. 478 if (!aActiveContainers->GetEntry(timeContainer)) return; 479 480 SMILTime containerTime = timeContainer->GetCurrentTimeAsSMILTime(); 481 482 MOZ_ASSERT(!timeContainer->IsSeeking(), 483 "Doing a regular sample but the time container is still seeking"); 484 aElement->TimedElement().SampleAt(containerTime); 485 } 486 487 /*static*/ 488 void SMILAnimationController::AddAnimationToCompositorTable( 489 SVGAnimationElement* aElement, SMILCompositorTable* aCompositorTable) { 490 // Add a compositor to the hash table if there's not already one there 491 SMILTargetIdentifier key; 492 if (!GetTargetIdentifierForAnimation(aElement, key)) 493 // Something's wrong/missing about animation's target; skip this animation 494 return; 495 496 SMILAnimationFunction& func = aElement->AnimationFunction(); 497 498 // Only add active animation functions. If there are no active animations 499 // targeting an attribute, no compositor will be created and any previously 500 // applied animations will be cleared. 501 if (func.IsActiveOrFrozen()) { 502 // Look up the compositor for our target, & add our animation function 503 // to its list of animation functions. 504 SMILCompositor* result = aCompositorTable->PutEntry(key); 505 result->AddAnimationFunction(&func); 506 507 } else if (func.HasChanged()) { 508 // Look up the compositor for our target, and force it to skip the 509 // "nothing's changed so don't bother compositing" optimization for this 510 // sample. |func| is inactive, but it's probably *newly* inactive (since 511 // it's got HasChanged() == true), so we need to make sure to recompose 512 // its target. 513 SMILCompositor* result = aCompositorTable->PutEntry(key); 514 result->ToggleForceCompositing(); 515 516 // We've now made sure that |func|'s inactivity will be reflected as of 517 // this sample. We need to clear its HasChanged() flag so that it won't 518 // trigger this same clause in future samples (until it changes again). 519 func.ClearHasChanged(); 520 } 521 } 522 523 static inline bool IsTransformAttribute(const Element* aElement, 524 int32_t aNamespaceID, 525 const nsAtom* aAttributeName) { 526 if (aNamespaceID != kNameSpaceID_None) { 527 return false; 528 } 529 if (auto* svgElement = SVGElement::FromNode(aElement)) { 530 return svgElement->GetTransformListAttrName() == aAttributeName; 531 } 532 return false; 533 } 534 535 // Helper function that, given a SVGAnimationElement, looks up its target 536 // element & target attribute and populates a SMILTargetIdentifier 537 // for this target. 538 /*static*/ 539 bool SMILAnimationController::GetTargetIdentifierForAnimation( 540 SVGAnimationElement* aAnimElem, SMILTargetIdentifier& aResult) { 541 // Look up target (animated) element 542 Element* targetElem = aAnimElem->GetTargetElementContent(); 543 if (!targetElem) 544 // Animation has no target elem -- skip it. 545 return false; 546 547 // Look up target (animated) attribute 548 // SMILANIM section 3.1, attributeName may 549 // have an XMLNS prefix to indicate the XML namespace. 550 RefPtr<nsAtom> attributeName; 551 int32_t attributeNamespaceID; 552 if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID, 553 getter_AddRefs(attributeName))) 554 // Animation has no target attr -- skip it. 555 return false; 556 557 // animateTransform can only animate transforms, conversely transforms 558 // can only be animated by animateTransform 559 if (IsTransformAttribute(targetElem, attributeNamespaceID, attributeName) != 560 aAnimElem->IsSVGElement(nsGkAtoms::animateTransform)) 561 return false; 562 563 // Construct the key 564 aResult.mElement = targetElem; 565 aResult.mAttributeName = attributeName; 566 aResult.mAttributeNamespaceID = attributeNamespaceID; 567 568 return true; 569 } 570 571 void SMILAnimationController::PreTraverse() { PreTraverseInSubtree(nullptr); } 572 573 void SMILAnimationController::PreTraverseInSubtree(Element* aRoot) { 574 MOZ_ASSERT(NS_IsMainThread()); 575 576 if (!mMightHavePendingStyleUpdates) { 577 return; 578 } 579 580 nsPresContext* context = mDocument->GetPresContext(); 581 if (!context) { 582 return; 583 } 584 585 for (SVGAnimationElement* animElement : mAnimationElementTable.Keys()) { 586 SMILTargetIdentifier key; 587 if (!GetTargetIdentifierForAnimation(animElement, key)) { 588 // Something's wrong/missing about animation's target; skip this animation 589 continue; 590 } 591 592 // Ignore restyles that aren't in the flattened tree subtree rooted at 593 // aRoot. 594 if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOf( 595 key.mElement, aRoot)) { 596 continue; 597 } 598 599 context->RestyleManager()->PostRestyleEventForAnimations( 600 key.mElement, PseudoStyleRequest::NotPseudo(), 601 RestyleHint::RESTYLE_SMIL); 602 } 603 604 // Only clear the mMightHavePendingStyleUpdates flag if we definitely posted 605 // all restyles. 606 if (!aRoot) { 607 mMightHavePendingStyleUpdates = false; 608 } 609 } 610 611 //---------------------------------------------------------------------- 612 // Add/remove child time containers 613 614 nsresult SMILAnimationController::AddChild(SMILTimeContainer& aChild) { 615 const bool wasEmpty = mChildContainerTable.IsEmpty(); 616 TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild); 617 NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY); 618 if (wasEmpty) { 619 UpdateSampling(); 620 } 621 return NS_OK; 622 } 623 624 void SMILAnimationController::RemoveChild(SMILTimeContainer& aChild) { 625 mChildContainerTable.RemoveEntry(&aChild); 626 if (mChildContainerTable.IsEmpty()) { 627 UpdateSampling(); 628 } 629 } 630 631 // Helper method 632 nsRefreshDriver* SMILAnimationController::GetRefreshDriver() { 633 if (!mDocument) { 634 NS_ERROR("Requesting refresh driver after document has disconnected!"); 635 return nullptr; 636 } 637 638 nsPresContext* context = mDocument->GetPresContext(); 639 return context ? context->RefreshDriver() : nullptr; 640 } 641 642 void SMILAnimationController::FlagDocumentNeedsFlush() { 643 if (PresShell* presShell = mDocument->GetPresShell()) { 644 presShell->SetNeedStyleFlush(); 645 } 646 } 647 648 } // namespace mozilla