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/RenderTreeUpdater.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/style/RenderTreeUpdater.cpp')
-rw-r--r-- | Source/WebCore/style/RenderTreeUpdater.cpp | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/Source/WebCore/style/RenderTreeUpdater.cpp b/Source/WebCore/style/RenderTreeUpdater.cpp new file mode 100644 index 000000000..65682ff91 --- /dev/null +++ b/Source/WebCore/style/RenderTreeUpdater.cpp @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderTreeUpdater.h" + +#include "AXObjectCache.h" +#include "ComposedTreeAncestorIterator.h" +#include "ComposedTreeIterator.h" +#include "Document.h" +#include "Element.h" +#include "FlowThreadController.h" +#include "HTMLSlotElement.h" +#include "InspectorInstrumentation.h" +#include "NodeRenderStyle.h" +#include "PseudoElement.h" +#include "RenderFullScreen.h" +#include "RenderNamedFlowThread.h" +#include "StyleResolver.h" +#include "StyleTreeResolver.h" + +#if PLATFORM(IOS) +#include "WKContentObservation.h" +#include "WKContentObservationInternal.h" +#endif + +namespace WebCore { + +#if PLATFORM(IOS) +class CheckForVisibilityChange { +public: + CheckForVisibilityChange(const Element&); + ~CheckForVisibilityChange(); + +private: + const Element& m_element; + EDisplay m_previousDisplay; + EVisibility m_previousVisibility; + EVisibility m_previousImplicitVisibility; +}; +#endif // PLATFORM(IOS) + +RenderTreeUpdater::Parent::Parent(ContainerNode& root) + : element(is<Document>(root) ? nullptr : downcast<Element>(&root)) + , renderTreePosition(RenderTreePosition(*root.renderer())) +{ +} + +RenderTreeUpdater::Parent::Parent(Element& element, Style::Change styleChange) + : element(&element) + , styleChange(styleChange) + , renderTreePosition(element.renderer() ? std::make_optional(RenderTreePosition(*element.renderer())) : std::nullopt) +{ +} + + +RenderTreeUpdater::RenderTreeUpdater(Document& document) + : m_document(document) +{ +} + +static ContainerNode* findRenderingRoot(ContainerNode& node) +{ + if (node.renderer()) + return &node; + for (auto& ancestor : composedTreeAncestors(node)) { + if (ancestor.renderer()) + return &ancestor; + if (!ancestor.hasDisplayContents()) + return nullptr; + } + return &node.document(); +} + +static ListHashSet<ContainerNode*> findRenderingRoots(const Style::Update& update) +{ + ListHashSet<ContainerNode*> renderingRoots; + for (auto* root : update.roots()) { + auto* renderingRoot = findRenderingRoot(*root); + if (!renderingRoot) + continue; + renderingRoots.add(renderingRoot); + } + return renderingRoots; +} + +void RenderTreeUpdater::commit(std::unique_ptr<const Style::Update> styleUpdate) +{ + ASSERT(&m_document == &styleUpdate->document()); + + if (!m_document.shouldCreateRenderers() || !m_document.renderView()) + return; + + Style::PostResolutionCallbackDisabler callbackDisabler(m_document); + + m_styleUpdate = WTFMove(styleUpdate); + + for (auto* root : findRenderingRoots(*m_styleUpdate)) + updateRenderTree(*root); + + m_styleUpdate = nullptr; +} + +static bool shouldCreateRenderer(const Element& element, const RenderElement& parentRenderer) +{ + if (!parentRenderer.canHaveChildren() && !(element.isPseudoElement() && parentRenderer.canHaveGeneratedChildren())) + return false; + if (parentRenderer.element() && !parentRenderer.element()->childShouldCreateRenderer(element)) + return false; + return true; +} + +void RenderTreeUpdater::updateRenderTree(ContainerNode& root) +{ + ASSERT(root.renderer()); + ASSERT(m_parentStack.isEmpty()); + + m_parentStack.append(Parent(root)); + + auto descendants = composedTreeDescendants(root); + auto it = descendants.begin(); + auto end = descendants.end(); + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=156172 + it.dropAssertions(); + + while (it != end) { + popParentsToDepth(it.depth()); + + auto& node = *it; + + if (auto* renderer = node.renderer()) + renderTreePosition().invalidateNextSibling(*renderer); + + if (is<Text>(node)) { + auto& text = downcast<Text>(node); + if (parent().styleChange == Style::Detach || m_styleUpdate->textUpdate(text) || m_invalidatedWhitespaceOnlyTextSiblings.contains(&text)) + updateTextRenderer(text); + + it.traverseNextSkippingChildren(); + continue; + } + + auto& element = downcast<Element>(node); + + auto* elementUpdate = m_styleUpdate->elementUpdate(element); + if (!elementUpdate) { + it.traverseNextSkippingChildren(); + continue; + } + + updateElementRenderer(element, *elementUpdate); + + bool mayHaveRenderedDescendants = element.renderer() || (element.hasDisplayContents() && shouldCreateRenderer(element, renderTreePosition().parent())); + if (!mayHaveRenderedDescendants) { + it.traverseNextSkippingChildren(); + continue; + } + + pushParent(element, elementUpdate ? elementUpdate->change : Style::NoChange); + + it.traverseNext(); + } + + popParentsToDepth(0); + + m_invalidatedWhitespaceOnlyTextSiblings.clear(); +} + +RenderTreePosition& RenderTreeUpdater::renderTreePosition() +{ + for (unsigned i = m_parentStack.size(); i; --i) { + if (auto& position = m_parentStack[i - 1].renderTreePosition) + return *position; + } + ASSERT_NOT_REACHED(); + return *m_parentStack.last().renderTreePosition; +} + +void RenderTreeUpdater::pushParent(Element& element, Style::Change changeType) +{ + m_parentStack.append(Parent(element, changeType)); + + updateBeforeOrAfterPseudoElement(element, BEFORE); +} + +void RenderTreeUpdater::popParent() +{ + auto& parent = m_parentStack.last(); + + if (parent.element) { + updateBeforeOrAfterPseudoElement(*parent.element, AFTER); + + if (parent.element->hasCustomStyleResolveCallbacks() && parent.styleChange == Style::Detach && parent.element->renderer()) + parent.element->didAttachRenderers(); + } + m_parentStack.removeLast(); +} + +void RenderTreeUpdater::popParentsToDepth(unsigned depth) +{ + ASSERT(m_parentStack.size() >= depth); + + while (m_parentStack.size() > depth) + popParent(); +} + +static bool pseudoStyleCacheIsInvalid(RenderElement* renderer, RenderStyle* newStyle) +{ + const RenderStyle& currentStyle = renderer->style(); + + const PseudoStyleCache* pseudoStyleCache = currentStyle.cachedPseudoStyles(); + if (!pseudoStyleCache) + return false; + + for (auto& cache : *pseudoStyleCache) { + PseudoId pseudoId = cache->styleType(); + std::unique_ptr<RenderStyle> newPseudoStyle = renderer->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId), newStyle, newStyle); + if (!newPseudoStyle) + return true; + if (*newPseudoStyle != *cache) { + newStyle->addCachedPseudoStyle(WTFMove(newPseudoStyle)); + return true; + } + } + return false; +} + +void RenderTreeUpdater::updateElementRenderer(Element& element, const Style::ElementUpdate& update) +{ +#if PLATFORM(IOS) + CheckForVisibilityChange checkForVisibilityChange(element); +#endif + + bool shouldTearDownRenderers = update.change == Style::Detach + && (element.renderer() || element.isNamedFlowContentElement() || element.hasDisplayContents()); + if (shouldTearDownRenderers) { + if (!element.renderer()) { + // We may be tearing down a descendant renderer cached in renderTreePosition. + renderTreePosition().invalidateNextSibling(); + } + tearDownRenderers(element, TeardownType::KeepHoverAndActive); + } + + bool hasDisplayContents = update.style->display() == CONTENTS; + if (hasDisplayContents != element.hasDisplayContents()) { + element.setHasDisplayContents(hasDisplayContents); + // Render tree position needs to be recomputed as rendering siblings may be found from the display:contents subtree. + renderTreePosition().invalidateNextSibling(); + } + + bool shouldCreateNewRenderer = !element.renderer() && !hasDisplayContents; + if (shouldCreateNewRenderer) { + if (element.hasCustomStyleResolveCallbacks()) + element.willAttachRenderers(); + createRenderer(element, RenderStyle::clone(*update.style)); + invalidateWhitespaceOnlyTextSiblingsAfterAttachIfNeeded(element); + return; + } + + if (!element.renderer()) + return; + auto& renderer = *element.renderer(); + + if (update.recompositeLayer) { + renderer.setStyle(RenderStyle::clone(*update.style), StyleDifferenceRecompositeLayer); + return; + } + + if (update.change == Style::NoChange) { + if (pseudoStyleCacheIsInvalid(&renderer, update.style.get())) { + renderer.setStyle(RenderStyle::clone(*update.style), StyleDifferenceEqual); + return; + } + return; + } + + renderer.setStyle(RenderStyle::clone(*update.style), StyleDifferenceEqual); +} + +#if ENABLE(CSS_REGIONS) +static void registerElementForFlowThreadIfNeeded(Element& element, const RenderStyle& style) +{ + if (!element.shouldMoveToFlowThread(style)) + return; + FlowThreadController& flowThreadController = element.document().renderView()->flowThreadController(); + flowThreadController.registerNamedFlowContentElement(element, flowThreadController.ensureRenderFlowThreadWithName(style.flowThread())); +} +#endif + +void RenderTreeUpdater::createRenderer(Element& element, RenderStyle&& style) +{ + auto computeInsertionPosition = [this, &element, &style] () { +#if ENABLE(CSS_REGIONS) + if (element.shouldMoveToFlowThread(style)) + return RenderTreePosition::insertionPositionForFlowThread(renderTreePosition().parent().element(), element, style); +#endif + renderTreePosition().computeNextSibling(element); + return renderTreePosition(); + }; + + if (!shouldCreateRenderer(element, renderTreePosition().parent())) + return; + +#if ENABLE(CSS_REGIONS) + // Even display: none elements need to be registered in FlowThreadController. + registerElementForFlowThreadIfNeeded(element, style); +#endif + + if (!element.rendererIsNeeded(style)) + return; + + RenderTreePosition insertionPosition = computeInsertionPosition(); + RenderElement* newRenderer = element.createElementRenderer(WTFMove(style), insertionPosition).leakPtr(); + if (!newRenderer) + return; + if (!insertionPosition.canInsert(*newRenderer)) { + newRenderer->destroy(); + return; + } + + // Make sure the RenderObject already knows it is going to be added to a RenderFlowThread before we set the style + // for the first time. Otherwise code using inRenderFlowThread() in the styleWillChange and styleDidChange will fail. + newRenderer->setFlowThreadState(insertionPosition.parent().flowThreadState()); + + element.setRenderer(newRenderer); + + auto& initialStyle = newRenderer->style(); + std::unique_ptr<RenderStyle> animatedStyle; + newRenderer->animation().updateAnimations(*newRenderer, initialStyle, animatedStyle); + if (animatedStyle) { + newRenderer->setStyleInternal(WTFMove(*animatedStyle)); + newRenderer->setHasInitialAnimatedStyle(true); + } + + newRenderer->initializeStyle(); + +#if ENABLE(FULLSCREEN_API) + if (m_document.webkitIsFullScreen() && m_document.webkitCurrentFullScreenElement() == &element) { + newRenderer = RenderFullScreen::wrapRenderer(newRenderer, &insertionPosition.parent(), m_document); + if (!newRenderer) + return; + } +#endif + // Note: Adding newRenderer instead of renderer(). renderer() may be a child of newRenderer. + insertionPosition.insert(*newRenderer); + + if (AXObjectCache* cache = m_document.axObjectCache()) + cache->updateCacheAfterNodeIsAttached(&element); +} + +static bool textRendererIsNeeded(const Text& textNode, const RenderTreePosition& renderTreePosition) +{ + const RenderElement& parentRenderer = renderTreePosition.parent(); + if (!parentRenderer.canHaveChildren()) + return false; + if (parentRenderer.element() && !parentRenderer.element()->childShouldCreateRenderer(textNode)) + return false; + if (textNode.isEditingText()) + return true; + if (!textNode.length()) + return false; + if (!textNode.containsOnlyWhitespace()) + return true; + // This text node has nothing but white space. We may still need a renderer in some cases. + if (parentRenderer.isTable() || parentRenderer.isTableRow() || parentRenderer.isTableSection() || parentRenderer.isRenderTableCol() || parentRenderer.isFrameSet()) + return false; + if (parentRenderer.style().preserveNewline()) // pre/pre-wrap/pre-line always make renderers. + return true; + + RenderObject* previousRenderer = renderTreePosition.previousSiblingRenderer(textNode); + if (previousRenderer && previousRenderer->isBR()) // <span><br/> <br/></span> + return false; + + if (parentRenderer.isRenderInline()) { + // <span><div/> <div/></span> + if (previousRenderer && !previousRenderer->isInline()) + return false; + } else { + if (parentRenderer.isRenderBlock() && !parentRenderer.childrenInline() && (!previousRenderer || !previousRenderer->isInline())) + return false; + + RenderObject* first = parentRenderer.firstChild(); + while (first && first->isFloatingOrOutOfFlowPositioned()) + first = first->nextSibling(); + RenderObject* nextRenderer = renderTreePosition.nextSiblingRenderer(textNode); + if (!first || nextRenderer == first) { + // Whitespace at the start of a block just goes away. Don't even make a render object for this text. + return false; + } + } + return true; +} + +static void createTextRenderer(Text& textNode, RenderTreePosition& renderTreePosition) +{ + ASSERT(!textNode.renderer()); + + auto newRenderer = textNode.createTextRenderer(renderTreePosition.parent().style()); + ASSERT(newRenderer); + + renderTreePosition.computeNextSibling(textNode); + + if (!renderTreePosition.canInsert(*newRenderer)) + return; + + textNode.setRenderer(newRenderer.get()); + renderTreePosition.insert(*newRenderer.leakPtr()); +} + +void RenderTreeUpdater::updateTextRenderer(Text& text) +{ + bool hasRenderer = text.renderer(); + bool needsRenderer = textRendererIsNeeded(text, renderTreePosition()); + if (hasRenderer) { + if (needsRenderer) + return; + tearDownRenderer(text); + invalidateWhitespaceOnlyTextSiblingsAfterAttachIfNeeded(text); + return; + } + if (!needsRenderer) + return; + createTextRenderer(text, renderTreePosition()); + invalidateWhitespaceOnlyTextSiblingsAfterAttachIfNeeded(text); +} + +void RenderTreeUpdater::invalidateWhitespaceOnlyTextSiblingsAfterAttachIfNeeded(Node& current) +{ + // FIXME: This needs to traverse in composed tree order. + + // This function finds sibling text renderers where the results of textRendererIsNeeded may have changed as a result of + // the current node gaining or losing the renderer. This can only affect white space text nodes. + for (Node* sibling = current.nextSibling(); sibling; sibling = sibling->nextSibling()) { + if (is<Element>(*sibling)) { + if (m_styleUpdate->elementUpdate(downcast<Element>(*sibling))) + return; + // Text renderers beyond rendered elements can't be affected. + if (!sibling->renderer() || RenderTreePosition::isRendererReparented(*sibling->renderer())) + continue; + return; + } + if (!is<Text>(*sibling)) + continue; + Text& textSibling = downcast<Text>(*sibling); + if (m_styleUpdate->textUpdate(textSibling)) + return; + if (!textSibling.containsOnlyWhitespace()) + continue; + m_invalidatedWhitespaceOnlyTextSiblings.add(&textSibling); + } +} + +static bool needsPseudoElement(Element& current, PseudoId pseudoId) +{ + if (!current.renderer() || !current.renderer()->canHaveGeneratedChildren()) + return false; + if (current.isPseudoElement()) + return false; + if (!pseudoElementRendererIsNeeded(current.renderer()->getCachedPseudoStyle(pseudoId))) + return false; + return true; +} + +void RenderTreeUpdater::updateBeforeOrAfterPseudoElement(Element& current, PseudoId pseudoId) +{ + PseudoElement* pseudoElement = pseudoId == BEFORE ? current.beforePseudoElement() : current.afterPseudoElement(); + + if (auto* renderer = pseudoElement ? pseudoElement->renderer() : nullptr) + renderTreePosition().invalidateNextSibling(*renderer); + + bool needsPseudoElement = WebCore::needsPseudoElement(current, pseudoId); + if (!needsPseudoElement) { + if (pseudoElement) { + if (pseudoId == BEFORE) + current.clearBeforePseudoElement(); + else + current.clearAfterPseudoElement(); + } + return; + } + + RefPtr<PseudoElement> newPseudoElement; + if (!pseudoElement) { + newPseudoElement = PseudoElement::create(current, pseudoId); + pseudoElement = newPseudoElement.get(); + } + + auto newStyle = RenderStyle::clonePtr(*current.renderer()->getCachedPseudoStyle(pseudoId, ¤t.renderer()->style())); + + auto elementUpdate = Style::TreeResolver::createAnimatedElementUpdate(WTFMove(newStyle), *pseudoElement, Style::NoChange); + + if (elementUpdate.change == Style::NoChange) + return; + + if (newPseudoElement) { + InspectorInstrumentation::pseudoElementCreated(m_document.page(), *newPseudoElement); + if (pseudoId == BEFORE) + current.setBeforePseudoElement(newPseudoElement.releaseNonNull()); + else + current.setAfterPseudoElement(newPseudoElement.releaseNonNull()); + } + + updateElementRenderer(*pseudoElement, elementUpdate); + + if (elementUpdate.change == Style::Detach) + pseudoElement->didAttachRenderers(); + else + pseudoElement->didRecalcStyle(elementUpdate.change); +} + +void RenderTreeUpdater::tearDownRenderers(Element& root, TeardownType teardownType) +{ + WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; + + Vector<Element*, 30> teardownStack; + + auto push = [&] (Element& element) { + if (element.hasCustomStyleResolveCallbacks()) + element.willDetachRenderers(); + teardownStack.append(&element); + }; + + auto pop = [&] (unsigned depth) { + while (teardownStack.size() > depth) { + auto& element = *teardownStack.takeLast(); + + if (teardownType != TeardownType::KeepHoverAndActive) + element.clearHoverAndActiveStatusBeforeDetachingRenderer(); + element.clearStyleDerivedDataBeforeDetachingRenderer(); + + if (auto* renderer = element.renderer()) { + renderer->destroyAndCleanupAnonymousWrappers(); + element.setRenderer(nullptr); + } + if (element.hasCustomStyleResolveCallbacks()) + element.didDetachRenderers(); + } + }; + + push(root); + + auto descendants = composedTreeDescendants(root); + for (auto it = descendants.begin(), end = descendants.end(); it != end; ++it) { + pop(it.depth()); + + if (is<Text>(*it)) { + tearDownRenderer(downcast<Text>(*it)); + continue; + } + + push(downcast<Element>(*it)); + } + + pop(0); +} + +void RenderTreeUpdater::tearDownRenderer(Text& text) +{ + auto* renderer = text.renderer(); + if (!renderer) + return; + renderer->destroyAndCleanupAnonymousWrappers(); + text.setRenderer(nullptr); +} + +#if PLATFORM(IOS) +static EVisibility elementImplicitVisibility(const Element& element) +{ + auto* renderer = element.renderer(); + if (!renderer) + return VISIBLE; + + auto& style = renderer->style(); + + auto width = style.width(); + auto height = style.height(); + if ((width.isFixed() && width.value() <= 0) || (height.isFixed() && height.value() <= 0)) + return HIDDEN; + + auto top = style.top(); + auto left = style.left(); + if (left.isFixed() && width.isFixed() && -left.value() >= width.value()) + return HIDDEN; + + if (top.isFixed() && height.isFixed() && -top.value() >= height.value()) + return HIDDEN; + return VISIBLE; +} + +CheckForVisibilityChange::CheckForVisibilityChange(const Element& element) + : m_element(element) + , m_previousDisplay(element.renderStyle() ? element.renderStyle()->display() : NONE) + , m_previousVisibility(element.renderStyle() ? element.renderStyle()->visibility() : HIDDEN) + , m_previousImplicitVisibility(WKObservingContentChanges() && WKObservedContentChange() != WKContentVisibilityChange ? elementImplicitVisibility(element) : VISIBLE) +{ +} + +CheckForVisibilityChange::~CheckForVisibilityChange() +{ + if (!WKObservingContentChanges()) + return; + if (m_element.isInUserAgentShadowTree()) + return; + auto* style = m_element.renderStyle(); + if (!style) + return; + if ((m_previousDisplay == NONE && style->display() != NONE) || (m_previousVisibility == HIDDEN && style->visibility() != HIDDEN) + || (m_previousImplicitVisibility == HIDDEN && elementImplicitVisibility(m_element) == VISIBLE)) + WKSetObservedContentChange(WKContentVisibilityChange); +} +#endif + +} |