Tue, 06 Jan 2015 21:39:09 +0100
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 |