Chromium Code Reviews| Index: Source/core/css/resolver/CSSVariableResolver.cpp |
| diff --git a/Source/core/css/resolver/CSSVariableResolver.cpp b/Source/core/css/resolver/CSSVariableResolver.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..73d1a069a0571d7a485d3213931b3ba80724547e |
| --- /dev/null |
| +++ b/Source/core/css/resolver/CSSVariableResolver.cpp |
| @@ -0,0 +1,233 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "config.h" |
| +#include "core/css/resolver/CSSVariableResolver.h" |
| + |
| +#include "core/CSSPropertyNames.h" |
| +#include "core/CSSValueKeywords.h" |
| +#include "core/StyleBuilderFunctions.h" |
| +#include "core/css/CSSVariableData.h" |
| +#include "core/css/parser/CSSParserToken.h" |
| +#include "core/css/parser/CSSParserTokenRange.h" |
| +#include "core/css/parser/CSSParserValues.h" |
| +#include "core/css/parser/CSSPropertyParser.h" |
| +#include "wtf/Vector.h" |
| + |
| +namespace blink { |
| + |
| +enum ResolutionBehavior { |
| + ResolvingVariableDefinitions, |
| + DoNotResolveVariableDefinitions |
| +}; |
| + |
| +static bool isVariableReferenceToken(CSSParserToken& token) |
| +{ |
| + return token.type() == FunctionToken && token.valueEqualsIgnoringCase("var"); |
| +} |
| + |
| +// TODO(leviw): These will get moved out of StyleBuilder |
| +// TODO(leviw): Validation should no longer be required here |
| +static bool findEndOfVariableReference(Vector<CSSParserToken>& resolvedTokens, unsigned startOffset, unsigned& end, unsigned& commaLocation) |
| +{ |
| + end = 0; |
| + commaLocation = 0; |
| + unsigned bracketCount = 0; |
| + bool validSyntax = true; |
| + for (unsigned i = startOffset; i < resolvedTokens.size(); ++i) { |
| + CSSParserTokenType type = resolvedTokens[i].type(); |
| + |
| + if (type == CommaToken && !commaLocation) { |
| + commaLocation = i; |
| + } else if (type == LeftParenthesisToken) { |
| + bracketCount++; |
| + } else if (type == RightParenthesisToken) { |
| + if (bracketCount) { |
| + bracketCount--; |
| + } else { |
| + end = i; |
| + break; |
| + } |
| + } else if (type != WhitespaceToken && !commaLocation) { |
| + // We need content after a comma for valid syntax, but if it occurs |
| + // before a comma its invalid. |
| + validSyntax = commaLocation; |
| + } |
| + } |
| + if (!end || !validSyntax) { |
| + end = resolvedTokens.size() - 1; |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +// TODO(leviw): Factor this into another file. Make it actually recursive. |
| +static unsigned resolveVariableTokensRecursive(Vector<CSSParserToken>& resolvedTokens, const StyleVariableData* styleVariableData, HashSet<AtomicString>& variablesSeen, unsigned startOffset, ResolutionBehavior resolutionBehavior) |
| +{ |
| + ASSERT(startOffset); |
| + // There should be at least 2 tokens after the start position: a the variable name and the close bracket |
| + if (resolvedTokens.size() < startOffset + 1) |
| + return -1; |
| + |
| + unsigned commaLocation, end; |
| + |
| + // Find the variable location |
| + unsigned variableLocation = 0; |
| + for (unsigned i = startOffset; i < resolvedTokens.size(); ++i) { |
| + if (resolvedTokens[i].type() == IdentToken) { |
| + variableLocation = i; |
| + break; |
| + } |
| + if (resolvedTokens[i].type() != WhitespaceToken) { |
| + findEndOfVariableReference(resolvedTokens, i, end, commaLocation); |
| + unsigned length = end - startOffset + 2; |
|
leviw_travelin_and_unemployed
2015/06/20 00:58:15
I need to go back and remove all these magic offse
|
| + resolvedTokens.remove(startOffset - 1, length); |
| + return -1; |
| + } |
| + } |
| + |
| + // Find default value and match braces |
| + if (!findEndOfVariableReference(resolvedTokens, variableLocation + 1, end, commaLocation)) { |
| + unsigned length = end - startOffset + 2; |
| + resolvedTokens.remove(startOffset - 1, length); |
| + return -1; |
| + } |
| + |
| + unsigned length = end - startOffset + 2; |
| + unsigned varFunctionPosition = startOffset - 1; |
| + |
| + AtomicString variableName = resolvedTokens[variableLocation].value(); |
| + |
| + if (variablesSeen.contains(variableName)) { |
| + // Cycle detected. |
| + resolvedTokens.clear(); |
| + return 0; |
| + } |
| + |
| + CSSVariableData* variableData = styleVariableData ? styleVariableData->getVariable(variableName) : nullptr; |
| + if (variableData) { |
| + Vector<CSSParserToken>& tokens = variableData->tokens(); |
| + if (variableData->needsVariableResolution()) { |
| + ASSERT(resolutionBehavior == ResolvingVariableDefinitions); |
| + variablesSeen.add(variableName); |
| + for (unsigned i = 0; i < tokens.size(); ++i) { |
| + if (isVariableReferenceToken(tokens[i])) { |
| + i += resolveVariableTokensRecursive(tokens, styleVariableData, variablesSeen, i + 1, resolutionBehavior); |
| + } |
| + } |
| + variablesSeen.remove(variableName); |
| + } |
| + if (tokens.size()) { |
| + resolvedTokens.remove(startOffset - 1, length); |
| + resolvedTokens.insert(startOffset - 1, tokens); |
| + return tokens.size(); |
| + } |
| + } |
| + |
| + // Fallback on default value if present |
| + if (!commaLocation) { |
| + resolvedTokens.remove(varFunctionPosition, length); |
| + return 0; |
| + } |
| + |
| + // Move the tokens to the beginning of the variable reference |
| + unsigned defaultValueOffset = commaLocation + 1; |
| + unsigned defaultValueLength = end - commaLocation - 1; |
| + for (unsigned i = 0; i < defaultValueLength; ++i) |
| + resolvedTokens[varFunctionPosition + i] = resolvedTokens[defaultValueOffset + i]; |
| + resolvedTokens.remove(varFunctionPosition + defaultValueLength, length - defaultValueLength); |
| + |
| + return commaLocation; |
| +} |
| + |
| +static void resolveVariableReferencesFromTokens(Vector<CSSParserToken>& tokens, const StyleVariableData* variables, ResolutionBehavior resolutionBehavior) |
| +{ |
| + HashSet<AtomicString> variablesSeen; |
| + |
| + for (unsigned i = 0; i < tokens.size(); ++i) { |
| + if (isVariableReferenceToken(tokens[i])) { |
| + unsigned validTokens = resolveVariableTokensRecursive(tokens, variables, variablesSeen, i + 1, resolutionBehavior); |
| + if (validTokens < 1) { |
| + tokens.clear(); |
| + break; |
| + } |
| + i += validTokens - 1; |
| + } |
| + } |
| +} |
| + |
| +void CSSVariableResolver::resolveAndApplyVariableReferences(StyleResolverState& state, CSSPropertyID id, CSSPrimitiveValue* value) |
| +{ |
| + // TODO(leviw): This should be a stack |
| + Vector<CSSParserToken> tokens = value->getVariableDataValue()->tokens(); |
| + |
| + resolveVariableReferencesFromTokens(tokens, state.style()->variables(), DoNotResolveVariableDefinitions); |
| + |
| + if (!tokens.size()) |
| + return; |
| + |
| + CSSParserContext context(HTMLStandardMode, 0); |
| + |
| + WillBeHeapVector<CSSProperty, 256> parsedProperties; |
| + |
| + bool usesRemUnits, usesVariables; |
| + |
| + CSSParserValueList valueList(CSSParserTokenRange(tokens, 0), usesRemUnits, usesVariables); |
| + |
| + if (!valueList.size()) |
| + return; |
| + |
| + CSSPropertyParser::parseValue(id, false, &valueList, context, parsedProperties, StyleRule::Type::Style); |
| + |
| + unsigned parsedPropertiesCount = parsedProperties.size(); |
| + for (unsigned i = 0; i < parsedPropertiesCount; ++i) |
| + StyleBuilder::applyProperty(parsedProperties[i].id(), state, parsedProperties[i].value()); |
| +} |
| + |
| +void CSSVariableResolver::resolveVariableDefinitions(StyleResolverState& state) |
| +{ |
| + StyleVariableData* variables = state.style()->variables(); |
| + if (!variables) |
| + return; |
| + |
| + // TODO(leviw): Skip resolving definitions without references. |
| + for (auto& variable : variables->m_data) { |
| + if (!variable.value->needsVariableResolution()) |
| + continue; |
| + Vector<CSSParserToken>& tokens = variable.value->tokens(); |
| + |
| + resolveVariableReferencesFromTokens(tokens, variables, ResolvingVariableDefinitions); |
| + |
| + // TODO(leviw): This should have been done at parse-time and used the normal machinery. |
| + // for (CSSParserToken& token : tokens) { |
| + // if (token.type() == IdentToken) { |
| + // if (token.valueEqualsIgnoringCase("initial")) { |
| + // tokens.clear(); |
| + // break; |
| + // } else if (token.valueEqualsIgnoringCase("inherit")) { |
| + // if (state.parentStyle()->variables()) { |
| + // if |
| + // } |
| + // tokens = |
| + // break; |
| + // } |
| + // } |
| + // } |
| + |
| + // TODO(leviw): this is a hack! |
| + variable.value->setNeedsVariableResolution(false); |
| + |
| + // CSSParserTokenRange range(resolvedTokens, &variable.value->scope()); |
| + // CSSVariableData* resolvedData = CSSVariableData::create(range).leakRef(); |
| + // resolvedData->setResolved(); |
| + // variable.value = resolvedData; |
| + |
| + // // TODO(leviw): Can I do this!? |
| + // variable.value->scope().m_tokens = resolvedTokens; |
| + // variable.value->setResolved(); |
| + |
| + } |
| +} |
| + |
| +} // namespace blink |