layout/style/CSSVariableResolver.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial