blob: 8c061c4875b9c75de4cfcabe191523180a9f935a [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Peter Kelly (pmk@post.com)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* (C) 2007 David Smith (catfish.man@gmail.com)
* Copyright (C) 2004-2010, 2012-2016 Apple Inc. All rights reserved.
* (C) 2007 Eric Seidel (eric@webkit.org)
*
* 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 "StyleTreeResolver.h"
#include "CSSFontSelector.h"
#include "ComposedTreeAncestorIterator.h"
#include "ComposedTreeIterator.h"
#include "ElementIterator.h"
#include "Frame.h"
#include "HTMLBodyElement.h"
#include "HTMLInputElement.h"
#include "HTMLMeterElement.h"
#include "HTMLNames.h"
#include "HTMLParserIdioms.h"
#include "HTMLProgressElement.h"
#include "HTMLSlotElement.h"
#include "LoaderStrategy.h"
#include "NodeRenderStyle.h"
#include "Page.h"
#include "PlatformStrategies.h"
#include "Quirks.h"
#include "RenderElement.h"
#include "RenderStyle.h"
#include "RuntimeEnabledFeatures.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "StyleAdjuster.h"
#include "StyleFontSizeFunctions.h"
#include "StyleResolver.h"
#include "StyleScope.h"
#include "Text.h"
#include "WebAnimationTypes.h"
#include "WebAnimationUtilities.h"
namespace WebCore {
namespace Style {
DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(TreeResolverScope);
TreeResolver::TreeResolver(Document& document, std::unique_ptr<Update> update)
: m_document(document)
, m_update(WTFMove(update))
{
}
TreeResolver::~TreeResolver() = default;
TreeResolver::Scope::Scope(Document& document)
: resolver(document.styleScope().resolver())
, sharingResolver(document, resolver->ruleSets(), selectorMatchingState)
{
document.setIsResolvingTreeStyle(true);
// Ensure all shadow tree resolvers exist so their construction doesn't depend on traversal.
for (auto* shadowRoot : document.inDocumentShadowRoots())
shadowRoot->styleScope().resolver();
}
TreeResolver::Scope::Scope(ShadowRoot& shadowRoot, Scope& enclosingScope)
: resolver(shadowRoot.styleScope().resolver())
, sharingResolver(shadowRoot.documentScope(), resolver->ruleSets(), selectorMatchingState)
, shadowRoot(&shadowRoot)
, enclosingScope(&enclosingScope)
{
selectorMatchingState.queryContainers = enclosingScope.selectorMatchingState.queryContainers;
}
TreeResolver::Scope::~Scope()
{
if (!shadowRoot)
resolver->document().setIsResolvingTreeStyle(false);
}
TreeResolver::Parent::Parent(Document& document)
: element(nullptr)
, style(*document.renderStyle())
{
}
TreeResolver::Parent::Parent(Element& element, const RenderStyle& style, Change change, DescendantsToResolve descendantsToResolve)
: element(&element)
, style(style)
, change(change)
, descendantsToResolve(descendantsToResolve)
{
}
void TreeResolver::pushScope(ShadowRoot& shadowRoot)
{
m_scopeStack.append(adoptRef(*new Scope(shadowRoot, scope())));
}
void TreeResolver::pushEnclosingScope()
{
ASSERT(scope().enclosingScope);
m_scopeStack.append(*scope().enclosingScope);
}
void TreeResolver::popScope()
{
return m_scopeStack.removeLast();
}
std::unique_ptr<RenderStyle> TreeResolver::styleForStyleable(const Styleable& styleable, ResolutionType resolutionType, const ResolutionContext& resolutionContext)
{
auto& element = styleable.element;
if (element.hasCustomStyleResolveCallbacks()) {
RenderStyle* shadowHostStyle = scope().shadowRoot ? m_update->elementStyle(*scope().shadowRoot->host()) : nullptr;
if (auto customStyle = element.resolveCustomStyle(resolutionContext, shadowHostStyle)) {
if (customStyle->relations)
commitRelations(WTFMove(customStyle->relations), *m_update);
return WTFMove(customStyle->renderStyle);
}
}
if (resolutionType == ResolutionType::FastPathInherit) {
// If the only reason we are computing the style is that some parent inherited properties changed, we can just copy them.
auto& existingStyle = *element.renderOrDisplayContentsStyle();
auto style = RenderStyle::clonePtr(existingStyle);
style->fastPathInheritFrom(parent().style);
return style;
}
if (auto style = scope().sharingResolver.resolve(styleable, *m_update))
return style;
auto elementStyle = scope().resolver->styleForElement(element, resolutionContext);
if (elementStyle.relations)
commitRelations(WTFMove(elementStyle.relations), *m_update);
return WTFMove(elementStyle.renderStyle);
}
static void resetStyleForNonRenderedDescendants(Element& current)
{
for (auto& child : childrenOfType<Element>(current)) {
if (child.needsStyleRecalc()) {
child.resetComputedStyle();
child.resetStyleRelations();
child.setHasValidStyle();
}
if (child.childNeedsStyleRecalc())
resetStyleForNonRenderedDescendants(child);
}
current.clearChildNeedsStyleRecalc();
}
static bool affectsRenderedSubtree(Element& element, const RenderStyle& newStyle)
{
if (newStyle.display() != DisplayType::None)
return true;
if (element.renderOrDisplayContentsStyle())
return true;
if (element.rendererIsNeeded(newStyle))
return true;
return false;
}
auto TreeResolver::computeDescendantsToResolve(Change change, Validity validity, DescendantsToResolve parentDescendantsToResolve) -> DescendantsToResolve
{
if (parentDescendantsToResolve == DescendantsToResolve::All)
return DescendantsToResolve::All;
if (validity >= Validity::SubtreeInvalid)
return DescendantsToResolve::All;
switch (change) {
case Change::None:
return DescendantsToResolve::None;
case Change::NonInherited:
return DescendantsToResolve::ChildrenWithExplicitInherit;
case Change::FastPathInherited:
case Change::Inherited:
return DescendantsToResolve::Children;
case Change::Descendants:
case Change::Renderer:
return DescendantsToResolve::All;
};
ASSERT_NOT_REACHED();
return DescendantsToResolve::None;
};
auto TreeResolver::resolveElement(Element& element, ResolutionType resolutionType) -> std::pair<ElementUpdate, DescendantsToResolve>
{
if (m_didSeePendingStylesheet && !element.renderOrDisplayContentsStyle() && !m_document.isIgnoringPendingStylesheets()) {
m_document.setHasNodesWithMissingStyle();
return { };
}
if (!element.rendererIsEverNeeded() && !element.hasDisplayContents())
return { };
auto resolutionContext = makeResolutionContext();
Styleable styleable { element, PseudoId::None };
auto newStyle = styleForStyleable(styleable, resolutionType, resolutionContext);
if (!affectsRenderedSubtree(element, *newStyle))
return { };
auto* existingStyle = element.renderOrDisplayContentsStyle();
if (m_didSeePendingStylesheet && (!existingStyle || existingStyle->isNotFinal())) {
newStyle->setIsNotFinal();
m_document.setHasNodesWithNonFinalStyle();
}
auto update = createAnimatedElementUpdate(WTFMove(newStyle), styleable, parent().change, resolutionContext);
auto descendantsToResolve = computeDescendantsToResolve(update.change, element.styleValidity(), parent().descendantsToResolve);
if (&element == m_document.documentElement()) {
m_documentElementStyle = RenderStyle::clonePtr(*update.style);
if (!existingStyle || existingStyle->computedFontPixelSize() != update.style->computedFontPixelSize()) {
// "rem" units are relative to the document element's font size so we need to recompute everything.
scope().resolver->invalidateMatchedDeclarationsCache();
descendantsToResolve = DescendantsToResolve::All;
}
}
// This is needed for resolving color:-webkit-text for subsequent elements.
// FIXME: We shouldn't mutate document when resolving style.
if (&element == m_document.body())
m_document.setTextColor(update.style->visitedDependentColor(CSSPropertyColor));
// FIXME: These elements should not change renderer based on appearance property.
if (element.hasTagName(HTMLNames::meterTag)
|| is<HTMLProgressElement>(element)
|| (is<HTMLInputElement>(element) && downcast<HTMLInputElement>(element).isSearchField())) {
if (existingStyle && update.style->effectiveAppearance() != existingStyle->effectiveAppearance()) {
update.change = Change::Renderer;
descendantsToResolve = DescendantsToResolve::All;
}
}
auto resolveAndAddPseudoElementStyle = [&](PseudoId pseudoId) {
auto pseudoElementUpdate = resolvePseudoElement(element, pseudoId, update);
auto pseudoElementChange = [&] {
if (pseudoElementUpdate)
return pseudoElementUpdate->change == Change::None ? Change::None : Change::NonInherited;
if (!existingStyle || !existingStyle->getCachedPseudoStyle(pseudoId))
return Change::None;
// If ::first-letter goes aways rebuild the renderers.
return pseudoId == PseudoId::FirstLetter ? Change::Renderer : Change::NonInherited;
}();
update.change = std::max(update.change, pseudoElementChange);
if (!pseudoElementUpdate)
return pseudoElementChange;
if (pseudoElementUpdate->recompositeLayer)
update.recompositeLayer = true;
update.style->addCachedPseudoStyle(WTFMove(pseudoElementUpdate->style));
return pseudoElementUpdate->change;
};
if (resolveAndAddPseudoElementStyle(PseudoId::FirstLine) != Change::None)
descendantsToResolve = DescendantsToResolve::All;
if (resolveAndAddPseudoElementStyle(PseudoId::FirstLetter) != Change::None)
descendantsToResolve = DescendantsToResolve::All;
resolveAndAddPseudoElementStyle(PseudoId::Marker);
resolveAndAddPseudoElementStyle(PseudoId::Before);
resolveAndAddPseudoElementStyle(PseudoId::After);
resolveAndAddPseudoElementStyle(PseudoId::Backdrop);
#if ENABLE(TOUCH_ACTION_REGIONS)
// FIXME: Track this exactly.
if (update.style->touchActions() != TouchAction::Auto && !m_document.quirks().shouldDisablePointerEventsQuirk())
m_document.setMayHaveElementsWithNonAutoTouchAction();
#endif
#if ENABLE(EDITABLE_REGION)
if (update.style->effectiveUserModify() != UserModify::ReadOnly)
m_document.setMayHaveEditableElements();
#endif
return { WTFMove(update), descendantsToResolve };
}
inline bool supportsFirstLineAndLetterPseudoElement(const RenderStyle& style)
{
auto display = style.display();
return display == DisplayType::Block
|| display == DisplayType::ListItem
|| display == DisplayType::InlineBlock
|| display == DisplayType::TableCell
|| display == DisplayType::TableCaption
|| display == DisplayType::FlowRoot;
};
std::optional<ElementUpdate> TreeResolver::resolvePseudoElement(Element& element, PseudoId pseudoId, const ElementUpdate& elementUpdate)
{
if (pseudoId == PseudoId::Backdrop && !element.isInTopLayer())
return { };
if (pseudoId == PseudoId::Marker && elementUpdate.style->display() != DisplayType::ListItem)
return { };
if (pseudoId == PseudoId::FirstLine && !scope().resolver->usesFirstLineRules())
return { };
if (pseudoId == PseudoId::FirstLetter && !scope().resolver->usesFirstLetterRules())
return { };
if (elementUpdate.style->display() == DisplayType::None)
return { };
if (!elementUpdate.style->hasPseudoStyle(pseudoId))
return resolveAncestorPseudoElement(element, pseudoId, elementUpdate);
if ((pseudoId == PseudoId::FirstLine || pseudoId == PseudoId::FirstLetter) && !supportsFirstLineAndLetterPseudoElement(*elementUpdate.style))
return { };
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, pseudoId);
auto pseudoStyle = scope().resolver->pseudoStyleForElement(element, { pseudoId }, resolutionContext);
if (!pseudoStyle)
return { };
// FIXME: This test shouldn't be needed.
bool alwaysNeedsPseudoElement = pseudoStyle->hasAnimationsOrTransitions()
|| element.hasKeyframeEffects(pseudoId)
|| pseudoId == PseudoId::FirstLine
|| pseudoId == PseudoId::FirstLetter;
if (!alwaysNeedsPseudoElement && !pseudoElementRendererIsNeeded(pseudoStyle.get()))
return { };
auto animatedUpdate = createAnimatedElementUpdate(WTFMove(pseudoStyle), { element, pseudoId }, elementUpdate.change, resolutionContext);
if (pseudoId == PseudoId::Before || pseudoId == PseudoId::After) {
if (scope().resolver->usesFirstLineRules()) {
// ::first-line can inherit to ::before/::after
if (auto firstLineContext = makeResolutionContextForInheritedFirstLine(elementUpdate, *elementUpdate.style)) {
auto firstLineStyle = scope().resolver->pseudoStyleForElement(element, { pseudoId }, *firstLineContext);
firstLineStyle->setStyleType(PseudoId::FirstLine);
animatedUpdate.style->addCachedPseudoStyle(WTFMove(firstLineStyle));
}
}
if (scope().resolver->usesFirstLetterRules()) {
auto beforeAfterContext = makeResolutionContextForPseudoElement(animatedUpdate, PseudoId::FirstLetter);
if (auto firstLetterStyle = resolveAncestorFirstLetterPseudoElement(element, elementUpdate, beforeAfterContext))
animatedUpdate.style->addCachedPseudoStyle(WTFMove(firstLetterStyle));
}
}
return animatedUpdate;
}
std::optional<ElementUpdate> TreeResolver::resolveAncestorPseudoElement(Element& element, PseudoId pseudoId, const ElementUpdate& elementUpdate)
{
ASSERT(!elementUpdate.style->hasPseudoStyle(pseudoId));
auto pseudoElementStyle = [&]() -> std::unique_ptr<RenderStyle> {
// ::first-line and ::first-letter defined on an ancestor element may need to be resolved for the current element.
if (pseudoId == PseudoId::FirstLine)
return resolveAncestorFirstLinePseudoElement(element, elementUpdate);
if (pseudoId == PseudoId::FirstLetter) {
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, PseudoId::FirstLetter);
return resolveAncestorFirstLetterPseudoElement(element, elementUpdate, resolutionContext);
}
return nullptr;
}();
if (!pseudoElementStyle)
return { };
auto* oldStyle = element.renderOrDisplayContentsStyle(pseudoId);
auto change = oldStyle ? determineChange(*oldStyle, *pseudoElementStyle) : Change::Renderer;
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, pseudoId);
return createAnimatedElementUpdate(WTFMove(pseudoElementStyle), { element, pseudoId }, change, resolutionContext);
}
static bool isChildInBlockFormattingContext(const RenderStyle& style)
{
// FIXME: Incomplete. There should be shared code with layout for this.
if (style.display() != DisplayType::Block && style.display() != DisplayType::ListItem)
return false;
if (style.hasOutOfFlowPosition())
return false;
if (style.floating() != Float::None)
return false;
if (style.overflowX() != Overflow::Visible || style.overflowY() != Overflow::Visible)
return false;
return true;
};
std::unique_ptr<RenderStyle> TreeResolver::resolveAncestorFirstLinePseudoElement(Element& element, const ElementUpdate& elementUpdate)
{
if (elementUpdate.style->display() == DisplayType::Inline) {
auto* parent = boxGeneratingParent();
if (!parent)
return { };
auto resolutionContext = makeResolutionContextForInheritedFirstLine(elementUpdate, parent->style);
if (!resolutionContext)
return { };
auto elementStyle = scope().resolver->styleForElement(element, *resolutionContext);
auto firstLineStyle = WTFMove(elementStyle.renderStyle);
firstLineStyle->setStyleType(PseudoId::FirstLine);
return firstLineStyle;
}
auto findFirstLineElementForBlock = [&]() -> Element* {
if (!isChildInBlockFormattingContext(*elementUpdate.style))
return nullptr;
// ::first-line is only propagated to the first block.
if (parent().resolvedFirstLineAndLetterChild)
return nullptr;
for (auto& parent : makeReversedRange(m_parentStack)) {
if (parent.style.display() == DisplayType::Contents)
continue;
if (!supportsFirstLineAndLetterPseudoElement(parent.style))
return nullptr;
if (parent.style.hasPseudoStyle(PseudoId::FirstLine))
return parent.element;
if (!isChildInBlockFormattingContext(parent.style))
return nullptr;
}
return nullptr;
};
auto firstLineElement = findFirstLineElementForBlock();
if (!firstLineElement)
return { };
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, PseudoId::FirstLine);
// Can't use the cached state since the element being resolved is not the current one.
resolutionContext.selectorMatchingState = nullptr;
return scope().resolver->pseudoStyleForElement(*firstLineElement, { PseudoId::FirstLine }, resolutionContext);
}
std::unique_ptr<RenderStyle> TreeResolver::resolveAncestorFirstLetterPseudoElement(Element& element, const ElementUpdate& elementUpdate, ResolutionContext& resolutionContext)
{
auto findFirstLetterElement = [&]() -> Element* {
if (elementUpdate.style->hasPseudoStyle(PseudoId::FirstLetter) && supportsFirstLineAndLetterPseudoElement(*elementUpdate.style))
return &element;
// ::first-letter is only propagated to the first box.
if (parent().resolvedFirstLineAndLetterChild)
return nullptr;
bool skipInlines = elementUpdate.style->display() == DisplayType::Inline;
if (!skipInlines && !isChildInBlockFormattingContext(*elementUpdate.style))
return nullptr;
for (auto& parent : makeReversedRange(m_parentStack)) {
if (parent.style.display() == DisplayType::Contents)
continue;
if (skipInlines && parent.style.display() == DisplayType::Inline)
continue;
skipInlines = false;
if (!supportsFirstLineAndLetterPseudoElement(parent.style))
return nullptr;
if (parent.style.hasPseudoStyle(PseudoId::FirstLetter))
return parent.element;
if (!isChildInBlockFormattingContext(parent.style))
return nullptr;
}
return nullptr;
};
auto firstLetterElement = findFirstLetterElement();
if (!firstLetterElement)
return { };
// Can't use the cached state since the element being resolved is not the current one.
resolutionContext.selectorMatchingState = nullptr;
return scope().resolver->pseudoStyleForElement(*firstLetterElement, { PseudoId::FirstLetter }, resolutionContext);
}
ResolutionContext TreeResolver::makeResolutionContext()
{
return {
&parent().style,
parentBoxStyle(),
m_documentElementStyle.get(),
&scope().selectorMatchingState
};
}
ResolutionContext TreeResolver::makeResolutionContextForPseudoElement(const ElementUpdate& elementUpdate, PseudoId pseudoId)
{
auto parentStyle = [&] {
if (pseudoId == PseudoId::FirstLetter) {
if (auto* firstLineStyle = elementUpdate.style->getCachedPseudoStyle(PseudoId::FirstLine))
return firstLineStyle;
}
return elementUpdate.style.get();
};
return {
parentStyle(),
parentBoxStyleForPseudoElement(elementUpdate),
m_documentElementStyle.get(),
&scope().selectorMatchingState
};
}
std::optional<ResolutionContext> TreeResolver::makeResolutionContextForInheritedFirstLine(const ElementUpdate& elementUpdate, const RenderStyle& inheritStyle)
{
auto parentFirstLineStyle = inheritStyle.getCachedPseudoStyle(PseudoId::FirstLine);
if (!parentFirstLineStyle)
return { };
// First line style for inlines is made by inheriting from parent first line style.
return ResolutionContext {
parentFirstLineStyle,
parentBoxStyleForPseudoElement(elementUpdate),
m_documentElementStyle.get(),
&scope().selectorMatchingState
};
}
auto TreeResolver::boxGeneratingParent() const -> const Parent*
{
// 'display: contents' doesn't generate boxes.
for (auto& parent : makeReversedRange(m_parentStack)) {
if (parent.style.display() == DisplayType::None)
return nullptr;
if (parent.style.display() != DisplayType::Contents)
return &parent;
}
ASSERT_NOT_REACHED();
return nullptr;
}
const RenderStyle* TreeResolver::parentBoxStyle() const
{
auto* parent = boxGeneratingParent();
return parent ? &parent->style : nullptr;
}
const RenderStyle* TreeResolver::parentBoxStyleForPseudoElement(const ElementUpdate& elementUpdate) const
{
switch (elementUpdate.style->display()) {
case DisplayType::None:
return nullptr;
case DisplayType::Contents:
return parentBoxStyle();
default:
return elementUpdate.style.get();
}
}
ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr<RenderStyle> newStyle, const Styleable& styleable, Change parentChange, const ResolutionContext& resolutionContext)
{
auto& element = styleable.element;
auto& document = element.document();
auto* oldStyle = element.renderOrDisplayContentsStyle(styleable.pseudoId);
// FIXME: Something like this is also needed for viewport units.
if (oldStyle && parent().needsUpdateQueryContainerDependentStyle)
styleable.queryContainerDidChange();
// First, we need to make sure that any new CSS animation occuring on this element has a matching WebAnimation
// on the document timeline.
if (document.backForwardCacheState() == Document::NotInBackForwardCache && !document.printing()) {
if (oldStyle && (oldStyle->hasTransitions() || newStyle->hasTransitions()))
styleable.updateCSSTransitions(*oldStyle, *newStyle);
// The order in which CSS Transitions and CSS Animations are updated matters since CSS Transitions define the after-change style
// to use CSS Animations as defined in the previous style change event. As such, we update CSS Animations after CSS Transitions
// such that when CSS Transitions are updated the CSS Animations data is the same as during the previous style change event.
if ((oldStyle && oldStyle->hasAnimations()) || newStyle->hasAnimations())
styleable.updateCSSAnimations(oldStyle, *newStyle, resolutionContext);
}
OptionSet<AnimationImpact> animationImpact;
// Now we can update all Web animations, which will include CSS Animations as well
// as animations created via the JS API.
if (styleable.hasKeyframeEffects()) {
auto previousLastStyleChangeEventStyle = styleable.lastStyleChangeEventStyle() ? RenderStyle::clonePtr(*styleable.lastStyleChangeEventStyle()) : nullptr;
// Record the style prior to applying animations for this style change event.
styleable.setLastStyleChangeEventStyle(RenderStyle::clonePtr(*newStyle));
// Apply all keyframe effects to the new style.
auto animatedStyle = RenderStyle::clonePtr(*newStyle);
animationImpact = styleable.applyKeyframeEffects(*animatedStyle, previousLastStyleChangeEventStyle.get(), resolutionContext);
newStyle = WTFMove(animatedStyle);
Adjuster adjuster(document, *resolutionContext.parentStyle, resolutionContext.parentBoxStyle, styleable.pseudoId == PseudoId::None ? &element : nullptr);
adjuster.adjustAnimatedStyle(*newStyle, animationImpact);
} else
styleable.setLastStyleChangeEventStyle(nullptr);
// Deduplication speeds up equality comparisons as the properties inherit to descendants.
// FIXME: There should be a more general mechanism for this.
if (oldStyle)
newStyle->deduplicateInheritedCustomProperties(*oldStyle);
auto change = oldStyle ? determineChange(*oldStyle, *newStyle) : Change::Renderer;
auto validity = element.styleValidity();
if (validity >= Validity::SubtreeAndRenderersInvalid || parentChange == Change::Renderer)
change = Change::Renderer;
bool shouldRecompositeLayer = animationImpact.contains(AnimationImpact::RequiresRecomposite) || element.styleResolutionShouldRecompositeLayer();
return { WTFMove(newStyle), change, shouldRecompositeLayer };
}
void TreeResolver::pushParent(Element& element, const RenderStyle& style, Change change, DescendantsToResolve descendantsToResolve)
{
scope().selectorMatchingState.selectorFilter.pushParent(&element);
if (style.containerType() != ContainerType::None)
scope().selectorMatchingState.queryContainers.append(element);
Parent parent(element, style, change, descendantsToResolve);
if (auto* shadowRoot = element.shadowRoot()) {
pushScope(*shadowRoot);
parent.didPushScope = true;
}
else if (is<HTMLSlotElement>(element) && downcast<HTMLSlotElement>(element).assignedNodes()) {
pushEnclosingScope();
parent.didPushScope = true;
}
parent.needsUpdateQueryContainerDependentStyle = m_parentStack.last().needsUpdateQueryContainerDependentStyle || element.needsUpdateQueryContainerDependentStyle();
element.clearNeedsUpdateQueryContainerDependentStyle();
m_parentStack.append(WTFMove(parent));
}
void TreeResolver::popParent()
{
auto& parentElement = *parent().element;
parentElement.setHasValidStyle();
// Don't clear the child flags if there are unresolved containers because we are going to resume the style resolution.
if (!hasUnresolvedQueryContainers())
parentElement.clearChildNeedsStyleRecalc();
if (parent().didPushScope)
popScope();
scope().selectorMatchingState.selectorFilter.popParent();
auto& queryContainers = scope().selectorMatchingState.queryContainers;
if (!queryContainers.isEmpty() && queryContainers.last().ptr() == &parentElement)
queryContainers.removeLast();
m_parentStack.removeLast();
}
void TreeResolver::popParentsToDepth(unsigned depth)
{
ASSERT(depth);
ASSERT(m_parentStack.size() >= depth);
while (m_parentStack.size() > depth)
popParent();
}
static bool shouldResolvePseudoElement(const PseudoElement* pseudoElement)
{
if (!pseudoElement)
return false;
return pseudoElement->needsStyleRecalc();
}
auto TreeResolver::determineResolutionType(const Element& element, DescendantsToResolve parentDescendantsToResolve, Change parentChange) -> std::optional<ResolutionType>
{
if (element.styleValidity() != Validity::Valid)
return ResolutionType::Full;
if (shouldResolvePseudoElement(element.beforePseudoElement()))
return ResolutionType::Full;
if (shouldResolvePseudoElement(element.afterPseudoElement()))
return ResolutionType::Full;
switch (parentDescendantsToResolve) {
case DescendantsToResolve::None:
return { };
case DescendantsToResolve::Children:
if (parentChange == Change::FastPathInherited) {
auto* existingStyle = element.renderOrDisplayContentsStyle();
if (existingStyle && !existingStyle->disallowsFastPathInheritance())
return ResolutionType::FastPathInherit;
}
return ResolutionType::Full;
case DescendantsToResolve::All:
return ResolutionType::Full;
case DescendantsToResolve::ChildrenWithExplicitInherit:
auto* existingStyle = element.renderOrDisplayContentsStyle();
if (existingStyle && existingStyle->hasExplicitlyInheritedProperties())
return ResolutionType::Full;
return { };
};
ASSERT_NOT_REACHED();
return { };
}
static void clearNeedsStyleResolution(Element& element)
{
element.setHasValidStyle();
if (auto* before = element.beforePseudoElement())
before->setHasValidStyle();
if (auto* after = element.afterPseudoElement())
after->setHasValidStyle();
}
static bool hasLoadingStylesheet(const Style::Scope& styleScope, const Element& element, bool checkDescendants)
{
if (!styleScope.hasPendingSheetsInBody())
return false;
if (styleScope.hasPendingSheetInBody(element))
return true;
if (!checkDescendants)
return false;
for (auto& descendant : descendantsOfType<Element>(element)) {
if (styleScope.hasPendingSheetInBody(descendant))
return true;
};
return false;
}
static std::unique_ptr<RenderStyle> createInheritedDisplayContentsStyleIfNeeded(const RenderStyle& parentElementStyle, const RenderStyle* parentBoxStyle)
{
if (parentElementStyle.display() != DisplayType::Contents)
return nullptr;
if (parentBoxStyle && parentBoxStyle->inheritedEqual(parentElementStyle))
return nullptr;
// Compute style for imaginary unstyled <span> around the text node.
auto style = RenderStyle::createPtr();
style->inheritFrom(parentElementStyle);
return style;
}
void TreeResolver::resetDescendantStyleRelations(Element& element, DescendantsToResolve descendantsToResolve)
{
switch (descendantsToResolve) {
case DescendantsToResolve::None:
case DescendantsToResolve::ChildrenWithExplicitInherit:
break;
case DescendantsToResolve::Children:
element.resetChildStyleRelations();
break;
case DescendantsToResolve::All:
element.resetAllDescendantStyleRelations();
break;
};
}
void TreeResolver::resolveComposedTree()
{
ASSERT(m_parentStack.size() == 1);
ASSERT(m_scopeStack.size() == 1);
auto descendants = composedTreeDescendants(m_document);
auto it = descendants.begin();
auto end = descendants.end();
while (it != end) {
popParentsToDepth(it.depth());
auto& node = *it;
auto& parent = this->parent();
ASSERT(node.isConnected());
ASSERT(node.containingShadowRoot() == scope().shadowRoot);
ASSERT(node.parentElement() == parent.element || is<ShadowRoot>(node.parentNode()) || node.parentElement()->shadowRoot());
if (is<Text>(node)) {
auto& text = downcast<Text>(node);
if ((text.styleValidity() >= Validity::SubtreeAndRenderersInvalid && parent.change != Change::Renderer) || parent.style.display() == DisplayType::Contents) {
TextUpdate textUpdate;
textUpdate.inheritedDisplayContentsStyle = createInheritedDisplayContentsStyleIfNeeded(parent.style, parentBoxStyle());
m_update->addText(text, parent.element, WTFMove(textUpdate));
}
if (!text.data().isAllSpecialCharacters<isHTMLSpace>())
parent.resolvedFirstLineAndLetterChild = true;
text.setHasValidStyle();
it.traverseNextSkippingChildren();
continue;
}
auto& element = downcast<Element>(node);
if (it.depth() > Settings::defaultMaximumRenderTreeDepth) {
resetStyleForNonRenderedDescendants(element);
it.traverseNextSkippingChildren();
continue;
}
auto* style = element.renderOrDisplayContentsStyle();
auto change = Change::None;
auto descendantsToResolve = DescendantsToResolve::None;
auto previousContainerType = style ? style->containerType() : ContainerType::None;
auto resolutionType = determineResolutionType(element, parent.descendantsToResolve, parent.change);
if (resolutionType) {
if (!element.hasDisplayContents())
element.resetComputedStyle();
element.resetStyleRelations();
if (element.hasCustomStyleResolveCallbacks())
element.willRecalcStyle(parent.change);
auto [elementUpdate, elementDescendantsToResolve] = resolveElement(element, *resolutionType);
if (element.hasCustomStyleResolveCallbacks())
element.didRecalcStyle(elementUpdate.change);
style = elementUpdate.style.get();
change = elementUpdate.change;
descendantsToResolve = elementDescendantsToResolve;
if (elementUpdate.style)
m_update->addElement(element, parent.element, WTFMove(elementUpdate));
clearNeedsStyleResolution(element);
}
if (!style)
resetStyleForNonRenderedDescendants(element);
auto queryContainerAction = determineQueryContainerAction(element, style, previousContainerType);
bool shouldIterateChildren = [&] {
// display::none, no need to resolve descendants.
if (!style)
return false;
// Style resolution will be resumed after the container has been resolved.
if (queryContainerAction == QueryContainerAction::Resolve)
return false;
return element.childNeedsStyleRecalc() || descendantsToResolve != DescendantsToResolve::None;
}();
// Ensure we respect DescendantsToResolve::All after resuming the style resolution.
if (queryContainerAction == QueryContainerAction::Resolve && descendantsToResolve == DescendantsToResolve::All)
element.invalidateStyleForSubtreeInternal();
if (!m_didSeePendingStylesheet)
m_didSeePendingStylesheet = hasLoadingStylesheet(m_document.styleScope(), element, !shouldIterateChildren);
if (!parent.resolvedFirstLineAndLetterChild && style && generatesBox(*style) && supportsFirstLineAndLetterPseudoElement(*style))
parent.resolvedFirstLineAndLetterChild = true;
if (!shouldIterateChildren) {
it.traverseNextSkippingChildren();
continue;
}
resetDescendantStyleRelations(element, descendantsToResolve);
pushParent(element, *style, change, descendantsToResolve);
it.traverseNext();
}
popParentsToDepth(1);
}
auto TreeResolver::determineQueryContainerAction(const Element& element, const RenderStyle* style, ContainerType previousContainerType) -> QueryContainerAction
{
if (!style)
return QueryContainerAction::None;
// FIXME: Render tree needs to be updated before proceeding to children also if we have a former query container
// because container unit resolution for descendants relies on it being up-to-date.
if (style->containerType() == ContainerType::None && previousContainerType == ContainerType::None)
return QueryContainerAction::None;
if (m_resolvedQueryContainers.contains(element))
return QueryContainerAction::None;
m_unresolvedQueryContainers.append(element);
return QueryContainerAction::Resolve;
}
std::unique_ptr<Update> TreeResolver::resolve()
{
m_resolvedQueryContainers.add(m_unresolvedQueryContainers.begin(), m_unresolvedQueryContainers.end());
m_unresolvedQueryContainers.clear();
Element* documentElement = m_document.documentElement();
if (!documentElement) {
m_document.styleScope().resolver();
return nullptr;
}
if (!documentElement->childNeedsStyleRecalc() && !documentElement->needsStyleRecalc())
return WTFMove(m_update);
m_didSeePendingStylesheet = m_document.styleScope().hasPendingSheetsBeforeBody();
if (!m_update)
m_update = makeUnique<Update>(m_document);
m_scopeStack.append(adoptRef(*new Scope(m_document)));
m_parentStack.append(Parent(m_document));
resolveComposedTree();
ASSERT(m_scopeStack.size() == 1);
ASSERT(m_parentStack.size() == 1);
m_parentStack.clear();
popScope();
if (m_update->roots().isEmpty())
return { };
return WTFMove(m_update);
}
static Vector<Function<void ()>>& postResolutionCallbackQueue()
{
static NeverDestroyed<Vector<Function<void ()>>> vector;
return vector;
}
static Vector<RefPtr<Frame>>& memoryCacheClientCallsResumeQueue()
{
static NeverDestroyed<Vector<RefPtr<Frame>>> vector;
return vector;
}
void deprecatedQueuePostResolutionCallback(Function<void()>&& callback)
{
postResolutionCallbackQueue().append(WTFMove(callback));
}
static void suspendMemoryCacheClientCalls(Document& document)
{
Page* page = document.page();
if (!page || !page->areMemoryCacheClientCallsEnabled())
return;
page->setMemoryCacheClientCallsEnabled(false);
memoryCacheClientCallsResumeQueue().append(&page->mainFrame());
}
static unsigned resolutionNestingDepth;
PostResolutionCallbackDisabler::PostResolutionCallbackDisabler(Document& document, DrainCallbacks drainCallbacks)
: m_drainCallbacks(drainCallbacks)
{
++resolutionNestingDepth;
if (resolutionNestingDepth == 1)
platformStrategies()->loaderStrategy()->suspendPendingRequests();
// FIXME: It's strange to build this into the disabler.
suspendMemoryCacheClientCalls(document);
}
PostResolutionCallbackDisabler::~PostResolutionCallbackDisabler()
{
if (resolutionNestingDepth == 1) {
if (m_drainCallbacks == DrainCallbacks::Yes) {
// Get size each time through the loop because a callback can add more callbacks to the end of the queue.
auto& queue = postResolutionCallbackQueue();
for (size_t i = 0; i < queue.size(); ++i)
queue[i]();
queue.clear();
}
auto& queue = memoryCacheClientCallsResumeQueue();
for (size_t i = 0; i < queue.size(); ++i) {
if (auto* page = queue[i]->page())
page->setMemoryCacheClientCallsEnabled(true);
}
queue.clear();
platformStrategies()->loaderStrategy()->resumePendingRequests();
}
--resolutionNestingDepth;
}
bool postResolutionCallbacksAreSuspended()
{
return resolutionNestingDepth;
}
}
}