| /* |
| * 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, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013 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 "StyleResolveTree.h" |
| |
| #include "AXObjectCache.h" |
| #include "CSSFontSelector.h" |
| #include "Element.h" |
| #include "ElementIterator.h" |
| #include "ElementRareData.h" |
| #include "ElementTraversal.h" |
| #include "FlowThreadController.h" |
| #include "NodeRenderStyle.h" |
| #include "NodeRenderingTraversal.h" |
| #include "NodeTraversal.h" |
| #include "RenderElement.h" |
| #include "RenderFullScreen.h" |
| #include "RenderNamedFlowThread.h" |
| #include "RenderText.h" |
| #include "RenderView.h" |
| #include "RenderWidget.h" |
| #include "Settings.h" |
| #include "ShadowRoot.h" |
| #include "StyleResolveForDocument.h" |
| #include "StyleResolver.h" |
| #include "Text.h" |
| |
| #if PLATFORM(IOS) |
| #include "CSSFontSelector.h" |
| #include "WKContentObservation.h" |
| #endif |
| |
| namespace WebCore { |
| |
| namespace Style { |
| |
| enum DetachType { NormalDetach, ReattachDetach }; |
| |
| static void attachRenderTree(Element&, PassRefPtr<RenderStyle>); |
| static void detachRenderTree(Element&, DetachType); |
| |
| Change determineChange(const RenderStyle* s1, const RenderStyle* s2, Settings* settings) |
| { |
| if (!s1 || !s2) |
| return Detach; |
| if (s1->display() != s2->display()) |
| return Detach; |
| if (s1->hasPseudoStyle(FIRST_LETTER) != s2->hasPseudoStyle(FIRST_LETTER)) |
| return Detach; |
| // We just detach if a renderer acquires or loses a column-span, since spanning elements |
| // typically won't contain much content. |
| if (s1->columnSpan() != s2->columnSpan()) |
| return Detach; |
| if (settings->regionBasedColumnsEnabled()) { |
| bool specifiesColumns1 = !s1->hasAutoColumnCount() || !s1->hasAutoColumnWidth(); |
| bool specifiesColumns2 = !s2->hasAutoColumnCount() || !s2->hasAutoColumnWidth(); |
| if (specifiesColumns1 != specifiesColumns2) |
| return Detach; |
| } |
| if (!s1->contentDataEquivalent(s2)) |
| return Detach; |
| // When text-combine property has been changed, we need to prepare a separate renderer object. |
| // When text-combine is on, we use RenderCombineText, otherwise RenderText. |
| // https://bugs.webkit.org/show_bug.cgi?id=55069 |
| if (s1->hasTextCombine() != s2->hasTextCombine()) |
| return Detach; |
| // We need to reattach the node, so that it is moved to the correct RenderFlowThread. |
| if (s1->flowThread() != s2->flowThread()) |
| return Detach; |
| // When the region thread has changed, we need to prepare a separate render region object. |
| if (s1->regionThread() != s2->regionThread()) |
| return Detach; |
| |
| if (*s1 != *s2) { |
| if (s1->inheritedNotEqual(s2)) |
| return Inherit; |
| if (s1->hasExplicitlyInheritedProperties() || s2->hasExplicitlyInheritedProperties()) |
| return Inherit; |
| |
| return NoInherit; |
| } |
| // If the pseudoStyles have changed, we want any StyleChange that is not NoChange |
| // because setStyle will do the right thing with anything else. |
| if (s1->hasAnyPublicPseudoStyles()) { |
| for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < FIRST_INTERNAL_PSEUDOID; pseudoId = static_cast<PseudoId>(pseudoId + 1)) { |
| if (s1->hasPseudoStyle(pseudoId)) { |
| RenderStyle* ps2 = s2->getCachedPseudoStyle(pseudoId); |
| if (!ps2) |
| return NoInherit; |
| RenderStyle* ps1 = s1->getCachedPseudoStyle(pseudoId); |
| if (!ps1 || *ps1 != *ps2) |
| return NoInherit; |
| } |
| } |
| } |
| |
| return NoChange; |
| } |
| |
| static bool isRendererReparented(const RenderObject* renderer) |
| { |
| if (!renderer->node()->isElementNode()) |
| return false; |
| if (renderer->style() && !renderer->style()->flowThread().isEmpty()) |
| return true; |
| return false; |
| } |
| |
| static RenderObject* nextSiblingRenderer(const Element& element, const ContainerNode* renderingParentNode) |
| { |
| // Avoid an O(N^2) problem with this function by not checking for |
| // nextRenderer() when the parent element hasn't attached yet. |
| // FIXME: Why would we get here anyway if parent is not attached? |
| if (renderingParentNode && !renderingParentNode->attached()) |
| return 0; |
| for (Node* sibling = NodeRenderingTraversal::nextSibling(&element); sibling; sibling = NodeRenderingTraversal::nextSibling(sibling)) { |
| RenderObject* renderer = sibling->renderer(); |
| if (renderer && !isRendererReparented(renderer)) |
| return renderer; |
| } |
| return 0; |
| } |
| |
| static bool shouldCreateRenderer(const Element& element, const ContainerNode* renderingParent) |
| { |
| if (!element.document().shouldCreateRenderers()) |
| return false; |
| if (!renderingParent) |
| return false; |
| RenderObject* parentRenderer = renderingParent->renderer(); |
| if (!parentRenderer) |
| return false; |
| if (!parentRenderer->canHaveChildren() && !(element.isPseudoElement() && parentRenderer->canHaveGeneratedChildren())) |
| return false; |
| if (!renderingParent->childShouldCreateRenderer(&element)) |
| return false; |
| return true; |
| } |
| |
| // Check the specific case of elements that are children of regions but are flowed into a flow thread themselves. |
| static bool elementInsideRegionNeedsRenderer(Element& element, const ContainerNode* renderingParentNode, RefPtr<RenderStyle>& style) |
| { |
| #if ENABLE(CSS_REGIONS) |
| const RenderObject* parentRenderer = renderingParentNode ? renderingParentNode->renderer() : 0; |
| |
| bool parentIsRegion = parentRenderer && !parentRenderer->canHaveChildren() && parentRenderer->isRenderNamedFlowFragmentContainer(); |
| bool parentIsNonRenderedInsideRegion = !parentRenderer && element.parentElement() && element.parentElement()->isInsideRegion(); |
| if (!parentIsRegion && !parentIsNonRenderedInsideRegion) |
| return false; |
| |
| if (!style) |
| style = element.styleForRenderer(); |
| |
| // Children of this element will only be allowed to be flowed into other flow-threads if display is NOT none. |
| if (element.rendererIsNeeded(*style)) |
| element.setIsInsideRegion(true); |
| |
| if (element.shouldMoveToFlowThread(*style)) |
| return true; |
| #else |
| UNUSED_PARAM(element); |
| UNUSED_PARAM(renderingParentNode); |
| UNUSED_PARAM(style); |
| #endif |
| return false; |
| } |
| |
| #if ENABLE(CSS_REGIONS) |
| static RenderNamedFlowThread* moveToFlowThreadIfNeeded(Element& element, const RenderStyle& style) |
| { |
| if (!element.shouldMoveToFlowThread(style)) |
| return 0; |
| FlowThreadController& flowThreadController = element.document().renderView()->flowThreadController(); |
| RenderNamedFlowThread& parentFlowRenderer = flowThreadController.ensureRenderFlowThreadWithName(style.flowThread()); |
| flowThreadController.registerNamedFlowContentElement(element, parentFlowRenderer); |
| return &parentFlowRenderer; |
| } |
| #endif |
| |
| static void createRendererIfNeeded(Element& element, PassRefPtr<RenderStyle> resolvedStyle) |
| { |
| ASSERT(!element.renderer()); |
| |
| ContainerNode* renderingParentNode = NodeRenderingTraversal::parent(&element); |
| |
| RefPtr<RenderStyle> style = resolvedStyle; |
| |
| element.setIsInsideRegion(false); |
| |
| if (!shouldCreateRenderer(element, renderingParentNode) && !elementInsideRegionNeedsRenderer(element, renderingParentNode, style)) |
| return; |
| |
| if (!style) |
| style = element.styleForRenderer(); |
| |
| RenderNamedFlowThread* parentFlowRenderer = 0; |
| #if ENABLE(CSS_REGIONS) |
| parentFlowRenderer = moveToFlowThreadIfNeeded(element, *style); |
| #endif |
| |
| if (!element.rendererIsNeeded(*style)) |
| return; |
| |
| RenderElement* parentRenderer; |
| RenderObject* nextRenderer; |
| if (parentFlowRenderer) { |
| parentRenderer = parentFlowRenderer; |
| nextRenderer = parentFlowRenderer->nextRendererForNode(&element); |
| } else { |
| // FIXME: Make this path Element only, handle the root special case separately. |
| parentRenderer = renderingParentNode->renderer(); |
| nextRenderer = nextSiblingRenderer(element, renderingParentNode); |
| } |
| |
| RenderElement* newRenderer = element.createRenderer(*style); |
| if (!newRenderer) |
| return; |
| if (!parentRenderer->isChildAllowed(*newRenderer, *style)) { |
| 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(parentRenderer->flowThreadState()); |
| |
| element.setRenderer(newRenderer); |
| newRenderer->setAnimatableStyle(style.releaseNonNull()); // setAnimatableStyle() can depend on renderer() already being set. |
| |
| #if ENABLE(FULLSCREEN_API) |
| Document& document = element.document(); |
| if (document.webkitIsFullScreen() && document.webkitCurrentFullScreenElement() == &element) { |
| newRenderer = RenderFullScreen::wrapRenderer(newRenderer, parentRenderer, document); |
| if (!newRenderer) |
| return; |
| } |
| #endif |
| // Note: Adding newRenderer instead of renderer(). renderer() may be a child of newRenderer. |
| parentRenderer->addChild(newRenderer, nextRenderer); |
| } |
| |
| static RenderObject* previousSiblingRenderer(const Text& textNode) |
| { |
| if (textNode.renderer()) |
| return textNode.renderer()->previousSibling(); |
| for (Node* sibling = NodeRenderingTraversal::previousSibling(&textNode); sibling; sibling = NodeRenderingTraversal::previousSibling(sibling)) { |
| RenderObject* renderer = sibling->renderer(); |
| if (renderer && !isRendererReparented(renderer)) |
| return renderer; |
| } |
| return 0; |
| } |
| |
| static RenderObject* nextSiblingRenderer(const Text& textNode) |
| { |
| if (textNode.renderer()) |
| return textNode.renderer()->nextSibling(); |
| for (Node* sibling = NodeRenderingTraversal::nextSibling(&textNode); sibling; sibling = NodeRenderingTraversal::nextSibling(sibling)) { |
| RenderObject* renderer = sibling->renderer(); |
| if (renderer && !isRendererReparented(renderer)) |
| return renderer; |
| } |
| return 0; |
| } |
| |
| static void reattachTextRenderersForWhitespaceOnlySiblingsAfterAttachIfNeeded(Node& current) |
| { |
| if (current.isInsertionPoint()) |
| return; |
| // 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 = NodeRenderingTraversal::nextSibling(¤t); sibling; sibling = NodeRenderingTraversal::nextSibling(sibling)) { |
| // Siblings haven't been attached yet. They will be handled normally when they are. |
| if (!sibling->attached()) |
| return; |
| if (sibling->isElementNode()) { |
| // Text renderers beyond rendered elements can't be affected. |
| if (!sibling->renderer() || isRendererReparented(sibling->renderer())) |
| continue; |
| return; |
| } |
| if (!sibling->isTextNode()) |
| continue; |
| Text& textSibling = *toText(sibling); |
| if (!textSibling.length() || !textSibling.containsOnlyWhitespace()) |
| return; |
| Text& whitespaceTextSibling = textSibling; |
| bool hadRenderer = whitespaceTextSibling.renderer(); |
| detachTextRenderer(whitespaceTextSibling); |
| attachTextRenderer(whitespaceTextSibling); |
| // No changes, futher renderers can't be affected. |
| if (hadRenderer == !!whitespaceTextSibling.renderer()) |
| return; |
| } |
| } |
| |
| static bool textRendererIsNeeded(const Text& textNode, const RenderObject& parentRenderer, const RenderStyle& style) |
| { |
| if (textNode.isEditingText()) |
| return true; |
| if (!textNode.length()) |
| return false; |
| if (style.display() == NONE) |
| 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 (style.preserveNewline()) // pre/pre-wrap/pre-line always make renderers. |
| return true; |
| |
| RenderObject* previousRenderer = 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 = toRenderElement(parentRenderer).firstChild(); |
| while (first && first->isFloatingOrOutOfFlowPositioned()) |
| first = first->nextSibling(); |
| RenderObject* nextRenderer = 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 createTextRendererIfNeeded(Text& textNode) |
| { |
| ASSERT(!textNode.renderer()); |
| |
| ContainerNode* renderingParentNode = NodeRenderingTraversal::parent(&textNode); |
| if (!renderingParentNode) |
| return; |
| RenderElement* parentRenderer = renderingParentNode->renderer(); |
| if (!parentRenderer || !parentRenderer->canHaveChildren()) |
| return; |
| if (!renderingParentNode->childShouldCreateRenderer(&textNode)) |
| return; |
| |
| RefPtr<RenderStyle> style = parentRenderer->style(); |
| |
| if (!textRendererIsNeeded(textNode, *parentRenderer, *style)) |
| return; |
| RenderText* newRenderer = textNode.createTextRenderer(*style); |
| if (!newRenderer) |
| return; |
| if (!parentRenderer->isChildAllowed(*newRenderer, *style)) { |
| 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(parentRenderer->flowThreadState()); |
| |
| RenderObject* nextRenderer = nextSiblingRenderer(textNode); |
| textNode.setRenderer(newRenderer); |
| // Parent takes care of the animations, no need to call setAnimatableStyle. |
| parentRenderer->addChild(newRenderer, nextRenderer); |
| } |
| |
| void attachTextRenderer(Text& textNode) |
| { |
| createTextRendererIfNeeded(textNode); |
| |
| textNode.setAttached(true); |
| textNode.clearNeedsStyleRecalc(); |
| } |
| |
| void detachTextRenderer(Text& textNode) |
| { |
| if (textNode.renderer()) |
| textNode.renderer()->destroyAndCleanupAnonymousWrappers(); |
| textNode.setRenderer(0); |
| textNode.setAttached(false); |
| } |
| |
| void updateTextRendererAfterContentChange(Text& textNode, unsigned offsetOfReplacedData, unsigned lengthOfReplacedData) |
| { |
| if (!textNode.attached()) |
| return; |
| RenderText* textRenderer = textNode.renderer(); |
| if (!textRenderer) { |
| attachTextRenderer(textNode); |
| reattachTextRenderersForWhitespaceOnlySiblingsAfterAttachIfNeeded(textNode); |
| return; |
| } |
| RenderObject* parentRenderer = NodeRenderingTraversal::parent(&textNode)->renderer(); |
| if (!textRendererIsNeeded(textNode, *parentRenderer, *textRenderer->style())) { |
| detachTextRenderer(textNode); |
| attachTextRenderer(textNode); |
| reattachTextRenderersForWhitespaceOnlySiblingsAfterAttachIfNeeded(textNode); |
| return; |
| } |
| textRenderer->setTextWithOffset(textNode.dataImpl(), offsetOfReplacedData, lengthOfReplacedData); |
| } |
| |
| static void attachChildren(ContainerNode& current) |
| { |
| for (Node* child = current.firstChild(); child; child = child->nextSibling()) { |
| ASSERT(!child->attached() || current.shadowRoot()); |
| if (child->attached()) |
| continue; |
| if (child->isTextNode()) { |
| attachTextRenderer(*toText(child)); |
| continue; |
| } |
| if (child->isElementNode()) |
| attachRenderTree(*toElement(child), nullptr); |
| } |
| } |
| |
| static void attachShadowRoot(ShadowRoot& shadowRoot) |
| { |
| if (shadowRoot.attached()) |
| return; |
| StyleResolver& styleResolver = shadowRoot.document().ensureStyleResolver(); |
| styleResolver.pushParentShadowRoot(&shadowRoot); |
| |
| attachChildren(shadowRoot); |
| |
| styleResolver.popParentShadowRoot(&shadowRoot); |
| |
| shadowRoot.clearNeedsStyleRecalc(); |
| shadowRoot.setAttached(true); |
| } |
| |
| static void attachRenderTree(Element& current, PassRefPtr<RenderStyle> resolvedStyle) |
| { |
| PostAttachCallbackDisabler callbackDisabler(current); |
| WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; |
| |
| if (current.hasCustomStyleResolveCallbacks()) |
| current.willAttachRenderers(); |
| |
| createRendererIfNeeded(current, resolvedStyle); |
| |
| if (current.parentElement() && current.parentElement()->isInCanvasSubtree()) |
| current.setIsInCanvasSubtree(true); |
| |
| current.updateBeforePseudoElement(NoChange); |
| |
| StyleResolverParentPusher parentPusher(¤t); |
| |
| // When a shadow root exists, it does the work of attaching the children. |
| if (ShadowRoot* shadowRoot = current.shadowRoot()) { |
| parentPusher.push(); |
| attachShadowRoot(*shadowRoot); |
| } else if (current.firstChild()) |
| parentPusher.push(); |
| |
| attachChildren(current); |
| |
| current.setAttached(true); |
| current.clearNeedsStyleRecalc(); |
| |
| if (AXObjectCache* cache = current.document().axObjectCache()) |
| cache->updateCacheAfterNodeIsAttached(¤t); |
| |
| current.updateAfterPseudoElement(NoChange); |
| |
| current.updateFocusAppearanceAfterAttachIfNeeded(); |
| |
| if (current.hasCustomStyleResolveCallbacks()) |
| current.didAttachRenderers(); |
| } |
| |
| static void detachChildren(ContainerNode& current, DetachType detachType) |
| { |
| for (Node* child = current.firstChild(); child; child = child->nextSibling()) { |
| if (child->isTextNode()) { |
| Style::detachTextRenderer(*toText(child)); |
| continue; |
| } |
| if (child->isElementNode()) |
| detachRenderTree(*toElement(child), detachType); |
| } |
| current.clearChildNeedsStyleRecalc(); |
| } |
| |
| static void detachShadowRoot(ShadowRoot& shadowRoot, DetachType detachType) |
| { |
| if (!shadowRoot.attached()) |
| return; |
| detachChildren(shadowRoot, detachType); |
| |
| shadowRoot.setAttached(false); |
| } |
| |
| static void detachRenderTree(Element& current, DetachType detachType) |
| { |
| WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; |
| |
| if (current.hasCustomStyleResolveCallbacks()) |
| current.willDetachRenderers(); |
| |
| current.clearStyleDerivedDataBeforeDetachingRenderer(); |
| |
| // Do not remove the element's hovered and active status |
| // if performing a reattach. |
| if (detachType != ReattachDetach) |
| current.clearHoverAndActiveStatusBeforeDetachingRenderer(); |
| |
| if (ShadowRoot* shadowRoot = current.shadowRoot()) |
| detachShadowRoot(*shadowRoot, detachType); |
| |
| detachChildren(current, detachType); |
| |
| if (current.renderer()) |
| current.renderer()->destroyAndCleanupAnonymousWrappers(); |
| current.setRenderer(0); |
| |
| current.setAttached(false); |
| |
| if (current.hasCustomStyleResolveCallbacks()) |
| current.didDetachRenderers(); |
| } |
| |
| static bool pseudoStyleCacheIsInvalid(RenderElement* renderer, RenderStyle* newStyle) |
| { |
| const RenderStyle* currentStyle = renderer->style(); |
| |
| const PseudoStyleCache* pseudoStyleCache = currentStyle->cachedPseudoStyles(); |
| if (!pseudoStyleCache) |
| return false; |
| |
| size_t cacheSize = pseudoStyleCache->size(); |
| for (size_t i = 0; i < cacheSize; ++i) { |
| RefPtr<RenderStyle> newPseudoStyle; |
| PseudoId pseudoId = pseudoStyleCache->at(i)->styleType(); |
| if (pseudoId == FIRST_LINE || pseudoId == FIRST_LINE_INHERITED) |
| newPseudoStyle = renderer->uncachedFirstLineStyle(newStyle); |
| else |
| newPseudoStyle = renderer->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId), newStyle, newStyle); |
| if (!newPseudoStyle) |
| return true; |
| if (*newPseudoStyle != *pseudoStyleCache->at(i)) { |
| if (pseudoId < FIRST_INTERNAL_PSEUDOID) |
| newStyle->setHasPseudoStyle(pseudoId); |
| newStyle->addCachedPseudoStyle(newPseudoStyle); |
| if (pseudoId == FIRST_LINE || pseudoId == FIRST_LINE_INHERITED) { |
| // FIXME: We should do an actual diff to determine whether a repaint vs. layout |
| // is needed, but for now just assume a layout will be required. The diff code |
| // in RenderObject::setStyle would need to be factored out so that it could be reused. |
| renderer->setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static Change resolveLocal(Element& current, Change inheritedChange) |
| { |
| Change localChange = Detach; |
| RefPtr<RenderStyle> newStyle; |
| RefPtr<RenderStyle> currentStyle = current.renderStyle(); |
| |
| Document& document = current.document(); |
| if (currentStyle) { |
| newStyle = current.styleForRenderer(); |
| localChange = determineChange(currentStyle.get(), newStyle.get(), document.settings()); |
| } |
| if (localChange == Detach) { |
| if (current.attached()) |
| detachRenderTree(current, ReattachDetach); |
| attachRenderTree(current, newStyle.release()); |
| reattachTextRenderersForWhitespaceOnlySiblingsAfterAttachIfNeeded(current); |
| |
| return Detach; |
| } |
| |
| if (RenderElement* renderer = current.renderer()) { |
| if (localChange != NoChange || pseudoStyleCacheIsInvalid(renderer, newStyle.get()) || (inheritedChange == Force && renderer->requiresForcedStyleRecalcPropagation()) || current.styleChangeType() == SyntheticStyleChange) |
| renderer->setAnimatableStyle(*newStyle.get()); |
| else if (current.needsStyleRecalc()) { |
| // Although no change occurred, we use the new style so that the cousin style sharing code won't get |
| // fooled into believing this style is the same. |
| renderer->setStyleInternal(newStyle.get()); |
| } |
| } |
| |
| // If "rem" units are used anywhere in the document, and if the document element's font size changes, then go ahead and force font updating |
| // all the way down the tree. This is simpler than having to maintain a cache of objects (and such font size changes should be rare anyway). |
| if (document.styleSheetCollection().usesRemUnits() && document.documentElement() == ¤t && localChange != NoChange && currentStyle && newStyle && currentStyle->fontSize() != newStyle->fontSize()) { |
| // Cached RenderStyles may depend on the re units. |
| if (StyleResolver* styleResolver = document.styleResolverIfExists()) |
| styleResolver->invalidateMatchedPropertiesCache(); |
| return Force; |
| } |
| if (inheritedChange == Force) |
| return Force; |
| if (current.styleChangeType() >= FullStyleChange) |
| return Force; |
| |
| return localChange; |
| } |
| |
| static void updateTextStyle(Text& text) |
| { |
| RenderText* renderer = text.renderer(); |
| |
| if (!text.needsStyleRecalc()) |
| return; |
| if (renderer) |
| renderer->setText(text.dataImpl()); |
| else { |
| attachTextRenderer(text); |
| reattachTextRenderersForWhitespaceOnlySiblingsAfterAttachIfNeeded(text); |
| } |
| text.clearNeedsStyleRecalc(); |
| } |
| |
| static void resolveShadowTree(ShadowRoot* shadowRoot, Style::Change change) |
| { |
| if (!shadowRoot) |
| return; |
| StyleResolver& styleResolver = shadowRoot->document().ensureStyleResolver(); |
| styleResolver.pushParentShadowRoot(shadowRoot); |
| |
| for (Node* child = shadowRoot->firstChild(); child; child = child->nextSibling()) { |
| if (child->isTextNode()) { |
| // Current user agent ShadowRoots don't have immediate text children so this branch is never actually taken. |
| updateTextStyle(*toText(child)); |
| continue; |
| } |
| resolveTree(*toElement(child), change); |
| } |
| |
| styleResolver.popParentShadowRoot(shadowRoot); |
| shadowRoot->clearNeedsStyleRecalc(); |
| shadowRoot->clearChildNeedsStyleRecalc(); |
| } |
| |
| #if PLATFORM(IOS) |
| static EVisibility elementImplicitVisibility(const Element* element) |
| { |
| RenderObject* renderer = element->renderer(); |
| if (!renderer) |
| return VISIBLE; |
| |
| RenderStyle* style = renderer->style(); |
| if (!style) |
| return VISIBLE; |
| |
| Length width(style->width()); |
| Length height(style->height()); |
| if ((width.isFixed() && width.value() <= 0) || (height.isFixed() && height.value() <= 0)) |
| return HIDDEN; |
| |
| Length top(style->top()); |
| Length 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; |
| } |
| |
| class CheckForVisibilityChangeOnRecalcStyle { |
| public: |
| CheckForVisibilityChangeOnRecalcStyle(Element* element, RenderStyle* currentStyle) |
| : m_element(element) |
| , m_previousDisplay(currentStyle ? currentStyle->display() : NONE) |
| , m_previousVisibility(currentStyle ? currentStyle->visibility() : HIDDEN) |
| , m_previousImplicitVisibility(WKObservingContentChanges() && WKContentChange() != WKContentVisibilityChange ? elementImplicitVisibility(element) : VISIBLE) |
| { |
| } |
| ~CheckForVisibilityChangeOnRecalcStyle() |
| { |
| if (!WKObservingContentChanges()) |
| return; |
| RenderStyle* 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.get()) == VISIBLE)) |
| WKSetObservedContentChange(WKContentVisibilityChange); |
| } |
| private: |
| RefPtr<Element> m_element; |
| EDisplay m_previousDisplay; |
| EVisibility m_previousVisibility; |
| EVisibility m_previousImplicitVisibility; |
| }; |
| #endif // PLATFORM(IOS) |
| |
| void resolveTree(Element& current, Change change) |
| { |
| ASSERT(change != Detach); |
| |
| if (current.hasCustomStyleResolveCallbacks()) { |
| if (!current.willRecalcStyle(change)) |
| return; |
| } |
| |
| ContainerNode* renderingParentNode = NodeRenderingTraversal::parent(¤t); |
| bool hasParentStyle = renderingParentNode && renderingParentNode->renderStyle(); |
| bool hasDirectAdjacentRules = current.childrenAffectedByDirectAdjacentRules(); |
| bool hasIndirectAdjacentRules = current.childrenAffectedByForwardPositionalRules(); |
| |
| #if PLATFORM(IOS) |
| CheckForVisibilityChangeOnRecalcStyle checkForVisibilityChange(¤t, current.renderStyle()); |
| #endif |
| |
| if (change > NoChange || current.needsStyleRecalc()) |
| current.resetComputedStyle(); |
| |
| if (hasParentStyle && (change >= Inherit || current.needsStyleRecalc())) |
| change = resolveLocal(current, change); |
| |
| if (change != Detach) { |
| StyleResolverParentPusher parentPusher(¤t); |
| |
| if (ShadowRoot* shadowRoot = current.shadowRoot()) { |
| if (change >= Inherit || shadowRoot->childNeedsStyleRecalc() || shadowRoot->needsStyleRecalc()) { |
| parentPusher.push(); |
| resolveShadowTree(shadowRoot, change); |
| } |
| } |
| |
| current.updateBeforePseudoElement(change); |
| |
| // FIXME: This check is good enough for :hover + foo, but it is not good enough for :hover + foo + bar. |
| // For now we will just worry about the common case, since it's a lot trickier to get the second case right |
| // without doing way too much re-resolution. |
| bool forceCheckOfNextElementSibling = false; |
| bool forceCheckOfAnyElementSibling = false; |
| for (Node* child = current.firstChild(); child; child = child->nextSibling()) { |
| if (child->isTextNode()) { |
| updateTextStyle(*toText(child)); |
| continue; |
| } |
| if (!child->isElementNode()) |
| continue; |
| Element* childElement = toElement(child); |
| bool childRulesChanged = childElement->needsStyleRecalc() && childElement->styleChangeType() == FullStyleChange; |
| if ((forceCheckOfNextElementSibling || forceCheckOfAnyElementSibling)) |
| childElement->setNeedsStyleRecalc(); |
| if (change >= Inherit || childElement->childNeedsStyleRecalc() || childElement->needsStyleRecalc()) { |
| parentPusher.push(); |
| resolveTree(*childElement, change); |
| } |
| forceCheckOfNextElementSibling = childRulesChanged && hasDirectAdjacentRules; |
| forceCheckOfAnyElementSibling = forceCheckOfAnyElementSibling || (childRulesChanged && hasIndirectAdjacentRules); |
| } |
| |
| current.updateAfterPseudoElement(change); |
| } |
| |
| current.clearNeedsStyleRecalc(); |
| current.clearChildNeedsStyleRecalc(); |
| |
| if (current.hasCustomStyleResolveCallbacks()) |
| current.didRecalcStyle(change); |
| } |
| |
| void resolveTree(Document& document, Change change) |
| { |
| bool resolveRootStyle = change == Force || (document.shouldDisplaySeamlesslyWithParent() && change >= Inherit); |
| if (resolveRootStyle) { |
| auto documentStyle = resolveForDocument(document); |
| |
| // Inserting the pictograph font at the end of the font fallback list is done by the |
| // font selector, so set a font selector if needed. |
| if (Settings* settings = document.settings()) { |
| StyleResolver* styleResolver = document.styleResolverIfExists(); |
| if (settings->fontFallbackPrefersPictographs() && styleResolver) |
| documentStyle.get().font().update(styleResolver->fontSelector()); |
| } |
| |
| Style::Change documentChange = determineChange(&documentStyle.get(), document.renderView()->style(), document.settings()); |
| if (documentChange != NoChange) |
| document.renderView()->setStyle(std::move(documentStyle)); |
| else |
| documentStyle.dropRef(); |
| } |
| |
| Element* documentElement = document.documentElement(); |
| if (!documentElement) |
| return; |
| if (change < Inherit && !documentElement->childNeedsStyleRecalc() && !documentElement->needsStyleRecalc()) |
| return; |
| resolveTree(*documentElement, change); |
| } |
| |
| void attachRenderTree(Element& element) |
| { |
| attachRenderTree(element, nullptr); |
| reattachTextRenderersForWhitespaceOnlySiblingsAfterAttachIfNeeded(element); |
| } |
| |
| void detachRenderTree(Element& element) |
| { |
| detachRenderTree(element, NormalDetach); |
| } |
| |
| void detachRenderTreeInReattachMode(Element& element) |
| { |
| detachRenderTree(element, ReattachDetach); |
| } |
| |
| void reattachRenderTree(Element& current) |
| { |
| if (current.attached()) |
| detachRenderTree(current, ReattachDetach); |
| attachRenderTree(current); |
| } |
| |
| } |
| } |