blob: e28f5e1ad907d57c85f655e609a25c44d8c786eb [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Copyright (C) 2016-2021 Apple Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "config.h"
#include "CSSVariableReferenceValue.h"
#include "CSSCustomPropertyValue.h"
#include "CSSParserTokenRange.h"
#include "CSSPropertyParser.h"
#include "CSSRegisteredCustomProperty.h"
#include "CSSVariableData.h"
#include "ConstantPropertyMap.h"
#include "CustomFunctionRegistry.h"
#include "Document.h"
#include "RenderStyle+GettersInlines.h"
#include "StyleBuilder.h"
#include "StyleCustomPropertyRegistry.h"
#include "StyleResolver.h"
#include "StyleScope.h"
namespace WebCore {
CSSVariableReferenceValue::CSSVariableReferenceValue(Ref<CSSVariableData>&& data)
: CSSValue(ClassType::VariableReference)
, m_data(WTF::move(data))
{
cacheSimpleReference();
}
Ref<CSSVariableReferenceValue> CSSVariableReferenceValue::create(const CSSParserTokenRange& range, const CSSParserContext& context)
{
return adoptRef(*new CSSVariableReferenceValue(CSSVariableData::create(range, context)));
}
Ref<CSSVariableReferenceValue> CSSVariableReferenceValue::create(Ref<CSSVariableData>&& data)
{
return adoptRef(*new CSSVariableReferenceValue(WTF::move(data)));
}
bool CSSVariableReferenceValue::equals(const CSSVariableReferenceValue& other) const
{
return arePointingToEqualData(m_data, other.m_data);
}
String CSSVariableReferenceValue::customCSSText(const CSS::SerializationContext&) const
{
if (m_stringValue.isNull())
m_stringValue = m_data->serialize();
return m_stringValue;
}
const CSSParserContext& CSSVariableReferenceValue::context() const
{
return m_data->context();
}
auto CSSVariableReferenceValue::resolveVariableFallback(const AtomString& variableName, CSSParserTokenRange range, CSSValueID functionId, Style::Builder& builder) const -> std::pair<FallbackResult, Vector<CSSParserToken>>
{
ASSERT(range.atEnd() || range.peek().type() == CommaToken);
if (range.atEnd())
return { FallbackResult::None, { } };
range.consumeIncludingWhitespace();
auto tokens = resolveTokenRange(range, builder);
if (functionId == CSSValueVar) {
auto* registered = builder.state().document().customPropertyRegistry().get(variableName);
if (registered && !registered->syntax.isUniversal()) {
// https://drafts.css-houdini.org/css-properties-values-api/#fallbacks-in-var-references
// The fallback value must match the syntax definition of the custom property being referenced,
// otherwise the declaration is invalid at computed-value time
if (!tokens || !CSSPropertyParser::isValidCustomPropertyValueForSyntax(registered->syntax, *tokens, context()))
return { FallbackResult::Invalid, { } };
return { FallbackResult::Valid, WTF::move(*tokens) };
}
}
if (!tokens)
return { FallbackResult::None, { } };
return { FallbackResult::Valid, WTF::move(*tokens) };
}
static const Style::CustomProperty* propertyValueForVariableName(const AtomString& variableName, CSSValueID functionId, Style::Builder& builder)
{
if (functionId == CSSValueEnv)
return builder.state().document().constantProperties().values().get(variableName);
// Apply this variable first, in case it is still unresolved
builder.applyCustomProperty(variableName);
return builder.state().checkedStyle()->customPropertyValue(variableName);
}
bool CSSVariableReferenceValue::resolveVariableReference(CSSParserTokenRange range, CSSValueID functionId, Vector<CSSParserToken>& tokens, Style::Builder& builder) const
{
ASSERT(functionId == CSSValueVar || functionId == CSSValueEnv);
range.consumeWhitespace();
ASSERT(range.peek().type() == IdentToken);
auto variableName = range.consumeIncludingWhitespace().value().toAtomString();
// Fallback has to be resolved even when not used to detect cycles and invalid syntax.
auto [fallbackResult, fallbackTokens] = resolveVariableFallback(variableName, range, functionId, builder);
if (fallbackResult == FallbackResult::Invalid)
return false;
RefPtr property = propertyValueForVariableName(variableName, functionId, builder);
if (!property || property->isGuaranteedInvalid()) {
if (fallbackTokens.size() > maxSubstitutionTokens)
return false;
if (fallbackResult == FallbackResult::Valid) {
tokens.appendVector(fallbackTokens);
return true;
}
return false;
}
if (property->tokens().size() > maxSubstitutionTokens)
return false;
tokens.appendVector(property->tokens());
return true;
}
bool CSSVariableReferenceValue::evaluateDashedFunction(StringView functionName, CSSParserTokenRange, Vector<CSSParserToken>& tokens, Style::Builder& builder) const
{
// https://drafts.csswg.org/css-mixins/#evaluating-custom-functions
if (!builder.state().element())
return false;
auto scopedFunctionName = Style::ScopedName { functionName.toAtomString(), builder.state().styleScopeOrdinal() };
CheckedPtr element = builder.state().element();
auto customFunction = Style::Scope::resolveTreeScopedReference(*element, scopedFunctionName, [](const Style::Scope& scope, const AtomString& name) -> CheckedPtr<const Style::CustomFunction> {
RefPtr resolver = scope.resolverIfExists();
CheckedPtr registry = resolver ? resolver->customFunctionRegistry() : nullptr;
return registry ? registry->functionForName(name) : nullptr;
});
if (!customFunction)
return false;
// FIXME: Evaluate the function instead of just substituting.
auto properties = customFunction->properties;
auto resultValue = dynamicDowncast<CSSCustomPropertyValue>(properties->getPropertyCSSValue(CSSPropertyResult));
if (!resultValue)
return false;
auto data = resultValue->asVariableData();
tokens.appendVector(data->tokens());
return true;
}
std::optional<Vector<CSSParserToken>> CSSVariableReferenceValue::resolveTokenRange(CSSParserTokenRange range, Style::Builder& builder) const
{
Vector<CSSParserToken> tokens;
bool success = true;
while (!range.atEnd()) {
auto token = range.peek();
if (token.type() == FunctionToken) {
auto functionId = token.functionId();
if (functionId == CSSValueVar || functionId == CSSValueEnv) {
if (!resolveVariableReference(range.consumeBlock(), functionId, tokens, builder))
success = false;
continue;
}
if (isCustomPropertyName(token.value())) {
// <dashed-function>
if (!evaluateDashedFunction(token.value(), range.consumeBlock(), tokens, builder))
success = false;
continue;
}
}
tokens.append(range.consume());
}
if (!success)
return { };
return tokens;
}
void CSSVariableReferenceValue::cacheSimpleReference()
{
ASSERT(!m_simpleReference);
auto range = m_data->tokenRange();
auto functionId = range.peek().functionId();
if (functionId != CSSValueVar && functionId != CSSValueEnv)
return;
auto variableRange = range.consumeBlock();
if (!range.atEnd())
return;
variableRange.consumeWhitespace();
auto variableName = variableRange.consumeIncludingWhitespace().value().toAtomString();
// No fallback support on this path.
if (!variableRange.atEnd())
return;
m_simpleReference = SimpleReference { variableName, functionId };
}
RefPtr<CSSVariableData> CSSVariableReferenceValue::tryResolveSimpleReference(Style::Builder& builder) const
{
if (!m_simpleReference)
return nullptr;
// Shortcut for the simple common case of property:var(--foo)
RefPtr property = propertyValueForVariableName(m_simpleReference->name, m_simpleReference->functionId, builder);
if (!property || !std::holds_alternative<Ref<CSSVariableData>>(property->value()))
return nullptr;
return std::get<Ref<CSSVariableData>>(property->value()).ptr();
}
RefPtr<CSSVariableData> CSSVariableReferenceValue::resolveVariableReferences(Style::Builder& builder) const
{
if (auto data = tryResolveSimpleReference(builder))
return data;
auto resolvedTokens = resolveTokenRange(m_data->tokenRange(), builder);
if (!resolvedTokens)
return nullptr;
return CSSVariableData::create(*resolvedTokens, context());
}
RefPtr<CSSValue> CSSVariableReferenceValue::resolveSingleValue(Style::Builder& builder, CSSPropertyID propertyID) const
{
if (!resolveAndCacheValue(builder, [this, propertyID](auto data) {
m_cachedValue = CSSPropertyParser::parseStylePropertyLonghand(propertyID, data->tokens(), context());
#if ASSERT_ENABLED
m_cachePropertyID = propertyID;
#endif
}))
return nullptr;
ASSERT(m_cachePropertyID == propertyID);
return m_cachedValue;
}
} // namespace WebCore