Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* object that resolves CSS variables using specified and inherited variable
7 * values
8 */
10 #include "CSSVariableResolver.h"
12 #include "CSSVariableDeclarations.h"
13 #include "CSSVariableValues.h"
14 #include "mozilla/PodOperations.h"
15 #include <algorithm>
17 namespace mozilla {
19 /**
20 * Data used by the EnumerateVariableReferences callback. Reset must be called
21 * on it before it is used.
22 */
23 class EnumerateVariableReferencesData
24 {
25 public:
26 EnumerateVariableReferencesData(CSSVariableResolver& aResolver)
27 : mResolver(aResolver)
28 , mReferences(new bool[aResolver.mVariables.Length()])
29 {
30 }
32 /**
33 * Resets the data so that it can be passed to another call of
34 * EnumerateVariableReferences for a different variable.
35 */
36 void Reset()
37 {
38 PodZero(mReferences.get(), mResolver.mVariables.Length());
39 mReferencesNonExistentVariable = false;
40 }
42 void RecordVariableReference(const nsAString& aVariableName)
43 {
44 size_t id;
45 if (mResolver.mVariableIDs.Get(aVariableName, &id)) {
46 mReferences[id] = true;
47 } else {
48 mReferencesNonExistentVariable = true;
49 }
50 }
52 bool HasReferenceToVariable(size_t aID) const
53 {
54 return mReferences[aID];
55 }
57 bool ReferencesNonExistentVariable() const
58 {
59 return mReferencesNonExistentVariable;
60 }
62 private:
63 CSSVariableResolver& mResolver;
65 // Array of booleans, where each index is a variable ID. If an element is
66 // true, it indicates that the variable we have called
67 // EnumerateVariableReferences for has a reference to the variable with
68 // that ID.
69 nsAutoArrayPtr<bool> mReferences;
71 // Whether the variable we have called EnumerateVariableReferences for
72 // references a variable that does not exist in the resolver.
73 bool mReferencesNonExistentVariable;
74 };
76 static void
77 RecordVariableReference(const nsAString& aVariableName,
78 void* aData)
79 {
80 static_cast<EnumerateVariableReferencesData*>(aData)->
81 RecordVariableReference(aVariableName);
82 }
84 void
85 CSSVariableResolver::RemoveCycles(size_t v)
86 {
87 mVariables[v].mIndex = mNextIndex;
88 mVariables[v].mLowLink = mNextIndex;
89 mVariables[v].mInStack = true;
90 mStack.AppendElement(v);
91 mNextIndex++;
93 for (size_t i = 0, n = mReferences[v].Length(); i < n; i++) {
94 size_t w = mReferences[v][i];
95 if (!mVariables[w].mIndex) {
96 RemoveCycles(w);
97 mVariables[v].mLowLink = std::min(mVariables[v].mLowLink,
98 mVariables[w].mLowLink);
99 } else if (mVariables[w].mInStack) {
100 mVariables[v].mLowLink = std::min(mVariables[v].mLowLink,
101 mVariables[w].mIndex);
102 }
103 }
105 if (mVariables[v].mLowLink == mVariables[v].mIndex) {
106 if (mStack.LastElement() == v) {
107 // A strongly connected component consisting of a single variable is not
108 // necessarily invalid. We handle variables that reference themselves
109 // earlier, in CSSVariableResolver::Resolve.
110 mVariables[mStack.LastElement()].mInStack = false;
111 mStack.TruncateLength(mStack.Length() - 1);
112 } else {
113 size_t w;
114 do {
115 w = mStack.LastElement();
116 mVariables[w].mValue.Truncate(0);
117 mVariables[w].mInStack = false;
118 mStack.TruncateLength(mStack.Length() - 1);
119 } while (w != v);
120 }
121 }
122 }
124 void
125 CSSVariableResolver::ResolveVariable(size_t aID)
126 {
127 if (mVariables[aID].mValue.IsEmpty() || mVariables[aID].mWasInherited) {
128 // The variable is invalid or was inherited. We can just copy the value
129 // and its first/last token information across.
130 mOutput->Put(mVariables[aID].mVariableName,
131 mVariables[aID].mValue,
132 mVariables[aID].mFirstToken,
133 mVariables[aID].mLastToken);
134 } else {
135 // Otherwise we need to resolve the variable references, after resolving
136 // all of our dependencies first. We do this even for variables that we
137 // know do not reference other variables so that we can find their
138 // first/last token.
139 //
140 // XXX We might want to do this first/last token finding during
141 // EnumerateVariableReferences, so that we can avoid calling
142 // ResolveVariableValue and parsing the value again.
143 for (size_t i = 0, n = mReferences[aID].Length(); i < n; i++) {
144 size_t j = mReferences[aID][i];
145 if (aID != j && !mVariables[j].mResolved) {
146 ResolveVariable(j);
147 }
148 }
149 nsString resolvedValue;
150 nsCSSTokenSerializationType firstToken, lastToken;
151 if (!mParser.ResolveVariableValue(mVariables[aID].mValue, mOutput,
152 resolvedValue, firstToken, lastToken)) {
153 resolvedValue.Truncate(0);
154 }
155 mOutput->Put(mVariables[aID].mVariableName, resolvedValue,
156 firstToken, lastToken);
157 }
158 mVariables[aID].mResolved = true;
159 }
161 void
162 CSSVariableResolver::Resolve(const CSSVariableValues* aInherited,
163 const CSSVariableDeclarations* aSpecified)
164 {
165 MOZ_ASSERT(!mResolved);
167 // The only time we would be worried about having a null aInherited is
168 // for the root, but in that case nsRuleNode::ComputeVariablesData will
169 // happen to pass in whatever we're using as mOutput for aInherited,
170 // which will initially be empty.
171 MOZ_ASSERT(aInherited);
172 MOZ_ASSERT(aSpecified);
174 aInherited->AddVariablesToResolver(this);
175 aSpecified->AddVariablesToResolver(this);
177 // First we look at each variable's value and record which other variables
178 // it references.
179 size_t n = mVariables.Length();
180 mReferences.SetLength(n);
181 EnumerateVariableReferencesData data(*this);
182 for (size_t id = 0; id < n; id++) {
183 data.Reset();
184 if (!mVariables[id].mWasInherited &&
185 !mVariables[id].mValue.IsEmpty()) {
186 if (mParser.EnumerateVariableReferences(mVariables[id].mValue,
187 RecordVariableReference,
188 &data)) {
189 // Convert the boolean array of dependencies in |data| to a list
190 // of dependencies.
191 for (size_t i = 0; i < n; i++) {
192 if (data.HasReferenceToVariable(i)) {
193 mReferences[id].AppendElement(i);
194 }
195 }
196 // If a variable references itself, it is invalid. (RemoveCycles
197 // does not check for cycles consisting of a single variable, so we
198 // check here.)
199 if (data.HasReferenceToVariable(id)) {
200 mVariables[id].mValue.Truncate();
201 }
202 // Also record whether it referenced any variables that don't exist
203 // in the resolver, so that we can ensure we still resolve its value
204 // in ResolveVariable, even though its mReferences list is empty.
205 mVariables[id].mReferencesNonExistentVariable =
206 data.ReferencesNonExistentVariable();
207 } else {
208 MOZ_ASSERT(false, "EnumerateVariableReferences should not have failed "
209 "if we previously parsed the specified value");
210 mVariables[id].mValue.Truncate(0);
211 }
212 }
213 }
215 // Next we remove any cycles in variable references using Tarjan's strongly
216 // connected components finding algorithm, setting variables in cycles to
217 // have an invalid value.
218 mNextIndex = 1;
219 for (size_t id = 0; id < n; id++) {
220 if (!mVariables[id].mIndex) {
221 RemoveCycles(id);
222 MOZ_ASSERT(mStack.IsEmpty());
223 }
224 }
226 // Finally we construct the computed value for the variable by substituting
227 // any variable references.
228 for (size_t id = 0; id < n; id++) {
229 if (!mVariables[id].mResolved) {
230 ResolveVariable(id);
231 }
232 }
234 mResolved = true;
235 }
237 void
238 CSSVariableResolver::Put(const nsAString& aVariableName,
239 nsString aValue,
240 nsCSSTokenSerializationType aFirstToken,
241 nsCSSTokenSerializationType aLastToken,
242 bool aWasInherited)
243 {
244 MOZ_ASSERT(!mResolved);
246 size_t id;
247 if (mVariableIDs.Get(aVariableName, &id)) {
248 MOZ_ASSERT(mVariables[id].mWasInherited && !aWasInherited,
249 "should only overwrite inherited variables with specified "
250 "variables");
251 mVariables[id].mValue = aValue;
252 mVariables[id].mFirstToken = aFirstToken;
253 mVariables[id].mLastToken = aLastToken;
254 mVariables[id].mWasInherited = aWasInherited;
255 } else {
256 id = mVariables.Length();
257 mVariableIDs.Put(aVariableName, id);
258 mVariables.AppendElement(Variable(aVariableName, aValue,
259 aFirstToken, aLastToken, aWasInherited));
260 }
261 }
263 }