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 "mozilla/dom/SVGSVGElement.h"
10 #include "nsComputedDOMStyle.h"
11 #include "nsCSSProps.h"
12 #include "nsHashKeys.h"
13 #include "SMILCSSProperty.h"
17 // PLDHashEntryHdr methods
18 bool SMILCompositor::KeyEquals(KeyTypePointer aKey
) const {
19 return aKey
&& aKey
->Equals(mKey
);
23 PLDHashNumber
SMILCompositor::HashKey(KeyTypePointer aKey
) {
24 // Combine the 3 values into one numeric value, which will be hashed.
25 // NOTE: We right-shift one of the pointers by 2 to get some randomness in
26 // its 2 lowest-order bits. (Those shifted-off bits will always be 0 since
27 // our pointers will be word-aligned.)
28 return (NS_PTR_TO_UINT32(aKey
->mElement
.get()) >> 2) +
29 NS_PTR_TO_UINT32(aKey
->mAttributeName
.get());
32 // Cycle-collection support
33 void SMILCompositor::Traverse(nsCycleCollectionTraversalCallback
* aCallback
) {
34 if (!mKey
.mElement
) return;
36 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback
, "Compositor mKey.mElement");
37 aCallback
->NoteXPCOMChild(mKey
.mElement
);
41 void SMILCompositor::AddAnimationFunction(SMILAnimationFunction
* aFunc
) {
43 mAnimationFunctions
.AppendElement(aFunc
);
47 void SMILCompositor::ComposeAttribute(bool& aMightHavePendingStyleUpdates
) {
48 if (!mKey
.mElement
) return;
50 // If we might need to resolve base styles, grab a suitable ComputedStyle
51 // for initializing our SMILAttr with.
52 RefPtr
<const ComputedStyle
> baseComputedStyle
;
53 if (MightNeedBaseStyle()) {
54 baseComputedStyle
= nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush(
55 mKey
.mElement
, PseudoStyleType::NotPseudo
);
58 // FIRST: Get the SMILAttr (to grab base value from, and to eventually
59 // give animated value to)
60 UniquePtr
<SMILAttr
> smilAttr
= CreateSMILAttr(baseComputedStyle
);
62 // Target attribute not found (or, out of memory)
65 if (mAnimationFunctions
.IsEmpty()) {
66 // No active animation functions. (We can still have a SMILCompositor in
67 // that case if an animation function has *just* become inactive)
68 smilAttr
->ClearAnimValue();
69 // Removing the animation effect may require a style update.
70 aMightHavePendingStyleUpdates
= true;
74 // SECOND: Sort the animationFunctions, to prepare for compositing.
75 SMILAnimationFunction::Comparator comparator
;
76 mAnimationFunctions
.Sort(comparator
);
78 // THIRD: Step backwards through animation functions to find out
79 // which ones we actually care about.
80 uint32_t firstFuncToCompose
= GetFirstFuncToAffectSandwich();
82 // FOURTH: Get & cache base value
83 SMILValue sandwichResultValue
;
84 if (!mAnimationFunctions
[firstFuncToCompose
]->WillReplace()) {
85 sandwichResultValue
= smilAttr
->GetBaseValue();
87 UpdateCachedBaseValue(sandwichResultValue
);
89 if (!mForceCompositing
) {
93 // FIFTH: Compose animation functions
94 aMightHavePendingStyleUpdates
= true;
95 uint32_t length
= mAnimationFunctions
.Length();
96 for (uint32_t i
= firstFuncToCompose
; i
< length
; ++i
) {
97 mAnimationFunctions
[i
]->ComposeResult(*smilAttr
, sandwichResultValue
);
99 if (sandwichResultValue
.IsNull()) {
100 smilAttr
->ClearAnimValue();
104 // SIXTH: Set the animated value to the final composited result.
105 nsresult rv
= smilAttr
->SetAnimValue(sandwichResultValue
);
107 NS_WARNING("SMILAttr::SetAnimValue failed");
111 void SMILCompositor::ClearAnimationEffects() {
112 if (!mKey
.mElement
|| !mKey
.mAttributeName
) return;
114 UniquePtr
<SMILAttr
> smilAttr
= CreateSMILAttr(nullptr);
116 // Target attribute not found (or, out of memory)
119 smilAttr
->ClearAnimValue();
122 // Protected Helper Functions
123 // --------------------------
124 UniquePtr
<SMILAttr
> SMILCompositor::CreateSMILAttr(
125 const ComputedStyle
* aBaseComputedStyle
) {
126 nsCSSPropertyID propID
= GetCSSPropertyToAnimate();
128 if (propID
!= eCSSProperty_UNKNOWN
) {
129 return MakeUnique
<SMILCSSProperty
>(propID
, mKey
.mElement
.get(),
133 return mKey
.mElement
->GetAnimatedAttr(mKey
.mAttributeNamespaceID
,
134 mKey
.mAttributeName
);
137 nsCSSPropertyID
SMILCompositor::GetCSSPropertyToAnimate() const {
138 if (mKey
.mAttributeNamespaceID
!= kNameSpaceID_None
) {
139 return eCSSProperty_UNKNOWN
;
142 nsCSSPropertyID propID
=
143 nsCSSProps::LookupProperty(nsAtomCString(mKey
.mAttributeName
));
145 if (!SMILCSSProperty::IsPropertyAnimatable(propID
)) {
146 return eCSSProperty_UNKNOWN
;
149 // If we are animating the 'width' or 'height' of an outer SVG
150 // element we should animate it as a CSS property, but for other elements
151 // in SVG namespace (e.g. <rect>) we should animate it as a length attribute.
152 if ((mKey
.mAttributeName
== nsGkAtoms::width
||
153 mKey
.mAttributeName
== nsGkAtoms::height
) &&
154 mKey
.mElement
->GetNameSpaceID() == kNameSpaceID_SVG
) {
155 // Not an <svg> element.
156 if (!mKey
.mElement
->IsSVGElement(nsGkAtoms::svg
)) {
157 return eCSSProperty_UNKNOWN
;
160 // An inner <svg> element
161 if (static_cast<dom::SVGSVGElement
const&>(*mKey
.mElement
).IsInner()) {
162 return eCSSProperty_UNKNOWN
;
165 // Indeed an outer <svg> element, fall through.
171 bool SMILCompositor::MightNeedBaseStyle() const {
172 if (GetCSSPropertyToAnimate() == eCSSProperty_UNKNOWN
) {
176 // We should return true if at least one animation function might build on
178 for (const SMILAnimationFunction
* func
: mAnimationFunctions
) {
179 if (!func
->WillReplace()) {
187 uint32_t SMILCompositor::GetFirstFuncToAffectSandwich() {
188 // For performance reasons, we throttle most animations on elements in
189 // display:none subtrees. (We can't throttle animations that target the
190 // "display" property itself, though -- if we did, display:none elements
191 // could never be dynamically displayed via animations.)
192 // To determine whether we're in a display:none subtree, we will check the
193 // element's primary frame since element in display:none subtree doesn't have
194 // a primary frame. Before this process, we will construct frame when we
195 // append an element to subtree. So we will not need to worry about pending
196 // frame construction in this step.
197 bool canThrottle
= mKey
.mAttributeName
!= nsGkAtoms::display
&&
198 !mKey
.mElement
->GetPrimaryFrame();
201 for (i
= mAnimationFunctions
.Length(); i
> 0; --i
) {
202 SMILAnimationFunction
* curAnimFunc
= mAnimationFunctions
[i
- 1];
203 // In the following, the lack of short-circuit behavior of |= means that we
204 // will ALWAYS run UpdateCachedTarget (even if mForceCompositing is true)
205 // but only call HasChanged and WasSkippedInPrevSample if necessary. This
206 // is important since we need UpdateCachedTarget to run in order to detect
207 // changes to the target in subsequent samples.
208 mForceCompositing
|= curAnimFunc
->UpdateCachedTarget(mKey
) ||
209 (curAnimFunc
->HasChanged() && !canThrottle
) ||
210 curAnimFunc
->WasSkippedInPrevSample();
212 if (curAnimFunc
->WillReplace()) {
218 // Mark remaining animation functions as having been skipped so if we later
219 // use them we'll know to force compositing.
220 // Note that we only really need to do this if something has changed
221 // (otherwise we would have set the flag on a previous sample) and if
222 // something has changed mForceCompositing will be true.
223 if (mForceCompositing
) {
224 for (uint32_t j
= i
; j
> 0; --j
) {
225 mAnimationFunctions
[j
- 1]->SetWasSkipped();
231 void SMILCompositor::UpdateCachedBaseValue(const SMILValue
& aBaseValue
) {
232 if (mCachedBaseValue
!= aBaseValue
) {
233 // Base value has changed since last sample.
234 mCachedBaseValue
= aBaseValue
;
235 mForceCompositing
= true;
239 } // namespace mozilla