blob: ec883658e94b5c3f1ed64ac98fe1d457c8c726c4 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll ([email protected])
* (C) 1999 Antti Koivisto ([email protected])
* (C) 2001 Dirk Mueller ([email protected])
* (C) 2006 Alexey Proskuryakov ([email protected])
* Copyright (C) 2004-2009, 2011-2012, 2015-2017 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 "StyleScope.h"
#include "CSSCounterStyleRegistry.h"
#include "CSSFontSelector.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 "ExtensionStyleSheets.h"
#include "HTMLHeadElement.h"
#include "HTMLIFrameElement.h"
#include "HTMLLinkElement.h"
#include "HTMLSlotElement.h"
#include "HTMLStyleElement.h"
#include "InspectorInstrumentation.h"
#include "Logging.h"
#include "MatchResultCache.h"
#include "ProcessingInstruction.h"
#include "RenderBoxInlines.h"
#include "RenderElementStyleInlines.h"
#include "RenderLayer.h"
#include "RenderObjectInlines.h"
#include "RenderView.h"
#include "RuleSet.h"
#include "SVGElementTypeHelpers.h"
#include "SVGStyleElement.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "StyleBuilder.h"
#include "StyleCustomPropertyRegistry.h"
#include "StyleInvalidator.h"
#include "StyleResolver.h"
#include "StyleSheetContents.h"
#include "StyleSheetList.h"
#include "UserContentController.h"
#include "UserContentURLPattern.h"
#include "UserStyleSheet.h"
#include <wtf/SetForScope.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
using namespace HTMLNames;
namespace Style {
WTF_MAKE_TZONE_ALLOCATED_IMPL(Scope);
Scope::Scope(Document& document)
: m_document(document)
, m_pendingUpdateTimer(*this, &Scope::pendingUpdateTimerFired)
, m_customPropertyRegistry(makeUniqueRef<CustomPropertyRegistry>(*this))
, m_counterStyleRegistry(makeUniqueRef<CSSCounterStyleRegistry>())
{
}
Scope::Scope(ShadowRoot& shadowRoot)
: m_document(shadowRoot.documentScope())
, m_shadowRoot(&shadowRoot)
, m_pendingUpdateTimer(*this, &Scope::pendingUpdateTimerFired)
, m_customPropertyRegistry(makeUniqueRef<CustomPropertyRegistry>(*this))
, m_counterStyleRegistry(makeUniqueRef<CSSCounterStyleRegistry>())
{
}
Scope::~Scope()
{
ASSERT(!hasPendingSheets());
weakPtrFactory().revokeAll();
}
Resolver& Scope::resolver()
{
if (!m_resolver) {
if (m_shadowRoot)
createOrFindSharedShadowTreeResolver();
else
createDocumentResolver();
if (m_resolver->ruleSets().features().usesHasPseudoClass())
m_usesHasPseudoClass = true;
}
return *m_resolver;
}
Ref<Resolver> Scope::protectedResolver()
{
return resolver();
}
void Scope::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());
m_document->protectedFontSelector()->buildStarted();
m_resolver->ruleSets().initializeUserStyle();
m_resolver->addCurrentSVGFontFaceRules();
m_resolver->appendAuthorStyleSheets(m_activeStyleSheets);
m_document->protectedFontSelector()->buildCompleted();
}
void Scope::createOrFindSharedShadowTreeResolver()
{
ASSERT(!m_resolver);
ASSERT(m_shadowRoot);
RELEASE_ASSERT(!m_isUpdatingStyleResolver);
auto key = makeResolverSharingKey();
auto result = documentScope().m_sharedShadowTreeResolvers.ensure(WTF::move(key), [&] {
SetForScope isUpdatingStyleResolver { m_isUpdatingStyleResolver, true };
m_resolver = Resolver::create(m_document, Resolver::ScopeType::ShadowTree);
m_resolver->ruleSets().setUsesSharedUserStyle(!isForUserAgentShadowTree());
m_resolver->appendAuthorStyleSheets(m_activeStyleSheets);
return Ref { *m_resolver };
});
if (!result.isNewEntry) {
m_resolver = result.iterator->value.ptr();
m_resolver->setSharedBetweenShadowTrees();
return;
}
static constexpr auto maximimumSharedResolverCount = 256;
if (documentScope().m_sharedShadowTreeResolvers.size() > maximimumSharedResolverCount)
documentScope().m_sharedShadowTreeResolvers.remove(documentScope().m_sharedShadowTreeResolvers.random());
}
void Scope::unshareShadowTreeResolverBeforeMutation()
{
ASSERT(m_shadowRoot);
documentScope().m_sharedShadowTreeResolvers.remove(makeResolverSharingKey());
}
auto Scope::makeResolverSharingKey() -> ResolverSharingKey
{
constexpr bool isNonEmptyHashTableValue = true;
return {
m_activeStyleSheets.map([&](auto& sheet) { return RefPtr { &sheet->contents() }; }),
isForUserAgentShadowTree(),
isNonEmptyHashTableValue
};
}
void Scope::clearResolver()
{
RELEASE_ASSERT(!m_isUpdatingStyleResolver);
RELEASE_ASSERT(!m_document->isResolvingTreeStyle());
m_resolver = nullptr;
customPropertyRegistry().clearRegisteredFromStylesheets();
counterStyleRegistry().clearAuthorCounterStyles();
}
void Scope::clearViewTransitionStyles()
{
clearResolver();
m_dynamicViewTransitionsStyle = nullptr;
}
void Scope::releaseMemory()
{
if (!m_shadowRoot) {
for (Ref descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot.get()).styleScope().releaseMemory();
}
#if ENABLE(CSS_SELECTOR_JIT)
for (auto& sheet : m_activeStyleSheets) {
sheet->contents().traverseRules([] (const StyleRuleBase& rule) {
if (auto* styleRule = dynamicDowncast<StyleRule>(rule))
styleRule->releaseCompiledSelectors();
return false;
});
}
#endif
clearResolver();
m_sharedShadowTreeResolvers.clear();
m_matchResultCache = { };
}
Scope& Scope::forNode(Node& node)
{
ASSERT(node.isConnected());
RefPtr shadowRoot = node.containingShadowRoot();
if (shadowRoot)
return shadowRoot->styleScope();
return node.document().styleScope();
}
const Scope& Scope::forNode(const Node& node)
{
return forNode(const_cast<Node&>(node));
}
Scope* Scope::forOrdinal(Element& element, ScopeOrdinal ordinal)
{
if (CheckedPtr pseudoElement = dynamicDowncast<PseudoElement>(element))
return forOrdinal(*pseudoElement->hostElement(), ordinal);
if (ordinal == ScopeOrdinal::Element)
return &forNode(element);
if (ordinal == ScopeOrdinal::Shadow) {
RefPtr shadowRoot = element.shadowRoot();
return shadowRoot ? &shadowRoot->styleScope() : nullptr;
}
if (ordinal <= ScopeOrdinal::ContainingHost) {
RefPtr host = hostForScopeOrdinal(element, ordinal);
return host ? &forNode(*host) : nullptr;
}
RefPtr slot = assignedSlotForScopeOrdinal(element, ordinal);
return slot ? &forNode(*slot) : nullptr;
}
const Scope* Scope::forOrdinal(const Element& element, ScopeOrdinal ordinal)
{
return forOrdinal(const_cast<Element&>(element), ordinal);
}
void Scope::setPreferredStylesheetSetName(const String& name)
{
if (m_preferredStylesheetSetName == name)
return;
m_preferredStylesheetSetName = name;
didChangeActiveStyleSheetCandidates();
}
void Scope::addPendingSheet(const Element& element)
{
ASSERT(!hasPendingSheet(element));
bool isInHead = ancestorsOfType<HTMLHeadElement>(element).first();
LOG_WITH_STREAM(StyleSheets, stream << "Scope " << this << " addPendingSheet() " << element << " isInHead " << isInHead);
if (isInHead)
m_elementsInHeadWithPendingSheets.add(element);
else
m_elementsInBodyWithPendingSheets.add(element);
}
// This method is called whenever a top-level stylesheet has finished loading.
void Scope::removePendingSheet(const Element& element)
{
ASSERT(hasPendingSheet(element));
if (!m_elementsInHeadWithPendingSheets.remove(element))
m_elementsInBodyWithPendingSheets.remove(element);
didRemovePendingStylesheet();
}
void Scope::addPendingSheet(const ProcessingInstruction& processingInstruction)
{
ASSERT(!m_processingInstructionsWithPendingSheets.contains(processingInstruction));
m_processingInstructionsWithPendingSheets.add(processingInstruction);
}
void Scope::removePendingSheet(const ProcessingInstruction& processingInstruction)
{
ASSERT(m_processingInstructionsWithPendingSheets.contains(processingInstruction));
m_processingInstructionsWithPendingSheets.remove(processingInstruction);
didRemovePendingStylesheet();
}
bool Scope::hasPendingSheets() const
{
return hasPendingSheetsBeforeBody() || !m_elementsInBodyWithPendingSheets.isEmptyIgnoringNullReferences();
}
bool Scope::hasPendingSheetsBeforeBody() const
{
return !m_elementsInHeadWithPendingSheets.isEmptyIgnoringNullReferences() || !m_processingInstructionsWithPendingSheets.isEmptyIgnoringNullReferences();
}
bool Scope::hasPendingSheetsInBody() const
{
return !m_elementsInBodyWithPendingSheets.isEmptyIgnoringNullReferences();
}
void Scope::didRemovePendingStylesheet()
{
if (hasPendingSheets())
return;
didChangeActiveStyleSheetCandidates();
if (!m_shadowRoot)
m_document->didRemoveAllPendingStylesheet();
}
bool Scope::hasPendingSheet(const Element& element) const
{
return m_elementsInHeadWithPendingSheets.contains(element) || hasPendingSheetInBody(element);
}
bool Scope::hasPendingSheetInBody(const Element& element) const
{
return m_elementsInBodyWithPendingSheets.contains(element);
}
bool Scope::hasPendingSheet(const ProcessingInstruction& processingInstruction) const
{
return m_processingInstructionsWithPendingSheets.contains(processingInstruction);
}
void Scope::addStyleSheetCandidateNode(Node& node, bool createdByParser)
{
if (!node.isConnected())
return;
// Until the <body> exists, we have no choice but to compare document positions,
// since styles outside of the body and head continue to be shunted into the head
// (and thus can shift to end up before dynamically added DOM content that is also
// outside the body).
if ((createdByParser && m_document->bodyOrFrameset()) || m_styleSheetCandidateNodes.isEmptyIgnoringNullReferences()) {
m_styleSheetCandidateNodes.add(node);
return;
}
// Determine an appropriate insertion point.
auto begin = m_styleSheetCandidateNodes.begin();
auto end = m_styleSheetCandidateNodes.end();
auto it = end;
RefPtr<Node> followingNode;
do {
--it;
Ref<Node> n = *it;
unsigned short position = n->compareDocumentPosition(node);
if (position == Node::DOCUMENT_POSITION_FOLLOWING) {
if (followingNode)
m_styleSheetCandidateNodes.insertBefore(*followingNode, node);
else
m_styleSheetCandidateNodes.appendOrMoveToLast(node);
return;
}
followingNode = WTF::move(n);
} while (it != begin);
LOG_WITH_STREAM(StyleSheets, stream << "Scope " << this << " addStyleSheetCandidateNode() " << node);
m_styleSheetCandidateNodes.insertBefore(*followingNode, node);
}
void Scope::removeStyleSheetCandidateNode(Node& node)
{
if (m_styleSheetCandidateNodes.remove(node))
didChangeActiveStyleSheetCandidates();
}
#if ENABLE(XSLT)
// FIXME: <https://webkit.org/b/178830> Remove XSLT relaed code from Style::Scope.
Vector<Ref<ProcessingInstruction>> Scope::collectXSLTransforms()
{
Vector<Ref<ProcessingInstruction>> processingInstructions;
for (Ref node : m_styleSheetCandidateNodes) {
if (RefPtr processingInstruction = dynamicDowncast<ProcessingInstruction>(node); processingInstruction && processingInstruction->isXSL())
processingInstructions.append(*processingInstruction);
}
return processingInstructions;
}
#endif
auto Scope::collectActiveStyleSheets() -> ActiveStyleSheetCollection
{
if (!m_document->settings().authorAndUserStylesEnabled())
return { };
LOG_WITH_STREAM(StyleSheets, stream << "Scope " << this << " collectActiveStyleSheets()");
Vector<Ref<StyleSheet>> sheets;
Vector<Ref<StyleSheet>> styleSheetsForStyleSheetsList;
for (Ref node : m_styleSheetCandidateNodes) {
RefPtr<StyleSheet> sheet;
if (RefPtr processingInstruction = dynamicDowncast<ProcessingInstruction>(node)) {
if (!processingInstruction->isCSS())
continue;
// We don't support linking to embedded CSS stylesheets, see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
sheet = processingInstruction->sheet();
if (sheet)
styleSheetsForStyleSheetsList.append(*sheet);
LOG_WITH_STREAM(StyleSheets, stream << " adding sheet " << sheet << " from ProcessingInstruction node " << node);
} else if (is<HTMLLinkElement>(node) || is<HTMLStyleElement>(node) || is<SVGStyleElement>(node)) {
Ref element = uncheckedDowncast<Element>(node);
AtomString title = element->isInShadowTree() ? nullAtom() : element->attributeWithoutSynchronization(titleAttr);
bool enabledViaScript = false;
if (RefPtr linkElement = dynamicDowncast<HTMLLinkElement>(element.get())) {
// <LINK> element
if (linkElement->isDisabled())
continue;
enabledViaScript = linkElement->isEnabledViaScript();
if (linkElement->styleSheetIsLoading()) {
// it is loading but we should still decide which style sheet set to use
if (!enabledViaScript && !title.isEmpty() && m_preferredStylesheetSetName.isEmpty()) {
if (!linkElement->attributeWithoutSynchronization(relAttr).contains("alternate"_s))
m_preferredStylesheetSetName = title;
}
continue;
}
if (!linkElement->sheet())
title = nullAtom();
}
// Get the current preferred styleset. This is the
// set of sheets that will be enabled.
if (auto* svgStyleElement = dynamicDowncast<SVGStyleElement>(element.get()))
sheet = svgStyleElement->sheet();
else if (auto* htmlLinkElement = dynamicDowncast<HTMLLinkElement>(element.get()))
sheet = htmlLinkElement->sheet();
else
sheet = downcast<HTMLStyleElement>(element.get()).sheet();
if (sheet)
styleSheetsForStyleSheetsList.append(*sheet);
// Check to see if this sheet belongs to a styleset
// (thus making it PREFERRED or ALTERNATE rather than
// PERSISTENT).
auto& rel = element->attributeWithoutSynchronization(relAttr);
if (!enabledViaScript && sheet && !title.isEmpty()) {
// Yes, we have a title.
if (m_preferredStylesheetSetName.isEmpty()) {
// No preferred set has been established. If
// we are NOT an alternate sheet, then establish
// us as the preferred set. Otherwise, just ignore
// this sheet.
if (is<HTMLStyleElement>(element.get()) || !rel.contains("alternate"_s))
m_preferredStylesheetSetName = title;
}
if (title != m_preferredStylesheetSetName)
sheet = nullptr;
}
if (rel.contains("alternate"_s) && title.isEmpty())
sheet = nullptr;
if (sheet)
LOG_WITH_STREAM(StyleSheets, stream << " adding sheet " << sheet << " from " << node);
}
if (sheet)
sheets.append(sheet.releaseNonNull());
}
auto canActivateAdoptedStyleSheet = [&](auto& sheet) {
if (sheet.disabled())
return false;
return sheet.title().isEmpty() || sheet.title() == m_preferredStylesheetSetName;
};
for (auto& adoptedStyleSheet : treeScope().adoptedStyleSheets()) {
if (!canActivateAdoptedStyleSheet(adoptedStyleSheet.get()))
continue;
styleSheetsForStyleSheetsList.append(adoptedStyleSheet.get());
sheets.append(adoptedStyleSheet.get());
}
return { WTF::move(sheets), WTF::move(styleSheetsForStyleSheetsList) };
}
Scope::StyleSheetChange Scope::analyzeStyleSheetChange(const Vector<Ref<CSSStyleSheet>>& newStylesheets)
{
unsigned newStylesheetCount = newStylesheets.size();
RefPtr resolver = resolverIfExists();
if (!resolver)
return { ResolverUpdateType::Reconstruct };
if (resolver->isSharedBetweenShadowTrees())
return { ResolverUpdateType::Reconstruct };
// Find out which stylesheets are new.
unsigned oldStylesheetCount = m_activeStyleSheets.size();
if (newStylesheetCount < oldStylesheetCount)
return { ResolverUpdateType::Reconstruct };
Vector<Ref<StyleSheetContents>> addedSheets;
unsigned newIndex = 0;
for (unsigned oldIndex = 0; oldIndex < oldStylesheetCount; ++oldIndex) {
if (newIndex >= newStylesheetCount)
return { ResolverUpdateType::Reconstruct };
while (m_activeStyleSheets[oldIndex] != newStylesheets[newIndex]) {
addedSheets.append(newStylesheets[newIndex]->contents());
++newIndex;
if (newIndex == newStylesheetCount)
return { ResolverUpdateType::Reconstruct };
}
++newIndex;
}
bool hasInsertions = !addedSheets.isEmpty();
while (newIndex < newStylesheetCount) {
addedSheets.append(newStylesheets[newIndex]->contents());
++newIndex;
}
// If all new sheets were added at the end of the list we can just add them to existing Resolver.
// If there were insertions we need to re-add all the stylesheets so rules are ordered correctly.
return { hasInsertions ? ResolverUpdateType::Reset : ResolverUpdateType::Additive, WTF::move(addedSheets) };
}
static void filterEnabledNonemptyCSSStyleSheets(Vector<Ref<CSSStyleSheet>>& result, const Vector<Ref<StyleSheet>>& sheets)
{
for (auto& sheet : sheets) {
RefPtr styleSheet = dynamicDowncast<CSSStyleSheet>(sheet.get());
if (!styleSheet)
continue;
if (styleSheet->isLoading())
continue;
if (styleSheet->disabled())
continue;
if (!styleSheet->length())
continue;
result.append(*styleSheet);
}
}
void Scope::updateActiveStyleSheets(UpdateType updateType)
{
ASSERT(!m_pendingUpdate);
if (!m_document->hasLivingRenderTree())
return;
if (m_document->inStyleRecalc() || m_document->inRenderTreeUpdate()) {
// Protect against deleting style resolver in the middle of a style resolution.
// Crash stacks indicate we can get here when a resource load fails synchronously (for example due to content blocking).
// FIXME: These kind of cases should be eliminated and this path replaced by an assert.
m_pendingUpdate = UpdateType::ContentsOrInterpretation;
m_document->scheduleFullStyleRebuild();
return;
}
auto collection = collectActiveStyleSheets();
Vector<Ref<CSSStyleSheet>> activeCSSStyleSheets;
if (!isForUserAgentShadowTree()) {
activeCSSStyleSheets.appendVector(m_document->extensionStyleSheets().injectedAuthorStyleSheets());
activeCSSStyleSheets.appendVector(m_document->extensionStyleSheets().authorStyleSheetsForTesting());
}
filterEnabledNonemptyCSSStyleSheets(activeCSSStyleSheets, collection.activeStyleSheets);
LOG_WITH_STREAM(StyleSheets, stream << "Scope::updateActiveStyleSheets for document " << m_document << " sheets " << activeCSSStyleSheets);
auto styleSheetChange = StyleSheetChange { ResolverUpdateType::Reconstruct };
if (updateType == UpdateType::ActiveSet)
styleSheetChange = analyzeStyleSheetChange(activeCSSStyleSheets);
updateResolver(activeCSSStyleSheets, styleSheetChange.resolverUpdateType);
m_weakCopyOfActiveStyleSheetListForFastLookup.clear();
m_activeStyleSheets.swap(activeCSSStyleSheets);
m_styleSheetsForStyleSheetList.swap(collection.styleSheetsForStyleSheetList);
InspectorInstrumentation::activeStyleSheetsUpdated(m_document);
for (const auto& sheet : m_activeStyleSheets) {
if (sheet->contents().usesStyleBasedEditability())
m_usesStyleBasedEditability = true;
}
if (m_resolver && m_resolver->ruleSets().features().usesHasPseudoClass())
m_usesHasPseudoClass = true;
invalidateStyleAfterStyleSheetChange(styleSheetChange);
}
void Scope::invalidateStyleAfterStyleSheetChange(const StyleSheetChange& styleSheetChange)
{
if (m_shadowRoot && !m_shadowRoot->isConnected())
return;
// If we are already parsing the body and so may have significant amount of elements, put some effort into trying to avoid style recalcs.
bool invalidateAll = !m_document->bodyOrFrameset() || m_document->hasNodesWithMissingStyle();
if (styleSheetChange.resolverUpdateType == ResolverUpdateType::Reconstruct || invalidateAll) {
Invalidator::invalidateAllStyle(*this);
return;
}
Invalidator invalidator(styleSheetChange.addedSheets, m_resolver->mediaQueryEvaluator());
invalidator.invalidateStyle(*this);
}
void Scope::updateResolver(std::span<const Ref<CSSStyleSheet>> activeStyleSheets, ResolverUpdateType updateType)
{
if (updateType == ResolverUpdateType::Reconstruct) {
clearResolver();
return;
}
if (m_shadowRoot)
unshareShadowTreeResolverBeforeMutation();
SetForScope isUpdatingStyleResolver { m_isUpdatingStyleResolver, true };
if (updateType == ResolverUpdateType::Reset) {
customPropertyRegistry().clearRegisteredFromStylesheets();
counterStyleRegistry().clearAuthorCounterStyles();
m_resolver->ruleSets().resetAuthorStyle();
m_resolver->appendAuthorStyleSheets(activeStyleSheets);
return;
}
ASSERT(updateType == ResolverUpdateType::Additive);
ASSERT(activeStyleSheets.size() >= m_activeStyleSheets.size());
auto firstNewIndex = m_activeStyleSheets.size();
m_resolver->appendAuthorStyleSheets(activeStyleSheets.subspan(firstNewIndex));
}
const Vector<Ref<CSSStyleSheet>> Scope::activeStyleSheetsForInspector()
{
Vector<Ref<CSSStyleSheet>> result;
if (CheckedPtr extensionStyleSheets = m_document->extensionStyleSheetsIfExists()) {
if (RefPtr pageUserSheet = extensionStyleSheets->pageUserSheet())
result.append(*pageUserSheet);
result.appendVector(extensionStyleSheets->documentUserStyleSheets());
result.appendVector(extensionStyleSheets->injectedUserStyleSheets());
result.appendVector(extensionStyleSheets->injectedAuthorStyleSheets());
result.appendVector(extensionStyleSheets->authorStyleSheetsForTesting());
}
for (auto& styleSheet : m_styleSheetsForStyleSheetList) {
RefPtr sheet = dynamicDowncast<CSSStyleSheet>(styleSheet.get());
if (!sheet)
continue;
if (sheet->disabled())
continue;
result.append(*sheet);
}
return result;
}
bool Scope::activeStyleSheetsContains(const CSSStyleSheet& sheet) const
{
if (m_activeStyleSheets.isEmpty())
return false;
if (m_weakCopyOfActiveStyleSheetListForFastLookup.isEmpty()) {
for (auto& activeStyleSheet : m_activeStyleSheets)
m_weakCopyOfActiveStyleSheetListForFastLookup.add(activeStyleSheet.get());
}
return m_weakCopyOfActiveStyleSheetListForFastLookup.contains(sheet);
}
void Scope::flushPendingSelfUpdate()
{
ASSERT(m_pendingUpdate);
auto updateType = *m_pendingUpdate;
clearPendingUpdate();
updateActiveStyleSheets(updateType);
}
void Scope::flushPendingDescendantUpdates()
{
ASSERT(m_hasDescendantWithPendingUpdate);
ASSERT(!m_shadowRoot);
for (Ref descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot.get()).styleScope().flushPendingUpdate();
m_hasDescendantWithPendingUpdate = false;
}
void Scope::clearPendingUpdate()
{
m_pendingUpdateTimer.stop();
m_pendingUpdate = { };
}
TreeScope& Scope::treeScope()
{
if (m_shadowRoot)
return *m_shadowRoot;
return m_document;
}
void Scope::scheduleUpdate(UpdateType update)
{
if (update == UpdateType::ContentsOrInterpretation) {
// :host and ::slotted rules might go away.
if (m_shadowRoot) {
Invalidator::invalidateHostAndSlottedStyleIfNeeded(*m_shadowRoot);
unshareShadowTreeResolverBeforeMutation();
}
clearResolver();
m_matchResultCache = { };
}
if (!m_pendingUpdate || *m_pendingUpdate < update) {
m_pendingUpdate = update;
if (m_shadowRoot)
documentScope().m_hasDescendantWithPendingUpdate = true;
}
if (m_pendingUpdateTimer.isActive())
return;
m_pendingUpdateTimer.startOneShot(0_s);
}
auto Scope::mediaQueryViewportStateForDocument(const Document& document) -> MediaQueryViewportState
{
// These things affect evaluation of viewport dependent media queries.
return { document.view()->layoutSize(), document.frame()->pageZoomFactor(), document.printing() };
}
void Scope::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 Scope::evaluateMediaQueriesForAccessibilitySettingsChange()
{
evaluateMediaQueries([] (Resolver& resolver) {
return resolver.evaluateDynamicMediaQueries();
});
}
void Scope::evaluateMediaQueriesForAppearanceChange()
{
evaluateMediaQueries([] (Resolver& resolver) {
return resolver.evaluateDynamicMediaQueries();
});
}
auto Scope::collectResolverScopes() -> ResolverScopes
{
ASSERT(!m_shadowRoot);
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 Scope::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 Scope::didChangeActiveStyleSheetCandidates()
{
scheduleUpdate(UpdateType::ActiveSet);
}
void Scope::didChangeStyleSheetContents()
{
scheduleUpdate(UpdateType::ContentsOrInterpretation);
}
void Scope::didChangeStyleSheetEnvironment()
{
RELEASE_ASSERT(!m_isUpdatingStyleResolver);
RELEASE_ASSERT(!m_document->isResolvingTreeStyle());
if (!m_shadowRoot) {
m_sharedShadowTreeResolvers.clear();
for (Ref descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot.get()).styleScope().scheduleUpdate(UpdateType::ContentsOrInterpretation);
m_document->invalidateCachedCSSParserContext();
}
scheduleUpdate(UpdateType::ContentsOrInterpretation);
}
void Scope::didChangeExtensionStyleSheets()
{
ASSERT(!m_shadowRoot);
// 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 (Ref descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot.get()).styleScope().scheduleUpdate(UpdateType::FullForExtensionStyleSheets);
scheduleUpdate(UpdateType::FullForExtensionStyleSheets);
}
void Scope::didChangeViewportSize()
{
Ref<ContainerNode> rootNode = m_document;
if (m_shadowRoot)
rootNode = *m_shadowRoot;
else {
if (!m_document->hasStyleWithViewportUnits())
return;
for (Ref descendantShadowRoot : m_document->inDocumentShadowRoots()) {
if (descendantShadowRoot->mode() == ShadowRootMode::UserAgent)
continue;
const_cast<ShadowRoot&>(descendantShadowRoot.get()).styleScope().didChangeViewportSize();
}
}
RefPtr resolver = resolverIfExists();
if (!resolver)
return;
resolver->clearCachedDeclarationsAffectedByViewportUnits();
if (customPropertyRegistry().invalidatePropertiesWithViewportUnits(m_document)) {
if (!m_shadowRoot) {
if (RefPtr element = m_document->documentElement())
element->invalidateStyleForSubtree();
}
return;
}
// FIXME: Ideally, we should save the list of elements that have viewport units and only iterate over those.
for (RefPtr element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::nextIncludingPseudo(*element)) {
auto* renderer = element->renderer();
if (renderer && renderer->style().usesViewportUnits())
element->invalidateStyle();
}
}
void Scope::invalidateMatchedDeclarationsCache()
{
if (!m_shadowRoot) {
for (Ref descendantShadowRoot : m_document->inDocumentShadowRoots())
const_cast<ShadowRoot&>(descendantShadowRoot.get()).styleScope().invalidateMatchedDeclarationsCache();
}
if (RefPtr resolver = resolverIfExists())
resolver->invalidateMatchedDeclarationsCache();
}
void Scope::pendingUpdateTimerFired()
{
RefPtr protectedShadowRoot { m_shadowRoot };
Ref protectedDocument { m_document.get() };
flushPendingUpdate();
}
const Vector<Ref<StyleSheet>>& Scope::styleSheetsForStyleSheetList()
{
// FIXME: StyleSheetList content should be updated separately from style resolver updates.
flushPendingUpdate();
return m_styleSheetsForStyleSheetList;
}
Scope& Scope::documentScope()
{
return m_document->styleScope();
}
bool Scope::isForUserAgentShadowTree() const
{
return m_shadowRoot && m_shadowRoot->mode() == ShadowRootMode::UserAgent;
}
bool Scope::invalidateForLayoutDependencies(LayoutDependencyUpdateContext& context)
{
auto didInvalidate = false;
didInvalidate |= invalidateForContainerDependencies(context);
didInvalidate |= invalidateForAnchorDependencies(context);
didInvalidate |= invalidateForPositionTryFallbacks(context);
return didInvalidate;
}
bool Scope::invalidateForContainerDependencies(LayoutDependencyUpdateContext& context)
{
ASSERT(!m_shadowRoot);
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) {
switch (containerRenderer.style().containerType()) {
case ContainerType::InlineSize:
return size.width() != oldSize.width();
case ContainerType::Size:
return size != oldSize;
case ContainerType::Normal:
RELEASE_ASSERT_NOT_REACHED();
}
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 Scope::invalidateForAnchorDependencies(LayoutDependencyUpdateContext& context)
{
ASSERT(!m_shadowRoot);
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.absoluteBoundingBoxRectIgnoringTransforms();
// 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 Scope::invalidateForPositionTryFallbacks(LayoutDependencyUpdateContext& context)
{
ASSERT(!m_shadowRoot);
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& Scope::matchResultCache()
{
ASSERT(!m_shadowRoot);
if (!m_matchResultCache)
m_matchResultCache = makeUnique<MatchResultCache>();
return *m_matchResultCache;
}
RefPtr<HTMLSlotElement> assignedSlotForScopeOrdinal(const Element& element, ScopeOrdinal scopeOrdinal)
{
ASSERT(scopeOrdinal >= ScopeOrdinal::FirstSlot);
RefPtr slot = element.assignedSlot();
for (auto scopeDepth = ScopeOrdinal::FirstSlot; slot && scopeDepth != scopeOrdinal; ++scopeDepth)
slot = slot->assignedSlot();
return slot;
}
RefPtr<Element> hostForScopeOrdinal(const Element& element, ScopeOrdinal scopeOrdinal)
{
ASSERT(scopeOrdinal <= ScopeOrdinal::ContainingHost);
RefPtr host = element.shadowHost();
for (auto scopeDepth = ScopeOrdinal::ContainingHost; host && scopeDepth != scopeOrdinal; --scopeDepth)
host = host->shadowHost();
return host;
}
CheckedPtr<const Scope> Scope::hostScope() const
{
if (!m_shadowRoot || !m_shadowRoot->host())
return nullptr;
return &forNode(*m_shadowRoot->host());
}
void Scope::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> Scope::lastSuccessfulPositionOptionIndexFor(const Styleable& styleable)
{
AnchorPositionedKey key { styleable.element, styleable.pseudoElementIdentifier };
return m_lastSuccessfulPositionOptionIndexes.getOptional(key);
}
void Scope::setLastSuccessfulPositionOptionIndexMap(HashMap<AnchorPositionedKey, size_t>&& map)
{
m_lastSuccessfulPositionOptionIndexes = WTF::move(map);
}
void Scope::forgetLastSuccessfulPositionOptionIndex(const Styleable& styleable)
{
AnchorPositionedKey key { styleable.element, styleable.pseudoElementIdentifier };
m_lastSuccessfulPositionOptionIndexes.remove(key);
}
}
}