1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SMILCompositor.h"
9 #include "nsComputedDOMStyle.h"
10 #include "nsCSSProps.h"
11 #include "nsHashKeys.h"
12 #include "SMILCSSProperty.h"
16 // PLDHashEntryHdr methods
17 bool SMILCompositor::KeyEquals(KeyTypePointer aKey
) const {
18 return aKey
&& aKey
->Equals(mKey
);
21 /*static*/ PLDHashNumber
SMILCompositor::HashKey(KeyTypePointer aKey
) {
22 // Combine the 3 values into one numeric value, which will be hashed.
23 // NOTE: We right-shift one of the pointers by 2 to get some randomness in
24 // its 2 lowest-order bits. (Those shifted-off bits will always be 0 since
25 // our pointers will be word-aligned.)
26 return (NS_PTR_TO_UINT32(aKey
->mElement
.get()) >> 2) +
27 NS_PTR_TO_UINT32(aKey
->mAttributeName
.get());
30 // Cycle-collection support
31 void SMILCompositor::Traverse(nsCycleCollectionTraversalCallback
* aCallback
) {
32 if (!mKey
.mElement
) return;
34 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback
, "Compositor mKey.mElement");
35 aCallback
->NoteXPCOMChild(mKey
.mElement
);
39 void SMILCompositor::AddAnimationFunction(SMILAnimationFunction
* aFunc
) {
41 mAnimationFunctions
.AppendElement(aFunc
);
45 void SMILCompositor::ComposeAttribute(bool& aMightHavePendingStyleUpdates
) {
46 if (!mKey
.mElement
) return;
48 // If we might need to resolve base styles, grab a suitable ComputedStyle
49 // for initializing our SMILAttr with.
50 RefPtr
<ComputedStyle
> baseComputedStyle
;
51 if (MightNeedBaseStyle()) {
52 baseComputedStyle
= nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush(
53 mKey
.mElement
, nullptr);
56 // FIRST: Get the SMILAttr (to grab base value from, and to eventually
57 // give animated value to)
58 UniquePtr
<SMILAttr
> smilAttr
= CreateSMILAttr(baseComputedStyle
);
60 // Target attribute not found (or, out of memory)
63 if (mAnimationFunctions
.IsEmpty()) {
64 // No active animation functions. (We can still have a SMILCompositor in
65 // that case if an animation function has *just* become inactive)
66 smilAttr
->ClearAnimValue();
67 // Removing the animation effect may require a style update.
68 aMightHavePendingStyleUpdates
= true;
72 // SECOND: Sort the animationFunctions, to prepare for compositing.
73 SMILAnimationFunction::Comparator comparator
;
74 mAnimationFunctions
.Sort(comparator
);
76 // THIRD: Step backwards through animation functions to find out
77 // which ones we actually care about.
78 uint32_t firstFuncToCompose
= GetFirstFuncToAffectSandwich();
80 // FOURTH: Get & cache base value
81 SMILValue sandwichResultValue
;
82 if (!mAnimationFunctions
[firstFuncToCompose
]->WillReplace()) {
83 sandwichResultValue
= smilAttr
->GetBaseValue();
85 UpdateCachedBaseValue(sandwichResultValue
);
87 if (!mForceCompositing
) {
91 // FIFTH: Compose animation functions
92 aMightHavePendingStyleUpdates
= true;
93 uint32_t length
= mAnimationFunctions
.Length();
94 for (uint32_t i
= firstFuncToCompose
; i
< length
; ++i
) {
95 mAnimationFunctions
[i
]->ComposeResult(*smilAttr
, sandwichResultValue
);
97 if (sandwichResultValue
.IsNull()) {
98 smilAttr
->ClearAnimValue();
102 // SIXTH: Set the animated value to the final composited result.
103 nsresult rv
= smilAttr
->SetAnimValue(sandwichResultValue
);
105 NS_WARNING("SMILAttr::SetAnimValue failed");
109 void SMILCompositor::ClearAnimationEffects() {
110 if (!mKey
.mElement
|| !mKey
.mAttributeName
) return;
112 UniquePtr
<SMILAttr
> smilAttr
= CreateSMILAttr(nullptr);
114 // Target attribute not found (or, out of memory)
117 smilAttr
->ClearAnimValue();
120 // Protected Helper Functions
121 // --------------------------
122 UniquePtr
<SMILAttr
> SMILCompositor::CreateSMILAttr(
123 ComputedStyle
* aBaseComputedStyle
) {
124 nsCSSPropertyID propID
= GetCSSPropertyToAnimate();
126 if (propID
!= eCSSProperty_UNKNOWN
) {
127 return MakeUnique
<SMILCSSProperty
>(propID
, mKey
.mElement
.get(),
131 return mKey
.mElement
->GetAnimatedAttr(mKey
.mAttributeNamespaceID
,
132 mKey
.mAttributeName
);
135 nsCSSPropertyID
SMILCompositor::GetCSSPropertyToAnimate() const {
136 if (mKey
.mAttributeNamespaceID
!= kNameSpaceID_None
) {
137 return eCSSProperty_UNKNOWN
;
140 nsCSSPropertyID propID
=
141 nsCSSProps::LookupProperty(nsDependentAtomString(mKey
.mAttributeName
));
143 if (!SMILCSSProperty::IsPropertyAnimatable(propID
)) {
144 return eCSSProperty_UNKNOWN
;
147 // If we are animating the 'width' or 'height' of an outer SVG
148 // element we should animate it as a CSS property, but for other elements
149 // (e.g. <rect>) we should animate it as a length attribute.
150 // The easiest way to test for an outer SVG element, is to see if it is an
151 // SVG-namespace element mapping its width/height attribute to style.
153 // If we have animation of 'width' or 'height' on an SVG element that is
154 // NOT mapping that attributes to style then it must not be an outermost SVG
155 // element so we should return eCSSProperty_UNKNOWN to indicate that we
156 // should animate as an attribute instead.
157 if ((mKey
.mAttributeName
== nsGkAtoms::width
||
158 mKey
.mAttributeName
== nsGkAtoms::height
) &&
159 mKey
.mElement
->GetNameSpaceID() == kNameSpaceID_SVG
&&
160 !mKey
.mElement
->IsAttributeMapped(mKey
.mAttributeName
)) {
161 return eCSSProperty_UNKNOWN
;
167 bool SMILCompositor::MightNeedBaseStyle() const {
168 if (GetCSSPropertyToAnimate() == eCSSProperty_UNKNOWN
) {
172 // We should return true if at least one animation function might build on
174 for (const SMILAnimationFunction
* func
: mAnimationFunctions
) {
175 if (!func
->WillReplace()) {
183 uint32_t SMILCompositor::GetFirstFuncToAffectSandwich() {
184 // For performance reasons, we throttle most animations on elements in
185 // display:none subtrees. (We can't throttle animations that target the
186 // "display" property itself, though -- if we did, display:none elements
187 // could never be dynamically displayed via animations.)
188 // To determine whether we're in a display:none subtree, we will check the
189 // element's primary frame since element in display:none subtree doesn't have
190 // a primary frame. Before this process, we will construct frame when we
191 // append an element to subtree. So we will not need to worry about pending
192 // frame construction in this step.
193 bool canThrottle
= mKey
.mAttributeName
!= nsGkAtoms::display
&&
194 !mKey
.mElement
->GetPrimaryFrame();
197 for (i
= mAnimationFunctions
.Length(); i
> 0; --i
) {
198 SMILAnimationFunction
* curAnimFunc
= mAnimationFunctions
[i
- 1];
199 // In the following, the lack of short-circuit behavior of |= means that we
200 // will ALWAYS run UpdateCachedTarget (even if mForceCompositing is true)
201 // but only call HasChanged and WasSkippedInPrevSample if necessary. This
202 // is important since we need UpdateCachedTarget to run in order to detect
203 // changes to the target in subsequent samples.
204 mForceCompositing
|= curAnimFunc
->UpdateCachedTarget(mKey
) ||
205 (curAnimFunc
->HasChanged() && !canThrottle
) ||
206 curAnimFunc
->WasSkippedInPrevSample();
208 if (curAnimFunc
->WillReplace()) {
214 // Mark remaining animation functions as having been skipped so if we later
215 // use them we'll know to force compositing.
216 // Note that we only really need to do this if something has changed
217 // (otherwise we would have set the flag on a previous sample) and if
218 // something has changed mForceCompositing will be true.
219 if (mForceCompositing
) {
220 for (uint32_t j
= i
; j
> 0; --j
) {
221 mAnimationFunctions
[j
- 1]->SetWasSkipped();
227 void SMILCompositor::UpdateCachedBaseValue(const SMILValue
& aBaseValue
) {
228 if (mCachedBaseValue
!= aBaseValue
) {
229 // Base value has changed since last sample.
230 mCachedBaseValue
= aBaseValue
;
231 mForceCompositing
= true;
235 } // namespace mozilla