/* * 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) // FIXME: this is outdated, see https://bugs.webkit.org/show_bug.cgi?id=218791. 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 case CSSPropertyAnimationDuration: case CSSPropertyAnimationTimingFunction: case CSSPropertyAnimationDelay: case CSSPropertyAnimationIterationCount: case CSSPropertyAnimationDirection: case CSSPropertyAnimationFillMode: case CSSPropertyAnimationPlayState: case CSSPropertyAnimationName: case CSSPropertyTransitionDuration: case CSSPropertyTransitionTimingFunction: case CSSPropertyTransitionDelay: case CSSPropertyTransitionProperty: 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 cascadeLevels, IncludedProperties includedProperties, Direction direction) : m_matchResult(matchResult) , m_includedProperties(includedProperties) , m_direction(direction) { buildCascade(cascadeLevels); } PropertyCascade::PropertyCascade(const PropertyCascade& parent, OptionSet cascadeLevels) : m_matchResult(parent.m_matchResult) , m_includedProperties(parent.m_includedProperties) , m_direction(parent.direction()) , m_directionIsUnresolved(false) { buildCascade(cascadeLevels); } PropertyCascade::~PropertyCascade() = default; void PropertyCascade::buildCascade(OptionSet cascadeLevels) { OptionSet 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)) { auto direction = this->direction(); id = CSSProperty::resolveDirectionAwareProperty(id, direction.textDirection, 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(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(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 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::UserAgent, CascadeLevel::User }; m_authorRollbackCascade = makeUnique(*this, cascadeLevels); } return m_authorRollbackCascade.get(); case CascadeLevel::User: if (!m_userRollbackCascade) { auto cascadeLevels = OptionSet { CascadeLevel::UserAgent }; m_userRollbackCascade = makeUnique(*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(*property.value()); hadImportantWritingMode = property.isImportant(); } break; case CSSPropertyDirection: if (!hadImportantDirection || property.isImportant()) { result.textDirection = downcast(*property.value()); hadImportantDirection = property.isImportant(); } break; default: break; } } } } return result; } PropertyCascade::Direction PropertyCascade::direction() const { if (m_directionIsUnresolved) { m_direction = resolveDirectionAndWritingMode(m_direction); m_directionIsUnresolved = false; } return m_direction; } } }