blob: 9d7f7eb126d6621398d946c39fbf3703d4d7914e [file]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* (C) 2006 Alexey Proskuryakov (ap@webkit.org)
* Copyright (C) 2004-2026 Apple Inc. All rights reserved.
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
* Copyright (C) 2008, 2009, 2011, 2012 Google Inc. All rights reserved.
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) Research In Motion Limited 2010-2011. All rights reserved.
*
* 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 "StyleDocumentScope.h"
#include "CSSCounterStyleRegistry.h"
#include "CSSFontSelector.h"
#include "CSSKeyframesRule.h"
#include "CSSStyleSheet.h"
#include "ContainerNodeInlines.h"
#include "DocumentInlines.h"
#include "DocumentView.h"
#include "Element.h"
#include "ElementAncestorIteratorInlines.h"
#include "ElementChildIteratorInlines.h"
#include "ElementRareData.h"
#include "InspectorInstrumentation.h"
#include "MatchResultCache.h"
#include "RenderBoxInlines.h"
#include "RenderElementStyleInlines.h"
#include "RenderLayer.h"
#include "RenderObjectInlines.h"
#include "RenderView.h"
#include "RuleSet.h"
#include "ShadowRoot.h"
#include "StyleableInlines.h"
#include "StyleCustomPropertyRegistry.h"
#include "StyleEnvironmentVariables.h"
#include "StyleInvalidator.h"
#include "StyleResolver.h"
#include "StyleSheetContents.h"
#include <wtf/SetForScope.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
namespace Style {
WTF_MAKE_TZONE_ALLOCATED_IMPL(DocumentScope);
DocumentScope::DocumentScope(Document& document)
: Scope(document)
{
}
DocumentScope::~DocumentScope() = default;
void DocumentScope::createDocumentResolver()
{
ASSERT(!m_resolver);
ASSERT(!m_shadowRoot);
SetForScope isUpdatingStyleResolver { m_isUpdatingStyleResolver, true };
m_resolver = Resolver::create(m_document, Resolver::ScopeType::Document);
if (!m_dynamicViewTransitionsStyle)
m_dynamicViewTransitionsStyle = RuleSet::create();
m_resolver->ruleSets().setDynamicViewTransitionsStyle(m_dynamicViewTransitionsStyle.get());
for (auto& keyframes : m_viewTransitionKeyframes)
m_resolver->addKeyframeStyle(keyframes.copyRef());
protect(m_document->fontSelector())->buildStarted();
m_resolver->ruleSets().initializeUserStyle();
m_resolver->addCurrentSVGFontFaceRules();
m_resolver->appendAuthorStyleSheets(m_activeStyleSheets);
protect(m_document->fontSelector())->buildCompleted();
}
void DocumentScope::clearViewTransitionStyles()
{
clearResolver();
m_dynamicViewTransitionsStyle = nullptr;
m_viewTransitionKeyframes.clear();
}
void DocumentScope::addViewTransitionKeyframes(Ref<StyleRuleKeyframes>&& rule)
{
// Add to the existing resolver, if any, and retain the keyframes so they can be
// re-established when the resolver is recreated (e.g. by a stylesheet mutation)
// during the transition.
if (RefPtr resolver = resolverIfExists())
resolver->addKeyframeStyle(rule.copyRef());
m_viewTransitionKeyframes.append(WTF::move(rule));
}
void DocumentScope::releaseMemory()
{
for (auto& descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot).styleScope().releaseMemory();
Scope::releaseMemory();
m_sharedShadowTreeResolvers.clear();
m_matchResultCache = { };
}
void DocumentScope::setPreferredStylesheetSetName(const WTF::String& name)
{
if (m_preferredStylesheetSetName == name)
return;
m_preferredStylesheetSetName = name;
didChangeActiveStyleSheetCandidates();
}
auto DocumentScope::mediaQueryViewportStateForDocument(const Document& document) -> MediaQueryViewportState
{
// These things affect evaluation of viewport dependent media queries.
return { document.view()->layoutSize(), document.frame()->pageZoomFactor(), document.printing() };
}
void DocumentScope::evaluateMediaQueriesForViewportChange()
{
auto viewportState = mediaQueryViewportStateForDocument(m_document);
if (m_viewportStateOnPreviousMediaQueryEvaluation && *m_viewportStateOnPreviousMediaQueryEvaluation == viewportState)
return;
// This doesn't need to be invalidated as any changes to the rules will compute their media queries to correct values.
m_viewportStateOnPreviousMediaQueryEvaluation = viewportState;
evaluateMediaQueries([] (Resolver& resolver) {
return resolver.evaluateDynamicMediaQueries();
});
}
void DocumentScope::evaluateMediaQueriesForAccessibilitySettingsChange()
{
evaluateMediaQueries([] (Resolver& resolver) {
return resolver.evaluateDynamicMediaQueries();
});
}
void DocumentScope::evaluateMediaQueriesForAppearanceChange()
{
evaluateMediaQueries([] (Resolver& resolver) {
return resolver.evaluateDynamicMediaQueries();
});
}
auto DocumentScope::collectResolverScopes() -> ResolverScopes
{
ResolverScopes resolverScopes;
if (RefPtr resolver = resolverIfExists())
resolverScopes.add(*resolver, Vector<WeakPtr<Scope>> { this });
for (Ref shadowRoot : m_document->inDocumentShadowRoots()) {
auto& scope = const_cast<ShadowRoot&>(shadowRoot.get()).styleScope();
if (RefPtr resolver = scope.resolverIfExists())
resolverScopes.add(*resolver, Vector<WeakPtr<Scope>> { }).iterator->value.append(&scope);
}
return resolverScopes;
}
template <typename TestFunction>
void DocumentScope::evaluateMediaQueries(TestFunction&& testFunction)
{
bool hadChanges = false;
auto resolverScopes = collectResolverScopes();
for (auto& [resolver, scopes] : resolverScopes) {
auto evaluationChanges = testFunction(resolver.get());
if (!evaluationChanges)
continue;
hadChanges = true;
for (auto& scope : scopes) {
switch (evaluationChanges->type) {
case DynamicMediaQueryEvaluationChanges::Type::InvalidateStyle: {
Invalidator invalidator(evaluationChanges->invalidationRuleSets);
invalidator.invalidateStyle(*scope);
break;
}
case DynamicMediaQueryEvaluationChanges::Type::ResetStyle:
scope->scheduleUpdate(UpdateType::ContentsOrInterpretation);
break;
}
}
}
if (hadChanges)
InspectorInstrumentation::mediaQueryResultChanged(m_document);
}
void DocumentScope::didChangeStyleSheetEnvironment()
{
RELEASE_ASSERT(!m_isUpdatingStyleResolver);
RELEASE_ASSERT(!m_document->isResolvingTreeStyle());
m_sharedShadowTreeResolvers.clear();
for (auto& descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot).styleScope().scheduleUpdate(UpdateType::ContentsOrInterpretation);
m_document->invalidateCachedCSSParserContext();
m_document->invalidateCachedInitialStyle();
scheduleUpdate(UpdateType::ContentsOrInterpretation);
}
void DocumentScope::didChangeExtensionStyleSheets()
{
// Extension stylesheets may mutate in the middle of a style update when resource loading triggers
// content extension processing. In this case we schedule an asyncronous full stylesheet update.
// FIXME: We should defer all resource loading after style resolution completes.
for (auto& descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot).styleScope().scheduleUpdate(UpdateType::FullForExtensionStyleSheets);
scheduleUpdate(UpdateType::FullForExtensionStyleSheets);
}
bool DocumentScope::invalidateForLayoutDependencies(LayoutDependencyUpdateContext& context)
{
auto didInvalidate = false;
didInvalidate |= invalidateForContainerDependencies(context);
didInvalidate |= invalidateForAnchorDependencies(context);
didInvalidate |= invalidateForPositionTryFallbacks(context);
return didInvalidate;
}
bool DocumentScope::invalidateForContainerDependencies(LayoutDependencyUpdateContext& context)
{
if (!m_document->renderView())
return false;
auto previousQueryContainerDimensions = WTF::move(m_queryContainerDimensionsOnLastUpdate);
m_queryContainerDimensionsOnLastUpdate.clear();
Vector<CheckedPtr<Element>> containersToInvalidate;
for (auto& containerRenderer : m_document->renderView()->containerQueryBoxes()) {
CheckedPtr containerElement = containerRenderer.element();
// Invalidation uses real elements, replace ::before/::after with its host.
if (auto* pseudoElement = dynamicDowncast<PseudoElement>(containerElement.get()))
containerElement = pseudoElement->hostElement();
if (!containerElement)
continue;
auto size = containerRenderer.logicalSize();
auto sizeChanged = [&](LayoutSize oldSize) {
auto& type = containerRenderer.style().containerType();
if (type.hasInlineSize())
return size.width() != oldSize.width();
if (type.hasSize())
return size != oldSize;
RELEASE_ASSERT_NOT_REACHED();
};
auto it = previousQueryContainerDimensions.find(*containerElement);
bool changed = it == previousQueryContainerDimensions.end() || sizeChanged(it->value);
// Protect against unstable layout by invalidating only once per container.
if (changed && context.invalidatedContainers.add(*containerElement).isNewEntry)
containersToInvalidate.append(containerElement);
m_queryContainerDimensionsOnLastUpdate.add(*containerElement, size);
}
for (auto& toInvalidate : containersToInvalidate)
toInvalidate->invalidateForQueryContainerSizeChange();
return !containersToInvalidate.isEmpty();
}
bool DocumentScope::invalidateForAnchorDependencies(LayoutDependencyUpdateContext& context)
{
if (!m_document->renderView())
return false;
auto previousAnchorPositions = WTF::move(m_anchorPositionsOnLastUpdate);
m_anchorPositionsOnLastUpdate.clear();
Vector<CheckedRef<Element>> anchoredElementsToInvalidate;
if (m_document->renderView()->anchors().isEmptyIgnoringNullReferences())
return false;
auto anchorMap = AnchorPositionEvaluator::makeAnchorPositionedForAnchorMap(m_anchorPositionedToAnchorMap);
auto makeAnchorPosition = [&](const RenderBoxModelObject& anchorRenderer) {
AnchorPosition result;
result.absoluteRect = anchorRenderer.absoluteBoundingBoxRect();
// Include containing block sizes as anchor function insets may be computed against any side and if they change
// we need to invalidate.
for (auto* containingBlock = anchorRenderer.containingBlock(); containingBlock; containingBlock = containingBlock->containingBlock()) {
if (containingBlock->canContainAbsolutelyPositionedObjects())
result.containingBlockSizes.append(containingBlock->contentBoxSize());
}
return result;
};
for (auto& anchorRenderer : m_document->renderView()->anchors()) {
auto anchorPosition = makeAnchorPosition(anchorRenderer);
m_anchorPositionsOnLastUpdate.add(anchorRenderer, anchorPosition);
auto it = previousAnchorPositions.find(anchorRenderer);
bool changed = it == previousAnchorPositions.end() || it->value != anchorPosition;
if (!changed)
continue;
auto anchoredElements = anchorMap.getOptional(anchorRenderer);
if (!anchoredElements)
continue;
for (auto& anchoredElement : *anchoredElements) {
if (!context.invalidatedAnchorPositioned.add(anchoredElement.get()).isNewEntry)
continue;
anchoredElementsToInvalidate.append(anchoredElement);
}
}
for (auto& toInvalidate : anchoredElementsToInvalidate) {
CheckedPtr renderer = toInvalidate->renderer();
if (renderer && AnchorPositionEvaluator::isLayoutTimeAnchorPositioned(renderer->style()))
renderer->setNeedsLayout();
toInvalidate->invalidateForAnchorRectChange();
}
return !anchoredElementsToInvalidate.isEmpty();
}
bool DocumentScope::invalidateForPositionTryFallbacks(LayoutDependencyUpdateContext& context)
{
if (!m_document->renderView())
return false;
bool invalidated = false;
for (auto& box : m_document->renderView()->positionTryBoxes()) {
if (!AnchorPositionEvaluator::overflowsInsetModifiedContainingBlock(box))
continue;
CheckedPtr element = box.element();
if (auto* pseudoElement = dynamicDowncast<PseudoElement>(element.get()))
element = pseudoElement->hostElement();
if (element) {
if (!context.invalidatedAnchorPositioned.add(*element).isNewEntry)
continue;
element->invalidateForAnchorRectChange();
invalidated = true;
}
}
return invalidated;
}
MatchResultCache& DocumentScope::matchResultCache()
{
if (!m_matchResultCache)
m_matchResultCache = makeUnique<MatchResultCache>();
return *m_matchResultCache;
}
EnvironmentVariables& DocumentScope::environmentVariables() const
{
if (!m_environmentVariables) {
auto& thisScope = const_cast<DocumentScope&>(*this);
thisScope.m_environmentVariables = makeUnique<EnvironmentVariables>(m_document.get());
}
return *m_environmentVariables;
}
void DocumentScope::updateAnchorPositioningStateAfterStyleResolution()
{
if (CheckedPtr renderView = m_document->renderView())
AnchorPositionEvaluator::updateScrollAdjustments(*renderView); // Is this necessary? Or will the combination of layout and scroll invalidation handle it sufficiently?
m_anchorPositionedToAnchorMap.removeIf([](auto& elementAndState) {
return elementAndState.value.anchors.isEmpty();
});
}
std::optional<size_t> DocumentScope::lastSuccessfulPositionOptionIndexFor(const Styleable& styleable)
{
return m_lastSuccessfulPositionOptionIndexes.getOptional(styleable);
}
void DocumentScope::setLastSuccessfulPositionOptionIndexMap(HashMap<WeakStyleable, size_t>&& map)
{
m_lastSuccessfulPositionOptionIndexes = WTF::move(map);
}
void DocumentScope::forgetLastSuccessfulPositionOptionIndex(const Styleable& styleable)
{
m_lastSuccessfulPositionOptionIndexes.remove(styleable);
}
}
}