layout/style/nsTransitionManager.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /* Code to start and animate CSS transitions. */
michael@0 8
michael@0 9 #include "nsTransitionManager.h"
michael@0 10 #include "nsAnimationManager.h"
michael@0 11 #include "nsIContent.h"
michael@0 12 #include "nsStyleContext.h"
michael@0 13 #include "nsCSSProps.h"
michael@0 14 #include "mozilla/MemoryReporting.h"
michael@0 15 #include "mozilla/TimeStamp.h"
michael@0 16 #include "nsRefreshDriver.h"
michael@0 17 #include "nsRuleProcessorData.h"
michael@0 18 #include "nsRuleWalker.h"
michael@0 19 #include "nsCSSPropertySet.h"
michael@0 20 #include "nsStyleAnimation.h"
michael@0 21 #include "mozilla/EventDispatcher.h"
michael@0 22 #include "mozilla/ContentEvents.h"
michael@0 23 #include "mozilla/dom/Element.h"
michael@0 24 #include "nsIFrame.h"
michael@0 25 #include "Layers.h"
michael@0 26 #include "FrameLayerBuilder.h"
michael@0 27 #include "nsDisplayList.h"
michael@0 28 #include "nsStyleChangeList.h"
michael@0 29 #include "nsStyleSet.h"
michael@0 30 #include "RestyleManager.h"
michael@0 31 #include "ActiveLayerTracker.h"
michael@0 32
michael@0 33 using mozilla::TimeStamp;
michael@0 34 using mozilla::TimeDuration;
michael@0 35
michael@0 36 using namespace mozilla;
michael@0 37 using namespace mozilla::layers;
michael@0 38 using namespace mozilla::css;
michael@0 39
michael@0 40 ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement,
michael@0 41 nsIAtom *aElementProperty,
michael@0 42 nsTransitionManager *aTransitionManager,
michael@0 43 TimeStamp aNow)
michael@0 44 : CommonElementAnimationData(aElement, aElementProperty,
michael@0 45 aTransitionManager, aNow)
michael@0 46 {
michael@0 47 }
michael@0 48
michael@0 49 double
michael@0 50 ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const
michael@0 51 {
michael@0 52 // Set |timePortion| to the portion of the way we are through the time
michael@0 53 // input to the transition's timing function (always within the range
michael@0 54 // 0-1).
michael@0 55 double duration = mIterationDuration.ToSeconds();
michael@0 56 NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden");
michael@0 57 double timePortion;
michael@0 58 if (IsRemovedSentinel()) {
michael@0 59 // The transition is being removed, but we still want an update so that any
michael@0 60 // new transitions start in the right place.
michael@0 61 timePortion = 1.0;
michael@0 62 } else if (duration == 0.0) {
michael@0 63 // When duration is zero, we can still have a transition when delay
michael@0 64 // is nonzero.
michael@0 65 if (aRefreshTime >= mStartTime + mDelay) {
michael@0 66 timePortion = 1.0;
michael@0 67 } else {
michael@0 68 timePortion = 0.0;
michael@0 69 }
michael@0 70 } else {
michael@0 71 timePortion = (aRefreshTime - (mStartTime + mDelay)).ToSeconds() / duration;
michael@0 72 if (timePortion < 0.0)
michael@0 73 timePortion = 0.0; // use start value during transition-delay
michael@0 74 if (timePortion > 1.0)
michael@0 75 timePortion = 1.0; // we might be behind on flushing
michael@0 76 }
michael@0 77 MOZ_ASSERT(mProperties.Length() == 1,
michael@0 78 "Should have one animation property for a transition");
michael@0 79 MOZ_ASSERT(mProperties[0].mSegments.Length() == 1,
michael@0 80 "Animation property should have one segment for a transition");
michael@0 81
michael@0 82 return mProperties[0].mSegments[0].mTimingFunction.GetValue(timePortion);
michael@0 83 }
michael@0 84
michael@0 85 static void
michael@0 86 ElementTransitionsPropertyDtor(void *aObject,
michael@0 87 nsIAtom *aPropertyName,
michael@0 88 void *aPropertyValue,
michael@0 89 void *aData)
michael@0 90 {
michael@0 91 ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue);
michael@0 92 #ifdef DEBUG
michael@0 93 NS_ABORT_IF_FALSE(!et->mCalledPropertyDtor, "can't call dtor twice");
michael@0 94 et->mCalledPropertyDtor = true;
michael@0 95 #endif
michael@0 96 delete et;
michael@0 97 }
michael@0 98
michael@0 99 void
michael@0 100 ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
michael@0 101 {
michael@0 102 if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) {
michael@0 103 mStyleRule = new css::AnimValuesStyleRule();
michael@0 104 mStyleRuleRefreshTime = aRefreshTime;
michael@0 105
michael@0 106 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i)
michael@0 107 {
michael@0 108 ElementPropertyTransition &pt = mPropertyTransitions[i];
michael@0 109 if (pt.IsRemovedSentinel()) {
michael@0 110 continue;
michael@0 111 }
michael@0 112
michael@0 113 MOZ_ASSERT(pt.mProperties.Length() == 1,
michael@0 114 "Should have one animation property for a transition");
michael@0 115 const AnimationProperty &prop = pt.mProperties[0];
michael@0 116
michael@0 117 nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty);
michael@0 118
michael@0 119 double valuePortion = pt.ValuePortionFor(aRefreshTime);
michael@0 120
michael@0 121 MOZ_ASSERT(prop.mSegments.Length() == 1,
michael@0 122 "Animation property should have one segment for a transition");
michael@0 123 #ifdef DEBUG
michael@0 124 bool ok =
michael@0 125 #endif
michael@0 126 nsStyleAnimation::Interpolate(prop.mProperty,
michael@0 127 prop.mSegments[0].mFromValue,
michael@0 128 prop.mSegments[0].mToValue,
michael@0 129 valuePortion, *val);
michael@0 130 NS_ABORT_IF_FALSE(ok, "could not interpolate values");
michael@0 131 }
michael@0 132 }
michael@0 133 }
michael@0 134
michael@0 135 bool
michael@0 136 ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const
michael@0 137 {
michael@0 138 for (uint32_t tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) {
michael@0 139 const ElementPropertyTransition& pt = mPropertyTransitions[tranIdx];
michael@0 140 if (pt.HasAnimationOfProperty(aProperty) && !pt.IsRemovedSentinel()) {
michael@0 141 return true;
michael@0 142 }
michael@0 143 }
michael@0 144 return false;
michael@0 145 }
michael@0 146
michael@0 147 bool
michael@0 148 ElementTransitions::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
michael@0 149 {
michael@0 150 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
michael@0 151 if (!frame) {
michael@0 152 return false;
michael@0 153 }
michael@0 154
michael@0 155 if (mElementProperty != nsGkAtoms::transitionsProperty) {
michael@0 156 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
michael@0 157 nsCString message;
michael@0 158 message.AppendLiteral("Gecko bug: Async transition of pseudoelements not supported. See bug 771367");
michael@0 159 LogAsyncAnimationFailure(message, mElement);
michael@0 160 }
michael@0 161 return false;
michael@0 162 }
michael@0 163
michael@0 164 TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
michael@0 165
michael@0 166 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
michael@0 167 const ElementPropertyTransition& pt = mPropertyTransitions[i];
michael@0 168 MOZ_ASSERT(pt.mProperties.Length() == 1,
michael@0 169 "Should have one animation property for a transition");
michael@0 170 if (css::IsGeometricProperty(pt.mProperties[0].mProperty) &&
michael@0 171 pt.IsRunningAt(now)) {
michael@0 172 aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
michael@0 173 break;
michael@0 174 }
michael@0 175 }
michael@0 176
michael@0 177 bool hasOpacity = false;
michael@0 178 bool hasTransform = false;
michael@0 179 bool existsProperty = false;
michael@0 180 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
michael@0 181 const ElementPropertyTransition& pt = mPropertyTransitions[i];
michael@0 182 if (!pt.IsRunningAt(now)) {
michael@0 183 continue;
michael@0 184 }
michael@0 185
michael@0 186 existsProperty = true;
michael@0 187
michael@0 188 MOZ_ASSERT(pt.mProperties.Length() == 1,
michael@0 189 "Should have one animation property for a transition");
michael@0 190 const AnimationProperty& prop = pt.mProperties[0];
michael@0 191
michael@0 192 if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(
michael@0 193 mElement, prop.mProperty, aFlags) ||
michael@0 194 css::CommonElementAnimationData::IsCompositorAnimationDisabledForFrame(frame)) {
michael@0 195 return false;
michael@0 196 }
michael@0 197 if (prop.mProperty == eCSSProperty_opacity) {
michael@0 198 hasOpacity = true;
michael@0 199 } else if (prop.mProperty == eCSSProperty_transform) {
michael@0 200 hasTransform = true;
michael@0 201 }
michael@0 202 }
michael@0 203
michael@0 204 // No properties to animate
michael@0 205 if (!existsProperty) {
michael@0 206 return false;
michael@0 207 }
michael@0 208
michael@0 209 // This transition can be done on the compositor. Mark the frame as active, in
michael@0 210 // case we are able to throttle this transition.
michael@0 211 if (hasOpacity) {
michael@0 212 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
michael@0 213 }
michael@0 214 if (hasTransform) {
michael@0 215 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
michael@0 216 }
michael@0 217 return true;
michael@0 218 }
michael@0 219
michael@0 220 /*****************************************************************************
michael@0 221 * nsTransitionManager *
michael@0 222 *****************************************************************************/
michael@0 223
michael@0 224 void
michael@0 225 nsTransitionManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
michael@0 226 nsStyleContext* aParentStyle,
michael@0 227 nsStyleChangeList& aChangeList)
michael@0 228 {
michael@0 229 dom::Element* element;
michael@0 230 if (aContent->IsElement()) {
michael@0 231 element = aContent->AsElement();
michael@0 232 } else {
michael@0 233 element = nullptr;
michael@0 234 }
michael@0 235
michael@0 236 nsRefPtr<nsStyleContext> newStyle;
michael@0 237
michael@0 238 ElementTransitions* et;
michael@0 239 if (element &&
michael@0 240 (et = GetElementTransitions(element,
michael@0 241 nsCSSPseudoElements::ePseudo_NotPseudoElement,
michael@0 242 false))) {
michael@0 243 // re-resolve our style
michael@0 244 newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList);
michael@0 245 // remove the current transition from the working set
michael@0 246 et->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
michael@0 247 } else {
michael@0 248 newStyle = ReparentContent(aContent, aParentStyle);
michael@0 249 }
michael@0 250
michael@0 251 // walk the children
michael@0 252 if (newStyle) {
michael@0 253 for (nsIContent *child = aContent->GetFirstChild(); child;
michael@0 254 child = child->GetNextSibling()) {
michael@0 255 UpdateThrottledStylesForSubtree(child, newStyle, aChangeList);
michael@0 256 }
michael@0 257 }
michael@0 258 }
michael@0 259
michael@0 260 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsTransitionManager,
michael@0 261 GetElementTransitions)
michael@0 262
michael@0 263 void
michael@0 264 nsTransitionManager::UpdateAllThrottledStyles()
michael@0 265 {
michael@0 266 if (PR_CLIST_IS_EMPTY(&mElementData)) {
michael@0 267 // no throttled transitions, leave early
michael@0 268 mPresContext->TickLastUpdateThrottledTransitionStyle();
michael@0 269 return;
michael@0 270 }
michael@0 271
michael@0 272 if (mPresContext->ThrottledTransitionStyleIsUpToDate()) {
michael@0 273 // throttled transitions are up to date, leave early
michael@0 274 return;
michael@0 275 }
michael@0 276
michael@0 277 mPresContext->TickLastUpdateThrottledTransitionStyle();
michael@0 278 UpdateAllThrottledStylesInternal();
michael@0 279 }
michael@0 280
michael@0 281 void
michael@0 282 nsTransitionManager::ElementDataRemoved()
michael@0 283 {
michael@0 284 // If we have no transitions or animations left, remove ourselves from
michael@0 285 // the refresh driver.
michael@0 286 if (PR_CLIST_IS_EMPTY(&mElementData)) {
michael@0 287 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
michael@0 288 }
michael@0 289 }
michael@0 290
michael@0 291 void
michael@0 292 nsTransitionManager::AddElementData(CommonElementAnimationData* aData)
michael@0 293 {
michael@0 294 if (PR_CLIST_IS_EMPTY(&mElementData)) {
michael@0 295 // We need to observe the refresh driver.
michael@0 296 nsRefreshDriver *rd = mPresContext->RefreshDriver();
michael@0 297 rd->AddRefreshObserver(this, Flush_Style);
michael@0 298 }
michael@0 299
michael@0 300 PR_INSERT_BEFORE(aData, &mElementData);
michael@0 301 }
michael@0 302
michael@0 303 already_AddRefed<nsIStyleRule>
michael@0 304 nsTransitionManager::StyleContextChanged(dom::Element *aElement,
michael@0 305 nsStyleContext *aOldStyleContext,
michael@0 306 nsStyleContext *aNewStyleContext)
michael@0 307 {
michael@0 308 NS_PRECONDITION(aOldStyleContext->GetPseudo() ==
michael@0 309 aNewStyleContext->GetPseudo(),
michael@0 310 "pseudo type mismatch");
michael@0 311 // If we were called from ReparentStyleContext, this assertion would
michael@0 312 // actually fire. If we need to be called from there, we can probably
michael@0 313 // just remove it; the condition probably isn't critical, although
michael@0 314 // it's worth thinking about some more.
michael@0 315 NS_PRECONDITION(aOldStyleContext->HasPseudoElementData() ==
michael@0 316 aNewStyleContext->HasPseudoElementData(),
michael@0 317 "pseudo type mismatch");
michael@0 318
michael@0 319 if (!mPresContext->IsDynamic()) {
michael@0 320 // For print or print preview, ignore transitions.
michael@0 321 return nullptr;
michael@0 322 }
michael@0 323
michael@0 324 // NOTE: Things in this function (and ConsiderStartingTransition)
michael@0 325 // should never call PeekStyleData because we don't preserve gotten
michael@0 326 // structs across reframes.
michael@0 327
michael@0 328 // Return sooner (before the startedAny check below) for the most
michael@0 329 // common case: no transitions specified or running.
michael@0 330 const nsStyleDisplay *disp = aNewStyleContext->StyleDisplay();
michael@0 331 nsCSSPseudoElements::Type pseudoType = aNewStyleContext->GetPseudoType();
michael@0 332 if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) {
michael@0 333 if (pseudoType != nsCSSPseudoElements::ePseudo_before &&
michael@0 334 pseudoType != nsCSSPseudoElements::ePseudo_after) {
michael@0 335 return nullptr;
michael@0 336 }
michael@0 337
michael@0 338 NS_ASSERTION((pseudoType == nsCSSPseudoElements::ePseudo_before &&
michael@0 339 aElement->Tag() == nsGkAtoms::mozgeneratedcontentbefore) ||
michael@0 340 (pseudoType == nsCSSPseudoElements::ePseudo_after &&
michael@0 341 aElement->Tag() == nsGkAtoms::mozgeneratedcontentafter),
michael@0 342 "Unexpected aElement coming through");
michael@0 343
michael@0 344 // Else the element we want to use from now on is the element the
michael@0 345 // :before or :after is attached to.
michael@0 346 aElement = aElement->GetParent()->AsElement();
michael@0 347 }
michael@0 348
michael@0 349 ElementTransitions *et =
michael@0 350 GetElementTransitions(aElement, pseudoType, false);
michael@0 351 if (!et &&
michael@0 352 disp->mTransitionPropertyCount == 1 &&
michael@0 353 disp->mTransitions[0].GetDelay() == 0.0f &&
michael@0 354 disp->mTransitions[0].GetDuration() == 0.0f) {
michael@0 355 return nullptr;
michael@0 356 }
michael@0 357
michael@0 358
michael@0 359 if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) {
michael@0 360 return nullptr;
michael@0 361 }
michael@0 362
michael@0 363 if (aNewStyleContext->GetParent() &&
michael@0 364 aNewStyleContext->GetParent()->HasPseudoElementData()) {
michael@0 365 // Ignore transitions on things that inherit properties from
michael@0 366 // pseudo-elements.
michael@0 367 // FIXME (Bug 522599): Add tests for this.
michael@0 368 return nullptr;
michael@0 369 }
michael@0 370
michael@0 371 NS_WARN_IF_FALSE(!nsLayoutUtils::AreAsyncAnimationsEnabled() ||
michael@0 372 mPresContext->ThrottledTransitionStyleIsUpToDate(),
michael@0 373 "throttled animations not up to date");
michael@0 374
michael@0 375 // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
michael@0 376 // I'll consider only the transitions from the number of items in
michael@0 377 // 'transition-property' on down, and later ones will override earlier
michael@0 378 // ones (tracked using |whichStarted|).
michael@0 379 bool startedAny = false;
michael@0 380 nsCSSPropertySet whichStarted;
michael@0 381 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
michael@0 382 const nsTransition& t = disp->mTransitions[i];
michael@0 383 // Check delay and duration first, since they default to zero, and
michael@0 384 // when they're both zero, we can ignore the transition.
michael@0 385 if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) {
michael@0 386 // We might have something to transition. See if any of the
michael@0 387 // properties in question changed and are animatable.
michael@0 388 // FIXME: Would be good to find a way to share code between this
michael@0 389 // interpretation of transition-property and the one below.
michael@0 390 nsCSSProperty property = t.GetProperty();
michael@0 391 if (property == eCSSPropertyExtra_no_properties ||
michael@0 392 property == eCSSPropertyExtra_variable ||
michael@0 393 property == eCSSProperty_UNKNOWN) {
michael@0 394 // Nothing to do, but need to exclude this from cases below.
michael@0 395 } else if (property == eCSSPropertyExtra_all_properties) {
michael@0 396 for (nsCSSProperty p = nsCSSProperty(0);
michael@0 397 p < eCSSProperty_COUNT_no_shorthands;
michael@0 398 p = nsCSSProperty(p + 1)) {
michael@0 399 ConsiderStartingTransition(p, t, aElement, et,
michael@0 400 aOldStyleContext, aNewStyleContext,
michael@0 401 &startedAny, &whichStarted);
michael@0 402 }
michael@0 403 } else if (nsCSSProps::IsShorthand(property)) {
michael@0 404 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
michael@0 405 ConsiderStartingTransition(*subprop, t, aElement, et,
michael@0 406 aOldStyleContext, aNewStyleContext,
michael@0 407 &startedAny, &whichStarted);
michael@0 408 }
michael@0 409 } else {
michael@0 410 ConsiderStartingTransition(property, t, aElement, et,
michael@0 411 aOldStyleContext, aNewStyleContext,
michael@0 412 &startedAny, &whichStarted);
michael@0 413 }
michael@0 414 }
michael@0 415 }
michael@0 416
michael@0 417 // Stop any transitions for properties that are no longer in
michael@0 418 // 'transition-property'.
michael@0 419 // Also stop any transitions for properties that just changed (and are
michael@0 420 // still in the set of properties to transition), but we didn't just
michael@0 421 // start the transition because delay and duration are both zero.
michael@0 422 if (et) {
michael@0 423 bool checkProperties =
michael@0 424 disp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
michael@0 425 nsCSSPropertySet allTransitionProperties;
michael@0 426 if (checkProperties) {
michael@0 427 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
michael@0 428 const nsTransition& t = disp->mTransitions[i];
michael@0 429 // FIXME: Would be good to find a way to share code between this
michael@0 430 // interpretation of transition-property and the one above.
michael@0 431 nsCSSProperty property = t.GetProperty();
michael@0 432 if (property == eCSSPropertyExtra_no_properties ||
michael@0 433 property == eCSSPropertyExtra_variable ||
michael@0 434 property == eCSSProperty_UNKNOWN) {
michael@0 435 // Nothing to do, but need to exclude this from cases below.
michael@0 436 } else if (property == eCSSPropertyExtra_all_properties) {
michael@0 437 for (nsCSSProperty p = nsCSSProperty(0);
michael@0 438 p < eCSSProperty_COUNT_no_shorthands;
michael@0 439 p = nsCSSProperty(p + 1)) {
michael@0 440 allTransitionProperties.AddProperty(p);
michael@0 441 }
michael@0 442 } else if (nsCSSProps::IsShorthand(property)) {
michael@0 443 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
michael@0 444 allTransitionProperties.AddProperty(*subprop);
michael@0 445 }
michael@0 446 } else {
michael@0 447 allTransitionProperties.AddProperty(property);
michael@0 448 }
michael@0 449 }
michael@0 450 }
michael@0 451
michael@0 452 nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
michael@0 453 uint32_t i = pts.Length();
michael@0 454 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
michael@0 455 nsStyleAnimation::Value currentValue;
michael@0 456 do {
michael@0 457 --i;
michael@0 458 ElementPropertyTransition &pt = pts[i];
michael@0 459 MOZ_ASSERT(pt.mProperties.Length() == 1,
michael@0 460 "Should have one animation property for a transition");
michael@0 461 MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1,
michael@0 462 "Animation property should have one segment for a transition");
michael@0 463 const AnimationProperty& prop = pt.mProperties[0];
michael@0 464 const AnimationPropertySegment& segment = prop.mSegments[0];
michael@0 465 // properties no longer in 'transition-property'
michael@0 466 if ((checkProperties &&
michael@0 467 !allTransitionProperties.HasProperty(prop.mProperty)) ||
michael@0 468 // properties whose computed values changed but delay and
michael@0 469 // duration are both zero
michael@0 470 !ExtractComputedValueForTransition(prop.mProperty, aNewStyleContext,
michael@0 471 currentValue) ||
michael@0 472 currentValue != segment.mToValue) {
michael@0 473 // stop the transition
michael@0 474 pts.RemoveElementAt(i);
michael@0 475 et->UpdateAnimationGeneration(mPresContext);
michael@0 476 }
michael@0 477 } while (i != 0);
michael@0 478
michael@0 479 if (pts.IsEmpty()) {
michael@0 480 et->Destroy();
michael@0 481 et = nullptr;
michael@0 482 }
michael@0 483 }
michael@0 484
michael@0 485 if (!startedAny) {
michael@0 486 return nullptr;
michael@0 487 }
michael@0 488
michael@0 489 NS_ABORT_IF_FALSE(et, "must have element transitions if we started "
michael@0 490 "any transitions");
michael@0 491
michael@0 492 // In the CSS working group discussion (2009 Jul 15 telecon,
michael@0 493 // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
michael@0 494 // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
michael@0 495 // the working group decided that a transition property on an
michael@0 496 // element should not cause any transitions if the property change
michael@0 497 // is itself inheriting a value that is transitioning on an
michael@0 498 // ancestor. So, to get the correct behavior, we continue the
michael@0 499 // restyle that caused this transition using a "covering" rule that
michael@0 500 // covers up any changes on which we started transitions, so that
michael@0 501 // descendants don't start their own transitions. (In the case of
michael@0 502 // negative transition delay, this covering rule produces different
michael@0 503 // results than applying the transition rule immediately would).
michael@0 504 // Our caller is responsible for restyling again using this covering
michael@0 505 // rule.
michael@0 506
michael@0 507 nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule;
michael@0 508
michael@0 509 nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
michael@0 510 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
michael@0 511 ElementPropertyTransition &pt = pts[i];
michael@0 512 MOZ_ASSERT(pt.mProperties.Length() == 1,
michael@0 513 "Should have one animation property for a transition");
michael@0 514 MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1,
michael@0 515 "Animation property should have one segment for a transition");
michael@0 516 AnimationProperty& prop = pt.mProperties[0];
michael@0 517 AnimationPropertySegment& segment = prop.mSegments[0];
michael@0 518 if (whichStarted.HasProperty(prop.mProperty)) {
michael@0 519 coverRule->AddValue(prop.mProperty, segment.mFromValue);
michael@0 520 }
michael@0 521 }
michael@0 522
michael@0 523 et->mStyleRule = nullptr;
michael@0 524
michael@0 525 return coverRule.forget();
michael@0 526 }
michael@0 527
michael@0 528 void
michael@0 529 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
michael@0 530 const nsTransition& aTransition,
michael@0 531 dom::Element* aElement,
michael@0 532 ElementTransitions*& aElementTransitions,
michael@0 533 nsStyleContext* aOldStyleContext,
michael@0 534 nsStyleContext* aNewStyleContext,
michael@0 535 bool* aStartedAny,
michael@0 536 nsCSSPropertySet* aWhichStarted)
michael@0 537 {
michael@0 538 // IsShorthand itself will assert if aProperty is not a property.
michael@0 539 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
michael@0 540 "property out of range");
michael@0 541 NS_ASSERTION(!aElementTransitions ||
michael@0 542 aElementTransitions->mElement == aElement, "Element mismatch");
michael@0 543
michael@0 544 if (aWhichStarted->HasProperty(aProperty)) {
michael@0 545 // A later item in transition-property already started a
michael@0 546 // transition for this property, so we ignore this one.
michael@0 547 // See comment above and
michael@0 548 // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
michael@0 549 return;
michael@0 550 }
michael@0 551
michael@0 552 if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
michael@0 553 return;
michael@0 554 }
michael@0 555
michael@0 556 ElementPropertyTransition pt;
michael@0 557
michael@0 558 nsStyleAnimation::Value startValue, endValue, dummyValue;
michael@0 559 bool haveValues =
michael@0 560 ExtractComputedValueForTransition(aProperty, aOldStyleContext,
michael@0 561 startValue) &&
michael@0 562 ExtractComputedValueForTransition(aProperty, aNewStyleContext,
michael@0 563 endValue);
michael@0 564
michael@0 565 bool haveChange = startValue != endValue;
michael@0 566
michael@0 567 bool shouldAnimate =
michael@0 568 haveValues &&
michael@0 569 haveChange &&
michael@0 570 // Check that we can interpolate between these values
michael@0 571 // (If this is ever a performance problem, we could add a
michael@0 572 // CanInterpolate method, but it seems fine for now.)
michael@0 573 nsStyleAnimation::Interpolate(aProperty, startValue, endValue,
michael@0 574 0.5, dummyValue);
michael@0 575
michael@0 576 bool haveCurrentTransition = false;
michael@0 577 uint32_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
michael@0 578 const ElementPropertyTransition *oldPT = nullptr;
michael@0 579 if (aElementTransitions) {
michael@0 580 nsTArray<ElementPropertyTransition> &pts =
michael@0 581 aElementTransitions->mPropertyTransitions;
michael@0 582 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
michael@0 583 MOZ_ASSERT(pts[i].mProperties.Length() == 1,
michael@0 584 "Should have one animation property for a transition");
michael@0 585 if (pts[i].mProperties[0].mProperty == aProperty) {
michael@0 586 haveCurrentTransition = true;
michael@0 587 currentIndex = i;
michael@0 588 oldPT = &aElementTransitions->mPropertyTransitions[currentIndex];
michael@0 589 break;
michael@0 590 }
michael@0 591 }
michael@0 592 }
michael@0 593
michael@0 594 // If we got a style change that changed the value to the endpoint
michael@0 595 // of the currently running transition, we don't want to interrupt
michael@0 596 // its timing function.
michael@0 597 // This needs to be before the !shouldAnimate && haveCurrentTransition
michael@0 598 // case below because we might be close enough to the end of the
michael@0 599 // transition that the current value rounds to the final value. In
michael@0 600 // this case, we'll end up with shouldAnimate as false (because
michael@0 601 // there's no value change), but we need to return early here rather
michael@0 602 // than cancel the running transition because shouldAnimate is false!
michael@0 603 MOZ_ASSERT(!oldPT || oldPT->mProperties[0].mSegments.Length() == 1,
michael@0 604 "Should have one animation property segment for a transition");
michael@0 605 if (haveCurrentTransition && haveValues &&
michael@0 606 oldPT->mProperties[0].mSegments[0].mToValue == endValue) {
michael@0 607 // WalkTransitionRule already called RestyleForAnimation.
michael@0 608 return;
michael@0 609 }
michael@0 610
michael@0 611 nsPresContext *presContext = aNewStyleContext->PresContext();
michael@0 612
michael@0 613 if (!shouldAnimate) {
michael@0 614 if (haveCurrentTransition) {
michael@0 615 // We're in the middle of a transition, and just got a non-transition
michael@0 616 // style change to something that we can't animate. This might happen
michael@0 617 // because we got a non-transition style change changing to the current
michael@0 618 // in-progress value (which is particularly easy to cause when we're
michael@0 619 // currently in the 'transition-delay'). It also might happen because we
michael@0 620 // just got a style change to a value that can't be interpolated.
michael@0 621 nsTArray<ElementPropertyTransition> &pts =
michael@0 622 aElementTransitions->mPropertyTransitions;
michael@0 623 pts.RemoveElementAt(currentIndex);
michael@0 624 aElementTransitions->UpdateAnimationGeneration(mPresContext);
michael@0 625
michael@0 626 if (pts.IsEmpty()) {
michael@0 627 aElementTransitions->Destroy();
michael@0 628 // |aElementTransitions| is now a dangling pointer!
michael@0 629 aElementTransitions = nullptr;
michael@0 630 }
michael@0 631 // WalkTransitionRule already called RestyleForAnimation.
michael@0 632 }
michael@0 633 return;
michael@0 634 }
michael@0 635
michael@0 636 TimeStamp mostRecentRefresh =
michael@0 637 presContext->RefreshDriver()->MostRecentRefresh();
michael@0 638
michael@0 639 const nsTimingFunction &tf = aTransition.GetTimingFunction();
michael@0 640 float delay = aTransition.GetDelay();
michael@0 641 float duration = aTransition.GetDuration();
michael@0 642 if (duration < 0.0) {
michael@0 643 // The spec says a negative duration is treated as zero.
michael@0 644 duration = 0.0;
michael@0 645 }
michael@0 646 pt.mStartForReversingTest = startValue;
michael@0 647 pt.mReversePortion = 1.0;
michael@0 648
michael@0 649 // If the new transition reverses an existing one, we'll need to
michael@0 650 // handle the timing differently.
michael@0 651 if (haveCurrentTransition &&
michael@0 652 !oldPT->IsRemovedSentinel() &&
michael@0 653 oldPT->mStartForReversingTest == endValue) {
michael@0 654 // Compute the appropriate negative transition-delay such that right
michael@0 655 // now we'd end up at the current position.
michael@0 656 double valuePortion =
michael@0 657 oldPT->ValuePortionFor(mostRecentRefresh) * oldPT->mReversePortion +
michael@0 658 (1.0 - oldPT->mReversePortion);
michael@0 659 // A timing function with negative y1 (or y2!) might make
michael@0 660 // valuePortion negative. In this case, we still want to apply our
michael@0 661 // reversing logic based on relative distances, not make duration
michael@0 662 // negative.
michael@0 663 if (valuePortion < 0.0) {
michael@0 664 valuePortion = -valuePortion;
michael@0 665 }
michael@0 666 // A timing function with y2 (or y1!) greater than one might
michael@0 667 // advance past its terminal value. It's probably a good idea to
michael@0 668 // clamp valuePortion to be at most one to preserve the invariant
michael@0 669 // that a transition will complete within at most its specified
michael@0 670 // time.
michael@0 671 if (valuePortion > 1.0) {
michael@0 672 valuePortion = 1.0;
michael@0 673 }
michael@0 674
michael@0 675 // Negative delays are essentially part of the transition
michael@0 676 // function, so reduce them along with the duration, but don't
michael@0 677 // reduce positive delays.
michael@0 678 if (delay < 0.0f) {
michael@0 679 delay *= valuePortion;
michael@0 680 }
michael@0 681
michael@0 682 duration *= valuePortion;
michael@0 683
michael@0 684 pt.mStartForReversingTest = oldPT->mProperties[0].mSegments[0].mToValue;
michael@0 685 pt.mReversePortion = valuePortion;
michael@0 686 }
michael@0 687
michael@0 688 AnimationProperty& prop = *pt.mProperties.AppendElement();
michael@0 689 prop.mProperty = aProperty;
michael@0 690
michael@0 691 AnimationPropertySegment& segment = *prop.mSegments.AppendElement();
michael@0 692 segment.mFromValue = startValue;
michael@0 693 segment.mToValue = endValue;
michael@0 694 segment.mFromKey = 0;
michael@0 695 segment.mToKey = 1;
michael@0 696 segment.mTimingFunction.Init(tf);
michael@0 697
michael@0 698 pt.mStartTime = mostRecentRefresh;
michael@0 699 pt.mDelay = TimeDuration::FromMilliseconds(delay);
michael@0 700 pt.mIterationDuration = TimeDuration::FromMilliseconds(duration);
michael@0 701 pt.mIterationCount = 1;
michael@0 702 pt.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
michael@0 703 pt.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
michael@0 704 pt.mPlayState = NS_STYLE_ANIMATION_PLAY_STATE_RUNNING;
michael@0 705 pt.mPauseStart = TimeStamp();
michael@0 706
michael@0 707 if (!aElementTransitions) {
michael@0 708 aElementTransitions =
michael@0 709 GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
michael@0 710 true);
michael@0 711 if (!aElementTransitions) {
michael@0 712 NS_WARNING("allocating ElementTransitions failed");
michael@0 713 return;
michael@0 714 }
michael@0 715 }
michael@0 716
michael@0 717 nsTArray<ElementPropertyTransition> &pts =
michael@0 718 aElementTransitions->mPropertyTransitions;
michael@0 719 #ifdef DEBUG
michael@0 720 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
michael@0 721 NS_ABORT_IF_FALSE(pts[i].mProperties.Length() == 1,
michael@0 722 "Should have one animation property for a transition");
michael@0 723 NS_ABORT_IF_FALSE(i == currentIndex ||
michael@0 724 pts[i].mProperties[0].mProperty != aProperty,
michael@0 725 "duplicate transitions for property");
michael@0 726 }
michael@0 727 #endif
michael@0 728 if (haveCurrentTransition) {
michael@0 729 pts[currentIndex] = pt;
michael@0 730 } else {
michael@0 731 if (!pts.AppendElement(pt)) {
michael@0 732 NS_WARNING("out of memory");
michael@0 733 return;
michael@0 734 }
michael@0 735 }
michael@0 736 aElementTransitions->UpdateAnimationGeneration(mPresContext);
michael@0 737
michael@0 738 nsRestyleHint hint =
michael@0 739 aNewStyleContext->GetPseudoType() ==
michael@0 740 nsCSSPseudoElements::ePseudo_NotPseudoElement ?
michael@0 741 eRestyle_Self : eRestyle_Subtree;
michael@0 742 presContext->PresShell()->RestyleForAnimation(aElement, hint);
michael@0 743
michael@0 744 *aStartedAny = true;
michael@0 745 aWhichStarted->AddProperty(aProperty);
michael@0 746 }
michael@0 747
michael@0 748 ElementTransitions*
michael@0 749 nsTransitionManager::GetElementTransitions(dom::Element *aElement,
michael@0 750 nsCSSPseudoElements::Type aPseudoType,
michael@0 751 bool aCreateIfNeeded)
michael@0 752 {
michael@0 753 if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
michael@0 754 // Early return for the most common case.
michael@0 755 return nullptr;
michael@0 756 }
michael@0 757
michael@0 758 nsIAtom *propName;
michael@0 759 if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
michael@0 760 propName = nsGkAtoms::transitionsProperty;
michael@0 761 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
michael@0 762 propName = nsGkAtoms::transitionsOfBeforeProperty;
michael@0 763 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
michael@0 764 propName = nsGkAtoms::transitionsOfAfterProperty;
michael@0 765 } else {
michael@0 766 NS_ASSERTION(!aCreateIfNeeded,
michael@0 767 "should never try to create transitions for pseudo "
michael@0 768 "other than :before or :after");
michael@0 769 return nullptr;
michael@0 770 }
michael@0 771 ElementTransitions *et = static_cast<ElementTransitions*>(
michael@0 772 aElement->GetProperty(propName));
michael@0 773 if (!et && aCreateIfNeeded) {
michael@0 774 // FIXME: Consider arena-allocating?
michael@0 775 et = new ElementTransitions(aElement, propName, this,
michael@0 776 mPresContext->RefreshDriver()->MostRecentRefresh());
michael@0 777 nsresult rv = aElement->SetProperty(propName, et,
michael@0 778 ElementTransitionsPropertyDtor, false);
michael@0 779 if (NS_FAILED(rv)) {
michael@0 780 NS_WARNING("SetProperty failed");
michael@0 781 delete et;
michael@0 782 return nullptr;
michael@0 783 }
michael@0 784 if (propName == nsGkAtoms::transitionsProperty) {
michael@0 785 aElement->SetMayHaveAnimations();
michael@0 786 }
michael@0 787
michael@0 788 AddElementData(et);
michael@0 789 }
michael@0 790
michael@0 791 return et;
michael@0 792 }
michael@0 793
michael@0 794 /*
michael@0 795 * nsIStyleRuleProcessor implementation
michael@0 796 */
michael@0 797
michael@0 798 void
michael@0 799 nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData,
michael@0 800 nsCSSPseudoElements::Type aPseudoType)
michael@0 801 {
michael@0 802 ElementTransitions *et =
michael@0 803 GetElementTransitions(aData->mElement, aPseudoType, false);
michael@0 804 if (!et) {
michael@0 805 return;
michael@0 806 }
michael@0 807
michael@0 808 if (!mPresContext->IsDynamic()) {
michael@0 809 // For print or print preview, ignore animations.
michael@0 810 return;
michael@0 811 }
michael@0 812
michael@0 813 if (aData->mPresContext->IsProcessingRestyles() &&
michael@0 814 !aData->mPresContext->IsProcessingAnimationStyleChange()) {
michael@0 815 // If we're processing a normal style change rather than one from
michael@0 816 // animation, don't add the transition rule. This allows us to
michael@0 817 // compute the new style value rather than having the transition
michael@0 818 // override it, so that we can start transitioning differently.
michael@0 819
michael@0 820 // We need to immediately restyle with animation
michael@0 821 // after doing this.
michael@0 822 nsRestyleHint hint =
michael@0 823 aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ?
michael@0 824 eRestyle_Self : eRestyle_Subtree;
michael@0 825 mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint);
michael@0 826 return;
michael@0 827 }
michael@0 828
michael@0 829 et->EnsureStyleRuleFor(
michael@0 830 aData->mPresContext->RefreshDriver()->MostRecentRefresh());
michael@0 831
michael@0 832 aData->mRuleWalker->Forward(et->mStyleRule);
michael@0 833 }
michael@0 834
michael@0 835 /* virtual */ void
michael@0 836 nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData)
michael@0 837 {
michael@0 838 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
michael@0 839 "pres context mismatch");
michael@0 840 WalkTransitionRule(aData,
michael@0 841 nsCSSPseudoElements::ePseudo_NotPseudoElement);
michael@0 842 }
michael@0 843
michael@0 844 /* virtual */ void
michael@0 845 nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData* aData)
michael@0 846 {
michael@0 847 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
michael@0 848 "pres context mismatch");
michael@0 849
michael@0 850 // Note: If we're the only thing keeping a pseudo-element frame alive
michael@0 851 // (per ProbePseudoStyleContext), we still want to keep it alive, so
michael@0 852 // this is ok.
michael@0 853 WalkTransitionRule(aData, aData->mPseudoType);
michael@0 854 }
michael@0 855
michael@0 856 /* virtual */ void
michael@0 857 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData)
michael@0 858 {
michael@0 859 }
michael@0 860
michael@0 861 #ifdef MOZ_XUL
michael@0 862 /* virtual */ void
michael@0 863 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData)
michael@0 864 {
michael@0 865 }
michael@0 866 #endif
michael@0 867
michael@0 868 /* virtual */ size_t
michael@0 869 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
michael@0 870 {
michael@0 871 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
michael@0 872 }
michael@0 873
michael@0 874 /* virtual */ size_t
michael@0 875 nsTransitionManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
michael@0 876 {
michael@0 877 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
michael@0 878 }
michael@0 879
michael@0 880 struct TransitionEventInfo {
michael@0 881 nsCOMPtr<nsIContent> mElement;
michael@0 882 InternalTransitionEvent mEvent;
michael@0 883
michael@0 884 TransitionEventInfo(nsIContent *aElement, nsCSSProperty aProperty,
michael@0 885 TimeDuration aDuration, const nsAString& aPseudoElement)
michael@0 886 : mElement(aElement)
michael@0 887 , mEvent(true, NS_TRANSITION_END)
michael@0 888 {
michael@0 889 // XXX Looks like nobody initialize WidgetEvent::time
michael@0 890 mEvent.propertyName =
michael@0 891 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
michael@0 892 mEvent.elapsedTime = aDuration.ToSeconds();
michael@0 893 mEvent.pseudoElement = aPseudoElement;
michael@0 894 }
michael@0 895
michael@0 896 // InternalTransitionEvent doesn't support copy-construction, so we need
michael@0 897 // to ourselves in order to work with nsTArray
michael@0 898 TransitionEventInfo(const TransitionEventInfo &aOther)
michael@0 899 : mElement(aOther.mElement)
michael@0 900 , mEvent(true, NS_TRANSITION_END)
michael@0 901 {
michael@0 902 mEvent.AssignTransitionEventData(aOther.mEvent, false);
michael@0 903 }
michael@0 904 };
michael@0 905
michael@0 906 /* virtual */ void
michael@0 907 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
michael@0 908 {
michael@0 909 NS_ABORT_IF_FALSE(mPresContext,
michael@0 910 "refresh driver should not notify additional observers "
michael@0 911 "after pres context has been destroyed");
michael@0 912 if (!mPresContext->GetPresShell()) {
michael@0 913 // Someone might be keeping mPresContext alive past the point
michael@0 914 // where it has been torn down; don't bother doing anything in
michael@0 915 // this case. But do get rid of all our transitions so we stop
michael@0 916 // triggering refreshes.
michael@0 917 RemoveAllElementData();
michael@0 918 return;
michael@0 919 }
michael@0 920
michael@0 921 FlushTransitions(Can_Throttle);
michael@0 922 }
michael@0 923
michael@0 924 void
michael@0 925 nsTransitionManager::FlushTransitions(FlushFlags aFlags)
michael@0 926 {
michael@0 927 if (PR_CLIST_IS_EMPTY(&mElementData)) {
michael@0 928 // no transitions, leave early
michael@0 929 return;
michael@0 930 }
michael@0 931
michael@0 932 nsTArray<TransitionEventInfo> events;
michael@0 933 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
michael@0 934 bool didThrottle = false;
michael@0 935 // Trim transitions that have completed, post restyle events for frames that
michael@0 936 // are still transitioning, and start transitions with delays.
michael@0 937 {
michael@0 938 PRCList *next = PR_LIST_HEAD(&mElementData);
michael@0 939 while (next != &mElementData) {
michael@0 940 ElementTransitions *et = static_cast<ElementTransitions*>(next);
michael@0 941 next = PR_NEXT_LINK(next);
michael@0 942
michael@0 943 bool canThrottleTick = aFlags == Can_Throttle &&
michael@0 944 et->CanPerformOnCompositorThread(
michael@0 945 CommonElementAnimationData::CanAnimateFlags(0)) &&
michael@0 946 et->CanThrottleAnimation(now);
michael@0 947
michael@0 948 NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() ==
michael@0 949 mPresContext->Document(),
michael@0 950 "Element::UnbindFromTree should have "
michael@0 951 "destroyed the element transitions object");
michael@0 952
michael@0 953 uint32_t i = et->mPropertyTransitions.Length();
michael@0 954 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
michael@0 955 bool transitionStartedOrEnded = false;
michael@0 956 do {
michael@0 957 --i;
michael@0 958 ElementPropertyTransition &pt = et->mPropertyTransitions[i];
michael@0 959 if (pt.IsRemovedSentinel()) {
michael@0 960 // Actually remove transitions one throttle-able cycle after their
michael@0 961 // completion. We only clear on a throttle-able cycle because that
michael@0 962 // means it is a regular restyle tick and thus it is safe to discard
michael@0 963 // the transition. If the flush is not throttle-able, we might still
michael@0 964 // have new transitions left to process. See comment below.
michael@0 965 if (aFlags == Can_Throttle) {
michael@0 966 et->mPropertyTransitions.RemoveElementAt(i);
michael@0 967 }
michael@0 968 } else if (pt.mStartTime + pt.mDelay + pt.mIterationDuration <= now) {
michael@0 969 MOZ_ASSERT(pt.mProperties.Length() == 1,
michael@0 970 "Should have one animation property for a transition");
michael@0 971 nsCSSProperty prop = pt.mProperties[0].mProperty;
michael@0 972 if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME))
michael@0 973 {
michael@0 974 prop = nsCSSProps::OtherNameFor(prop);
michael@0 975 }
michael@0 976 nsIAtom* ep = et->mElementProperty;
michael@0 977 NS_NAMED_LITERAL_STRING(before, "::before");
michael@0 978 NS_NAMED_LITERAL_STRING(after, "::after");
michael@0 979 events.AppendElement(
michael@0 980 TransitionEventInfo(et->mElement, prop, pt.mIterationDuration,
michael@0 981 ep == nsGkAtoms::transitionsProperty ?
michael@0 982 EmptyString() :
michael@0 983 ep == nsGkAtoms::transitionsOfBeforeProperty ?
michael@0 984 before :
michael@0 985 after));
michael@0 986
michael@0 987 // Leave this transition in the list for one more refresh
michael@0 988 // cycle, since we haven't yet processed its style change, and
michael@0 989 // if we also have (already, or will have from processing
michael@0 990 // transitionend events or other refresh driver notifications)
michael@0 991 // a non-animation style change that would affect it, we need
michael@0 992 // to know not to start a new transition for the transition
michael@0 993 // from the almost-completed value to the final value.
michael@0 994 pt.SetRemovedSentinel();
michael@0 995 et->UpdateAnimationGeneration(mPresContext);
michael@0 996 transitionStartedOrEnded = true;
michael@0 997 } else if (pt.mStartTime + pt.mDelay <= now && canThrottleTick &&
michael@0 998 !pt.mIsRunningOnCompositor) {
michael@0 999 // Start a transition with a delay where we should start the
michael@0 1000 // transition proper.
michael@0 1001 et->UpdateAnimationGeneration(mPresContext);
michael@0 1002 transitionStartedOrEnded = true;
michael@0 1003 }
michael@0 1004 } while (i != 0);
michael@0 1005
michael@0 1006 // We need to restyle even if the transition rule no longer
michael@0 1007 // applies (in which case we just made it not apply).
michael@0 1008 NS_ASSERTION(et->mElementProperty == nsGkAtoms::transitionsProperty ||
michael@0 1009 et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty ||
michael@0 1010 et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty,
michael@0 1011 "Unexpected element property; might restyle too much");
michael@0 1012 if (!canThrottleTick || transitionStartedOrEnded) {
michael@0 1013 nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ?
michael@0 1014 eRestyle_Self : eRestyle_Subtree;
michael@0 1015 mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint);
michael@0 1016 } else {
michael@0 1017 didThrottle = true;
michael@0 1018 }
michael@0 1019
michael@0 1020 if (et->mPropertyTransitions.IsEmpty()) {
michael@0 1021 et->Destroy();
michael@0 1022 // |et| is now a dangling pointer!
michael@0 1023 et = nullptr;
michael@0 1024 }
michael@0 1025 }
michael@0 1026 }
michael@0 1027
michael@0 1028 if (didThrottle) {
michael@0 1029 mPresContext->Document()->SetNeedStyleFlush();
michael@0 1030 }
michael@0 1031
michael@0 1032 for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
michael@0 1033 TransitionEventInfo &info = events[i];
michael@0 1034 EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
michael@0 1035
michael@0 1036 if (!mPresContext) {
michael@0 1037 break;
michael@0 1038 }
michael@0 1039 }
michael@0 1040 }

mercurial