1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/style/nsTransitionManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1040 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* Code to start and animate CSS transitions. */ 1.11 + 1.12 +#include "nsTransitionManager.h" 1.13 +#include "nsAnimationManager.h" 1.14 +#include "nsIContent.h" 1.15 +#include "nsStyleContext.h" 1.16 +#include "nsCSSProps.h" 1.17 +#include "mozilla/MemoryReporting.h" 1.18 +#include "mozilla/TimeStamp.h" 1.19 +#include "nsRefreshDriver.h" 1.20 +#include "nsRuleProcessorData.h" 1.21 +#include "nsRuleWalker.h" 1.22 +#include "nsCSSPropertySet.h" 1.23 +#include "nsStyleAnimation.h" 1.24 +#include "mozilla/EventDispatcher.h" 1.25 +#include "mozilla/ContentEvents.h" 1.26 +#include "mozilla/dom/Element.h" 1.27 +#include "nsIFrame.h" 1.28 +#include "Layers.h" 1.29 +#include "FrameLayerBuilder.h" 1.30 +#include "nsDisplayList.h" 1.31 +#include "nsStyleChangeList.h" 1.32 +#include "nsStyleSet.h" 1.33 +#include "RestyleManager.h" 1.34 +#include "ActiveLayerTracker.h" 1.35 + 1.36 +using mozilla::TimeStamp; 1.37 +using mozilla::TimeDuration; 1.38 + 1.39 +using namespace mozilla; 1.40 +using namespace mozilla::layers; 1.41 +using namespace mozilla::css; 1.42 + 1.43 +ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement, 1.44 + nsIAtom *aElementProperty, 1.45 + nsTransitionManager *aTransitionManager, 1.46 + TimeStamp aNow) 1.47 + : CommonElementAnimationData(aElement, aElementProperty, 1.48 + aTransitionManager, aNow) 1.49 +{ 1.50 +} 1.51 + 1.52 +double 1.53 +ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const 1.54 +{ 1.55 + // Set |timePortion| to the portion of the way we are through the time 1.56 + // input to the transition's timing function (always within the range 1.57 + // 0-1). 1.58 + double duration = mIterationDuration.ToSeconds(); 1.59 + NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden"); 1.60 + double timePortion; 1.61 + if (IsRemovedSentinel()) { 1.62 + // The transition is being removed, but we still want an update so that any 1.63 + // new transitions start in the right place. 1.64 + timePortion = 1.0; 1.65 + } else if (duration == 0.0) { 1.66 + // When duration is zero, we can still have a transition when delay 1.67 + // is nonzero. 1.68 + if (aRefreshTime >= mStartTime + mDelay) { 1.69 + timePortion = 1.0; 1.70 + } else { 1.71 + timePortion = 0.0; 1.72 + } 1.73 + } else { 1.74 + timePortion = (aRefreshTime - (mStartTime + mDelay)).ToSeconds() / duration; 1.75 + if (timePortion < 0.0) 1.76 + timePortion = 0.0; // use start value during transition-delay 1.77 + if (timePortion > 1.0) 1.78 + timePortion = 1.0; // we might be behind on flushing 1.79 + } 1.80 + MOZ_ASSERT(mProperties.Length() == 1, 1.81 + "Should have one animation property for a transition"); 1.82 + MOZ_ASSERT(mProperties[0].mSegments.Length() == 1, 1.83 + "Animation property should have one segment for a transition"); 1.84 + 1.85 + return mProperties[0].mSegments[0].mTimingFunction.GetValue(timePortion); 1.86 +} 1.87 + 1.88 +static void 1.89 +ElementTransitionsPropertyDtor(void *aObject, 1.90 + nsIAtom *aPropertyName, 1.91 + void *aPropertyValue, 1.92 + void *aData) 1.93 +{ 1.94 + ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue); 1.95 +#ifdef DEBUG 1.96 + NS_ABORT_IF_FALSE(!et->mCalledPropertyDtor, "can't call dtor twice"); 1.97 + et->mCalledPropertyDtor = true; 1.98 +#endif 1.99 + delete et; 1.100 +} 1.101 + 1.102 +void 1.103 +ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime) 1.104 +{ 1.105 + if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) { 1.106 + mStyleRule = new css::AnimValuesStyleRule(); 1.107 + mStyleRuleRefreshTime = aRefreshTime; 1.108 + 1.109 + for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) 1.110 + { 1.111 + ElementPropertyTransition &pt = mPropertyTransitions[i]; 1.112 + if (pt.IsRemovedSentinel()) { 1.113 + continue; 1.114 + } 1.115 + 1.116 + MOZ_ASSERT(pt.mProperties.Length() == 1, 1.117 + "Should have one animation property for a transition"); 1.118 + const AnimationProperty &prop = pt.mProperties[0]; 1.119 + 1.120 + nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty); 1.121 + 1.122 + double valuePortion = pt.ValuePortionFor(aRefreshTime); 1.123 + 1.124 + MOZ_ASSERT(prop.mSegments.Length() == 1, 1.125 + "Animation property should have one segment for a transition"); 1.126 +#ifdef DEBUG 1.127 + bool ok = 1.128 +#endif 1.129 + nsStyleAnimation::Interpolate(prop.mProperty, 1.130 + prop.mSegments[0].mFromValue, 1.131 + prop.mSegments[0].mToValue, 1.132 + valuePortion, *val); 1.133 + NS_ABORT_IF_FALSE(ok, "could not interpolate values"); 1.134 + } 1.135 + } 1.136 +} 1.137 + 1.138 +bool 1.139 +ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const 1.140 +{ 1.141 + for (uint32_t tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) { 1.142 + const ElementPropertyTransition& pt = mPropertyTransitions[tranIdx]; 1.143 + if (pt.HasAnimationOfProperty(aProperty) && !pt.IsRemovedSentinel()) { 1.144 + return true; 1.145 + } 1.146 + } 1.147 + return false; 1.148 +} 1.149 + 1.150 +bool 1.151 +ElementTransitions::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const 1.152 +{ 1.153 + nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement); 1.154 + if (!frame) { 1.155 + return false; 1.156 + } 1.157 + 1.158 + if (mElementProperty != nsGkAtoms::transitionsProperty) { 1.159 + if (nsLayoutUtils::IsAnimationLoggingEnabled()) { 1.160 + nsCString message; 1.161 + message.AppendLiteral("Gecko bug: Async transition of pseudoelements not supported. See bug 771367"); 1.162 + LogAsyncAnimationFailure(message, mElement); 1.163 + } 1.164 + return false; 1.165 + } 1.166 + 1.167 + TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh(); 1.168 + 1.169 + for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) { 1.170 + const ElementPropertyTransition& pt = mPropertyTransitions[i]; 1.171 + MOZ_ASSERT(pt.mProperties.Length() == 1, 1.172 + "Should have one animation property for a transition"); 1.173 + if (css::IsGeometricProperty(pt.mProperties[0].mProperty) && 1.174 + pt.IsRunningAt(now)) { 1.175 + aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty); 1.176 + break; 1.177 + } 1.178 + } 1.179 + 1.180 + bool hasOpacity = false; 1.181 + bool hasTransform = false; 1.182 + bool existsProperty = false; 1.183 + for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) { 1.184 + const ElementPropertyTransition& pt = mPropertyTransitions[i]; 1.185 + if (!pt.IsRunningAt(now)) { 1.186 + continue; 1.187 + } 1.188 + 1.189 + existsProperty = true; 1.190 + 1.191 + MOZ_ASSERT(pt.mProperties.Length() == 1, 1.192 + "Should have one animation property for a transition"); 1.193 + const AnimationProperty& prop = pt.mProperties[0]; 1.194 + 1.195 + if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor( 1.196 + mElement, prop.mProperty, aFlags) || 1.197 + css::CommonElementAnimationData::IsCompositorAnimationDisabledForFrame(frame)) { 1.198 + return false; 1.199 + } 1.200 + if (prop.mProperty == eCSSProperty_opacity) { 1.201 + hasOpacity = true; 1.202 + } else if (prop.mProperty == eCSSProperty_transform) { 1.203 + hasTransform = true; 1.204 + } 1.205 + } 1.206 + 1.207 + // No properties to animate 1.208 + if (!existsProperty) { 1.209 + return false; 1.210 + } 1.211 + 1.212 + // This transition can be done on the compositor. Mark the frame as active, in 1.213 + // case we are able to throttle this transition. 1.214 + if (hasOpacity) { 1.215 + ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity); 1.216 + } 1.217 + if (hasTransform) { 1.218 + ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform); 1.219 + } 1.220 + return true; 1.221 +} 1.222 + 1.223 +/***************************************************************************** 1.224 + * nsTransitionManager * 1.225 + *****************************************************************************/ 1.226 + 1.227 +void 1.228 +nsTransitionManager::UpdateThrottledStylesForSubtree(nsIContent* aContent, 1.229 + nsStyleContext* aParentStyle, 1.230 + nsStyleChangeList& aChangeList) 1.231 +{ 1.232 + dom::Element* element; 1.233 + if (aContent->IsElement()) { 1.234 + element = aContent->AsElement(); 1.235 + } else { 1.236 + element = nullptr; 1.237 + } 1.238 + 1.239 + nsRefPtr<nsStyleContext> newStyle; 1.240 + 1.241 + ElementTransitions* et; 1.242 + if (element && 1.243 + (et = GetElementTransitions(element, 1.244 + nsCSSPseudoElements::ePseudo_NotPseudoElement, 1.245 + false))) { 1.246 + // re-resolve our style 1.247 + newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList); 1.248 + // remove the current transition from the working set 1.249 + et->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh(); 1.250 + } else { 1.251 + newStyle = ReparentContent(aContent, aParentStyle); 1.252 + } 1.253 + 1.254 + // walk the children 1.255 + if (newStyle) { 1.256 + for (nsIContent *child = aContent->GetFirstChild(); child; 1.257 + child = child->GetNextSibling()) { 1.258 + UpdateThrottledStylesForSubtree(child, newStyle, aChangeList); 1.259 + } 1.260 + } 1.261 +} 1.262 + 1.263 +IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsTransitionManager, 1.264 + GetElementTransitions) 1.265 + 1.266 +void 1.267 +nsTransitionManager::UpdateAllThrottledStyles() 1.268 +{ 1.269 + if (PR_CLIST_IS_EMPTY(&mElementData)) { 1.270 + // no throttled transitions, leave early 1.271 + mPresContext->TickLastUpdateThrottledTransitionStyle(); 1.272 + return; 1.273 + } 1.274 + 1.275 + if (mPresContext->ThrottledTransitionStyleIsUpToDate()) { 1.276 + // throttled transitions are up to date, leave early 1.277 + return; 1.278 + } 1.279 + 1.280 + mPresContext->TickLastUpdateThrottledTransitionStyle(); 1.281 + UpdateAllThrottledStylesInternal(); 1.282 +} 1.283 + 1.284 +void 1.285 +nsTransitionManager::ElementDataRemoved() 1.286 +{ 1.287 + // If we have no transitions or animations left, remove ourselves from 1.288 + // the refresh driver. 1.289 + if (PR_CLIST_IS_EMPTY(&mElementData)) { 1.290 + mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style); 1.291 + } 1.292 +} 1.293 + 1.294 +void 1.295 +nsTransitionManager::AddElementData(CommonElementAnimationData* aData) 1.296 +{ 1.297 + if (PR_CLIST_IS_EMPTY(&mElementData)) { 1.298 + // We need to observe the refresh driver. 1.299 + nsRefreshDriver *rd = mPresContext->RefreshDriver(); 1.300 + rd->AddRefreshObserver(this, Flush_Style); 1.301 + } 1.302 + 1.303 + PR_INSERT_BEFORE(aData, &mElementData); 1.304 +} 1.305 + 1.306 +already_AddRefed<nsIStyleRule> 1.307 +nsTransitionManager::StyleContextChanged(dom::Element *aElement, 1.308 + nsStyleContext *aOldStyleContext, 1.309 + nsStyleContext *aNewStyleContext) 1.310 +{ 1.311 + NS_PRECONDITION(aOldStyleContext->GetPseudo() == 1.312 + aNewStyleContext->GetPseudo(), 1.313 + "pseudo type mismatch"); 1.314 + // If we were called from ReparentStyleContext, this assertion would 1.315 + // actually fire. If we need to be called from there, we can probably 1.316 + // just remove it; the condition probably isn't critical, although 1.317 + // it's worth thinking about some more. 1.318 + NS_PRECONDITION(aOldStyleContext->HasPseudoElementData() == 1.319 + aNewStyleContext->HasPseudoElementData(), 1.320 + "pseudo type mismatch"); 1.321 + 1.322 + if (!mPresContext->IsDynamic()) { 1.323 + // For print or print preview, ignore transitions. 1.324 + return nullptr; 1.325 + } 1.326 + 1.327 + // NOTE: Things in this function (and ConsiderStartingTransition) 1.328 + // should never call PeekStyleData because we don't preserve gotten 1.329 + // structs across reframes. 1.330 + 1.331 + // Return sooner (before the startedAny check below) for the most 1.332 + // common case: no transitions specified or running. 1.333 + const nsStyleDisplay *disp = aNewStyleContext->StyleDisplay(); 1.334 + nsCSSPseudoElements::Type pseudoType = aNewStyleContext->GetPseudoType(); 1.335 + if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) { 1.336 + if (pseudoType != nsCSSPseudoElements::ePseudo_before && 1.337 + pseudoType != nsCSSPseudoElements::ePseudo_after) { 1.338 + return nullptr; 1.339 + } 1.340 + 1.341 + NS_ASSERTION((pseudoType == nsCSSPseudoElements::ePseudo_before && 1.342 + aElement->Tag() == nsGkAtoms::mozgeneratedcontentbefore) || 1.343 + (pseudoType == nsCSSPseudoElements::ePseudo_after && 1.344 + aElement->Tag() == nsGkAtoms::mozgeneratedcontentafter), 1.345 + "Unexpected aElement coming through"); 1.346 + 1.347 + // Else the element we want to use from now on is the element the 1.348 + // :before or :after is attached to. 1.349 + aElement = aElement->GetParent()->AsElement(); 1.350 + } 1.351 + 1.352 + ElementTransitions *et = 1.353 + GetElementTransitions(aElement, pseudoType, false); 1.354 + if (!et && 1.355 + disp->mTransitionPropertyCount == 1 && 1.356 + disp->mTransitions[0].GetDelay() == 0.0f && 1.357 + disp->mTransitions[0].GetDuration() == 0.0f) { 1.358 + return nullptr; 1.359 + } 1.360 + 1.361 + 1.362 + if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) { 1.363 + return nullptr; 1.364 + } 1.365 + 1.366 + if (aNewStyleContext->GetParent() && 1.367 + aNewStyleContext->GetParent()->HasPseudoElementData()) { 1.368 + // Ignore transitions on things that inherit properties from 1.369 + // pseudo-elements. 1.370 + // FIXME (Bug 522599): Add tests for this. 1.371 + return nullptr; 1.372 + } 1.373 + 1.374 + NS_WARN_IF_FALSE(!nsLayoutUtils::AreAsyncAnimationsEnabled() || 1.375 + mPresContext->ThrottledTransitionStyleIsUpToDate(), 1.376 + "throttled animations not up to date"); 1.377 + 1.378 + // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html 1.379 + // I'll consider only the transitions from the number of items in 1.380 + // 'transition-property' on down, and later ones will override earlier 1.381 + // ones (tracked using |whichStarted|). 1.382 + bool startedAny = false; 1.383 + nsCSSPropertySet whichStarted; 1.384 + for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) { 1.385 + const nsTransition& t = disp->mTransitions[i]; 1.386 + // Check delay and duration first, since they default to zero, and 1.387 + // when they're both zero, we can ignore the transition. 1.388 + if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) { 1.389 + // We might have something to transition. See if any of the 1.390 + // properties in question changed and are animatable. 1.391 + // FIXME: Would be good to find a way to share code between this 1.392 + // interpretation of transition-property and the one below. 1.393 + nsCSSProperty property = t.GetProperty(); 1.394 + if (property == eCSSPropertyExtra_no_properties || 1.395 + property == eCSSPropertyExtra_variable || 1.396 + property == eCSSProperty_UNKNOWN) { 1.397 + // Nothing to do, but need to exclude this from cases below. 1.398 + } else if (property == eCSSPropertyExtra_all_properties) { 1.399 + for (nsCSSProperty p = nsCSSProperty(0); 1.400 + p < eCSSProperty_COUNT_no_shorthands; 1.401 + p = nsCSSProperty(p + 1)) { 1.402 + ConsiderStartingTransition(p, t, aElement, et, 1.403 + aOldStyleContext, aNewStyleContext, 1.404 + &startedAny, &whichStarted); 1.405 + } 1.406 + } else if (nsCSSProps::IsShorthand(property)) { 1.407 + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) { 1.408 + ConsiderStartingTransition(*subprop, t, aElement, et, 1.409 + aOldStyleContext, aNewStyleContext, 1.410 + &startedAny, &whichStarted); 1.411 + } 1.412 + } else { 1.413 + ConsiderStartingTransition(property, t, aElement, et, 1.414 + aOldStyleContext, aNewStyleContext, 1.415 + &startedAny, &whichStarted); 1.416 + } 1.417 + } 1.418 + } 1.419 + 1.420 + // Stop any transitions for properties that are no longer in 1.421 + // 'transition-property'. 1.422 + // Also stop any transitions for properties that just changed (and are 1.423 + // still in the set of properties to transition), but we didn't just 1.424 + // start the transition because delay and duration are both zero. 1.425 + if (et) { 1.426 + bool checkProperties = 1.427 + disp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties; 1.428 + nsCSSPropertySet allTransitionProperties; 1.429 + if (checkProperties) { 1.430 + for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) { 1.431 + const nsTransition& t = disp->mTransitions[i]; 1.432 + // FIXME: Would be good to find a way to share code between this 1.433 + // interpretation of transition-property and the one above. 1.434 + nsCSSProperty property = t.GetProperty(); 1.435 + if (property == eCSSPropertyExtra_no_properties || 1.436 + property == eCSSPropertyExtra_variable || 1.437 + property == eCSSProperty_UNKNOWN) { 1.438 + // Nothing to do, but need to exclude this from cases below. 1.439 + } else if (property == eCSSPropertyExtra_all_properties) { 1.440 + for (nsCSSProperty p = nsCSSProperty(0); 1.441 + p < eCSSProperty_COUNT_no_shorthands; 1.442 + p = nsCSSProperty(p + 1)) { 1.443 + allTransitionProperties.AddProperty(p); 1.444 + } 1.445 + } else if (nsCSSProps::IsShorthand(property)) { 1.446 + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) { 1.447 + allTransitionProperties.AddProperty(*subprop); 1.448 + } 1.449 + } else { 1.450 + allTransitionProperties.AddProperty(property); 1.451 + } 1.452 + } 1.453 + } 1.454 + 1.455 + nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions; 1.456 + uint32_t i = pts.Length(); 1.457 + NS_ABORT_IF_FALSE(i != 0, "empty transitions list?"); 1.458 + nsStyleAnimation::Value currentValue; 1.459 + do { 1.460 + --i; 1.461 + ElementPropertyTransition &pt = pts[i]; 1.462 + MOZ_ASSERT(pt.mProperties.Length() == 1, 1.463 + "Should have one animation property for a transition"); 1.464 + MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1, 1.465 + "Animation property should have one segment for a transition"); 1.466 + const AnimationProperty& prop = pt.mProperties[0]; 1.467 + const AnimationPropertySegment& segment = prop.mSegments[0]; 1.468 + // properties no longer in 'transition-property' 1.469 + if ((checkProperties && 1.470 + !allTransitionProperties.HasProperty(prop.mProperty)) || 1.471 + // properties whose computed values changed but delay and 1.472 + // duration are both zero 1.473 + !ExtractComputedValueForTransition(prop.mProperty, aNewStyleContext, 1.474 + currentValue) || 1.475 + currentValue != segment.mToValue) { 1.476 + // stop the transition 1.477 + pts.RemoveElementAt(i); 1.478 + et->UpdateAnimationGeneration(mPresContext); 1.479 + } 1.480 + } while (i != 0); 1.481 + 1.482 + if (pts.IsEmpty()) { 1.483 + et->Destroy(); 1.484 + et = nullptr; 1.485 + } 1.486 + } 1.487 + 1.488 + if (!startedAny) { 1.489 + return nullptr; 1.490 + } 1.491 + 1.492 + NS_ABORT_IF_FALSE(et, "must have element transitions if we started " 1.493 + "any transitions"); 1.494 + 1.495 + // In the CSS working group discussion (2009 Jul 15 telecon, 1.496 + // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of 1.497 + // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html , 1.498 + // the working group decided that a transition property on an 1.499 + // element should not cause any transitions if the property change 1.500 + // is itself inheriting a value that is transitioning on an 1.501 + // ancestor. So, to get the correct behavior, we continue the 1.502 + // restyle that caused this transition using a "covering" rule that 1.503 + // covers up any changes on which we started transitions, so that 1.504 + // descendants don't start their own transitions. (In the case of 1.505 + // negative transition delay, this covering rule produces different 1.506 + // results than applying the transition rule immediately would). 1.507 + // Our caller is responsible for restyling again using this covering 1.508 + // rule. 1.509 + 1.510 + nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule; 1.511 + 1.512 + nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions; 1.513 + for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) { 1.514 + ElementPropertyTransition &pt = pts[i]; 1.515 + MOZ_ASSERT(pt.mProperties.Length() == 1, 1.516 + "Should have one animation property for a transition"); 1.517 + MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1, 1.518 + "Animation property should have one segment for a transition"); 1.519 + AnimationProperty& prop = pt.mProperties[0]; 1.520 + AnimationPropertySegment& segment = prop.mSegments[0]; 1.521 + if (whichStarted.HasProperty(prop.mProperty)) { 1.522 + coverRule->AddValue(prop.mProperty, segment.mFromValue); 1.523 + } 1.524 + } 1.525 + 1.526 + et->mStyleRule = nullptr; 1.527 + 1.528 + return coverRule.forget(); 1.529 +} 1.530 + 1.531 +void 1.532 +nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty, 1.533 + const nsTransition& aTransition, 1.534 + dom::Element* aElement, 1.535 + ElementTransitions*& aElementTransitions, 1.536 + nsStyleContext* aOldStyleContext, 1.537 + nsStyleContext* aNewStyleContext, 1.538 + bool* aStartedAny, 1.539 + nsCSSPropertySet* aWhichStarted) 1.540 +{ 1.541 + // IsShorthand itself will assert if aProperty is not a property. 1.542 + NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty), 1.543 + "property out of range"); 1.544 + NS_ASSERTION(!aElementTransitions || 1.545 + aElementTransitions->mElement == aElement, "Element mismatch"); 1.546 + 1.547 + if (aWhichStarted->HasProperty(aProperty)) { 1.548 + // A later item in transition-property already started a 1.549 + // transition for this property, so we ignore this one. 1.550 + // See comment above and 1.551 + // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html . 1.552 + return; 1.553 + } 1.554 + 1.555 + if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) { 1.556 + return; 1.557 + } 1.558 + 1.559 + ElementPropertyTransition pt; 1.560 + 1.561 + nsStyleAnimation::Value startValue, endValue, dummyValue; 1.562 + bool haveValues = 1.563 + ExtractComputedValueForTransition(aProperty, aOldStyleContext, 1.564 + startValue) && 1.565 + ExtractComputedValueForTransition(aProperty, aNewStyleContext, 1.566 + endValue); 1.567 + 1.568 + bool haveChange = startValue != endValue; 1.569 + 1.570 + bool shouldAnimate = 1.571 + haveValues && 1.572 + haveChange && 1.573 + // Check that we can interpolate between these values 1.574 + // (If this is ever a performance problem, we could add a 1.575 + // CanInterpolate method, but it seems fine for now.) 1.576 + nsStyleAnimation::Interpolate(aProperty, startValue, endValue, 1.577 + 0.5, dummyValue); 1.578 + 1.579 + bool haveCurrentTransition = false; 1.580 + uint32_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex; 1.581 + const ElementPropertyTransition *oldPT = nullptr; 1.582 + if (aElementTransitions) { 1.583 + nsTArray<ElementPropertyTransition> &pts = 1.584 + aElementTransitions->mPropertyTransitions; 1.585 + for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) { 1.586 + MOZ_ASSERT(pts[i].mProperties.Length() == 1, 1.587 + "Should have one animation property for a transition"); 1.588 + if (pts[i].mProperties[0].mProperty == aProperty) { 1.589 + haveCurrentTransition = true; 1.590 + currentIndex = i; 1.591 + oldPT = &aElementTransitions->mPropertyTransitions[currentIndex]; 1.592 + break; 1.593 + } 1.594 + } 1.595 + } 1.596 + 1.597 + // If we got a style change that changed the value to the endpoint 1.598 + // of the currently running transition, we don't want to interrupt 1.599 + // its timing function. 1.600 + // This needs to be before the !shouldAnimate && haveCurrentTransition 1.601 + // case below because we might be close enough to the end of the 1.602 + // transition that the current value rounds to the final value. In 1.603 + // this case, we'll end up with shouldAnimate as false (because 1.604 + // there's no value change), but we need to return early here rather 1.605 + // than cancel the running transition because shouldAnimate is false! 1.606 + MOZ_ASSERT(!oldPT || oldPT->mProperties[0].mSegments.Length() == 1, 1.607 + "Should have one animation property segment for a transition"); 1.608 + if (haveCurrentTransition && haveValues && 1.609 + oldPT->mProperties[0].mSegments[0].mToValue == endValue) { 1.610 + // WalkTransitionRule already called RestyleForAnimation. 1.611 + return; 1.612 + } 1.613 + 1.614 + nsPresContext *presContext = aNewStyleContext->PresContext(); 1.615 + 1.616 + if (!shouldAnimate) { 1.617 + if (haveCurrentTransition) { 1.618 + // We're in the middle of a transition, and just got a non-transition 1.619 + // style change to something that we can't animate. This might happen 1.620 + // because we got a non-transition style change changing to the current 1.621 + // in-progress value (which is particularly easy to cause when we're 1.622 + // currently in the 'transition-delay'). It also might happen because we 1.623 + // just got a style change to a value that can't be interpolated. 1.624 + nsTArray<ElementPropertyTransition> &pts = 1.625 + aElementTransitions->mPropertyTransitions; 1.626 + pts.RemoveElementAt(currentIndex); 1.627 + aElementTransitions->UpdateAnimationGeneration(mPresContext); 1.628 + 1.629 + if (pts.IsEmpty()) { 1.630 + aElementTransitions->Destroy(); 1.631 + // |aElementTransitions| is now a dangling pointer! 1.632 + aElementTransitions = nullptr; 1.633 + } 1.634 + // WalkTransitionRule already called RestyleForAnimation. 1.635 + } 1.636 + return; 1.637 + } 1.638 + 1.639 + TimeStamp mostRecentRefresh = 1.640 + presContext->RefreshDriver()->MostRecentRefresh(); 1.641 + 1.642 + const nsTimingFunction &tf = aTransition.GetTimingFunction(); 1.643 + float delay = aTransition.GetDelay(); 1.644 + float duration = aTransition.GetDuration(); 1.645 + if (duration < 0.0) { 1.646 + // The spec says a negative duration is treated as zero. 1.647 + duration = 0.0; 1.648 + } 1.649 + pt.mStartForReversingTest = startValue; 1.650 + pt.mReversePortion = 1.0; 1.651 + 1.652 + // If the new transition reverses an existing one, we'll need to 1.653 + // handle the timing differently. 1.654 + if (haveCurrentTransition && 1.655 + !oldPT->IsRemovedSentinel() && 1.656 + oldPT->mStartForReversingTest == endValue) { 1.657 + // Compute the appropriate negative transition-delay such that right 1.658 + // now we'd end up at the current position. 1.659 + double valuePortion = 1.660 + oldPT->ValuePortionFor(mostRecentRefresh) * oldPT->mReversePortion + 1.661 + (1.0 - oldPT->mReversePortion); 1.662 + // A timing function with negative y1 (or y2!) might make 1.663 + // valuePortion negative. In this case, we still want to apply our 1.664 + // reversing logic based on relative distances, not make duration 1.665 + // negative. 1.666 + if (valuePortion < 0.0) { 1.667 + valuePortion = -valuePortion; 1.668 + } 1.669 + // A timing function with y2 (or y1!) greater than one might 1.670 + // advance past its terminal value. It's probably a good idea to 1.671 + // clamp valuePortion to be at most one to preserve the invariant 1.672 + // that a transition will complete within at most its specified 1.673 + // time. 1.674 + if (valuePortion > 1.0) { 1.675 + valuePortion = 1.0; 1.676 + } 1.677 + 1.678 + // Negative delays are essentially part of the transition 1.679 + // function, so reduce them along with the duration, but don't 1.680 + // reduce positive delays. 1.681 + if (delay < 0.0f) { 1.682 + delay *= valuePortion; 1.683 + } 1.684 + 1.685 + duration *= valuePortion; 1.686 + 1.687 + pt.mStartForReversingTest = oldPT->mProperties[0].mSegments[0].mToValue; 1.688 + pt.mReversePortion = valuePortion; 1.689 + } 1.690 + 1.691 + AnimationProperty& prop = *pt.mProperties.AppendElement(); 1.692 + prop.mProperty = aProperty; 1.693 + 1.694 + AnimationPropertySegment& segment = *prop.mSegments.AppendElement(); 1.695 + segment.mFromValue = startValue; 1.696 + segment.mToValue = endValue; 1.697 + segment.mFromKey = 0; 1.698 + segment.mToKey = 1; 1.699 + segment.mTimingFunction.Init(tf); 1.700 + 1.701 + pt.mStartTime = mostRecentRefresh; 1.702 + pt.mDelay = TimeDuration::FromMilliseconds(delay); 1.703 + pt.mIterationDuration = TimeDuration::FromMilliseconds(duration); 1.704 + pt.mIterationCount = 1; 1.705 + pt.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL; 1.706 + pt.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS; 1.707 + pt.mPlayState = NS_STYLE_ANIMATION_PLAY_STATE_RUNNING; 1.708 + pt.mPauseStart = TimeStamp(); 1.709 + 1.710 + if (!aElementTransitions) { 1.711 + aElementTransitions = 1.712 + GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(), 1.713 + true); 1.714 + if (!aElementTransitions) { 1.715 + NS_WARNING("allocating ElementTransitions failed"); 1.716 + return; 1.717 + } 1.718 + } 1.719 + 1.720 + nsTArray<ElementPropertyTransition> &pts = 1.721 + aElementTransitions->mPropertyTransitions; 1.722 +#ifdef DEBUG 1.723 + for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) { 1.724 + NS_ABORT_IF_FALSE(pts[i].mProperties.Length() == 1, 1.725 + "Should have one animation property for a transition"); 1.726 + NS_ABORT_IF_FALSE(i == currentIndex || 1.727 + pts[i].mProperties[0].mProperty != aProperty, 1.728 + "duplicate transitions for property"); 1.729 + } 1.730 +#endif 1.731 + if (haveCurrentTransition) { 1.732 + pts[currentIndex] = pt; 1.733 + } else { 1.734 + if (!pts.AppendElement(pt)) { 1.735 + NS_WARNING("out of memory"); 1.736 + return; 1.737 + } 1.738 + } 1.739 + aElementTransitions->UpdateAnimationGeneration(mPresContext); 1.740 + 1.741 + nsRestyleHint hint = 1.742 + aNewStyleContext->GetPseudoType() == 1.743 + nsCSSPseudoElements::ePseudo_NotPseudoElement ? 1.744 + eRestyle_Self : eRestyle_Subtree; 1.745 + presContext->PresShell()->RestyleForAnimation(aElement, hint); 1.746 + 1.747 + *aStartedAny = true; 1.748 + aWhichStarted->AddProperty(aProperty); 1.749 +} 1.750 + 1.751 +ElementTransitions* 1.752 +nsTransitionManager::GetElementTransitions(dom::Element *aElement, 1.753 + nsCSSPseudoElements::Type aPseudoType, 1.754 + bool aCreateIfNeeded) 1.755 +{ 1.756 + if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) { 1.757 + // Early return for the most common case. 1.758 + return nullptr; 1.759 + } 1.760 + 1.761 + nsIAtom *propName; 1.762 + if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) { 1.763 + propName = nsGkAtoms::transitionsProperty; 1.764 + } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) { 1.765 + propName = nsGkAtoms::transitionsOfBeforeProperty; 1.766 + } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) { 1.767 + propName = nsGkAtoms::transitionsOfAfterProperty; 1.768 + } else { 1.769 + NS_ASSERTION(!aCreateIfNeeded, 1.770 + "should never try to create transitions for pseudo " 1.771 + "other than :before or :after"); 1.772 + return nullptr; 1.773 + } 1.774 + ElementTransitions *et = static_cast<ElementTransitions*>( 1.775 + aElement->GetProperty(propName)); 1.776 + if (!et && aCreateIfNeeded) { 1.777 + // FIXME: Consider arena-allocating? 1.778 + et = new ElementTransitions(aElement, propName, this, 1.779 + mPresContext->RefreshDriver()->MostRecentRefresh()); 1.780 + nsresult rv = aElement->SetProperty(propName, et, 1.781 + ElementTransitionsPropertyDtor, false); 1.782 + if (NS_FAILED(rv)) { 1.783 + NS_WARNING("SetProperty failed"); 1.784 + delete et; 1.785 + return nullptr; 1.786 + } 1.787 + if (propName == nsGkAtoms::transitionsProperty) { 1.788 + aElement->SetMayHaveAnimations(); 1.789 + } 1.790 + 1.791 + AddElementData(et); 1.792 + } 1.793 + 1.794 + return et; 1.795 +} 1.796 + 1.797 +/* 1.798 + * nsIStyleRuleProcessor implementation 1.799 + */ 1.800 + 1.801 +void 1.802 +nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData, 1.803 + nsCSSPseudoElements::Type aPseudoType) 1.804 +{ 1.805 + ElementTransitions *et = 1.806 + GetElementTransitions(aData->mElement, aPseudoType, false); 1.807 + if (!et) { 1.808 + return; 1.809 + } 1.810 + 1.811 + if (!mPresContext->IsDynamic()) { 1.812 + // For print or print preview, ignore animations. 1.813 + return; 1.814 + } 1.815 + 1.816 + if (aData->mPresContext->IsProcessingRestyles() && 1.817 + !aData->mPresContext->IsProcessingAnimationStyleChange()) { 1.818 + // If we're processing a normal style change rather than one from 1.819 + // animation, don't add the transition rule. This allows us to 1.820 + // compute the new style value rather than having the transition 1.821 + // override it, so that we can start transitioning differently. 1.822 + 1.823 + // We need to immediately restyle with animation 1.824 + // after doing this. 1.825 + nsRestyleHint hint = 1.826 + aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ? 1.827 + eRestyle_Self : eRestyle_Subtree; 1.828 + mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint); 1.829 + return; 1.830 + } 1.831 + 1.832 + et->EnsureStyleRuleFor( 1.833 + aData->mPresContext->RefreshDriver()->MostRecentRefresh()); 1.834 + 1.835 + aData->mRuleWalker->Forward(et->mStyleRule); 1.836 +} 1.837 + 1.838 +/* virtual */ void 1.839 +nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData) 1.840 +{ 1.841 + NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, 1.842 + "pres context mismatch"); 1.843 + WalkTransitionRule(aData, 1.844 + nsCSSPseudoElements::ePseudo_NotPseudoElement); 1.845 +} 1.846 + 1.847 +/* virtual */ void 1.848 +nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData* aData) 1.849 +{ 1.850 + NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, 1.851 + "pres context mismatch"); 1.852 + 1.853 + // Note: If we're the only thing keeping a pseudo-element frame alive 1.854 + // (per ProbePseudoStyleContext), we still want to keep it alive, so 1.855 + // this is ok. 1.856 + WalkTransitionRule(aData, aData->mPseudoType); 1.857 +} 1.858 + 1.859 +/* virtual */ void 1.860 +nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData) 1.861 +{ 1.862 +} 1.863 + 1.864 +#ifdef MOZ_XUL 1.865 +/* virtual */ void 1.866 +nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData) 1.867 +{ 1.868 +} 1.869 +#endif 1.870 + 1.871 +/* virtual */ size_t 1.872 +nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const 1.873 +{ 1.874 + return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf); 1.875 +} 1.876 + 1.877 +/* virtual */ size_t 1.878 +nsTransitionManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const 1.879 +{ 1.880 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.881 +} 1.882 + 1.883 +struct TransitionEventInfo { 1.884 + nsCOMPtr<nsIContent> mElement; 1.885 + InternalTransitionEvent mEvent; 1.886 + 1.887 + TransitionEventInfo(nsIContent *aElement, nsCSSProperty aProperty, 1.888 + TimeDuration aDuration, const nsAString& aPseudoElement) 1.889 + : mElement(aElement) 1.890 + , mEvent(true, NS_TRANSITION_END) 1.891 + { 1.892 + // XXX Looks like nobody initialize WidgetEvent::time 1.893 + mEvent.propertyName = 1.894 + NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty)); 1.895 + mEvent.elapsedTime = aDuration.ToSeconds(); 1.896 + mEvent.pseudoElement = aPseudoElement; 1.897 + } 1.898 + 1.899 + // InternalTransitionEvent doesn't support copy-construction, so we need 1.900 + // to ourselves in order to work with nsTArray 1.901 + TransitionEventInfo(const TransitionEventInfo &aOther) 1.902 + : mElement(aOther.mElement) 1.903 + , mEvent(true, NS_TRANSITION_END) 1.904 + { 1.905 + mEvent.AssignTransitionEventData(aOther.mEvent, false); 1.906 + } 1.907 +}; 1.908 + 1.909 +/* virtual */ void 1.910 +nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) 1.911 +{ 1.912 + NS_ABORT_IF_FALSE(mPresContext, 1.913 + "refresh driver should not notify additional observers " 1.914 + "after pres context has been destroyed"); 1.915 + if (!mPresContext->GetPresShell()) { 1.916 + // Someone might be keeping mPresContext alive past the point 1.917 + // where it has been torn down; don't bother doing anything in 1.918 + // this case. But do get rid of all our transitions so we stop 1.919 + // triggering refreshes. 1.920 + RemoveAllElementData(); 1.921 + return; 1.922 + } 1.923 + 1.924 + FlushTransitions(Can_Throttle); 1.925 +} 1.926 + 1.927 +void 1.928 +nsTransitionManager::FlushTransitions(FlushFlags aFlags) 1.929 +{ 1.930 + if (PR_CLIST_IS_EMPTY(&mElementData)) { 1.931 + // no transitions, leave early 1.932 + return; 1.933 + } 1.934 + 1.935 + nsTArray<TransitionEventInfo> events; 1.936 + TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); 1.937 + bool didThrottle = false; 1.938 + // Trim transitions that have completed, post restyle events for frames that 1.939 + // are still transitioning, and start transitions with delays. 1.940 + { 1.941 + PRCList *next = PR_LIST_HEAD(&mElementData); 1.942 + while (next != &mElementData) { 1.943 + ElementTransitions *et = static_cast<ElementTransitions*>(next); 1.944 + next = PR_NEXT_LINK(next); 1.945 + 1.946 + bool canThrottleTick = aFlags == Can_Throttle && 1.947 + et->CanPerformOnCompositorThread( 1.948 + CommonElementAnimationData::CanAnimateFlags(0)) && 1.949 + et->CanThrottleAnimation(now); 1.950 + 1.951 + NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() == 1.952 + mPresContext->Document(), 1.953 + "Element::UnbindFromTree should have " 1.954 + "destroyed the element transitions object"); 1.955 + 1.956 + uint32_t i = et->mPropertyTransitions.Length(); 1.957 + NS_ABORT_IF_FALSE(i != 0, "empty transitions list?"); 1.958 + bool transitionStartedOrEnded = false; 1.959 + do { 1.960 + --i; 1.961 + ElementPropertyTransition &pt = et->mPropertyTransitions[i]; 1.962 + if (pt.IsRemovedSentinel()) { 1.963 + // Actually remove transitions one throttle-able cycle after their 1.964 + // completion. We only clear on a throttle-able cycle because that 1.965 + // means it is a regular restyle tick and thus it is safe to discard 1.966 + // the transition. If the flush is not throttle-able, we might still 1.967 + // have new transitions left to process. See comment below. 1.968 + if (aFlags == Can_Throttle) { 1.969 + et->mPropertyTransitions.RemoveElementAt(i); 1.970 + } 1.971 + } else if (pt.mStartTime + pt.mDelay + pt.mIterationDuration <= now) { 1.972 + MOZ_ASSERT(pt.mProperties.Length() == 1, 1.973 + "Should have one animation property for a transition"); 1.974 + nsCSSProperty prop = pt.mProperties[0].mProperty; 1.975 + if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME)) 1.976 + { 1.977 + prop = nsCSSProps::OtherNameFor(prop); 1.978 + } 1.979 + nsIAtom* ep = et->mElementProperty; 1.980 + NS_NAMED_LITERAL_STRING(before, "::before"); 1.981 + NS_NAMED_LITERAL_STRING(after, "::after"); 1.982 + events.AppendElement( 1.983 + TransitionEventInfo(et->mElement, prop, pt.mIterationDuration, 1.984 + ep == nsGkAtoms::transitionsProperty ? 1.985 + EmptyString() : 1.986 + ep == nsGkAtoms::transitionsOfBeforeProperty ? 1.987 + before : 1.988 + after)); 1.989 + 1.990 + // Leave this transition in the list for one more refresh 1.991 + // cycle, since we haven't yet processed its style change, and 1.992 + // if we also have (already, or will have from processing 1.993 + // transitionend events or other refresh driver notifications) 1.994 + // a non-animation style change that would affect it, we need 1.995 + // to know not to start a new transition for the transition 1.996 + // from the almost-completed value to the final value. 1.997 + pt.SetRemovedSentinel(); 1.998 + et->UpdateAnimationGeneration(mPresContext); 1.999 + transitionStartedOrEnded = true; 1.1000 + } else if (pt.mStartTime + pt.mDelay <= now && canThrottleTick && 1.1001 + !pt.mIsRunningOnCompositor) { 1.1002 + // Start a transition with a delay where we should start the 1.1003 + // transition proper. 1.1004 + et->UpdateAnimationGeneration(mPresContext); 1.1005 + transitionStartedOrEnded = true; 1.1006 + } 1.1007 + } while (i != 0); 1.1008 + 1.1009 + // We need to restyle even if the transition rule no longer 1.1010 + // applies (in which case we just made it not apply). 1.1011 + NS_ASSERTION(et->mElementProperty == nsGkAtoms::transitionsProperty || 1.1012 + et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty || 1.1013 + et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty, 1.1014 + "Unexpected element property; might restyle too much"); 1.1015 + if (!canThrottleTick || transitionStartedOrEnded) { 1.1016 + nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ? 1.1017 + eRestyle_Self : eRestyle_Subtree; 1.1018 + mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint); 1.1019 + } else { 1.1020 + didThrottle = true; 1.1021 + } 1.1022 + 1.1023 + if (et->mPropertyTransitions.IsEmpty()) { 1.1024 + et->Destroy(); 1.1025 + // |et| is now a dangling pointer! 1.1026 + et = nullptr; 1.1027 + } 1.1028 + } 1.1029 + } 1.1030 + 1.1031 + if (didThrottle) { 1.1032 + mPresContext->Document()->SetNeedStyleFlush(); 1.1033 + } 1.1034 + 1.1035 + for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) { 1.1036 + TransitionEventInfo &info = events[i]; 1.1037 + EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent); 1.1038 + 1.1039 + if (!mPresContext) { 1.1040 + break; 1.1041 + } 1.1042 + } 1.1043 +}