michael@0: /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AnimationCommon.h" michael@0: #include "nsTransitionManager.h" michael@0: #include "nsAnimationManager.h" michael@0: michael@0: #include "gfxPlatform.h" michael@0: #include "nsRuleData.h" michael@0: #include "nsCSSValue.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "Layers.h" michael@0: #include "FrameLayerBuilder.h" michael@0: #include "nsDisplayList.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "RestyleManager.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsStyleChangeList.h" michael@0: michael@0: michael@0: using mozilla::layers::Layer; michael@0: michael@0: namespace mozilla { michael@0: namespace css { michael@0: michael@0: /* static */ bool michael@0: IsGeometricProperty(nsCSSProperty aProperty) michael@0: { michael@0: switch (aProperty) { michael@0: case eCSSProperty_bottom: michael@0: case eCSSProperty_height: michael@0: case eCSSProperty_left: michael@0: case eCSSProperty_right: michael@0: case eCSSProperty_top: michael@0: case eCSSProperty_width: michael@0: return true; michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: CommonAnimationManager::CommonAnimationManager(nsPresContext *aPresContext) michael@0: : mPresContext(aPresContext) michael@0: { michael@0: PR_INIT_CLIST(&mElementData); michael@0: } michael@0: michael@0: CommonAnimationManager::~CommonAnimationManager() michael@0: { michael@0: NS_ABORT_IF_FALSE(!mPresContext, "Disconnect should have been called"); michael@0: } michael@0: michael@0: void michael@0: CommonAnimationManager::Disconnect() michael@0: { michael@0: // Content nodes might outlive the transition or animation manager. michael@0: RemoveAllElementData(); michael@0: michael@0: mPresContext = nullptr; michael@0: } michael@0: michael@0: void michael@0: CommonAnimationManager::RemoveAllElementData() michael@0: { michael@0: while (!PR_CLIST_IS_EMPTY(&mElementData)) { michael@0: CommonElementAnimationData *head = michael@0: static_cast(PR_LIST_HEAD(&mElementData)); michael@0: head->Destroy(); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * nsISupports implementation michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS(CommonAnimationManager, nsIStyleRuleProcessor) michael@0: michael@0: nsRestyleHint michael@0: CommonAnimationManager::HasStateDependentStyle(StateRuleProcessorData* aData) michael@0: { michael@0: return nsRestyleHint(0); michael@0: } michael@0: michael@0: nsRestyleHint michael@0: CommonAnimationManager::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) michael@0: { michael@0: return nsRestyleHint(0); michael@0: } michael@0: michael@0: bool michael@0: CommonAnimationManager::HasDocumentStateDependentStyle(StateRuleProcessorData* aData) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: nsRestyleHint michael@0: CommonAnimationManager::HasAttributeDependentStyle(AttributeRuleProcessorData* aData) michael@0: { michael@0: return nsRestyleHint(0); michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: CommonAnimationManager::MediumFeaturesChanged(nsPresContext* aPresContext) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: /* virtual */ size_t michael@0: CommonAnimationManager::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: // Measurement of the following members may be added later if DMD finds it is michael@0: // worthwhile: michael@0: // - mElementData michael@0: // michael@0: // The following members are not measured michael@0: // - mPresContext, because it's non-owning michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: /* virtual */ size_t michael@0: CommonAnimationManager::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: /* static */ bool michael@0: CommonAnimationManager::ExtractComputedValueForTransition( michael@0: nsCSSProperty aProperty, michael@0: nsStyleContext* aStyleContext, michael@0: nsStyleAnimation::Value& aComputedValue) michael@0: { michael@0: bool result = michael@0: nsStyleAnimation::ExtractComputedValue(aProperty, aStyleContext, michael@0: aComputedValue); michael@0: if (aProperty == eCSSProperty_visibility) { michael@0: NS_ABORT_IF_FALSE(aComputedValue.GetUnit() == michael@0: nsStyleAnimation::eUnit_Enumerated, michael@0: "unexpected unit"); michael@0: aComputedValue.SetIntValue(aComputedValue.GetIntValue(), michael@0: nsStyleAnimation::eUnit_Visibility); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: already_AddRefed michael@0: CommonAnimationManager::ReparentContent(nsIContent* aContent, michael@0: nsStyleContext* aParentStyle) michael@0: { michael@0: nsStyleSet* styleSet = mPresContext->PresShell()->StyleSet(); michael@0: nsIFrame* primaryFrame = nsLayoutUtils::GetStyleFrame(aContent); michael@0: if (!primaryFrame) { michael@0: return nullptr; michael@0: } michael@0: michael@0: dom::Element* element = aContent->IsElement() michael@0: ? aContent->AsElement() michael@0: : nullptr; michael@0: michael@0: nsRefPtr newStyle = michael@0: styleSet->ReparentStyleContext(primaryFrame->StyleContext(), michael@0: aParentStyle, element); michael@0: primaryFrame->SetStyleContext(newStyle); michael@0: ReparentBeforeAndAfter(element, primaryFrame, newStyle, styleSet); michael@0: michael@0: return newStyle.forget(); michael@0: } michael@0: michael@0: /* static */ void michael@0: CommonAnimationManager::ReparentBeforeAndAfter(dom::Element* aElement, michael@0: nsIFrame* aPrimaryFrame, michael@0: nsStyleContext* aNewStyle, michael@0: nsStyleSet* aStyleSet) michael@0: { michael@0: if (nsIFrame* before = nsLayoutUtils::GetBeforeFrame(aPrimaryFrame)) { michael@0: nsRefPtr beforeStyle = michael@0: aStyleSet->ReparentStyleContext(before->StyleContext(), michael@0: aNewStyle, aElement); michael@0: before->SetStyleContext(beforeStyle); michael@0: } michael@0: if (nsIFrame* after = nsLayoutUtils::GetBeforeFrame(aPrimaryFrame)) { michael@0: nsRefPtr afterStyle = michael@0: aStyleSet->ReparentStyleContext(after->StyleContext(), michael@0: aNewStyle, aElement); michael@0: after->SetStyleContext(afterStyle); michael@0: } michael@0: } michael@0: michael@0: nsStyleContext* michael@0: CommonAnimationManager::UpdateThrottledStyle(dom::Element* aElement, michael@0: nsStyleContext* aParentStyle, michael@0: nsStyleChangeList& aChangeList) michael@0: { michael@0: NS_ASSERTION(mPresContext->TransitionManager()->GetElementTransitions( michael@0: aElement, michael@0: nsCSSPseudoElements::ePseudo_NotPseudoElement, michael@0: false) || michael@0: mPresContext->AnimationManager()->GetElementAnimations( michael@0: aElement, michael@0: nsCSSPseudoElements::ePseudo_NotPseudoElement, michael@0: false), "element not animated"); michael@0: michael@0: nsIFrame* primaryFrame = nsLayoutUtils::GetStyleFrame(aElement); michael@0: if (!primaryFrame) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsStyleContext* oldStyle = primaryFrame->StyleContext(); michael@0: nsRuleNode* ruleNode = oldStyle->RuleNode(); michael@0: nsTArray rules; michael@0: do { michael@0: if (ruleNode->IsRoot()) { michael@0: break; michael@0: } michael@0: michael@0: nsStyleSet::RuleAndLevel curRule; michael@0: curRule.mLevel = ruleNode->GetLevel(); michael@0: michael@0: if (curRule.mLevel == nsStyleSet::eAnimationSheet) { michael@0: ElementAnimations* ea = michael@0: mPresContext->AnimationManager()->GetElementAnimations( michael@0: aElement, michael@0: oldStyle->GetPseudoType(), michael@0: false); michael@0: NS_ASSERTION(ea, michael@0: "Rule has level eAnimationSheet without animation on manager"); michael@0: michael@0: mPresContext->AnimationManager()->EnsureStyleRuleFor(ea); michael@0: curRule.mRule = ea->mStyleRule; michael@0: } else if (curRule.mLevel == nsStyleSet::eTransitionSheet) { michael@0: ElementTransitions *et = michael@0: mPresContext->TransitionManager()->GetElementTransitions( michael@0: aElement, michael@0: oldStyle->GetPseudoType(), michael@0: false); michael@0: NS_ASSERTION(et, michael@0: "Rule has level eTransitionSheet without transition on manager"); michael@0: michael@0: et->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh()); michael@0: curRule.mRule = et->mStyleRule; michael@0: } else { michael@0: curRule.mRule = ruleNode->GetRule(); michael@0: } michael@0: michael@0: if (curRule.mRule) { michael@0: rules.AppendElement(curRule); michael@0: } michael@0: } while ((ruleNode = ruleNode->GetParent())); michael@0: michael@0: nsRefPtr newStyle = mPresContext->PresShell()->StyleSet()-> michael@0: ResolveStyleForRules(aParentStyle, oldStyle, rules); michael@0: michael@0: // We absolutely must call CalcStyleDifference in order to ensure the michael@0: // new context has all the structs cached that the old context had. michael@0: // We also need it for processing of the changes. michael@0: nsChangeHint styleChange = michael@0: oldStyle->CalcStyleDifference(newStyle, nsChangeHint(0)); michael@0: aChangeList.AppendChange(primaryFrame, primaryFrame->GetContent(), michael@0: styleChange); michael@0: michael@0: primaryFrame->SetStyleContext(newStyle); michael@0: michael@0: ReparentBeforeAndAfter(aElement, primaryFrame, newStyle, michael@0: mPresContext->PresShell()->StyleSet()); michael@0: michael@0: return newStyle; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(AnimValuesStyleRule, nsIStyleRule) michael@0: michael@0: /* virtual */ void michael@0: AnimValuesStyleRule::MapRuleInfoInto(nsRuleData* aRuleData) michael@0: { michael@0: nsStyleContext *contextParent = aRuleData->mStyleContext->GetParent(); michael@0: if (contextParent && contextParent->HasPseudoElementData()) { michael@0: // Don't apply transitions or animations to things inside of michael@0: // pseudo-elements. michael@0: // FIXME (Bug 522599): Add tests for this. michael@0: return; michael@0: } michael@0: michael@0: for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) { michael@0: PropertyValuePair &cv = mPropertyValuePairs[i]; michael@0: if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID( michael@0: nsCSSProps::kSIDTable[cv.mProperty])) michael@0: { michael@0: nsCSSValue *prop = aRuleData->ValueFor(cv.mProperty); michael@0: if (prop->GetUnit() == eCSSUnit_Null) { michael@0: #ifdef DEBUG michael@0: bool ok = michael@0: #endif michael@0: nsStyleAnimation::UncomputeValue(cv.mProperty, cv.mValue, *prop); michael@0: NS_ABORT_IF_FALSE(ok, "could not store computed value"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: /* virtual */ void michael@0: AnimValuesStyleRule::List(FILE* out, int32_t aIndent) const michael@0: { michael@0: for (int32_t index = aIndent; --index >= 0; ) fputs(" ", out); michael@0: fputs("[anim values] { ", out); michael@0: for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) { michael@0: const PropertyValuePair &pair = mPropertyValuePairs[i]; michael@0: nsAutoString value; michael@0: nsStyleAnimation::UncomputeValue(pair.mProperty, pair.mValue, value); michael@0: fprintf(out, "%s: %s; ", nsCSSProps::GetStringValue(pair.mProperty).get(), michael@0: NS_ConvertUTF16toUTF8(value).get()); michael@0: } michael@0: fputs("}\n", out); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: ComputedTimingFunction::Init(const nsTimingFunction &aFunction) michael@0: { michael@0: mType = aFunction.mType; michael@0: if (mType == nsTimingFunction::Function) { michael@0: mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1, michael@0: aFunction.mFunc.mX2, aFunction.mFunc.mY2); michael@0: } else { michael@0: mSteps = aFunction.mSteps; michael@0: } michael@0: } michael@0: michael@0: static inline double michael@0: StepEnd(uint32_t aSteps, double aPortion) michael@0: { michael@0: NS_ABORT_IF_FALSE(0.0 <= aPortion && aPortion <= 1.0, "out of range"); michael@0: uint32_t step = uint32_t(aPortion * aSteps); // floor michael@0: return double(step) / double(aSteps); michael@0: } michael@0: michael@0: double michael@0: ComputedTimingFunction::GetValue(double aPortion) const michael@0: { michael@0: switch (mType) { michael@0: case nsTimingFunction::Function: michael@0: return mTimingFunction.GetSplineValue(aPortion); michael@0: case nsTimingFunction::StepStart: michael@0: // There are diagrams in the spec that seem to suggest this check michael@0: // and the bounds point should not be symmetric with StepEnd, but michael@0: // should actually step up at rather than immediately after the michael@0: // fraction points. However, we rely on rounding negative values michael@0: // up to zero, so we can't do that. And it's not clear the spec michael@0: // really meant it. michael@0: return 1.0 - StepEnd(mSteps, 1.0 - aPortion); michael@0: default: michael@0: NS_ABORT_IF_FALSE(false, "bad type"); michael@0: // fall through michael@0: case nsTimingFunction::StepEnd: michael@0: return StepEnd(mSteps, aPortion); michael@0: } michael@0: } michael@0: michael@0: } /* end sub-namespace css */ michael@0: michael@0: bool michael@0: StyleAnimation::IsRunningAt(TimeStamp aTime) const michael@0: { michael@0: if (IsPaused() || mIterationDuration.ToMilliseconds() <= 0.0 || michael@0: mStartTime.IsNull()) { michael@0: return false; michael@0: } michael@0: michael@0: double iterationsElapsed = ElapsedDurationAt(aTime) / mIterationDuration; michael@0: return 0.0 <= iterationsElapsed && iterationsElapsed < mIterationCount; michael@0: } michael@0: michael@0: bool michael@0: StyleAnimation::HasAnimationOfProperty(nsCSSProperty aProperty) const michael@0: { michael@0: for (uint32_t propIdx = 0, propEnd = mProperties.Length(); michael@0: propIdx != propEnd; ++propIdx) { michael@0: if (aProperty == mProperties[propIdx].mProperty) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: namespace css { michael@0: michael@0: bool michael@0: CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *aElement, michael@0: nsCSSProperty aProperty, michael@0: CanAnimateFlags aFlags) michael@0: { michael@0: bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled(); michael@0: if (!gfxPlatform::OffMainThreadCompositingEnabled()) { michael@0: if (shouldLog) { michael@0: nsCString message; michael@0: message.AppendLiteral("Performance warning: Compositor disabled"); michael@0: LogAsyncAnimationFailure(message); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsIFrame* frame = nsLayoutUtils::GetStyleFrame(aElement); michael@0: if (IsGeometricProperty(aProperty)) { michael@0: if (shouldLog) { michael@0: nsCString message; michael@0: message.AppendLiteral("Performance warning: Async animation of geometric property '"); michael@0: message.Append(nsCSSProps::GetStringValue(aProperty)); michael@0: message.AppendLiteral("' is disabled"); michael@0: LogAsyncAnimationFailure(message, aElement); michael@0: } michael@0: return false; michael@0: } michael@0: if (aProperty == eCSSProperty_transform) { michael@0: if (frame->Preserves3D() && michael@0: frame->Preserves3DChildren()) { michael@0: if (shouldLog) { michael@0: nsCString message; michael@0: message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' transforms is not supported. See bug 779598"); michael@0: LogAsyncAnimationFailure(message, aElement); michael@0: } michael@0: return false; michael@0: } michael@0: if (frame->IsSVGTransformed()) { michael@0: if (shouldLog) { michael@0: nsCString message; michael@0: message.AppendLiteral("Gecko bug: Async 'transform' animations of frames with SVG transforms is not supported. See bug 779599"); michael@0: LogAsyncAnimationFailure(message, aElement); michael@0: } michael@0: return false; michael@0: } michael@0: if (aFlags & CanAnimate_HasGeometricProperty) { michael@0: if (shouldLog) { michael@0: nsCString message; michael@0: message.AppendLiteral("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties"); michael@0: LogAsyncAnimationFailure(message, aElement); michael@0: } michael@0: return false; michael@0: } michael@0: } michael@0: bool enabled = nsLayoutUtils::AreAsyncAnimationsEnabled(); michael@0: if (!enabled && shouldLog) { michael@0: nsCString message; michael@0: message.AppendLiteral("Performance warning: Async animations are disabled"); michael@0: LogAsyncAnimationFailure(message); michael@0: } michael@0: bool propertyAllowed = (aProperty == eCSSProperty_transform) || michael@0: (aProperty == eCSSProperty_opacity) || michael@0: (aFlags & CanAnimate_AllowPartial); michael@0: return enabled && propertyAllowed; michael@0: } michael@0: michael@0: /* static */ bool michael@0: CommonElementAnimationData::IsCompositorAnimationDisabledForFrame(nsIFrame* aFrame) michael@0: { michael@0: void* prop = aFrame->Properties().Get(nsIFrame::RefusedAsyncAnimation()); michael@0: return bool(reinterpret_cast(prop)); michael@0: } michael@0: michael@0: /* static */ void michael@0: CommonElementAnimationData::LogAsyncAnimationFailure(nsCString& aMessage, michael@0: const nsIContent* aContent) michael@0: { michael@0: if (aContent) { michael@0: aMessage.AppendLiteral(" ["); michael@0: aMessage.Append(nsAtomCString(aContent->Tag())); michael@0: michael@0: nsIAtom* id = aContent->GetID(); michael@0: if (id) { michael@0: aMessage.AppendLiteral(" with id '"); michael@0: aMessage.Append(nsAtomCString(aContent->GetID())); michael@0: aMessage.AppendLiteral("'"); michael@0: } michael@0: aMessage.AppendLiteral("]"); michael@0: } michael@0: aMessage.AppendLiteral("\n"); michael@0: printf_stderr(aMessage.get()); michael@0: } michael@0: michael@0: bool michael@0: CommonElementAnimationData::CanThrottleTransformChanges(TimeStamp aTime) michael@0: { michael@0: if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) { michael@0: return false; michael@0: } michael@0: michael@0: // If we know that the animation cannot cause overflow, michael@0: // we can just disable flushes for this animation. michael@0: michael@0: // If we don't show scrollbars, we don't care about overflow. michael@0: if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) { michael@0: return true; michael@0: } michael@0: michael@0: // If this animation can cause overflow, we can throttle some of the ticks. michael@0: if ((aTime - mStyleRuleRefreshTime) < TimeDuration::FromMilliseconds(200)) { michael@0: return true; michael@0: } michael@0: michael@0: // If the nearest scrollable ancestor has overflow:hidden, michael@0: // we don't care about overflow. michael@0: nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame( michael@0: nsLayoutUtils::GetStyleFrame(mElement)); michael@0: if (!scrollable) { michael@0: return true; michael@0: } michael@0: michael@0: ScrollbarStyles ss = scrollable->GetScrollbarStyles(); michael@0: if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN && michael@0: ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && michael@0: scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CommonElementAnimationData::CanThrottleAnimation(TimeStamp aTime) michael@0: { michael@0: nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement); michael@0: if (!frame) { michael@0: return false; michael@0: } michael@0: michael@0: bool hasTransform = HasAnimationOfProperty(eCSSProperty_transform); michael@0: bool hasOpacity = HasAnimationOfProperty(eCSSProperty_opacity); michael@0: if (hasOpacity) { michael@0: Layer* layer = FrameLayerBuilder::GetDedicatedLayer( michael@0: frame, nsDisplayItem::TYPE_OPACITY); michael@0: if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!hasTransform) { michael@0: return true; michael@0: } michael@0: michael@0: Layer* layer = FrameLayerBuilder::GetDedicatedLayer( michael@0: frame, nsDisplayItem::TYPE_TRANSFORM); michael@0: if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) { michael@0: return false; michael@0: } michael@0: michael@0: return CanThrottleTransformChanges(aTime); michael@0: } michael@0: michael@0: void michael@0: CommonElementAnimationData::UpdateAnimationGeneration(nsPresContext* aPresContext) michael@0: { michael@0: mAnimationGeneration = michael@0: aPresContext->RestyleManager()->GetAnimationGeneration(); michael@0: } michael@0: michael@0: } michael@0: }