| /* |
| * 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-2021 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 "Document.h" |
| #include "HTMLElement.h" |
| #include "PaintWorkletGlobalScope.h" |
| #include "Settings.h" |
| #include "StyleBuilderGenerated.h" |
| #include "StyleFontSizeFunctions.h" |
| #include "StylePropertyShorthand.h" |
| |
| #include <wtf/SetForScope.h> |
| |
| namespace WebCore { |
| namespace Style { |
| |
| static const CSSPropertyID firstLowPriorityProperty = static_cast<CSSPropertyID>(lastHighPriorityProperty + 1); |
| |
| 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 CSSPropertyTextEmphasisColor: |
| 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, CascadeLevel cascadeLevel, PropertyCascade::IncludedProperties includedProperties) |
| : m_cascade(matchResult, cascadeLevel, includedProperties) |
| , m_state(*this, style, WTFMove(context)) |
| { |
| } |
| |
| Builder::~Builder() = default; |
| |
| void Builder::applyAllProperties() |
| { |
| applyHighPriorityProperties(); |
| applyNonHighPriorityProperties(); |
| } |
| |
| // 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(firstHighPriorityProperty, lastHighPriorityProperty); |
| |
| m_state.updateFont(); |
| } |
| |
| void Builder::applyNonHighPriorityProperties() |
| { |
| ASSERT(!m_state.fontDirty()); |
| |
| applyCustomProperties(); |
| applyProperties(firstLowPriorityProperty, lastLowPriorityProperty); |
| applyDeferredProperties(); |
| |
| ASSERT(!m_state.fontDirty()); |
| } |
| |
| void Builder::applyDeferredProperties() |
| { |
| for (auto id : m_cascade.deferredPropertyIDs()) |
| applyCascadeProperty(m_cascade.deferredProperty(id)); |
| } |
| |
| 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.hasNormalProperty(propertyID)) |
| continue; |
| ASSERT(propertyID != CSSPropertyCustom); |
| auto& property = m_cascade.normalProperty(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 AtomString& 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); |
| |
| SetForScope levelScope(m_state.m_currentProperty, &property); |
| |
| 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 valueToApply = 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 (std::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)) { |
| SetForScope scopedLinkMatchMutation(m_state.m_linkMatch, index); |
| applyProperty(CSSPropertyCustom, valueToApply.get(), index); |
| } |
| } |
| |
| 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 valueToApply = downcast<CSSCustomPropertyValue>(*property.cssValue[index]); |
| |
| if (inCycle && std::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) |
| { |
| SetForScope levelScope(m_state.m_currentProperty, &property); |
| |
| auto applyWithLinkMatch = [&](SelectorChecker::LinkMatchMask linkMatch) { |
| if (property.cssValue[linkMatch]) { |
| SetForScope scopedLinkMatchMutation(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::applyRollbackCascadeProperty(const PropertyCascade::Property& property, SelectorChecker::LinkMatchMask linkMatchMask) |
| { |
| auto* value = property.cssValue[linkMatchMask]; |
| if (!value) |
| return; |
| |
| SetForScope levelScope(m_state.m_currentProperty, &property); |
| |
| applyProperty(property.id, *value, linkMatchMask); |
| } |
| |
| 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); |
| auto& style = m_state.style(); |
| |
| if (CSSProperty::isDirectionAwareProperty(id)) { |
| CSSPropertyID newId = CSSProperty::resolveDirectionAwareProperty(id, style.direction(), style.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 (std::holds_alternative<CSSValueID>(customPropertyValue->value())) |
| customPropertyValueID = std::get<CSSValueID>(customPropertyValue->value()); |
| auto& name = customPropertyValue->name(); |
| customPropertyRegistered = m_state.document().getCSSRegisteredCustomPropertySet().get(name); |
| } |
| |
| bool isInherit = valueToApply->isInheritValue() || customPropertyValueID == CSSValueInherit; |
| bool isInitial = valueToApply->isInitialValue() || customPropertyValueID == CSSValueInitial; |
| |
| bool isUnset = valueToApply->isUnsetValue() || customPropertyValueID == CSSValueUnset; |
| bool isRevert = valueToApply->isRevertValue() || customPropertyValueID == CSSValueRevert; |
| bool isRevertLayer = valueToApply->isRevertLayerValue() || customPropertyValueID == CSSValueRevertLayer; |
| |
| if (isRevert || isRevertLayer) { |
| // In @keyframes, 'revert-layer' rolls back the cascaded value to the author level. |
| // We can just not apply the property in order to keep the value from the base style. |
| if (isRevertLayer && m_state.m_isBuildingKeyframeStyle) |
| return; |
| |
| auto* rollbackCascade = isRevert ? ensureRollbackCascadeForRevert() : ensureRollbackCascadeForRevertLayer(); |
| |
| if (rollbackCascade) { |
| // 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 && rollbackCascade->hasCustomProperty(customPropertyValue->name())) { |
| auto property = rollbackCascade->customProperty(customPropertyValue->name()); |
| applyRollbackCascadeProperty(property, linkMatchMask); |
| return; |
| } |
| } else if (id < firstDeferredProperty) { |
| if (rollbackCascade->hasNormalProperty(id)) { |
| auto& property = rollbackCascade->normalProperty(id); |
| applyRollbackCascadeProperty(property, linkMatchMask); |
| return; |
| } |
| } else if (auto* property = rollbackCascade->lastDeferredPropertyResolvingRelated(id, style.direction(), style.writingMode())) { |
| applyRollbackCascadeProperty(*property, 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)) |
| 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)) { |
| Locker locker { paintWorklet->paintDefinitionLock() }; |
| if (auto* registration = paintWorklet->paintDefinitionMap().get(name)) { |
| for (auto& property : registration->inputProperties) |
| 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().createValue(CSSValueInherit); |
| return CSSValuePool::singleton().createValue(CSSValueInitial); |
| } |
| |
| return *variableValue; |
| } |
| |
| RefPtr<CSSValue> Builder::resolvedVariableValue(CSSPropertyID propertyID, const CSSValue& value) |
| { |
| return CSSParser(m_state.document()).parseValueWithVariableReferences(propertyID, value, m_state); |
| } |
| |
| const PropertyCascade* Builder::ensureRollbackCascadeForRevert() |
| { |
| auto rollbackCascadeLevel = m_state.m_currentProperty->cascadeLevel; |
| if (rollbackCascadeLevel == CascadeLevel::UserAgent) |
| return nullptr; |
| |
| --rollbackCascadeLevel; |
| |
| auto key = makeRollbackCascadeKey(rollbackCascadeLevel); |
| return m_rollbackCascades.ensure(key, [&] { |
| return makeUnique<const PropertyCascade>(m_cascade, rollbackCascadeLevel); |
| }).iterator->value.get(); |
| } |
| |
| const PropertyCascade* Builder::ensureRollbackCascadeForRevertLayer() |
| { |
| auto& property = *m_state.m_currentProperty; |
| auto rollbackLayerPriority = property.cascadeLayerPriority; |
| if (!rollbackLayerPriority) |
| return ensureRollbackCascadeForRevert(); |
| |
| ASSERT(property.fromStyleAttribute == FromStyleAttribute::No || property.cascadeLayerPriority == RuleSet::cascadeLayerPriorityForUnlayered); |
| |
| // Style attribute reverts to the regular author style. |
| if (property.fromStyleAttribute == FromStyleAttribute::No) |
| --rollbackLayerPriority; |
| |
| auto key = makeRollbackCascadeKey(property.cascadeLevel, property.styleScopeOrdinal, rollbackLayerPriority); |
| return m_rollbackCascades.ensure(key, [&] { |
| return makeUnique<const PropertyCascade>(m_cascade, property.cascadeLevel, property.styleScopeOrdinal, rollbackLayerPriority); |
| }).iterator->value.get(); |
| } |
| |
| auto Builder::makeRollbackCascadeKey(CascadeLevel cascadeLevel, ScopeOrdinal scopeOrdinal, CascadeLayerPriority cascadeLayerPriority) -> RollbackCascadeKey |
| { |
| return { static_cast<unsigned>(cascadeLevel), static_cast<unsigned>(scopeOrdinal), static_cast<unsigned>(cascadeLayerPriority) }; |
| } |
| |
| } |
| } |