blob: 9e9b29b7c454486152eea3aee8c76ded7ad7810e [file] [log] [blame]
/*
* 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 "CSSCustomPropertyValue.h"
#include "CSSPaintImageValue.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSValuePool.h"
#include "PaintWorkletGlobalScope.h"
#include "PropertyAllowlist.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 CSSPropertyTextDecorationSkipInk:
case CSSPropertyTextUnderlinePosition:
case CSSPropertyTextUnderlineOffset:
case CSSPropertyTextDecorationThickness:
case CSSPropertyTextDecoration:
return true;
default:
return false;
}
}
PropertyCascade::PropertyCascade(const MatchResult& matchResult, CascadeLevel maximumCascadeLevel, IncludedProperties includedProperties, Direction direction)
: m_matchResult(matchResult)
, m_includedProperties(includedProperties)
, m_maximumCascadeLevel(maximumCascadeLevel)
, m_direction(direction)
{
buildCascade();
}
PropertyCascade::PropertyCascade(const PropertyCascade& parent, CascadeLevel maximumCascadeLevel, std::optional<CascadeLayerPriority> maximumCascadeLayerPriorityForRollback)
: m_matchResult(parent.m_matchResult)
, m_includedProperties(parent.m_includedProperties)
, m_maximumCascadeLevel(maximumCascadeLevel)
, m_maximumCascadeLayerPriorityForRollback(maximumCascadeLayerPriorityForRollback)
, m_direction(parent.direction())
, m_directionIsUnresolved(false)
{
buildCascade();
}
PropertyCascade::~PropertyCascade() = default;
void PropertyCascade::buildCascade()
{
OptionSet<CascadeLevel> cascadeLevelsWithImportant;
for (auto cascadeLevel : { CascadeLevel::UserAgent, CascadeLevel::User, CascadeLevel::Author }) {
if (cascadeLevel > m_maximumCascadeLevel)
break;
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, const MatchedProperties& matchedProperties, CascadeLevel cascadeLevel)
{
ASSERT(matchedProperties.linkMatchType <= SelectorChecker::MatchAll);
property.id = id;
property.cascadeLevel = cascadeLevel;
property.styleScopeOrdinal = matchedProperties.styleScopeOrdinal;
property.cascadeLayerPriority = matchedProperties.cascadeLayerPriority;
property.fromStyleAttribute = matchedProperties.fromStyleAttribute;
if (matchedProperties.linkMatchType == SelectorChecker::MatchAll) {
property.cssValue[0] = &cssValue;
property.cssValue[SelectorChecker::MatchLink] = &cssValue;
property.cssValue[SelectorChecker::MatchVisited] = &cssValue;
} else
property.cssValue[matchedProperties.linkMatchType] = &cssValue;
}
void PropertyCascade::set(CSSPropertyID id, CSSValue& cssValue, const MatchedProperties& matchedProperties, CascadeLevel cascadeLevel)
{
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<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, matchedProperties, cascadeLevel);
m_customProperties.set(customValue.name(), property);
} else {
Property property = customProperty(customValue.name());
setPropertyInternal(property, id, cssValue, matchedProperties, cascadeLevel);
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, matchedProperties, cascadeLevel);
}
void PropertyCascade::setDeferred(CSSPropertyID id, CSSValue& cssValue, const MatchedProperties& matchedProperties, CascadeLevel cascadeLevel)
{
ASSERT(!CSSProperty::isDirectionAwareProperty(id));
ASSERT(shouldApplyPropertyInParseOrder(id));
Property property;
memset(property.cssValue, 0, sizeof(property.cssValue));
setPropertyInternal(property, id, cssValue, matchedProperties, cascadeLevel);
m_deferredProperties.append(property);
}
bool PropertyCascade::addMatch(const MatchedProperties& matchedProperties, CascadeLevel cascadeLevel, bool important)
{
auto skipForRollback = [&] {
if (!m_maximumCascadeLayerPriorityForRollback)
return false;
if (matchedProperties.styleScopeOrdinal != ScopeOrdinal::Element)
return false;
if (cascadeLevel < m_maximumCascadeLevel)
return false;
if (matchedProperties.fromStyleAttribute == FromStyleAttribute::Yes)
return true;
return matchedProperties.cascadeLayerPriority > *m_maximumCascadeLayerPriorityForRollback;
};
if (skipForRollback())
return false;
auto& styleProperties = *matchedProperties.properties;
auto propertyAllowlist = 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()->isInheritValue());
continue;
}
CSSPropertyID propertyID = current.id();
#if ENABLE(VIDEO)
if (propertyAllowlist == PropertyAllowlist::Cue && !isValidCueStyleProperty(propertyID))
continue;
#endif
if (propertyAllowlist == PropertyAllowlist::Marker && !isValidMarkerStyleProperty(propertyID))
continue;
if (shouldApplyPropertyInParseOrder(propertyID))
setDeferred(propertyID, *current.value(), matchedProperties, cascadeLevel);
else
set(propertyID, *current.value(), matchedProperties, cascadeLevel);
}
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 ImportantMatch {
unsigned index;
ScopeOrdinal ordinal;
CascadeLayerPriority layerPriority;
FromStyleAttribute fromStyleAttribute;
};
Vector<ImportantMatch> importantMatches;
bool hasMatchesFromOtherScopesOrLayers = 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, matchedProperties.cascadeLayerPriority, matchedProperties.fromStyleAttribute });
if (matchedProperties.styleScopeOrdinal != ScopeOrdinal::Element || matchedProperties.cascadeLayerPriority != RuleSet::cascadeLayerPriorityForUnlayered)
hasMatchesFromOtherScopesOrLayers = true;
}
if (importantMatches.isEmpty())
return;
if (hasMatchesFromOtherScopesOrLayers) {
// Match results are sorted in reverse tree context order so this is not needed for normal properties.
std::stable_sort(importantMatches.begin(), importantMatches.end(), [] (auto& a, auto& b) {
// For !important properties a later shadow tree wins.
if (a.ordinal != b.ordinal)
return a.ordinal < b.ordinal;
// Lower priority layer wins, except if style attribute is involved.
if (a.fromStyleAttribute != b.fromStyleAttribute)
return a.fromStyleAttribute == FromStyleAttribute::No;
return a.layerPriority > b.layerPriority;
});
}
for (auto& match : importantMatches)
addMatch(matchedDeclarations[match.index], cascadeLevel, true);
}
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() || property.value()->isCSSWideKeyword())
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;
}
PropertyCascade::Direction PropertyCascade::direction() const
{
if (m_directionIsUnresolved) {
m_direction = resolveDirectionAndWritingMode(m_direction);
m_directionIsUnresolved = false;
}
return m_direction;
}
}
}