| /* |
| * 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 "CSSCustomPropertySyntax.h" |
| #include "CSSCustomPropertyValue.h" |
| #include "CSSFontSelector.h" |
| #include "CSSPaintImageValue.h" |
| #include "CSSPropertyParser.h" |
| #include "CSSPropertyParserConsumer+Ident.h" |
| #include "CSSRegisteredCustomProperty.h" |
| #include "CSSShorthandSubstitutionValue.h" |
| #include "CSSValuePair.h" |
| #include "CSSValuePool.h" |
| #include "CSSWideKeyword.h" |
| #include "ComputedStyleDependencies.h" |
| #include "Document.h" |
| #include "HTMLElement.h" |
| #include "PaintWorkletGlobalScope.h" |
| #include "Settings.h" |
| #include "StyleAdjuster.h" |
| #include "StyleBuilderGenerated.h" |
| #include "StyleBuilderStateInlines.h" |
| #include "StyleComputedStyle+GettersInlines.h" |
| #include "StyleComputedStyle+InitialInlines.h" |
| #include "StyleComputedStyle+SettersInlines.h" |
| #include "StyleCustomProperty.h" |
| #include "StyleCustomPropertyData.h" |
| #include "StyleCustomPropertyRegistry.h" |
| #include "StyleFontSizeFunctions.h" |
| #include "StyleLocalPropertyRegistry.h" |
| #include "StylePropertyShorthand.h" |
| #include "StyleSubstitutionResolver.h" |
| #include <wtf/SetForScope.h> |
| #include <wtf/TZoneMallocInlines.h> |
| |
| namespace WebCore { |
| namespace Style { |
| |
| WTF_MAKE_TZONE_ALLOCATED_IMPL(Builder); |
| |
| static const CSSPropertyID firstLowPriorityProperty = static_cast<CSSPropertyID>(lastHighPriorityProperty + 1); |
| |
| inline bool NODELETE 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; |
| } |
| |
| static const StyleProperties* NODELETE positionTryFallbackProperties(const BuilderContext& context) |
| { |
| return context.positionTryFallback ? context.positionTryFallback->properties.get() : nullptr; |
| } |
| |
| static bool isInheritedCustomProperty(const CSSRegisteredCustomProperty* registered) |
| { |
| return !registered || registered->inherits; |
| } |
| |
| Builder::Builder(Style::ComputedStyle& style, BuilderContext&& context, const MatchResult& matchResult, PropertyCascade::IncludedProperties&& includedProperties, const HashMap<AnimatableCSSProperty, EnumSet<PropertyCascade::AnimationSource>>* animatedProperties) |
| : m_cascade(matchResult, WTF::move(includedProperties), animatedProperties, positionTryFallbackProperties(context)) |
| , m_state(BuilderState::create(style, WTF::move(context))) |
| { |
| } |
| |
| Builder::~Builder() = default; |
| |
| void Builder::applyAllProperties() |
| { |
| if (m_cascade.isEmpty()) |
| return; |
| |
| applyTopPriorityProperties(); |
| applyHighPriorityProperties(); |
| applyNonHighPriorityProperties(); |
| |
| adjustAfterApplying(); |
| } |
| |
| // The values are computed values from the parent highlight style, so this doesn't depend on the |
| // high priority properties having been applied. |
| void Builder::applyHighlightInheritance() |
| { |
| ASSERT(m_state->isBuildingHighlightStyle()); |
| |
| BuilderGenerated::applyHighlightInheritAllProperties(m_state); |
| } |
| |
| // Top priority properties affect resolution of high priority properties. |
| void Builder::applyTopPriorityProperties() |
| { |
| if (m_cascade.applyLowPriorityOnly()) |
| return; |
| |
| applyProperties(firstTopPriorityProperty, lastTopPriorityProperty); |
| m_state->adjustStyleForInterCharacterRuby(); |
| } |
| |
| // High priority properties may affect resolution of other properties (they are mostly font related). |
| void Builder::applyHighPriorityProperties() |
| { |
| if (m_cascade.applyLowPriorityOnly()) |
| return; |
| |
| applyProperties(firstHighPriorityProperty, lastHighPriorityProperty); |
| m_state->updateFont(); |
| // This needs to apply before other properties for the `lh` unit, but after updating the font. |
| applyProperties(CSSPropertyLineHeight, CSSPropertyLineHeight); |
| } |
| |
| void Builder::applyNonHighPriorityProperties() |
| { |
| ASSERT(!m_state->fontDirty()); |
| |
| applyProperties(firstLowPriorityProperty, lastLowPriorityProperty); |
| applyLogicalGroupProperties(); |
| // Any referenced custom properties are already resolved. This will resolve the remaining ones. |
| applyCustomProperties(); |
| |
| ASSERT(!m_state->fontDirty()); |
| } |
| |
| void Builder::adjustAfterApplying() |
| { |
| Adjuster::adjustFromBuilder(m_state->style()); |
| } |
| |
| void Builder::applyLogicalGroupProperties() |
| { |
| // Properties in a logical property group are applied in author specified order which is maintained separately for them. |
| for (auto id : m_cascade.logicalGroupPropertyIDs()) |
| applyCascadeProperty(m_cascade.logicalGroupProperty(id)); |
| } |
| |
| void Builder::applyProperties(int firstProperty, int lastProperty) |
| { |
| if (m_cascade.customProperties().isEmpty()) [[likely]] |
| return applyPropertiesImpl<CustomPropertyCycleTracking::Disabled>(firstProperty, lastProperty); |
| |
| return applyPropertiesImpl<CustomPropertyCycleTracking::Enabled>(firstProperty, lastProperty); |
| } |
| |
| template<Builder::CustomPropertyCycleTracking trackCycles> |
| inline void Builder::applyPropertiesImpl(int firstProperty, int lastProperty) |
| { |
| auto applyProperty = [&](size_t index) ALWAYS_INLINE_LAMBDA { |
| CSSPropertyID propertyID = static_cast<CSSPropertyID>(index); |
| ASSERT(propertyID != CSSPropertyCustom); |
| auto& property = m_cascade.normalProperty(propertyID); |
| |
| if constexpr (trackCycles == CustomPropertyCycleTracking::Enabled) { |
| m_state->m_inProgressProperties.set(propertyID); |
| applyCascadeProperty(property); |
| m_state->m_inProgressProperties.clear(propertyID); |
| return; |
| } |
| |
| // If we don't have any custom properties, then there can't be any cycles. |
| applyCascadeProperty(property); |
| }; |
| |
| if (m_cascade.propertyIsPresent().size() == static_cast<size_t>(lastProperty + 1)) { |
| m_cascade.propertyIsPresent().forEachSetBit(firstProperty, applyProperty); |
| return; |
| } |
| |
| for (int id = firstProperty; id <= lastProperty; ++id) { |
| CSSPropertyID propertyID = static_cast<CSSPropertyID>(id); |
| if (!m_cascade.hasNormalProperty(propertyID)) |
| continue; |
| applyProperty(id); |
| } |
| } |
| |
| void Builder::applyCustomProperties() |
| { |
| for (auto& [name, value] : m_cascade.customProperties()) { |
| if (m_state->m_appliedCustomProperties.contains(name)) |
| continue; |
| applyCustomPropertyImpl(name, value); |
| } |
| } |
| |
| void Builder::applyCustomProperty(const AtomString& name) |
| { |
| if (m_state->m_appliedCustomProperties.contains(name)) |
| return; |
| |
| auto iterator = m_cascade.customProperties().find(name); |
| if (iterator == m_cascade.customProperties().end()) { |
| // A property missing from this cascade is resolved against the function evaluation context, if any. |
| if (m_state->callingContextBuilder()) |
| applyCustomPropertyFromCallingContext(name); |
| return; |
| } |
| |
| applyCustomPropertyImpl(name, iterator->value); |
| } |
| |
| // A custom property absent from a function's cascade is either declared by this function (a parameter or |
| // local: it shadows inheritance and resolves to its registered value) or one inherited from the calling |
| // context (resolved lazily). An enclosing function's parameter is registered here but not declared here, |
| // so it inherits. https://drafts.csswg.org/css-mixins/#evaluating-custom-functions |
| void Builder::applyCustomPropertyFromCallingContext(const AtomString& name) |
| { |
| auto* callingContextBuilder = m_state->callingContextBuilder(); |
| ASSERT(callingContextBuilder); |
| |
| auto* localRegistry = m_state->localPropertyRegistry(); |
| if (localRegistry && localRegistry->declares(name)) |
| applyCustomProperty(name, CSSWideKeyword::Initial); |
| else { |
| callingContextBuilder->applyCustomProperty(name); |
| if (RefPtr value = callingContextBuilder->state().style().customPropertyValue(name)) |
| m_state->style().setCustomPropertyValue(value.releaseNonNull(), isInheritedCustomProperty(m_state->registeredProperty(name))); |
| } |
| m_state->m_appliedCustomProperties.add(name); |
| } |
| |
| void Builder::applyCustomPropertyImpl(const AtomString& name, const PropertyCascade::Property& property) |
| { |
| if (!property.cssValue[SelectorChecker::MatchDefault]) |
| return; |
| |
| Ref customPropertyValue = downcast<CSSCustomPropertyValue>(*property.cssValue[SelectorChecker::MatchDefault]); |
| |
| // https://drafts.csswg.org/css-values-5/#guarded |
| auto guard = m_state->guardSubstitutionContext({ SubstitutionContext::Type::Property, name }); |
| |
| if (guard.isCyclicContext()) |
| return; |
| |
| auto createInvalidOrUnset = [&] -> CustomPropertyOrKeyword { |
| // https://drafts.csswg.org/css-variables-2/#invalid-variables |
| auto* registered = m_state->registeredProperty(name); |
| // The property is a non-registered custom property: |
| // The property is a registered custom property with universal syntax: |
| // The computed value is the guaranteed-invalid value. |
| if (!registered || registered->syntax.isUniversal()) |
| return CustomProperty::createForGuaranteedInvalid(name); |
| // For a custom function's hypothetical element, an invalid value resolves to the guaranteed-invalid |
| // value (the parameter overrides inheritance), not unset. |
| // https://drafts.csswg.org/css-mixins/#evaluating-custom-functions |
| if (m_state->callingContextBuilder()) |
| return CustomProperty::createForGuaranteedInvalid(name); |
| // Otherwise: |
| // ...as if the property’s value had been specified as the unset keyword. |
| return CSSWideKeyword::Unset; |
| }; |
| |
| BuilderStatePropertyScope levelScope(m_state, &property); |
| SetForScope scopedLinkMatchMutation(m_state->m_linkMatch, SelectorChecker::MatchDefault); |
| |
| auto resolvedValue = resolveCustomPropertyValue(customPropertyValue.get()); |
| |
| // If the context became cyclic during resolution, the value is invalid. |
| if (!resolvedValue || guard.isCyclicContext()) |
| resolvedValue = createInvalidOrUnset(); |
| |
| applyCustomProperty(name, WTF::move(*resolvedValue)); |
| |
| m_state->m_appliedCustomProperties.add(name); |
| } |
| |
| inline void Builder::applyCascadeProperty(const PropertyCascade::Property& property) |
| { |
| BuilderStatePropertyScope levelScope(m_state, &property); |
| |
| auto applyWithLinkMatch = [&](SelectorChecker::LinkMatchMask linkMatch) { |
| if (property.cssValue[linkMatch]) { |
| SetForScope scopedLinkMatchMutation(m_state->m_linkMatch, linkMatch); |
| SUPPRESS_UNCOUNTED_ARG applyProperty(property.id, *property.cssValue[linkMatch], linkMatch, property.origins[linkMatch]); |
| } |
| }; |
| |
| applyWithLinkMatch(SelectorChecker::MatchDefault); |
| |
| if (m_state->style().insideLink() == InsideLink::NotInside) |
| return; |
| |
| applyWithLinkMatch(SelectorChecker::MatchLink); |
| applyWithLinkMatch(SelectorChecker::MatchVisited); |
| |
| m_state->m_linkMatch = SelectorChecker::MatchDefault; |
| } |
| |
| bool Builder::applyRollbackCascadeProperty(const PropertyCascade& rollbackCascade, CSSPropertyID propertyID, SelectorChecker::LinkMatchMask linkMatchMask) |
| { |
| ASSERT(propertyID != CSSPropertyCustom); |
| |
| auto* rollbackProperty = [&]() -> const PropertyCascade::Property* { |
| if (propertyID < firstLogicalGroupProperty) { |
| if (rollbackCascade.hasNormalProperty(propertyID)) |
| return &rollbackCascade.normalProperty(propertyID); |
| return nullptr; |
| } |
| return rollbackCascade.lastPropertyResolvingLogicalPropertyPair(propertyID, m_state->style().writingMode()); |
| }(); |
| |
| if (!rollbackProperty) |
| return false; |
| |
| if (RefPtr value = rollbackProperty->cssValue[linkMatchMask]) { |
| BuilderStatePropertyScope levelScope(m_state, rollbackProperty); |
| applyProperty(propertyID, *value, linkMatchMask, rollbackProperty->origin); |
| } |
| return true; |
| } |
| |
| bool Builder::applyRollbackCascadeCustomProperty(const PropertyCascade& rollbackCascade, const AtomString& name) |
| { |
| auto iterator = rollbackCascade.customProperties().find(name); |
| if (iterator == rollbackCascade.customProperties().end()) |
| return false; |
| |
| auto& rollbackProperty = iterator->value; |
| if (RefPtr value = rollbackProperty.cssValue[SelectorChecker::MatchDefault]) { |
| Ref customPropertyValue = downcast<CSSCustomPropertyValue>(*value); |
| |
| BuilderStatePropertyScope levelScope(m_state, &rollbackProperty); |
| auto resolvedValue = resolveCustomPropertyValue(customPropertyValue); |
| if (!resolvedValue) |
| resolvedValue = CustomProperty::createForGuaranteedInvalid(name); |
| |
| applyCustomProperty(name, WTF::move(*resolvedValue)); |
| } |
| return true; |
| } |
| |
| void Builder::applyProperty(CSSPropertyID id, CSSValue& value, SelectorChecker::LinkMatchMask linkMatchMask, PropertyCascade::Origin cascadeOrigin) |
| { |
| ASSERT_WITH_MESSAGE(!isShorthand(id), "Shorthand property id = %d wasn't expanded at parsing time", id); |
| ASSERT_WITH_MESSAGE(id != CSSPropertyCustom, "Custom property should be handled by applyCustomProperty"); |
| |
| auto& style = m_state->style(); |
| |
| id = CSSProperty::resolveDirectionAwareProperty(id, style.writingMode()); |
| |
| auto valueToApply = resolveSubstitutionFunctions(id, value); |
| |
| if (m_state->positionTryFallback()) |
| id = AnchorPositionEvaluator::resolvePositionTryFallbackProperty(id, style.writingMode(), *m_state->positionTryFallback()); |
| |
| auto valueID = WebCore::valueID(valueToApply.get()); |
| auto valueType = [&] { |
| if (valueID == CSSValueInherit) |
| return ApplyValueType::Inherit; |
| if (valueID == CSSValueInitial) |
| return ApplyValueType::Initial; |
| return ApplyValueType::Value; |
| }(); |
| |
| bool isUnset = valueID == CSSValueUnset; |
| bool isRevert = valueID == CSSValueRevert; |
| bool isRevertLayer = valueID == CSSValueRevertLayer; |
| bool isRevertRule = valueID == CSSValueRevertRule; |
| bool isAnyRevert = isRevert || isRevertLayer || isRevertRule; |
| |
| if (isAnyRevert) { |
| // In @keyframes, 'revert-layer' and 'revert-rule' roll 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 || isRevertRule) && m_state->m_isBuildingKeyframeStyle) { |
| m_state->m_hasRevertRuleOrLayerInKeyframeStyle = true; |
| return; |
| } |
| |
| auto* rollbackCascade = [&] -> const PropertyCascade* { |
| if (isRevert) |
| return ensureRollbackCascadeForRevert(); |
| if (isRevertLayer) |
| return ensureRollbackCascadeForRevertLayer(); |
| return ensureRollbackCascadeForRevertRule(); |
| }(); |
| |
| 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. |
| SetForScope rollbackScope(m_state->m_currentRollbackCascade, rollbackCascade); |
| if (applyRollbackCascadeProperty(*rollbackCascade, id, linkMatchMask)) |
| return; |
| } |
| } |
| |
| auto isInheritedProperty = [&] { |
| return CSSProperty::isInheritedProperty(id); |
| }; |
| |
| // https://drafts.csswg.org/css-pseudo-4/#highlight-cascade |
| // In a highlight pseudo-element the properties that inherit from the corresponding highlight |
| // pseudo-element of the originating element's parent do so whether or not they are inherited |
| // properties. The ones that apply without inheriting through the chain, fill and stroke, are |
| // inherited properties, so unset means inherit for them too. |
| auto unsetValueType = [&] { |
| if (m_state->isBuildingHighlightStyle()) |
| return ApplyValueType::Inherit; |
| // https://drafts.csswg.org/css-cascade-4/#inherit-initial |
| // The unset CSS-wide keyword acts as either inherit or initial, depending on whether the property is inherited or not. |
| return isInheritedProperty() ? ApplyValueType::Inherit : ApplyValueType::Initial; |
| }; |
| |
| if (isUnset || isAnyRevert) |
| valueType = unsetValueType(); |
| |
| if (!m_state->applyPropertyToRegularStyle() && !isValidVisitedLinkProperty(id)) { |
| // Limit the properties that can be applied to only the ones honored by :visited. |
| return; |
| } |
| |
| if (valueType == ApplyValueType::Inherit && !isInheritedProperty()) |
| style.setHasExplicitlyInheritedProperties(); |
| |
| if (RefPtr paintImageValue = dynamicDowncast<CSSPaintImageValue>(valueToApply.get())) { |
| auto& name = paintImageValue->name().value; |
| if (RefPtr 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); |
| } |
| } |
| } |
| |
| if (id == CSSPropertyPageSize && valueType == ApplyValueType::Value) [[unlikely]] { |
| applyPageSizeDescriptor(valueToApply.get()); |
| return; |
| } |
| |
| auto apply = [&](ApplyValueType valueType) { |
| if (m_state->isBuildingHighlightStyle()) { |
| BuilderGenerated::applyHighlightProperty(id, m_state, valueToApply.get(), valueType); |
| return; |
| } |
| BuilderGenerated::applyProperty(id, m_state, valueToApply.get(), valueType); |
| }; |
| |
| apply(valueType); |
| |
| if (!isAnyRevert) |
| m_state->disableNativeAppearanceIfNeeded(id, cascadeOrigin); |
| |
| if (!isUnset && !isAnyRevert && m_state->isCurrentPropertyInvalidAtComputedValueTime()) { |
| // https://drafts.csswg.org/css-variables-2/#invalid-variables |
| // A declaration can be invalid at computed-value time if... |
| // When this happens, the computed value is one of the following... |
| // Otherwise: Either the property’s inherited value or its initial value depending on whether the property |
| // is inherited or not, respectively, as if the property’s value had been specified as the unset keyword |
| apply(unsetValueType()); |
| } |
| } |
| |
| void Builder::applyCustomProperty(const AtomString& name, CustomPropertyOrKeyword&& parsedCustomProperty) |
| { |
| auto& style = m_state->style(); |
| |
| auto registeredCustomProperty = m_state->registeredProperty(name); |
| |
| auto applyValue = [&](Ref<const CustomProperty>&& valueToApply) { |
| state().style().setCustomPropertyValue(WTF::move(valueToApply), isInheritedCustomProperty(registeredCustomProperty)); |
| }; |
| |
| auto applyInitial = [&] { |
| if (registeredCustomProperty && registeredCustomProperty->initialValue) { |
| applyValue(*registeredCustomProperty->initialValue); |
| return; |
| } |
| applyValue(CustomProperty::createForGuaranteedInvalid(name)); |
| }; |
| |
| auto applyInherit = [&] { |
| // For a custom function's hypothetical element, the inherited value comes from the calling |
| // context, whose custom properties are resolved lazily. Ensure this one is resolved there. |
| if (auto* callingContextBuilder = m_state->callingContextBuilder()) |
| callingContextBuilder->applyCustomProperty(name); |
| RefPtr parentValue = protect(state().parentStyle().inheritedCustomProperties())->get(name); |
| if (parentValue && isInheritedCustomProperty(registeredCustomProperty)) { |
| applyValue(*parentValue); |
| return; |
| } |
| if (RefPtr nonInheritedParentValue = protect(state().parentStyle().nonInheritedCustomProperties())->get(name)) { |
| applyValue(*nonInheritedParentValue); |
| return; |
| } |
| applyInitial(); |
| }; |
| |
| return WTF::switchOn(WTF::move(parsedCustomProperty), |
| [&](CSSWideKeyword&& keyword) { |
| ApplyValueType valueType = ApplyValueType::Value; |
| bool isRevert = false; |
| bool isRevertLayer = false; |
| bool isRevertRule = false; |
| |
| auto unsetValueType = [&] { |
| // https://drafts.csswg.org/css-cascade-4/#inherit-initial |
| // The unset CSS-wide keyword acts as either inherit or initial, depending on whether the property is inherited or not. |
| return isInheritedCustomProperty(registeredCustomProperty) ? ApplyValueType::Inherit : ApplyValueType::Initial; |
| }; |
| |
| switch (keyword) { |
| case CSSWideKeyword::Initial: |
| valueType = ApplyValueType::Initial; |
| break; |
| case CSSWideKeyword::Inherit: |
| valueType = ApplyValueType::Inherit; |
| break; |
| case CSSWideKeyword::Unset: |
| valueType = unsetValueType(); |
| break; |
| case CSSWideKeyword::Revert: |
| isRevert = true; |
| valueType = unsetValueType(); |
| break; |
| case CSSWideKeyword::RevertLayer: |
| isRevertLayer = true; |
| valueType = unsetValueType(); |
| break; |
| case CSSWideKeyword::RevertRule: |
| isRevertRule = true; |
| valueType = unsetValueType(); |
| break; |
| } |
| |
| bool isAnyRevert = isRevert || isRevertLayer || isRevertRule; |
| if (isAnyRevert) { |
| // In @keyframes, 'revert-layer' and 'revert-rule' roll 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 || isRevertRule) && m_state->m_isBuildingKeyframeStyle) { |
| m_state->m_hasRevertRuleOrLayerInKeyframeStyle = true; |
| return; |
| } |
| |
| auto* rollbackCascade = [&] -> const PropertyCascade* { |
| if (isRevert) |
| return ensureRollbackCascadeForRevert(); |
| if (isRevertLayer) |
| return ensureRollbackCascadeForRevertLayer(); |
| return ensureRollbackCascadeForRevertRule(); |
| }(); |
| |
| 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. |
| SetForScope rollbackScope(m_state->m_currentRollbackCascade, rollbackCascade); |
| if (applyRollbackCascadeCustomProperty(*rollbackCascade, name)) |
| return; |
| } |
| } |
| |
| if (!m_state->applyPropertyToRegularStyle()) { |
| // Limit the properties that can be applied to only the ones honored by :visited. |
| return; |
| } |
| |
| if (valueType == ApplyValueType::Inherit && !isInheritedCustomProperty(registeredCustomProperty)) |
| style.setHasExplicitlyInheritedProperties(); |
| |
| switch (valueType) { |
| case ApplyValueType::Initial: |
| applyInitial(); |
| break; |
| case ApplyValueType::Inherit: |
| applyInherit(); |
| break; |
| case ApplyValueType::Value: |
| ASSERT_NOT_REACHED(); |
| break; |
| }; |
| }, |
| [&](Ref<const CustomProperty>&& resolved) { |
| if (!m_state->applyPropertyToRegularStyle()) { |
| // Limit the properties that can be applied to only the ones honored by :visited. |
| return; |
| } |
| applyValue(WTF::move(resolved)); |
| } |
| ); |
| } |
| |
| Ref<CSSValue> Builder::resolveSubstitutionFunctions(CSSPropertyID propertyID, CSSValue& value) |
| { |
| if (!value.hasSubstitutionFunctions()) |
| return value; |
| |
| SubstitutionResolver substitutionResolver(*this); |
| |
| auto variableValue = [&]() -> RefPtr<CSSValue> { |
| if (auto* substitution = dynamicDowncast<CSSShorthandSubstitutionValue>(value)) |
| return substitutionResolver.substituteAndParseShorthand(*substitution, propertyID); |
| return substitutionResolver.substituteAndParse(downcast<CSSSubstitutionValue>(value), propertyID); |
| }(); |
| |
| // https://drafts.csswg.org/css-variables-2/#invalid-variables |
| // ...as if the property’s value had been specified as the unset keyword. |
| if (!variableValue || m_state->m_invalidAtComputedValueTimeProperties.get(propertyID)) |
| return CSSKeywordValue::create(CSSValueUnset); |
| |
| return *variableValue; |
| } |
| |
| RefPtr<const CustomProperty> Builder::resolveCustomPropertyForContainerQueries(const CSSCustomPropertyValue& value) |
| { |
| m_state->setIsResolvingContainerQueries(); |
| |
| auto resolvedValue = resolveCustomPropertyValue(const_cast<CSSCustomPropertyValue&>(value)); |
| if (!resolvedValue) |
| return CustomProperty::createForGuaranteedInvalid(value.name()); |
| |
| return WTF::switchOn(*resolvedValue, |
| [&](const CSSWideKeyword& keyword) -> RefPtr<const CustomProperty> { |
| auto name = value.name(); |
| auto* registered = m_state->registeredProperty(name); |
| bool isInherited = isInheritedCustomProperty(registered); |
| |
| auto initial = [&]() -> RefPtr<const CustomProperty> { |
| if (registered) |
| return registered->initialValue; |
| return CustomProperty::createForGuaranteedInvalid(name); |
| }; |
| |
| auto inherit = [&]() -> RefPtr<const CustomProperty> { |
| RefPtr parentValue = isInherited |
| ? protect(m_state->parentStyle().inheritedCustomProperties())->get(name) |
| : protect(m_state->parentStyle().nonInheritedCustomProperties())->get(name); |
| if (parentValue) |
| return parentValue; |
| |
| return initial(); |
| }; |
| |
| switch (keyword) { |
| case CSSWideKeyword::Initial: |
| return initial(); |
| case CSSWideKeyword::Inherit: |
| return inherit(); |
| case CSSWideKeyword::Unset: |
| return isInherited ? inherit() : initial(); |
| case CSSWideKeyword::Revert: |
| case CSSWideKeyword::RevertLayer: |
| case CSSWideKeyword::RevertRule: |
| // https://drafts.csswg.org/css-conditional-5/#style-container |
| // "Cascade-dependent keywords, such as revert and revert-layer, are invalid as values in a style feature, |
| // and cause the container style query to be false." |
| return nullptr; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| return nullptr; |
| }, |
| [](const Ref<const CustomProperty>& resolvedValue) -> RefPtr<const CustomProperty> { |
| return resolvedValue.copyRef(); |
| } |
| ); |
| } |
| |
| std::optional<Builder::CustomPropertyOrKeyword> Builder::resolveFunctionResult() |
| { |
| // resolveFunctionResult is only called while evaluating a custom function. |
| ASSERT(m_state->callingContextBuilder()); |
| |
| if (!m_cascade.hasNormalProperty(CSSPropertyResult)) |
| return { }; |
| |
| BuilderStatePropertyScope resultScope(m_state, &m_cascade.functionResultProperty()); |
| |
| // Apply all local variables, not just those reached by result. A local can be in a cycle with the |
| // function even when result never references it. |
| // https://drafts.csswg.org/css-mixins/#evaluating-custom-functions |
| applyCustomProperties(); |
| |
| RefPtr resultValue = dynamicDowncast<CSSCustomPropertyValue>(m_cascade.functionResultProperty().cssValue[SelectorChecker::MatchDefault]); |
| if (!resultValue) |
| return { }; |
| |
| // The caller (custom function evaluation) handles a CSS-wide keyword result, which per spec is |
| // left unresolved. https://drafts.csswg.org/css-mixins/#evaluating-custom-functions |
| return resolveCustomPropertyValue(*resultValue); |
| } |
| |
| std::optional<Builder::CustomPropertyOrKeyword> Builder::resolveCustomPropertyValue(CSSCustomPropertyValue& value) |
| { |
| auto name = value.name(); |
| |
| if (auto keyword = value.tryCSSWideKeyword()) |
| return { { *keyword } }; |
| |
| auto* registered = m_state->registeredProperty(name); |
| auto preResolved = switchOn(value.value(), |
| [&](const Ref<CSSSubstitutionValue>&) -> std::optional<CustomPropertyOrKeyword> { |
| return { }; |
| }, |
| [&](const Ref<CSSVariableData>& data) -> std::optional<CustomPropertyOrKeyword> { |
| if (!registered) |
| return { { CustomProperty::createForVariableData(name, data.copyRef()) } }; |
| return { }; |
| }, |
| [&](const CSSWideKeyword& keyword) -> std::optional<CustomPropertyOrKeyword> { |
| if (!registered) |
| return { { keyword } }; |
| return { }; |
| } |
| ); |
| if (preResolved) |
| return preResolved; |
| |
| auto resolvedData = switchOn(value.value(), |
| [&](const Ref<CSSSubstitutionValue>& substitutionValue) -> RefPtr<CSSVariableData> { |
| SubstitutionResolver substitutionResolver(*this, registered); |
| return substitutionResolver.substitute(substitutionValue.get()); |
| }, |
| [&](const Ref<CSSVariableData>& data) -> RefPtr<CSSVariableData> { |
| return data.ptr(); |
| }, |
| [&](const CSSWideKeyword&) -> RefPtr<CSSVariableData> { |
| return nullptr; |
| } |
| ); |
| |
| if (!resolvedData) |
| return { }; |
| |
| // A CSS-wide keyword can surface after substitution, e.g. from a var() fallback or a custom |
| // function result. https://drafts.csswg.org/css-mixins/#evaluating-custom-functions |
| auto keywordTokens = resolvedData->tokenRange(); |
| keywordTokens.consumeWhitespace(); |
| if (auto keyword = CSSPropertyParserHelpers::consumeCSSWideKeyword(keywordTokens)) |
| return { { *keyword } }; |
| |
| if (!registered) |
| return { { CustomProperty::createForVariableData(name, *resolvedData) } }; |
| |
| return computeCustomPropertyValueForSyntax(name, registered->syntax, *resolvedData); |
| } |
| |
| // Parses an already-substituted value against a syntax and computes it on this builder's element. |
| // Shared by registered custom properties and by custom function parameters. |
| std::optional<Builder::CustomPropertyOrKeyword> Builder::computeCustomPropertyValueForSyntax(const AtomString& name, const CSSCustomPropertySyntax& syntax, const CSSVariableData& resolvedData) |
| { |
| auto dependencies = CSSPropertyParser::collectParsedCustomPropertyValueDependencies(syntax, resolvedData.tokens(), resolvedData.context()); |
| |
| // https://drafts.css-houdini.org/css-properties-values-api/#dependency-cycles |
| bool hasCycles = false; |
| bool isFontDependent = false; |
| |
| auto checkDependencies = [&](auto& propertyDependencies) { |
| for (auto property : propertyDependencies) { |
| if (m_state->m_inProgressProperties.get(property)) { |
| m_state->m_invalidAtComputedValueTimeProperties.set(property); |
| hasCycles = true; |
| } |
| if (property == CSSPropertyFontSize) |
| isFontDependent = true; |
| } |
| }; |
| |
| checkDependencies(dependencies.properties); |
| |
| if (m_state->element() == m_state->document().documentElement()) |
| checkDependencies(dependencies.rootProperties); |
| |
| if (hasCycles) |
| return { }; |
| |
| if (isFontDependent) |
| m_state->updateFont(); |
| |
| auto isAttrTainted = resolvedData.isAttrTainted(); |
| |
| // https://drafts.csswg.org/css-values-5/#attr-security |
| // A registered custom property with <url> or <image> syntax resolved from attr()-tainted data is IACVT. |
| if (isAttrTainted == IsAttrTainted::Yes) { |
| for (auto& component : syntax.definition) { |
| if (component.type == CSSCustomPropertySyntax::Type::URL || component.type == CSSCustomPropertySyntax::Type::Image) |
| return { }; |
| } |
| } |
| |
| return CSSPropertyParser::parseTypedCustomPropertyValue(name, syntax, resolvedData.tokens(), m_state, resolvedData.context(), isAttrTainted); |
| } |
| |
| void Builder::applyPageSizeDescriptor(CSSValue& value) |
| { |
| m_state->style().setPageSize(Style::ComputedStyle::initialPageSize()); |
| |
| auto convertedPageSize = toStyleFromCSSValue<PageSize>(m_state, value); |
| |
| if (m_state->isCurrentPropertyInvalidAtComputedValueTime()) |
| return; |
| |
| m_state->style().setPageSize(WTF::move(convertedPageSize)); |
| } |
| |
| const PropertyCascade* Builder::ensureRollbackCascadeForRevert() |
| { |
| auto rollbackOrigin = m_state->m_currentProperty->origin; |
| |
| switch (rollbackOrigin) { |
| case PropertyCascade::Origin::PositionFallback: |
| // "Similar to the Animation Origin, use of the revert value acts as if the property was part of the Author Origin." |
| // https://drafts.csswg.org/css-anchor-position-1/#fallback-rule |
| case PropertyCascade::Origin::Author: |
| rollbackOrigin = PropertyCascade::Origin::User; |
| break; |
| case PropertyCascade::Origin::User: |
| rollbackOrigin = PropertyCascade::Origin::UserAgent; |
| break; |
| case PropertyCascade::Origin::UserAgent: |
| return nullptr; |
| } |
| |
| auto& parentCascade = parentCascadeForRollback(); |
| auto key = makeRollbackCascadeKey(parentCascade, rollbackOrigin); |
| return m_rollbackCascades.ensure(key, [&] { |
| return makeUnique<const PropertyCascade>(m_cascade, rollbackOrigin); |
| }).iterator->value.get(); |
| } |
| |
| const PropertyCascade* Builder::ensureRollbackCascadeForRevertLayer() |
| { |
| const 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& parentCascade = parentCascadeForRollback(); |
| auto key = makeRollbackCascadeKey(parentCascade, property.origin, property.styleScopeOrdinal, rollbackLayerPriority); |
| |
| return m_rollbackCascades.ensure(key, [&] { |
| return makeUnique<const PropertyCascade>(parentCascade, property.origin, property.styleScopeOrdinal, rollbackLayerPriority); |
| }).iterator->value.get(); |
| } |
| |
| const PropertyCascade* Builder::ensureRollbackCascadeForRevertRule() |
| { |
| auto& parentCascade = parentCascadeForRollback(); |
| auto key = makeRollbackCascadeKeyForRevertRule(parentCascade); |
| |
| return m_rollbackCascades.ensure(key, [&] { |
| return makeUnique<const PropertyCascade>(parentCascade, PropertyCascade::RevertRule); |
| }).iterator->value.get(); |
| } |
| |
| const PropertyCascade& Builder::parentCascadeForRollback() |
| { |
| return m_state->m_currentRollbackCascade ? *m_state->m_currentRollbackCascade : m_cascade; |
| } |
| |
| auto Builder::makeRollbackCascadeKey(const PropertyCascade& parentCascade, PropertyCascade::Origin cascadeOrigin, ScopeOrdinal scopeOrdinal, CascadeLayerPriority cascadeLayerPriority) -> RollbackCascadeKey |
| { |
| return { &parentCascade, static_cast<unsigned>(cascadeOrigin), static_cast<unsigned>(scopeOrdinal), static_cast<unsigned>(cascadeLayerPriority), false }; |
| } |
| |
| auto Builder::makeRollbackCascadeKeyForRevertRule(const PropertyCascade& parentCascade) -> RollbackCascadeKey |
| { |
| return { &parentCascade, 0, 0, 0, true }; |
| } |
| |
| } |
| } |