layout/style/nsAnimationManager.cpp

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

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

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

michael@0 1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
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 "nsAnimationManager.h"
michael@0 7 #include "nsTransitionManager.h"
michael@0 8
michael@0 9 #include "mozilla/EventDispatcher.h"
michael@0 10 #include "mozilla/MemoryReporting.h"
michael@0 11
michael@0 12 #include "nsPresContext.h"
michael@0 13 #include "nsRuleProcessorData.h"
michael@0 14 #include "nsStyleSet.h"
michael@0 15 #include "nsStyleChangeList.h"
michael@0 16 #include "nsCSSRules.h"
michael@0 17 #include "RestyleManager.h"
michael@0 18 #include "nsStyleAnimation.h"
michael@0 19 #include "nsLayoutUtils.h"
michael@0 20 #include "nsIFrame.h"
michael@0 21 #include "nsIDocument.h"
michael@0 22 #include "ActiveLayerTracker.h"
michael@0 23 #include <math.h>
michael@0 24
michael@0 25 using namespace mozilla;
michael@0 26 using namespace mozilla::css;
michael@0 27
michael@0 28 ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement,
michael@0 29 nsIAtom *aElementProperty,
michael@0 30 nsAnimationManager *aAnimationManager,
michael@0 31 TimeStamp aNow)
michael@0 32 : CommonElementAnimationData(aElement, aElementProperty,
michael@0 33 aAnimationManager, aNow),
michael@0 34 mNeedsRefreshes(true)
michael@0 35 {
michael@0 36 }
michael@0 37
michael@0 38 static void
michael@0 39 ElementAnimationsPropertyDtor(void *aObject,
michael@0 40 nsIAtom *aPropertyName,
michael@0 41 void *aPropertyValue,
michael@0 42 void *aData)
michael@0 43 {
michael@0 44 ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
michael@0 45 #ifdef DEBUG
michael@0 46 NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice");
michael@0 47 ea->mCalledPropertyDtor = true;
michael@0 48 #endif
michael@0 49 delete ea;
michael@0 50 }
michael@0 51
michael@0 52 double
michael@0 53 ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration,
michael@0 54 TimeDuration aIterationDuration,
michael@0 55 double aIterationCount,
michael@0 56 uint32_t aDirection,
michael@0 57 StyleAnimation* aAnimation,
michael@0 58 ElementAnimations* aEa,
michael@0 59 EventArray* aEventsToDispatch)
michael@0 60 {
michael@0 61 MOZ_ASSERT(!aAnimation == !aEa && !aAnimation == !aEventsToDispatch);
michael@0 62
michael@0 63 // Set |currentIterationCount| to the (fractional) number of
michael@0 64 // iterations we've completed up to the current position.
michael@0 65 double currentIterationCount = aElapsedDuration / aIterationDuration;
michael@0 66 bool dispatchStartOrIteration = false;
michael@0 67 if (currentIterationCount >= aIterationCount) {
michael@0 68 if (aAnimation) {
michael@0 69 // Dispatch 'animationend' when needed.
michael@0 70 if (aAnimation->mLastNotification !=
michael@0 71 StyleAnimation::LAST_NOTIFICATION_END) {
michael@0 72 aAnimation->mLastNotification = StyleAnimation::LAST_NOTIFICATION_END;
michael@0 73 AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END,
michael@0 74 aElapsedDuration, aEa->PseudoElement());
michael@0 75 aEventsToDispatch->AppendElement(ei);
michael@0 76 }
michael@0 77
michael@0 78 if (!aAnimation->FillsForwards()) {
michael@0 79 // No animation data.
michael@0 80 return -1;
michael@0 81 }
michael@0 82 } else {
michael@0 83 // If aAnimation is null, that means we're on the compositor
michael@0 84 // thread. We want to just keep filling forwards until the main
michael@0 85 // thread gets around to updating the compositor thread (which
michael@0 86 // might take a little while). So just assume we fill fowards and
michael@0 87 // move on.
michael@0 88 }
michael@0 89 currentIterationCount = aIterationCount;
michael@0 90 } else {
michael@0 91 if (aAnimation && !aAnimation->IsPaused()) {
michael@0 92 aEa->mNeedsRefreshes = true;
michael@0 93 }
michael@0 94 if (currentIterationCount < 0.0) {
michael@0 95 NS_ASSERTION(aAnimation, "Should not run animation that hasn't started yet on the compositor");
michael@0 96 if (!aAnimation->FillsBackwards()) {
michael@0 97 // No animation data.
michael@0 98 return -1;
michael@0 99 }
michael@0 100 currentIterationCount = 0.0;
michael@0 101 } else {
michael@0 102 dispatchStartOrIteration = aAnimation && !aAnimation->IsPaused();
michael@0 103 }
michael@0 104 }
michael@0 105
michael@0 106 // Set |positionInIteration| to the position from 0% to 100% along
michael@0 107 // the keyframes.
michael@0 108 NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
michael@0 109 double whichIteration = floor(currentIterationCount);
michael@0 110 if (whichIteration == aIterationCount && whichIteration != 0.0) {
michael@0 111 // When the animation's iteration count is an integer (as it
michael@0 112 // normally is), we need to end at 100% of its last iteration
michael@0 113 // rather than 0% of the next one (unless it's zero).
michael@0 114 whichIteration -= 1.0;
michael@0 115 }
michael@0 116 double positionInIteration = currentIterationCount - whichIteration;
michael@0 117
michael@0 118 bool thisIterationReverse = false;
michael@0 119 switch (aDirection) {
michael@0 120 case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
michael@0 121 thisIterationReverse = false;
michael@0 122 break;
michael@0 123 case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
michael@0 124 thisIterationReverse = true;
michael@0 125 break;
michael@0 126 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
michael@0 127 // uint64_t has more integer precision than double does, so if
michael@0 128 // whichIteration is that large, we've already lost and we're just
michael@0 129 // guessing. But the animation is presumably oscillating so fast
michael@0 130 // it doesn't matter anyway.
michael@0 131 thisIterationReverse = (uint64_t(whichIteration) & 1) == 1;
michael@0 132 break;
michael@0 133 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
michael@0 134 // see as previous case
michael@0 135 thisIterationReverse = (uint64_t(whichIteration) & 1) == 0;
michael@0 136 break;
michael@0 137 }
michael@0 138 if (thisIterationReverse) {
michael@0 139 positionInIteration = 1.0 - positionInIteration;
michael@0 140 }
michael@0 141
michael@0 142 // Dispatch 'animationstart' or 'animationiteration' when needed.
michael@0 143 if (aAnimation && dispatchStartOrIteration &&
michael@0 144 whichIteration != aAnimation->mLastNotification) {
michael@0 145 // Notify 'animationstart' even if a negative delay puts us
michael@0 146 // past the first iteration.
michael@0 147 // Note that when somebody changes the animation-duration
michael@0 148 // dynamically, this will fire an extra iteration event
michael@0 149 // immediately in many cases. It's not clear to me if that's the
michael@0 150 // right thing to do.
michael@0 151 uint32_t message =
michael@0 152 aAnimation->mLastNotification == StyleAnimation::LAST_NOTIFICATION_NONE
michael@0 153 ? NS_ANIMATION_START : NS_ANIMATION_ITERATION;
michael@0 154
michael@0 155 aAnimation->mLastNotification = whichIteration;
michael@0 156 AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message,
michael@0 157 aElapsedDuration, aEa->PseudoElement());
michael@0 158 aEventsToDispatch->AppendElement(ei);
michael@0 159 }
michael@0 160
michael@0 161 return positionInIteration;
michael@0 162 }
michael@0 163
michael@0 164 void
michael@0 165 ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
michael@0 166 EventArray& aEventsToDispatch,
michael@0 167 bool aIsThrottled)
michael@0 168 {
michael@0 169 if (!mNeedsRefreshes) {
michael@0 170 mStyleRuleRefreshTime = aRefreshTime;
michael@0 171 return;
michael@0 172 }
michael@0 173
michael@0 174 // If we're performing animations on the compositor thread, then we can skip
michael@0 175 // most of the work in this method. But even if we are throttled, then we
michael@0 176 // have to do the work if an animation is ending in order to get correct end
michael@0 177 // of animation behaviour (the styles of the animation disappear, or the fill
michael@0 178 // mode behaviour). This loop checks for any finishing animations and forces
michael@0 179 // the style recalculation if we find any.
michael@0 180 if (aIsThrottled) {
michael@0 181 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
michael@0 182 StyleAnimation &anim = mAnimations[animIdx];
michael@0 183
michael@0 184 if (anim.mProperties.Length() == 0 ||
michael@0 185 anim.mIterationDuration.ToMilliseconds() <= 0.0) {
michael@0 186 continue;
michael@0 187 }
michael@0 188
michael@0 189 uint32_t oldLastNotification = anim.mLastNotification;
michael@0 190
michael@0 191 // We need to call GetPositionInIteration here to populate
michael@0 192 // aEventsToDispatch.
michael@0 193 // The ElapsedDurationAt() call here handles pausing. But:
michael@0 194 // FIXME: avoid recalculating every time when paused.
michael@0 195 GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
michael@0 196 anim.mIterationDuration, anim.mIterationCount,
michael@0 197 anim.mDirection, &anim, this, &aEventsToDispatch);
michael@0 198
michael@0 199 // GetPositionInIteration just adjusted mLastNotification; check
michael@0 200 // its new value against the value before we called
michael@0 201 // GetPositionInIteration.
michael@0 202 // XXX We shouldn't really be using mLastNotification as a general
michael@0 203 // indicator that the animation has finished, it should be reserved for
michael@0 204 // events. If we use it differently in the future this use might need
michael@0 205 // changing.
michael@0 206 if (!anim.mIsRunningOnCompositor ||
michael@0 207 (anim.mLastNotification != oldLastNotification &&
michael@0 208 anim.mLastNotification == StyleAnimation::LAST_NOTIFICATION_END)) {
michael@0 209 aIsThrottled = false;
michael@0 210 break;
michael@0 211 }
michael@0 212 }
michael@0 213 }
michael@0 214
michael@0 215 if (aIsThrottled) {
michael@0 216 return;
michael@0 217 }
michael@0 218
michael@0 219 // mStyleRule may be null and valid, if we have no style to apply.
michael@0 220 if (mStyleRuleRefreshTime.IsNull() ||
michael@0 221 mStyleRuleRefreshTime != aRefreshTime) {
michael@0 222 mStyleRuleRefreshTime = aRefreshTime;
michael@0 223 mStyleRule = nullptr;
michael@0 224 // We'll set mNeedsRefreshes to true below in all cases where we need them.
michael@0 225 mNeedsRefreshes = false;
michael@0 226
michael@0 227 // FIXME(spec): assume that properties in higher animations override
michael@0 228 // those in lower ones.
michael@0 229 // Therefore, we iterate from last animation to first.
michael@0 230 nsCSSPropertySet properties;
michael@0 231
michael@0 232 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
michael@0 233 StyleAnimation &anim = mAnimations[animIdx];
michael@0 234
michael@0 235 if (anim.mProperties.Length() == 0 ||
michael@0 236 anim.mIterationDuration.ToMilliseconds() <= 0.0) {
michael@0 237 // No animation data.
michael@0 238 continue;
michael@0 239 }
michael@0 240
michael@0 241 // The ElapsedDurationAt() call here handles pausing. But:
michael@0 242 // FIXME: avoid recalculating every time when paused.
michael@0 243 double positionInIteration =
michael@0 244 GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
michael@0 245 anim.mIterationDuration, anim.mIterationCount,
michael@0 246 anim.mDirection, &anim, this,
michael@0 247 &aEventsToDispatch);
michael@0 248
michael@0 249 // The position is -1 when we don't have fill data for the current time,
michael@0 250 // so we shouldn't animate.
michael@0 251 if (positionInIteration == -1)
michael@0 252 continue;
michael@0 253
michael@0 254 NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
michael@0 255 positionInIteration <= 1.0,
michael@0 256 "position should be in [0-1]");
michael@0 257
michael@0 258 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
michael@0 259 propIdx != propEnd; ++propIdx)
michael@0 260 {
michael@0 261 const AnimationProperty &prop = anim.mProperties[propIdx];
michael@0 262
michael@0 263 NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
michael@0 264 "incorrect first from key");
michael@0 265 NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
michael@0 266 == 1.0,
michael@0 267 "incorrect last to key");
michael@0 268
michael@0 269 if (properties.HasProperty(prop.mProperty)) {
michael@0 270 // A later animation already set this property.
michael@0 271 continue;
michael@0 272 }
michael@0 273 properties.AddProperty(prop.mProperty);
michael@0 274
michael@0 275 NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
michael@0 276 "property should not be in animations if it "
michael@0 277 "has no segments");
michael@0 278
michael@0 279 // FIXME: Maybe cache the current segment?
michael@0 280 const AnimationPropertySegment *segment = prop.mSegments.Elements(),
michael@0 281 *segmentEnd = segment + prop.mSegments.Length();
michael@0 282 while (segment->mToKey < positionInIteration) {
michael@0 283 NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
michael@0 284 "incorrect keys");
michael@0 285 ++segment;
michael@0 286 if (segment == segmentEnd) {
michael@0 287 NS_ABORT_IF_FALSE(false, "incorrect positionInIteration");
michael@0 288 break; // in order to continue in outer loop (just below)
michael@0 289 }
michael@0 290 NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
michael@0 291 "incorrect keys");
michael@0 292 }
michael@0 293 if (segment == segmentEnd) {
michael@0 294 continue;
michael@0 295 }
michael@0 296 NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
michael@0 297 "incorrect keys");
michael@0 298 NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() &&
michael@0 299 size_t(segment - prop.mSegments.Elements()) <
michael@0 300 prop.mSegments.Length(),
michael@0 301 "out of array bounds");
michael@0 302
michael@0 303 if (!mStyleRule) {
michael@0 304 // Allocate the style rule now that we know we have animation data.
michael@0 305 mStyleRule = new css::AnimValuesStyleRule();
michael@0 306 }
michael@0 307
michael@0 308 double positionInSegment = (positionInIteration - segment->mFromKey) /
michael@0 309 (segment->mToKey - segment->mFromKey);
michael@0 310 double valuePosition =
michael@0 311 segment->mTimingFunction.GetValue(positionInSegment);
michael@0 312
michael@0 313 nsStyleAnimation::Value *val =
michael@0 314 mStyleRule->AddEmptyValue(prop.mProperty);
michael@0 315
michael@0 316 #ifdef DEBUG
michael@0 317 bool result =
michael@0 318 #endif
michael@0 319 nsStyleAnimation::Interpolate(prop.mProperty,
michael@0 320 segment->mFromValue, segment->mToValue,
michael@0 321 valuePosition, *val);
michael@0 322 NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
michael@0 323 }
michael@0 324 }
michael@0 325 }
michael@0 326 }
michael@0 327
michael@0 328
michael@0 329 bool
michael@0 330 ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const
michael@0 331 {
michael@0 332 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
michael@0 333 const StyleAnimation &anim = mAnimations[animIdx];
michael@0 334 if (anim.HasAnimationOfProperty(aProperty)) {
michael@0 335 return true;
michael@0 336 }
michael@0 337 }
michael@0 338 return false;
michael@0 339 }
michael@0 340
michael@0 341 bool
michael@0 342 ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
michael@0 343 {
michael@0 344 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
michael@0 345 if (!frame) {
michael@0 346 return false;
michael@0 347 }
michael@0 348
michael@0 349 if (mElementProperty != nsGkAtoms::animationsProperty) {
michael@0 350 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
michael@0 351 nsCString message;
michael@0 352 message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 (");
michael@0 353 message.Append(nsAtomCString(mElementProperty));
michael@0 354 message.AppendLiteral(")");
michael@0 355 LogAsyncAnimationFailure(message, mElement);
michael@0 356 }
michael@0 357 return false;
michael@0 358 }
michael@0 359
michael@0 360 TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
michael@0 361
michael@0 362 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
michael@0 363 const StyleAnimation& anim = mAnimations[animIdx];
michael@0 364 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
michael@0 365 propIdx != propEnd; ++propIdx) {
michael@0 366 if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) &&
michael@0 367 anim.IsRunningAt(now)) {
michael@0 368 aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
michael@0 369 break;
michael@0 370 }
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 bool hasOpacity = false;
michael@0 375 bool hasTransform = false;
michael@0 376 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
michael@0 377 const StyleAnimation& anim = mAnimations[animIdx];
michael@0 378 if (!anim.IsRunningAt(now)) {
michael@0 379 continue;
michael@0 380 }
michael@0 381
michael@0 382 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
michael@0 383 propIdx != propEnd; ++propIdx) {
michael@0 384 const AnimationProperty& prop = anim.mProperties[propIdx];
michael@0 385 if (!CanAnimatePropertyOnCompositor(mElement,
michael@0 386 prop.mProperty,
michael@0 387 aFlags) ||
michael@0 388 IsCompositorAnimationDisabledForFrame(frame)) {
michael@0 389 return false;
michael@0 390 }
michael@0 391 if (prop.mProperty == eCSSProperty_opacity) {
michael@0 392 hasOpacity = true;
michael@0 393 } else if (prop.mProperty == eCSSProperty_transform) {
michael@0 394 hasTransform = true;
michael@0 395 }
michael@0 396 }
michael@0 397 }
michael@0 398 // This animation can be done on the compositor. Mark the frame as active, in
michael@0 399 // case we are able to throttle this animation.
michael@0 400 if (hasOpacity) {
michael@0 401 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
michael@0 402 }
michael@0 403 if (hasTransform) {
michael@0 404 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
michael@0 405 }
michael@0 406 return true;
michael@0 407 }
michael@0 408
michael@0 409 ElementAnimations*
michael@0 410 nsAnimationManager::GetElementAnimations(dom::Element *aElement,
michael@0 411 nsCSSPseudoElements::Type aPseudoType,
michael@0 412 bool aCreateIfNeeded)
michael@0 413 {
michael@0 414 if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
michael@0 415 // Early return for the most common case.
michael@0 416 return nullptr;
michael@0 417 }
michael@0 418
michael@0 419 nsIAtom *propName;
michael@0 420 if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
michael@0 421 propName = nsGkAtoms::animationsProperty;
michael@0 422 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
michael@0 423 propName = nsGkAtoms::animationsOfBeforeProperty;
michael@0 424 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
michael@0 425 propName = nsGkAtoms::animationsOfAfterProperty;
michael@0 426 } else {
michael@0 427 NS_ASSERTION(!aCreateIfNeeded,
michael@0 428 "should never try to create transitions for pseudo "
michael@0 429 "other than :before or :after");
michael@0 430 return nullptr;
michael@0 431 }
michael@0 432 ElementAnimations *ea = static_cast<ElementAnimations*>(
michael@0 433 aElement->GetProperty(propName));
michael@0 434 if (!ea && aCreateIfNeeded) {
michael@0 435 // FIXME: Consider arena-allocating?
michael@0 436 ea = new ElementAnimations(aElement, propName, this,
michael@0 437 mPresContext->RefreshDriver()->MostRecentRefresh());
michael@0 438 nsresult rv = aElement->SetProperty(propName, ea,
michael@0 439 ElementAnimationsPropertyDtor, false);
michael@0 440 if (NS_FAILED(rv)) {
michael@0 441 NS_WARNING("SetProperty failed");
michael@0 442 delete ea;
michael@0 443 return nullptr;
michael@0 444 }
michael@0 445 if (propName == nsGkAtoms::animationsProperty) {
michael@0 446 aElement->SetMayHaveAnimations();
michael@0 447 }
michael@0 448
michael@0 449 AddElementData(ea);
michael@0 450 }
michael@0 451
michael@0 452 return ea;
michael@0 453 }
michael@0 454
michael@0 455
michael@0 456 void
michael@0 457 nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET)
michael@0 458 {
michael@0 459 aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(),
michael@0 460 mPendingEvents,
michael@0 461 false);
michael@0 462 CheckNeedsRefresh();
michael@0 463 }
michael@0 464
michael@0 465 /* virtual */ void
michael@0 466 nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
michael@0 467 {
michael@0 468 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
michael@0 469 "pres context mismatch");
michael@0 470 nsIStyleRule *rule =
michael@0 471 GetAnimationRule(aData->mElement,
michael@0 472 nsCSSPseudoElements::ePseudo_NotPseudoElement);
michael@0 473 if (rule) {
michael@0 474 aData->mRuleWalker->Forward(rule);
michael@0 475 }
michael@0 476 }
michael@0 477
michael@0 478 /* virtual */ void
michael@0 479 nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData)
michael@0 480 {
michael@0 481 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
michael@0 482 "pres context mismatch");
michael@0 483 if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before &&
michael@0 484 aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) {
michael@0 485 return;
michael@0 486 }
michael@0 487
michael@0 488 // FIXME: Do we really want to be the only thing keeping a
michael@0 489 // pseudo-element alive? I *think* the non-animation restyle should
michael@0 490 // handle that, but should add a test.
michael@0 491 nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType);
michael@0 492 if (rule) {
michael@0 493 aData->mRuleWalker->Forward(rule);
michael@0 494 }
michael@0 495 }
michael@0 496
michael@0 497 /* virtual */ void
michael@0 498 nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
michael@0 499 {
michael@0 500 }
michael@0 501
michael@0 502 #ifdef MOZ_XUL
michael@0 503 /* virtual */ void
michael@0 504 nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
michael@0 505 {
michael@0 506 }
michael@0 507 #endif
michael@0 508
michael@0 509 /* virtual */ size_t
michael@0 510 nsAnimationManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
michael@0 511 {
michael@0 512 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
michael@0 513
michael@0 514 // Measurement of the following members may be added later if DMD finds it is
michael@0 515 // worthwhile:
michael@0 516 // - mPendingEvents
michael@0 517 }
michael@0 518
michael@0 519 /* virtual */ size_t
michael@0 520 nsAnimationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
michael@0 521 {
michael@0 522 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
michael@0 523 }
michael@0 524
michael@0 525 nsIStyleRule*
michael@0 526 nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
michael@0 527 mozilla::dom::Element* aElement)
michael@0 528 {
michael@0 529 if (!mPresContext->IsProcessingAnimationStyleChange()) {
michael@0 530 if (!mPresContext->IsDynamic()) {
michael@0 531 // For print or print preview, ignore animations.
michael@0 532 return nullptr;
michael@0 533 }
michael@0 534
michael@0 535 // Everything that causes our animation data to change triggers a
michael@0 536 // style change, which in turn triggers a non-animation restyle.
michael@0 537 // Likewise, when we initially construct frames, we're not in a
michael@0 538 // style change, but also not in an animation restyle.
michael@0 539
michael@0 540 const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
michael@0 541 ElementAnimations *ea =
michael@0 542 GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false);
michael@0 543 if (!ea &&
michael@0 544 disp->mAnimationNameCount == 1 &&
michael@0 545 disp->mAnimations[0].GetName().IsEmpty()) {
michael@0 546 return nullptr;
michael@0 547 }
michael@0 548
michael@0 549 // build the animations list
michael@0 550 InfallibleTArray<StyleAnimation> newAnimations;
michael@0 551 BuildAnimations(aStyleContext, newAnimations);
michael@0 552
michael@0 553 if (newAnimations.IsEmpty()) {
michael@0 554 if (ea) {
michael@0 555 ea->Destroy();
michael@0 556 }
michael@0 557 return nullptr;
michael@0 558 }
michael@0 559
michael@0 560 TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
michael@0 561
michael@0 562 if (ea) {
michael@0 563 ea->mStyleRule = nullptr;
michael@0 564 ea->mStyleRuleRefreshTime = TimeStamp();
michael@0 565 ea->UpdateAnimationGeneration(mPresContext);
michael@0 566
michael@0 567 // Copy over the start times and (if still paused) pause starts
michael@0 568 // for each animation (matching on name only) that was also in the
michael@0 569 // old list of animations.
michael@0 570 // This means that we honor dynamic changes, which isn't what the
michael@0 571 // spec says to do, but WebKit seems to honor at least some of
michael@0 572 // them. See
michael@0 573 // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
michael@0 574 // In order to honor what the spec said, we'd copy more data over
michael@0 575 // (or potentially optimize BuildAnimations to avoid rebuilding it
michael@0 576 // in the first place).
michael@0 577 if (!ea->mAnimations.IsEmpty()) {
michael@0 578 for (uint32_t newIdx = 0, newEnd = newAnimations.Length();
michael@0 579 newIdx != newEnd; ++newIdx) {
michael@0 580 StyleAnimation *newAnim = &newAnimations[newIdx];
michael@0 581
michael@0 582 // Find the matching animation with this name in the old list
michael@0 583 // of animations. Because of this code, they must all have
michael@0 584 // the same start time, though they might differ in pause
michael@0 585 // state. So if a page uses multiple copies of the same
michael@0 586 // animation in one element's animation list, and gives them
michael@0 587 // different pause states, they, well, get what they deserve.
michael@0 588 // We'll use the last one since it's more likely to be the one
michael@0 589 // doing something.
michael@0 590 const StyleAnimation *oldAnim = nullptr;
michael@0 591 for (uint32_t oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) {
michael@0 592 const StyleAnimation *a = &ea->mAnimations[oldIdx];
michael@0 593 if (a->mName == newAnim->mName) {
michael@0 594 oldAnim = a;
michael@0 595 break;
michael@0 596 }
michael@0 597 }
michael@0 598 if (!oldAnim) {
michael@0 599 continue;
michael@0 600 }
michael@0 601
michael@0 602 newAnim->mStartTime = oldAnim->mStartTime;
michael@0 603 newAnim->mLastNotification = oldAnim->mLastNotification;
michael@0 604
michael@0 605 if (oldAnim->IsPaused()) {
michael@0 606 if (newAnim->IsPaused()) {
michael@0 607 // Copy pause start just like start time.
michael@0 608 newAnim->mPauseStart = oldAnim->mPauseStart;
michael@0 609 } else {
michael@0 610 // Handle change in pause state by adjusting start
michael@0 611 // time to unpause.
michael@0 612 newAnim->mStartTime += refreshTime - oldAnim->mPauseStart;
michael@0 613 }
michael@0 614 }
michael@0 615 }
michael@0 616 }
michael@0 617 } else {
michael@0 618 ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(),
michael@0 619 true);
michael@0 620 }
michael@0 621 ea->mAnimations.SwapElements(newAnimations);
michael@0 622 ea->mNeedsRefreshes = true;
michael@0 623
michael@0 624 ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false);
michael@0 625 CheckNeedsRefresh();
michael@0 626 // We don't actually dispatch the mPendingEvents now. We'll either
michael@0 627 // dispatch them the next time we get a refresh driver notification
michael@0 628 // or the next time somebody calls
michael@0 629 // nsPresShell::FlushPendingNotifications.
michael@0 630 if (!mPendingEvents.IsEmpty()) {
michael@0 631 mPresContext->Document()->SetNeedStyleFlush();
michael@0 632 }
michael@0 633 }
michael@0 634
michael@0 635 return GetAnimationRule(aElement, aStyleContext->GetPseudoType());
michael@0 636 }
michael@0 637
michael@0 638 class PercentageHashKey : public PLDHashEntryHdr
michael@0 639 {
michael@0 640 public:
michael@0 641 typedef const float& KeyType;
michael@0 642 typedef const float* KeyTypePointer;
michael@0 643
michael@0 644 PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { }
michael@0 645 PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { }
michael@0 646 ~PercentageHashKey() { }
michael@0 647
michael@0 648 KeyType GetKey() const { return mValue; }
michael@0 649 bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
michael@0 650
michael@0 651 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
michael@0 652 static PLDHashNumber HashKey(KeyTypePointer aKey) {
michael@0 653 static_assert(sizeof(PLDHashNumber) == sizeof(uint32_t),
michael@0 654 "this hash function assumes PLDHashNumber is uint32_t");
michael@0 655 static_assert(PLDHashNumber(-1) > PLDHashNumber(0),
michael@0 656 "this hash function assumes PLDHashNumber is uint32_t");
michael@0 657 float key = *aKey;
michael@0 658 NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range");
michael@0 659 return PLDHashNumber(key * UINT32_MAX);
michael@0 660 }
michael@0 661 enum { ALLOW_MEMMOVE = true };
michael@0 662
michael@0 663 private:
michael@0 664 const float mValue;
michael@0 665 };
michael@0 666
michael@0 667 struct KeyframeData {
michael@0 668 float mKey;
michael@0 669 uint32_t mIndex; // store original order since sort algorithm is not stable
michael@0 670 nsCSSKeyframeRule *mRule;
michael@0 671 };
michael@0 672
michael@0 673 struct KeyframeDataComparator {
michael@0 674 bool Equals(const KeyframeData& A, const KeyframeData& B) const {
michael@0 675 return A.mKey == B.mKey && A.mIndex == B.mIndex;
michael@0 676 }
michael@0 677 bool LessThan(const KeyframeData& A, const KeyframeData& B) const {
michael@0 678 return A.mKey < B.mKey || (A.mKey == B.mKey && A.mIndex < B.mIndex);
michael@0 679 }
michael@0 680 };
michael@0 681
michael@0 682 class ResolvedStyleCache {
michael@0 683 public:
michael@0 684 ResolvedStyleCache() : mCache(16) {}
michael@0 685 nsStyleContext* Get(nsPresContext *aPresContext,
michael@0 686 nsStyleContext *aParentStyleContext,
michael@0 687 nsCSSKeyframeRule *aKeyframe);
michael@0 688
michael@0 689 private:
michael@0 690 nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache;
michael@0 691 };
michael@0 692
michael@0 693 nsStyleContext*
michael@0 694 ResolvedStyleCache::Get(nsPresContext *aPresContext,
michael@0 695 nsStyleContext *aParentStyleContext,
michael@0 696 nsCSSKeyframeRule *aKeyframe)
michael@0 697 {
michael@0 698 // FIXME (spec): The css3-animations spec isn't very clear about how
michael@0 699 // properties are resolved when they have values that depend on other
michael@0 700 // properties (e.g., values in 'em'). I presume that they're resolved
michael@0 701 // relative to the other styles of the element. The question is
michael@0 702 // whether they are resolved relative to other animations: I assume
michael@0 703 // that they're not, since that would prevent us from caching a lot of
michael@0 704 // data that we'd really like to cache (in particular, the
michael@0 705 // nsStyleAnimation::Value values in AnimationPropertySegment).
michael@0 706 nsStyleContext *result = mCache.GetWeak(aKeyframe);
michael@0 707 if (!result) {
michael@0 708 nsCOMArray<nsIStyleRule> rules;
michael@0 709 rules.AppendObject(aKeyframe);
michael@0 710 nsRefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->
michael@0 711 ResolveStyleByAddingRules(aParentStyleContext, rules);
michael@0 712 mCache.Put(aKeyframe, resultStrong);
michael@0 713 result = resultStrong;
michael@0 714 }
michael@0 715 return result;
michael@0 716 }
michael@0 717
michael@0 718 void
michael@0 719 nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext,
michael@0 720 InfallibleTArray<StyleAnimation>&
michael@0 721 aAnimations)
michael@0 722 {
michael@0 723 NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array");
michael@0 724
michael@0 725 ResolvedStyleCache resolvedStyles;
michael@0 726
michael@0 727 const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
michael@0 728 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
michael@0 729 for (uint32_t animIdx = 0, animEnd = disp->mAnimationNameCount;
michael@0 730 animIdx != animEnd; ++animIdx) {
michael@0 731 const nsAnimation& aSrc = disp->mAnimations[animIdx];
michael@0 732 StyleAnimation& aDest = *aAnimations.AppendElement();
michael@0 733
michael@0 734 aDest.mName = aSrc.GetName();
michael@0 735 aDest.mIterationCount = aSrc.GetIterationCount();
michael@0 736 aDest.mDirection = aSrc.GetDirection();
michael@0 737 aDest.mFillMode = aSrc.GetFillMode();
michael@0 738 aDest.mPlayState = aSrc.GetPlayState();
michael@0 739
michael@0 740 aDest.mDelay = TimeDuration::FromMilliseconds(aSrc.GetDelay());
michael@0 741 aDest.mStartTime = now;
michael@0 742 if (aDest.IsPaused()) {
michael@0 743 aDest.mPauseStart = now;
michael@0 744 } else {
michael@0 745 aDest.mPauseStart = TimeStamp();
michael@0 746 }
michael@0 747
michael@0 748 aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration());
michael@0 749
michael@0 750 nsCSSKeyframesRule* rule =
michael@0 751 mPresContext->StyleSet()->KeyframesRuleForName(mPresContext, aDest.mName);
michael@0 752 if (!rule) {
michael@0 753 // no segments
michael@0 754 continue;
michael@0 755 }
michael@0 756
michael@0 757 // While current drafts of css3-animations say that later keyframes
michael@0 758 // with the same key entirely replace earlier ones (no cascading),
michael@0 759 // this is a bad idea and contradictory to the rest of CSS. So
michael@0 760 // we're going to keep all the keyframes for each key and then do
michael@0 761 // the replacement on a per-property basis rather than a per-rule
michael@0 762 // basis, just like everything else in CSS.
michael@0 763
michael@0 764 AutoInfallibleTArray<KeyframeData, 16> sortedKeyframes;
michael@0 765
michael@0 766 for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount();
michael@0 767 ruleIdx != ruleEnd; ++ruleIdx) {
michael@0 768 css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx);
michael@0 769 NS_ABORT_IF_FALSE(cssRule, "must have rule");
michael@0 770 NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
michael@0 771 "must be keyframe rule");
michael@0 772 nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule);
michael@0 773
michael@0 774 const nsTArray<float> &keys = kfRule->GetKeys();
michael@0 775 for (uint32_t keyIdx = 0, keyEnd = keys.Length();
michael@0 776 keyIdx != keyEnd; ++keyIdx) {
michael@0 777 float key = keys[keyIdx];
michael@0 778 // FIXME (spec): The spec doesn't say what to do with
michael@0 779 // out-of-range keyframes. We'll ignore them.
michael@0 780 // (And PercentageHashKey currently assumes we either ignore or
michael@0 781 // clamp them.)
michael@0 782 if (0.0f <= key && key <= 1.0f) {
michael@0 783 KeyframeData *data = sortedKeyframes.AppendElement();
michael@0 784 data->mKey = key;
michael@0 785 data->mIndex = ruleIdx;
michael@0 786 data->mRule = kfRule;
michael@0 787 }
michael@0 788 }
michael@0 789 }
michael@0 790
michael@0 791 sortedKeyframes.Sort(KeyframeDataComparator());
michael@0 792
michael@0 793 if (sortedKeyframes.Length() == 0) {
michael@0 794 // no segments
michael@0 795 continue;
michael@0 796 }
michael@0 797
michael@0 798 // Record the properties that are present in any keyframe rules we
michael@0 799 // are using.
michael@0 800 nsCSSPropertySet properties;
michael@0 801
michael@0 802 for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
michael@0 803 kfIdx != kfEnd; ++kfIdx) {
michael@0 804 css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration();
michael@0 805 for (uint32_t propIdx = 0, propEnd = decl->Count();
michael@0 806 propIdx != propEnd; ++propIdx) {
michael@0 807 nsCSSProperty prop = decl->GetPropertyAt(propIdx);
michael@0 808 if (prop != eCSSPropertyExtra_variable) {
michael@0 809 // CSS Variables are not animatable
michael@0 810 properties.AddProperty(prop);
michael@0 811 }
michael@0 812 }
michael@0 813 }
michael@0 814
michael@0 815 for (nsCSSProperty prop = nsCSSProperty(0);
michael@0 816 prop < eCSSProperty_COUNT_no_shorthands;
michael@0 817 prop = nsCSSProperty(prop + 1)) {
michael@0 818 if (!properties.HasProperty(prop) ||
michael@0 819 nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
michael@0 820 continue;
michael@0 821 }
michael@0 822
michael@0 823 // Build a list of the keyframes to use for this property. This
michael@0 824 // means we need every keyframe with the property in it, except
michael@0 825 // for those keyframes where a later keyframe with the *same key*
michael@0 826 // also has the property.
michael@0 827 AutoInfallibleTArray<uint32_t, 16> keyframesWithProperty;
michael@0 828 float lastKey = 100.0f; // an invalid key
michael@0 829 for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
michael@0 830 kfIdx != kfEnd; ++kfIdx) {
michael@0 831 KeyframeData &kf = sortedKeyframes[kfIdx];
michael@0 832 if (!kf.mRule->Declaration()->HasProperty(prop)) {
michael@0 833 continue;
michael@0 834 }
michael@0 835 if (kf.mKey == lastKey) {
michael@0 836 // Replace previous occurrence of same key.
michael@0 837 keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx;
michael@0 838 } else {
michael@0 839 keyframesWithProperty.AppendElement(kfIdx);
michael@0 840 }
michael@0 841 lastKey = kf.mKey;
michael@0 842 }
michael@0 843
michael@0 844 AnimationProperty &propData = *aDest.mProperties.AppendElement();
michael@0 845 propData.mProperty = prop;
michael@0 846
michael@0 847 KeyframeData *fromKeyframe = nullptr;
michael@0 848 nsRefPtr<nsStyleContext> fromContext;
michael@0 849 bool interpolated = true;
michael@0 850 for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length();
michael@0 851 wpIdx != wpEnd; ++wpIdx) {
michael@0 852 uint32_t kfIdx = keyframesWithProperty[wpIdx];
michael@0 853 KeyframeData &toKeyframe = sortedKeyframes[kfIdx];
michael@0 854
michael@0 855 nsRefPtr<nsStyleContext> toContext =
michael@0 856 resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule);
michael@0 857
michael@0 858 if (fromKeyframe) {
michael@0 859 interpolated = interpolated &&
michael@0 860 BuildSegment(propData.mSegments, prop, aSrc,
michael@0 861 fromKeyframe->mKey, fromContext,
michael@0 862 fromKeyframe->mRule->Declaration(),
michael@0 863 toKeyframe.mKey, toContext);
michael@0 864 } else {
michael@0 865 if (toKeyframe.mKey != 0.0f) {
michael@0 866 // There's no data for this property at 0%, so use the
michael@0 867 // cascaded value above us.
michael@0 868 interpolated = interpolated &&
michael@0 869 BuildSegment(propData.mSegments, prop, aSrc,
michael@0 870 0.0f, aStyleContext, nullptr,
michael@0 871 toKeyframe.mKey, toContext);
michael@0 872 }
michael@0 873 }
michael@0 874
michael@0 875 fromContext = toContext;
michael@0 876 fromKeyframe = &toKeyframe;
michael@0 877 }
michael@0 878
michael@0 879 if (fromKeyframe->mKey != 1.0f) {
michael@0 880 // There's no data for this property at 100%, so use the
michael@0 881 // cascaded value above us.
michael@0 882 interpolated = interpolated &&
michael@0 883 BuildSegment(propData.mSegments, prop, aSrc,
michael@0 884 fromKeyframe->mKey, fromContext,
michael@0 885 fromKeyframe->mRule->Declaration(),
michael@0 886 1.0f, aStyleContext);
michael@0 887 }
michael@0 888
michael@0 889 // If we failed to build any segments due to inability to
michael@0 890 // interpolate, remove the property from the animation. (It's not
michael@0 891 // clear if this is the right thing to do -- we could run some of
michael@0 892 // the segments, but it's really not clear whether we should skip
michael@0 893 // values (which?) or skip segments, so best to skip the whole
michael@0 894 // thing for now.)
michael@0 895 if (!interpolated) {
michael@0 896 aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1);
michael@0 897 }
michael@0 898 }
michael@0 899 }
michael@0 900 }
michael@0 901
michael@0 902 bool
michael@0 903 nsAnimationManager::BuildSegment(InfallibleTArray<AnimationPropertySegment>&
michael@0 904 aSegments,
michael@0 905 nsCSSProperty aProperty,
michael@0 906 const nsAnimation& aAnimation,
michael@0 907 float aFromKey, nsStyleContext* aFromContext,
michael@0 908 mozilla::css::Declaration* aFromDeclaration,
michael@0 909 float aToKey, nsStyleContext* aToContext)
michael@0 910 {
michael@0 911 nsStyleAnimation::Value fromValue, toValue, dummyValue;
michael@0 912 if (!ExtractComputedValueForTransition(aProperty, aFromContext, fromValue) ||
michael@0 913 !ExtractComputedValueForTransition(aProperty, aToContext, toValue) ||
michael@0 914 // Check that we can interpolate between these values
michael@0 915 // (If this is ever a performance problem, we could add a
michael@0 916 // CanInterpolate method, but it seems fine for now.)
michael@0 917 !nsStyleAnimation::Interpolate(aProperty, fromValue, toValue,
michael@0 918 0.5, dummyValue)) {
michael@0 919 return false;
michael@0 920 }
michael@0 921
michael@0 922 AnimationPropertySegment &segment = *aSegments.AppendElement();
michael@0 923
michael@0 924 segment.mFromValue = fromValue;
michael@0 925 segment.mToValue = toValue;
michael@0 926 segment.mFromKey = aFromKey;
michael@0 927 segment.mToKey = aToKey;
michael@0 928 const nsTimingFunction *tf;
michael@0 929 if (aFromDeclaration &&
michael@0 930 aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) {
michael@0 931 tf = &aFromContext->StyleDisplay()->mAnimations[0].GetTimingFunction();
michael@0 932 } else {
michael@0 933 tf = &aAnimation.GetTimingFunction();
michael@0 934 }
michael@0 935 segment.mTimingFunction.Init(*tf);
michael@0 936
michael@0 937 return true;
michael@0 938 }
michael@0 939
michael@0 940 nsIStyleRule*
michael@0 941 nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
michael@0 942 nsCSSPseudoElements::Type aPseudoType)
michael@0 943 {
michael@0 944 NS_ABORT_IF_FALSE(
michael@0 945 aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
michael@0 946 aPseudoType == nsCSSPseudoElements::ePseudo_before ||
michael@0 947 aPseudoType == nsCSSPseudoElements::ePseudo_after,
michael@0 948 "forbidden pseudo type");
michael@0 949
michael@0 950 if (!mPresContext->IsDynamic()) {
michael@0 951 // For print or print preview, ignore animations.
michael@0 952 return nullptr;
michael@0 953 }
michael@0 954
michael@0 955 ElementAnimations *ea =
michael@0 956 GetElementAnimations(aElement, aPseudoType, false);
michael@0 957 if (!ea) {
michael@0 958 return nullptr;
michael@0 959 }
michael@0 960
michael@0 961 if (mPresContext->IsProcessingRestyles() &&
michael@0 962 !mPresContext->IsProcessingAnimationStyleChange()) {
michael@0 963 // During the non-animation part of processing restyles, we don't
michael@0 964 // add the animation rule.
michael@0 965
michael@0 966 if (ea->mStyleRule) {
michael@0 967 ea->PostRestyleForAnimation(mPresContext);
michael@0 968 }
michael@0 969
michael@0 970 return nullptr;
michael@0 971 }
michael@0 972
michael@0 973 NS_WARN_IF_FALSE(!ea->mNeedsRefreshes ||
michael@0 974 ea->mStyleRuleRefreshTime ==
michael@0 975 mPresContext->RefreshDriver()->MostRecentRefresh(),
michael@0 976 "should already have refreshed style rule");
michael@0 977
michael@0 978 return ea->mStyleRule;
michael@0 979 }
michael@0 980
michael@0 981 /* virtual */ void
michael@0 982 nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime)
michael@0 983 {
michael@0 984 NS_ABORT_IF_FALSE(mPresContext,
michael@0 985 "refresh driver should not notify additional observers "
michael@0 986 "after pres context has been destroyed");
michael@0 987 if (!mPresContext->GetPresShell()) {
michael@0 988 // Someone might be keeping mPresContext alive past the point
michael@0 989 // where it has been torn down; don't bother doing anything in
michael@0 990 // this case. But do get rid of all our transitions so we stop
michael@0 991 // triggering refreshes.
michael@0 992 RemoveAllElementData();
michael@0 993 return;
michael@0 994 }
michael@0 995
michael@0 996 FlushAnimations(Can_Throttle);
michael@0 997 }
michael@0 998
michael@0 999 void
michael@0 1000 nsAnimationManager::AddElementData(CommonElementAnimationData* aData)
michael@0 1001 {
michael@0 1002 if (!mObservingRefreshDriver) {
michael@0 1003 NS_ASSERTION(static_cast<ElementAnimations*>(aData)->mNeedsRefreshes,
michael@0 1004 "Added data which doesn't need refreshing?");
michael@0 1005 // We need to observe the refresh driver.
michael@0 1006 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
michael@0 1007 mObservingRefreshDriver = true;
michael@0 1008 }
michael@0 1009
michael@0 1010 PR_INSERT_BEFORE(aData, &mElementData);
michael@0 1011 }
michael@0 1012
michael@0 1013 void
michael@0 1014 nsAnimationManager::CheckNeedsRefresh()
michael@0 1015 {
michael@0 1016 for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
michael@0 1017 l = PR_NEXT_LINK(l)) {
michael@0 1018 if (static_cast<ElementAnimations*>(l)->mNeedsRefreshes) {
michael@0 1019 if (!mObservingRefreshDriver) {
michael@0 1020 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
michael@0 1021 mObservingRefreshDriver = true;
michael@0 1022 }
michael@0 1023 return;
michael@0 1024 }
michael@0 1025 }
michael@0 1026 if (mObservingRefreshDriver) {
michael@0 1027 mObservingRefreshDriver = false;
michael@0 1028 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
michael@0 1029 }
michael@0 1030 }
michael@0 1031
michael@0 1032 void
michael@0 1033 nsAnimationManager::FlushAnimations(FlushFlags aFlags)
michael@0 1034 {
michael@0 1035 // FIXME: check that there's at least one style rule that's not
michael@0 1036 // in its "done" state, and if there isn't, remove ourselves from
michael@0 1037 // the refresh driver (but leave the animations!).
michael@0 1038 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
michael@0 1039 bool didThrottle = false;
michael@0 1040 for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
michael@0 1041 l = PR_NEXT_LINK(l)) {
michael@0 1042 ElementAnimations *ea = static_cast<ElementAnimations*>(l);
michael@0 1043 bool canThrottleTick = aFlags == Can_Throttle &&
michael@0 1044 ea->CanPerformOnCompositorThread(
michael@0 1045 CommonElementAnimationData::CanAnimateFlags(0)) &&
michael@0 1046 ea->CanThrottleAnimation(now);
michael@0 1047
michael@0 1048 nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
michael@0 1049 ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick);
michael@0 1050 CheckNeedsRefresh();
michael@0 1051 if (oldStyleRule != ea->mStyleRule) {
michael@0 1052 ea->PostRestyleForAnimation(mPresContext);
michael@0 1053 } else {
michael@0 1054 didThrottle = true;
michael@0 1055 }
michael@0 1056 }
michael@0 1057
michael@0 1058 if (didThrottle) {
michael@0 1059 mPresContext->Document()->SetNeedStyleFlush();
michael@0 1060 }
michael@0 1061
michael@0 1062 DispatchEvents(); // may destroy us
michael@0 1063 }
michael@0 1064
michael@0 1065 void
michael@0 1066 nsAnimationManager::DoDispatchEvents()
michael@0 1067 {
michael@0 1068 EventArray events;
michael@0 1069 mPendingEvents.SwapElements(events);
michael@0 1070 for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
michael@0 1071 AnimationEventInfo &info = events[i];
michael@0 1072 EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
michael@0 1073
michael@0 1074 if (!mPresContext) {
michael@0 1075 break;
michael@0 1076 }
michael@0 1077 }
michael@0 1078 }
michael@0 1079
michael@0 1080 void
michael@0 1081 nsAnimationManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
michael@0 1082 nsStyleContext* aParentStyle,
michael@0 1083 nsStyleChangeList& aChangeList)
michael@0 1084 {
michael@0 1085 dom::Element* element;
michael@0 1086 if (aContent->IsElement()) {
michael@0 1087 element = aContent->AsElement();
michael@0 1088 } else {
michael@0 1089 element = nullptr;
michael@0 1090 }
michael@0 1091
michael@0 1092 nsRefPtr<nsStyleContext> newStyle;
michael@0 1093
michael@0 1094 ElementAnimations* ea;
michael@0 1095 if (element &&
michael@0 1096 (ea = GetElementAnimations(element,
michael@0 1097 nsCSSPseudoElements::ePseudo_NotPseudoElement,
michael@0 1098 false))) {
michael@0 1099 // re-resolve our style
michael@0 1100 newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList);
michael@0 1101 // remove the current transition from the working set
michael@0 1102 ea->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
michael@0 1103 } else {
michael@0 1104 newStyle = ReparentContent(aContent, aParentStyle);
michael@0 1105 }
michael@0 1106
michael@0 1107 // walk the children
michael@0 1108 if (newStyle) {
michael@0 1109 for (nsIContent *child = aContent->GetFirstChild(); child;
michael@0 1110 child = child->GetNextSibling()) {
michael@0 1111 UpdateThrottledStylesForSubtree(child, newStyle, aChangeList);
michael@0 1112 }
michael@0 1113 }
michael@0 1114 }
michael@0 1115
michael@0 1116 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsAnimationManager,
michael@0 1117 GetElementAnimations)
michael@0 1118
michael@0 1119 void
michael@0 1120 nsAnimationManager::UpdateAllThrottledStyles()
michael@0 1121 {
michael@0 1122 if (PR_CLIST_IS_EMPTY(&mElementData)) {
michael@0 1123 // no throttled animations, leave early
michael@0 1124 mPresContext->TickLastUpdateThrottledAnimationStyle();
michael@0 1125 return;
michael@0 1126 }
michael@0 1127
michael@0 1128 if (mPresContext->ThrottledAnimationStyleIsUpToDate()) {
michael@0 1129 // throttled transitions are up to date, leave early
michael@0 1130 return;
michael@0 1131 }
michael@0 1132
michael@0 1133 mPresContext->TickLastUpdateThrottledAnimationStyle();
michael@0 1134
michael@0 1135 UpdateAllThrottledStylesInternal();
michael@0 1136 }
michael@0 1137

mercurial