blob: 1eddf06312a464dec671ffeb9dcae63b8fc4a65b [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* Copyright (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
* Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com)
* Copyright (C) 2005-2019 Apple Inc. All rights reserved.
* Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
* Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org>
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
* Copyright (C) Research In Motion Limited 2011. All rights reserved.
* Copyright (C) 2012, 2013 Google Inc. All rights reserved.
* Copyright (C) 2014 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "StyleBuilder.h"
#include "CSSFontSelector.h"
#include "CSSPaintImageValue.h"
#include "CSSValuePool.h"
#include "HTMLElement.h"
#include "PaintWorkletGlobalScope.h"
#include "Settings.h"
#include "StyleBuilderGenerated.h"
#include "StyleFontSizeFunctions.h"
#include "StylePropertyShorthand.h"
namespace WebCore {
namespace Style {
static const CSSPropertyID firstLowPriorityProperty = static_cast<CSSPropertyID>(lastHighPriorityProperty + 1);
inline PropertyCascade::Direction directionFromStyle(const RenderStyle& style)
{
return { style.direction(), style.writingMode() };
}
inline bool isValidVisitedLinkProperty(CSSPropertyID id)
{
switch (id) {
case CSSPropertyBackgroundColor:
case CSSPropertyBorderLeftColor:
case CSSPropertyBorderRightColor:
case CSSPropertyBorderTopColor:
case CSSPropertyBorderBottomColor:
case CSSPropertyCaretColor:
case CSSPropertyColor:
case CSSPropertyOutlineColor:
case CSSPropertyColumnRuleColor:
case CSSPropertyTextDecorationColor:
case CSSPropertyWebkitTextEmphasisColor:
case CSSPropertyWebkitTextFillColor:
case CSSPropertyWebkitTextStrokeColor:
case CSSPropertyFill:
case CSSPropertyStroke:
case CSSPropertyStrokeColor:
return true;
default:
break;
}
return false;
}
Builder::Builder(RenderStyle& style, BuilderContext&& context, const MatchResult& matchResult, OptionSet<CascadeLevel> cascadeLevels, PropertyCascade::IncludedProperties includedProperties)
: m_cascade(matchResult, cascadeLevels, includedProperties, directionFromStyle(style))
, m_state(*this, style, WTFMove(context))
{
}
Builder::~Builder() = default;
void Builder::applyAllProperties()
{
applyHighPriorityProperties();
applyLowPriorityProperties();
}
// High priority properties may affect resolution of other properties (they are mostly font related).
void Builder::applyHighPriorityProperties()
{
applyProperties(CSSPropertyWebkitRubyPosition, CSSPropertyWebkitRubyPosition);
m_state.adjustStyleForInterCharacterRuby();
#if ENABLE(DARK_MODE_CSS)
// Supported color schemes can affect resolved colors, so we need to apply that property before any color properties.
applyProperties(CSSPropertyColorScheme, CSSPropertyColorScheme);
#endif
applyProperties(firstCSSProperty, lastHighPriorityProperty);
m_state.updateFont();
}
void Builder::applyLowPriorityProperties()
{
ASSERT(!m_state.fontDirty());
applyCustomProperties();
applyProperties(firstLowPriorityProperty, lastCSSProperty);
applyDeferredProperties();
ASSERT(!m_state.fontDirty());
}
void Builder::applyPropertyValue(CSSPropertyID propertyID, CSSValue* value)
{
if (!value)
return;
applyProperty(propertyID, *value, SelectorChecker::MatchDefault);
m_state.updateFont();
}
void Builder::applyDeferredProperties()
{
for (auto& property : m_cascade.deferredProperties())
applyCascadeProperty(property);
}
void Builder::applyProperties(int firstProperty, int lastProperty)
{
if (LIKELY(m_cascade.customProperties().isEmpty()))
return applyPropertiesImpl<CustomPropertyCycleTracking::Disabled>(firstProperty, lastProperty);
return applyPropertiesImpl<CustomPropertyCycleTracking::Enabled>(firstProperty, lastProperty);
}
template<Builder::CustomPropertyCycleTracking trackCycles>
inline void Builder::applyPropertiesImpl(int firstProperty, int lastProperty)
{
for (int id = firstProperty; id <= lastProperty; ++id) {
CSSPropertyID propertyID = static_cast<CSSPropertyID>(id);
if (!m_cascade.hasProperty(propertyID))
continue;
ASSERT(propertyID != CSSPropertyCustom);
auto& property = m_cascade.property(propertyID);
if (trackCycles == CustomPropertyCycleTracking::Enabled) {
if (UNLIKELY(m_state.m_inProgressProperties.get(propertyID))) {
// We are in a cycle (eg. setting font size using registered custom property value containing em).
// So this value should be unset.
m_state.m_appliedProperties.set(propertyID);
// This property is in a cycle, and only the root of the call stack will have firstProperty != lastProperty.
ASSERT(firstProperty == lastProperty);
continue;
}
m_state.m_inProgressProperties.set(propertyID);
applyCascadeProperty(property);
m_state.m_appliedProperties.set(propertyID);
m_state.m_inProgressProperties.set(propertyID, false);
continue;
}
// If we don't have any custom properties, then there can't be any cycles.
applyCascadeProperty(property);
}
}
void Builder::applyCustomProperties()
{
for (auto& name : m_cascade.customProperties().keys())
applyCustomProperty(name);
}
void Builder::applyCustomProperty(const String& name)
{
if (m_state.m_appliedCustomProperties.contains(name) || !m_cascade.customProperties().contains(name))
return;
auto property = m_cascade.customProperty(name);
bool inCycle = m_state.m_inProgressPropertiesCustom.contains(name);
for (auto index : { SelectorChecker::MatchDefault, SelectorChecker::MatchLink, SelectorChecker::MatchVisited }) {
if (!property.cssValue[index])
continue;
if (index != SelectorChecker::MatchDefault && m_state.style().insideLink() == InsideLink::NotInside)
continue;
Ref<CSSCustomPropertyValue> valueToApply = CSSCustomPropertyValue::create(downcast<CSSCustomPropertyValue>(*property.cssValue[index]));
if (inCycle) {
m_state.m_appliedCustomProperties.add(name); // Make sure we do not try to apply this property again while resolving it.
valueToApply = CSSCustomPropertyValue::createWithID(name, CSSValueInvalid);
}
m_state.m_inProgressPropertiesCustom.add(name);
if (WTF::holds_alternative<Ref<CSSVariableReferenceValue>>(valueToApply->value())) {
RefPtr<CSSValue> parsedValue = resolvedVariableValue(CSSPropertyCustom, valueToApply.get());
if (m_state.m_appliedCustomProperties.contains(name))
return; // There was a cycle and the value was reset, so bail.
if (!parsedValue)
parsedValue = CSSCustomPropertyValue::createWithID(name, CSSValueUnset);
valueToApply = downcast<CSSCustomPropertyValue>(*parsedValue);
}
if (m_state.m_inProgressPropertiesCustom.contains(name)) {
m_state.m_linkMatch = index;
applyProperty(CSSPropertyCustom, valueToApply.get(), index);
}
}
m_state.m_linkMatch = SelectorChecker::MatchDefault;
m_state.m_inProgressPropertiesCustom.remove(name);
m_state.m_appliedCustomProperties.add(name);
for (auto index : { SelectorChecker::MatchDefault, SelectorChecker::MatchLink, SelectorChecker::MatchVisited }) {
if (!property.cssValue[index])
continue;
if (index != SelectorChecker::MatchDefault && m_state.style().insideLink() == InsideLink::NotInside)
continue;
Ref<CSSCustomPropertyValue> valueToApply = CSSCustomPropertyValue::create(downcast<CSSCustomPropertyValue>(*property.cssValue[index]));
if (inCycle && WTF::holds_alternative<Ref<CSSVariableReferenceValue>>(valueToApply->value())) {
// Resolve this value so that we reset its dependencies.
resolvedVariableValue(CSSPropertyCustom, valueToApply.get());
}
}
}
inline void Builder::applyCascadeProperty(const PropertyCascade::Property& property)
{
m_state.m_cascadeLevel = property.level;
m_state.m_styleScopeOrdinal = property.styleScopeOrdinal;
auto applyWithLinkMatch = [&](SelectorChecker::LinkMatchMask linkMatch) {
if (property.cssValue[linkMatch]) {
m_state.m_linkMatch = linkMatch;
applyProperty(property.id, *property.cssValue[linkMatch], linkMatch);
}
};
applyWithLinkMatch(SelectorChecker::MatchDefault);
if (m_state.style().insideLink() == InsideLink::NotInside)
return;
applyWithLinkMatch(SelectorChecker::MatchLink);
applyWithLinkMatch(SelectorChecker::MatchVisited);
m_state.m_linkMatch = SelectorChecker::MatchDefault;
}
void Builder::applyProperty(CSSPropertyID id, CSSValue& value, SelectorChecker::LinkMatchMask linkMatchMask)
{
ASSERT_WITH_MESSAGE(!isShorthandCSSProperty(id), "Shorthand property id = %d wasn't expanded at parsing time", id);
auto valueToApply = resolveValue(id, value);
if (CSSProperty::isDirectionAwareProperty(id)) {
auto direction = m_cascade.direction();
CSSPropertyID newId = CSSProperty::resolveDirectionAwareProperty(id, direction.textDirection, direction.writingMode);
ASSERT(newId != id);
return applyProperty(newId, valueToApply.get(), linkMatchMask);
}
CSSCustomPropertyValue* customPropertyValue = nullptr;
CSSValueID customPropertyValueID = CSSValueInvalid;
CSSRegisteredCustomProperty* customPropertyRegistered = nullptr;
if (id == CSSPropertyCustom) {
customPropertyValue = downcast<CSSCustomPropertyValue>(valueToApply.ptr());
ASSERT(customPropertyValue->isResolved());
if (WTF::holds_alternative<CSSValueID>(customPropertyValue->value()))
customPropertyValueID = WTF::get<CSSValueID>(customPropertyValue->value());
auto& name = customPropertyValue->name();
customPropertyRegistered = m_state.document().getCSSRegisteredCustomPropertySet().get(name);
}
bool isInherit = valueToApply->isInheritedValue() || customPropertyValueID == CSSValueInherit;
bool isInitial = valueToApply->isInitialValue() || customPropertyValueID == CSSValueInitial;
bool isUnset = valueToApply->isUnsetValue() || customPropertyValueID == CSSValueUnset;
bool isRevert = valueToApply->isRevertValue() || customPropertyValueID == CSSValueRevert;
if (isRevert) {
if (auto* rollback = m_cascade.propertyCascadeForRollback(m_state.m_cascadeLevel)) {
// With the rollback cascade built, we need to obtain the property and apply it. If the property is
// not present, then we behave like "unset." Otherwise we apply the property instead of
// our own.
if (customPropertyValue) {
if (customPropertyRegistered && customPropertyRegistered->inherits && rollback->hasCustomProperty(customPropertyValue->name())) {
auto property = rollback->customProperty(customPropertyValue->name());
if (property.cssValue[linkMatchMask])
applyProperty(property.id, *property.cssValue[linkMatchMask], linkMatchMask);
return;
}
} else if (rollback->hasProperty(id)) {
auto& property = rollback->property(id);
if (property.cssValue[linkMatchMask])
applyProperty(property.id, *property.cssValue[linkMatchMask], linkMatchMask);
return;
}
}
isUnset = true;
}
if (isUnset) {
if (CSSProperty::isInheritedProperty(id))
isInherit = true;
else
isInitial = true;
}
ASSERT(!isInherit || !isInitial); // isInherit -> !isInitial && isInitial -> !isInherit
if (m_state.applyPropertyToVisitedLinkStyle() && !isValidVisitedLinkProperty(id)) {
// Limit the properties that can be applied to only the ones honored by :visited.
return;
}
if (isInherit && !CSSProperty::isInheritedProperty(id))
m_state.style().setHasExplicitlyInheritedProperties();
#if ENABLE(CSS_PAINTING_API)
if (is<CSSPaintImageValue>(valueToApply)) {
auto& name = downcast<CSSPaintImageValue>(valueToApply.get()).name();
if (auto* paintWorklet = const_cast<Document&>(m_state.document()).paintWorkletGlobalScopeForName(name)) {
auto locker = holdLock(paintWorklet->paintDefinitionLock());
if (auto* registration = paintWorklet->paintDefinitionMap().get(name)) {
for (auto& property : registration->inputProperties)
m_state.style().addCustomPaintWatchProperty(property);
}
}
}
#endif
BuilderGenerated::applyProperty(id, m_state, valueToApply.get(), isInitial, isInherit, customPropertyRegistered);
}
Ref<CSSValue> Builder::resolveValue(CSSPropertyID propertyID, CSSValue& value)
{
if (!value.hasVariableReferences())
return value;
auto variableValue = resolvedVariableValue(propertyID, value);
// If the cascade has already applied this id, then we detected a cycle, and this value should be unset.
if (!variableValue || m_state.m_appliedProperties.get(propertyID)) {
if (CSSProperty::isInheritedProperty(propertyID))
return CSSValuePool::singleton().createInheritedValue();
return CSSValuePool::singleton().createExplicitInitialValue();
}
return *variableValue;
}
RefPtr<CSSValue> Builder::resolvedVariableValue(CSSPropertyID propID, const CSSValue& value)
{
CSSParser parser(m_state.document());
return parser.parseValueWithVariableReferences(propID, value, m_state);
}
}
}