diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/style/StyleTreeResolver.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/style/StyleTreeResolver.cpp')
-rw-r--r-- | Source/WebCore/style/StyleTreeResolver.cpp | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/Source/WebCore/style/StyleTreeResolver.cpp b/Source/WebCore/style/StyleTreeResolver.cpp new file mode 100644 index 000000000..7cf382da0 --- /dev/null +++ b/Source/WebCore/style/StyleTreeResolver.cpp @@ -0,0 +1,560 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Peter Kelly (pmk@post.com) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * (C) 2007 David Smith (catfish.man@gmail.com) + * Copyright (C) 2004-2010, 2012-2016 Apple Inc. All rights reserved. + * (C) 2007 Eric Seidel (eric@webkit.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "StyleTreeResolver.h" + +#include "CSSFontSelector.h" +#include "ComposedTreeAncestorIterator.h" +#include "ComposedTreeIterator.h" +#include "ElementIterator.h" +#include "HTMLBodyElement.h" +#include "HTMLMeterElement.h" +#include "HTMLNames.h" +#include "HTMLProgressElement.h" +#include "HTMLSlotElement.h" +#include "LoaderStrategy.h" +#include "MainFrame.h" +#include "NodeRenderStyle.h" +#include "Page.h" +#include "PlatformStrategies.h" +#include "Settings.h" +#include "ShadowRoot.h" +#include "StyleFontSizeFunctions.h" +#include "StyleResolver.h" +#include "StyleScope.h" +#include "Text.h" + +namespace WebCore { + +namespace Style { + +static std::unique_ptr<RenderStyle> makePlaceholderStyle(Document& document) +{ + auto placeholderStyle = RenderStyle::createPtr(); + placeholderStyle->setDisplay(NONE); + placeholderStyle->setIsPlaceholderStyle(); + + FontCascadeDescription fontDescription; + fontDescription.setOneFamily(standardFamily); + fontDescription.setKeywordSizeFromIdentifier(CSSValueMedium); + float size = Style::fontSizeForKeyword(CSSValueMedium, false, document); + fontDescription.setSpecifiedSize(size); + fontDescription.setComputedSize(size); + placeholderStyle->setFontDescription(fontDescription); + + placeholderStyle->fontCascade().update(&document.fontSelector()); + return placeholderStyle; +} + +TreeResolver::TreeResolver(Document& document) + : m_document(document) +{ +} + +TreeResolver::~TreeResolver() +{ +} + +TreeResolver::Scope::Scope(Document& document) + : styleResolver(document.styleScope().resolver()) + , sharingResolver(document, styleResolver.ruleSets(), selectorFilter) +{ +} + +TreeResolver::Scope::Scope(ShadowRoot& shadowRoot, Scope& enclosingScope) + : styleResolver(shadowRoot.styleScope().resolver()) + , sharingResolver(shadowRoot.documentScope(), styleResolver.ruleSets(), selectorFilter) + , shadowRoot(&shadowRoot) + , enclosingScope(&enclosingScope) +{ +} + +TreeResolver::Parent::Parent(Document& document, Change change) + : element(nullptr) + , style(*document.renderStyle()) + , change(change) +{ +} + +TreeResolver::Parent::Parent(Element& element, const RenderStyle& style, Change change) + : element(&element) + , style(style) + , change(change) +{ +} + +void TreeResolver::pushScope(ShadowRoot& shadowRoot) +{ + m_scopeStack.append(adoptRef(*new Scope(shadowRoot, scope()))); + scope().styleResolver.setOverrideDocumentElementStyle(m_documentElementStyle.get()); +} + +void TreeResolver::pushEnclosingScope() +{ + ASSERT(scope().enclosingScope); + m_scopeStack.append(*scope().enclosingScope); + scope().styleResolver.setOverrideDocumentElementStyle(m_documentElementStyle.get()); +} + +void TreeResolver::popScope() +{ + scope().styleResolver.setOverrideDocumentElementStyle(nullptr); + return m_scopeStack.removeLast(); +} + +std::unique_ptr<RenderStyle> TreeResolver::styleForElement(Element& element, const RenderStyle& inheritedStyle) +{ + if (!m_document.haveStylesheetsLoaded() && !element.renderer()) { + m_document.setHasNodesWithPlaceholderStyle(); + return makePlaceholderStyle(m_document); + } + + 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(element, *m_update)) + return style; + + auto elementStyle = scope().styleResolver.styleForElement(element, &inheritedStyle, parentBoxStyle(), MatchAllRules, nullptr, &scope().selectorFilter); + + if (elementStyle.relations) + commitRelations(WTFMove(elementStyle.relations), *m_update); + + return WTFMove(elementStyle.renderStyle); +} + +static void resetStyleForNonRenderedDescendants(Element& current) +{ + // FIXME: This is not correct with shadow trees. This should be done with ComposedTreeIterator. + bool elementNeedingStyleRecalcAffectsNextSiblingElementStyle = false; + for (auto& child : childrenOfType<Element>(current)) { + bool affectedByPreviousSibling = child.styleIsAffectedByPreviousSibling() && elementNeedingStyleRecalcAffectsNextSiblingElementStyle; + if (child.needsStyleRecalc() || elementNeedingStyleRecalcAffectsNextSiblingElementStyle) + elementNeedingStyleRecalcAffectsNextSiblingElementStyle = child.affectsNextSiblingElementStyle(); + + if (child.needsStyleRecalc() || affectedByPreviousSibling) { + child.resetComputedStyle(); + child.resetStyleRelations(); + child.setHasValidStyle(); + } + + if (child.childNeedsStyleRecalc()) { + resetStyleForNonRenderedDescendants(child); + child.clearChildNeedsStyleRecalc(); + } + } +} + +static bool affectsRenderedSubtree(Element& element, const RenderStyle& newStyle) +{ + if (element.renderer()) + return true; + if (newStyle.display() != NONE) + return true; + if (element.rendererIsNeeded(newStyle)) + return true; +#if ENABLE(CSS_REGIONS) + if (element.shouldMoveToFlowThread(newStyle)) + return true; +#endif + return false; +} + +ElementUpdate TreeResolver::resolveElement(Element& element) +{ + auto newStyle = styleForElement(element, parent().style); + + if (!affectsRenderedSubtree(element, *newStyle)) + return { }; + + auto update = createAnimatedElementUpdate(WTFMove(newStyle), element, parent().change); + + auto* existingStyle = element.renderStyle(); + + if (&element == m_document.documentElement()) { + m_documentElementStyle = RenderStyle::clonePtr(*update.style); + scope().styleResolver.setOverrideDocumentElementStyle(m_documentElementStyle.get()); + + if (update.change != NoChange && existingStyle && existingStyle->fontSize() != update.style->fontSize()) { + // "rem" units are relative to the document element's font size so we need to recompute everything. + // In practice this is rare. + scope().styleResolver.invalidateMatchedPropertiesCache(); + update.change = std::max(update.change, Force); + } + } + + // This is needed for resolving color:-webkit-text for subsequent elements. + // FIXME: We shouldn't mutate document when resolving style. + if (&element == m_document.body()) + m_document.setTextColor(update.style->visitedDependentColor(CSSPropertyColor)); + + // FIXME: These elements should not change renderer based on appearance property. + if (element.hasTagName(HTMLNames::meterTag) || is<HTMLProgressElement>(element)) { + if (existingStyle && update.style->appearance() != existingStyle->appearance()) + update.change = Detach; + } + + return update; +} + +const RenderStyle* TreeResolver::parentBoxStyle() const +{ + // 'display: contents' doesn't generate boxes. + for (unsigned i = m_parentStack.size(); i; --i) { + auto& parent = m_parentStack[i - 1]; + if (parent.style.display() == NONE) + return nullptr; + if (parent.style.display() != CONTENTS) + return &parent.style; + } + ASSERT_NOT_REACHED(); + return nullptr; +} + +ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr<RenderStyle> newStyle, Element& element, Change parentChange) +{ + auto validity = element.styleValidity(); + bool recompositeLayer = element.styleResolutionShouldRecompositeLayer(); + + auto makeUpdate = [&] (std::unique_ptr<RenderStyle> style, Change change) { + if (validity >= Validity::SubtreeInvalid) + change = std::max(change, validity == Validity::SubtreeAndRenderersInvalid ? Detach : Force); + if (parentChange >= Force) + change = std::max(change, parentChange); + return ElementUpdate { WTFMove(style), change, recompositeLayer }; + }; + + auto* renderer = element.renderer(); + + bool shouldReconstruct = validity >= Validity::SubtreeAndRenderersInvalid || parentChange == Detach; + if (shouldReconstruct) + return makeUpdate(WTFMove(newStyle), Detach); + + if (!renderer) { + auto keepsDisplayContents = newStyle->display() == CONTENTS && element.hasDisplayContents(); + // Some inherited property might have changed. + return makeUpdate(WTFMove(newStyle), keepsDisplayContents ? Inherit : Detach); + } + + std::unique_ptr<RenderStyle> animatedStyle; + if (element.document().frame()->animation().updateAnimations(*renderer, *newStyle, animatedStyle)) + recompositeLayer = true; + + if (animatedStyle) { + auto change = determineChange(renderer->style(), *animatedStyle); + if (renderer->hasInitialAnimatedStyle()) { + renderer->setHasInitialAnimatedStyle(false); + // When we initialize a newly created renderer with initial animated style we don't inherit it to descendants. + // The first animation frame needs to correct this. + // FIXME: We should compute animated style correctly during initial style resolution when we don't have renderers yet. + // https://bugs.webkit.org/show_bug.cgi?id=171926 + change = std::max(change, Inherit); + } + // If animation forces render tree reconstruction pass the original style. The animation will be applied on renderer construction. + // FIXME: We should always use the animated style here. + auto style = change == Detach ? WTFMove(newStyle) : WTFMove(animatedStyle); + return makeUpdate(WTFMove(style), change); + } + + auto change = determineChange(renderer->style(), *newStyle); + return makeUpdate(WTFMove(newStyle), change); +} + +void TreeResolver::pushParent(Element& element, const RenderStyle& style, Change change) +{ + scope().selectorFilter.pushParent(&element); + + Parent parent(element, style, change); + + if (auto* shadowRoot = element.shadowRoot()) { + pushScope(*shadowRoot); + parent.didPushScope = true; + } + else if (is<HTMLSlotElement>(element) && downcast<HTMLSlotElement>(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, Style::Change parentChange) +{ + if (parentChange >= Inherit) + return true; + if (parentChange == NoInherit) { + auto* existingStyle = element.renderStyle(); + if (existingStyle && existingStyle->hasExplicitlyInheritedProperties()) + return true; + } + if (element.needsStyleRecalc()) + return true; + if (element.hasDisplayContents()) + return true; + if (shouldResolvePseudoElement(element.beforePseudoElement())) + return true; + if (shouldResolvePseudoElement(element.afterPseudoElement())) + return true; + + return false; +} + +static void clearNeedsStyleResolution(Element& element) +{ + element.setHasValidStyle(); + if (auto* before = element.beforePseudoElement()) + before->setHasValidStyle(); + if (auto* after = element.afterPseudoElement()) + after->setHasValidStyle(); +} + +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(); + + // FIXME: SVG <use> element may cause tree mutations during style recalc. + it.dropAssertions(); + + while (it != end) { + popParentsToDepth(it.depth()); + + auto& node = *it; + auto& parent = this->parent(); + + ASSERT(node.isConnected()); + ASSERT(node.containingShadowRoot() == scope().shadowRoot); + ASSERT(node.parentElement() == parent.element || is<ShadowRoot>(node.parentNode()) || node.parentElement()->shadowRoot()); + + if (is<Text>(node)) { + auto& text = downcast<Text>(node); + if (text.styleValidity() >= Validity::SubtreeAndRenderersInvalid && parent.change != Detach) + m_update->addText(text, parent.element); + + text.setHasValidStyle(); + it.traverseNextSkippingChildren(); + continue; + } + + auto& element = downcast<Element>(node); + + if (it.depth() > Settings::defaultMaximumRenderTreeDepth) { + resetStyleForNonRenderedDescendants(element); + element.clearChildNeedsStyleRecalc(); + it.traverseNextSkippingChildren(); + continue; + } + + // FIXME: We should deal with this during style invalidation. + bool affectedByPreviousSibling = element.styleIsAffectedByPreviousSibling() && parent.elementNeedingStyleRecalcAffectsNextSiblingElementStyle; + if (element.needsStyleRecalc() || parent.elementNeedingStyleRecalcAffectsNextSiblingElementStyle) + parent.elementNeedingStyleRecalcAffectsNextSiblingElementStyle = element.affectsNextSiblingElementStyle(); + + auto* style = element.renderStyle(); + auto change = NoChange; + + bool shouldResolve = shouldResolveElement(element, parent.change) || affectedByPreviousSibling; + if (shouldResolve) { + element.resetComputedStyle(); + element.resetStyleRelations(); + + if (element.hasCustomStyleResolveCallbacks()) + element.willRecalcStyle(parent.change); + + auto elementUpdate = resolveElement(element); + + if (element.hasCustomStyleResolveCallbacks()) + element.didRecalcStyle(elementUpdate.change); + + style = elementUpdate.style.get(); + change = elementUpdate.change; + + if (affectedByPreviousSibling && change != Detach) + change = Force; + + if (elementUpdate.style) + m_update->addElement(element, parent.element, WTFMove(elementUpdate)); + + clearNeedsStyleResolution(element); + } + + if (!style) { + resetStyleForNonRenderedDescendants(element); + element.clearChildNeedsStyleRecalc(); + } + + bool shouldIterateChildren = style && (element.childNeedsStyleRecalc() || change != NoChange); + if (!shouldIterateChildren) { + it.traverseNextSkippingChildren(); + continue; + } + + pushParent(element, *style, change); + + it.traverseNext(); + } + + popParentsToDepth(1); +} + +std::unique_ptr<Update> TreeResolver::resolve(Change change) +{ + auto& renderView = *m_document.renderView(); + + Element* documentElement = m_document.documentElement(); + if (!documentElement) { + m_document.styleScope().resolver(); + return nullptr; + } + if (change != Force && !documentElement->childNeedsStyleRecalc() && !documentElement->needsStyleRecalc()) + return nullptr; + + m_update = std::make_unique<Update>(m_document); + m_scopeStack.append(adoptRef(*new Scope(m_document))); + m_parentStack.append(Parent(m_document, change)); + + // Pseudo element removal and similar may only work with these flags still set. Reset them after the style recalc. + renderView.setUsesFirstLineRules(renderView.usesFirstLineRules() || scope().styleResolver.usesFirstLineRules()); + renderView.setUsesFirstLetterRules(renderView.usesFirstLetterRules() || scope().styleResolver.usesFirstLetterRules()); + + resolveComposedTree(); + + renderView.setUsesFirstLineRules(scope().styleResolver.usesFirstLineRules()); + renderView.setUsesFirstLetterRules(scope().styleResolver.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<Function<void ()>>& postResolutionCallbackQueue() +{ + static NeverDestroyed<Vector<Function<void ()>>> vector; + return vector; +} + +void queuePostResolutionCallback(Function<void ()>&& callback) +{ + postResolutionCallbackQueue().append(WTFMove(callback)); +} + +static void suspendMemoryCacheClientCalls(Document& document) +{ + Page* page = document.page(); + if (!page || !page->areMemoryCacheClientCallsEnabled()) + return; + + page->setMemoryCacheClientCallsEnabled(false); + + postResolutionCallbackQueue().append([protectedMainFrame = Ref<MainFrame>(page->mainFrame())] { + if (Page* page = protectedMainFrame->page()) + page->setMemoryCacheClientCallsEnabled(true); + }); +} + +static unsigned resolutionNestingDepth; + +PostResolutionCallbackDisabler::PostResolutionCallbackDisabler(Document& document) +{ + ++resolutionNestingDepth; + + if (resolutionNestingDepth == 1) + platformStrategies()->loaderStrategy()->suspendPendingRequests(); + + // FIXME: It's strange to build this into the disabler. + suspendMemoryCacheClientCalls(document); +} + +PostResolutionCallbackDisabler::~PostResolutionCallbackDisabler() +{ + if (resolutionNestingDepth == 1) { + // 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(); + + platformStrategies()->loaderStrategy()->resumePendingRequests(); + } + + --resolutionNestingDepth; +} + +bool postResolutionCallbacksAreSuspended() +{ + return resolutionNestingDepth; +} + +} +} |