| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) |
| * (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com) |
| * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2013 Apple Inc. All rights reserved. |
| * Copyright (C) 2010, 2012 Google Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "RenderElement.h" |
| |
| #include "AXObjectCache.h" |
| #include "ContentData.h" |
| #include "ControlStates.h" |
| #include "CursorList.h" |
| #include "EventHandler.h" |
| #include "Frame.h" |
| #include "FrameSelection.h" |
| #include "HTMLElement.h" |
| #include "HTMLNames.h" |
| #include "FlowThreadController.h" |
| #include "RenderCounter.h" |
| #include "RenderDeprecatedFlexibleBox.h" |
| #include "RenderFlexibleBox.h" |
| #include "RenderImage.h" |
| #include "RenderImageResourceStyleImage.h" |
| #include "RenderIterator.h" |
| #include "RenderLayer.h" |
| #include "RenderLayerCompositor.h" |
| #include "RenderLineBreak.h" |
| #include "RenderListItem.h" |
| #include "RenderNamedFlowThread.h" |
| #include "RenderRegion.h" |
| #include "RenderRuby.h" |
| #include "RenderRubyText.h" |
| #include "RenderTableCaption.h" |
| #include "RenderTableCell.h" |
| #include "RenderTableCol.h" |
| #include "RenderTableRow.h" |
| #include "RenderText.h" |
| #include "RenderTheme.h" |
| #include "RenderView.h" |
| #include "SVGRenderSupport.h" |
| #include "Settings.h" |
| #include "StyleResolver.h" |
| #include <wtf/MathExtras.h> |
| #include <wtf/StackStats.h> |
| |
| #if ENABLE(CSS_GRID_LAYOUT) |
| #include "RenderGrid.h" |
| #endif |
| |
| namespace WebCore { |
| |
| bool RenderElement::s_affectsParentBlock = false; |
| bool RenderElement::s_noLongerAffectsParentBlock = false; |
| |
| static HashMap<const RenderObject*, ControlStates*>& controlStatesRendererMap() |
| { |
| static NeverDestroyed<HashMap<const RenderObject*, ControlStates*>> map; |
| return map; |
| } |
| |
| inline RenderElement::RenderElement(ContainerNode& elementOrDocument, PassRef<RenderStyle> style, unsigned baseTypeFlags) |
| : RenderObject(elementOrDocument) |
| , m_baseTypeFlags(baseTypeFlags) |
| , m_ancestorLineBoxDirty(false) |
| , m_hasInitializedStyle(false) |
| , m_renderInlineAlwaysCreatesLineBoxes(false) |
| , m_renderBoxNeedsLazyRepaint(false) |
| , m_hasPausedImageAnimations(false) |
| , m_hasCounterNodeMap(false) |
| , m_firstChild(nullptr) |
| , m_lastChild(nullptr) |
| , m_style(WTF::move(style)) |
| { |
| } |
| |
| RenderElement::RenderElement(Element& element, PassRef<RenderStyle> style, unsigned baseTypeFlags) |
| : RenderElement(static_cast<ContainerNode&>(element), WTF::move(style), baseTypeFlags) |
| { |
| } |
| |
| RenderElement::RenderElement(Document& document, PassRef<RenderStyle> style, unsigned baseTypeFlags) |
| : RenderElement(static_cast<ContainerNode&>(document), WTF::move(style), baseTypeFlags) |
| { |
| } |
| |
| RenderElement::~RenderElement() |
| { |
| if (hasInitializedStyle()) { |
| for (const FillLayer* bgLayer = m_style->backgroundLayers(); bgLayer; bgLayer = bgLayer->next()) { |
| if (StyleImage* backgroundImage = bgLayer->image()) |
| backgroundImage->removeClient(this); |
| } |
| |
| for (const FillLayer* maskLayer = m_style->maskLayers(); maskLayer; maskLayer = maskLayer->next()) { |
| if (StyleImage* maskImage = maskLayer->image()) |
| maskImage->removeClient(this); |
| } |
| |
| if (StyleImage* borderImage = m_style->borderImage().image()) |
| borderImage->removeClient(this); |
| |
| if (StyleImage* maskBoxImage = m_style->maskBoxImage().image()) |
| maskBoxImage->removeClient(this); |
| |
| #if ENABLE(CSS_SHAPES) |
| if (auto shapeValue = m_style->shapeOutside()) { |
| if (auto shapeImage = shapeValue->image()) |
| shapeImage->removeClient(this); |
| } |
| #endif |
| } |
| if (m_hasPausedImageAnimations) |
| view().removeRendererWithPausedImageAnimations(*this); |
| } |
| |
| RenderPtr<RenderElement> RenderElement::createFor(Element& element, PassRef<RenderStyle> style) |
| { |
| // Minimal support for content properties replacing an entire element. |
| // Works only if we have exactly one piece of content and it's a URL. |
| // Otherwise acts as if we didn't support this feature. |
| const ContentData* contentData = style.get().contentData(); |
| if (contentData && !contentData->next() && contentData->isImage() && !element.isPseudoElement()) { |
| auto& styleImage = toImageContentData(contentData)->image(); |
| auto image = createRenderer<RenderImage>(element, WTF::move(style), const_cast<StyleImage*>(&styleImage)); |
| image->setIsGeneratedContent(); |
| return WTF::move(image); |
| } |
| |
| if (element.hasTagName(HTMLNames::rubyTag)) { |
| if (style.get().display() == INLINE) |
| return createRenderer<RenderRubyAsInline>(element, WTF::move(style)); |
| if (style.get().display() == BLOCK) |
| return createRenderer<RenderRubyAsBlock>(element, WTF::move(style)); |
| } |
| // treat <rt> as ruby text ONLY if it still has its default treatment of block |
| if (element.hasTagName(HTMLNames::rtTag) && style.get().display() == BLOCK) |
| return createRenderer<RenderRubyText>(element, WTF::move(style)); |
| switch (style.get().display()) { |
| case NONE: |
| style.dropRef(); |
| return nullptr; |
| case INLINE: |
| return createRenderer<RenderInline>(element, WTF::move(style)); |
| case BLOCK: |
| case INLINE_BLOCK: |
| case COMPACT: |
| return createRenderer<RenderBlockFlow>(element, WTF::move(style)); |
| case LIST_ITEM: |
| return createRenderer<RenderListItem>(element, WTF::move(style)); |
| case TABLE: |
| case INLINE_TABLE: |
| return createRenderer<RenderTable>(element, WTF::move(style)); |
| case TABLE_ROW_GROUP: |
| case TABLE_HEADER_GROUP: |
| case TABLE_FOOTER_GROUP: |
| return createRenderer<RenderTableSection>(element, WTF::move(style)); |
| case TABLE_ROW: |
| return createRenderer<RenderTableRow>(element, WTF::move(style)); |
| case TABLE_COLUMN_GROUP: |
| case TABLE_COLUMN: |
| return createRenderer<RenderTableCol>(element, WTF::move(style)); |
| case TABLE_CELL: |
| return createRenderer<RenderTableCell>(element, WTF::move(style)); |
| case TABLE_CAPTION: |
| return createRenderer<RenderTableCaption>(element, WTF::move(style)); |
| case BOX: |
| case INLINE_BOX: |
| return createRenderer<RenderDeprecatedFlexibleBox>(element, WTF::move(style)); |
| case FLEX: |
| case INLINE_FLEX: |
| case WEBKIT_FLEX: |
| case WEBKIT_INLINE_FLEX: |
| return createRenderer<RenderFlexibleBox>(element, WTF::move(style)); |
| #if ENABLE(CSS_GRID_LAYOUT) |
| case GRID: |
| case INLINE_GRID: |
| return createRenderer<RenderGrid>(element, WTF::move(style)); |
| #endif |
| } |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| enum StyleCacheState { |
| Cached, |
| Uncached |
| }; |
| |
| static PassRefPtr<RenderStyle> firstLineStyleForCachedUncachedType(StyleCacheState type, const RenderElement& renderer, RenderStyle* style) |
| { |
| RenderElement& rendererForFirstLineStyle = renderer.isBeforeOrAfterContent() ? *renderer.parent() : const_cast<RenderElement&>(renderer); |
| |
| if (rendererForFirstLineStyle.isRenderBlockFlow() || rendererForFirstLineStyle.isRenderButton()) { |
| if (RenderBlock* firstLineBlock = rendererForFirstLineStyle.firstLineBlock()) { |
| if (type == Cached) |
| return firstLineBlock->getCachedPseudoStyle(FIRST_LINE, style); |
| return firstLineBlock->getUncachedPseudoStyle(PseudoStyleRequest(FIRST_LINE), style, firstLineBlock == &renderer ? style : nullptr); |
| } |
| } else if (!rendererForFirstLineStyle.isAnonymous() && rendererForFirstLineStyle.isRenderInline()) { |
| RenderStyle& parentStyle = rendererForFirstLineStyle.parent()->firstLineStyle(); |
| if (&parentStyle != &rendererForFirstLineStyle.parent()->style()) { |
| if (type == Cached) { |
| // A first-line style is in effect. Cache a first-line style for ourselves. |
| rendererForFirstLineStyle.style().setHasPseudoStyle(FIRST_LINE_INHERITED); |
| return rendererForFirstLineStyle.getCachedPseudoStyle(FIRST_LINE_INHERITED, &parentStyle); |
| } |
| return rendererForFirstLineStyle.getUncachedPseudoStyle(PseudoStyleRequest(FIRST_LINE_INHERITED), &parentStyle, style); |
| } |
| } |
| return nullptr; |
| } |
| |
| PassRefPtr<RenderStyle> RenderElement::uncachedFirstLineStyle(RenderStyle* style) const |
| { |
| if (!document().styleSheetCollection().usesFirstLineRules()) |
| return nullptr; |
| |
| return firstLineStyleForCachedUncachedType(Uncached, *this, style); |
| } |
| |
| RenderStyle* RenderElement::cachedFirstLineStyle() const |
| { |
| ASSERT(document().styleSheetCollection().usesFirstLineRules()); |
| |
| RenderStyle& style = this->style(); |
| if (RefPtr<RenderStyle> firstLineStyle = firstLineStyleForCachedUncachedType(Cached, *this, &style)) |
| return firstLineStyle.get(); |
| |
| return &style; |
| } |
| |
| StyleDifference RenderElement::adjustStyleDifference(StyleDifference diff, unsigned contextSensitiveProperties) const |
| { |
| // If transform changed, and we are not composited, need to do a layout. |
| if (contextSensitiveProperties & ContextSensitivePropertyTransform) { |
| // Text nodes share style with their parents but transforms don't apply to them, |
| // hence the !isText() check. |
| // FIXME: when transforms are taken into account for overflow, we will need to do a layout. |
| if (!hasLayer() || !toRenderLayerModelObject(this)->layer()->isComposited()) { |
| // We need to set at least SimplifiedLayout, but if PositionedMovementOnly is already set |
| // then we actually need SimplifiedLayoutAndPositionedMovement. |
| if (!hasLayer()) |
| diff = StyleDifferenceLayout; // FIXME: Do this for now since SimplifiedLayout cannot handle updating floating objects lists. |
| else if (diff < StyleDifferenceLayoutPositionedMovementOnly) |
| diff = StyleDifferenceSimplifiedLayout; |
| else if (diff < StyleDifferenceSimplifiedLayout) |
| diff = StyleDifferenceSimplifiedLayoutAndPositionedMovement; |
| } else if (diff < StyleDifferenceRecompositeLayer) |
| diff = StyleDifferenceRecompositeLayer; |
| } |
| |
| // If opacity changed, and we are not composited, need to repaint (also |
| // ignoring text nodes) |
| if (contextSensitiveProperties & ContextSensitivePropertyOpacity) { |
| if (!hasLayer() || !toRenderLayerModelObject(this)->layer()->isComposited()) |
| diff = StyleDifferenceRepaintLayer; |
| else if (diff < StyleDifferenceRecompositeLayer) |
| diff = StyleDifferenceRecompositeLayer; |
| } |
| |
| if ((contextSensitiveProperties & ContextSensitivePropertyFilter) && hasLayer()) { |
| RenderLayer* layer = toRenderLayerModelObject(this)->layer(); |
| if (!layer->isComposited() || layer->paintsWithFilters()) |
| diff = StyleDifferenceRepaintLayer; |
| else if (diff < StyleDifferenceRecompositeLayer) |
| diff = StyleDifferenceRecompositeLayer; |
| } |
| |
| // The answer to requiresLayer() for plugins, iframes, and canvas can change without the actual |
| // style changing, since it depends on whether we decide to composite these elements. When the |
| // layer status of one of these elements changes, we need to force a layout. |
| if (diff == StyleDifferenceEqual && isRenderLayerModelObject()) { |
| if (hasLayer() != toRenderLayerModelObject(this)->requiresLayer()) |
| diff = StyleDifferenceLayout; |
| } |
| |
| // If we have no layer(), just treat a RepaintLayer hint as a normal Repaint. |
| if (diff == StyleDifferenceRepaintLayer && !hasLayer()) |
| diff = StyleDifferenceRepaint; |
| |
| return diff; |
| } |
| |
| inline bool RenderElement::hasImmediateNonWhitespaceTextChildOrBorderOrOutline() const |
| { |
| for (auto& child : childrenOfType<RenderObject>(*this)) { |
| if (is<RenderText>(child) && !downcast<RenderText>(child).isAllCollapsibleWhitespace()) |
| return true; |
| if (child.style().hasOutline() || child.style().hasBorder()) |
| return true; |
| } |
| return false; |
| } |
| |
| inline bool RenderElement::shouldRepaintForStyleDifference(StyleDifference diff) const |
| { |
| return diff == StyleDifferenceRepaint || (diff == StyleDifferenceRepaintIfTextOrBorderOrOutline && hasImmediateNonWhitespaceTextChildOrBorderOrOutline()); |
| } |
| |
| void RenderElement::updateFillImages(const FillLayer* oldLayers, const FillLayer* newLayers) |
| { |
| // Optimize the common case |
| if (oldLayers && !oldLayers->next() && newLayers && !newLayers->next() && (oldLayers->image() == newLayers->image())) |
| return; |
| |
| // Go through the new layers and addClients first, to avoid removing all clients of an image. |
| for (const FillLayer* currNew = newLayers; currNew; currNew = currNew->next()) { |
| if (currNew->image()) |
| currNew->image()->addClient(this); |
| } |
| |
| for (const FillLayer* currOld = oldLayers; currOld; currOld = currOld->next()) { |
| if (currOld->image()) |
| currOld->image()->removeClient(this); |
| } |
| } |
| |
| void RenderElement::updateImage(StyleImage* oldImage, StyleImage* newImage) |
| { |
| if (oldImage == newImage) |
| return; |
| if (oldImage) |
| oldImage->removeClient(this); |
| if (newImage) |
| newImage->addClient(this); |
| } |
| |
| #if ENABLE(CSS_SHAPES) |
| void RenderElement::updateShapeImage(const ShapeValue* oldShapeValue, const ShapeValue* newShapeValue) |
| { |
| if (oldShapeValue || newShapeValue) |
| updateImage(oldShapeValue ? oldShapeValue->image() : nullptr, newShapeValue ? newShapeValue->image() : nullptr); |
| } |
| #endif |
| |
| void RenderElement::initializeStyle() |
| { |
| styleWillChange(StyleDifferenceEqual, style()); |
| |
| m_hasInitializedStyle = true; |
| |
| updateFillImages(nullptr, m_style->backgroundLayers()); |
| updateFillImages(nullptr, m_style->maskLayers()); |
| |
| updateImage(nullptr, m_style->borderImage().image()); |
| updateImage(nullptr, m_style->maskBoxImage().image()); |
| |
| #if ENABLE(CSS_SHAPES) |
| updateShapeImage(nullptr, m_style->shapeOutside()); |
| #endif |
| |
| // We need to ensure that view->maximalOutlineSize() is valid for any repaints that happen |
| // during styleDidChange (it's used by clippedOverflowRectForRepaint()). |
| if (m_style->outlineWidth() > 0 && m_style->outlineSize() > maximalOutlineSize(PaintPhaseOutline)) |
| view().setMaximalOutlineSize(std::max(theme().platformFocusRingMaxWidth(), static_cast<int>(m_style->outlineSize()))); |
| |
| styleDidChange(StyleDifferenceEqual, nullptr); |
| |
| // We shouldn't have any text children that would need styleDidChange at this point. |
| ASSERT(!childrenOfType<RenderText>(*this).first()); |
| |
| // It would be nice to assert that !parent() here, but some RenderLayer subrenderers |
| // have their parent set before getting a call to initializeStyle() :| |
| } |
| |
| void RenderElement::setStyle(PassRef<RenderStyle> style) |
| { |
| // FIXME: Should change RenderView so it can use initializeStyle too. |
| // If we do that, we can assert m_hasInitializedStyle unconditionally, |
| // and remove the check of m_hasInitializedStyle below too. |
| ASSERT(m_hasInitializedStyle || isRenderView()); |
| |
| if (&m_style.get() == &style.get()) { |
| // FIXME: Can we change things so we never hit this code path? |
| // We need to run through adjustStyleDifference() for iframes, plugins, and canvas so |
| // style sharing is disabled for them. That should ensure that we never hit this code path. |
| ASSERT(!isRenderIFrame()); |
| ASSERT(!isEmbeddedObject()); |
| ASSERT(!isCanvas()); |
| style.dropRef(); |
| return; |
| } |
| |
| StyleDifference diff = StyleDifferenceEqual; |
| unsigned contextSensitiveProperties = ContextSensitivePropertyNone; |
| if (m_hasInitializedStyle) |
| diff = m_style->diff(&style.get(), contextSensitiveProperties); |
| |
| diff = adjustStyleDifference(diff, contextSensitiveProperties); |
| |
| styleWillChange(diff, style.get()); |
| |
| Ref<RenderStyle> oldStyle(m_style.replace(WTF::move(style))); |
| |
| updateFillImages(oldStyle.get().backgroundLayers(), m_style->backgroundLayers()); |
| updateFillImages(oldStyle.get().maskLayers(), m_style->maskLayers()); |
| |
| updateImage(oldStyle.get().borderImage().image(), m_style->borderImage().image()); |
| updateImage(oldStyle.get().maskBoxImage().image(), m_style->maskBoxImage().image()); |
| |
| #if ENABLE(CSS_SHAPES) |
| updateShapeImage(oldStyle.get().shapeOutside(), m_style->shapeOutside()); |
| #endif |
| |
| // We need to ensure that view->maximalOutlineSize() is valid for any repaints that happen |
| // during styleDidChange (it's used by clippedOverflowRectForRepaint()). |
| if (m_style->outlineWidth() > 0 && m_style->outlineSize() > maximalOutlineSize(PaintPhaseOutline)) |
| view().setMaximalOutlineSize(std::max(theme().platformFocusRingMaxWidth(), static_cast<int>(m_style->outlineSize()))); |
| |
| bool doesNotNeedLayout = !parent(); |
| |
| styleDidChange(diff, &oldStyle.get()); |
| |
| // Text renderers use their parent style. Notify them about the change. |
| for (auto& child : childrenOfType<RenderText>(*this)) |
| child.styleDidChange(diff, &oldStyle.get()); |
| |
| // FIXME: |this| might be destroyed here. This can currently happen for a RenderTextFragment when |
| // its first-letter block gets an update in RenderTextFragment::styleDidChange. For RenderTextFragment(s), |
| // we will safely bail out with the doesNotNeedLayout flag. We might want to broaden this condition |
| // in the future as we move renderer changes out of layout and into style changes. |
| if (doesNotNeedLayout) |
| return; |
| |
| // Now that the layer (if any) has been updated, we need to adjust the diff again, |
| // check whether we should layout now, and decide if we need to repaint. |
| StyleDifference updatedDiff = adjustStyleDifference(diff, contextSensitiveProperties); |
| |
| if (diff <= StyleDifferenceLayoutPositionedMovementOnly) { |
| if (updatedDiff == StyleDifferenceLayout) |
| setNeedsLayoutAndPrefWidthsRecalc(); |
| else if (updatedDiff == StyleDifferenceLayoutPositionedMovementOnly) |
| setNeedsPositionedMovementLayout(&oldStyle.get()); |
| else if (updatedDiff == StyleDifferenceSimplifiedLayoutAndPositionedMovement) { |
| setNeedsPositionedMovementLayout(&oldStyle.get()); |
| setNeedsSimplifiedNormalFlowLayout(); |
| } else if (updatedDiff == StyleDifferenceSimplifiedLayout) |
| setNeedsSimplifiedNormalFlowLayout(); |
| } |
| |
| if (updatedDiff == StyleDifferenceRepaintLayer || shouldRepaintForStyleDifference(updatedDiff)) { |
| // Do a repaint with the new style now, e.g., for example if we go from |
| // not having an outline to having an outline. |
| repaint(); |
| } |
| } |
| |
| void RenderElement::addChild(RenderObject* newChild, RenderObject* beforeChild) |
| { |
| bool needsTable = false; |
| |
| if (newChild->isRenderTableCol()) { |
| RenderTableCol* newTableColumn = toRenderTableCol(newChild); |
| bool isColumnInColumnGroup = newTableColumn->isTableColumn() && isRenderTableCol(); |
| needsTable = !isTable() && !isColumnInColumnGroup; |
| } else if (newChild->isTableCaption()) |
| needsTable = !isTable(); |
| else if (newChild->isTableSection()) |
| needsTable = !isTable(); |
| else if (newChild->isTableRow()) |
| needsTable = !isTableSection(); |
| else if (newChild->isTableCell()) |
| needsTable = !isTableRow(); |
| |
| if (needsTable) { |
| RenderTable* table; |
| RenderObject* afterChild = beforeChild ? beforeChild->previousSibling() : m_lastChild; |
| if (afterChild && afterChild->isAnonymous() && afterChild->isTable() && !afterChild->isBeforeContent()) |
| table = toRenderTable(afterChild); |
| else { |
| table = RenderTable::createAnonymousWithParentRenderer(this); |
| addChild(table, beforeChild); |
| } |
| table->addChild(newChild); |
| } else |
| insertChildInternal(newChild, beforeChild, NotifyChildren); |
| |
| if (is<RenderText>(newChild)) |
| downcast<RenderText>(*newChild).styleDidChange(StyleDifferenceEqual, nullptr); |
| |
| // SVG creates renderers for <g display="none">, as SVG requires children of hidden |
| // <g>s to have renderers - at least that's how our implementation works. Consider: |
| // <g display="none"><foreignObject><body style="position: relative">FOO... |
| // - requiresLayer() would return true for the <body>, creating a new RenderLayer |
| // - when the document is painted, both layers are painted. The <body> layer doesn't |
| // know that it's inside a "hidden SVG subtree", and thus paints, even if it shouldn't. |
| // To avoid the problem alltogether, detect early if we're inside a hidden SVG subtree |
| // and stop creating layers at all for these cases - they're not used anyways. |
| if (newChild->hasLayer() && !layerCreationAllowedForSubtree()) |
| toRenderLayerModelObject(newChild)->layer()->removeOnlyThisLayer(); |
| |
| SVGRenderSupport::childAdded(*this, *newChild); |
| } |
| |
| RenderObject* RenderElement::removeChild(RenderObject& oldChild) |
| { |
| return removeChildInternal(oldChild, NotifyChildren); |
| } |
| |
| void RenderElement::destroyLeftoverChildren() |
| { |
| while (m_firstChild) { |
| if (m_firstChild->isListMarker() || (m_firstChild->style().styleType() == FIRST_LETTER && !m_firstChild->isText())) { |
| m_firstChild->removeFromParent(); // List markers are owned by their enclosing list and so don't get destroyed by this container. Similarly, first letters are destroyed by their remaining text fragment. |
| } else { |
| // Destroy any anonymous children remaining in the render tree, as well as implicit (shadow) DOM elements like those used in the engine-based text fields. |
| if (m_firstChild->node()) |
| m_firstChild->node()->setRenderer(nullptr); |
| m_firstChild->destroy(); |
| } |
| } |
| } |
| |
| void RenderElement::insertChildInternal(RenderObject* newChild, RenderObject* beforeChild, NotifyChildrenType notifyChildren) |
| { |
| ASSERT(canHaveChildren() || canHaveGeneratedChildren()); |
| ASSERT(!newChild->parent()); |
| ASSERT(!isRenderBlockFlow() || (!newChild->isTableSection() && !newChild->isTableRow() && !newChild->isTableCell())); |
| |
| while (beforeChild && beforeChild->parent() && beforeChild->parent() != this) |
| beforeChild = beforeChild->parent(); |
| |
| // This should never happen, but if it does prevent render tree corruption |
| // where child->parent() ends up being owner but child->nextSibling()->parent() |
| // is not owner. |
| if (beforeChild && beforeChild->parent() != this) { |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| |
| newChild->setParent(this); |
| |
| if (m_firstChild == beforeChild) |
| m_firstChild = newChild; |
| |
| if (beforeChild) { |
| RenderObject* previousSibling = beforeChild->previousSibling(); |
| if (previousSibling) |
| previousSibling->setNextSibling(newChild); |
| newChild->setPreviousSibling(previousSibling); |
| newChild->setNextSibling(beforeChild); |
| beforeChild->setPreviousSibling(newChild); |
| } else { |
| if (lastChild()) |
| lastChild()->setNextSibling(newChild); |
| newChild->setPreviousSibling(lastChild()); |
| m_lastChild = newChild; |
| } |
| |
| if (!documentBeingDestroyed()) { |
| if (notifyChildren == NotifyChildren) |
| newChild->insertedIntoTree(); |
| if (newChild->isRenderElement()) |
| RenderCounter::rendererSubtreeAttached(toRenderElement(*newChild)); |
| } |
| |
| newChild->setNeedsLayoutAndPrefWidthsRecalc(); |
| setPreferredLogicalWidthsDirty(true); |
| if (!normalChildNeedsLayout()) |
| setChildNeedsLayout(); // We may supply the static position for an absolute positioned child. |
| |
| if (AXObjectCache* cache = document().axObjectCache()) |
| cache->childrenChanged(this, newChild); |
| } |
| |
| RenderObject* RenderElement::removeChildInternal(RenderObject& oldChild, NotifyChildrenType notifyChildren) |
| { |
| ASSERT(canHaveChildren() || canHaveGeneratedChildren()); |
| ASSERT(oldChild.parent() == this); |
| |
| if (oldChild.isFloatingOrOutOfFlowPositioned()) |
| toRenderBox(oldChild).removeFloatingOrPositionedChildFromBlockLists(); |
| |
| // So that we'll get the appropriate dirty bit set (either that a normal flow child got yanked or |
| // that a positioned child got yanked). We also repaint, so that the area exposed when the child |
| // disappears gets repainted properly. |
| if (!documentBeingDestroyed() && notifyChildren == NotifyChildren && oldChild.everHadLayout()) { |
| oldChild.setNeedsLayoutAndPrefWidthsRecalc(); |
| // We only repaint |oldChild| if we have a RenderLayer as its visual overflow may not be tracked by its parent. |
| if (oldChild.isBody()) |
| view().repaintRootContents(); |
| else |
| oldChild.repaint(); |
| } |
| |
| // If we have a line box wrapper, delete it. |
| if (oldChild.isBox()) |
| toRenderBox(oldChild).deleteLineBoxWrapper(); |
| else if (oldChild.isLineBreak()) |
| toRenderLineBreak(oldChild).deleteInlineBoxWrapper(); |
| |
| // If oldChild is the start or end of the selection, then clear the selection to |
| // avoid problems of invalid pointers. |
| if (!documentBeingDestroyed() && oldChild.isSelectionBorder()) |
| frame().selection().setNeedsSelectionUpdate(); |
| |
| if (!documentBeingDestroyed() && notifyChildren == NotifyChildren) |
| oldChild.willBeRemovedFromTree(); |
| |
| // WARNING: There should be no code running between willBeRemovedFromTree and the actual removal below. |
| // This is needed to avoid race conditions where willBeRemovedFromTree would dirty the tree's structure |
| // and the code running here would force an untimely rebuilding, leaving |oldChild| dangling. |
| |
| RenderObject* nextSibling = oldChild.nextSibling(); |
| |
| if (oldChild.previousSibling()) |
| oldChild.previousSibling()->setNextSibling(nextSibling); |
| if (nextSibling) |
| nextSibling->setPreviousSibling(oldChild.previousSibling()); |
| |
| if (m_firstChild == &oldChild) |
| m_firstChild = nextSibling; |
| if (m_lastChild == &oldChild) |
| m_lastChild = oldChild.previousSibling(); |
| |
| oldChild.setPreviousSibling(nullptr); |
| oldChild.setNextSibling(nullptr); |
| oldChild.setParent(nullptr); |
| |
| // rendererRemovedFromTree walks the whole subtree. We can improve performance |
| // by skipping this step when destroying the entire tree. |
| if (!documentBeingDestroyed() && oldChild.isRenderElement()) |
| RenderCounter::rendererRemovedFromTree(toRenderElement(oldChild)); |
| |
| if (AXObjectCache* cache = document().existingAXObjectCache()) |
| cache->childrenChanged(this); |
| |
| return nextSibling; |
| } |
| |
| static void addLayers(RenderElement& renderer, RenderLayer* parentLayer, RenderElement*& newObject, RenderLayer*& beforeChild) |
| { |
| if (renderer.hasLayer()) { |
| if (!beforeChild && newObject) { |
| // We need to figure out the layer that follows newObject. We only do |
| // this the first time we find a child layer, and then we update the |
| // pointer values for newObject and beforeChild used by everyone else. |
| beforeChild = newObject->parent()->findNextLayer(parentLayer, newObject); |
| newObject = nullptr; |
| } |
| parentLayer->addChild(toRenderLayerModelObject(renderer).layer(), beforeChild); |
| return; |
| } |
| |
| for (auto& child : childrenOfType<RenderElement>(renderer)) |
| addLayers(child, parentLayer, newObject, beforeChild); |
| } |
| |
| void RenderElement::addLayers(RenderLayer* parentLayer) |
| { |
| if (!parentLayer) |
| return; |
| |
| RenderElement* renderer = this; |
| RenderLayer* beforeChild = nullptr; |
| WebCore::addLayers(*this, parentLayer, renderer, beforeChild); |
| } |
| |
| void RenderElement::removeLayers(RenderLayer* parentLayer) |
| { |
| if (!parentLayer) |
| return; |
| |
| if (hasLayer()) { |
| parentLayer->removeChild(toRenderLayerModelObject(this)->layer()); |
| return; |
| } |
| |
| for (auto& child : childrenOfType<RenderElement>(*this)) |
| child.removeLayers(parentLayer); |
| } |
| |
| void RenderElement::moveLayers(RenderLayer* oldParent, RenderLayer* newParent) |
| { |
| if (!newParent) |
| return; |
| |
| if (hasLayer()) { |
| RenderLayer* layer = toRenderLayerModelObject(this)->layer(); |
| ASSERT(oldParent == layer->parent()); |
| if (oldParent) |
| oldParent->removeChild(layer); |
| newParent->addChild(layer); |
| return; |
| } |
| |
| for (auto& child : childrenOfType<RenderElement>(*this)) |
| child.moveLayers(oldParent, newParent); |
| } |
| |
| RenderLayer* RenderElement::findNextLayer(RenderLayer* parentLayer, RenderObject* startPoint, bool checkParent) |
| { |
| // Error check the parent layer passed in. If it's null, we can't find anything. |
| if (!parentLayer) |
| return nullptr; |
| |
| // Step 1: If our layer is a child of the desired parent, then return our layer. |
| RenderLayer* ourLayer = hasLayer() ? toRenderLayerModelObject(this)->layer() : nullptr; |
| if (ourLayer && ourLayer->parent() == parentLayer) |
| return ourLayer; |
| |
| // Step 2: If we don't have a layer, or our layer is the desired parent, then descend |
| // into our siblings trying to find the next layer whose parent is the desired parent. |
| if (!ourLayer || ourLayer == parentLayer) { |
| for (RenderObject* child = startPoint ? startPoint->nextSibling() : firstChild(); child; child = child->nextSibling()) { |
| if (!child->isRenderElement()) |
| continue; |
| RenderLayer* nextLayer = toRenderElement(child)->findNextLayer(parentLayer, nullptr, false); |
| if (nextLayer) |
| return nextLayer; |
| } |
| } |
| |
| // Step 3: If our layer is the desired parent layer, then we're finished. We didn't |
| // find anything. |
| if (parentLayer == ourLayer) |
| return nullptr; |
| |
| // Step 4: If |checkParent| is set, climb up to our parent and check its siblings that |
| // follow us to see if we can locate a layer. |
| if (checkParent && parent()) |
| return parent()->findNextLayer(parentLayer, this, true); |
| |
| return nullptr; |
| } |
| |
| bool RenderElement::layerCreationAllowedForSubtree() const |
| { |
| RenderElement* parentRenderer = parent(); |
| while (parentRenderer) { |
| if (parentRenderer->isSVGHiddenContainer()) |
| return false; |
| parentRenderer = parentRenderer->parent(); |
| } |
| |
| return true; |
| } |
| |
| void RenderElement::propagateStyleToAnonymousChildren(StylePropagationType propagationType) |
| { |
| // FIXME: We could save this call when the change only affected non-inherited properties. |
| for (auto& elementChild : childrenOfType<RenderElement>(*this)) { |
| if (!elementChild.isAnonymous() || elementChild.style().styleType() != NOPSEUDO) |
| continue; |
| |
| if (propagationType == PropagateToBlockChildrenOnly && !is<RenderBlock>(elementChild)) |
| continue; |
| |
| #if ENABLE(FULLSCREEN_API) |
| if (elementChild.isRenderFullScreen() || elementChild.isRenderFullScreenPlaceholder()) |
| continue; |
| #endif |
| |
| // RenderFlowThreads are updated through the RenderView::styleDidChange function. |
| if (is<RenderFlowThread>(elementChild)) |
| continue; |
| |
| auto newStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), elementChild.style().display()); |
| if (style().specifiesColumns()) { |
| if (elementChild.style().specifiesColumns()) |
| newStyle.get().inheritColumnPropertiesFrom(&style()); |
| if (elementChild.style().columnSpan()) |
| newStyle.get().setColumnSpan(ColumnSpanAll); |
| } |
| |
| // Preserve the position style of anonymous block continuations as they can have relative or sticky position when |
| // they contain block descendants of relative or sticky positioned inlines. |
| if (elementChild.isInFlowPositioned() && downcast<RenderBlock>(elementChild).isAnonymousBlockContinuation()) |
| newStyle.get().setPosition(elementChild.style().position()); |
| |
| elementChild.setStyle(WTF::move(newStyle)); |
| } |
| } |
| |
| static inline bool rendererHasBackground(const RenderElement* renderer) |
| { |
| return renderer && renderer->hasBackground(); |
| } |
| |
| void RenderElement::styleWillChange(StyleDifference diff, const RenderStyle& newStyle) |
| { |
| RenderStyle* oldStyle = hasInitializedStyle() ? &style() : nullptr; |
| if (oldStyle) { |
| // If our z-index changes value or our visibility changes, |
| // we need to dirty our stacking context's z-order list. |
| bool visibilityChanged = m_style->visibility() != newStyle.visibility() |
| || m_style->zIndex() != newStyle.zIndex() |
| || m_style->hasAutoZIndex() != newStyle.hasAutoZIndex(); |
| #if ENABLE(DASHBOARD_SUPPORT) |
| if (visibilityChanged) |
| document().setAnnotatedRegionsDirty(true); |
| #endif |
| #if PLATFORM(IOS) && ENABLE(TOUCH_EVENTS) |
| if (visibilityChanged) |
| document().dirtyTouchEventRects(); |
| #endif |
| if (visibilityChanged) { |
| if (AXObjectCache* cache = document().existingAXObjectCache()) |
| cache->childrenChanged(parent(), this); |
| } |
| |
| // Keep layer hierarchy visibility bits up to date if visibility changes. |
| if (m_style->visibility() != newStyle.visibility()) { |
| if (RenderLayer* layer = enclosingLayer()) { |
| if (newStyle.visibility() == VISIBLE) |
| layer->setHasVisibleContent(); |
| else if (layer->hasVisibleContent() && (this == &layer->renderer() || layer->renderer().style().visibility() != VISIBLE)) { |
| layer->dirtyVisibleContentStatus(); |
| if (diff > StyleDifferenceRepaintLayer) |
| repaint(); |
| } |
| } |
| } |
| |
| if (m_parent && (newStyle.outlineSize() < m_style->outlineSize() || shouldRepaintForStyleDifference(diff))) |
| repaint(); |
| if (isFloating() && (m_style->floating() != newStyle.floating())) |
| // For changes in float styles, we need to conceivably remove ourselves |
| // from the floating objects list. |
| toRenderBox(this)->removeFloatingOrPositionedChildFromBlockLists(); |
| else if (isOutOfFlowPositioned() && (m_style->position() != newStyle.position())) |
| // For changes in positioning styles, we need to conceivably remove ourselves |
| // from the positioned objects list. |
| toRenderBox(this)->removeFloatingOrPositionedChildFromBlockLists(); |
| |
| s_affectsParentBlock = isFloatingOrOutOfFlowPositioned() |
| && (!newStyle.isFloating() && !newStyle.hasOutOfFlowPosition()) |
| && parent() && (parent()->isRenderBlockFlow() || parent()->isRenderInline()); |
| |
| s_noLongerAffectsParentBlock = ((!isFloating() && newStyle.isFloating()) || (!isOutOfFlowPositioned() && newStyle.hasOutOfFlowPosition())) |
| && parent() && parent()->isRenderBlock(); |
| |
| // reset style flags |
| if (diff == StyleDifferenceLayout || diff == StyleDifferenceLayoutPositionedMovementOnly) { |
| setFloating(false); |
| clearPositionedState(); |
| } |
| setHorizontalWritingMode(true); |
| setHasBoxDecorations(false); |
| setHasOverflowClip(false); |
| setHasTransform(false); |
| setHasReflection(false); |
| } else { |
| s_affectsParentBlock = false; |
| s_noLongerAffectsParentBlock = false; |
| } |
| |
| bool newStyleUsesFixedBackgrounds = newStyle.hasFixedBackgroundImage(); |
| bool oldStyleUsesFixedBackgrounds = m_style->hasFixedBackgroundImage(); |
| if (newStyleUsesFixedBackgrounds || oldStyleUsesFixedBackgrounds) { |
| bool repaintFixedBackgroundsOnScroll = !frame().settings().fixedBackgroundsPaintRelativeToDocument(); |
| |
| bool newStyleSlowScroll = repaintFixedBackgroundsOnScroll && newStyleUsesFixedBackgrounds; |
| bool oldStyleSlowScroll = oldStyle && repaintFixedBackgroundsOnScroll && oldStyleUsesFixedBackgrounds; |
| bool drawsRootBackground = isRoot() || (isBody() && !rendererHasBackground(document().documentElement()->renderer())); |
| if (drawsRootBackground && repaintFixedBackgroundsOnScroll) { |
| if (view().compositor().supportsFixedRootBackgroundCompositing()) { |
| if (newStyleSlowScroll && newStyle.hasEntirelyFixedBackground()) |
| newStyleSlowScroll = false; |
| |
| if (oldStyleSlowScroll && m_style->hasEntirelyFixedBackground()) |
| oldStyleSlowScroll = false; |
| } |
| } |
| |
| if (oldStyleSlowScroll != newStyleSlowScroll) { |
| if (oldStyleSlowScroll) |
| view().frameView().removeSlowRepaintObject(this); |
| |
| if (newStyleSlowScroll) |
| view().frameView().addSlowRepaintObject(this); |
| } |
| } |
| |
| if (isRoot() || isBody()) |
| view().frameView().updateExtendBackgroundIfNecessary(); |
| } |
| |
| #if !PLATFORM(IOS) |
| static bool areNonIdenticalCursorListsEqual(const RenderStyle* a, const RenderStyle* b) |
| { |
| ASSERT(a->cursors() != b->cursors()); |
| return a->cursors() && b->cursors() && *a->cursors() == *b->cursors(); |
| } |
| |
| static inline bool areCursorsEqual(const RenderStyle* a, const RenderStyle* b) |
| { |
| return a->cursor() == b->cursor() && (a->cursors() == b->cursors() || areNonIdenticalCursorListsEqual(a, b)); |
| } |
| #endif |
| |
| void RenderElement::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
| { |
| if (s_affectsParentBlock) |
| handleDynamicFloatPositionChange(); |
| |
| if (s_noLongerAffectsParentBlock) |
| removeAnonymousWrappersForInlinesIfNecessary(); |
| |
| SVGRenderSupport::styleChanged(*this, oldStyle); |
| |
| if (!m_parent) |
| return; |
| |
| if (diff == StyleDifferenceLayout || diff == StyleDifferenceSimplifiedLayout) { |
| RenderCounter::rendererStyleChanged(*this, oldStyle, &m_style.get()); |
| |
| // If the object already needs layout, then setNeedsLayout won't do |
| // any work. But if the containing block has changed, then we may need |
| // to mark the new containing blocks for layout. The change that can |
| // directly affect the containing block of this object is a change to |
| // the position style. |
| if (needsLayout() && oldStyle->position() != m_style->position()) |
| markContainingBlocksForLayout(); |
| |
| if (diff == StyleDifferenceLayout) |
| setNeedsLayoutAndPrefWidthsRecalc(); |
| else |
| setNeedsSimplifiedNormalFlowLayout(); |
| } else if (diff == StyleDifferenceSimplifiedLayoutAndPositionedMovement) { |
| setNeedsPositionedMovementLayout(oldStyle); |
| setNeedsSimplifiedNormalFlowLayout(); |
| } else if (diff == StyleDifferenceLayoutPositionedMovementOnly) |
| setNeedsPositionedMovementLayout(oldStyle); |
| |
| // Don't check for repaint here; we need to wait until the layer has been |
| // updated by subclasses before we know if we have to repaint (in setStyle()). |
| |
| #if !PLATFORM(IOS) |
| if (oldStyle && !areCursorsEqual(oldStyle, &style())) |
| frame().eventHandler().scheduleCursorUpdate(); |
| #endif |
| } |
| |
| void RenderElement::insertedIntoTree() |
| { |
| if (auto* containerFlowThread = parent()->renderNamedFlowThreadWrapper()) |
| containerFlowThread->addFlowChild(*this); |
| |
| // Keep our layer hierarchy updated. Optimize for the common case where we don't have any children |
| // and don't have a layer attached to ourselves. |
| RenderLayer* layer = nullptr; |
| if (firstChild() || hasLayer()) { |
| layer = parent()->enclosingLayer(); |
| addLayers(layer); |
| } |
| |
| // If |this| is visible but this object was not, tell the layer it has some visible content |
| // that needs to be drawn and layer visibility optimization can't be used |
| if (parent()->style().visibility() != VISIBLE && style().visibility() == VISIBLE && !hasLayer()) { |
| if (!layer) |
| layer = parent()->enclosingLayer(); |
| if (layer) |
| layer->setHasVisibleContent(); |
| } |
| |
| RenderObject::insertedIntoTree(); |
| } |
| |
| void RenderElement::willBeRemovedFromTree() |
| { |
| // If we remove a visible child from an invisible parent, we don't know the layer visibility any more. |
| RenderLayer* layer = nullptr; |
| if (parent()->style().visibility() != VISIBLE && style().visibility() == VISIBLE && !hasLayer()) { |
| if ((layer = parent()->enclosingLayer())) |
| layer->dirtyVisibleContentStatus(); |
| } |
| // Keep our layer hierarchy updated. |
| if (firstChild() || hasLayer()) { |
| if (!layer) |
| layer = parent()->enclosingLayer(); |
| removeLayers(layer); |
| } |
| |
| if (m_style->hasFixedBackgroundImage() && !frame().settings().fixedBackgroundsPaintRelativeToDocument()) |
| view().frameView().removeSlowRepaintObject(this); |
| |
| if (isOutOfFlowPositioned() && parent()->childrenInline()) |
| parent()->dirtyLinesFromChangedChild(this); |
| |
| if (auto* containerFlowThread = parent()->renderNamedFlowThreadWrapper()) |
| containerFlowThread->removeFlowChild(*this); |
| |
| RenderObject::willBeRemovedFromTree(); |
| } |
| |
| void RenderElement::willBeDestroyed() |
| { |
| animation().cancelAnimations(this); |
| |
| destroyLeftoverChildren(); |
| |
| if (hasCounterNodeMap()) |
| RenderCounter::destroyCounterNodes(*this); |
| |
| RenderObject::willBeDestroyed(); |
| |
| #if !ASSERT_DISABLED |
| if (!documentBeingDestroyed() && view().hasRenderNamedFlowThreads()) { |
| // After remove, the object and the associated information should not be in any flow thread. |
| for (auto& flowThread : *view().flowThreadController().renderNamedFlowThreadList()) { |
| ASSERT(!flowThread->hasChild(*this)); |
| ASSERT(!flowThread->hasChildInfo(this)); |
| } |
| } |
| #endif |
| } |
| |
| void RenderElement::setNeedsPositionedMovementLayout(const RenderStyle* oldStyle) |
| { |
| ASSERT(!isSetNeedsLayoutForbidden()); |
| if (needsPositionedMovementLayout()) |
| return; |
| setNeedsPositionedMovementLayoutBit(true); |
| markContainingBlocksForLayout(); |
| if (hasLayer()) { |
| if (oldStyle && style().diffRequiresLayerRepaint(*oldStyle, toRenderLayerModelObject(this)->layer()->isComposited())) |
| setLayerNeedsFullRepaint(); |
| else |
| setLayerNeedsFullRepaintForPositionedMovementLayout(); |
| } |
| } |
| |
| void RenderElement::clearChildNeedsLayout() |
| { |
| setNormalChildNeedsLayoutBit(false); |
| setPosChildNeedsLayoutBit(false); |
| setNeedsSimplifiedNormalFlowLayoutBit(false); |
| setNormalChildNeedsLayoutBit(false); |
| setNeedsPositionedMovementLayoutBit(false); |
| } |
| |
| void RenderElement::setNeedsSimplifiedNormalFlowLayout() |
| { |
| ASSERT(!isSetNeedsLayoutForbidden()); |
| if (needsSimplifiedNormalFlowLayout()) |
| return; |
| setNeedsSimplifiedNormalFlowLayoutBit(true); |
| markContainingBlocksForLayout(); |
| if (hasLayer()) |
| setLayerNeedsFullRepaint(); |
| } |
| |
| RenderElement& RenderElement::rendererForRootBackground() |
| { |
| ASSERT(isRoot()); |
| if (!hasBackground() && element() && element()->hasTagName(HTMLNames::htmlTag)) { |
| // Locate the <body> element using the DOM. This is easier than trying |
| // to crawl around a render tree with potential :before/:after content and |
| // anonymous blocks created by inline <body> tags etc. We can locate the <body> |
| // render object very easily via the DOM. |
| if (auto body = document().body()) { |
| if (body->hasTagName(HTMLNames::bodyTag)) { |
| if (auto renderer = body->renderer()) |
| return *renderer; |
| } |
| } |
| } |
| return *this; |
| } |
| |
| RenderElement* RenderElement::hoverAncestor() const |
| { |
| // When searching for the hover ancestor and encountering a named flow thread, |
| // the search will continue with the DOM ancestor of the top-most element |
| // in the named flow thread. |
| // See https://bugs.webkit.org/show_bug.cgi?id=111749 |
| RenderElement* hoverAncestor = parent(); |
| |
| // Skip anonymous blocks directly flowed into flow threads as it would |
| // prevent us from continuing the search on the DOM tree when reaching the named flow thread. |
| if (hoverAncestor && hoverAncestor->isAnonymousBlock() && hoverAncestor->parent() && hoverAncestor->parent()->isRenderNamedFlowThread()) |
| hoverAncestor = hoverAncestor->parent(); |
| |
| if (hoverAncestor && hoverAncestor->isRenderNamedFlowThread()) { |
| hoverAncestor = nullptr; |
| if (Element* element = this->element()) { |
| if (auto parent = element->parentNode()) |
| hoverAncestor = parent->renderer(); |
| } |
| } |
| |
| return hoverAncestor; |
| } |
| |
| void RenderElement::layout() |
| { |
| StackStats::LayoutCheckPoint layoutCheckPoint; |
| ASSERT(needsLayout()); |
| RenderObject* child = firstChild(); |
| while (child) { |
| if (child->needsLayout()) |
| toRenderElement(child)->layout(); |
| ASSERT(!child->needsLayout()); |
| child = child->nextSibling(); |
| } |
| clearNeedsLayout(); |
| } |
| |
| static bool mustRepaintFillLayers(const RenderElement& renderer, const FillLayer* layer) |
| { |
| // Nobody will use multiple layers without wanting fancy positioning. |
| if (layer->next()) |
| return true; |
| |
| // Make sure we have a valid image. |
| StyleImage* image = layer->image(); |
| if (!image || !image->canRender(&renderer, renderer.style().effectiveZoom())) |
| return false; |
| |
| if (!layer->xPosition().isZero() || !layer->yPosition().isZero()) |
| return true; |
| |
| EFillSizeType sizeType = layer->sizeType(); |
| |
| if (sizeType == Contain || sizeType == Cover) |
| return true; |
| |
| if (sizeType == SizeLength) { |
| LengthSize size = layer->sizeLength(); |
| if (size.width().isPercent() || size.height().isPercent()) |
| return true; |
| // If the image has neither an intrinsic width nor an intrinsic height, its size is determined as for 'contain'. |
| if ((size.width().isAuto() || size.height().isAuto()) && image->isGeneratedImage()) |
| return true; |
| } else if (image->usesImageContainerSize()) |
| return true; |
| |
| return false; |
| } |
| |
| static bool mustRepaintBackgroundOrBorder(const RenderElement& renderer) |
| { |
| if (renderer.hasMask() && mustRepaintFillLayers(renderer, renderer.style().maskLayers())) |
| return true; |
| |
| // If we don't have a background/border/mask, then nothing to do. |
| if (!renderer.hasBoxDecorations()) |
| return false; |
| |
| if (mustRepaintFillLayers(renderer, renderer.style().backgroundLayers())) |
| return true; |
| |
| // Our fill layers are ok. Let's check border. |
| if (renderer.style().hasBorder() && renderer.borderImageIsLoadedAndCanBeRendered()) |
| return true; |
| |
| return false; |
| } |
| |
| bool RenderElement::repaintAfterLayoutIfNeeded(const RenderLayerModelObject* repaintContainer, const LayoutRect& oldBounds, const LayoutRect& oldOutlineBox, const LayoutRect* newBoundsPtr, const LayoutRect* newOutlineBoxRectPtr) |
| { |
| if (view().printing()) |
| return false; // Don't repaint if we're printing. |
| |
| // This ASSERT fails due to animations. See https://bugs.webkit.org/show_bug.cgi?id=37048 |
| // ASSERT(!newBoundsPtr || *newBoundsPtr == clippedOverflowRectForRepaint(repaintContainer)); |
| LayoutRect newBounds = newBoundsPtr ? *newBoundsPtr : clippedOverflowRectForRepaint(repaintContainer); |
| LayoutRect newOutlineBox; |
| |
| bool fullRepaint = selfNeedsLayout(); |
| // Presumably a background or a border exists if border-fit:lines was specified. |
| if (!fullRepaint && style().borderFit() == BorderFitLines) |
| fullRepaint = true; |
| if (!fullRepaint) { |
| // This ASSERT fails due to animations. See https://bugs.webkit.org/show_bug.cgi?id=37048 |
| // ASSERT(!newOutlineBoxRectPtr || *newOutlineBoxRectPtr == outlineBoundsForRepaint(repaintContainer)); |
| newOutlineBox = newOutlineBoxRectPtr ? *newOutlineBoxRectPtr : outlineBoundsForRepaint(repaintContainer); |
| if (newOutlineBox.location() != oldOutlineBox.location() || (mustRepaintBackgroundOrBorder(*this) && (newBounds != oldBounds || newOutlineBox != oldOutlineBox))) |
| fullRepaint = true; |
| } |
| |
| if (!repaintContainer) |
| repaintContainer = &view(); |
| |
| if (fullRepaint) { |
| repaintUsingContainer(repaintContainer, oldBounds); |
| if (newBounds != oldBounds) |
| repaintUsingContainer(repaintContainer, newBounds); |
| return true; |
| } |
| |
| if (newBounds == oldBounds && newOutlineBox == oldOutlineBox) |
| return false; |
| |
| LayoutUnit deltaLeft = newBounds.x() - oldBounds.x(); |
| if (deltaLeft > 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(oldBounds.x(), oldBounds.y(), deltaLeft, oldBounds.height())); |
| else if (deltaLeft < 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(newBounds.x(), newBounds.y(), -deltaLeft, newBounds.height())); |
| |
| LayoutUnit deltaRight = newBounds.maxX() - oldBounds.maxX(); |
| if (deltaRight > 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(oldBounds.maxX(), newBounds.y(), deltaRight, newBounds.height())); |
| else if (deltaRight < 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(newBounds.maxX(), oldBounds.y(), -deltaRight, oldBounds.height())); |
| |
| LayoutUnit deltaTop = newBounds.y() - oldBounds.y(); |
| if (deltaTop > 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(oldBounds.x(), oldBounds.y(), oldBounds.width(), deltaTop)); |
| else if (deltaTop < 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(newBounds.x(), newBounds.y(), newBounds.width(), -deltaTop)); |
| |
| LayoutUnit deltaBottom = newBounds.maxY() - oldBounds.maxY(); |
| if (deltaBottom > 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(newBounds.x(), oldBounds.maxY(), newBounds.width(), deltaBottom)); |
| else if (deltaBottom < 0) |
| repaintUsingContainer(repaintContainer, LayoutRect(oldBounds.x(), newBounds.maxY(), oldBounds.width(), -deltaBottom)); |
| |
| if (newOutlineBox == oldOutlineBox) |
| return false; |
| |
| // We didn't move, but we did change size. Invalidate the delta, which will consist of possibly |
| // two rectangles (but typically only one). |
| const RenderStyle& outlineStyle = outlineStyleForRepaint(); |
| LayoutUnit outlineWidth = outlineStyle.outlineSize(); |
| LayoutBoxExtent insetShadowExtent = style().getBoxShadowInsetExtent(); |
| LayoutUnit width = absoluteValue(newOutlineBox.width() - oldOutlineBox.width()); |
| if (width) { |
| LayoutUnit shadowLeft; |
| LayoutUnit shadowRight; |
| style().getBoxShadowHorizontalExtent(shadowLeft, shadowRight); |
| LayoutUnit borderRight = isBox() ? toRenderBox(this)->borderRight() : LayoutUnit::fromPixel(0); |
| LayoutUnit boxWidth = isBox() ? toRenderBox(this)->width() : LayoutUnit(); |
| LayoutUnit minInsetRightShadowExtent = std::min<LayoutUnit>(-insetShadowExtent.right(), std::min<LayoutUnit>(newBounds.width(), oldBounds.width())); |
| LayoutUnit borderWidth = std::max<LayoutUnit>(borderRight, std::max<LayoutUnit>(valueForLength(style().borderTopRightRadius().width(), boxWidth), valueForLength(style().borderBottomRightRadius().width(), boxWidth))); |
| LayoutUnit decorationsWidth = std::max<LayoutUnit>(-outlineStyle.outlineOffset(), borderWidth + minInsetRightShadowExtent) + std::max<LayoutUnit>(outlineWidth, shadowRight); |
| LayoutRect rightRect(newOutlineBox.x() + std::min(newOutlineBox.width(), oldOutlineBox.width()) - decorationsWidth, |
| newOutlineBox.y(), |
| width + decorationsWidth, |
| std::max(newOutlineBox.height(), oldOutlineBox.height())); |
| LayoutUnit right = std::min<LayoutUnit>(newBounds.maxX(), oldBounds.maxX()); |
| if (rightRect.x() < right) { |
| rightRect.setWidth(std::min(rightRect.width(), right - rightRect.x())); |
| repaintUsingContainer(repaintContainer, rightRect); |
| } |
| } |
| LayoutUnit height = absoluteValue(newOutlineBox.height() - oldOutlineBox.height()); |
| if (height) { |
| LayoutUnit shadowTop; |
| LayoutUnit shadowBottom; |
| style().getBoxShadowVerticalExtent(shadowTop, shadowBottom); |
| LayoutUnit borderBottom = isBox() ? toRenderBox(this)->borderBottom() : LayoutUnit::fromPixel(0); |
| LayoutUnit boxHeight = isBox() ? toRenderBox(this)->height() : LayoutUnit(); |
| LayoutUnit minInsetBottomShadowExtent = std::min<LayoutUnit>(-insetShadowExtent.bottom(), std::min<LayoutUnit>(newBounds.height(), oldBounds.height())); |
| LayoutUnit borderHeight = std::max<LayoutUnit>(borderBottom, std::max<LayoutUnit>(valueForLength(style().borderBottomLeftRadius().height(), boxHeight), |
| valueForLength(style().borderBottomRightRadius().height(), boxHeight))); |
| LayoutUnit decorationsHeight = std::max<LayoutUnit>(-outlineStyle.outlineOffset(), borderHeight + minInsetBottomShadowExtent) + std::max<LayoutUnit>(outlineWidth, shadowBottom); |
| LayoutRect bottomRect(newOutlineBox.x(), |
| std::min(newOutlineBox.maxY(), oldOutlineBox.maxY()) - decorationsHeight, |
| std::max(newOutlineBox.width(), oldOutlineBox.width()), |
| height + decorationsHeight); |
| LayoutUnit bottom = std::min(newBounds.maxY(), oldBounds.maxY()); |
| if (bottomRect.y() < bottom) { |
| bottomRect.setHeight(std::min(bottomRect.height(), bottom - bottomRect.y())); |
| repaintUsingContainer(repaintContainer, bottomRect); |
| } |
| } |
| return false; |
| } |
| |
| bool RenderElement::borderImageIsLoadedAndCanBeRendered() const |
| { |
| ASSERT(style().hasBorder()); |
| |
| StyleImage* borderImage = style().borderImage().image(); |
| return borderImage && borderImage->canRender(this, style().effectiveZoom()) && borderImage->isLoaded(); |
| } |
| |
| static bool shouldRepaintForImageAnimation(const RenderElement& renderer, const IntRect& visibleRect) |
| { |
| const Document& document = renderer.document(); |
| if (document.inPageCache()) |
| return false; |
| auto& frameView = renderer.view().frameView(); |
| if (frameView.isOffscreen()) |
| return false; |
| #if PLATFORM(IOS) |
| if (document.frame()->timersPaused()) |
| return false; |
| #endif |
| if (document.activeDOMObjectsAreSuspended()) |
| return false; |
| if (renderer.style().visibility() != VISIBLE) |
| return false; |
| if (!visibleRect.intersects(renderer.absoluteBoundingBoxRect())) |
| return false; |
| |
| return true; |
| } |
| |
| void RenderElement::newImageAnimationFrameAvailable(CachedImage& image) |
| { |
| auto& frameView = view().frameView(); |
| auto visibleRect = frameView.windowToContents(frameView.windowClipRect()); |
| if (!shouldRepaintForImageAnimation(*this, visibleRect)) { |
| view().addRendererWithPausedImageAnimations(*this); |
| return; |
| } |
| imageChanged(&image); |
| } |
| |
| bool RenderElement::repaintForPausedImageAnimationsIfNeeded(const IntRect& visibleRect) |
| { |
| ASSERT(m_hasPausedImageAnimations); |
| if (!shouldRepaintForImageAnimation(*this, visibleRect)) |
| return false; |
| repaint(); |
| return true; |
| } |
| |
| RenderNamedFlowThread* RenderElement::renderNamedFlowThreadWrapper() |
| { |
| auto renderer = this; |
| while (renderer && renderer->isAnonymousBlock() && !renderer->isRenderNamedFlowThread()) |
| renderer = renderer->parent(); |
| return renderer && renderer->isRenderNamedFlowThread() ? toRenderNamedFlowThread(renderer) : nullptr; |
| } |
| |
| bool RenderElement::hasControlStatesForRenderer(const RenderObject* o) |
| { |
| return controlStatesRendererMap().contains(o); |
| } |
| |
| ControlStates* RenderElement::controlStatesForRenderer(const RenderObject* o) |
| { |
| return controlStatesRendererMap().get(o); |
| } |
| |
| void RenderElement::removeControlStatesForRenderer(const RenderObject* o) |
| { |
| ControlStates* states = controlStatesRendererMap().get(o); |
| if (states) { |
| controlStatesRendererMap().remove(o); |
| delete states; |
| } |
| } |
| |
| void RenderElement::addControlStatesForRenderer(const RenderObject* o, ControlStates* states) |
| { |
| controlStatesRendererMap().add(o, states); |
| } |
| |
| } |