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

     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 }

mercurial