blob: 114b8111db84d832be056607205f8ac38d7614c2 [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 "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;
}
}
}