blob: 089abe88cc99912b4c7b8dd329acc1c61900b039 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll ([email protected])
* (C) 1999 Antti Koivisto ([email protected])
* (C) 2001 Peter Kelly ([email protected])
* (C) 2001 Dirk Mueller ([email protected])
* (C) 2007 David Smith ([email protected])
* Copyright (C) 2004-2026 Apple Inc. All rights reserved.
* (C) 2007 Eric Seidel ([email protected])
*
* 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 "AXObjectCache.h"
#include "AnchorPositionEvaluator.h"
#include "CSSFontSelector.h"
#include "CSSPositionTryRule.h"
#include "CSSSerializationContext.h"
#include "ComposedTreeAncestorIterator.h"
#include "ComposedTreeIterator.h"
#include "Document.h"
#include "DocumentPage.h"
#include "DocumentQuirks.h"
#include "DocumentTimeline.h"
#include "DocumentView.h"
#include "EventTarget.h"
#include "HTMLBodyElement.h"
#include "HTMLInputElement.h"
#include "HTMLMeterElement.h"
#include "HTMLNames.h"
#include "HTMLProgressElement.h"
#include "HTMLSlotElement.h"
#include "LoaderStrategy.h"
#include "LocalFrame.h"
#include "MatchResultCache.h"
#include "NodeInlines.h"
#include "NodeRenderStyle.h"
#include "Page.h"
#include "PlatformStrategies.h"
#include "PositionTryOrder.h"
#include "PositionedLayoutConstraints.h"
#include "RenderBoxInlines.h"
#include "RenderElement.h"
#include "RenderStyle+SettersInlines.h"
#include "RenderView.h"
#include "ResolvedStyle.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "StyleAdjuster.h"
#include "StyleBuilder.h"
#include "StyleFontSizeFunctions.h"
#include "StyleOriginatedTimelinesController.h"
#include "StylePositionTryFallbackTactic.h"
#include "StyleResolver.h"
#include "StyleScope.h"
#include "Text.h"
#include "TypedElementDescendantIteratorInlines.h"
#include "ViewTransition.h"
#include "WebAnimationTypes.h"
#include "WebAnimationUtilities.h"
#include <ranges>
namespace WebCore {
namespace Style {
DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(TreeResolverScope);
TreeResolver::TreeResolver(Document& document, std::unique_ptr<Update> update)
: m_document(document)
, m_update(WTF::move(update))
{
}
TreeResolver::~TreeResolver()
{
m_document->styleScope().updateAnchorPositioningStateAfterStyleResolution();
}
TreeResolver::Scope::Scope(Document& document, Update& update)
: resolver(document.styleScope().resolver())
{
document.setIsResolvingTreeStyle(true);
// Ensure all shadow tree resolvers exist so their construction doesn't depend on traversal.
for (Ref shadowRoot : document.inDocumentShadowRoots())
const_cast<ShadowRoot&>(shadowRoot.get()).styleScope().resolver();
selectorMatchingState.containerQueryEvaluationState.styleUpdate = &update;
}
TreeResolver::Scope::Scope(ShadowRoot& shadowRoot, Scope& enclosingScope)
: resolver(shadowRoot.styleScope().resolver())
, shadowRoot(&shadowRoot)
, enclosingScope(&enclosingScope)
{
selectorMatchingState.containerQueryEvaluationState = enclosingScope.selectorMatchingState.containerQueryEvaluationState;
}
TreeResolver::Scope::~Scope()
{
if (!shadowRoot)
resolver->document().setIsResolvingTreeStyle(false);
}
TreeResolver::Parent::Parent(Document& document)
: element(nullptr)
, style(*document.initialContainingBlockStyle())
{
}
TreeResolver::Parent::Parent(Element& element, const RenderStyle& style, OptionSet<Change> changes, DescendantsToResolve descendantsToResolve, IsInDisplayNoneTree isInDisplayNoneTree)
: element(&element)
, style(style)
, changes(changes)
, descendantsToResolve(descendantsToResolve)
, isInDisplayNoneTree(isInDisplayNoneTree)
{
}
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();
}
// Takes an old style (from previous style resolution) and new style (from current
// style resolution), determine if the last successful position option should be
// invalidated. This follows the criterias in the spec:
// https://drafts.csswg.org/css-anchor-position-1/#last-successful-position-option
static bool shouldInvalidateLastSuccessfulPositionOptionIndex(const RenderStyle* oldStyle, const RenderStyle* newStyle)
{
if (oldStyle && newStyle) {
if (oldStyle->positionTryFallbacks() != newStyle->positionTryFallbacks())
return true;
if (oldStyle->positionTryOrder() != newStyle->positionTryOrder())
return true;
// FIXME: add missing invalidation criterias in the spec.
}
return false;
}
ResolvedStyle TreeResolver::styleForStyleable(const Styleable& styleable, ResolutionType resolutionType, const ResolutionContext& resolutionContext, const RenderStyle* existingStyle)
{
if (resolutionType == ResolutionType::AnimationOnly && styleable.lastStyleChangeEventStyle() && !styleable.hasPropertiesOverridenAfterAnimation())
return { RenderStyle::clonePtr(*styleable.lastStyleChangeEventStyle()) };
Ref element = styleable.element;
if (auto optionStyle = tryChoosePositionOption(styleable, resolutionContext))
return WTF::move(*optionStyle);
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 style = RenderStyle::clonePtr(*existingStyle);
style->fastPathInheritFrom(parent().style);
m_document->styleScope().matchResultCache().updateForFastPathInherit(element, parent().style);
return { WTF::move(style) };
}
auto unadjustedStyle = [&] {
if (element->hasCustomStyleResolveCallbacks()) {
RenderStyle* shadowHostStyle = scope().shadowRoot ? m_update->elementStyle(*scope().shadowRoot->host()) : nullptr;
if (auto customStyle = element->resolveCustomStyle(resolutionContext, shadowHostStyle))
return WTF::move(*customStyle);
}
if (resolutionType == ResolutionType::FullWithMatchResultCache) {
if (auto cachedResult = m_document->styleScope().matchResultCache().resultWithCurrentInlineStyle(element.get())) {
auto result = scope().resolver->unadjustedStyleForCachedMatchResult(element.get(), resolutionContext, WTF::move(*cachedResult));
MatchResultCache::update(*cachedResult, *result.style);
return result;
}
}
auto result = scope().resolver->unadjustedStyleForElement(element.get(), resolutionContext);
m_document->styleScope().matchResultCache().set(element.get(), result);
return result;
}();
if (unadjustedStyle.relations)
commitRelations(WTF::move(unadjustedStyle.relations), *m_update);
auto style = WTF::move(unadjustedStyle.style);
// Fully custom styles in UA shadow trees that don't originate from selector matching don't need adjusting.
if (unadjustedStyle.matchResult) {
Adjuster adjuster(m_document, *resolutionContext.parentStyle, resolutionContext.parentBoxStyle, element.ptr());
adjuster.adjust(*style);
}
ResolvedStyle resolvedStyle {
.style = WTF::move(style),
.relations = { },
.matchResult = WTF::move(unadjustedStyle.matchResult)
};
// Invalidate the last successful position option here. This is the only place where
// we have access to the old and new style, and hence we could determine whether the
// style changes enough to invalidate.
if (shouldInvalidateLastSuccessfulPositionOptionIndex(existingStyle, resolvedStyle.style.get()))
m_document->styleScope().forgetLastSuccessfulPositionOptionIndex(styleable);
generatePositionOptionsIfNeeded(resolvedStyle, styleable, resolutionContext);
return resolvedStyle;
}
void TreeResolver::resetStyleForNonRenderedDescendants(Element& subtreeRoot)
{
auto descendants = composedTreeDescendants(subtreeRoot);
for (auto it = descendants.begin(); it != descendants.end();) {
if (RefPtr element = dynamicDowncast<Element>(*it)) {
if (element->needsStyleRecalc()) {
element->resetComputedStyle();
element->resetStyleRelations();
element->setHasValidStyle();
}
}
if (it->childNeedsStyleRecalc()) {
it->clearChildNeedsStyleRecalc();
it.traverseNext();
} else
it.traverseNextSkippingChildren();
}
auto nonRenderedElementsWithPositionOptions = [&] () {
Vector<RefPtr<const Element>> result;
for (auto& styleable : m_positionOptions.keys()) {
if (styleable.first->isComposedTreeDescendantOf(subtreeRoot))
result.append(styleable.first);
}
return result;
}();
m_positionOptions.removeIf([&nonRenderedElementsWithPositionOptions] (const auto& kv) {
return nonRenderedElementsWithPositionOptions.contains(kv.key.first);
});
subtreeRoot.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(const ElementUpdate& update, const RenderStyle* existingStyle, Validity validity) const -> DescendantsToResolve
{
if (parent().descendantsToResolve == DescendantsToResolve::All)
return DescendantsToResolve::All;
if (validity >= Validity::SubtreeInvalid)
return DescendantsToResolve::All;
if (update.changes && existingStyle) {
auto customPropertyInStyleContainerQueryChanged = [&] {
auto& namesInQueries = scope().resolver->ruleSets().customPropertyNamesInStyleContainerQueries();
for (auto& name : namesInQueries) {
// Any descendant may depend on this changed custom property via a style query.
if (!existingStyle->customPropertyValueEqual(*update.style, name))
return true;
}
return false;
}();
if (customPropertyInStyleContainerQueryChanged)
return DescendantsToResolve::All;
}
if (update.changes.containsAny({ Change::Container, Change::Renderer }))
return DescendantsToResolve::All;
if (update.changes.containsAny(inheritedChanges()))
return DescendantsToResolve::Children;
if (update.changes.contains(Change::NonInherited))
return DescendantsToResolve::ChildrenWithExplicitInherit;
ASSERT(!update.changes);
return DescendantsToResolve::None;
};
static bool styleChangeAffectsRelativeUnits(const RenderStyle& style, const RenderStyle* existingStyle)
{
if (!existingStyle)
return true;
return !existingStyle->fontCascadeEqual(style)
|| existingStyle->computedLineHeight() != style.computedLineHeight();
}
auto TreeResolver::resolveElement(Element& element, const RenderStyle* existingStyle, ResolutionType resolutionType) -> std::pair<ElementUpdate, DescendantsToResolve>
{
if (m_didSeePendingStylesheet && !element.renderOrDisplayContentsStyle() && !m_document->isIgnoringPendingStylesheets()) {
m_document->setHasNodesWithMissingStyle();
return { };
}
if (resolutionType == ResolutionType::RebuildUsingExisting) {
return {
ElementUpdate { RenderStyle::clonePtr(*existingStyle), Change::Renderer },
DescendantsToResolve::RebuildAllUsingExisting
};
}
auto resolutionContext = makeResolutionContext();
Styleable styleable { element, { } };
auto resolvedStyle = styleForStyleable(styleable, resolutionType, resolutionContext, existingStyle);
updateForPositionVisibility(*resolvedStyle.style, styleable);
auto update = createAnimatedElementUpdate(WTF::move(resolvedStyle), styleable, parent().changes, resolutionContext, parent().isInDisplayNoneTree);
if (!affectsRenderedSubtree(element, *update.style)) {
styleable.setLastStyleChangeEventStyle(nullptr);
if (update.style->display() == DisplayType::None && element.hasDisplayNone())
return { WTF::move(update), DescendantsToResolve::None };
return { };
}
auto descendantsToResolve = computeDescendantsToResolve(update, existingStyle, element.styleValidity());
bool isDocumentElement = &element == m_document->documentElement();
if (isDocumentElement) {
if (styleChangeAffectsRelativeUnits(*update.style, existingStyle)) {
// "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());
// FIXME: These elements should not change renderer based on appearance property.
if (RefPtr input = dynamicDowncast<HTMLInputElement>(element); (input && input->isSearchField())
|| is<HTMLMeterElement>(element)
|| is<HTMLProgressElement>(element)) {
if (existingStyle && update.style->usedAppearance() != existingStyle->usedAppearance()) {
update.changes.add(Change::Renderer);
descendantsToResolve = DescendantsToResolve::All;
}
}
auto resolveAndAddPseudoElementStyle = [&](const PseudoElementIdentifier& pseudoElementIdentifier) {
const RenderStyle* existingPseudoStyle = existingStyle ? existingStyle->getCachedPseudoStyle(pseudoElementIdentifier) : nullptr;
auto pseudoElementUpdate = resolvePseudoElement(element, pseudoElementIdentifier, update, parent().isInDisplayNoneTree, existingPseudoStyle);
auto pseudoElementChanges = [&]() -> OptionSet<Change> {
if (pseudoElementUpdate) {
if (pseudoElementIdentifier.type == PseudoElementType::WebKitScrollbar)
return pseudoElementUpdate->changes;
if (!pseudoElementUpdate->changes)
return { };
return { Change::NonInherited };
}
if (!existingStyle || !existingStyle->getCachedPseudoStyle(pseudoElementIdentifier))
return { };
// If ::first-letter goes aways rebuild the renderers.
if (pseudoElementIdentifier.type == PseudoElementType::FirstLetter)
return { Change::Renderer };
return { Change::NonInherited };
}();
update.changes |= pseudoElementChanges;
if (!pseudoElementUpdate)
return pseudoElementChanges;
if (pseudoElementUpdate->recompositeLayer)
update.recompositeLayer = true;
update.style->addCachedPseudoStyle(WTF::move(pseudoElementUpdate->style));
return pseudoElementUpdate->changes;
};
if (resolveAndAddPseudoElementStyle({ PseudoElementType::FirstLine }))
descendantsToResolve = DescendantsToResolve::All;
if (resolveAndAddPseudoElementStyle({ PseudoElementType::FirstLetter }))
descendantsToResolve = DescendantsToResolve::All;
if (resolveAndAddPseudoElementStyle({ PseudoElementType::WebKitScrollbar }))
descendantsToResolve = DescendantsToResolve::All;
resolveAndAddPseudoElementStyle({ PseudoElementType::Marker });
resolveAndAddPseudoElementStyle({ PseudoElementType::Before });
resolveAndAddPseudoElementStyle({ PseudoElementType::After });
resolveAndAddPseudoElementStyle({ PseudoElementType::Backdrop });
resolveAndAddPseudoElementStyle({ PseudoElementType::Checkmark });
if (isDocumentElement && m_document->hasViewTransitionPseudoElementTree()) {
resolveAndAddPseudoElementStyle({ PseudoElementType::ViewTransition });
RefPtr activeViewTransition = m_document->activeViewTransition();
ASSERT(activeViewTransition);
for (auto& name : activeViewTransition->namedElements().keys()) {
resolveAndAddPseudoElementStyle({ PseudoElementType::ViewTransitionGroup, name });
resolveAndAddPseudoElementStyle({ PseudoElementType::ViewTransitionImagePair, name });
resolveAndAddPseudoElementStyle({ PseudoElementType::ViewTransitionNew, name });
resolveAndAddPseudoElementStyle({ PseudoElementType::ViewTransitionOld, name });
}
}
#if ENABLE(TOUCH_ACTION_REGIONS)
// FIXME: Track this exactly.
if (!update.style->touchAction().isAuto() && !m_document->quirks().shouldDisablePointerEventsQuirk())
m_document->setMayHaveElementsWithNonAutoTouchAction();
#endif
#if ENABLE(EDITABLE_REGION)
if (update.style->usedUserModify() != UserModify::ReadOnly)
m_document->setMayHaveEditableElements();
#endif
return { WTF::move(update), descendantsToResolve };
}
std::optional<ElementUpdate> TreeResolver::resolvePseudoElement(Element& element, const PseudoElementIdentifier& pseudoElementIdentifier, const ElementUpdate& elementUpdate, IsInDisplayNoneTree isInDisplayNoneTree, const RenderStyle* existingStyle)
{
if (elementUpdate.style->display() == DisplayType::None)
return { };
if (pseudoElementIdentifier.type == PseudoElementType::Backdrop && !element.isInTopLayer())
return { };
if (pseudoElementIdentifier.type == PseudoElementType::Marker && elementUpdate.style->display() != DisplayType::ListItem)
return { };
auto userAgentShadowTreeEnclosingResolver = [&] -> Resolver* {
if (element.isInUserAgentShadowTree())
return scope().enclosingScope->resolver.ptr();
return nullptr;
};
if (pseudoElementIdentifier.type == PseudoElementType::FirstLine && !scope().resolver->usesFirstLineRules()) {
// For user-agent shadow tree elements, also check the enclosing (document) scope
// because user-agent pseudo-elements like details::details-content::first-line
// are defined in the document scope but target elements in the shadow tree
RefPtr resolver = userAgentShadowTreeEnclosingResolver();
if (!resolver || !resolver->usesFirstLineRules())
return { };
}
if (pseudoElementIdentifier.type == PseudoElementType::FirstLetter && !scope().resolver->usesFirstLetterRules()) {
RefPtr resolver = userAgentShadowTreeEnclosingResolver();
if (!resolver || !resolver->usesFirstLetterRules())
return { };
}
if (pseudoElementIdentifier.type == PseudoElementType::WebKitScrollbar && elementUpdate.style->overflowX() != Overflow::Scroll && elementUpdate.style->overflowY() != Overflow::Scroll)
return { };
ASSERT_IMPLIES(pseudoElementIdentifier.type == PseudoElementType::ViewTransition
|| pseudoElementIdentifier.type == PseudoElementType::ViewTransitionGroup
|| pseudoElementIdentifier.type == PseudoElementType::ViewTransitionImagePair
|| pseudoElementIdentifier.type == PseudoElementType::ViewTransitionNew
|| pseudoElementIdentifier.type == PseudoElementType::ViewTransitionOld,
m_document->hasViewTransitionPseudoElementTree() && &element == m_document->documentElement());
if (!elementUpdate.style->hasPseudoStyle(pseudoElementIdentifier.type))
return resolveAncestorPseudoElement(element, pseudoElementIdentifier, elementUpdate);
if ((pseudoElementIdentifier.type == PseudoElementType::FirstLine || pseudoElementIdentifier.type == PseudoElementType::FirstLetter) && !supportsFirstLineAndLetterPseudoElement(*elementUpdate.style))
return { };
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, pseudoElementIdentifier);
bool pseudoSupportsPositionTry = pseudoElementIdentifier.type == PseudoElementType::Before || pseudoElementIdentifier.type == PseudoElementType::After || pseudoElementIdentifier.type == PseudoElementType::Backdrop;
Styleable styleable { element, pseudoElementIdentifier };
auto resolvedStyle = [&] () {
std::optional<ResolvedStyle> resolvedStyle;
if (pseudoSupportsPositionTry)
resolvedStyle = tryChoosePositionOption(styleable, resolutionContext);
if (!resolvedStyle) {
resolvedStyle = scope().resolver->styleForPseudoElement(element, pseudoElementIdentifier, resolutionContext);
if (resolvedStyle && pseudoSupportsPositionTry) {
if (shouldInvalidateLastSuccessfulPositionOptionIndex(existingStyle, resolvedStyle->style.get()))
m_document->styleScope().forgetLastSuccessfulPositionOptionIndex(styleable);
generatePositionOptionsIfNeeded(*resolvedStyle, styleable, resolutionContext);
}
}
return resolvedStyle;
}();
if (!resolvedStyle)
return { };
auto animatedUpdate = createAnimatedElementUpdate(WTF::move(*resolvedStyle), styleable, elementUpdate.changes, resolutionContext, isInDisplayNoneTree);
if (pseudoElementIdentifier.type == PseudoElementType::Before || pseudoElementIdentifier.type == PseudoElementType::After) {
if (scope().resolver->usesFirstLineRules()) {
// ::first-line can inherit to ::before/::after
if (auto firstLineContext = makeResolutionContextForInheritedFirstLine(elementUpdate, *elementUpdate.style)) {
auto firstLineStyle = scope().resolver->styleForPseudoElement(element, pseudoElementIdentifier, *firstLineContext);
firstLineStyle->style->setPseudoElementIdentifier({ { PseudoElementType::FirstLine } });
animatedUpdate.style->addCachedPseudoStyle(WTF::move(firstLineStyle->style));
}
}
if (scope().resolver->usesFirstLetterRules()) {
auto beforeAfterContext = makeResolutionContextForPseudoElement(animatedUpdate, { PseudoElementType::FirstLetter });
if (auto firstLetterStyle = resolveAncestorFirstLetterPseudoElement(element, elementUpdate, beforeAfterContext))
animatedUpdate.style->addCachedPseudoStyle(WTF::move(firstLetterStyle->style));
}
}
return animatedUpdate;
}
std::optional<ElementUpdate> TreeResolver::resolveAncestorPseudoElement(Element& element, const PseudoElementIdentifier& pseudoElementIdentifier, const ElementUpdate& elementUpdate)
{
ASSERT(!elementUpdate.style->hasPseudoStyle(pseudoElementIdentifier.type));
auto pseudoElementStyle = [&]() -> std::optional<ResolvedStyle> {
// ::first-line and ::first-letter defined on an ancestor element may need to be resolved for the current element.
if (pseudoElementIdentifier.type == PseudoElementType::FirstLine)
return resolveAncestorFirstLinePseudoElement(element, elementUpdate);
if (pseudoElementIdentifier.type == PseudoElementType::FirstLetter) {
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, { PseudoElementType::FirstLetter });
return resolveAncestorFirstLetterPseudoElement(element, elementUpdate, resolutionContext);
}
return { };
}();
if (!pseudoElementStyle)
return { };
auto* oldStyle = element.renderOrDisplayContentsStyle(pseudoElementIdentifier);
auto changes = oldStyle ? determineChanges(*oldStyle, *pseudoElementStyle->style) : Change::Renderer;
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, pseudoElementIdentifier);
return createAnimatedElementUpdate(WTF::move(*pseudoElementStyle), { element, pseudoElementIdentifier }, changes, 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::optional<ResolvedStyle> 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);
elementStyle.style->setPseudoElementIdentifier({ { PseudoElementType::FirstLine } });
return elementStyle;
}
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 : m_parentStack | std::views::reverse) {
if (parent.style.display() == DisplayType::Contents)
continue;
if (!supportsFirstLineAndLetterPseudoElement(parent.style))
return nullptr;
if (parent.style.hasPseudoStyle(PseudoElementType::FirstLine))
return parent.element;
if (!isChildInBlockFormattingContext(parent.style))
return nullptr;
}
return nullptr;
};
RefPtr firstLineElement = findFirstLineElementForBlock();
if (!firstLineElement)
return { };
auto resolutionContext = makeResolutionContextForPseudoElement(elementUpdate, { PseudoElementType::FirstLine });
// Can't use the cached state since the element being resolved is not the current one.
resolutionContext.selectorMatchingState = nullptr;
return scope().resolver->styleForPseudoElement(*firstLineElement, { PseudoElementType::FirstLine }, resolutionContext);
}
std::optional<ResolvedStyle> TreeResolver::resolveAncestorFirstLetterPseudoElement(Element& element, const ElementUpdate& elementUpdate, ResolutionContext& resolutionContext)
{
auto findFirstLetterElement = [&]() -> Element* {
if (elementUpdate.style->hasPseudoStyle(PseudoElementType::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 : m_parentStack | std::views::reverse) {
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(PseudoElementType::FirstLetter))
return parent.element;
if (!isChildInBlockFormattingContext(parent.style))
return nullptr;
}
return nullptr;
};
RefPtr 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->styleForPseudoElement(*firstLetterElement, { PseudoElementType::FirstLetter }, resolutionContext);
}
ResolutionContext TreeResolver::makeResolutionContext()
{
return {
&parent().style,
parentBoxStyle(),
documentElementStyle(),
&scope().selectorMatchingState,
&m_treeResolutionState
};
}
ResolutionContext TreeResolver::makeResolutionContextForPseudoElement(const ElementUpdate& elementUpdate, const PseudoElementIdentifier& pseudoElementIdentifier)
{
auto parentStyle = [&]() -> const RenderStyle* {
if (auto parentPseudoId = parentPseudoElement(pseudoElementIdentifier.type)) {
if (auto* parentPseudoStyle = elementUpdate.style->getCachedPseudoStyle({ *parentPseudoId, (*parentPseudoId == PseudoElementType::ViewTransitionGroup || *parentPseudoId == PseudoElementType::ViewTransitionImagePair) ? pseudoElementIdentifier.nameArgument : nullAtom() }))
return parentPseudoStyle;
}
return elementUpdate.style.get();
};
return {
parentStyle(),
parentBoxStyleForPseudoElement(elementUpdate),
documentElementStyle(),
&scope().selectorMatchingState,
&m_treeResolutionState
};
}
std::optional<ResolutionContext> TreeResolver::makeResolutionContextForInheritedFirstLine(const ElementUpdate& elementUpdate, const RenderStyle& inheritStyle)
{
auto parentFirstLineStyle = inheritStyle.getCachedPseudoStyle({ PseudoElementType::FirstLine });
if (!parentFirstLineStyle)
return { };
// First line style for inlines is made by inheriting from parent first line style.
return ResolutionContext {
parentFirstLineStyle,
parentBoxStyleForPseudoElement(elementUpdate),
documentElementStyle(),
&scope().selectorMatchingState,
&m_treeResolutionState
};
}
const RenderStyle* TreeResolver::documentElementStyle() const
{
if (m_computedDocumentElementStyle)
return m_computedDocumentElementStyle.get();
return m_document->documentElement()->renderStyle();
}
auto TreeResolver::boxGeneratingParent() const -> const Parent*
{
// 'display: contents' doesn't generate boxes.
for (auto& parent : m_parentStack | std::views::reverse) {
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(ResolvedStyle&& resolvedStyle, const Styleable& styleable, OptionSet<Change> parentChanges, const ResolutionContext& resolutionContext, IsInDisplayNoneTree isInDisplayNoneTree)
{
Ref element = styleable.element;
Ref document = element->document();
auto* currentStyle = element->renderOrDisplayContentsStyle(styleable.pseudoElementIdentifier);
std::unique_ptr<RenderStyle> startingStyle;
// The style of the styleable is constantly in flux during anchor resolution and/or trying
// position options. Hence we skip updating/applying animations until both processes are
// complete and the style is stable.
auto skipAnimationForAnchorPosition = hasUnresolvedAnchorPosition(styleable) || isTryingPositionOption(styleable);
auto* oldStyle = [&]() -> const RenderStyle* {
if (auto* styleBefore = beforeResolutionStyle(element.get(), styleable.pseudoElementIdentifier))
return styleBefore;
if (resolvedStyle.style->hasTransitions()) {
// https://drafts.csswg.org/css-transitions-2/#at-ruledef-starting-style
// "If an element does not have a before-change style for a given style change event, the starting style is used instead."
startingStyle = resolveStartingStyle(resolvedStyle, styleable, resolutionContext);
return startingStyle.get();
}
return nullptr;
}();
if (skipAnimationForAnchorPosition) {
// A styleable gets its style resolved multiple times for anchor positioning.
// Therefore when updating animation is deferred, save the old style so it's restored
// (using beforeResolutionStyle) and can be used when animation is finally updated/applied.
saveBeforeResolutionStyleForInterleaving(styleable.element, oldStyle);
}
auto unanimatedDisplay = resolvedStyle.style->display();
WeakStyleOriginatedAnimations newStyleOriginatedAnimations;
auto updateAnimations = [&] {
if (document->backForwardCacheState() != Document::NotInBackForwardCache || document->printing())
return;
if (skipAnimationForAnchorPosition)
return;
if (oldStyle && (oldStyle->hasTransitions() || resolvedStyle.style->hasTransitions()))
styleable.updateCSSTransitions(*oldStyle, *resolvedStyle.style, newStyleOriginatedAnimations);
if ((oldStyle && oldStyle->hasScrollTimelines()) || resolvedStyle.style->hasScrollTimelines())
styleable.updateCSSScrollTimelines(oldStyle, *resolvedStyle.style);
if ((oldStyle && oldStyle->hasViewTimelines()) || resolvedStyle.style->hasViewTimelines())
styleable.updateCSSViewTimelines(oldStyle, *resolvedStyle.style);
if ((oldStyle && oldStyle->timelineScope().type != NameScope::Type::None) || resolvedStyle.style->timelineScope().type != NameScope::Type::None) {
CheckedRef styleOriginatedTimelinesController = element->protectedDocument()->ensureStyleOriginatedTimelinesController();
styleOriginatedTimelinesController->updateNamedTimelineMapForTimelineScope(resolvedStyle.style->timelineScope(), styleable);
}
// 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()) || resolvedStyle.style->hasAnimations())
styleable.updateCSSAnimations(oldStyle, *resolvedStyle.style, resolutionContext, newStyleOriginatedAnimations, isInDisplayNoneTree);
};
auto applyAnimations = [&]() -> std::pair<std::unique_ptr<RenderStyle>, OptionSet<AnimationImpact>> {
if (skipAnimationForAnchorPosition) {
auto newStyle = WTF::move(resolvedStyle.style);
ASSERT(newStyle);
// When display transitions to none, it keeps its old value throughout the transition,
// and only switches to none after the transition ends. CSS transition machinery normally
// handles this, except we skip applying transitions when anchor position hasn't been resolved.
// This results in display: none until its anchor position is resolved, when it switches back
// to the old value. To remedy this, we manually patch display to be the old value if:
// 1. the old style's display is not none, and
// 2. the new style has display: none and specifies a transition on display.
if (oldStyle && oldStyle->hasTransitions() && oldStyle->display() != DisplayType::None && styleHasDisplayTransition(*newStyle) && newStyle->display() == DisplayType::None)
newStyle->setDisplay(oldStyle->display());
return { WTF::move(newStyle), OptionSet<AnimationImpact> { } };
}
if (!styleable.hasKeyframeEffects()) {
// FIXME: Push after-change style into parent stack instead.
styleable.setLastStyleChangeEventStyle(resolveAfterChangeStyleForNonAnimated(resolvedStyle, styleable, resolutionContext));
styleable.setHasPropertiesOverridenAfterAnimation(false);
return { WTF::move(resolvedStyle.style), OptionSet<AnimationImpact> { } };
}
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(*resolvedStyle.style));
// Apply all keyframe effects to the new style.
HashSet<AnimatableCSSProperty> animatedProperties;
auto animatedStyle = RenderStyle::clonePtr(*resolvedStyle.style);
auto animationImpact = styleable.applyKeyframeEffects(*animatedStyle, animatedProperties, previousLastStyleChangeEventStyle.get(), resolutionContext);
if (*resolvedStyle.style == *animatedStyle && animationImpact.isEmpty() && previousLastStyleChangeEventStyle)
return { WTF::move(resolvedStyle.style), animationImpact };
if (resolvedStyle.matchResult) {
auto animatedStyleBeforeCascadeApplication = RenderStyle::clonePtr(*animatedStyle);
// The cascade may override animated properties and have dependencies to them.
// FIXME: This is wrong if there are both transitions and animations running on the same element.
auto overriddenAnimatedProperties = applyCascadeAfterAnimation(*animatedStyle, animatedProperties, styleable.hasRunningTransitions(), *resolvedStyle.matchResult, element, resolutionContext);
ASSERT(styleable.keyframeEffectStack());
styleable.keyframeEffectStack()->cascadeDidOverrideProperties(overriddenAnimatedProperties, document);
styleable.setHasPropertiesOverridenAfterAnimation(!overriddenAnimatedProperties.isEmpty());
}
Adjuster adjuster(document, *resolutionContext.parentStyle, resolutionContext.parentBoxStyle, !styleable.pseudoElementIdentifier ? &styleable.element : nullptr);
adjuster.adjustAnimatedStyle(*animatedStyle, animationImpact);
return { WTF::move(animatedStyle), animationImpact };
};
// FIXME: Something like this is also needed for viewport units.
if (currentStyle && 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.
updateAnimations();
// Now we can update all Web animations, which will include CSS Animations as well
// as animations created via the JS API.
auto [newStyle, animationImpact] = applyAnimations();
// Deduplication speeds up equality comparisons as the properties inherit to descendants.
// FIXME: There should be a more general mechanism for this.
if (currentStyle)
newStyle->deduplicateCustomProperties(*currentStyle);
auto changes = currentStyle ? determineChanges(*currentStyle, *newStyle) : Change::Renderer;
if (element->hasInvalidRenderer() || parentChanges.contains(Change::Renderer))
changes.add(Change::Renderer);
collectChangedAnchorNames(*newStyle, currentStyle);
auto animationsAffectedDisplay = [&, animatedDisplay = newStyle->display()]() {
auto* keyframeEffectStack = styleable.keyframeEffectStack();
if (!keyframeEffectStack)
return false;
if (unanimatedDisplay != animatedDisplay)
return true;
return keyframeEffectStack->containsProperty(CSSPropertyDisplay);
}();
if (!affectsRenderedSubtree(styleable.element, *newStyle) && !animationsAffectedDisplay) {
// If after updating animations we end up not rendering this element or its subtree
// and the update did not change the "display" value then we should cancel all
// style-originated animations while ensuring that the new ones are canceled silently,
// as if they hadn't been created.
styleable.cancelStyleOriginatedAnimations(newStyleOriginatedAnimations);
} else if (!newStyleOriginatedAnimations.isEmpty()) {
// If style-originated animations were not canceled, then we should make sure that
// the creation of new style-originated animations during this update is known to the
// document's timeline as animation scheduling was paused for any animation created
// during this update.
if (RefPtr timeline = m_document->existingTimeline())
timeline->styleOriginatedAnimationsWereCreated();
}
if (animationsAffectedDisplay)
newStyle->setHasDisplayAffectedByAnimations();
bool shouldRecompositeLayer = animationImpact.contains(AnimationImpact::RequiresRecomposite) || element->styleResolutionShouldRecompositeLayer();
auto mayNeedRebuildRoot = [&, newStyle = newStyle.get()] {
if (changes.contains(Change::Renderer))
return true;
// We may need to rebuild the tree starting from further up if there is a position property change to clean up continuations.
if (currentStyle && currentStyle->position() != newStyle->position())
return true;
return false;
}();
return { WTF::move(newStyle), changes, shouldRecompositeLayer, mayNeedRebuildRoot };
}
std::unique_ptr<RenderStyle> TreeResolver::resolveStartingStyle(const ResolvedStyle& resolvedStyle, const Styleable& styleable, const ResolutionContext& resolutionContext)
{
if (!resolvedStyle.matchResult || !resolvedStyle.matchResult->hasStartingStyle)
return nullptr;
// "Starting style inherits from the parent’s after-change style just like after-change style does."
auto& parentStyle = parentAfterChangeStyle(styleable, resolutionContext);
// We now resolve the starting style by applying all rules (including @starting-style ones) again.
// We could compute it along with the primary style and include it in MatchedPropertiesCache but it is not
// clear this would be benefitial as it is typically only used once.
return resolveAgainInDifferentContext(resolvedStyle, styleable, parentStyle, PropertyCascade::startingStylePropertyTypes(), { }, resolutionContext);
}
std::unique_ptr<RenderStyle> TreeResolver::resolveAfterChangeStyleForNonAnimated(const ResolvedStyle& resolvedStyle, const Styleable& styleable, const ResolutionContext& resolutionContext)
{
// Element may have after-change style differing from the current style in case they are inheriting from a transitioning element.
// We need after-change style for non-animating elements only in case there @starting-style rules in the subtree.
if (!scope().resolver->usesStartingStyleRules())
return nullptr;
if (!resolvedStyle.matchResult)
return nullptr;
if (styleable.pseudoElementIdentifier)
return nullptr;
if (!parent().element || !parent().element->lastStyleChangeEventStyle({ }))
return nullptr;
// "Likewise, define the after-change style as.. and inheriting from the after-change style of the parent."
auto& parentStyle = parentAfterChangeStyle(styleable, resolutionContext);
return resolveAgainInDifferentContext(resolvedStyle, styleable, parentStyle, PropertyCascade::normalPropertyTypes(), { }, resolutionContext);
}
std::unique_ptr<RenderStyle> TreeResolver::resolveAgainInDifferentContext(const ResolvedStyle& resolvedStyle, const Styleable& styleable, const RenderStyle& parentStyle, OptionSet<PropertyCascade::PropertyType> properties, std::optional<BuilderPositionTryFallback>&& positionTryFallback, const ResolutionContext& resolutionContext)
{
ASSERT(resolvedStyle.matchResult);
auto newStyle = RenderStyle::createPtr();
newStyle->inheritFrom(parentStyle);
newStyle->setPseudoElementIdentifier(resolvedStyle.style->pseudoElementIdentifier());
newStyle->copyPseudoElementBitsFrom(*resolvedStyle.style);
auto builderContext = BuilderContext {
m_document.get(),
&parentStyle,
resolutionContext.documentElementStyle,
&styleable.element,
&m_treeResolutionState,
WTF::move(positionTryFallback)
};
auto styleBuilder = Builder {
*newStyle,
WTF::move(builderContext),
*resolvedStyle.matchResult,
{ properties }
};
styleBuilder.applyAllProperties();
if (newStyle->display() == DisplayType::None)
return nullptr;
Adjuster adjuster(m_document, parentStyle, resolutionContext.parentBoxStyle, !styleable.pseudoElementIdentifier ? &styleable.element : nullptr);
adjuster.adjust(*newStyle);
return newStyle;
}
const RenderStyle& TreeResolver::parentAfterChangeStyle(const Styleable& styleable, const ResolutionContext& resolutionContext) const
{
if (RefPtr parentElement = !styleable.pseudoElementIdentifier ? parent().element : &styleable.element) {
if (auto* afterChangeStyle = parentElement->lastStyleChangeEventStyle({ }))
return *afterChangeStyle;
}
return *resolutionContext.parentStyle;
}
HashSet<AnimatableCSSProperty> TreeResolver::applyCascadeAfterAnimation(RenderStyle& animatedStyle, const HashSet<AnimatableCSSProperty>& animatedProperties, bool isTransition, const MatchResult& matchResult, const Element& element, const ResolutionContext& resolutionContext)
{
auto builderContext = BuilderContext {
m_document.get(),
resolutionContext.parentStyle,
resolutionContext.documentElementStyle,
&element,
&m_treeResolutionState
};
auto styleBuilder = Builder {
animatedStyle,
WTF::move(builderContext),
matchResult,
{ isTransition ? PropertyCascade::PropertyType::AfterTransition : PropertyCascade::PropertyType::AfterAnimation },
&animatedProperties
};
styleBuilder.applyAllProperties();
return styleBuilder.overriddenAnimatedProperties();
}
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void TreeResolver::pushParent(Element& element, const RenderStyle& style, OptionSet<Change> changes, DescendantsToResolve descendantsToResolve, IsInDisplayNoneTree isInDisplayNoneTree, bool didAXUpdateFontSubtree, bool didAXUpdateTextColorSubtree)
#else
void TreeResolver::pushParent(Element& element, const RenderStyle& style, OptionSet<Change> changes, DescendantsToResolve descendantsToResolve, IsInDisplayNoneTree isInDisplayNoneTree)
#endif
{
scope().selectorMatchingState.selectorFilter.pushParent(&element);
if (style.containerType() != ContainerType::Normal)
scope().selectorMatchingState.containerQueryEvaluationState.sizeQueryContainers.append(element);
Parent parent(element, style, changes, descendantsToResolve, isInDisplayNoneTree);
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
parent.didAXUpdateFontSubtree = didAXUpdateFontSubtree;
parent.didAXUpdateTextColorSubtree = didAXUpdateTextColorSubtree;
#endif
if (RefPtr shadowRoot = element.shadowRoot()) {
pushScope(*shadowRoot);
parent.didPushScope = true;
} else if (RefPtr slot = dynamicDowncast<HTMLSlotElement>(element); slot && slot->assignedNodes()) {
pushEnclosingScope();
parent.didPushScope = true;
}
parent.needsUpdateQueryContainerDependentStyle = m_parentStack.last().needsUpdateQueryContainerDependentStyle || element.needsUpdateQueryContainerDependentStyle();
element.clearNeedsUpdateQueryContainerDependentStyle();
m_parentStack.append(WTF::move(parent));
}
void TreeResolver::popParent()
{
Ref parentElement = *parent().element;
parentElement->setHasValidStyle();
parentElement->clearChildNeedsStyleRecalc();
// FIXME: Push after-change style into parent stack instead.
if (!parentElement->hasKeyframeEffects({ }))
parentElement->setLastStyleChangeEventStyle({ }, nullptr);
if (parent().didPushScope)
popScope();
scope().selectorMatchingState.selectorFilter.popParent();
auto& queryContainers = scope().selectorMatchingState.containerQueryEvaluationState.sizeQueryContainers;
if (!queryContainers.isEmpty() && queryContainers.last().ptr() == parentElement.ptr())
queryContainers.removeLast();
m_parentStack.removeLast();
}
void TreeResolver::popParentsToDepth(unsigned depth)
{
ASSERT(depth);
ASSERT(m_parentStack.size() >= depth);
while (m_parentStack.size() > depth)
popParent();
}
auto TreeResolver::determineResolutionType(const Element& element, const RenderStyle* existingStyle, DescendantsToResolve parentDescendantsToResolve, OptionSet<Change> parentChanges) -> std::optional<ResolutionType>
{
auto combinedValidity = [&] {
auto validity = element.styleValidity();
if (RefPtr pseudoElement = element.beforePseudoElement())
validity = std::max(validity, pseudoElement->styleValidity());
if (RefPtr pseudoElement = element.afterPseudoElement())
validity = std::max(validity, pseudoElement->styleValidity());
return validity;
}();
if (parentDescendantsToResolve == DescendantsToResolve::None) {
if (combinedValidity == Validity::AnimationInvalid)
return ResolutionType::AnimationOnly;
if (combinedValidity == Validity::Valid && element.hasInvalidRenderer())
return existingStyle ? ResolutionType::RebuildUsingExisting : ResolutionType::Full;
if (combinedValidity == Validity::InlineStyleInvalid && existingStyle)
return ResolutionType::FullWithMatchResultCache;
}
if (combinedValidity > Validity::Valid)
return ResolutionType::Full;
switch (parentDescendantsToResolve) {
case DescendantsToResolve::None:
return { };
case DescendantsToResolve::RebuildAllUsingExisting:
return existingStyle ? ResolutionType::RebuildUsingExisting : ResolutionType::Full;
case DescendantsToResolve::Children: {
auto canFastPathInherit = [&] {
if (parentChanges.contains(Change::Inherited))
return false;
if (!parentChanges.contains(Change::FastPathInherited))
return false;
if (!existingStyle || existingStyle->disallowsFastPathInheritance())
return false;
// Some non-inherited property changed along with a fast-path property and we may need to inherit it too.
if (parentChanges.contains(Change::NonInherited) && existingStyle->hasExplicitlyInheritedProperties())
return false;
return true;
};
return canFastPathInherit() ? ResolutionType::FastPathInherit : ResolutionType::Full;
}
case DescendantsToResolve::All:
return ResolutionType::Full;
case DescendantsToResolve::ChildrenWithExplicitInherit:
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 (Ref descendant : descendantsOfType<Element>(element)) {
if (styleScope.hasPendingSheetInBody(descendant.get()))
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::RebuildAllUsingExisting:
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());
Ref 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 (RefPtr text = dynamicDowncast<Text>(node)) {
auto containsOnlyASCIIWhitespace = text->containsOnlyASCIIWhitespace();
auto needsTextUpdate = [&] {
if ((text->hasInvalidRenderer() && parent.changes != Change::Renderer) || parent.style.display() == DisplayType::Contents)
return true;
if (!text->renderer() && containsOnlyASCIIWhitespace && parent.style.preserveNewline()) {
// FIXME: This really needs to be done only when parent.style.preserveNewline() changes value.
return true;
}
return false;
};
if (needsTextUpdate()) {
TextUpdate textUpdate;
textUpdate.inheritedDisplayContentsStyle = createInheritedDisplayContentsStyleIfNeeded(parent.style, parentBoxStyle());
m_update->addText(*text, parent.element, WTF::move(textUpdate));
}
if (!containsOnlyASCIIWhitespace)
parent.resolvedFirstLineAndLetterChild = true;
text->setHasValidStyle();
it.traverseNextSkippingChildren();
continue;
}
Ref element = Ref { downcast<Element>(node.get()) };
if (it.depth() > Settings::defaultMaximumRenderTreeDepth) {
resetStyleForNonRenderedDescendants(element.get());
it.traverseNextSkippingChildren();
continue;
}
auto* style = existingStyle(element.get());
auto changes = OptionSet<Change> { };
auto descendantsToResolve = DescendantsToResolve::None;
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
bool didAXUpdateFontSubtree = parent.didAXUpdateFontSubtree;
bool didAXUpdateTextColorSubtree = parent.didAXUpdateTextColorSubtree;
#endif
auto resolutionType = determineResolutionType(element.get(), style, parent.descendantsToResolve, parent.changes);
if (resolutionType) {
element->resetComputedStyle();
if (*resolutionType == ResolutionType::Full)
element->resetStyleRelations();
if (element->hasCustomStyleResolveCallbacks())
element->willRecalcStyle(parent.changes);
auto [elementUpdate, elementDescendantsToResolve] = resolveElement(element.get(), style, *resolutionType);
if (element->hasCustomStyleResolveCallbacks())
element->didRecalcStyle(elementUpdate.changes);
if (CheckedPtr cache = m_document->existingAXObjectCache()) {
cache->onStyleChange(element.get(), elementUpdate.changes, style, elementUpdate.style.get());
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
if (!didAXUpdateFontSubtree)
didAXUpdateFontSubtree = cache->onFontChange(element.get(), style, elementUpdate.style.get());
if (!didAXUpdateTextColorSubtree)
didAXUpdateTextColorSubtree = cache->onTextColorChange(element.get(), style, elementUpdate.style.get());
#endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE)
}
style = elementUpdate.style.get();
changes = elementUpdate.changes;
descendantsToResolve = elementDescendantsToResolve;
if (style || element->hasDisplayNone())
m_update->addElement(element.get(), parent.element, WTF::move(elementUpdate));
if (style && element.ptr() == m_document->documentElement())
m_computedDocumentElementStyle = RenderStyle::clonePtr(*style);
clearNeedsStyleResolution(element.get());
}
if (!style)
resetStyleForNonRenderedDescendants(element.get());
auto queryContainerAction = updateStateForQueryContainer(element.get(), style, descendantsToResolve);
auto anchorPositionedElementAction = updateAnchorPositioningState(element.get(), style);
resumeDescendantResolutionIfNeeded(element.get(), changes, descendantsToResolve);
bool shouldIterateChildren = [&] {
// display::none, no need to resolve descendants.
if (!style)
return false;
// Style resolution will be resumed after the container or anchor-positioned element has been resolved.
if (queryContainerAction == LayoutInterleavingAction::SkipDescendants || anchorPositionedElementAction == LayoutInterleavingAction::SkipDescendants) {
deferDescendantResolution(element.get(), changes, descendantsToResolve);
return false;
}
return element->childNeedsStyleRecalc() || descendantsToResolve != DescendantsToResolve::None;
}();
if (!m_didSeePendingStylesheet)
m_didSeePendingStylesheet = hasLoadingStylesheet(m_document->styleScope(), element.get(), !shouldIterateChildren);
if (!parent.resolvedFirstLineAndLetterChild && style && generatesBox(*style) && supportsFirstLineAndLetterPseudoElement(*style))
parent.resolvedFirstLineAndLetterChild = true;
if (!shouldIterateChildren) {
it.traverseNextSkippingChildren();
continue;
}
resetDescendantStyleRelations(element.get(), descendantsToResolve);
auto isInDisplayNoneTree = parent.isInDisplayNoneTree == IsInDisplayNoneTree::Yes || !style || style->display() == DisplayType::None;
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
pushParent(element.get(), *style, changes, descendantsToResolve, isInDisplayNoneTree ? IsInDisplayNoneTree::Yes : IsInDisplayNoneTree::No, didAXUpdateFontSubtree, didAXUpdateTextColorSubtree);
#else
pushParent(element.get(), *style, changes, descendantsToResolve, isInDisplayNoneTree ? IsInDisplayNoneTree::Yes : IsInDisplayNoneTree::No);
#endif
it.traverseNext();
}
popParentsToDepth(1);
}
const RenderStyle* TreeResolver::existingStyle(const Element& element)
{
auto* style = element.renderOrDisplayContentsStyle();
if (style && &element == m_document->documentElement()) {
// Document element style may have got adjusted based on body style but we don't want to inherit those adjustments.
m_computedDocumentElementStyle = Adjuster::restoreUsedDocumentElementStyleToComputed(*style);
if (m_computedDocumentElementStyle)
style = m_computedDocumentElementStyle.get();
}
return style;
}
void TreeResolver::deferDescendantResolution(Element& element, OptionSet<Change> changes, DescendantsToResolve descendantsToResolve)
{
m_deferredDescendantResolutionStates.add(element, DeferredDescendantResolutionState {
.changes = changes,
.descendantsToResolve = descendantsToResolve
});
}
void TreeResolver::resumeDescendantResolutionIfNeeded(Element& element, OptionSet<Change>& changes, DescendantsToResolve& descendantsToResolve)
{
auto it = m_deferredDescendantResolutionStates.find(element);
if (it == m_deferredDescendantResolutionStates.end())
return;
const auto& state = it->value;
changes |= state.changes;
descendantsToResolve = std::max(descendantsToResolve, state.descendantsToResolve);
m_deferredDescendantResolutionStates.remove(it);
}
auto TreeResolver::updateStateForQueryContainer(Element& element, const RenderStyle* style, DescendantsToResolve& descendantsToResolve) -> LayoutInterleavingAction
{
if (!style)
return LayoutInterleavingAction::None;
if (m_queryContainerStates.get(element).invalidated)
return LayoutInterleavingAction::None;
auto* existingStyle = element.renderOrDisplayContentsStyle();
if (style->containerType() != ContainerType::Normal || (existingStyle && existingStyle->containerType() != ContainerType::Normal)) {
// If any of the queries use font-size relative units then a font size change
// may affect their evaluation, so force re-evaluating all descendants.
if (styleChangeAffectsRelativeUnits(*style, existingStyle))
descendantsToResolve = DescendantsToResolve::All;
m_queryContainerStates.add(element, QueryContainerState { });
return LayoutInterleavingAction::SkipDescendants;
}
return LayoutInterleavingAction::None;
}
std::unique_ptr<Update> TreeResolver::resolve()
{
auto didInterleavedLayout = std::exchange(m_needsInterleavedLayout, false);
if (didInterleavedLayout)
m_didFirstInterleavedLayout = true;
RefPtr documentElement = m_document->documentElement();
if (!documentElement) {
m_document->styleScope().resolver();
return nullptr;
}
if (didInterleavedLayout)
AnchorPositionEvaluator::updateAnchorPositioningStatesAfterInterleavedLayout(m_document, m_treeResolutionState.anchorPositionedStates);
if (!documentElement->childNeedsStyleRecalc() && !documentElement->needsStyleRecalc())
return WTF::move(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_update)));
m_parentStack.append(Parent(m_document));
resolveComposedTree();
ASSERT(m_scopeStack.size() == 1);
ASSERT(m_parentStack.size() == 1);
m_parentStack.clear();
popScope();
for (auto& [element, state] : m_queryContainerStates) {
// Ensure that resumed resolution reaches the container.
if (!state.invalidated) {
element->invalidateForResumingQueryContainerResolution();
state.invalidated = true;
m_needsInterleavedLayout = true;
}
}
for (auto& elementAndState : m_treeResolutionState.anchorPositionedStates) {
// Ensure that style resolution visits any unresolved anchor-positioned elements.
if (elementAndState.value->stage < AnchorPositionResolutionStage::Resolved) {
const_cast<Element&>(*elementAndState.key.first).invalidateForResumingAnchorPositionedElementResolution();
m_needsInterleavedLayout = true;
}
}
for (auto& [styleable, options] : m_positionOptions) {
if (!options.chosen) {
ASSERT(styleable.first);
const_cast<Element&>(*styleable.first).invalidateForResumingAnchorPositionedElementResolution();
m_needsInterleavedLayout = true;
}
}
if (!m_changedAnchorNames.isEmpty() || m_allAnchorNamesInvalid) {
// If there are changes to the anchor names then loop through the existing anchors and see if any of them references those names.
for (auto entry : m_document->styleScope().anchorPositionedToAnchorMap()) {
CheckedRef anchorPositionedElement = entry.key;
auto& anchors = entry.value;
bool anchorPositionedReferencesChangedAnchorNames = [&] {
if (m_allAnchorNamesInvalid)
return true;
for (auto anchor : anchors.anchors) {
if (m_changedAnchorNames.contains(anchor.name.name()))
return true;
}
return false;
}();
if (anchorPositionedReferencesChangedAnchorNames) {
// Invalidate the anchor-positioned element, so subsequent style resolution rounds would visit it.
anchorPositionedElement->invalidateForResumingAnchorPositionedElementResolution();
// Mark that additional style resolution round is needed.
m_needsInterleavedLayout = true;
// If the anchor-positioned element is currently being tracked for resolution,
// reset the resolution stage to FindAnchor. This re-runs anchor resolution to
// pick up new anchor name changes.
AnchorPositionedKey anchorPositionedKey { anchorPositionedElement.ptr(), anchors.pseudoElementIdentifier };
auto stateIt = m_treeResolutionState.anchorPositionedStates.find(anchorPositionedKey);
if (stateIt != m_treeResolutionState.anchorPositionedStates.end()) {
ASSERT(stateIt->value);
stateIt->value->stage = AnchorPositionResolutionStage::FindAnchors;
}
}
}
m_changedAnchorNames = { };
m_allAnchorNamesInvalid = false;
}
if (m_update->roots().isEmpty())
return { };
// Ensure we do at least one interleaved layout as any style change may affect existing anchor positions.
if (!m_didFirstInterleavedLayout && !m_document->styleScope().anchorPositionedToAnchorMap().isEmptyIgnoringNullReferences())
m_needsInterleavedLayout = true;
Adjuster::propagateToDocumentElementAndInitialContainingBlock(*m_update, m_document);
return WTF::move(m_update);
}
auto TreeResolver::updateAnchorPositioningState(Element& element, const RenderStyle* style) -> LayoutInterleavingAction
{
if (!style)
return LayoutInterleavingAction::None;
auto update = [&](const RenderStyle* style) {
if (!style)
return;
AnchorPositionEvaluator::updateAnchorPositionedStateForDefaultAnchorAndPositionVisibility(element, *style, m_treeResolutionState.anchorPositionedStates);
};
update(style);
update(style->getCachedPseudoStyle({ PseudoElementType::Before }));
update(style->getCachedPseudoStyle({ PseudoElementType::After }));
auto needsInterleavedLayout = hasUnresolvedAnchorPosition({ element, { } });
if (needsInterleavedLayout)
return LayoutInterleavingAction::SkipDescendants;
return LayoutInterleavingAction::None;
}
static std::optional<LayoutSize> scrollContainerSizeForPositionOptions(const Styleable& anchored)
{
CheckedPtr anchoredRenderer = anchored.renderer();
if (!anchoredRenderer)
return { };
// Overlay scrollbars can't affect anchor() function resolution so we don't need to save the size.
CheckedRef containingBlock = *anchoredRenderer->containingBlock();
if (containingBlock->canUseOverlayScrollbars())
return { };
bool isOverflowScroller = containingBlock->isScrollContainerY() || containingBlock->isScrollContainerY();
if (!containingBlock->isRenderView() && !isOverflowScroller)
return { };
return containingBlock->contentBoxSize();
}
void TreeResolver::generatePositionOptionsIfNeeded(const ResolvedStyle& resolvedStyle, const Styleable& styleable, const ResolutionContext& resolutionContext)
{
// https://drafts.csswg.org/css-anchor-position-1/#fallback-apply
if (!resolvedStyle.style || resolvedStyle.style->positionTryFallbacks().isNone())
return;
if (!resolvedStyle.style->hasOutOfFlowPosition())
return;
AnchorPositionedKey positionOptionsKey { styleable.element, styleable.pseudoElementIdentifier };
if (m_positionOptions.contains(positionOptionsKey))
return;
auto generatePositionOptions = [&] {
ResolvedStyle clonedResolvedStyle {
.style = RenderStyle::clonePtr(*resolvedStyle.style),
.relations = { },
.matchResult = resolvedStyle.matchResult
};
PositionOptions options { .originalResolvedStyle = WTF::move(clonedResolvedStyle) };
auto scrollContainerSizeOnGeneration = scrollContainerSizeForPositionOptions(styleable);
options.optionStyles.append({ RenderStyle::clonePtr(*resolvedStyle.style), { }, scrollContainerSizeOnGeneration });
for (auto [i, fallback] : indexedRange(resolvedStyle.style->positionTryFallbacks())) {
auto optionStyle = generatePositionOption(fallback, options.originalResolvedStyle, styleable, resolutionContext);
if (!optionStyle)
continue;
optionStyle->setUsedPositionOptionIndex(i);
options.optionStyles.append({ WTF::move(optionStyle), fallback, scrollContainerSizeOnGeneration });
}
return options;
};
auto options = generatePositionOptions();
// If the fallbacks contain anchor references we need to resolve the anchors first and regenerate the options.
if (hasUnresolvedAnchorPosition(styleable))
return;
m_positionOptions.add(positionOptionsKey, WTF::move(options));
}
std::unique_ptr<RenderStyle> TreeResolver::generatePositionOption(const PositionTryFallback& fallback, const ResolvedStyle& resolvedStyle, const Styleable& styleable, const ResolutionContext& resolutionContext)
{
// https://drafts.csswg.org/css-anchor-position-1/#fallback-apply
if (!resolvedStyle.matchResult)
return { };
auto resolveFallbackProperties = [&] -> RefPtr<const StyleProperties> {
if (fallback.positionArea.properties) {
ASSERT(!fallback.ruleAndTactics.rule);
ASSERT(!fallback.ruleAndTactics.tactics);
return fallback.positionArea.properties;
}
if (!fallback.ruleAndTactics.rule)
return nullptr;
// "If an at-rule or property defines a name that other CSS constructs can refer to it by, ... it must be defined as a tree-scoped name."
// https://drafts.csswg.org/css-scoping-1/#shadow-names
return Style::Scope::resolveTreeScopedReference(styleable.element, *fallback.ruleAndTactics.rule, [](const Style::Scope& scope, const AtomString& name) -> RefPtr<const StyleProperties> {
Ref ruleSet = scope.resolverIfExists()->ruleSets().authorStyle();
auto rule = ruleSet->positionTryRuleForName(name);
if (!rule)
return nullptr;
return rule->properties();
});
};
auto builderFallback = BuilderPositionTryFallback {
.properties = resolveFallbackProperties(),
.tactics = fallback.ruleAndTactics.tactics->value,
};
return resolveAgainInDifferentContext(resolvedStyle, styleable, *resolutionContext.parentStyle, PropertyCascade::normalPropertyTypes(), WTF::move(builderFallback), resolutionContext);
}
const RenderStyle& TreeResolver::PositionOptions::originalStyle() const
{
ASSERT(optionStyles.size());
ASSERT(optionStyles[0].style);
return *optionStyles[0].style;
}
std::unique_ptr<RenderStyle> TreeResolver::PositionOptions::currentOption() const
{
ASSERT(index < optionStyles.size());
ASSERT(optionStyles[index].style);
return RenderStyle::clonePtr(*optionStyles[index].style);
}
void TreeResolver::sortPositionOptionsIfNeeded(PositionOptions& options, const Styleable& styleable)
{
if (options.sorted)
return;
options.sorted = true;
CheckedPtr box = dynamicDowncast<RenderBox>(styleable.renderer());
if (!box || !box->isOutOfFlowPositioned())
return;
auto order = options.originalStyle().positionTryOrder();
if (order != PositionTryOrder::Normal && options.optionStyles.size() > 2) {
// "For each entry in the position options list, apply that position option to the box, and find
// the specified inset-modified containing block size that results from those styles."
// https://drafts.csswg.org/css-anchor-position-1/#position-try-order-property
auto boxAxis = boxAxisForPositionTryOrder(order, options.originalStyle().writingMode());
struct SortingOption {
PositionOption option;
LayoutUnit containingBlockSize;
};
Vector<SortingOption> optionsForSorting;
optionsForSorting.reserveInitialCapacity(options.optionStyles.size());
for (size_t i = 1; i < options.optionStyles.size(); ++i) {
auto constraints = PositionedLayoutConstraints { *box, *options.optionStyles[i].style, boxAxis };
constraints.computeInsets();
optionsForSorting.append({ WTF::move(options.optionStyles[i]), constraints.insetModifiedContainingSize() });
}
// "Stably sort the position options list according to this size, with the largest coming first."
std::ranges::stable_sort(optionsForSorting, std::ranges::greater { }, &SortingOption::containingBlockSize);
for (size_t i = 0; i < optionsForSorting.size(); ++i)
options.optionStyles[i + 1] = WTF::move(optionsForSorting[i].option);
}
// If the styleable has a last successful position option...
if (auto lastSuccessfulIndex = m_document->styleScope().lastSuccessfulPositionOptionIndexFor(styleable)) {
// ... find which style in options.optionStyles has that index
auto lastSuccessfulIndexInOptionStyles = options.optionStyles.findIf([lastSuccessfulIndex] (const auto& option) {
ASSERT(option.style);
return option.style->usedPositionOptionIndex() == *lastSuccessfulIndex;
});
// If there's one, move it to the beginning.
// (if it's at index zero, do nothing since it's already at the beginning)
if (lastSuccessfulIndexInOptionStyles && lastSuccessfulIndexInOptionStyles != notFound) {
auto lastSuccessfulOption = WTF::move(options.optionStyles[lastSuccessfulIndexInOptionStyles]);
options.optionStyles.removeAt(lastSuccessfulIndexInOptionStyles);
options.optionStyles.insert(0, WTF::move(lastSuccessfulOption));
}
}
}
std::optional<ResolvedStyle> TreeResolver::tryChoosePositionOption(const Styleable& styleable, const ResolutionContext& resolutionContext)
{
// https://drafts.csswg.org/css-anchor-position-1/#fallback-apply
AnchorPositionedKey anchorPositionedKey { styleable.element, styleable.pseudoElementIdentifier };
auto optionIt = m_positionOptions.find(anchorPositionedKey);
if (optionIt == m_positionOptions.end())
return { };
auto& options = optionIt->value;
sortPositionOptionsIfNeeded(options, styleable);
auto invalidateQueryContainer = [&] () {
// Pseudo-elements can't be query containers, so skip invalidating for pseudo-elements.
if (styleable.pseudoElementIdentifier)
return;
auto iter = m_queryContainerStates.find(styleable.element);
if (iter == m_queryContainerStates.end())
return;
iter->value.invalidated = false;
};
auto renderer = dynamicDowncast<RenderBox>(styleable.renderer());
if (!renderer) {
invalidateQueryContainer();
options.chosen = true;
options.index = 0;
return ResolvedStyle { RenderStyle::clonePtr(options.originalStyle()) };
}
// On the first try, we force apply the original style (which _could_ be different from
// the existing style, since the original style might have a fallback applied to it)
if (options.isFirstTry) {
invalidateQueryContainer();
options.isFirstTry = false;
options.index = 0;
return ResolvedStyle { options.currentOption() };
}
if (!options.chosen) {
ASSERT(options.index < options.optionStyles.size());
auto& option = options.optionStyles[options.index];
auto newScrollContainerSize = scrollContainerSizeForPositionOptions(styleable);
// Re-generate the options if a scrollbar change changes the view size. It may affect anchor() function resolution.
if (option.scrollContainerSizeOnGeneration != newScrollContainerSize) {
option.scrollContainerSizeOnGeneration = newScrollContainerSize;
if (option.option)
option.style = generatePositionOption(*option.option, options.originalResolvedStyle, styleable, resolutionContext);
return ResolvedStyle { options.currentOption() };
}
}
// We can't test for overflow before the box has been positioned.
auto* anchorPositionedState = m_treeResolutionState.anchorPositionedStates.get({ &styleable.element, styleable.pseudoElementIdentifier });
if (anchorPositionedState && anchorPositionedState->stage < AnchorPositionResolutionStage::Positioned)
return ResolvedStyle { options.currentOption() };
if (!AnchorPositionEvaluator::overflowsInsetModifiedContainingBlock(*renderer)) {
// We don't overflow anymore so this is a good style.
options.chosen = true;
return ResolvedStyle { options.currentOption() };
}
if (options.chosen) {
// We have already chosen.
return ResolvedStyle { options.currentOption() };
}
// Next option to try if this doesn't work.
++options.index;
invalidateQueryContainer();
if (options.index >= options.optionStyles.size()) {
// None of the options worked, return back to the original.
options.index = 0;
options.chosen = true;
return ResolvedStyle { options.currentOption() };
}
return ResolvedStyle { options.currentOption() };
}
void TreeResolver::updateForPositionVisibility(RenderStyle& style, const Styleable& styleable)
{
if (!hasResolvedAnchorPosition(styleable))
return;
auto shouldHideAnchorPositioned = [&] {
CheckedPtr anchored = dynamicDowncast<RenderBox>(styleable.renderer());
if (!anchored)
return false;
if (style.positionVisibility().contains(PositionVisibilityValue::AnchorsVisible)) {
// "If the box has a default anchor box but that anchor box is invisible or clipped by intervening boxes, the box’s visibility property computes to force-hidden."
if (AnchorPositionEvaluator::isDefaultAnchorInvisibleOrClippedByInterveningBoxes(*anchored))
return true;
}
if (style.positionVisibility().contains(PositionVisibilityValue::NoOverflow)) {
if (AnchorPositionEvaluator::overflowsInsetModifiedContainingBlock(*anchored))
return true;
}
if (style.positionVisibility().contains(PositionVisibilityValue::AnchorsValid)) {
auto* anchorPositionedState = m_treeResolutionState.anchorPositionedStates.get({ &styleable.element, styleable.pseudoElementIdentifier });
if (anchorPositionedState) {
for (auto& anchorElement : anchorPositionedState->anchorElements.values()) {
if (!anchorElement)
return true;
}
}
}
return false;
};
// FIXME: Implement via "visibility: force-hidden".
if (shouldHideAnchorPositioned())
style.setIsForceHidden();
}
const RenderStyle* TreeResolver::beforeResolutionStyle(const Element& element, std::optional<PseudoElementIdentifier> pseudo)
{
auto resolvePseudoStyle = [&](auto* style) -> const RenderStyle* {
if (!pseudo)
return style;
if (!style)
return nullptr;
return style->getCachedPseudoStyle(*pseudo);
};
auto it = m_savedBeforeResolutionStylesForInterleaving.find(element);
if (it != m_savedBeforeResolutionStylesForInterleaving.end())
return resolvePseudoStyle(it->value.get());
return resolvePseudoStyle(element.renderOrDisplayContentsStyle());
}
void TreeResolver::saveBeforeResolutionStyleForInterleaving(const Element& element, const RenderStyle* style)
{
m_savedBeforeResolutionStylesForInterleaving.add(element, style ? RenderStyle::clonePtr(*style) : nullptr);
}
bool TreeResolver::hasUnresolvedAnchorPosition(const Styleable& styleable) const
{
auto* anchorPositionedState = m_treeResolutionState.anchorPositionedStates.get({ &styleable.element, styleable.pseudoElementIdentifier });
if (anchorPositionedState && anchorPositionedState->stage < AnchorPositionResolutionStage::Resolved)
return true;
return false;
}
bool TreeResolver::hasResolvedAnchorPosition(const Styleable& styleable) const
{
auto* anchorPositionedState = m_treeResolutionState.anchorPositionedStates.get({ &styleable.element, styleable.pseudoElementIdentifier });
if (anchorPositionedState && anchorPositionedState->stage >= AnchorPositionResolutionStage::Resolved)
return true;
return false;
}
bool TreeResolver::isTryingPositionOption(const Styleable& styleable) const
{
if (auto it = m_positionOptions.find({ styleable.element, styleable.pseudoElementIdentifier }); it != m_positionOptions.end())
return !it->value.chosen;
return false;
}
void TreeResolver::collectChangedAnchorNames(const RenderStyle& newStyle, const RenderStyle* currentStyle)
{
// A changed anchor name is either a name being added, a name being removed, or a name whose interpretation changes.
// This may change which elements get anchored to it.
if (!currentStyle || currentStyle->anchorNames() != newStyle.anchorNames()) {
// This could check which individual names differ but usually there is just one.
auto addChanged = [&](auto& style) {
for (auto& name : style.anchorNames())
m_changedAnchorNames.add(name.name);
};
if (currentStyle)
addChanged(*currentStyle);
addChanged(newStyle);
}
// Only anchor-names in self and descendants are affected by anchor-scope so we need to check only if there is an existing style.
if (currentStyle && currentStyle->anchorScope() != newStyle.anchorScope()) {
auto addChanged = [&](auto& style) {
switch (style.anchorScope().type) {
case NameScope::Type::None:
break;
case NameScope::Type::All:
// This affects desdendants too so lets just say all names are invalid.
m_allAnchorNamesInvalid = true;
break;
case NameScope::Type::Ident:
// A scope change changes interpretation of these names.
for (auto& name : style.anchorScope().names)
m_changedAnchorNames.add(name.value);
break;
}
};
addChanged(*currentStyle);
addChanged(newStyle);
}
}
static Vector<Function<void ()>>& postResolutionCallbackQueue()
{
static NeverDestroyed<Vector<Function<void ()>>> vector;
return vector;
}
static Vector<Ref<Frame>>& memoryCacheClientCallsResumeQueue()
{
static NeverDestroyed<Vector<Ref<Frame>>> vector;
return vector;
}
void deprecatedQueuePostResolutionCallback(Function<void()>&& callback)
{
postResolutionCallbackQueue().append(WTF::move(callback));
}
static void suspendMemoryCacheClientCalls(Document& document)
{
RefPtr page = document.page();
if (!page || !page->areMemoryCacheClientCallsEnabled())
return;
page->setMemoryCacheClientCallsEnabled(false);
if (RefPtr mainFrame = page->mainFrame())
memoryCacheClientCallsResumeQueue().append(mainFrame.releaseNonNull());
}
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 (RefPtr page = queue[i]->page())
page->setMemoryCacheClientCallsEnabled(true);
}
queue.clear();
platformStrategies()->loaderStrategy()->resumePendingRequests();
}
--resolutionNestingDepth;
}
bool postResolutionCallbacksAreSuspended()
{
return resolutionNestingDepth;
}
}
}