Line data Source code
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/. */
6 :
7 : #include "nsSMILCompositor.h"
8 :
9 : #include "nsComputedDOMStyle.h"
10 : #include "nsCSSProps.h"
11 : #include "nsHashKeys.h"
12 : #include "nsSMILCSSProperty.h"
13 :
14 : // PLDHashEntryHdr methods
15 : bool
16 0 : nsSMILCompositor::KeyEquals(KeyTypePointer aKey) const
17 : {
18 0 : return aKey && aKey->Equals(mKey);
19 : }
20 :
21 : /*static*/ PLDHashNumber
22 0 : nsSMILCompositor::HashKey(KeyTypePointer aKey)
23 : {
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 0 : return (NS_PTR_TO_UINT32(aKey->mElement.get()) >> 2) +
29 0 : NS_PTR_TO_UINT32(aKey->mAttributeName.get());
30 : }
31 :
32 : // Cycle-collection support
33 : void
34 0 : nsSMILCompositor::Traverse(nsCycleCollectionTraversalCallback* aCallback)
35 : {
36 0 : if (!mKey.mElement)
37 0 : return;
38 :
39 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "Compositor mKey.mElement");
40 0 : aCallback->NoteXPCOMChild(mKey.mElement);
41 : }
42 :
43 : // Other methods
44 : void
45 0 : nsSMILCompositor::AddAnimationFunction(nsSMILAnimationFunction* aFunc)
46 : {
47 0 : if (aFunc) {
48 0 : mAnimationFunctions.AppendElement(aFunc);
49 : }
50 0 : }
51 :
52 : void
53 0 : nsSMILCompositor::ComposeAttribute(bool& aMightHavePendingStyleUpdates)
54 : {
55 0 : if (!mKey.mElement)
56 0 : return;
57 :
58 : // If we might need to resolve base styles, grab a suitable style context
59 : // for initializing our nsISMILAttr with.
60 0 : RefPtr<nsStyleContext> baseStyleContext;
61 0 : if (MightNeedBaseStyle()) {
62 : baseStyleContext =
63 0 : nsComputedDOMStyle::GetUnanimatedStyleContextNoFlush(mKey.mElement,
64 0 : nullptr, nullptr);
65 : }
66 :
67 : // FIRST: Get the nsISMILAttr (to grab base value from, and to eventually
68 : // give animated value to)
69 0 : UniquePtr<nsISMILAttr> smilAttr = CreateSMILAttr(baseStyleContext);
70 0 : if (!smilAttr) {
71 : // Target attribute not found (or, out of memory)
72 0 : return;
73 : }
74 0 : if (mAnimationFunctions.IsEmpty()) {
75 : // No active animation functions. (We can still have a nsSMILCompositor in
76 : // that case if an animation function has *just* become inactive)
77 0 : smilAttr->ClearAnimValue();
78 : // Removing the animation effect may require a style update.
79 0 : aMightHavePendingStyleUpdates = true;
80 0 : return;
81 : }
82 :
83 : // SECOND: Sort the animationFunctions, to prepare for compositing.
84 : nsSMILAnimationFunction::Comparator comparator;
85 0 : mAnimationFunctions.Sort(comparator);
86 :
87 : // THIRD: Step backwards through animation functions to find out
88 : // which ones we actually care about.
89 0 : uint32_t firstFuncToCompose = GetFirstFuncToAffectSandwich();
90 :
91 : // FOURTH: Get & cache base value
92 0 : nsSMILValue sandwichResultValue;
93 0 : if (!mAnimationFunctions[firstFuncToCompose]->WillReplace()) {
94 0 : sandwichResultValue = smilAttr->GetBaseValue();
95 0 : MOZ_ASSERT(!sandwichResultValue.IsNull(),
96 : "Result of GetBaseValue should not be null");
97 : }
98 0 : UpdateCachedBaseValue(sandwichResultValue);
99 :
100 0 : if (!mForceCompositing) {
101 0 : return;
102 : }
103 :
104 : // FIFTH: Compose animation functions
105 0 : aMightHavePendingStyleUpdates = true;
106 0 : uint32_t length = mAnimationFunctions.Length();
107 0 : for (uint32_t i = firstFuncToCompose; i < length; ++i) {
108 0 : mAnimationFunctions[i]->ComposeResult(*smilAttr, sandwichResultValue);
109 : }
110 0 : if (sandwichResultValue.IsNull()) {
111 0 : smilAttr->ClearAnimValue();
112 0 : return;
113 : }
114 :
115 : // SIXTH: Set the animated value to the final composited result.
116 0 : nsresult rv = smilAttr->SetAnimValue(sandwichResultValue);
117 0 : if (NS_FAILED(rv)) {
118 0 : NS_WARNING("nsISMILAttr::SetAnimValue failed");
119 : }
120 : }
121 :
122 : void
123 0 : nsSMILCompositor::ClearAnimationEffects()
124 : {
125 0 : if (!mKey.mElement || !mKey.mAttributeName)
126 0 : return;
127 :
128 0 : UniquePtr<nsISMILAttr> smilAttr = CreateSMILAttr(nullptr);
129 0 : if (!smilAttr) {
130 : // Target attribute not found (or, out of memory)
131 0 : return;
132 : }
133 0 : smilAttr->ClearAnimValue();
134 : }
135 :
136 : // Protected Helper Functions
137 : // --------------------------
138 : UniquePtr<nsISMILAttr>
139 0 : nsSMILCompositor::CreateSMILAttr(nsStyleContext* aBaseStyleContext)
140 : {
141 0 : nsCSSPropertyID propID = GetCSSPropertyToAnimate();
142 :
143 0 : if (propID != eCSSProperty_UNKNOWN) {
144 0 : return MakeUnique<nsSMILCSSProperty>(propID, mKey.mElement.get(),
145 0 : aBaseStyleContext);
146 : }
147 :
148 0 : return mKey.mElement->GetAnimatedAttr(mKey.mAttributeNamespaceID,
149 0 : mKey.mAttributeName);
150 : }
151 :
152 : nsCSSPropertyID
153 0 : nsSMILCompositor::GetCSSPropertyToAnimate() const
154 : {
155 0 : if (mKey.mAttributeNamespaceID != kNameSpaceID_None) {
156 0 : return eCSSProperty_UNKNOWN;
157 : }
158 :
159 : nsCSSPropertyID propID =
160 0 : nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName),
161 0 : CSSEnabledState::eForAllContent);
162 :
163 0 : if (!nsSMILCSSProperty::IsPropertyAnimatable(propID,
164 0 : mKey.mElement->OwnerDoc()->GetStyleBackendType())) {
165 0 : return eCSSProperty_UNKNOWN;
166 : }
167 :
168 : // If we are animating the 'width' or 'height' of an outer SVG
169 : // element we should animate it as a CSS property, but for other elements
170 : // (e.g. <rect>) we should animate it as a length attribute.
171 : // The easiest way to test for an outer SVG element, is to see if it is an
172 : // SVG-namespace element mapping its width/height attribute to style.
173 : //
174 : // If we have animation of 'width' or 'height' on an SVG element that is
175 : // NOT mapping that attributes to style then it must not be an outermost SVG
176 : // element so we should return eCSSProperty_UNKNOWN to indicate that we
177 : // should animate as an attribute instead.
178 0 : if ((mKey.mAttributeName == nsGkAtoms::width ||
179 0 : mKey.mAttributeName == nsGkAtoms::height) &&
180 0 : mKey.mElement->GetNameSpaceID() == kNameSpaceID_SVG &&
181 0 : !mKey.mElement->IsAttributeMapped(mKey.mAttributeName)) {
182 0 : return eCSSProperty_UNKNOWN;
183 : }
184 :
185 0 : return propID;
186 : }
187 :
188 : bool
189 0 : nsSMILCompositor::MightNeedBaseStyle() const
190 : {
191 0 : if (GetCSSPropertyToAnimate() == eCSSProperty_UNKNOWN) {
192 0 : return false;
193 : }
194 :
195 : // We should return true if at least one animation function might build on
196 : // the base value.
197 0 : for (const nsSMILAnimationFunction* func : mAnimationFunctions) {
198 0 : if (!func->WillReplace()) {
199 0 : return true;
200 : }
201 : }
202 :
203 0 : return false;
204 : }
205 :
206 : uint32_t
207 0 : nsSMILCompositor::GetFirstFuncToAffectSandwich()
208 : {
209 : // For performance reasons, we throttle most animations on elements in
210 : // display:none subtrees. (We can't throttle animations that target the
211 : // "display" property itself, though -- if we did, display:none elements
212 : // could never be dynamically displayed via animations.)
213 : // To determine whether we're in a display:none subtree, we will check the
214 : // element's primary frame since element in display:none subtree doesn't have
215 : // a primary frame. Before this process, we will construct frame when we
216 : // append an element to subtree. So we will not need to worry about pending
217 : // frame construction in this step.
218 0 : bool canThrottle = mKey.mAttributeName != nsGkAtoms::display &&
219 0 : !mKey.mElement->GetPrimaryFrame();
220 :
221 : uint32_t i;
222 0 : for (i = mAnimationFunctions.Length(); i > 0; --i) {
223 0 : nsSMILAnimationFunction* curAnimFunc = mAnimationFunctions[i-1];
224 : // In the following, the lack of short-circuit behavior of |= means that we
225 : // will ALWAYS run UpdateCachedTarget (even if mForceCompositing is true)
226 : // but only call HasChanged and WasSkippedInPrevSample if necessary. This
227 : // is important since we need UpdateCachedTarget to run in order to detect
228 : // changes to the target in subsequent samples.
229 0 : mForceCompositing |=
230 0 : curAnimFunc->UpdateCachedTarget(mKey) ||
231 0 : (curAnimFunc->HasChanged() && !canThrottle) ||
232 0 : curAnimFunc->WasSkippedInPrevSample();
233 :
234 0 : if (curAnimFunc->WillReplace()) {
235 0 : --i;
236 0 : break;
237 : }
238 : }
239 :
240 : // Mark remaining animation functions as having been skipped so if we later
241 : // use them we'll know to force compositing.
242 : // Note that we only really need to do this if something has changed
243 : // (otherwise we would have set the flag on a previous sample) and if
244 : // something has changed mForceCompositing will be true.
245 0 : if (mForceCompositing) {
246 0 : for (uint32_t j = i; j > 0; --j) {
247 0 : mAnimationFunctions[j-1]->SetWasSkipped();
248 : }
249 : }
250 0 : return i;
251 : }
252 :
253 : void
254 0 : nsSMILCompositor::UpdateCachedBaseValue(const nsSMILValue& aBaseValue)
255 : {
256 0 : if (mCachedBaseValue != aBaseValue) {
257 : // Base value has changed since last sample.
258 0 : mCachedBaseValue = aBaseValue;
259 0 : mForceCompositing = true;
260 : }
261 0 : }
|