blob: fe6b6d98e7ef1a91a7c5199c2db24d49738af510 [file] [log] [blame]
/*
* 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-2014 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 "StyleResolver.h"
#include "CSSCalculationValue.h"
#include "CSSCursorImageValue.h"
#include "CSSCustomPropertyValue.h"
#include "CSSDefaultStyleSheets.h"
#include "CSSFilterImageValue.h"
#include "CSSFontSelector.h"
#include "CSSFunctionValue.h"
#include "CSSGradientValue.h"
#include "CSSImageSetValue.h"
#include "CSSImageValue.h"
#include "CSSKeyframeRule.h"
#include "CSSKeyframesRule.h"
#include "CSSPaintImageValue.h"
#include "CSSParser.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSPropertyNames.h"
#include "CSSReflectValue.h"
#include "CSSSelector.h"
#include "CSSShadowValue.h"
#include "CSSStyleRule.h"
#include "CSSStyleSheet.h"
#include "CSSValueList.h"
#include "CSSValuePool.h"
#include "CachedResourceLoader.h"
#include "ElementRuleCollector.h"
#include "FilterOperation.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "FrameView.h"
#include "HTMLInputElement.h"
#include "HTMLMarqueeElement.h"
#include "HTMLNames.h"
#include "HTMLSlotElement.h"
#include "HTMLTableElement.h"
#include "HTMLTextAreaElement.h"
#include "InspectorInstrumentation.h"
#include "KeyframeList.h"
#include "Logging.h"
#include "MathMLElement.h"
#include "MathMLNames.h"
#include "MediaList.h"
#include "MediaQueryEvaluator.h"
#include "NodeRenderStyle.h"
#include "PageRuleCollector.h"
#include "PaintWorkletGlobalScope.h"
#include "Pair.h"
#include "Quirks.h"
#include "RenderScrollbar.h"
#include "RenderStyleConstants.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "RuleSet.h"
#include "RuntimeEnabledFeatures.h"
#include "SVGDocument.h"
#include "SVGDocumentExtensions.h"
#include "SVGFontFaceElement.h"
#include "SVGNames.h"
#include "SVGSVGElement.h"
#include "SVGURIReference.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "SharedStringHash.h"
#include "StyleBuilder.h"
#include "StyleColor.h"
#include "StyleCachedImage.h"
#include "StyleFontSizeFunctions.h"
#include "StyleGeneratedImage.h"
#include "StyleProperties.h"
#include "StylePropertyShorthand.h"
#include "StyleRule.h"
#include "StyleSheetContents.h"
#include "TransformFunctions.h"
#include "TransformOperations.h"
#include "UserAgentStyleSheets.h"
#include "ViewportStyleResolver.h"
#include "VisitedLinkState.h"
#include "WebKitFontFamilyNames.h"
#include <bitset>
#include <wtf/Seconds.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
#include <wtf/text/AtomStringHash.h>
namespace WebCore {
using namespace HTMLNames;
static const CSSPropertyID firstLowPriorityProperty = static_cast<CSSPropertyID>(lastHighPriorityProperty + 1);
static void extractDirectionAndWritingMode(const RenderStyle&, const MatchResult&, TextDirection&, WritingMode&);
inline void StyleResolver::State::cacheBorderAndBackground()
{
m_hasUAAppearance = m_style->hasAppearance();
if (m_hasUAAppearance) {
m_borderData = m_style->border();
m_backgroundData = m_style->backgroundLayers();
m_backgroundColor = m_style->backgroundColor();
}
}
inline void StyleResolver::State::clear()
{
m_element = nullptr;
m_parentStyle = nullptr;
m_ownedParentStyle = nullptr;
m_cssToLengthConversionData = CSSToLengthConversionData();
}
StyleResolver::StyleResolver(Document& document)
: m_ruleSets(*this)
, m_matchedPropertiesCacheSweepTimer(*this, &StyleResolver::sweepMatchedPropertiesCache)
, m_document(document)
#if ENABLE(CSS_DEVICE_ADAPTATION)
, m_viewportStyleResolver(ViewportStyleResolver::create(&document))
#endif
, m_styleMap(this)
, m_matchAuthorAndUserStyles(m_document.settings().authorAndUserStylesEnabled())
{
Element* root = m_document.documentElement();
CSSDefaultStyleSheets::initDefaultStyle(root);
// construct document root element default style. this is needed
// to evaluate media queries that contain relative constraints, like "screen and (max-width: 10em)"
// This is here instead of constructor, because when constructor is run,
// document doesn't have documentElement
// NOTE: this assumes that element that gets passed to styleForElement -call
// is always from the document that owns the style selector
FrameView* view = m_document.view();
if (view)
m_mediaQueryEvaluator = MediaQueryEvaluator { view->mediaType() };
else
m_mediaQueryEvaluator = MediaQueryEvaluator { "all" };
if (root) {
m_rootDefaultStyle = styleForElement(*root, m_document.renderStyle(), nullptr, RuleMatchingBehavior::MatchOnlyUserAgentRules).renderStyle;
// Turn off assertion against font lookups during style resolver initialization. We may need root style font for media queries.
m_document.fontSelector().incrementIsComputingRootStyleFont();
m_rootDefaultStyle->fontCascade().update(&m_document.fontSelector());
m_rootDefaultStyle->fontCascade().primaryFont();
m_document.fontSelector().decrementIsComputingRootStyleFont();
}
if (m_rootDefaultStyle && view)
m_mediaQueryEvaluator = MediaQueryEvaluator { view->mediaType(), m_document, m_rootDefaultStyle.get() };
m_ruleSets.resetAuthorStyle();
m_ruleSets.resetUserAgentMediaQueryStyle();
}
void StyleResolver::addCurrentSVGFontFaceRules()
{
#if ENABLE(SVG_FONTS)
if (m_document.svgExtensions()) {
const HashSet<SVGFontFaceElement*>& svgFontFaceElements = m_document.svgExtensions()->svgFontFaceElements();
for (auto* svgFontFaceElement : svgFontFaceElements)
m_document.fontSelector().addFontFaceRule(svgFontFaceElement->fontFaceRule(), svgFontFaceElement->isInUserAgentShadowTree());
}
#endif
}
void StyleResolver::appendAuthorStyleSheets(const Vector<RefPtr<CSSStyleSheet>>& styleSheets)
{
m_ruleSets.appendAuthorStyleSheets(styleSheets, &m_mediaQueryEvaluator, m_inspectorCSSOMWrappers, this);
if (auto renderView = document().renderView())
renderView->style().fontCascade().update(&document().fontSelector());
#if ENABLE(CSS_DEVICE_ADAPTATION)
viewportStyleResolver()->resolve();
#endif
}
// This is a simplified style setting function for keyframe styles
void StyleResolver::addKeyframeStyle(Ref<StyleRuleKeyframes>&& rule)
{
AtomString s(rule->name());
m_keyframesRuleMap.set(s.impl(), WTFMove(rule));
}
StyleResolver::~StyleResolver()
{
RELEASE_ASSERT(!m_document.isResolvingTreeStyle());
RELEASE_ASSERT(!m_isDeleted);
m_isDeleted = true;
#if ENABLE(CSS_DEVICE_ADAPTATION)
m_viewportStyleResolver->clearDocument();
#endif
}
void StyleResolver::sweepMatchedPropertiesCache()
{
// Look for cache entries containing a style declaration with a single ref and remove them.
// This may happen when an element attribute mutation causes it to generate a new inlineStyle()
// or presentationAttributeStyle(), potentially leaving this cache with the last ref on the old one.
auto hasOneRef = [](auto& declarations) {
for (auto& matchedProperties : declarations) {
if (matchedProperties.properties->hasOneRef())
return true;
}
return false;
};
m_matchedPropertiesCache.removeIf([&](auto& keyValue) {
auto& entry = keyValue.value;
return hasOneRef(entry.userAgentDeclarations) || hasOneRef(entry.userDeclarations) || hasOneRef(entry.authorDeclarations);
});
m_matchedPropertiesCacheAdditionsSinceLastSweep = 0;
}
StyleResolver::State::State(const Element& element, const RenderStyle* parentStyle, const RenderStyle* documentElementStyle, const SelectorFilter* selectorFilter)
: m_element(&element)
, m_parentStyle(parentStyle)
, m_selectorFilter(selectorFilter)
, m_elementLinkState(element.document().visitedLinkState().determineLinkState(element))
{
bool resetStyleInheritance = hasShadowRootParent(element) && downcast<ShadowRoot>(element.parentNode())->resetStyleInheritance();
if (resetStyleInheritance)
m_parentStyle = nullptr;
auto& document = element.document();
auto* documentElement = document.documentElement();
if (!documentElement || documentElement == &element)
m_rootElementStyle = document.renderStyle();
else
m_rootElementStyle = documentElementStyle ? documentElementStyle : documentElement->renderStyle();
updateConversionData();
}
inline void StyleResolver::State::updateConversionData()
{
m_cssToLengthConversionData = CSSToLengthConversionData(m_style.get(), m_rootElementStyle, m_element ? m_element->document().renderView() : nullptr);
}
inline void StyleResolver::State::setStyle(std::unique_ptr<RenderStyle> style)
{
m_style = WTFMove(style);
updateConversionData();
}
inline void StyleResolver::State::setParentStyle(std::unique_ptr<RenderStyle> parentStyle)
{
m_ownedParentStyle = WTFMove(parentStyle);
m_parentStyle = m_ownedParentStyle.get();
}
static inline bool isAtShadowBoundary(const Element& element)
{
auto* parentNode = element.parentNode();
return parentNode && parentNode->isShadowRoot();
}
void StyleResolver::setNewStateWithElement(const Element& element)
{
// Apply the declaration to the style. This is a simplified version of the logic in styleForElement.
m_state = State(element, nullptr);
}
ElementStyle StyleResolver::styleForElement(const Element& element, const RenderStyle* parentStyle, const RenderStyle* parentBoxStyle, RuleMatchingBehavior matchingBehavior, const SelectorFilter* selectorFilter)
{
RELEASE_ASSERT(!m_isDeleted);
m_state = State(element, parentStyle, m_overrideDocumentElementStyle, selectorFilter);
State& state = m_state;
if (state.parentStyle()) {
state.setStyle(RenderStyle::createPtr());
state.style()->inheritFrom(*state.parentStyle());
} else {
state.setStyle(defaultStyleForElement());
state.setParentStyle(RenderStyle::clonePtr(*state.style()));
}
auto& style = *state.style();
if (element.isLink()) {
style.setIsLink(true);
InsideLink linkState = state.elementLinkState();
if (linkState != InsideLink::NotInside) {
bool forceVisited = InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoClassVisited);
if (forceVisited)
linkState = InsideLink::InsideVisited;
}
style.setInsideLink(linkState);
}
CSSDefaultStyleSheets::ensureDefaultStyleSheetsForElement(element);
ElementRuleCollector collector(element, m_ruleSets, m_state.selectorFilter());
collector.setMedium(&m_mediaQueryEvaluator);
if (matchingBehavior == RuleMatchingBehavior::MatchOnlyUserAgentRules)
collector.matchUARules();
else
collector.matchAllRules(m_matchAuthorAndUserStyles, matchingBehavior != RuleMatchingBehavior::MatchAllRulesExcludingSMIL);
if (collector.matchedPseudoElementIds())
style.setHasPseudoStyles(collector.matchedPseudoElementIds());
// This is required for style sharing.
if (collector.didMatchUncommonAttributeSelector())
style.setUnique();
auto elementStyleRelations = Style::commitRelationsToRenderStyle(style, element, collector.styleRelations());
applyMatchedProperties(collector.matchResult(), element);
// Clean up our style object's display and text decorations (among other fixups).
adjustRenderStyle(*state.style(), *state.parentStyle(), parentBoxStyle, &element);
if (state.style()->hasViewportUnits())
document().setHasStyleWithViewportUnits();
state.clear(); // Clear out for the next resolve.
return { state.takeStyle(), WTFMove(elementStyleRelations) };
}
std::unique_ptr<RenderStyle> StyleResolver::styleForKeyframe(const RenderStyle* elementStyle, const StyleRuleKeyframe* keyframe, KeyframeValue& keyframeValue)
{
RELEASE_ASSERT(!m_isDeleted);
MatchResult result;
result.authorDeclarations.append({ &keyframe->properties() });
ASSERT(!m_state.style());
State& state = m_state;
// Create the style
state.setStyle(RenderStyle::clonePtr(*elementStyle));
state.setParentStyle(RenderStyle::clonePtr(*elementStyle));
TextDirection direction;
WritingMode writingMode;
extractDirectionAndWritingMode(*state.style(), result, direction, writingMode);
// We don't need to bother with !important. Since there is only ever one
// decl, there's nothing to override. So just add the first properties.
Style::PropertyCascade cascade(direction, writingMode);
cascade.addNormalMatches(result, Style::CascadeLevel::Author);
ApplyCascadedPropertyState applyState { this, &cascade, &result };
applyCascadedProperties(firstCSSProperty, lastHighPriorityProperty, applyState);
// If our font got dirtied, update it now.
updateFont();
// Now resolve remaining custom properties and the rest, in any order
for (auto it = cascade.customProperties().begin(); it != cascade.customProperties().end(); ++it)
applyCascadedCustomProperty(it->key, applyState);
applyCascadedProperties(firstLowPriorityProperty, lastCSSProperty, applyState);
// If our font got dirtied by one of the non-essential font props, update it a second time.
updateFont();
cascade.applyDeferredProperties(*this, applyState);
adjustRenderStyle(*state.style(), *state.parentStyle(), nullptr, nullptr);
// Add all the animating properties to the keyframe.
unsigned propertyCount = keyframe->properties().propertyCount();
for (unsigned i = 0; i < propertyCount; ++i) {
CSSPropertyID property = keyframe->properties().propertyAt(i).id();
// Timing-function within keyframes is special, because it is not animated; it just
// describes the timing function between this keyframe and the next.
if (property != CSSPropertyAnimationTimingFunction)
keyframeValue.addProperty(property);
}
return state.takeStyle();
}
bool StyleResolver::isAnimationNameValid(const String& name)
{
return m_keyframesRuleMap.find(AtomString(name).impl()) != m_keyframesRuleMap.end();
}
void StyleResolver::keyframeStylesForAnimation(const Element& element, const RenderStyle* elementStyle, KeyframeList& list)
{
list.clear();
// Get the keyframesRule for this name.
if (list.animationName().isEmpty())
return;
m_keyframesRuleMap.checkConsistency();
KeyframesRuleMap::iterator it = m_keyframesRuleMap.find(list.animationName().impl());
if (it == m_keyframesRuleMap.end())
return;
const StyleRuleKeyframes* keyframesRule = it->value.get();
auto* keyframes = &keyframesRule->keyframes();
Vector<Ref<StyleRuleKeyframe>> newKeyframesIfNecessary;
bool hasDuplicateKeys = false;
HashSet<double> keyframeKeys;
for (auto& keyframe : *keyframes) {
for (auto key : keyframe->keys()) {
if (!keyframeKeys.add(key)) {
hasDuplicateKeys = true;
break;
}
}
if (hasDuplicateKeys)
break;
}
// FIXME: If HashMaps could have Ref<> as value types, we wouldn't need
// to copy the HashMap into a Vector.
if (hasDuplicateKeys) {
// Merge duplicate key times.
HashMap<double, RefPtr<StyleRuleKeyframe>> keyframesMap;
for (auto& originalKeyframe : keyframesRule->keyframes()) {
for (auto key : originalKeyframe->keys()) {
if (auto keyframe = keyframesMap.get(key))
keyframe->mutableProperties().mergeAndOverrideOnConflict(originalKeyframe->properties());
else {
auto StyleRuleKeyframe = StyleRuleKeyframe::create(MutableStyleProperties::create());
StyleRuleKeyframe.ptr()->setKey(key);
StyleRuleKeyframe.ptr()->mutableProperties().mergeAndOverrideOnConflict(originalKeyframe->properties());
keyframesMap.set(key, StyleRuleKeyframe.ptr());
}
}
}
for (auto& keyframe : keyframesMap.values())
newKeyframesIfNecessary.append(*keyframe.get());
keyframes = &newKeyframesIfNecessary;
}
// Construct and populate the style for each keyframe.
for (auto& keyframe : *keyframes) {
setNewStateWithElement(element);
// Add this keyframe style to all the indicated key times
for (auto key : keyframe->keys()) {
KeyframeValue keyframeValue(0, nullptr);
keyframeValue.setStyle(styleForKeyframe(elementStyle, keyframe.ptr(), keyframeValue));
keyframeValue.setKey(key);
if (auto timingFunctionCSSValue = keyframe->properties().getPropertyCSSValue(CSSPropertyAnimationTimingFunction))
keyframeValue.setTimingFunction(TimingFunction::createFromCSSValue(*timingFunctionCSSValue.get()));
list.insert(WTFMove(keyframeValue));
}
}
// If the 0% keyframe is missing, create it (but only if there is at least one other keyframe).
int initialListSize = list.size();
if (initialListSize > 0 && list[0].key()) {
static StyleRuleKeyframe* zeroPercentKeyframe;
if (!zeroPercentKeyframe) {
zeroPercentKeyframe = &StyleRuleKeyframe::create(MutableStyleProperties::create()).leakRef();
zeroPercentKeyframe->setKey(0);
}
KeyframeValue keyframeValue(0, nullptr);
keyframeValue.setStyle(styleForKeyframe(elementStyle, zeroPercentKeyframe, keyframeValue));
list.insert(WTFMove(keyframeValue));
}
// If the 100% keyframe is missing, create it (but only if there is at least one other keyframe).
if (initialListSize > 0 && (list[list.size() - 1].key() != 1)) {
static StyleRuleKeyframe* hundredPercentKeyframe;
if (!hundredPercentKeyframe) {
hundredPercentKeyframe = &StyleRuleKeyframe::create(MutableStyleProperties::create()).leakRef();
hundredPercentKeyframe->setKey(1);
}
KeyframeValue keyframeValue(1, nullptr);
keyframeValue.setStyle(styleForKeyframe(elementStyle, hundredPercentKeyframe, keyframeValue));
list.insert(WTFMove(keyframeValue));
}
}
std::unique_ptr<RenderStyle> StyleResolver::pseudoStyleForElement(const Element& element, const PseudoStyleRequest& pseudoStyleRequest, const RenderStyle& parentStyle, const SelectorFilter* selectorFilter)
{
m_state = State(element, &parentStyle, m_overrideDocumentElementStyle, selectorFilter);
State& state = m_state;
if (m_state.parentStyle()) {
state.setStyle(RenderStyle::createPtr());
state.style()->inheritFrom(*m_state.parentStyle());
} else {
state.setStyle(defaultStyleForElement());
state.setParentStyle(RenderStyle::clonePtr(*state.style()));
}
// Since we don't use pseudo-elements in any of our quirk/print user agent rules, don't waste time walking
// those rules.
// Check UA, user and author rules.
ElementRuleCollector collector(element, m_ruleSets, m_state.selectorFilter());
collector.setPseudoStyleRequest(pseudoStyleRequest);
collector.setMedium(&m_mediaQueryEvaluator);
collector.matchUARules();
if (m_matchAuthorAndUserStyles) {
collector.matchUserRules();
collector.matchAuthorRules();
}
ASSERT(!collector.matchedPseudoElementIds());
if (collector.matchResult().isEmpty())
return nullptr;
state.style()->setStyleType(pseudoStyleRequest.pseudoId);
applyMatchedProperties(collector.matchResult(), element);
// Clean up our style object's display and text decorations (among other fixups).
adjustRenderStyle(*state.style(), *m_state.parentStyle(), nullptr, nullptr);
if (state.style()->hasViewportUnits())
document().setHasStyleWithViewportUnits();
// Now return the style.
return state.takeStyle();
}
std::unique_ptr<RenderStyle> StyleResolver::styleForPage(int pageIndex)
{
RELEASE_ASSERT(!m_isDeleted);
auto* documentElement = m_document.documentElement();
if (!documentElement)
return RenderStyle::createPtr();
m_state = State(*documentElement, m_document.renderStyle());
m_state.setStyle(RenderStyle::createPtr());
m_state.style()->inheritFrom(*m_state.rootElementStyle());
PageRuleCollector collector(m_state, m_ruleSets);
collector.matchAllPageRules(pageIndex);
auto& result = collector.matchResult();
TextDirection direction;
WritingMode writingMode;
extractDirectionAndWritingMode(*m_state.style(), result, direction, writingMode);
Style::PropertyCascade cascade(direction, writingMode);
cascade.addNormalMatches(result, Style::CascadeLevel::Author);
ApplyCascadedPropertyState applyState { this, &cascade, &result };
applyCascadedProperties(firstCSSProperty, lastHighPriorityProperty, applyState);
// If our font got dirtied, update it now.
updateFont();
// Now resolve remaining custom properties and the rest, in any order
for (auto it = cascade.customProperties().begin(); it != cascade.customProperties().end(); ++it)
applyCascadedCustomProperty(it->key, applyState);
applyCascadedProperties(firstLowPriorityProperty, lastCSSProperty, applyState);
cascade.applyDeferredProperties(*this, applyState);
// Now return the style.
return m_state.takeStyle();
}
std::unique_ptr<RenderStyle> StyleResolver::defaultStyleForElement()
{
m_state.setStyle(RenderStyle::createPtr());
// Make sure our fonts are initialized if we don't inherit them from our parent style.
initializeFontStyle();
m_state.style()->fontCascade().update(&document().fontSelector());
return m_state.takeStyle();
}
static void addIntrinsicMargins(RenderStyle& style)
{
// Intrinsic margin value.
const int intrinsicMargin = clampToInteger(2 * style.effectiveZoom());
// FIXME: Using width/height alone and not also dealing with min-width/max-width is flawed.
// FIXME: Using "hasQuirk" to decide the margin wasn't set is kind of lame.
if (style.width().isIntrinsicOrAuto()) {
if (style.marginLeft().hasQuirk())
style.setMarginLeft(Length(intrinsicMargin, Fixed));
if (style.marginRight().hasQuirk())
style.setMarginRight(Length(intrinsicMargin, Fixed));
}
if (style.height().isAuto()) {
if (style.marginTop().hasQuirk())
style.setMarginTop(Length(intrinsicMargin, Fixed));
if (style.marginBottom().hasQuirk())
style.setMarginBottom(Length(intrinsicMargin, Fixed));
}
}
static DisplayType equivalentBlockDisplay(const RenderStyle& style, const Document& document)
{
switch (auto display = style.display()) {
case DisplayType::Block:
case DisplayType::Table:
case DisplayType::Box:
case DisplayType::Flex:
case DisplayType::WebKitFlex:
case DisplayType::Grid:
case DisplayType::FlowRoot:
return display;
case DisplayType::ListItem:
// It is a WinIE bug that floated list items lose their bullets, so we'll emulate the quirk, but only in quirks mode.
if (document.inQuirksMode() && style.isFloating())
return DisplayType::Block;
return display;
case DisplayType::InlineTable:
return DisplayType::Table;
case DisplayType::InlineBox:
return DisplayType::Box;
case DisplayType::InlineFlex:
case DisplayType::WebKitInlineFlex:
return DisplayType::Flex;
case DisplayType::InlineGrid:
return DisplayType::Grid;
case DisplayType::Inline:
case DisplayType::Compact:
case DisplayType::InlineBlock:
case DisplayType::TableRowGroup:
case DisplayType::TableHeaderGroup:
case DisplayType::TableFooterGroup:
case DisplayType::TableRow:
case DisplayType::TableColumnGroup:
case DisplayType::TableColumn:
case DisplayType::TableCell:
case DisplayType::TableCaption:
return DisplayType::Block;
case DisplayType::Contents:
ASSERT_NOT_REACHED();
return DisplayType::Contents;
case DisplayType::None:
ASSERT_NOT_REACHED();
return DisplayType::None;
}
ASSERT_NOT_REACHED();
return DisplayType::Block;
}
// CSS requires text-decoration to be reset at each DOM element for tables,
// inline blocks, inline tables, shadow DOM crossings, floating elements,
// and absolute or relatively positioned elements.
static bool doesNotInheritTextDecoration(const RenderStyle& style, const Element* element)
{
return style.display() == DisplayType::Table || style.display() == DisplayType::InlineTable
|| style.display() == DisplayType::InlineBlock || style.display() == DisplayType::InlineBox || (element && isAtShadowBoundary(*element))
|| style.isFloating() || style.hasOutOfFlowPosition();
}
#if ENABLE(OVERFLOW_SCROLLING_TOUCH) || ENABLE(POINTER_EVENTS)
static bool isScrollableOverflow(Overflow overflow)
{
return overflow == Overflow::Scroll || overflow == Overflow::Auto;
}
#endif
void StyleResolver::adjustStyleForInterCharacterRuby()
{
RenderStyle* style = m_state.style();
if (style->rubyPosition() != RubyPosition::InterCharacter || !m_state.element() || !m_state.element()->hasTagName(rtTag))
return;
style->setTextAlign(TextAlignMode::Center);
if (style->isHorizontalWritingMode())
style->setWritingMode(LeftToRightWritingMode);
}
static bool hasEffectiveDisplayNoneForDisplayContents(const Element& element)
{
// https://drafts.csswg.org/css-display-3/#unbox-html
static NeverDestroyed<HashSet<AtomString>> tagNames = [] {
static const HTMLQualifiedName* const tagList[] = {
&brTag.get(),
&wbrTag.get(),
&meterTag.get(),
&appletTag.get(),
&progressTag.get(),
&canvasTag.get(),
&embedTag.get(),
&objectTag.get(),
&audioTag.get(),
&iframeTag.get(),
&imgTag.get(),
&videoTag.get(),
&frameTag.get(),
&framesetTag.get(),
&inputTag.get(),
&textareaTag.get(),
&selectTag.get(),
};
HashSet<AtomString> set;
for (auto& name : tagList)
set.add(name->localName());
return set;
}();
// https://drafts.csswg.org/css-display-3/#unbox-svg
// FIXME: <g>, <use> and <tspan> have special (?) behavior for display:contents in the current draft spec.
if (is<SVGElement>(element))
return true;
#if ENABLE(MATHML)
// Not sure MathML code can handle it.
if (is<MathMLElement>(element))
return true;
#endif // ENABLE(MATHML)
if (!is<HTMLElement>(element))
return false;
return tagNames.get().contains(element.localName());
}
static void adjustDisplayContentsStyle(RenderStyle& style, const Element* element)
{
bool displayContentsEnabled = is<HTMLSlotElement>(element) || RuntimeEnabledFeatures::sharedFeatures().displayContentsEnabled();
if (!displayContentsEnabled) {
style.setDisplay(DisplayType::Inline);
return;
}
if (!element) {
if (style.styleType() != PseudoId::Before && style.styleType() != PseudoId::After)
style.setDisplay(DisplayType::None);
return;
}
if (element->document().documentElement() == element) {
style.setDisplay(DisplayType::Block);
return;
}
if (hasEffectiveDisplayNoneForDisplayContents(*element))
style.setDisplay(DisplayType::None);
}
void StyleResolver::adjustSVGElementStyle(const SVGElement& svgElement, RenderStyle& style)
{
// Only the root <svg> element in an SVG document fragment tree honors css position
auto isPositioningAllowed = svgElement.hasTagName(SVGNames::svgTag) && svgElement.parentNode() && !svgElement.parentNode()->isSVGElement() && !svgElement.correspondingElement();
if (!isPositioningAllowed)
style.setPosition(RenderStyle::initialPosition());
// RenderSVGRoot handles zooming for the whole SVG subtree, so foreignObject content should
// not be scaled again.
if (svgElement.hasTagName(SVGNames::foreignObjectTag))
style.setEffectiveZoom(RenderStyle::initialZoom());
// SVG text layout code expects us to be a block-level style element.
if ((svgElement.hasTagName(SVGNames::foreignObjectTag) || svgElement.hasTagName(SVGNames::textTag)) && style.isDisplayInlineType())
style.setDisplay(DisplayType::Block);
}
#if ENABLE(POINTER_EVENTS)
static OptionSet<TouchAction> computeEffectiveTouchActions(const RenderStyle& style, OptionSet<TouchAction> effectiveTouchActions)
{
// https://w3c.github.io/pointerevents/#determining-supported-touch-behavior
// "A touch behavior is supported if it conforms to the touch-action property of each element between
// the hit tested element and its nearest ancestor with the default touch behavior (including both the
// hit tested element and the element with the default touch behavior)."
bool hasDefaultTouchBehavior = isScrollableOverflow(style.overflowX()) || isScrollableOverflow(style.overflowY());
if (hasDefaultTouchBehavior)
effectiveTouchActions = RenderStyle::initialTouchActions();
auto touchActions = style.touchActions();
if (touchActions == RenderStyle::initialTouchActions())
return effectiveTouchActions;
if (effectiveTouchActions.contains(TouchAction::None))
return { TouchAction::None };
if (effectiveTouchActions.containsAny({ TouchAction::Auto, TouchAction::Manipulation }))
return touchActions;
if (touchActions.containsAny({ TouchAction::Auto, TouchAction::Manipulation }))
return effectiveTouchActions;
auto sharedTouchActions = effectiveTouchActions & touchActions;
if (sharedTouchActions.isEmpty())
return { TouchAction::None };
return sharedTouchActions;
}
#endif
#if ENABLE(TEXT_AUTOSIZING)
static bool hasTextChild(const Element& element)
{
for (auto* child = element.firstChild(); child; child = child->nextSibling()) {
if (is<Text>(child))
return true;
}
return false;
}
bool StyleResolver::adjustRenderStyleForTextAutosizing(RenderStyle& style, const Element& element)
{
if (!settings().textAutosizingEnabled() || !settings().textAutosizingUsesIdempotentMode())
return false;
AutosizeStatus::updateStatus(style);
if (style.textSizeAdjust().isNone())
return false;
float initialScale = document().page() ? document().page()->initialScale() : 1;
auto adjustLineHeightIfNeeded = [&](auto computedFontSize) {
auto lineHeight = style.specifiedLineHeight();
constexpr static unsigned eligibleFontSize = 12;
if (computedFontSize * initialScale >= eligibleFontSize)
return;
constexpr static float boostFactor = 1.25;
auto minimumLineHeight = boostFactor * computedFontSize;
if (!lineHeight.isFixed() || lineHeight.value() >= minimumLineHeight)
return;
if (AutosizeStatus::probablyContainsASmallFixedNumberOfLines(style))
return;
style.setLineHeight({ minimumLineHeight, Fixed });
};
auto fontDescription = style.fontDescription();
auto initialComputedFontSize = fontDescription.computedSize();
auto specifiedFontSize = fontDescription.specifiedSize();
bool isCandidate = style.isIdempotentTextAutosizingCandidate();
if (!isCandidate && WTF::areEssentiallyEqual(initialComputedFontSize, specifiedFontSize))
return false;
auto adjustedFontSize = AutosizeStatus::idempotentTextSize(fontDescription.specifiedSize(), initialScale);
if (isCandidate && WTF::areEssentiallyEqual(initialComputedFontSize, adjustedFontSize))
return false;
if (!hasTextChild(element))
return false;
fontDescription.setComputedSize(isCandidate ? adjustedFontSize : specifiedFontSize);
style.setFontDescription(WTFMove(fontDescription));
style.fontCascade().update(&document().fontSelector());
// FIXME: We should restore computed line height to its original value in the case where the element is not
// an idempotent text autosizing candidate; otherwise, if an element that is a text autosizing candidate contains
// children which are not autosized, the non-autosized content will end up with a boosted line height.
if (isCandidate)
adjustLineHeightIfNeeded(adjustedFontSize);
return true;
}
#endif
void StyleResolver::adjustRenderStyle(RenderStyle& style, const RenderStyle& parentStyle, const RenderStyle* parentBoxStyle, const Element* element)
{
// If the composed tree parent has display:contents, the parent box style will be different from the parent style.
// We don't have it when resolving computed style for display:none subtree. Use parent style for adjustments in that case.
if (!parentBoxStyle)
parentBoxStyle = &parentStyle;
// Cache our original display.
style.setOriginalDisplay(style.display());
if (style.display() == DisplayType::Contents)
adjustDisplayContentsStyle(style, element);
if (style.display() != DisplayType::None && style.display() != DisplayType::Contents) {
if (element) {
// If we have a <td> that specifies a float property, in quirks mode we just drop the float
// property.
// Sites also commonly use display:inline/block on <td>s and <table>s. In quirks mode we force
// these tags to retain their display types.
if (document().inQuirksMode()) {
if (element->hasTagName(tdTag)) {
style.setDisplay(DisplayType::TableCell);
style.setFloating(Float::No);
} else if (is<HTMLTableElement>(*element))
style.setDisplay(style.isDisplayInlineType() ? DisplayType::InlineTable : DisplayType::Table);
}
if (element->hasTagName(tdTag) || element->hasTagName(thTag)) {
if (style.whiteSpace() == WhiteSpace::KHTMLNoWrap) {
// Figure out if we are really nowrapping or if we should just
// use normal instead. If the width of the cell is fixed, then
// we don't actually use WhiteSpace::NoWrap.
if (style.width().isFixed())
style.setWhiteSpace(WhiteSpace::Normal);
else
style.setWhiteSpace(WhiteSpace::NoWrap);
}
}
// Tables never support the -webkit-* values for text-align and will reset back to the default.
if (is<HTMLTableElement>(*element) && (style.textAlign() == TextAlignMode::WebKitLeft || style.textAlign() == TextAlignMode::WebKitCenter || style.textAlign() == TextAlignMode::WebKitRight))
style.setTextAlign(TextAlignMode::Start);
// Frames and framesets never honor position:relative or position:absolute. This is necessary to
// fix a crash where a site tries to position these objects. They also never honor display.
if (element->hasTagName(frameTag) || element->hasTagName(framesetTag)) {
style.setPosition(PositionType::Static);
style.setDisplay(DisplayType::Block);
}
// Ruby text does not support float or position. This might change with evolution of the specification.
if (element->hasTagName(rtTag)) {
style.setPosition(PositionType::Static);
style.setFloating(Float::No);
}
// User agents are expected to have a rule in their user agent stylesheet that matches th elements that have a parent
// node whose computed value for the 'text-align' property is its initial value, whose declaration block consists of
// just a single declaration that sets the 'text-align' property to the value 'center'.
// https://html.spec.whatwg.org/multipage/rendering.html#rendering
if (element->hasTagName(thTag) && !style.hasExplicitlySetTextAlign() && parentStyle.textAlign() == RenderStyle::initialTextAlign())
style.setTextAlign(TextAlignMode::Center);
if (element->hasTagName(legendTag))
style.setDisplay(DisplayType::Block);
}
// Absolute/fixed positioned elements, floating elements and the document element need block-like outside display.
if (style.hasOutOfFlowPosition() || style.isFloating() || (element && element->document().documentElement() == element))
style.setDisplay(equivalentBlockDisplay(style, document()));
// FIXME: Don't support this mutation for pseudo styles like first-letter or first-line, since it's not completely
// clear how that should work.
if (style.display() == DisplayType::Inline && style.styleType() == PseudoId::None && style.writingMode() != parentStyle.writingMode())
style.setDisplay(DisplayType::InlineBlock);
// After performing the display mutation, check table rows. We do not honor position:relative or position:sticky on
// table rows or cells. This has been established for position:relative in CSS2.1 (and caused a crash in containingBlock()
// on some sites).
if ((style.display() == DisplayType::TableHeaderGroup || style.display() == DisplayType::TableRowGroup
|| style.display() == DisplayType::TableFooterGroup || style.display() == DisplayType::TableRow)
&& style.position() == PositionType::Relative)
style.setPosition(PositionType::Static);
// writing-mode does not apply to table row groups, table column groups, table rows, and table columns.
// FIXME: Table cells should be allowed to be perpendicular or flipped with respect to the table, though.
if (style.display() == DisplayType::TableColumn || style.display() == DisplayType::TableColumnGroup || style.display() == DisplayType::TableFooterGroup
|| style.display() == DisplayType::TableHeaderGroup || style.display() == DisplayType::TableRow || style.display() == DisplayType::TableRowGroup
|| style.display() == DisplayType::TableCell)
style.setWritingMode(parentStyle.writingMode());
// FIXME: Since we don't support block-flow on flexible boxes yet, disallow setting
// of block-flow to anything other than TopToBottomWritingMode.
// https://bugs.webkit.org/show_bug.cgi?id=46418 - Flexible box support.
if (style.writingMode() != TopToBottomWritingMode && (style.display() == DisplayType::Box || style.display() == DisplayType::InlineBox))
style.setWritingMode(TopToBottomWritingMode);
// https://www.w3.org/TR/css-display/#transformations
// "A parent with a grid or flex display value blockifies the box’s display type."
if (parentBoxStyle->isDisplayFlexibleOrGridBox()) {
style.setFloating(Float::No);
style.setDisplay(equivalentBlockDisplay(style, document()));
}
}
// Make sure our z-index value is only applied if the object is positioned.
if (style.position() == PositionType::Static && !parentBoxStyle->isDisplayFlexibleOrGridBox())
style.setHasAutoZIndex();
// Auto z-index becomes 0 for the root element and transparent objects. This prevents
// cases where objects that should be blended as a single unit end up with a non-transparent
// object wedged in between them. Auto z-index also becomes 0 for objects that specify transforms/masks/reflections.
if (style.hasAutoZIndex()) {
if ((element && element->document().documentElement() == element)
|| style.opacity() < 1.0f
|| style.hasTransformRelatedProperty()
|| style.hasMask()
|| style.clipPath()
|| style.boxReflect()
|| style.hasFilter()
#if ENABLE(FILTERS_LEVEL_2)
|| style.hasBackdropFilter()
#endif
|| style.hasBlendMode()
|| style.hasIsolation()
|| style.position() == PositionType::Sticky
|| style.position() == PositionType::Fixed
|| style.willChangeCreatesStackingContext())
style.setZIndex(0);
}
if (element) {
// Textarea considers overflow visible as auto.
if (is<HTMLTextAreaElement>(*element)) {
style.setOverflowX(style.overflowX() == Overflow::Visible ? Overflow::Auto : style.overflowX());
style.setOverflowY(style.overflowY() == Overflow::Visible ? Overflow::Auto : style.overflowY());
}
// Disallow -webkit-user-modify on :pseudo and ::pseudo elements.
if (!element->shadowPseudoId().isNull())
style.setUserModify(UserModify::ReadOnly);
if (is<HTMLMarqueeElement>(*element)) {
// For now, <marquee> requires an overflow clip to work properly.
style.setOverflowX(Overflow::Hidden);
style.setOverflowY(Overflow::Hidden);
bool isVertical = style.marqueeDirection() == MarqueeDirection::Up || style.marqueeDirection() == MarqueeDirection::Down;
// Make horizontal marquees not wrap.
if (!isVertical) {
style.setWhiteSpace(WhiteSpace::NoWrap);
style.setTextAlign(TextAlignMode::Start);
}
// Apparently this is the expected legacy behavior.
if (isVertical && style.height().isAuto())
style.setHeight(Length(200, Fixed));
}
}
if (doesNotInheritTextDecoration(style, element))
style.setTextDecorationsInEffect(style.textDecoration());
else
style.addToTextDecorationsInEffect(style.textDecoration());
// If either overflow value is not visible, change to auto.
if (style.overflowX() == Overflow::Visible && style.overflowY() != Overflow::Visible) {
// FIXME: Once we implement pagination controls, overflow-x should default to hidden
// if overflow-y is set to -webkit-paged-x or -webkit-page-y. For now, we'll let it
// default to auto so we can at least scroll through the pages.
style.setOverflowX(Overflow::Auto);
} else if (style.overflowY() == Overflow::Visible && style.overflowX() != Overflow::Visible)
style.setOverflowY(Overflow::Auto);
// Call setStylesForPaginationMode() if a pagination mode is set for any non-root elements. If these
// styles are specified on a root element, then they will be incorporated in
// Style::createForDocument().
if ((style.overflowY() == Overflow::PagedX || style.overflowY() == Overflow::PagedY) && !(element && (element->hasTagName(htmlTag) || element->hasTagName(bodyTag))))
style.setColumnStylesFromPaginationMode(WebCore::paginationModeForRenderStyle(style));
// Table rows, sections and the table itself will support overflow:hidden and will ignore scroll/auto.
// FIXME: Eventually table sections will support auto and scroll.
if (style.display() == DisplayType::Table || style.display() == DisplayType::InlineTable
|| style.display() == DisplayType::TableRowGroup || style.display() == DisplayType::TableRow) {
if (style.overflowX() != Overflow::Visible && style.overflowX() != Overflow::Hidden)
style.setOverflowX(Overflow::Visible);
if (style.overflowY() != Overflow::Visible && style.overflowY() != Overflow::Hidden)
style.setOverflowY(Overflow::Visible);
}
// Menulists should have visible overflow
if (style.appearance() == MenulistPart) {
style.setOverflowX(Overflow::Visible);
style.setOverflowY(Overflow::Visible);
}
#if ENABLE(OVERFLOW_SCROLLING_TOUCH)
// Touch overflow scrolling creates a stacking context.
if (style.hasAutoZIndex() && style.useTouchOverflowScrolling() && (isScrollableOverflow(style.overflowX()) || isScrollableOverflow(style.overflowY())))
style.setZIndex(0);
#endif
// Cull out any useless layers and also repeat patterns into additional layers.
style.adjustBackgroundLayers();
style.adjustMaskLayers();
// Do the same for animations and transitions.
style.adjustAnimations();
style.adjustTransitions();
// Important: Intrinsic margins get added to controls before the theme has adjusted the style, since the theme will
// alter fonts and heights/widths.
if (is<HTMLFormControlElement>(element) && style.computedFontPixelSize() >= 11) {
// Don't apply intrinsic margins to image buttons. The designer knows how big the images are,
// so we have to treat all image buttons as though they were explicitly sized.
if (!is<HTMLInputElement>(*element) || !downcast<HTMLInputElement>(*element).isImageButton())
addIntrinsicMargins(style);
}
// Let the theme also have a crack at adjusting the style.
if (style.hasAppearance())
RenderTheme::singleton().adjustStyle(*this, style, element, m_state.hasUAAppearance(), m_state.borderData(), m_state.backgroundData(), m_state.backgroundColor());
// If we have first-letter pseudo style, do not share this style.
if (style.hasPseudoStyle(PseudoId::FirstLetter))
style.setUnique();
// FIXME: when dropping the -webkit prefix on transform-style, we should also have opacity < 1 cause flattening.
if (style.preserves3D() && (style.overflowX() != Overflow::Visible
|| style.overflowY() != Overflow::Visible
|| style.hasClip()
|| style.clipPath()
|| style.hasFilter()
#if ENABLE(FILTERS_LEVEL_2)
|| style.hasBackdropFilter()
#endif
|| style.hasBlendMode()))
style.setTransformStyle3D(TransformStyle3D::Flat);
if (is<SVGElement>(element))
adjustSVGElementStyle(downcast<SVGElement>(*element), style);
// If the inherited value of justify-items includes the 'legacy' keyword (plus 'left', 'right' or
// 'center'), 'legacy' computes to the the inherited value. Otherwise, 'auto' computes to 'normal'.
if (parentBoxStyle->justifyItems().positionType() == ItemPositionType::Legacy && style.justifyItems().position() == ItemPosition::Legacy)
style.setJustifyItems(parentBoxStyle->justifyItems());
#if ENABLE(POINTER_EVENTS)
style.setEffectiveTouchActions(computeEffectiveTouchActions(style, parentStyle.effectiveTouchActions()));
#endif
if (element) {
#if ENABLE(TEXT_AUTOSIZING)
adjustRenderStyleForTextAutosizing(style, *element);
#endif
adjustRenderStyleForSiteSpecificQuirks(style, *element);
}
}
void StyleResolver::adjustRenderStyleForSiteSpecificQuirks(RenderStyle& style, const Element& element)
{
if (document().quirks().needsGMailOverflowScrollQuirk()) {
// This turns sidebar scrollable without mouse move event.
static NeverDestroyed<AtomString> roleValue("navigation", AtomString::ConstructFromLiteral);
if (style.overflowY() == Overflow::Hidden && element.attributeWithoutSynchronization(roleAttr) == roleValue)
style.setOverflowY(Overflow::Auto);
}
if (document().quirks().needsYouTubeOverflowScrollQuirk()) {
// This turns sidebar scrollable without hover.
static NeverDestroyed<AtomString> idValue("guide-inner-content", AtomString::ConstructFromLiteral);
if (style.overflowY() == Overflow::Hidden && element.idForStyleResolution() == idValue)
style.setOverflowY(Overflow::Auto);
}
}
static void checkForOrientationChange(RenderStyle& style)
{
auto [fontOrientation, glyphOrientation] = style.fontAndGlyphOrientation();
const auto& fontDescription = style.fontDescription();
if (fontDescription.orientation() == fontOrientation && fontDescription.nonCJKGlyphOrientation() == glyphOrientation)
return;
auto newFontDescription = fontDescription;
newFontDescription.setNonCJKGlyphOrientation(glyphOrientation);
newFontDescription.setOrientation(fontOrientation);
style.setFontDescription(WTFMove(newFontDescription));
}
void StyleResolver::updateFont()
{
if (!m_state.fontDirty())
return;
auto& style = *m_state.style();
#if ENABLE(TEXT_AUTOSIZING)
checkForTextSizeAdjust(style);
#endif
checkForGenericFamilyChange(style, m_state.parentStyle());
checkForZoomChange(style, m_state.parentStyle());
checkForOrientationChange(style);
style.fontCascade().update(&document().fontSelector());
if (m_state.fontSizeHasViewportUnits())
style.setHasViewportUnits(true);
m_state.setFontDirty(false);
}
Vector<RefPtr<StyleRule>> StyleResolver::styleRulesForElement(const Element* element, unsigned rulesToInclude)
{
return pseudoStyleRulesForElement(element, PseudoId::None, rulesToInclude);
}
Vector<RefPtr<StyleRule>> StyleResolver::pseudoStyleRulesForElement(const Element* element, PseudoId pseudoId, unsigned rulesToInclude)
{
if (!element)
return { };
m_state = State(*element, nullptr);
ElementRuleCollector collector(*element, m_ruleSets, m_state.selectorFilter());
collector.setMode(SelectorChecker::Mode::CollectingRules);
collector.setPseudoStyleRequest(PseudoStyleRequest(pseudoId));
collector.setMedium(&m_mediaQueryEvaluator);
collector.setIncludeEmptyRules(rulesToInclude & EmptyCSSRules);
if (rulesToInclude & UAAndUserCSSRules) {
// First we match rules from the user agent sheet.
collector.matchUARules();
// Now we check user sheet rules.
if (m_matchAuthorAndUserStyles)
collector.matchUserRules();
}
if (m_matchAuthorAndUserStyles && (rulesToInclude & AuthorCSSRules))
collector.matchAuthorRules();
return collector.matchedRuleList();
}
static bool elementTypeHasAppearanceFromUAStyle(const Element& element)
{
// NOTE: This is just a hard-coded list of elements that have some -webkit-appearance value in html.css
const auto& localName = element.localName();
return localName == HTMLNames::inputTag
|| localName == HTMLNames::textareaTag
|| localName == HTMLNames::buttonTag
|| localName == HTMLNames::progressTag
|| localName == HTMLNames::selectTag
|| localName == HTMLNames::meterTag;
}
unsigned StyleResolver::computeMatchedPropertiesHash(const MatchResult& matchResult)
{
return StringHasher::hashMemory(matchResult.userAgentDeclarations.data(), sizeof(MatchedProperties) * matchResult.userAgentDeclarations.size())
^ StringHasher::hashMemory(matchResult.userDeclarations.data(), sizeof(MatchedProperties) * matchResult.userDeclarations.size())
^ StringHasher::hashMemory(matchResult.authorDeclarations.data(), sizeof(MatchedProperties) * matchResult.authorDeclarations.size());
}
const StyleResolver::MatchedPropertiesCacheItem* StyleResolver::findFromMatchedPropertiesCache(unsigned hash, const MatchResult& matchResult)
{
ASSERT(hash);
MatchedPropertiesCache::iterator it = m_matchedPropertiesCache.find(hash);
if (it == m_matchedPropertiesCache.end())
return nullptr;
auto& cacheItem = it->value;
if (matchResult.userAgentDeclarations != cacheItem.userAgentDeclarations || matchResult.userDeclarations != cacheItem.userDeclarations || matchResult.authorDeclarations != cacheItem.authorDeclarations)
return nullptr;
return &cacheItem;
}
void StyleResolver::addToMatchedPropertiesCache(const RenderStyle* style, const RenderStyle* parentStyle, unsigned hash, const MatchResult& matchResult)
{
static const unsigned matchedDeclarationCacheAdditionsBetweenSweeps = 100;
if (++m_matchedPropertiesCacheAdditionsSinceLastSweep >= matchedDeclarationCacheAdditionsBetweenSweeps
&& !m_matchedPropertiesCacheSweepTimer.isActive()) {
static const Seconds matchedDeclarationCacheSweepTime { 1_min };
m_matchedPropertiesCacheSweepTimer.startOneShot(matchedDeclarationCacheSweepTime);
}
ASSERT(hash);
// Note that we don't cache the original RenderStyle instance. It may be further modified.
// The RenderStyle in the cache is really just a holder for the substructures and never used as-is.
MatchedPropertiesCacheItem cacheItem(matchResult, style, parentStyle);
m_matchedPropertiesCache.add(hash, WTFMove(cacheItem));
}
void StyleResolver::invalidateMatchedPropertiesCache()
{
m_matchedPropertiesCache.clear();
}
void StyleResolver::clearCachedPropertiesAffectedByViewportUnits()
{
Vector<unsigned, 16> toRemove;
for (auto& cacheKeyValue : m_matchedPropertiesCache) {
if (cacheKeyValue.value.renderStyle->hasViewportUnits())
toRemove.append(cacheKeyValue.key);
}
for (auto key : toRemove)
m_matchedPropertiesCache.remove(key);
}
static bool isCacheableInMatchedPropertiesCache(const Element& element, const RenderStyle* style, const RenderStyle* parentStyle)
{
// FIXME: Writing mode and direction properties modify state when applying to document element by calling
// Document::setWritingMode/DirectionSetOnDocumentElement. We can't skip the applying by caching.
if (&element == element.document().documentElement())
return false;
// content:attr() value depends on the element it is being applied to.
if (style->hasAttrContent() || (style->styleType() != PseudoId::None && parentStyle->hasAttrContent()))
return false;
if (style->hasAppearance())
return false;
if (style->zoom() != RenderStyle::initialZoom())
return false;
if (style->writingMode() != RenderStyle::initialWritingMode() || style->direction() != RenderStyle::initialDirection())
return false;
// The cache assumes static knowledge about which properties are inherited.
if (style->hasExplicitlyInheritedProperties())
return false;
return true;
}
void extractDirectionAndWritingMode(const RenderStyle& style, const MatchResult& matchResult, TextDirection& direction, WritingMode& writingMode)
{
direction = style.direction();
writingMode = style.writingMode();
bool hadImportantWritingMode = false;
bool hadImportantDirection = false;
for (auto* matchedDeclarations : { &matchResult.userAgentDeclarations, &matchResult.userDeclarations, &matchResult.authorDeclarations }) {
for (const auto& matchedProperties : *matchedDeclarations) {
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()) {
writingMode = downcast<CSSPrimitiveValue>(*property.value());
hadImportantWritingMode = property.isImportant();
}
break;
case CSSPropertyDirection:
if (!hadImportantDirection || property.isImportant()) {
direction = downcast<CSSPrimitiveValue>(*property.value());
hadImportantDirection = property.isImportant();
}
break;
default:
break;
}
}
}
}
}
void StyleResolver::applyMatchedProperties(const MatchResult& matchResult, const Element& element, ShouldUseMatchedPropertiesCache shouldUseMatchedPropertiesCache)
{
State& state = m_state;
unsigned cacheHash = shouldUseMatchedPropertiesCache && matchResult.isCacheable ? computeMatchedPropertiesHash(matchResult) : 0;
bool applyInheritedOnly = false;
const MatchedPropertiesCacheItem* cacheItem = nullptr;
if (cacheHash && (cacheItem = findFromMatchedPropertiesCache(cacheHash, matchResult))
&& isCacheableInMatchedPropertiesCache(element, state.style(), state.parentStyle())) {
// We can build up the style by copying non-inherited properties from an earlier style object built using the same exact
// style declarations. We then only need to apply the inherited properties, if any, as their values can depend on the
// element context. This is fast and saves memory by reusing the style data structures.
state.style()->copyNonInheritedFrom(*cacheItem->renderStyle);
if (state.parentStyle()->inheritedDataShared(cacheItem->parentRenderStyle.get()) && !isAtShadowBoundary(element)) {
InsideLink linkStatus = state.style()->insideLink();
// If the cache item parent style has identical inherited properties to the current parent style then the
// resulting style will be identical too. We copy the inherited properties over from the cache and are done.
state.style()->inheritFrom(*cacheItem->renderStyle);
// Unfortunately the link status is treated like an inherited property. We need to explicitly restore it.
state.style()->setInsideLink(linkStatus);
return;
}
applyInheritedOnly = true;
}
// Directional properties (*-before/after) are aliases that depend on the TextDirection and WritingMode.
// These must be resolved before we can begin the property cascade.
TextDirection direction;
WritingMode writingMode;
extractDirectionAndWritingMode(*state.style(), matchResult, direction, writingMode);
if (elementTypeHasAppearanceFromUAStyle(*state.element())) {
// FIXME: This is such a hack.
// Find out if there's a -webkit-appearance property in effect from the UA sheet.
// If so, we cache the border and background styles so that RenderTheme::adjustStyle()
// can look at them later to figure out if this is a styled form control or not.
Style::PropertyCascade cascade(direction, writingMode);
cascade.addNormalMatches(matchResult, Style::CascadeLevel::UserAgent, applyInheritedOnly);
cascade.addImportantMatches(matchResult, Style::CascadeLevel::UserAgent, applyInheritedOnly);
ApplyCascadedPropertyState applyState { this, &cascade, &matchResult };
applyCascadedProperties(CSSPropertyWebkitRubyPosition, CSSPropertyWebkitRubyPosition, applyState);
adjustStyleForInterCharacterRuby();
#if ENABLE(DARK_MODE_CSS)
// Supported color schemes can affect resolved colors, so we need to apply that property before any color properties.
applyCascadedProperties(CSSPropertyColorScheme, CSSPropertyColorScheme, applyState);
#endif
applyCascadedProperties(firstCSSProperty, lastHighPriorityProperty, applyState);
// If our font got dirtied, update it now.
updateFont();
// Now resolve remaining custom properties and the rest, in any order
for (auto it = cascade.customProperties().begin(); it != cascade.customProperties().end(); ++it)
applyCascadedCustomProperty(it->key, applyState);
applyCascadedProperties(firstLowPriorityProperty, lastCSSProperty, applyState);
state.cacheBorderAndBackground();
}
Style::PropertyCascade cascade(direction, writingMode);
cascade.addNormalMatches(matchResult, Style::CascadeLevel::UserAgent, applyInheritedOnly);
cascade.addNormalMatches(matchResult, Style::CascadeLevel::User, applyInheritedOnly);
cascade.addNormalMatches(matchResult, Style::CascadeLevel::Author, applyInheritedOnly);
cascade.addImportantMatches(matchResult, Style::CascadeLevel::Author, applyInheritedOnly);
cascade.addImportantMatches(matchResult, Style::CascadeLevel::User, applyInheritedOnly);
cascade.addImportantMatches(matchResult, Style::CascadeLevel::UserAgent, applyInheritedOnly);
ApplyCascadedPropertyState applyState { this, &cascade, &matchResult };
applyCascadedProperties(CSSPropertyWebkitRubyPosition, CSSPropertyWebkitRubyPosition, applyState);
adjustStyleForInterCharacterRuby();
#if ENABLE(DARK_MODE_CSS)
// Supported color schemes can affect resolved colors, so we need to apply that property before any color properties.
applyCascadedProperties(CSSPropertyColorScheme, CSSPropertyColorScheme, applyState);
#endif
applyCascadedProperties(firstCSSProperty, lastHighPriorityProperty, applyState);
// If the effective zoom value changes, we can't use the matched properties cache. Start over.
if (cacheItem && cacheItem->renderStyle->effectiveZoom() != state.style()->effectiveZoom())
return applyMatchedProperties(matchResult, element, DoNotUseMatchedPropertiesCache);
// If our font got dirtied, update it now.
updateFont();
// If the font changed, we can't use the matched properties cache. Start over.
if (cacheItem && cacheItem->renderStyle->fontDescription() != state.style()->fontDescription())
return applyMatchedProperties(matchResult, element, DoNotUseMatchedPropertiesCache);
// Now resolve remaining custom properties and the rest, in any order
for (auto it = cascade.customProperties().begin(); it != cascade.customProperties().end(); ++it)
applyCascadedCustomProperty(it->key, applyState);
applyCascadedProperties(firstLowPriorityProperty, lastCSSProperty, applyState);
// Finally, some properties must be applied in the order they were parsed.
// There are some CSS properties that affect the same RenderStyle values,
// so to preserve behavior, we queue them up during cascade and flush here.
cascade.applyDeferredProperties(*this, applyState);
ASSERT(!state.fontDirty());
if (cacheItem || !cacheHash)
return;
if (!isCacheableInMatchedPropertiesCache(*state.element(), state.style(), state.parentStyle()))
return;
addToMatchedPropertiesCache(state.style(), state.parentStyle(), cacheHash, matchResult);
}
void StyleResolver::applyPropertyToStyle(CSSPropertyID id, CSSValue* value, std::unique_ptr<RenderStyle> style)
{
m_state = State();
m_state.setParentStyle(RenderStyle::clonePtr(*style));
m_state.setStyle(WTFMove(style));
applyPropertyToCurrentStyle(id, value);
}
void StyleResolver::applyPropertyToCurrentStyle(CSSPropertyID id, CSSValue* value)
{
ApplyCascadedPropertyState applyState { this, nullptr, nullptr };
if (value)
applyProperty(id, value, applyState);
}
inline bool 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 CSSPropertyWebkitTextEmphasisColor:
case CSSPropertyWebkitTextFillColor:
case CSSPropertyWebkitTextStrokeColor:
case CSSPropertyFill:
case CSSPropertyStroke:
case CSSPropertyStrokeColor:
return true;
default:
break;
}
return false;
}
// SVG handles zooming in a different way compared to CSS. The whole document is scaled instead
// of each individual length value in the render style / tree. CSSPrimitiveValue::computeLength*()
// multiplies each resolved length with the zoom multiplier - so for SVG we need to disable that.
// Though all CSS values that can be applied to outermost <svg> elements (width/height/border/padding...)
// need to respect the scaling. RenderBox (the parent class of RenderSVGRoot) grabs values like
// width/height/border/padding/... from the RenderStyle -> for SVG these values would never scale,
// if we'd pass a 1.0 zoom factor everyhwere. So we only pass a zoom factor of 1.0 for specific
// properties that are NOT allowed to scale within a zoomed SVG document (letter/word-spacing/font-size).
bool StyleResolver::useSVGZoomRules() const
{
return m_state.element() && m_state.element()->isSVGElement();
}
// Scale with/height properties on inline SVG root.
bool StyleResolver::useSVGZoomRulesForLength() const
{
return is<SVGElement>(m_state.element()) && !(is<SVGSVGElement>(*m_state.element()) && m_state.element()->parentNode());
}
Style::PropertyCascade* StyleResolver::cascadedPropertiesForRollback(const MatchResult& matchResult)
{
TextDirection direction;
WritingMode writingMode;
extractDirectionAndWritingMode(*state().style(), matchResult, direction, writingMode);
switch (state().cascadeLevel()) {
case Style::CascadeLevel::Author: {
auto* authorRollback = state().authorRollback();
if (authorRollback)
return authorRollback;
auto newAuthorRollback = makeUnique<Style::PropertyCascade>(direction, writingMode);
// This special rollback cascade contains UA rules and user rules but no author rules.
newAuthorRollback->addNormalMatches(matchResult, Style::CascadeLevel::UserAgent, false);
newAuthorRollback->addNormalMatches(matchResult, Style::CascadeLevel::User, false);
newAuthorRollback->addImportantMatches(matchResult, Style::CascadeLevel::User, false);
newAuthorRollback->addImportantMatches(matchResult, Style::CascadeLevel::UserAgent, false);
state().setAuthorRollback(newAuthorRollback);
return state().authorRollback();
}
case Style::CascadeLevel::User: {
auto* userRollback = state().userRollback();
if (userRollback)
return userRollback;
auto newUserRollback = makeUnique<Style::PropertyCascade>(direction, writingMode);
// This special rollback cascade contains only UA rules.
newUserRollback->addNormalMatches(matchResult, Style::CascadeLevel::UserAgent, false);
newUserRollback->addImportantMatches(matchResult, Style::CascadeLevel::UserAgent, false);
state().setUserRollback(newUserRollback);
return state().userRollback();
}
case Style::CascadeLevel::UserAgent:
break;
}
ASSERT_NOT_REACHED();
return nullptr;
}
void StyleResolver::applyProperty(CSSPropertyID id, CSSValue* value, ApplyCascadedPropertyState& applyState, SelectorChecker::LinkMatchMask linkMatchMask)
{
auto* matchResult = applyState.matchResult;
ASSERT_WITH_MESSAGE(!isShorthandCSSProperty(id), "Shorthand property id = %d wasn't expanded at parsing time", id);
State& state = m_state;
RefPtr<CSSValue> valueToApply = value;
if (value->hasVariableReferences()) {
valueToApply = resolvedVariableValue(id, *value, applyState);
// If appliedProperties already has this id, then we detected a cycle, and this value should be unset.
if (!valueToApply || applyState.appliedProperties.get(id)) {
if (CSSProperty::isInheritedProperty(id))
valueToApply = CSSValuePool::singleton().createInheritedValue();
else
valueToApply = CSSValuePool::singleton().createExplicitInitialValue();
}
}
if (CSSProperty::isDirectionAwareProperty(id)) {
CSSPropertyID newId = CSSProperty::resolveDirectionAwareProperty(id, state.style()->direction(), state.style()->writingMode());
ASSERT(newId != id);
return applyProperty(newId, valueToApply.get(), applyState, linkMatchMask);
}
CSSValue* valueToCheckForInheritInitial = valueToApply.get();
CSSCustomPropertyValue* customPropertyValue = nullptr;
CSSValueID customPropertyValueID = CSSValueInvalid;
CSSRegisteredCustomProperty* customPropertyRegistered = nullptr;
if (id == CSSPropertyCustom) {
customPropertyValue = &downcast<CSSCustomPropertyValue>(*valueToApply);
ASSERT(customPropertyValue->isResolved());
if (WTF::holds_alternative<CSSValueID>(customPropertyValue->value()))
customPropertyValueID = WTF::get<CSSValueID>(customPropertyValue->value());
auto& name = customPropertyValue->name();
customPropertyRegistered = document().getCSSRegisteredCustomPropertySet().get(name);
}
bool isInherit = state.parentStyle() ? valueToCheckForInheritInitial->isInheritedValue() || customPropertyValueID == CSSValueInherit : false;
bool isInitial = valueToCheckForInheritInitial->isInitialValue() || customPropertyValueID == CSSValueInitial || (!state.parentStyle() && (valueToCheckForInheritInitial->isInheritedValue() || customPropertyValueID == CSSValueInherit));
bool isUnset = valueToCheckForInheritInitial->isUnsetValue() || customPropertyValueID == CSSValueUnset;
bool isRevert = valueToCheckForInheritInitial->isRevertValue() || customPropertyValueID == CSSValueRevert;
if (isRevert) {
if (state.cascadeLevel() == Style::CascadeLevel::UserAgent || !matchResult)
isUnset = true;
else {
// Fetch the correct rollback object from the state, building it if necessary.
// This requires having the original MatchResult available.
auto* rollback = cascadedPropertiesForRollback(*matchResult);
ASSERT(rollback);
// With the 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.
if (customPropertyValue) {
if (customPropertyRegistered && customPropertyRegistered->inherits && rollback->hasCustomProperty(customPropertyValue->name())) {
auto property = rollback->customProperty(customPropertyValue->name());
if (property.cssValue[linkMatchMask])
applyProperty(property.id, property.cssValue[linkMatchMask], applyState, linkMatchMask);
return;
}
} else if (rollback->hasProperty(id)) {
auto& property = rollback->property(id);
if (property.cssValue[linkMatchMask])
applyProperty(property.id, property.cssValue[linkMatchMask], applyState, linkMatchMask);
return;
}
isUnset = true;
}
}
if (isUnset) {
if (CSSProperty::isInheritedProperty(id))
isInherit = true;
else
isInitial = true;
}
ASSERT(!isInherit || !isInitial); // isInherit -> !isInitial && isInitial -> !isInherit
if (!state.applyPropertyToRegularStyle() && (!state.applyPropertyToVisitedLinkStyle() || !isValidVisitedLinkProperty(id))) {
// Limit the properties that can be applied to only the ones honored by :visited.
return;
}
if (isInherit && !CSSProperty::isInheritedProperty(id))
state.style()->setHasExplicitlyInheritedProperties();
#if ENABLE(CSS_PAINTING_API)
if (is<CSSPaintImageValue>(*valueToApply)) {
auto& name = downcast<CSSPaintImageValue>(*valueToApply).name();
if (auto* paintWorklet = document().paintWorkletGlobalScopeForName(name)) {
auto locker = holdLock(paintWorklet->paintDefinitionLock());
if (auto* registration = paintWorklet->paintDefinitionMap().get(name)) {
for (auto& property : registration->inputProperties)
state.style()->addCustomPaintWatchProperty(property);
}
}
}
#endif
// Use the generated StyleBuilder.
StyleBuilder::applyProperty(id, *this, *valueToApply, isInitial, isInherit, customPropertyRegistered);
}
RefPtr<CSSValue> StyleResolver::resolvedVariableValue(CSSPropertyID propID, const CSSValue& value, ApplyCascadedPropertyState& state) const
{
CSSParser parser(document());
return parser.parseValueWithVariableReferences(propID, value, state);
}
RefPtr<StyleImage> StyleResolver::styleImage(CSSValue& value)
{
if (is<CSSImageGeneratorValue>(value)) {
if (is<CSSGradientValue>(value))
return StyleGeneratedImage::create(downcast<CSSGradientValue>(value).gradientWithStylesResolved(*this));
if (is<CSSFilterImageValue>(value)) {
// FilterImage needs to calculate FilterOperations.
downcast<CSSFilterImageValue>(value).createFilterOperations(this);
}
return StyleGeneratedImage::create(downcast<CSSImageGeneratorValue>(value));
}
if (is<CSSImageValue>(value) || is<CSSImageSetValue>(value) || is<CSSCursorImageValue>(value))
return StyleCachedImage::create(value);
return nullptr;
}
#if ENABLE(TEXT_AUTOSIZING)
void StyleResolver::checkForTextSizeAdjust(RenderStyle& style)
{
if (style.textSizeAdjust().isAuto()
|| !settings().textAutosizingEnabled()
|| (settings().textAutosizingUsesIdempotentMode() && !style.textSizeAdjust().isNone()))
return;
auto newFontDescription = style.fontDescription();
if (!style.textSizeAdjust().isNone())
newFontDescription.setComputedSize(newFontDescription.specifiedSize() * style.textSizeAdjust().multiplier());
else
newFontDescription.setComputedSize(newFontDescription.specifiedSize());
style.setFontDescription(WTFMove(newFontDescription));
}
#endif
void StyleResolver::checkForZoomChange(RenderStyle& style, const RenderStyle* parentStyle)
{
if (!parentStyle)
return;
if (style.effectiveZoom() == parentStyle->effectiveZoom() && style.textZoom() == parentStyle->textZoom())
return;
const auto& childFont = style.fontDescription();
auto newFontDescription = childFont;
setFontSize(newFontDescription, childFont.specifiedSize());
style.setFontDescription(WTFMove(newFontDescription));
}
void StyleResolver::checkForGenericFamilyChange(RenderStyle& style, const RenderStyle* parentStyle)
{
const auto& childFont = style.fontDescription();
if (childFont.isAbsoluteSize() || !parentStyle)
return;
const auto& parentFont = parentStyle->fontDescription();
if (childFont.useFixedDefaultSize() == parentFont.useFixedDefaultSize())
return;
// We know the parent is monospace or the child is monospace, and that font
// size was unspecified. We want to scale our font size as appropriate.
// If the font uses a keyword size, then we refetch from the table rather than
// multiplying by our scale factor.
float size;
if (CSSValueID sizeIdentifier = childFont.keywordSizeAsIdentifier())
size = Style::fontSizeForKeyword(sizeIdentifier, childFont.useFixedDefaultSize(), document());
else {
float fixedScaleFactor = (settings().defaultFixedFontSize() && settings().defaultFontSize())
? static_cast<float>(settings().defaultFixedFontSize()) / settings().defaultFontSize()
: 1;
size = parentFont.useFixedDefaultSize() ?
childFont.specifiedSize() / fixedScaleFactor :
childFont.specifiedSize() * fixedScaleFactor;
}
auto newFontDescription = childFont;
setFontSize(newFontDescription, size);
style.setFontDescription(WTFMove(newFontDescription));
}
void StyleResolver::initializeFontStyle()
{
FontCascadeDescription fontDescription;
fontDescription.setRenderingMode(settings().fontRenderingMode());
fontDescription.setOneFamily(standardFamily);
fontDescription.setKeywordSizeFromIdentifier(CSSValueMedium);
setFontSize(fontDescription, Style::fontSizeForKeyword(CSSValueMedium, false, document()));
fontDescription.setShouldAllowUserInstalledFonts(settings().shouldAllowUserInstalledFonts() ? AllowUserInstalledFonts::Yes : AllowUserInstalledFonts::No);
setFontDescription(WTFMove(fontDescription));
}
void StyleResolver::setFontSize(FontCascadeDescription& fontDescription, float size)
{
fontDescription.setSpecifiedSize(size);
fontDescription.setComputedSize(Style::computedFontSizeFromSpecifiedSize(size, fontDescription.isAbsoluteSize(), useSVGZoomRules(), m_state.style(), document()));
}
bool StyleResolver::colorFromPrimitiveValueIsDerivedFromElement(const CSSPrimitiveValue& value)
{
switch (value.valueID()) {
case CSSValueWebkitText:
case CSSValueWebkitLink:
case CSSValueWebkitActivelink:
case CSSValueCurrentcolor:
return true;
default:
return false;
}
}
Color StyleResolver::colorFromPrimitiveValue(const CSSPrimitiveValue& value, bool forVisitedLink) const
{
if (value.isRGBColor())
return value.color();
auto identifier = value.valueID();
switch (identifier) {
case CSSValueWebkitText:
return document().textColor();
case CSSValueWebkitLink:
return (m_state.element()->isLink() && forVisitedLink) ? document().visitedLinkColor() : document().linkColor();
case CSSValueWebkitActivelink:
return document().activeLinkColor();
case CSSValueWebkitFocusRingColor:
return RenderTheme::singleton().focusRingColor(document().styleColorOptions(m_state.style()));
case CSSValueCurrentcolor:
// Color is an inherited property so depending on it effectively makes the property inherited.
// FIXME: Setting the flag as a side effect of calling this function is a bit oblique. Can we do better?
m_state.style()->setHasExplicitlyInheritedProperties();
return m_state.style()->color();
default:
return StyleColor::colorFromKeyword(identifier, document().styleColorOptions(m_state.style()));
}
}
void StyleResolver::addViewportDependentMediaQueryResult(const MediaQueryExpression& expression, bool result)
{
m_viewportDependentMediaQueryResults.append(MediaQueryResult { expression, result });
}
bool StyleResolver::hasMediaQueriesAffectedByViewportChange() const
{
LOG(MediaQueries, "StyleResolver::hasMediaQueriesAffectedByViewportChange evaluating queries");
for (auto& result : m_viewportDependentMediaQueryResults) {
if (m_mediaQueryEvaluator.evaluate(result.expression) != result.result)
return true;
}
return false;
}
void StyleResolver::addAccessibilitySettingsDependentMediaQueryResult(const MediaQueryExpression& expression, bool result)
{
m_accessibilitySettingsDependentMediaQueryResults.append(MediaQueryResult { expression, result });
}
bool StyleResolver::hasMediaQueriesAffectedByAccessibilitySettingsChange() const
{
LOG(MediaQueries, "StyleResolver::hasMediaQueriesAffectedByAccessibilitySettingsChange evaluating queries");
for (auto& result : m_accessibilitySettingsDependentMediaQueryResults) {
if (m_mediaQueryEvaluator.evaluate(result.expression) != result.result)
return true;
}
return false;
}
void StyleResolver::addAppearanceDependentMediaQueryResult(const MediaQueryExpression& expression, bool result)
{
m_appearanceDependentMediaQueryResults.append(MediaQueryResult { expression, result });
}
bool StyleResolver::hasMediaQueriesAffectedByAppearanceChange() const
{
LOG(MediaQueries, "StyleResolver::hasMediaQueriesAffectedByAppearanceChange evaluating queries");
for (auto& result : m_appearanceDependentMediaQueryResults) {
if (m_mediaQueryEvaluator.evaluate(result.expression) != result.result)
return true;
}
return false;
}
static FilterOperation::OperationType filterOperationForType(CSSValueID type)
{
switch (type) {
case CSSValueUrl:
return FilterOperation::REFERENCE;
case CSSValueGrayscale:
return FilterOperation::GRAYSCALE;
case CSSValueSepia:
return FilterOperation::SEPIA;
case CSSValueSaturate:
return FilterOperation::SATURATE;
case CSSValueHueRotate:
return FilterOperation::HUE_ROTATE;
case CSSValueInvert:
return FilterOperation::INVERT;
case CSSValueAppleInvertLightness:
return FilterOperation::APPLE_INVERT_LIGHTNESS;
case CSSValueOpacity:
return FilterOperation::OPACITY;
case CSSValueBrightness:
return FilterOperation::BRIGHTNESS;
case CSSValueContrast:
return FilterOperation::CONTRAST;
case CSSValueBlur:
return FilterOperation::BLUR;
case CSSValueDropShadow:
return FilterOperation::DROP_SHADOW;
default:
break;
}
ASSERT_NOT_REACHED();
return FilterOperation::NONE;
}
bool StyleResolver::createFilterOperations(const CSSValue& inValue, FilterOperations& outOperations)
{
State& state = m_state;
ASSERT(outOperations.isEmpty());
if (is<CSSPrimitiveValue>(inValue)) {
auto& primitiveValue = downcast<CSSPrimitiveValue>(inValue);
if (primitiveValue.valueID() == CSSValueNone)
return true;
}
if (!is<CSSValueList>(inValue))
return false;
FilterOperations operations;
for (auto& currentValue : downcast<CSSValueList>(inValue)) {
if (is<CSSPrimitiveValue>(currentValue)) {
auto& primitiveValue = downcast<CSSPrimitiveValue>(currentValue.get());
if (!primitiveValue.isURI())
continue;
String cssUrl = primitiveValue.stringValue();
URL url = document().completeURL(cssUrl);
auto operation = ReferenceFilterOperation::create(cssUrl, url.fragmentIdentifier());
operations.operations().append(WTFMove(operation));
continue;
}
if (!is<CSSFunctionValue>(currentValue))
continue;
auto& filterValue = downcast<CSSFunctionValue>(currentValue.get());
FilterOperation::OperationType operationType = filterOperationForType(filterValue.name());
// Check that all parameters are primitive values, with the
// exception of drop shadow which has a CSSShadowValue parameter.
const CSSPrimitiveValue* firstValue = nullptr;
if (operationType != FilterOperation::DROP_SHADOW) {
bool haveNonPrimitiveValue = false;
for (unsigned j = 0; j < filterValue.length(); ++j) {
if (!is<CSSPrimitiveValue>(*filterValue.itemWithoutBoundsCheck(j))) {
haveNonPrimitiveValue = true;
break;
}
}
if (haveNonPrimitiveValue)
continue;
if (filterValue.length())
firstValue = downcast<CSSPrimitiveValue>(filterValue.itemWithoutBoundsCheck(0));
}
switch (operationType) {
case FilterOperation::GRAYSCALE:
case FilterOperation::SEPIA:
case FilterOperation::SATURATE: {
double amount = 1;
if (filterValue.length() == 1) {
amount = firstValue->doubleValue();
if (firstValue->isPercentage())
amount /= 100;
}
operations.operations().append(BasicColorMatrixFilterOperation::create(amount, operationType));
break;
}
case FilterOperation::HUE_ROTATE: {
double angle = 0;
if (filterValue.length() == 1)
angle = firstValue->computeDegrees();
operations.operations().append(BasicColorMatrixFilterOperation::create(angle, operationType));
break;
}
case FilterOperation::INVERT:
case FilterOperation::BRIGHTNESS:
case FilterOperation::CONTRAST:
case FilterOperation::OPACITY: {
double amount = 1;
if (filterValue.length() == 1) {
amount = firstValue->doubleValue();
if (firstValue->isPercentage())
amount /= 100;
}
operations.operations().append(BasicComponentTransferFilterOperation::create(amount, operationType));
break;
}
case FilterOperation::APPLE_INVERT_LIGHTNESS: {
operations.operations().append(InvertLightnessFilterOperation::create());
break;
}
case FilterOperation::BLUR: {
Length stdDeviation = Length(0, Fixed);
if (filterValue.length() >= 1)
stdDeviation = convertToFloatLength(firstValue, state.cssToLengthConversionData());
if (stdDeviation.isUndefined())
return false;
operations.operations().append(BlurFilterOperation::create(stdDeviation));
break;
}
case FilterOperation::DROP_SHADOW: {
if (filterValue.length() != 1)
return false;
const auto* cssValue = filterValue.itemWithoutBoundsCheck(0);
if (!is<CSSShadowValue>(cssValue))
continue;
const auto& item = downcast<CSSShadowValue>(*cssValue);
int x = item.x->computeLength<int>(state.cssToLengthConversionData());
int y = item.y->computeLength<int>(state.cssToLengthConversionData());
IntPoint location(x, y);
int blur = item.blur ? item.blur->computeLength<int>(state.cssToLengthConversionData()) : 0;
Color color;
if (item.color)
color = colorFromPrimitiveValue(*item.color);
operations.operations().append(DropShadowFilterOperation::create(location, blur, color.isValid() ? color : Color::transparent));
break;
}
default:
ASSERT_NOT_REACHED();
break;
}
}
outOperations = operations;
return true;
}
void StyleResolver::applyCascadedCustomProperty(const String& name, ApplyCascadedPropertyState& state)
{
if (state.appliedCustomProperties.contains(name) || !state.cascade->customProperties().contains(name))
return;
auto property = state.cascade->customProperties().get(name);
bool inCycle = state.inProgressPropertiesCustom.contains(name);
for (auto index : { SelectorChecker::MatchDefault, SelectorChecker::MatchLink, SelectorChecker::MatchVisited }) {
if (!property.cssValue[index])
continue;
if (index != SelectorChecker::MatchDefault && this->state().style()->insideLink() == InsideLink::NotInside)
continue;
Ref<CSSCustomPropertyValue> valueToApply = CSSCustomPropertyValue::create(downcast<CSSCustomPropertyValue>(*property.cssValue[index]));
if (inCycle) {
state.appliedCustomProperties.add(name); // Make sure we do not try to apply this property again while resolving it.
valueToApply = CSSCustomPropertyValue::createWithID(name, CSSValueInvalid);
}
state.inProgressPropertiesCustom.add(name);
if (WTF::holds_alternative<Ref<CSSVariableReferenceValue>>(valueToApply->value())) {
RefPtr<CSSValue> parsedValue = resolvedVariableValue(CSSPropertyCustom, valueToApply.get(), state);
if (state.appliedCustomProperties.contains(name))
return; // There was a cycle and the value was reset, so bail.
if (!parsedValue)
parsedValue = CSSCustomPropertyValue::createWithID(name, CSSValueUnset);
valueToApply = downcast<CSSCustomPropertyValue>(*parsedValue);
}
if (state.inProgressPropertiesCustom.contains(name)) {
if (index == SelectorChecker::MatchDefault) {
this->state().setApplyPropertyToRegularStyle(true);
this->state().setApplyPropertyToVisitedLinkStyle(false);
}
if (index == SelectorChecker::MatchLink) {
this->state().setApplyPropertyToRegularStyle(true);
this->state().setApplyPropertyToVisitedLinkStyle(false);
}
if (index == SelectorChecker::MatchVisited) {
this->state().setApplyPropertyToRegularStyle(false);
this->state().setApplyPropertyToVisitedLinkStyle(true);
}
applyProperty(CSSPropertyCustom, valueToApply.ptr(), state, index);
}
}
state.inProgressPropertiesCustom.remove(name);
state.appliedCustomProperties.add(name);
for (auto index : { SelectorChecker::MatchDefault, SelectorChecker::MatchLink, SelectorChecker::MatchVisited }) {
if (!property.cssValue[index])
continue;
if (index != SelectorChecker::MatchDefault && this->state().style()->insideLink() == InsideLink::NotInside)
continue;
Ref<CSSCustomPropertyValue> valueToApply = CSSCustomPropertyValue::create(downcast<CSSCustomPropertyValue>(*property.cssValue[index]));
if (inCycle && WTF::holds_alternative<Ref<CSSVariableReferenceValue>>(valueToApply->value())) {
// Resolve this value so that we reset its dependencies.
resolvedVariableValue(CSSPropertyCustom, valueToApply.get(), state);
}
}
}
void StyleResolver::applyCascadedProperties(int firstProperty, int lastProperty, ApplyCascadedPropertyState& state)
{
if (LIKELY(state.cascade->customProperties().isEmpty()))
return applyCascadedPropertiesImpl<CustomPropertyCycleTracking::Disabled>(firstProperty, lastProperty, state);
return applyCascadedPropertiesImpl<CustomPropertyCycleTracking::Enabled>(firstProperty, lastProperty, state);
}
template<StyleResolver::CustomPropertyCycleTracking TrackCycles>
inline void StyleResolver::applyCascadedPropertiesImpl(int firstProperty, int lastProperty, ApplyCascadedPropertyState& state)
{
for (int id = firstProperty; id <= lastProperty; ++id) {
CSSPropertyID propertyID = static_cast<CSSPropertyID>(id);
if (!state.cascade->hasProperty(propertyID))
continue;
ASSERT(propertyID != CSSPropertyCustom);
auto& property = state.cascade->property(propertyID);
if (TrackCycles == CustomPropertyCycleTracking::Disabled) {
// If we don't have any custom properties, then there can't be any cycles.
property.apply(*this, state);
} else {
if (UNLIKELY(state.inProgressProperties.get(propertyID))) {
// We are in a cycle (eg. setting font size using registered custom property value containing em).
// So this value should be unset.
state.appliedProperties.set(propertyID);
// This property is in a cycle, and only the root of the call stack will have firstProperty != lastProperty.
ASSERT(firstProperty == lastProperty);
continue;
}
state.inProgressProperties.set(propertyID);
property.apply(*this, state);
state.appliedProperties.set(propertyID);
state.inProgressProperties.set(propertyID, false);
}
}
}
} // namespace WebCore