|
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/. */ |
|
5 |
|
6 /* object that resolves CSS variables using specified and inherited variable |
|
7 * values |
|
8 */ |
|
9 |
|
10 #include "CSSVariableResolver.h" |
|
11 |
|
12 #include "CSSVariableDeclarations.h" |
|
13 #include "CSSVariableValues.h" |
|
14 #include "mozilla/PodOperations.h" |
|
15 #include <algorithm> |
|
16 |
|
17 namespace mozilla { |
|
18 |
|
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 } |
|
31 |
|
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 } |
|
41 |
|
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 } |
|
51 |
|
52 bool HasReferenceToVariable(size_t aID) const |
|
53 { |
|
54 return mReferences[aID]; |
|
55 } |
|
56 |
|
57 bool ReferencesNonExistentVariable() const |
|
58 { |
|
59 return mReferencesNonExistentVariable; |
|
60 } |
|
61 |
|
62 private: |
|
63 CSSVariableResolver& mResolver; |
|
64 |
|
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; |
|
70 |
|
71 // Whether the variable we have called EnumerateVariableReferences for |
|
72 // references a variable that does not exist in the resolver. |
|
73 bool mReferencesNonExistentVariable; |
|
74 }; |
|
75 |
|
76 static void |
|
77 RecordVariableReference(const nsAString& aVariableName, |
|
78 void* aData) |
|
79 { |
|
80 static_cast<EnumerateVariableReferencesData*>(aData)-> |
|
81 RecordVariableReference(aVariableName); |
|
82 } |
|
83 |
|
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++; |
|
92 |
|
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 } |
|
104 |
|
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 } |
|
123 |
|
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 } |
|
160 |
|
161 void |
|
162 CSSVariableResolver::Resolve(const CSSVariableValues* aInherited, |
|
163 const CSSVariableDeclarations* aSpecified) |
|
164 { |
|
165 MOZ_ASSERT(!mResolved); |
|
166 |
|
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); |
|
173 |
|
174 aInherited->AddVariablesToResolver(this); |
|
175 aSpecified->AddVariablesToResolver(this); |
|
176 |
|
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 } |
|
214 |
|
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 } |
|
225 |
|
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 } |
|
233 |
|
234 mResolved = true; |
|
235 } |
|
236 |
|
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); |
|
245 |
|
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 } |
|
262 |
|
263 } |