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