| /* |
| * Copyright (C) 2019 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: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "PropertyCascade.h" |
| |
| #include "CSSPaintImageValue.h" |
| #include "CSSPrimitiveValueMappings.h" |
| #include "CSSValuePool.h" |
| #include "PaintWorkletGlobalScope.h" |
| #include "StyleBuilderGenerated.h" |
| #include "StylePropertyShorthand.h" |
| |
| namespace WebCore { |
| namespace Style { |
| |
| static inline bool shouldApplyPropertyInParseOrder(CSSPropertyID propertyID) |
| { |
| switch (propertyID) { |
| case CSSPropertyWebkitBackgroundClip: |
| case CSSPropertyBackgroundClip: |
| case CSSPropertyWebkitBackgroundOrigin: |
| case CSSPropertyBackgroundOrigin: |
| case CSSPropertyWebkitBackgroundSize: |
| case CSSPropertyBackgroundSize: |
| case CSSPropertyWebkitBorderImage: |
| case CSSPropertyBorderImage: |
| case CSSPropertyBorderImageSlice: |
| case CSSPropertyBorderImageSource: |
| case CSSPropertyBorderImageOutset: |
| case CSSPropertyBorderImageRepeat: |
| case CSSPropertyBorderImageWidth: |
| case CSSPropertyWebkitBoxShadow: |
| case CSSPropertyBoxShadow: |
| case CSSPropertyWebkitTextDecoration: |
| case CSSPropertyTextDecorationLine: |
| case CSSPropertyTextDecorationStyle: |
| case CSSPropertyTextDecorationColor: |
| case CSSPropertyTextDecorationSkip: |
| case CSSPropertyTextUnderlinePosition: |
| case CSSPropertyTextUnderlineOffset: |
| case CSSPropertyTextDecorationThickness: |
| case CSSPropertyTextDecoration: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // https://www.w3.org/TR/css-pseudo-4/#marker-pseudo (Editor's Draft, 25 July 2017) |
| static inline bool isValidMarkerStyleProperty(CSSPropertyID id) |
| { |
| switch (id) { |
| case CSSPropertyColor: |
| case CSSPropertyFontFamily: |
| case CSSPropertyFontFeatureSettings: |
| case CSSPropertyFontSize: |
| case CSSPropertyFontStretch: |
| case CSSPropertyFontStyle: |
| case CSSPropertyFontSynthesis: |
| case CSSPropertyFontVariantAlternates: |
| case CSSPropertyFontVariantCaps: |
| case CSSPropertyFontVariantEastAsian: |
| case CSSPropertyFontVariantLigatures: |
| case CSSPropertyFontVariantNumeric: |
| case CSSPropertyFontVariantPosition: |
| case CSSPropertyFontWeight: |
| #if ENABLE(VARIATION_FONTS) |
| case CSSPropertyFontOpticalSizing: |
| case CSSPropertyFontVariationSettings: |
| #endif |
| return true; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| #if ENABLE(VIDEO) |
| static inline bool isValidCueStyleProperty(CSSPropertyID id) |
| { |
| switch (id) { |
| case CSSPropertyBackground: |
| case CSSPropertyBackgroundAttachment: |
| case CSSPropertyBackgroundClip: |
| case CSSPropertyBackgroundColor: |
| case CSSPropertyBackgroundImage: |
| case CSSPropertyBackgroundOrigin: |
| case CSSPropertyBackgroundPosition: |
| case CSSPropertyBackgroundPositionX: |
| case CSSPropertyBackgroundPositionY: |
| case CSSPropertyBackgroundRepeat: |
| case CSSPropertyBackgroundSize: |
| case CSSPropertyColor: |
| case CSSPropertyFont: |
| case CSSPropertyFontFamily: |
| case CSSPropertyFontSize: |
| case CSSPropertyFontStyle: |
| case CSSPropertyFontVariantCaps: |
| case CSSPropertyFontWeight: |
| case CSSPropertyLineHeight: |
| case CSSPropertyOpacity: |
| case CSSPropertyOutline: |
| case CSSPropertyOutlineColor: |
| case CSSPropertyOutlineOffset: |
| case CSSPropertyOutlineStyle: |
| case CSSPropertyOutlineWidth: |
| case CSSPropertyVisibility: |
| case CSSPropertyWhiteSpace: |
| case CSSPropertyTextDecoration: |
| case CSSPropertyTextShadow: |
| case CSSPropertyBorderStyle: |
| case CSSPropertyPaintOrder: |
| case CSSPropertyStrokeLinejoin: |
| case CSSPropertyStrokeLinecap: |
| case CSSPropertyStrokeColor: |
| case CSSPropertyStrokeWidth: |
| return true; |
| default: |
| break; |
| } |
| return false; |
| } |
| #endif |
| |
| PropertyCascade::PropertyCascade(const MatchResult& matchResult, OptionSet<CascadeLevel> cascadeLevels, IncludedProperties includedProperties, Direction direction) |
| : m_matchResult(matchResult) |
| , m_includedProperties(includedProperties) |
| , m_direction(resolveDirectionAndWritingMode(direction)) |
| { |
| buildCascade(cascadeLevels); |
| } |
| |
| PropertyCascade::PropertyCascade(const PropertyCascade& parent, OptionSet<CascadeLevel> cascadeLevels) |
| : m_matchResult(parent.m_matchResult) |
| , m_includedProperties(parent.m_includedProperties) |
| , m_direction(parent.m_direction) |
| { |
| buildCascade(cascadeLevels); |
| } |
| |
| PropertyCascade::~PropertyCascade() = default; |
| |
| void PropertyCascade::buildCascade(OptionSet<CascadeLevel> cascadeLevels) |
| { |
| OptionSet<CascadeLevel> cascadeLevelsWithImportant; |
| |
| for (auto cascadeLevel : cascadeLevels) { |
| bool hasImportant = addNormalMatches(cascadeLevel); |
| if (hasImportant) |
| cascadeLevelsWithImportant.add(cascadeLevel); |
| } |
| |
| for (auto cascadeLevel : { CascadeLevel::Author, CascadeLevel::User, CascadeLevel::UserAgent }) { |
| if (!cascadeLevelsWithImportant.contains(cascadeLevel)) |
| continue; |
| addImportantMatches(cascadeLevel); |
| } |
| } |
| |
| void PropertyCascade::setPropertyInternal(Property& property, CSSPropertyID id, CSSValue& cssValue, unsigned linkMatchType, CascadeLevel cascadeLevel, ScopeOrdinal styleScopeOrdinal) |
| { |
| ASSERT(linkMatchType <= SelectorChecker::MatchAll); |
| property.id = id; |
| property.level = cascadeLevel; |
| property.styleScopeOrdinal = styleScopeOrdinal; |
| if (linkMatchType == SelectorChecker::MatchAll) { |
| property.cssValue[0] = &cssValue; |
| property.cssValue[SelectorChecker::MatchLink] = &cssValue; |
| property.cssValue[SelectorChecker::MatchVisited] = &cssValue; |
| } else |
| property.cssValue[linkMatchType] = &cssValue; |
| } |
| |
| void PropertyCascade::set(CSSPropertyID id, CSSValue& cssValue, unsigned linkMatchType, CascadeLevel cascadeLevel, ScopeOrdinal styleScopeOrdinal) |
| { |
| if (CSSProperty::isDirectionAwareProperty(id)) |
| id = CSSProperty::resolveDirectionAwareProperty(id, m_direction.textDirection, m_direction.writingMode); |
| |
| ASSERT(!shouldApplyPropertyInParseOrder(id)); |
| |
| auto& property = m_properties[id]; |
| ASSERT(id < m_propertyIsPresent.size()); |
| if (id == CSSPropertyCustom) { |
| m_propertyIsPresent.set(id); |
| const auto& customValue = downcast<CSSCustomPropertyValue>(cssValue); |
| bool hasValue = m_customProperties.contains(customValue.name()); |
| if (!hasValue) { |
| Property property; |
| property.id = id; |
| memset(property.cssValue, 0, sizeof(property.cssValue)); |
| setPropertyInternal(property, id, cssValue, linkMatchType, cascadeLevel, styleScopeOrdinal); |
| m_customProperties.set(customValue.name(), property); |
| } else { |
| Property property = customProperty(customValue.name()); |
| setPropertyInternal(property, id, cssValue, linkMatchType, cascadeLevel, styleScopeOrdinal); |
| m_customProperties.set(customValue.name(), property); |
| } |
| return; |
| } |
| |
| if (!m_propertyIsPresent[id]) |
| memset(property.cssValue, 0, sizeof(property.cssValue)); |
| m_propertyIsPresent.set(id); |
| setPropertyInternal(property, id, cssValue, linkMatchType, cascadeLevel, styleScopeOrdinal); |
| } |
| |
| void PropertyCascade::setDeferred(CSSPropertyID id, CSSValue& cssValue, unsigned linkMatchType, CascadeLevel cascadeLevel, ScopeOrdinal styleScopeOrdinal) |
| { |
| ASSERT(!CSSProperty::isDirectionAwareProperty(id)); |
| ASSERT(shouldApplyPropertyInParseOrder(id)); |
| |
| Property property; |
| memset(property.cssValue, 0, sizeof(property.cssValue)); |
| setPropertyInternal(property, id, cssValue, linkMatchType, cascadeLevel, styleScopeOrdinal); |
| m_deferredProperties.append(property); |
| } |
| |
| |
| bool PropertyCascade::addMatch(const MatchedProperties& matchedProperties, CascadeLevel cascadeLevel, bool important) |
| { |
| auto& styleProperties = *matchedProperties.properties; |
| auto propertyAllowlistType = static_cast<PropertyAllowlistType>(matchedProperties.allowlistType); |
| bool hasImportantProperties = false; |
| |
| for (unsigned i = 0, count = styleProperties.propertyCount(); i < count; ++i) { |
| auto current = styleProperties.propertyAt(i); |
| |
| if (current.isImportant()) |
| hasImportantProperties = true; |
| if (important != current.isImportant()) |
| continue; |
| |
| if (m_includedProperties == IncludedProperties::InheritedOnly && !current.isInherited()) { |
| // Inherited only mode is used after matched properties cache hit. |
| // A match with a value that is explicitly inherited should never have been cached. |
| ASSERT(!current.value()->isInheritedValue()); |
| continue; |
| } |
| CSSPropertyID propertyID = current.id(); |
| |
| #if ENABLE(VIDEO) |
| if (propertyAllowlistType == PropertyAllowlistCue && !isValidCueStyleProperty(propertyID)) |
| continue; |
| #endif |
| if (propertyAllowlistType == PropertyAllowlistMarker && !isValidMarkerStyleProperty(propertyID)) |
| continue; |
| |
| if (shouldApplyPropertyInParseOrder(propertyID)) |
| setDeferred(propertyID, *current.value(), matchedProperties.linkMatchType, cascadeLevel, matchedProperties.styleScopeOrdinal); |
| else |
| set(propertyID, *current.value(), matchedProperties.linkMatchType, cascadeLevel, matchedProperties.styleScopeOrdinal); |
| } |
| |
| return hasImportantProperties; |
| } |
| |
| static auto& declarationsForCascadeLevel(const MatchResult& matchResult, CascadeLevel cascadeLevel) |
| { |
| switch (cascadeLevel) { |
| case CascadeLevel::UserAgent: return matchResult.userAgentDeclarations; |
| case CascadeLevel::User: return matchResult.userDeclarations; |
| case CascadeLevel::Author: return matchResult.authorDeclarations; |
| } |
| ASSERT_NOT_REACHED(); |
| return matchResult.authorDeclarations; |
| } |
| |
| bool PropertyCascade::addNormalMatches(CascadeLevel cascadeLevel) |
| { |
| bool hasImportant = false; |
| for (auto& matchedDeclarations : declarationsForCascadeLevel(m_matchResult, cascadeLevel)) |
| hasImportant |= addMatch(matchedDeclarations, cascadeLevel, false); |
| |
| return hasImportant; |
| } |
| |
| static bool hasImportantProperties(const StyleProperties& properties) |
| { |
| for (unsigned i = 0, count = properties.propertyCount(); i < count; ++i) { |
| if (properties.propertyAt(i).isImportant()) |
| return true; |
| } |
| return false; |
| } |
| |
| void PropertyCascade::addImportantMatches(CascadeLevel cascadeLevel) |
| { |
| struct IndexAndOrdinal { |
| unsigned index; |
| ScopeOrdinal ordinal; |
| }; |
| Vector<IndexAndOrdinal> importantMatches; |
| bool hasMatchesFromOtherScopes = false; |
| |
| auto& matchedDeclarations = declarationsForCascadeLevel(m_matchResult, cascadeLevel); |
| |
| for (unsigned i = 0; i < matchedDeclarations.size(); ++i) { |
| const MatchedProperties& matchedProperties = matchedDeclarations[i]; |
| |
| if (!hasImportantProperties(*matchedProperties.properties)) |
| continue; |
| |
| importantMatches.append({ i, matchedProperties.styleScopeOrdinal }); |
| |
| if (matchedProperties.styleScopeOrdinal != ScopeOrdinal::Element) |
| hasMatchesFromOtherScopes = true; |
| } |
| |
| if (importantMatches.isEmpty()) |
| return; |
| |
| if (hasMatchesFromOtherScopes) { |
| // For !important properties a later shadow tree wins. |
| // Match results are sorted in reverse tree context order so this is not needed for normal properties. |
| std::stable_sort(importantMatches.begin(), importantMatches.end(), [] (const IndexAndOrdinal& a, const IndexAndOrdinal& b) { |
| return a.ordinal < b.ordinal; |
| }); |
| } |
| |
| for (auto& match : importantMatches) |
| addMatch(matchedDeclarations[match.index], cascadeLevel, true); |
| } |
| |
| const PropertyCascade* PropertyCascade::propertyCascadeForRollback(CascadeLevel cascadeLevel) const |
| { |
| switch (cascadeLevel) { |
| case CascadeLevel::Author: |
| if (!m_authorRollbackCascade) { |
| auto cascadeLevels = OptionSet<CascadeLevel> { CascadeLevel::UserAgent, CascadeLevel::User }; |
| m_authorRollbackCascade = makeUnique<const PropertyCascade>(*this, cascadeLevels); |
| } |
| return m_authorRollbackCascade.get(); |
| |
| case CascadeLevel::User: |
| if (!m_userRollbackCascade) { |
| auto cascadeLevels = OptionSet<CascadeLevel> { CascadeLevel::UserAgent }; |
| m_userRollbackCascade = makeUnique<const PropertyCascade>(*this, cascadeLevels); |
| } |
| return m_userRollbackCascade.get(); |
| |
| case CascadeLevel::UserAgent: |
| return nullptr; |
| } |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| PropertyCascade::Direction PropertyCascade::resolveDirectionAndWritingMode(Direction inheritedDirection) const |
| { |
| Direction result = inheritedDirection; |
| |
| bool hadImportantWritingMode = false; |
| bool hadImportantDirection = false; |
| |
| for (auto cascadeLevel : { CascadeLevel::UserAgent, CascadeLevel::User, CascadeLevel::Author }) { |
| for (const auto& matchedProperties : declarationsForCascadeLevel(m_matchResult, cascadeLevel)) { |
| for (unsigned i = 0, count = matchedProperties.properties->propertyCount(); i < count; ++i) { |
| auto property = matchedProperties.properties->propertyAt(i); |
| if (!property.value()->isPrimitiveValue()) |
| continue; |
| switch (property.id()) { |
| case CSSPropertyWritingMode: |
| if (!hadImportantWritingMode || property.isImportant()) { |
| result.writingMode = downcast<CSSPrimitiveValue>(*property.value()); |
| hadImportantWritingMode = property.isImportant(); |
| } |
| break; |
| case CSSPropertyDirection: |
| if (!hadImportantDirection || property.isImportant()) { |
| result.textDirection = downcast<CSSPrimitiveValue>(*property.value()); |
| hadImportantDirection = property.isImportant(); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| } |
| } |