blob: 936cb2d86bac8bfa013b6839cf01a7486b08df71 [file] [log] [blame]
// 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 "core/css/resolver/CSSVariableResolver.h"
#include "core/CSSPropertyNames.h"
#include "core/CSSValueKeywords.h"
#include "core/StyleBuilderFunctions.h"
#include "core/StylePropertyShorthand.h"
#include "core/css/CSSValuePool.h"
#include "core/css/CSSVariableData.h"
#include "core/css/CSSVariableReferenceValue.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 "core/css/resolver/StyleBuilder.h"
#include "core/css/resolver/StyleResolverState.h"
#include "core/style/StyleVariableData.h"
#include "wtf/Vector.h"
namespace blink {
bool CSSVariableResolver::resolveFallback(CSSParserTokenRange range, Vector<CSSParserToken>& result)
{
if (range.atEnd())
return false;
ASSERT(range.peek().type() == CommaToken);
range.consume();
return resolveTokenRange(range, result);
}
CSSVariableData* CSSVariableResolver::valueForCustomProperty(AtomicString name)
{
if (m_variablesSeen.contains(name)) {
m_cycleStartPoints.add(name);
return nullptr;
}
if (!m_styleVariableData)
return nullptr;
CSSVariableData* variableData = m_styleVariableData->getVariable(name);
if (!variableData)
return nullptr;
if (!variableData->needsVariableResolution())
return variableData;
RefPtr<CSSVariableData> newVariableData = resolveCustomProperty(name, *variableData);
m_styleVariableData->setVariable(name, newVariableData);
return newVariableData.get();
}
PassRefPtr<CSSVariableData> CSSVariableResolver::resolveCustomProperty(AtomicString name, const CSSVariableData& variableData)
{
ASSERT(variableData.needsVariableResolution());
Vector<CSSParserToken> tokens;
m_variablesSeen.add(name);
bool success = resolveTokenRange(variableData.tokens(), tokens);
m_variablesSeen.remove(name);
// The old variable data holds onto the backing string the new resolved CSSVariableData
// relies on. Ensure it will live beyond us overwriting the RefPtr in StyleVariableData.
ASSERT(variableData.refCount() > 1);
if (!success || !m_cycleStartPoints.isEmpty()) {
m_cycleStartPoints.remove(name);
return nullptr;
}
return CSSVariableData::createResolved(tokens, variableData);
}
bool CSSVariableResolver::resolveVariableReference(CSSParserTokenRange range, Vector<CSSParserToken>& result)
{
range.consumeWhitespace();
ASSERT(range.peek().type() == IdentToken);
AtomicString variableName = range.consumeIncludingWhitespace().value();
ASSERT(range.atEnd() || (range.peek().type() == CommaToken));
CSSVariableData* variableData = valueForCustomProperty(variableName);
if (!variableData)
return resolveFallback(range, result);
result.appendVector(variableData->tokens());
Vector<CSSParserToken> trash;
resolveFallback(range, trash);
return true;
}
void CSSVariableResolver::resolveApplyAtRule(CSSParserTokenRange& range,
Vector<CSSParserToken>& result)
{
ASSERT(range.peek().type() == AtKeywordToken && range.peek().valueEqualsIgnoringASCIICase("apply"));
range.consumeIncludingWhitespace();
const CSSParserToken& variableName = range.consumeIncludingWhitespace();
// TODO(timloh): Should we actually be consuming this?
if (range.peek().type() == SemicolonToken)
range.consume();
CSSVariableData* variableData = valueForCustomProperty(variableName.value());
if (!variableData)
return; // Invalid custom property
CSSParserTokenRange rule = variableData->tokenRange();
rule.consumeWhitespace();
if (rule.peek().type() != LeftBraceToken)
return;
CSSParserTokenRange ruleContents = rule.consumeBlock();
rule.consumeWhitespace();
if (!rule.atEnd())
return;
result.appendRange(ruleContents.begin(), ruleContents.end());
}
bool CSSVariableResolver::resolveTokenRange(CSSParserTokenRange range,
Vector<CSSParserToken>& result)
{
bool success = true;
while (!range.atEnd()) {
if (range.peek().functionId() == CSSValueVar) {
success &= resolveVariableReference(range.consumeBlock(), result);
} else if (range.peek().type() == AtKeywordToken && range.peek().valueEqualsIgnoringASCIICase("apply")
&& RuntimeEnabledFeatures::cssApplyAtRulesEnabled()) {
resolveApplyAtRule(range, result);
} else {
result.append(range.consume());
}
}
return success;
}
PassRefPtrWillBeRawPtr<CSSValue> CSSVariableResolver::resolveVariableReferences(StyleVariableData* styleVariableData, CSSPropertyID id, const CSSVariableReferenceValue& value)
{
ASSERT(!isShorthandProperty(id));
CSSVariableResolver resolver(styleVariableData);
Vector<CSSParserToken> tokens;
if (!resolver.resolveTokenRange(value.variableDataValue()->tokens(), tokens))
return cssValuePool().createUnsetValue();
RefPtrWillBeRawPtr<CSSValue> result = CSSPropertyParser::parseSingleValue(id, tokens, strictCSSParserContext());
if (!result)
return cssValuePool().createUnsetValue();
return result.release();
}
void CSSVariableResolver::resolveAndApplyVariableReferences(StyleResolverState& state, CSSPropertyID id, const CSSVariableReferenceValue& value)
{
CSSVariableResolver resolver(state.style()->variables());
Vector<CSSParserToken> tokens;
if (resolver.resolveTokenRange(value.variableDataValue()->tokens(), tokens)) {
CSSParserContext context(HTMLStandardMode, 0);
WillBeHeapVector<CSSProperty, 256> parsedProperties;
// TODO: Non-shorthands should just call CSSPropertyParser::parseSingleValue
if (CSSPropertyParser::parseValue(id, false, CSSParserTokenRange(tokens), context, parsedProperties, StyleRule::RuleType::Style)) {
unsigned parsedPropertiesCount = parsedProperties.size();
for (unsigned i = 0; i < parsedPropertiesCount; ++i)
StyleBuilder::applyProperty(parsedProperties[i].id(), state, parsedProperties[i].value());
return;
}
}
RefPtrWillBeRawPtr<CSSUnsetValue> unset = cssValuePool().createUnsetValue();
if (isShorthandProperty(id)) {
StylePropertyShorthand shorthand = shorthandForProperty(id);
for (unsigned i = 0; i < shorthand.length(); i++)
StyleBuilder::applyProperty(shorthand.properties()[i], state, unset.get());
return;
}
StyleBuilder::applyProperty(id, state, unset.get());
}
void CSSVariableResolver::resolveVariableDefinitions(StyleVariableData* variables)
{
if (!variables)
return;
CSSVariableResolver resolver(variables);
for (auto& variable : variables->m_data) {
if (variable.value && variable.value->needsVariableResolution())
variable.value = resolver.resolveCustomProperty(variable.key, *variable.value);
}
}
CSSVariableResolver::CSSVariableResolver(StyleVariableData* styleVariableData)
: m_styleVariableData(styleVariableData)
{
}
} // namespace blink