/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Peter Kelly (pmk@post.com) * (C) 2001 Dirk Mueller (mueller@kde.org) * (C) 2007 David Smith (catfish.man@gmail.com) * Copyright (C) 2004-2010, 2012-2016 Apple Inc. All rights reserved. * (C) 2007 Eric Seidel (eric@webkit.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "StyleTreeResolver.h" #include "CSSFontSelector.h" #include "ComposedTreeAncestorIterator.h" #include "ComposedTreeIterator.h" #include "DocumentTimeline.h" #include "ElementIterator.h" #include "Frame.h" #include "HTMLBodyElement.h" #include "HTMLInputElement.h" #include "HTMLMeterElement.h" #include "HTMLNames.h" #include "HTMLProgressElement.h" #include "HTMLSlotElement.h" #include "LoaderStrategy.h" #include "NodeRenderStyle.h" #include "Page.h" #include "PlatformStrategies.h" #include "Quirks.h" #include "RenderElement.h" #include "RenderStyle.h" #include "RenderView.h" #include "RuntimeEnabledFeatures.h" #include "Settings.h" #include "ShadowRoot.h" #include "StyleAdjuster.h" #include "StyleFontSizeFunctions.h" #include "StyleResolver.h" #include "StyleScope.h" #include "Text.h" #include "WebAnimationTypes.h" #include "WebAnimationUtilities.h" namespace WebCore { namespace Style { DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(TreeResolverScope); TreeResolver::TreeResolver(Document& document, std::unique_ptr update) : m_document(document) , m_update(WTFMove(update)) { } TreeResolver::~TreeResolver() = default; TreeResolver::Scope::Scope(Document& document) : resolver(document.styleScope().resolver()) , sharingResolver(document, resolver->ruleSets(), selectorFilter) { document.setIsResolvingTreeStyle(true); // Ensure all shadow tree resolvers exist so their construction doesn't depend on traversal. for (auto* shadowRoot : document.inDocumentShadowRoots()) shadowRoot->styleScope().resolver(); } TreeResolver::Scope::Scope(ShadowRoot& shadowRoot, Scope& enclosingScope) : resolver(shadowRoot.styleScope().resolver()) , sharingResolver(shadowRoot.documentScope(), resolver->ruleSets(), selectorFilter) , shadowRoot(&shadowRoot) , enclosingScope(&enclosingScope) { resolver->setOverrideDocumentElementStyle(enclosingScope.resolver->overrideDocumentElementStyle()); } TreeResolver::Scope::~Scope() { if (!shadowRoot) resolver->document().setIsResolvingTreeStyle(false); resolver->setOverrideDocumentElementStyle(nullptr); } TreeResolver::Parent::Parent(Document& document) : element(nullptr) , style(*document.renderStyle()) { } TreeResolver::Parent::Parent(Element& element, const RenderStyle& style, Change change, DescendantsToResolve descendantsToResolve) : element(&element) , style(style) , change(change) , descendantsToResolve(descendantsToResolve) { } void TreeResolver::pushScope(ShadowRoot& shadowRoot) { m_scopeStack.append(adoptRef(*new Scope(shadowRoot, scope()))); } void TreeResolver::pushEnclosingScope() { ASSERT(scope().enclosingScope); m_scopeStack.append(*scope().enclosingScope); } void TreeResolver::popScope() { return m_scopeStack.removeLast(); } std::unique_ptr TreeResolver::styleForStyleable(const Styleable& styleable, const RenderStyle& inheritedStyle) { auto& element = styleable.element; if (element.hasCustomStyleResolveCallbacks()) { RenderStyle* shadowHostStyle = scope().shadowRoot ? m_update->elementStyle(*scope().shadowRoot->host()) : nullptr; if (auto customStyle = element.resolveCustomStyle(inheritedStyle, shadowHostStyle)) { if (customStyle->relations) commitRelations(WTFMove(customStyle->relations), *m_update); return WTFMove(customStyle->renderStyle); } } if (auto style = scope().sharingResolver.resolve(styleable, *m_update)) return style; auto elementStyle = scope().resolver->styleForElement(element, &inheritedStyle, parentBoxStyle(), RuleMatchingBehavior::MatchAllRules, &scope().selectorFilter); if (elementStyle.relations) commitRelations(WTFMove(elementStyle.relations), *m_update); return WTFMove(elementStyle.renderStyle); } static void resetStyleForNonRenderedDescendants(Element& current) { for (auto& child : childrenOfType(current)) { if (child.needsStyleRecalc()) { child.resetComputedStyle(); child.resetStyleRelations(); child.setHasValidStyle(); } if (child.childNeedsStyleRecalc()) resetStyleForNonRenderedDescendants(child); } current.clearChildNeedsStyleRecalc(); } static bool affectsRenderedSubtree(Element& element, const RenderStyle& newStyle) { if (newStyle.display() != DisplayType::None) return true; if (element.renderOrDisplayContentsStyle()) return true; if (element.rendererIsNeeded(newStyle)) return true; return false; } static DescendantsToResolve computeDescendantsToResolve(Change change, Validity validity, DescendantsToResolve parentDescendantsToResolve) { if (parentDescendantsToResolve == DescendantsToResolve::All) return DescendantsToResolve::All; if (validity >= Validity::SubtreeInvalid) return DescendantsToResolve::All; switch (change) { case Change::None: return DescendantsToResolve::None; case Change::NonInherited: return DescendantsToResolve::ChildrenWithExplicitInherit; case Change::Inherited: return DescendantsToResolve::Children; case Change::Renderer: return DescendantsToResolve::All; }; ASSERT_NOT_REACHED(); return DescendantsToResolve::None; }; ElementUpdates TreeResolver::resolveElement(Element& element) { if (m_didSeePendingStylesheet && !element.renderer() && !m_document.isIgnoringPendingStylesheets()) { m_document.setHasNodesWithMissingStyle(); return { }; } if (!element.rendererIsEverNeeded() && !element.hasDisplayContents()) return { }; Styleable styleable { element, PseudoId::None }; auto newStyle = styleForStyleable(styleable, parent().style); if (!affectsRenderedSubtree(element, *newStyle)) return { }; auto* existingStyle = element.renderOrDisplayContentsStyle(); if (m_didSeePendingStylesheet && (!existingStyle || existingStyle->isNotFinal())) { newStyle->setIsNotFinal(); m_document.setHasNodesWithNonFinalStyle(); } auto update = createAnimatedElementUpdate(WTFMove(newStyle), styleable, parent().change, parent().style, parentBoxStyle()); auto descendantsToResolve = computeDescendantsToResolve(update.change, element.styleValidity(), parent().descendantsToResolve); if (&element == m_document.documentElement()) { m_documentElementStyle = RenderStyle::clonePtr(*update.style); scope().resolver->setOverrideDocumentElementStyle(m_documentElementStyle.get()); if (!existingStyle || existingStyle->computedFontPixelSize() != update.style->computedFontPixelSize()) { // "rem" units are relative to the document element's font size so we need to recompute everything. scope().resolver->invalidateMatchedDeclarationsCache(); descendantsToResolve = DescendantsToResolve::All; } } // This is needed for resolving color:-webkit-text for subsequent elements. // FIXME: We shouldn't mutate document when resolving style. if (&element == m_document.body()) m_document.setTextColor(update.style->visitedDependentColor(CSSPropertyColor)); // FIXME: These elements should not change renderer based on appearance property. if (element.hasTagName(HTMLNames::meterTag) || is(element) || (is(element) && downcast(element).isSearchField())) { if (existingStyle && update.style->appearance() != existingStyle->appearance()) { update.change = Change::Renderer; descendantsToResolve = DescendantsToResolve::All; } } PseudoIdToElementUpdateMap pseudoUpdates; if (auto markerElementUpdate = resolvePseudoStyle(element, update, PseudoId::Marker)) pseudoUpdates.set(PseudoId::Marker, WTFMove(*markerElementUpdate)); if (auto beforeElementUpdate = resolvePseudoStyle(element, update, PseudoId::Before)) pseudoUpdates.set(PseudoId::Before, WTFMove(*beforeElementUpdate)); if (auto afterElementUpdate = resolvePseudoStyle(element, update, PseudoId::After)) pseudoUpdates.set(PseudoId::After, WTFMove(*afterElementUpdate)); #if ENABLE(TOUCH_ACTION_REGIONS) // FIXME: Track this exactly. if (update.style->touchActions() != TouchAction::Auto && !m_document.quirks().shouldDisablePointerEventsQuirk()) m_document.setMayHaveElementsWithNonAutoTouchAction(); #endif #if ENABLE(EDITABLE_REGION) if (update.style->userModify() != UserModify::ReadOnly) m_document.setMayHaveEditableElements(); #endif return { WTFMove(update), descendantsToResolve, WTFMove(pseudoUpdates) }; } std::optional TreeResolver::resolvePseudoStyle(Element& element, const ElementUpdate& elementUpdate, PseudoId pseudoId) { ASSERT(pseudoId != PseudoId::Backdrop, "This method does not handle ::backdrop currently"); if (pseudoId == PseudoId::Marker && elementUpdate.style->display() != DisplayType::ListItem) return { }; if (elementUpdate.style->display() == DisplayType::None) return { }; if (!elementUpdate.style->hasPseudoStyle(pseudoId)) return { }; auto& parentStyle = *elementUpdate.style; auto* parentBoxStyle = parentBoxStyleForPseudo(elementUpdate); auto pseudoStyle = scope().resolver->pseudoStyleForElement(element, { pseudoId }, parentStyle, parentBoxStyle, &scope().selectorFilter); if (!pseudoStyle) return { }; bool hasAnimations = pseudoStyle->hasAnimationsOrTransitions() || element.hasKeyframeEffects(pseudoId); if (!pseudoElementRendererIsNeeded(pseudoStyle.get()) && !hasAnimations) return { }; return createAnimatedElementUpdate(WTFMove(pseudoStyle), { element, pseudoId }, elementUpdate.change, parentStyle, parentBoxStyle); } const RenderStyle* TreeResolver::parentBoxStyle() const { // 'display: contents' doesn't generate boxes. for (auto i = m_parentStack.size(); i--;) { auto& parent = m_parentStack[i]; if (parent.style.display() == DisplayType::None) return nullptr; if (parent.style.display() != DisplayType::Contents) return &parent.style; } ASSERT_NOT_REACHED(); return nullptr; } const RenderStyle* TreeResolver::parentBoxStyleForPseudo(const ElementUpdate& elementUpdate) const { switch (elementUpdate.style->display()) { case DisplayType::None: return nullptr; case DisplayType::Contents: return parentBoxStyle(); default: return elementUpdate.style.get(); } } ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr newStyle, const Styleable& styleable, Change parentChange, const RenderStyle& parentStyle, const RenderStyle* parentBoxStyle) { auto& element = styleable.element; auto& document = element.document(); auto* oldStyle = element.renderOrDisplayContentsStyle(styleable.pseudoId); OptionSet animationImpact; // First, we need to make sure that any new CSS animation occuring on this element has a matching WebAnimation // on the document timeline. Note that we get timeline() on the Document here because we need a timeline created // in case no Web Animations have been created through the JS API. if (document.backForwardCacheState() == Document::NotInBackForwardCache && !document.renderView()->printing()) { if (oldStyle && (oldStyle->hasTransitions() || newStyle->hasTransitions())) styleable.updateCSSTransitions(*oldStyle, *newStyle); // The order in which CSS Transitions and CSS Animations are updated matters since CSS Transitions define the after-change style // to use CSS Animations as defined in the previous style change event. As such, we update CSS Animations after CSS Transitions // such that when CSS Transitions are updated the CSS Animations data is the same as during the previous style change event. if ((oldStyle && oldStyle->hasAnimations()) || newStyle->hasAnimations()) styleable.updateCSSAnimations(oldStyle, *newStyle, &parentStyle); } // Now we can update all Web animations, which will include CSS Animations as well // as animations created via the JS API. if (styleable.hasKeyframeEffects()) { auto previousLastStyleChangeEventStyle = styleable.lastStyleChangeEventStyle() ? RenderStyle::clonePtr(*styleable.lastStyleChangeEventStyle()) : RenderStyle::createPtr(); // Record the style prior to applying animations for this style change event. styleable.setLastStyleChangeEventStyle(RenderStyle::clonePtr(*newStyle)); // Apply all keyframe effects to the new style. auto animatedStyle = RenderStyle::clonePtr(*newStyle); animationImpact = styleable.applyKeyframeEffects(*animatedStyle, *previousLastStyleChangeEventStyle, &parentStyle); newStyle = WTFMove(animatedStyle); Adjuster adjuster(document, parentStyle, parentBoxStyle, styleable.pseudoId == PseudoId::None ? &element : nullptr); adjuster.adjustAnimatedStyle(*newStyle, animationImpact); } else styleable.setLastStyleChangeEventStyle(nullptr); // Deduplication speeds up equality comparisons as the properties inherit to descendants. // FIXME: There should be a more general mechanism for this. if (oldStyle) newStyle->deduplicateInheritedCustomProperties(*oldStyle); auto change = oldStyle ? determineChange(*oldStyle, *newStyle) : Change::Renderer; auto validity = element.styleValidity(); if (validity >= Validity::SubtreeAndRenderersInvalid || parentChange == Change::Renderer) change = Change::Renderer; bool shouldRecompositeLayer = animationImpact.contains(AnimationImpact::RequiresRecomposite) || element.styleResolutionShouldRecompositeLayer(); return { WTFMove(newStyle), change, shouldRecompositeLayer }; } void TreeResolver::pushParent(Element& element, const RenderStyle& style, Change change, DescendantsToResolve descendantsToResolve) { scope().selectorFilter.pushParent(&element); Parent parent(element, style, change, descendantsToResolve); if (auto* shadowRoot = element.shadowRoot()) { pushScope(*shadowRoot); parent.didPushScope = true; } else if (is(element) && downcast(element).assignedNodes()) { pushEnclosingScope(); parent.didPushScope = true; } m_parentStack.append(WTFMove(parent)); } void TreeResolver::popParent() { auto& parentElement = *parent().element; parentElement.setHasValidStyle(); parentElement.clearChildNeedsStyleRecalc(); if (parent().didPushScope) popScope(); scope().selectorFilter.popParent(); m_parentStack.removeLast(); } void TreeResolver::popParentsToDepth(unsigned depth) { ASSERT(depth); ASSERT(m_parentStack.size() >= depth); while (m_parentStack.size() > depth) popParent(); } static bool shouldResolvePseudoElement(const PseudoElement* pseudoElement) { if (!pseudoElement) return false; return pseudoElement->needsStyleRecalc(); } static bool shouldResolveElement(const Element& element, DescendantsToResolve parentDescendantsToResolve) { if (element.styleValidity() != Validity::Valid) return true; if (shouldResolvePseudoElement(element.beforePseudoElement())) return true; if (shouldResolvePseudoElement(element.afterPseudoElement())) return true; switch (parentDescendantsToResolve) { case DescendantsToResolve::None: return false; case DescendantsToResolve::Children: case DescendantsToResolve::All: return true; case DescendantsToResolve::ChildrenWithExplicitInherit: auto* existingStyle = element.renderOrDisplayContentsStyle(); return existingStyle && existingStyle->hasExplicitlyInheritedProperties(); }; ASSERT_NOT_REACHED(); return false; } static void clearNeedsStyleResolution(Element& element) { element.setHasValidStyle(); if (auto* before = element.beforePseudoElement()) before->setHasValidStyle(); if (auto* after = element.afterPseudoElement()) after->setHasValidStyle(); } static bool hasLoadingStylesheet(const Style::Scope& styleScope, const Element& element, bool checkDescendants) { if (!styleScope.hasPendingSheetsInBody()) return false; if (styleScope.hasPendingSheetInBody(element)) return true; if (!checkDescendants) return false; for (auto& descendant : descendantsOfType(element)) { if (styleScope.hasPendingSheetInBody(descendant)) return true; }; return false; } static std::unique_ptr 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 around the text node. auto style = RenderStyle::createPtr(); style->inheritFrom(parentElementStyle); return style; } static void resetDescendantStyleRelations(Element& element, DescendantsToResolve descendantsToResolve) { switch (descendantsToResolve) { case DescendantsToResolve::None: case DescendantsToResolve::ChildrenWithExplicitInherit: break; case DescendantsToResolve::Children: element.resetChildStyleRelations(); break; case DescendantsToResolve::All: element.resetAllDescendantStyleRelations(); break; }; } void TreeResolver::resolveComposedTree() { ASSERT(m_parentStack.size() == 1); ASSERT(m_scopeStack.size() == 1); auto descendants = composedTreeDescendants(m_document); auto it = descendants.begin(); auto end = descendants.end(); while (it != end) { popParentsToDepth(it.depth()); auto& node = *it; auto& parent = this->parent(); ASSERT(node.isConnected()); ASSERT(node.containingShadowRoot() == scope().shadowRoot); ASSERT(node.parentElement() == parent.element || is(node.parentNode()) || node.parentElement()->shadowRoot()); if (is(node)) { auto& text = downcast(node); if ((text.styleValidity() >= Validity::SubtreeAndRenderersInvalid && parent.change != Change::Renderer) || parent.style.display() == DisplayType::Contents) { TextUpdate textUpdate; textUpdate.inheritedDisplayContentsStyle = createInheritedDisplayContentsStyleIfNeeded(parent.style, parentBoxStyle()); m_update->addText(text, parent.element, WTFMove(textUpdate)); } text.setHasValidStyle(); it.traverseNextSkippingChildren(); continue; } auto& element = downcast(node); if (it.depth() > Settings::defaultMaximumRenderTreeDepth) { resetStyleForNonRenderedDescendants(element); it.traverseNextSkippingChildren(); continue; } auto* style = element.renderOrDisplayContentsStyle(); auto change = Change::None; auto descendantsToResolve = DescendantsToResolve::None; bool shouldResolve = shouldResolveElement(element, parent.descendantsToResolve); if (shouldResolve) { if (!element.hasDisplayContents()) element.resetComputedStyle(); element.resetStyleRelations(); if (element.hasCustomStyleResolveCallbacks()) element.willRecalcStyle(parent.change); auto elementUpdates = resolveElement(element); if (element.hasCustomStyleResolveCallbacks()) element.didRecalcStyle(elementUpdates.update.change); style = elementUpdates.update.style.get(); change = elementUpdates.update.change; descendantsToResolve = elementUpdates.descendantsToResolve; if (elementUpdates.update.style) m_update->addElement(element, parent.element, WTFMove(elementUpdates)); clearNeedsStyleResolution(element); } if (!style) resetStyleForNonRenderedDescendants(element); bool shouldIterateChildren = style && (element.childNeedsStyleRecalc() || descendantsToResolve != DescendantsToResolve::None); if (!m_didSeePendingStylesheet) m_didSeePendingStylesheet = hasLoadingStylesheet(m_document.styleScope(), element, !shouldIterateChildren); if (!shouldIterateChildren) { it.traverseNextSkippingChildren(); continue; } resetDescendantStyleRelations(element, descendantsToResolve); pushParent(element, *style, change, descendantsToResolve); it.traverseNext(); } popParentsToDepth(1); } std::unique_ptr TreeResolver::resolve() { auto& renderView = *m_document.renderView(); Element* documentElement = m_document.documentElement(); if (!documentElement) { m_document.styleScope().resolver(); return nullptr; } if (!documentElement->childNeedsStyleRecalc() && !documentElement->needsStyleRecalc()) return WTFMove(m_update); m_didSeePendingStylesheet = m_document.styleScope().hasPendingSheetsBeforeBody(); if (!m_update) m_update = makeUnique(m_document); m_scopeStack.append(adoptRef(*new Scope(m_document))); m_parentStack.append(Parent(m_document)); auto rootResolver = scope().resolver; // Pseudo element removal and similar may only work with these flags still set. Reset them after the style recalc. renderView.setUsesFirstLineRules(renderView.usesFirstLineRules() || rootResolver->usesFirstLineRules()); renderView.setUsesFirstLetterRules(renderView.usesFirstLetterRules() || rootResolver->usesFirstLetterRules()); resolveComposedTree(); renderView.setUsesFirstLineRules(rootResolver->usesFirstLineRules()); renderView.setUsesFirstLetterRules(rootResolver->usesFirstLetterRules()); ASSERT(m_scopeStack.size() == 1); ASSERT(m_parentStack.size() == 1); m_parentStack.clear(); popScope(); if (m_update->roots().isEmpty()) return { }; return WTFMove(m_update); } static Vector>& postResolutionCallbackQueue() { static NeverDestroyed>> vector; return vector; } static Vector>& memoryCacheClientCallsResumeQueue() { static NeverDestroyed>> vector; return vector; } void queuePostResolutionCallback(Function&& callback) { postResolutionCallbackQueue().append(WTFMove(callback)); } static void suspendMemoryCacheClientCalls(Document& document) { Page* page = document.page(); if (!page || !page->areMemoryCacheClientCallsEnabled()) return; page->setMemoryCacheClientCallsEnabled(false); memoryCacheClientCallsResumeQueue().append(&page->mainFrame()); } static unsigned resolutionNestingDepth; PostResolutionCallbackDisabler::PostResolutionCallbackDisabler(Document& document, DrainCallbacks drainCallbacks) : m_drainCallbacks(drainCallbacks) { ++resolutionNestingDepth; if (resolutionNestingDepth == 1) platformStrategies()->loaderStrategy()->suspendPendingRequests(); // FIXME: It's strange to build this into the disabler. suspendMemoryCacheClientCalls(document); } PostResolutionCallbackDisabler::~PostResolutionCallbackDisabler() { if (resolutionNestingDepth == 1) { if (m_drainCallbacks == DrainCallbacks::Yes) { // Get size each time through the loop because a callback can add more callbacks to the end of the queue. auto& queue = postResolutionCallbackQueue(); for (size_t i = 0; i < queue.size(); ++i) queue[i](); queue.clear(); } auto& queue = memoryCacheClientCallsResumeQueue(); for (size_t i = 0; i < queue.size(); ++i) { if (auto* page = queue[i]->page()) page->setMemoryCacheClientCallsEnabled(true); } queue.clear(); platformStrategies()->loaderStrategy()->resumePendingRequests(); } --resolutionNestingDepth; } bool postResolutionCallbacksAreSuspended() { return resolutionNestingDepth; } } }