layout/style/CSSVariableResolver.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/layout/style/CSSVariableResolver.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,263 @@
     1.4 +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +/* object that resolves CSS variables using specified and inherited variable
    1.10 + * values
    1.11 + */
    1.12 +
    1.13 +#include "CSSVariableResolver.h"
    1.14 +
    1.15 +#include "CSSVariableDeclarations.h"
    1.16 +#include "CSSVariableValues.h"
    1.17 +#include "mozilla/PodOperations.h"
    1.18 +#include <algorithm>
    1.19 +
    1.20 +namespace mozilla {
    1.21 +
    1.22 +/**
    1.23 + * Data used by the EnumerateVariableReferences callback.  Reset must be called
    1.24 + * on it before it is used.
    1.25 + */
    1.26 +class EnumerateVariableReferencesData
    1.27 +{
    1.28 +public:
    1.29 +  EnumerateVariableReferencesData(CSSVariableResolver& aResolver)
    1.30 +    : mResolver(aResolver)
    1.31 +    , mReferences(new bool[aResolver.mVariables.Length()])
    1.32 +  {
    1.33 +  }
    1.34 +
    1.35 +  /**
    1.36 +   * Resets the data so that it can be passed to another call of
    1.37 +   * EnumerateVariableReferences for a different variable.
    1.38 +   */
    1.39 +  void Reset()
    1.40 +  {
    1.41 +    PodZero(mReferences.get(), mResolver.mVariables.Length());
    1.42 +    mReferencesNonExistentVariable = false;
    1.43 +  }
    1.44 +
    1.45 +  void RecordVariableReference(const nsAString& aVariableName)
    1.46 +  {
    1.47 +    size_t id;
    1.48 +    if (mResolver.mVariableIDs.Get(aVariableName, &id)) {
    1.49 +      mReferences[id] = true;
    1.50 +    } else {
    1.51 +      mReferencesNonExistentVariable = true;
    1.52 +    }
    1.53 +  }
    1.54 +
    1.55 +  bool HasReferenceToVariable(size_t aID) const
    1.56 +  {
    1.57 +    return mReferences[aID];
    1.58 +  }
    1.59 +
    1.60 +  bool ReferencesNonExistentVariable() const
    1.61 +  {
    1.62 +   return mReferencesNonExistentVariable;
    1.63 +  }
    1.64 +
    1.65 +private:
    1.66 +  CSSVariableResolver& mResolver;
    1.67 +
    1.68 +  // Array of booleans, where each index is a variable ID.  If an element is
    1.69 +  // true, it indicates that the variable we have called
    1.70 +  // EnumerateVariableReferences for has a reference to the variable with
    1.71 +  // that ID.
    1.72 +  nsAutoArrayPtr<bool> mReferences;
    1.73 +
    1.74 +  // Whether the variable we have called EnumerateVariableReferences for
    1.75 +  // references a variable that does not exist in the resolver.
    1.76 +  bool mReferencesNonExistentVariable;
    1.77 +};
    1.78 +
    1.79 +static void
    1.80 +RecordVariableReference(const nsAString& aVariableName,
    1.81 +                        void* aData)
    1.82 +{
    1.83 +  static_cast<EnumerateVariableReferencesData*>(aData)->
    1.84 +    RecordVariableReference(aVariableName);
    1.85 +}
    1.86 +
    1.87 +void
    1.88 +CSSVariableResolver::RemoveCycles(size_t v)
    1.89 +{
    1.90 +  mVariables[v].mIndex = mNextIndex;
    1.91 +  mVariables[v].mLowLink = mNextIndex;
    1.92 +  mVariables[v].mInStack = true;
    1.93 +  mStack.AppendElement(v);
    1.94 +  mNextIndex++;
    1.95 +
    1.96 +  for (size_t i = 0, n = mReferences[v].Length(); i < n; i++) {
    1.97 +    size_t w = mReferences[v][i];
    1.98 +    if (!mVariables[w].mIndex) {
    1.99 +      RemoveCycles(w);
   1.100 +      mVariables[v].mLowLink = std::min(mVariables[v].mLowLink,
   1.101 +                                        mVariables[w].mLowLink);
   1.102 +    } else if (mVariables[w].mInStack) {
   1.103 +      mVariables[v].mLowLink = std::min(mVariables[v].mLowLink,
   1.104 +                                        mVariables[w].mIndex);
   1.105 +    }
   1.106 +  }
   1.107 +
   1.108 +  if (mVariables[v].mLowLink == mVariables[v].mIndex) {
   1.109 +    if (mStack.LastElement() == v) {
   1.110 +      // A strongly connected component consisting of a single variable is not
   1.111 +      // necessarily invalid.  We handle variables that reference themselves
   1.112 +      // earlier, in CSSVariableResolver::Resolve.
   1.113 +      mVariables[mStack.LastElement()].mInStack = false;
   1.114 +      mStack.TruncateLength(mStack.Length() - 1);
   1.115 +    } else {
   1.116 +      size_t w;
   1.117 +      do {
   1.118 +        w = mStack.LastElement();
   1.119 +        mVariables[w].mValue.Truncate(0);
   1.120 +        mVariables[w].mInStack = false;
   1.121 +        mStack.TruncateLength(mStack.Length() - 1);
   1.122 +      } while (w != v);
   1.123 +    }
   1.124 +  }
   1.125 +}
   1.126 +
   1.127 +void
   1.128 +CSSVariableResolver::ResolveVariable(size_t aID)
   1.129 +{
   1.130 +  if (mVariables[aID].mValue.IsEmpty() || mVariables[aID].mWasInherited) {
   1.131 +    // The variable is invalid or was inherited.   We can just copy the value
   1.132 +    // and its first/last token information across.
   1.133 +    mOutput->Put(mVariables[aID].mVariableName,
   1.134 +                 mVariables[aID].mValue,
   1.135 +                 mVariables[aID].mFirstToken,
   1.136 +                 mVariables[aID].mLastToken);
   1.137 +  } else {
   1.138 +    // Otherwise we need to resolve the variable references, after resolving
   1.139 +    // all of our dependencies first.  We do this even for variables that we
   1.140 +    // know do not reference other variables so that we can find their
   1.141 +    // first/last token.
   1.142 +    //
   1.143 +    // XXX We might want to do this first/last token finding during
   1.144 +    // EnumerateVariableReferences, so that we can avoid calling
   1.145 +    // ResolveVariableValue and parsing the value again.
   1.146 +    for (size_t i = 0, n = mReferences[aID].Length(); i < n; i++) {
   1.147 +      size_t j = mReferences[aID][i];
   1.148 +      if (aID != j && !mVariables[j].mResolved) {
   1.149 +        ResolveVariable(j);
   1.150 +      }
   1.151 +    }
   1.152 +    nsString resolvedValue;
   1.153 +    nsCSSTokenSerializationType firstToken, lastToken;
   1.154 +    if (!mParser.ResolveVariableValue(mVariables[aID].mValue, mOutput,
   1.155 +                                      resolvedValue, firstToken, lastToken)) {
   1.156 +      resolvedValue.Truncate(0);
   1.157 +    }
   1.158 +    mOutput->Put(mVariables[aID].mVariableName, resolvedValue,
   1.159 +                 firstToken, lastToken);
   1.160 +  }
   1.161 +  mVariables[aID].mResolved = true;
   1.162 +}
   1.163 +
   1.164 +void
   1.165 +CSSVariableResolver::Resolve(const CSSVariableValues* aInherited,
   1.166 +                             const CSSVariableDeclarations* aSpecified)
   1.167 +{
   1.168 +  MOZ_ASSERT(!mResolved);
   1.169 +
   1.170 +  // The only time we would be worried about having a null aInherited is
   1.171 +  // for the root, but in that case nsRuleNode::ComputeVariablesData will
   1.172 +  // happen to pass in whatever we're using as mOutput for aInherited,
   1.173 +  // which will initially be empty.
   1.174 +  MOZ_ASSERT(aInherited);
   1.175 +  MOZ_ASSERT(aSpecified);
   1.176 +
   1.177 +  aInherited->AddVariablesToResolver(this);
   1.178 +  aSpecified->AddVariablesToResolver(this);
   1.179 +
   1.180 +  // First we look at each variable's value and record which other variables
   1.181 +  // it references.
   1.182 +  size_t n = mVariables.Length();
   1.183 +  mReferences.SetLength(n);
   1.184 +  EnumerateVariableReferencesData data(*this);
   1.185 +  for (size_t id = 0; id < n; id++) {
   1.186 +    data.Reset();
   1.187 +    if (!mVariables[id].mWasInherited &&
   1.188 +        !mVariables[id].mValue.IsEmpty()) {
   1.189 +      if (mParser.EnumerateVariableReferences(mVariables[id].mValue,
   1.190 +                                              RecordVariableReference,
   1.191 +                                              &data)) {
   1.192 +        // Convert the boolean array of dependencies in |data| to a list
   1.193 +        // of dependencies.
   1.194 +        for (size_t i = 0; i < n; i++) {
   1.195 +          if (data.HasReferenceToVariable(i)) {
   1.196 +            mReferences[id].AppendElement(i);
   1.197 +          }
   1.198 +        }
   1.199 +        // If a variable references itself, it is invalid.  (RemoveCycles
   1.200 +        // does not check for cycles consisting of a single variable, so we
   1.201 +        // check here.)
   1.202 +        if (data.HasReferenceToVariable(id)) {
   1.203 +          mVariables[id].mValue.Truncate();
   1.204 +        }
   1.205 +        // Also record whether it referenced any variables that don't exist
   1.206 +        // in the resolver, so that we can ensure we still resolve its value
   1.207 +        // in ResolveVariable, even though its mReferences list is empty.
   1.208 +        mVariables[id].mReferencesNonExistentVariable =
   1.209 +          data.ReferencesNonExistentVariable();
   1.210 +      } else {
   1.211 +        MOZ_ASSERT(false, "EnumerateVariableReferences should not have failed "
   1.212 +                          "if we previously parsed the specified value");
   1.213 +        mVariables[id].mValue.Truncate(0);
   1.214 +      }
   1.215 +    }
   1.216 +  }
   1.217 +
   1.218 +  // Next we remove any cycles in variable references using Tarjan's strongly
   1.219 +  // connected components finding algorithm, setting variables in cycles to
   1.220 +  // have an invalid value.
   1.221 +  mNextIndex = 1;
   1.222 +  for (size_t id = 0; id < n; id++) {
   1.223 +    if (!mVariables[id].mIndex) {
   1.224 +      RemoveCycles(id);
   1.225 +      MOZ_ASSERT(mStack.IsEmpty());
   1.226 +    }
   1.227 +  }
   1.228 +
   1.229 +  // Finally we construct the computed value for the variable by substituting
   1.230 +  // any variable references.
   1.231 +  for (size_t id = 0; id < n; id++) {
   1.232 +    if (!mVariables[id].mResolved) {
   1.233 +      ResolveVariable(id);
   1.234 +    }
   1.235 +  }
   1.236 +
   1.237 +  mResolved = true;
   1.238 +}
   1.239 +
   1.240 +void
   1.241 +CSSVariableResolver::Put(const nsAString& aVariableName,
   1.242 +                         nsString aValue,
   1.243 +                         nsCSSTokenSerializationType aFirstToken,
   1.244 +                         nsCSSTokenSerializationType aLastToken,
   1.245 +                         bool aWasInherited)
   1.246 +{
   1.247 +  MOZ_ASSERT(!mResolved);
   1.248 +
   1.249 +  size_t id;
   1.250 +  if (mVariableIDs.Get(aVariableName, &id)) {
   1.251 +    MOZ_ASSERT(mVariables[id].mWasInherited && !aWasInherited,
   1.252 +               "should only overwrite inherited variables with specified "
   1.253 +               "variables");
   1.254 +    mVariables[id].mValue = aValue;
   1.255 +    mVariables[id].mFirstToken = aFirstToken;
   1.256 +    mVariables[id].mLastToken = aLastToken;
   1.257 +    mVariables[id].mWasInherited = aWasInherited;
   1.258 +  } else {
   1.259 +    id = mVariables.Length();
   1.260 +    mVariableIDs.Put(aVariableName, id);
   1.261 +    mVariables.AppendElement(Variable(aVariableName, aValue,
   1.262 +                                      aFirstToken, aLastToken, aWasInherited));
   1.263 +  }
   1.264 +}
   1.265 +
   1.266 +}

mercurial