| /* |
| * 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 "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); |
| } |
| |
| } |
| } |