blob: 077c67262baa2cd960d01229ea5c5215a580c555 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Dirk Mueller (mueller@kde.org)
* (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2011 Apple Inc.
* All rights reserved.
* Copyright (C) 2009 Google Inc. All rights reserved.
* Copyright (C) 2009 Torch Mobile Inc. All rights reserved.
* (http://www.torchmobile.com/)
*
* 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 "core/layout/LayoutObject.h"
#include <algorithm>
#include <memory>
#include "core/animation/ElementAnimations.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/AXObjectCache.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/StyleChangeReason.h"
#include "core/dom/StyleEngine.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/TextAffinity.h"
#include "core/frame/DeprecatedScheduleStyleRecalcDuringLayout.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLHtmlElement.h"
#include "core/html/HTMLTableCellElement.h"
#include "core/html/HTMLTableElement.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutCounter.h"
#include "core/layout/LayoutDeprecatedFlexibleBox.h"
#include "core/layout/LayoutFlexibleBox.h"
#include "core/layout/LayoutFlowThread.h"
#include "core/layout/LayoutGrid.h"
#include "core/layout/LayoutImage.h"
#include "core/layout/LayoutImageResourceStyleImage.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutListItem.h"
#include "core/layout/LayoutMultiColumnSpannerPlaceholder.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/LayoutScrollbarPart.h"
#include "core/layout/LayoutTableCaption.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutTableCol.h"
#include "core/layout/LayoutTableRow.h"
#include "core/layout/LayoutTheme.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutAPIShim.h"
#include "core/layout/api/LayoutPartItem.h"
#include "core/layout/ng/layout_ng_block_flow.h"
#include "core/page/AutoscrollController.h"
#include "core/page/Page.h"
#include "core/paint/ObjectPaintInvalidator.h"
#include "core/paint/ObjectPaintProperties.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ContentData.h"
#include "core/style/CursorData.h"
#include "platform/InstanceCounters.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/geometry/TransformState.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/instrumentation/tracing/TracedValue.h"
#include "wtf/allocator/Partitions.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/text/WTFString.h"
#ifndef NDEBUG
#include <stdio.h>
#endif
namespace blink {
namespace {
static bool gModifyLayoutTreeStructureAnyState = false;
} // namespace
#if DCHECK_IS_ON()
LayoutObject::SetLayoutNeededForbiddenScope::SetLayoutNeededForbiddenScope(
LayoutObject& layoutObject)
: m_layoutObject(layoutObject),
m_preexistingForbidden(m_layoutObject.isSetNeedsLayoutForbidden()) {
m_layoutObject.setNeedsLayoutIsForbidden(true);
}
LayoutObject::SetLayoutNeededForbiddenScope::~SetLayoutNeededForbiddenScope() {
m_layoutObject.setNeedsLayoutIsForbidden(m_preexistingForbidden);
}
#endif
struct SameSizeAsLayoutObject : DisplayItemClient {
virtual ~SameSizeAsLayoutObject() {} // Allocate vtable pointer.
void* pointers[5];
Member<void*> members[1];
#if DCHECK_IS_ON()
unsigned m_debugBitfields : 2;
#endif
unsigned m_bitfields;
unsigned m_bitfields2;
LayoutRect m_visualRect;
LayoutPoint m_paintOffset;
std::unique_ptr<void*> m_paintProperties;
};
static_assert(sizeof(LayoutObject) == sizeof(SameSizeAsLayoutObject),
"LayoutObject should stay small");
bool LayoutObject::s_affectsParentBlock = false;
void* LayoutObject::operator new(size_t sz) {
ASSERT(isMainThread());
return PartitionAlloc(WTF::Partitions::layoutPartition(), sz,
WTF_HEAP_PROFILER_TYPE_NAME(LayoutObject));
}
void LayoutObject::operator delete(void* ptr) {
ASSERT(isMainThread());
WTF::PartitionFree(ptr);
}
LayoutObject* LayoutObject::createObject(Element* element,
const ComputedStyle& style) {
ASSERT(isAllowedToModifyLayoutTreeStructure(element->document()));
// 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.contentData();
if (contentData && !contentData->next() && contentData->isImage() &&
!element->isPseudoElement()) {
LayoutImage* image = new LayoutImage(element);
// LayoutImageResourceStyleImage requires a style being present on the image
// but we don't want to trigger a style change now as the node is not fully
// attached. Moving this code to style change doesn't make sense as it
// should be run once at layoutObject creation.
image->setStyleInternal(const_cast<ComputedStyle*>(&style));
if (const StyleImage* styleImage =
toImageContentData(contentData)->image()) {
image->setImageResource(LayoutImageResourceStyleImage::create(
const_cast<StyleImage*>(styleImage)));
image->setIsGeneratedContent();
} else {
image->setImageResource(LayoutImageResource::create());
}
image->setStyleInternal(nullptr);
return image;
}
switch (style.display()) {
case EDisplay::None:
case EDisplay::Contents:
return nullptr;
case EDisplay::Inline:
return new LayoutInline(element);
case EDisplay::Block:
case EDisplay::FlowRoot:
case EDisplay::InlineBlock:
if (RuntimeEnabledFeatures::layoutNGEnabled())
return new LayoutNGBlockFlow(element);
return new LayoutBlockFlow(element);
case EDisplay::ListItem:
return new LayoutListItem(element);
case EDisplay::Table:
case EDisplay::InlineTable:
return new LayoutTable(element);
case EDisplay::TableRowGroup:
case EDisplay::TableHeaderGroup:
case EDisplay::TableFooterGroup:
return new LayoutTableSection(element);
case EDisplay::TableRow:
return new LayoutTableRow(element);
case EDisplay::TableColumnGroup:
case EDisplay::TableColumn:
return new LayoutTableCol(element);
case EDisplay::TableCell:
return new LayoutTableCell(element);
case EDisplay::TableCaption:
return new LayoutTableCaption(element);
case EDisplay::WebkitBox:
case EDisplay::WebkitInlineBox:
return new LayoutDeprecatedFlexibleBox(*element);
case EDisplay::Flex:
case EDisplay::InlineFlex:
return new LayoutFlexibleBox(element);
case EDisplay::Grid:
case EDisplay::InlineGrid:
return new LayoutGrid(element);
}
ASSERT_NOT_REACHED();
return nullptr;
}
LayoutObject::LayoutObject(Node* node)
: m_style(nullptr),
m_node(node),
m_parent(nullptr),
m_previous(nullptr),
m_next(nullptr),
#if DCHECK_IS_ON()
m_hasAXObject(false),
m_setNeedsLayoutForbidden(false),
#endif
m_bitfields(node) {
InstanceCounters::incrementCounter(InstanceCounters::LayoutObjectCounter);
if (m_node)
frameView()->incrementLayoutObjectCount();
}
LayoutObject::~LayoutObject() {
#if DCHECK_IS_ON()
DCHECK(!m_hasAXObject);
#endif
InstanceCounters::decrementCounter(InstanceCounters::LayoutObjectCounter);
}
bool LayoutObject::isDescendantOf(const LayoutObject* obj) const {
for (const LayoutObject* r = this; r; r = r->m_parent) {
if (r == obj)
return true;
}
return false;
}
bool LayoutObject::isHR() const {
return isHTMLHRElement(node());
}
bool LayoutObject::isLegend() const {
return isHTMLLegendElement(node());
}
void LayoutObject::setIsInsideFlowThreadIncludingDescendants(
bool insideFlowThread) {
LayoutObject* next;
for (LayoutObject* object = this; object; object = next) {
// If object is a fragmentation context it already updated the descendants
// flag accordingly.
if (object->isLayoutFlowThread()) {
next = object->nextInPreOrderAfterChildren(this);
continue;
}
next = object->nextInPreOrder(this);
ASSERT(insideFlowThread != object->isInsideFlowThread());
object->setIsInsideFlowThread(insideFlowThread);
}
}
bool LayoutObject::requiresAnonymousTableWrappers(
const LayoutObject* newChild) const {
// Check should agree with:
// CSS 2.1 Tables: 17.2.1 Anonymous table objects
// http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
if (newChild->isLayoutTableCol()) {
const LayoutTableCol* newTableColumn = toLayoutTableCol(newChild);
bool isColumnInColumnGroup =
newTableColumn->isTableColumn() && isLayoutTableCol();
return !isTable() && !isColumnInColumnGroup;
}
if (newChild->isTableCaption())
return !isTable();
if (newChild->isTableSection())
return !isTable();
if (newChild->isTableRow())
return !isTableSection();
if (newChild->isTableCell())
return !isTableRow();
return false;
}
DISABLE_CFI_PERF
void LayoutObject::addChild(LayoutObject* newChild, LayoutObject* beforeChild) {
ASSERT(isAllowedToModifyLayoutTreeStructure(document()));
LayoutObjectChildList* children = virtualChildren();
ASSERT(children);
if (!children)
return;
if (requiresAnonymousTableWrappers(newChild)) {
// Generate an anonymous table or reuse existing one from previous child
// Per: 17.2.1 Anonymous table objects 3. Generate missing parents
// http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
LayoutTable* table;
LayoutObject* afterChild =
beforeChild ? beforeChild->previousSibling() : children->lastChild();
if (afterChild && afterChild->isAnonymous() && afterChild->isTable() &&
!afterChild->isBeforeContent()) {
table = toLayoutTable(afterChild);
} else {
table = LayoutTable::createAnonymousWithParent(this);
children->insertChildNode(this, table, beforeChild);
}
table->addChild(newChild);
} else {
children->insertChildNode(this, newChild, beforeChild);
}
if (newChild->isText() &&
newChild->style()->textTransform() == ETextTransform::kCapitalize)
toLayoutText(newChild)->transformText();
// SVG creates layoutObjects for <g display="none">, as SVG requires children
// of hidden <g>s to have layoutObjects - at least that's how our
// implementation works.
// Consider:
// <g display="none"><foreignObject><body style="position: relative">FOO...
// - layerTypeRequired() would return true for the <body>, creating a new
// Layer
// - 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 altogether, 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())
toLayoutBoxModelObject(newChild)
->layer()
->removeOnlyThisLayerAfterStyleChange();
}
void LayoutObject::removeChild(LayoutObject* oldChild) {
ASSERT(isAllowedToModifyLayoutTreeStructure(document()));
LayoutObjectChildList* children = virtualChildren();
ASSERT(children);
if (!children)
return;
children->removeChildNode(this, oldChild);
}
void LayoutObject::setDangerousOneWayParent(LayoutObject* parent) {
ASSERT(!previousSibling());
ASSERT(!nextSibling());
ASSERT(!parent || !m_parent);
setParent(parent);
}
void LayoutObject::registerSubtreeChangeListenerOnDescendants(bool value) {
// If we're set to the same value then we're done as that means it's
// set down the tree that way already.
if (m_bitfields.subtreeChangeListenerRegistered() == value)
return;
m_bitfields.setSubtreeChangeListenerRegistered(value);
for (LayoutObject* curr = slowFirstChild(); curr; curr = curr->nextSibling())
curr->registerSubtreeChangeListenerOnDescendants(value);
}
void LayoutObject::notifyAncestorsOfSubtreeChange() {
if (m_bitfields.notifiedOfSubtreeChange())
return;
m_bitfields.setNotifiedOfSubtreeChange(true);
if (parent())
parent()->notifyAncestorsOfSubtreeChange();
}
void LayoutObject::notifyOfSubtreeChange() {
if (!m_bitfields.subtreeChangeListenerRegistered())
return;
if (m_bitfields.notifiedOfSubtreeChange())
return;
notifyAncestorsOfSubtreeChange();
// We can modify the layout tree during layout which means that we may
// try to schedule this during performLayout. This should no longer
// happen when crbug.com/370457 is fixed.
DeprecatedScheduleStyleRecalcDuringLayout marker(document().lifecycle());
document().scheduleLayoutTreeUpdateIfNeeded();
}
void LayoutObject::handleSubtreeModifications() {
ASSERT(wasNotifiedOfSubtreeChange());
ASSERT(document().lifecycle().stateAllowsLayoutTreeNotifications());
if (consumesSubtreeChangeNotification())
subtreeDidChange();
m_bitfields.setNotifiedOfSubtreeChange(false);
for (LayoutObject* object = slowFirstChild(); object;
object = object->nextSibling()) {
if (!object->wasNotifiedOfSubtreeChange())
continue;
object->handleSubtreeModifications();
}
}
LayoutObject* LayoutObject::nextInPreOrder() const {
if (LayoutObject* o = slowFirstChild())
return o;
return nextInPreOrderAfterChildren();
}
LayoutObject* LayoutObject::nextInPreOrderAfterChildren() const {
LayoutObject* o = nextSibling();
if (!o) {
o = parent();
while (o && !o->nextSibling())
o = o->parent();
if (o)
o = o->nextSibling();
}
return o;
}
LayoutObject* LayoutObject::nextInPreOrder(
const LayoutObject* stayWithin) const {
if (LayoutObject* o = slowFirstChild())
return o;
return nextInPreOrderAfterChildren(stayWithin);
}
LayoutObject* LayoutObject::nextInPreOrderAfterChildren(
const LayoutObject* stayWithin) const {
if (this == stayWithin)
return nullptr;
const LayoutObject* current = this;
LayoutObject* next = current->nextSibling();
for (; !next; next = current->nextSibling()) {
current = current->parent();
if (!current || current == stayWithin)
return nullptr;
}
return next;
}
LayoutObject* LayoutObject::previousInPreOrder() const {
if (LayoutObject* o = previousSibling()) {
while (LayoutObject* lastChild = o->slowLastChild())
o = lastChild;
return o;
}
return parent();
}
LayoutObject* LayoutObject::previousInPreOrder(
const LayoutObject* stayWithin) const {
if (this == stayWithin)
return nullptr;
return previousInPreOrder();
}
LayoutObject* LayoutObject::childAt(unsigned index) const {
LayoutObject* child = slowFirstChild();
for (unsigned i = 0; child && i < index; i++)
child = child->nextSibling();
return child;
}
LayoutObject* LayoutObject::lastLeafChild() const {
LayoutObject* r = slowLastChild();
while (r) {
LayoutObject* n = nullptr;
n = r->slowLastChild();
if (!n)
break;
r = n;
}
return r;
}
static void addLayers(LayoutObject* obj,
PaintLayer* parentLayer,
LayoutObject*& newObject,
PaintLayer*& beforeChild) {
if (obj->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(toLayoutBoxModelObject(obj)->layer(), beforeChild);
return;
}
for (LayoutObject* curr = obj->slowFirstChild(); curr;
curr = curr->nextSibling())
addLayers(curr, parentLayer, newObject, beforeChild);
}
void LayoutObject::addLayers(PaintLayer* parentLayer) {
if (!parentLayer)
return;
LayoutObject* object = this;
PaintLayer* beforeChild = nullptr;
blink::addLayers(this, parentLayer, object, beforeChild);
}
void LayoutObject::removeLayers(PaintLayer* parentLayer) {
if (!parentLayer)
return;
if (hasLayer()) {
parentLayer->removeChild(toLayoutBoxModelObject(this)->layer());
return;
}
for (LayoutObject* curr = slowFirstChild(); curr; curr = curr->nextSibling())
curr->removeLayers(parentLayer);
}
void LayoutObject::moveLayers(PaintLayer* oldParent, PaintLayer* newParent) {
if (!newParent)
return;
if (hasLayer()) {
PaintLayer* layer = toLayoutBoxModelObject(this)->layer();
ASSERT(oldParent == layer->parent());
if (oldParent)
oldParent->removeChild(layer);
newParent->addChild(layer);
return;
}
for (LayoutObject* curr = slowFirstChild(); curr; curr = curr->nextSibling())
curr->moveLayers(oldParent, newParent);
}
PaintLayer* LayoutObject::findNextLayer(PaintLayer* parentLayer,
LayoutObject* startPoint,
bool checkParent) {
// Error check the parent layer passed in. If it's null, we can't find
// anything.
if (!parentLayer)
return 0;
// Step 1: If our layer is a child of the desired parent, then return our
// layer.
PaintLayer* ourLayer =
hasLayer() ? toLayoutBoxModelObject(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 (LayoutObject* curr = startPoint ? startPoint->nextSibling()
: slowFirstChild();
curr; curr = curr->nextSibling()) {
PaintLayer* nextLayer = curr->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;
}
PaintLayer* LayoutObject::enclosingLayer() const {
for (const LayoutObject* current = this; current;
current = current->parent()) {
if (current->hasLayer())
return toLayoutBoxModelObject(current)->layer();
}
// TODO(crbug.com/365897): we should get rid of detached layout subtrees, at
// which point this code should not be reached.
return nullptr;
}
PaintLayer* LayoutObject::paintingLayer() const {
for (const LayoutObject* current = this; current;
// Use containingBlock instead of paintInvalidationParent for floating
// object to omit any self-painting layers of inline objects that don't
// paint the floating object.
current = current->isFloating() ? current->containingBlock()
: current->paintInvalidationParent()) {
if (current->hasLayer() &&
toLayoutBoxModelObject(current)->layer()->isSelfPaintingLayer()) {
return toLayoutBoxModelObject(current)->layer();
} else if (current->isColumnSpanAll()) {
// Column spanners paint through their multicolumn containers which can
// be accessed through the associated out-of-flow placeholder's parent.
current = current->spannerPlaceholder();
}
}
// TODO(crbug.com/365897): we should get rid of detached layout subtrees, at
// which point this code should not be reached.
return nullptr;
}
bool LayoutObject::scrollRectToVisible(const LayoutRect& rect,
const ScrollAlignment& alignX,
const ScrollAlignment& alignY,
ScrollType scrollType,
bool makeVisibleInVisualViewport) {
LayoutBox* enclosingBox = this->enclosingBox();
if (!enclosingBox)
return false;
enclosingBox->scrollRectToVisible(rect, alignX, alignY, scrollType,
makeVisibleInVisualViewport);
return true;
}
LayoutBox* LayoutObject::enclosingBox() const {
LayoutObject* curr = const_cast<LayoutObject*>(this);
while (curr) {
if (curr->isBox())
return toLayoutBox(curr);
curr = curr->parent();
}
ASSERT_NOT_REACHED();
return nullptr;
}
LayoutBoxModelObject* LayoutObject::enclosingBoxModelObject() const {
LayoutObject* curr = const_cast<LayoutObject*>(this);
while (curr) {
if (curr->isBoxModelObject())
return toLayoutBoxModelObject(curr);
curr = curr->parent();
}
ASSERT_NOT_REACHED();
return nullptr;
}
LayoutBox* LayoutObject::enclosingScrollableBox() const {
for (LayoutObject* ancestor = parent(); ancestor;
ancestor = ancestor->parent()) {
if (!ancestor->isBox())
continue;
LayoutBox* ancestorBox = toLayoutBox(ancestor);
if (ancestorBox->canBeScrolledAndHasScrollableArea())
return ancestorBox;
}
return nullptr;
}
LayoutFlowThread* LayoutObject::locateFlowThreadContainingBlock() const {
ASSERT(isInsideFlowThread());
// See if we have the thread cached because we're in the middle of layout.
if (LayoutState* layoutState = view()->layoutState()) {
if (LayoutFlowThread* flowThread = layoutState->flowThread())
return flowThread;
}
// Not in the middle of layout so have to find the thread the slow way.
return LayoutFlowThread::locateFlowThreadContainingBlockOf(*this);
}
static inline bool objectIsRelayoutBoundary(const LayoutObject* object) {
// FIXME: In future it may be possible to broaden these conditions in order to
// improve performance.
if (object->isTextControl())
return true;
if (object->isSVGRoot())
return true;
// LayoutInline can't be relayout roots since LayoutBlockFlow is responsible
// for layouting them.
if (object->isLayoutInline())
return false;
// Table parts can't be relayout roots since the table is responsible for
// layouting all the parts.
if (object->isTablePart())
return false;
const ComputedStyle* style = object->style();
if (style->containsLayout() && style->containsSize())
return true;
if (!object->hasOverflowClip())
return false;
// If either dimension is percent-based, intrinsic, or anything but fixed,
// this object cannot form a re-layout boundary. A non-fixed computed logical
// height will allow the object to grow and shrink based on the content
// inside. The same goes for for logical width, if this objects is inside a
// shrink-to-fit container, for instance.
if (!style->width().isFixed() || !style->height().isFixed())
return false;
// Scrollbar parts can be removed during layout. Avoid the complexity of
// having to deal with that.
if (object->isLayoutScrollbarPart())
return false;
// In general we can't relayout a flex item independently of its container;
// not only is the result incorrect due to the override size that's set, it
// also messes with the cached main size on the flexbox.
if (object->isBox() && toLayoutBox(object)->isFlexItem())
return false;
// Inside multicol it's generally problematic to allow relayout roots. The
// multicol container itself may be scheduled for relayout as well (due to
// other changes that may have happened since the previous layout pass),
// which might affect the column heights, which may affect how this object
// breaks across columns). Spanners may also have been added or removed since
// the previous layout pass, which is just another way of affecting the column
// heights (and the number of rows). Instead of identifying cases where it's
// safe to allow relayout roots, just disallow them inside multicol.
if (object->isInsideFlowThread())
return false;
return true;
}
void LayoutObject::markContainerChainForLayout(bool scheduleRelayout,
SubtreeLayoutScope* layouter) {
#if DCHECK_IS_ON()
DCHECK(!isSetNeedsLayoutForbidden());
#endif
ASSERT(!layouter || this != layouter->root());
// When we're in layout, we're marking a descendant as needing layout with
// the intention of visiting it during this layout. We shouldn't be
// scheduling it to be laid out later. Also, scheduleRelayout() must not be
// called while iterating FrameView::m_layoutSubtreeRootList.
scheduleRelayout &= !frameView()->isInPerformLayout();
LayoutObject* object = container();
LayoutObject* last = this;
bool simplifiedNormalFlowLayout = needsSimplifiedNormalFlowLayout() &&
!selfNeedsLayout() &&
!normalChildNeedsLayout();
while (object) {
if (object->selfNeedsLayout())
return;
// Don't mark the outermost object of an unrooted subtree. That object will
// be marked when the subtree is added to the document.
LayoutObject* container = object->container();
if (!container && !object->isLayoutView())
return;
if (!last->isTextOrSVGChild() && last->style()->hasOutOfFlowPosition()) {
object = last->containingBlock();
if (object->posChildNeedsLayout())
return;
container = object->container();
object->setPosChildNeedsLayout(true);
simplifiedNormalFlowLayout = true;
} else if (simplifiedNormalFlowLayout) {
if (object->needsSimplifiedNormalFlowLayout())
return;
object->setNeedsSimplifiedNormalFlowLayout(true);
} else {
if (object->normalChildNeedsLayout())
return;
object->setNormalChildNeedsLayout(true);
}
#if DCHECK_IS_ON()
DCHECK(!object->isSetNeedsLayoutForbidden());
#endif
if (layouter) {
layouter->recordObjectMarkedForLayout(object);
if (object == layouter->root())
return;
}
last = object;
if (scheduleRelayout && objectIsRelayoutBoundary(last))
break;
object = container;
}
if (scheduleRelayout)
last->scheduleRelayout();
}
#if DCHECK_IS_ON()
void LayoutObject::checkBlockPositionedObjectsNeedLayout() {
ASSERT(!needsLayout());
if (isLayoutBlock())
toLayoutBlock(this)->checkPositionedObjectsNeedLayout();
}
#endif
void LayoutObject::setPreferredLogicalWidthsDirty(MarkingBehavior markParents) {
m_bitfields.setPreferredLogicalWidthsDirty(true);
if (markParents == MarkContainerChain &&
(isText() || !style()->hasOutOfFlowPosition()))
invalidateContainerPreferredLogicalWidths();
}
void LayoutObject::clearPreferredLogicalWidthsDirty() {
m_bitfields.setPreferredLogicalWidthsDirty(false);
}
inline void LayoutObject::invalidateContainerPreferredLogicalWidths() {
// In order to avoid pathological behavior when inlines are deeply nested, we
// do include them in the chain that we mark dirty (even though they're kind
// of irrelevant).
LayoutObject* o = isTableCell() ? containingBlock() : container();
while (o && !o->preferredLogicalWidthsDirty()) {
// Don't invalidate the outermost object of an unrooted subtree. That object
// will be invalidated when the subtree is added to the document.
LayoutObject* container =
o->isTableCell() ? o->containingBlock() : o->container();
if (!container && !o->isLayoutView())
break;
o->m_bitfields.setPreferredLogicalWidthsDirty(true);
// A positioned object has no effect on the min/max width of its containing
// block ever. We can optimize this case and not go up any further.
if (o->style()->hasOutOfFlowPosition())
break;
o = container;
}
}
LayoutObject* LayoutObject::containerForAbsolutePosition(
AncestorSkipInfo* skipInfo) const {
// We technically just want our containing block, but we may not have one if
// we're part of an uninstalled subtree. We'll climb as high as we can though.
for (LayoutObject* object = parent(); object; object = object->parent()) {
if (object->canContainAbsolutePositionObjects())
return object;
if (skipInfo)
skipInfo->update(*object);
}
return nullptr;
}
LayoutBlock* LayoutObject::containerForFixedPosition(
AncestorSkipInfo* skipInfo) const {
DCHECK(!isText());
LayoutObject* object = parent();
for (; object && !object->canContainFixedPositionObjects();
object = object->parent()) {
if (skipInfo)
skipInfo->update(*object);
}
ASSERT(!object || !object->isAnonymousBlock());
return toLayoutBlock(object);
}
LayoutBlock* LayoutObject::containingBlockForAbsolutePosition(
AncestorSkipInfo* skipInfo) const {
LayoutObject* object = containerForAbsolutePosition(skipInfo);
// For relpositioned inlines, we return the nearest non-anonymous enclosing
// block. We don't try to return the inline itself. This allows us to avoid
// having a positioned objects list in all LayoutInlines and lets us return a
// strongly-typed LayoutBlock* result from this method. The container() method
// can actually be used to obtain the inline directly.
if (object && object->isInline() && !object->isAtomicInlineLevel()) {
DCHECK(object->style()->hasInFlowPosition());
object = object->containingBlock(skipInfo);
}
if (object && !object->isLayoutBlock())
object = object->containingBlock(skipInfo);
while (object && object->isAnonymousBlock())
object = object->containingBlock(skipInfo);
if (!object || !object->isLayoutBlock())
return nullptr; // This can still happen in case of an orphaned tree
return toLayoutBlock(object);
}
LayoutBlock* LayoutObject::containingBlock(AncestorSkipInfo* skipInfo) const {
LayoutObject* object = parent();
if (!object && isLayoutScrollbarPart())
object = toLayoutScrollbarPart(this)->scrollbarStyleSource();
if (!isTextOrSVGChild()) {
if (m_style->position() == EPosition::kFixed)
return containerForFixedPosition(skipInfo);
if (m_style->position() == EPosition::kAbsolute)
return containingBlockForAbsolutePosition(skipInfo);
}
if (isColumnSpanAll()) {
object = spannerPlaceholder()->containingBlock();
} else {
while (object && ((object->isInline() && !object->isAtomicInlineLevel()) ||
!object->isLayoutBlock())) {
if (skipInfo)
skipInfo->update(*object);
object = object->parent();
}
}
if (!object || !object->isLayoutBlock())
return nullptr; // This can still happen in case of an orphaned tree
return toLayoutBlock(object);
}
FloatRect LayoutObject::absoluteBoundingBoxFloatRect() const {
Vector<FloatQuad> quads;
absoluteQuads(quads);
size_t n = quads.size();
if (n == 0)
return FloatRect();
FloatRect result = quads[0].boundingBox();
for (size_t i = 1; i < n; ++i)
result.unite(quads[i].boundingBox());
return result;
}
IntRect LayoutObject::absoluteBoundingBoxRect(MapCoordinatesFlags flags) const {
Vector<FloatQuad> quads;
absoluteQuads(quads, flags);
size_t n = quads.size();
if (!n)
return IntRect();
IntRect result = quads[0].enclosingBoundingBox();
for (size_t i = 1; i < n; ++i)
result.unite(quads[i].enclosingBoundingBox());
return result;
}
IntRect LayoutObject::absoluteBoundingBoxRectIgnoringTransforms() const {
FloatPoint absPos = localToAbsolute();
Vector<IntRect> rects;
absoluteRects(rects, flooredLayoutPoint(absPos));
size_t n = rects.size();
if (!n)
return IntRect();
IntRect result = rects[0];
for (size_t i = 1; i < n; ++i)
result.unite(rects[i]);
return result;
}
IntRect LayoutObject::absoluteElementBoundingBoxRect() const {
Vector<LayoutRect> rects;
const LayoutBoxModelObject& container = enclosingLayer()->layoutObject();
addElementVisualOverflowRects(
rects, LayoutPoint(localToAncestorPoint(FloatPoint(), &container)));
return container.localToAbsoluteQuad(FloatQuad(FloatRect(unionRect(rects))))
.enclosingBoundingBox();
}
FloatRect LayoutObject::absoluteBoundingBoxRectForRange(const Range* range) {
if (!range || !range->startContainer())
return FloatRect();
range->ownerDocument().updateStyleAndLayout();
Vector<FloatQuad> quads;
range->textQuads(quads);
FloatRect result;
for (size_t i = 0; i < quads.size(); ++i)
result.unite(quads[i].boundingBox());
return result;
}
void LayoutObject::addAbsoluteRectForLayer(IntRect& result) {
if (hasLayer())
result.unite(absoluteBoundingBoxRect());
for (LayoutObject* current = slowFirstChild(); current;
current = current->nextSibling())
current->addAbsoluteRectForLayer(result);
}
IntRect LayoutObject::absoluteBoundingBoxRectIncludingDescendants() const {
IntRect result = absoluteBoundingBoxRect();
for (LayoutObject* current = slowFirstChild(); current;
current = current->nextSibling())
current->addAbsoluteRectForLayer(result);
return result;
}
void LayoutObject::paint(const PaintInfo&, const LayoutPoint&) const {}
const LayoutBoxModelObject& LayoutObject::containerForPaintInvalidation()
const {
CHECK(isRooted());
if (const LayoutBoxModelObject* paintInvalidationContainer =
enclosingCompositedContainer())
return *paintInvalidationContainer;
// If the current frame is not composited, we send just return the main
// frame's LayoutView so that we generate invalidations on the window.
const LayoutView* layoutView = view();
while (const LayoutObject* ownerObject = LayoutAPIShim::constLayoutObjectFrom(
layoutView->frame()->ownerLayoutItem()))
layoutView = ownerObject->view();
DCHECK(layoutView);
return *layoutView;
}
const LayoutBoxModelObject* LayoutObject::enclosingCompositedContainer() const {
LayoutBoxModelObject* container = nullptr;
// FIXME: CompositingState is not necessarily up to date for many callers of
// this function.
DisableCompositingQueryAsserts disabler;
if (PaintLayer* paintingLayer = this->paintingLayer()) {
if (PaintLayer* compositingLayer =
paintingLayer
->enclosingLayerForPaintInvalidationCrossingFrameBoundaries())
container = &compositingLayer->layoutObject();
}
return container;
}
String LayoutObject::decoratedName() const {
StringBuilder name;
name.append(this->name());
if (isAnonymous())
name.append(" (anonymous)");
// FIXME: Remove the special case for LayoutView here (requires rebaseline of
// all tests).
if (isOutOfFlowPositioned() && !isLayoutView())
name.append(" (positioned)");
if (isRelPositioned())
name.append(" (relative positioned)");
if (isStickyPositioned())
name.append(" (sticky positioned)");
if (isFloating())
name.append(" (floating)");
if (spannerPlaceholder())
name.append(" (column spanner)");
return name.toString();
}
String LayoutObject::debugName() const {
StringBuilder name;
name.append(decoratedName());
if (const Node* node = this->node()) {
name.append(' ');
name.append(node->debugName());
}
return name.toString();
}
bool LayoutObject::isPaintInvalidationContainer() const {
return hasLayer() &&
toLayoutBoxModelObject(this)->layer()->isPaintInvalidationContainer();
}
void LayoutObject::invalidateDisplayItemClients(
PaintInvalidationReason reason) const {
// This default implementation invalidates only the object itself as a
// DisplayItemClient.
ObjectPaintInvalidator(*this).invalidateDisplayItemClient(*this, reason);
}
bool LayoutObject::compositedScrollsWithRespectTo(
const LayoutBoxModelObject& paintInvalidationContainer) const {
return paintInvalidationContainer.usesCompositedScrolling() &&
this != &paintInvalidationContainer;
}
IntSize LayoutObject::scrollAdjustmentForPaintInvalidation(
const LayoutBoxModelObject& paintInvalidationContainer) const {
// Non-composited scrolling should be included in the bounds of scrolleditems.
// Since mapToVisualRectInAncestorSpace does not include scrolling of the
// ancestor, we need to add it back in after.
if (paintInvalidationContainer.isBox() &&
!paintInvalidationContainer.usesCompositedScrolling() &&
this != &paintInvalidationContainer) {
const LayoutBox* box = toLayoutBox(&paintInvalidationContainer);
if (box->hasOverflowClip())
return -box->scrolledContentOffset();
}
return IntSize();
}
LayoutRect LayoutObject::invalidatePaintRectangle(
const LayoutRect& dirtyRect,
DisplayItemClient* displayItemClient) const {
return ObjectPaintInvalidator(*this).invalidatePaintRectangle(
dirtyRect, displayItemClient);
}
void LayoutObject::invalidateTreeIfNeeded(
const PaintInvalidationState& paintInvalidationState) {
DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled());
ensureIsReadyForPaintInvalidation();
// If we didn't need paint invalidation then our children don't need as well.
// Skip walking down the tree as everything should be fine below us.
if (!shouldCheckForPaintInvalidation(paintInvalidationState))
return;
PaintInvalidationState newPaintInvalidationState(paintInvalidationState,
*this);
if (mayNeedPaintInvalidationSubtree()) {
newPaintInvalidationState
.setForceSubtreeInvalidationCheckingWithinContainer();
}
PaintInvalidationReason reason =
invalidatePaintIfNeeded(newPaintInvalidationState);
newPaintInvalidationState.updateForChildren(reason);
invalidatePaintOfSubtreesIfNeeded(newPaintInvalidationState);
clearPaintInvalidationFlags();
}
DISABLE_CFI_PERF
void LayoutObject::invalidatePaintOfSubtreesIfNeeded(
const PaintInvalidationState& childPaintInvalidationState) {
DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled());
for (auto* child = slowFirstChild(); child; child = child->nextSibling())
child->invalidateTreeIfNeeded(childPaintInvalidationState);
}
LayoutRect LayoutObject::selectionRectInViewCoordinates() const {
LayoutRect selectionRect = localSelectionRect();
if (!selectionRect.isEmpty())
mapToVisualRectInAncestorSpace(view(), selectionRect);
return selectionRect;
}
PaintInvalidationReason LayoutObject::invalidatePaintIfNeeded(
const PaintInvalidationState& paintInvalidationState) {
DCHECK(&paintInvalidationState.currentObject() == this);
if (styleRef().hasOutline()) {
PaintLayer& layer = paintInvalidationState.paintingLayer();
if (&layer.layoutObject() != this)
layer.setNeedsPaintPhaseDescendantOutlines();
}
LayoutView* v = view();
if (v->document().printing())
return PaintInvalidationNone; // Don't invalidate paints if we're printing.
PaintInvalidatorContextAdapter context(paintInvalidationState);
const LayoutBoxModelObject& paintInvalidationContainer =
paintInvalidationState.paintInvalidationContainer();
DCHECK(paintInvalidationContainer == containerForPaintInvalidation());
ObjectPaintInvalidator paintInvalidator(*this);
context.oldVisualRect = visualRect();
context.oldLocation = paintInvalidator.locationInBacking();
LayoutRect newVisualRect =
paintInvalidationState.computeVisualRectInBacking();
context.newLocation =
paintInvalidationState.computeLocationInBacking(newVisualRect.location());
IntSize adjustment =
scrollAdjustmentForPaintInvalidation(paintInvalidationContainer);
context.newLocation.move(adjustment);
newVisualRect.move(adjustment);
adjustVisualRectForRasterEffects(newVisualRect);
setVisualRect(newVisualRect);
paintInvalidator.setLocationInBacking(context.newLocation);
if (!shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState() &&
paintInvalidationState
.forcedSubtreeInvalidationRectUpdateWithinContainerOnly()) {
// We are done updating the visual rect. No other paint invalidation work
// to do for this object.
return PaintInvalidationNone;
}
return invalidatePaintIfNeeded(context);
}
DISABLE_CFI_PERF
PaintInvalidationReason LayoutObject::invalidatePaintIfNeeded(
const PaintInvalidatorContext& context) const {
return ObjectPaintInvalidatorWithContext(*this, context)
.invalidatePaintIfNeeded();
}
void LayoutObject::adjustVisualRectForCompositedScrolling(
LayoutRect& rect,
const LayoutBoxModelObject& paintInvalidationContainer) const {
if (compositedScrollsWithRespectTo(paintInvalidationContainer)) {
LayoutSize offset(
-toLayoutBox(&paintInvalidationContainer)->scrolledContentOffset());
rect.move(offset);
}
}
LayoutRect LayoutObject::visualRectIncludingCompositedScrolling(
const LayoutBoxModelObject& paintInvalidationContainer) const {
LayoutRect rect = visualRect();
adjustVisualRectForCompositedScrolling(rect, paintInvalidationContainer);
return rect;
}
void LayoutObject::clearPreviousVisualRects() {
setVisualRect(LayoutRect());
ObjectPaintInvalidator(*this).setLocationInBacking(LayoutPoint());
// Ensure check paint invalidation of subtree that would be triggered by
// location change if we had valid previous location.
setMayNeedPaintInvalidationSubtree();
// After clearing ("invalidating") the visual rects, mark this object as
// needing to re-compute them.
setShouldDoFullPaintInvalidation();
}
LayoutRect LayoutObject::absoluteVisualRect() const {
LayoutRect rect = localVisualRect();
mapToVisualRectInAncestorSpace(view(), rect);
return rect;
}
LayoutRect LayoutObject::localVisualRect() const {
NOTREACHED();
return LayoutRect();
}
bool LayoutObject::mapToVisualRectInAncestorSpace(
const LayoutBoxModelObject* ancestor,
LayoutRect& rect,
VisualRectFlags visualRectFlags) const {
TransformState transformState(TransformState::ApplyTransformDirection,
FloatQuad(FloatRect(rect)));
bool retval = mapToVisualRectInAncestorSpaceInternal(ancestor, transformState,
visualRectFlags);
transformState.flatten();
rect = LayoutRect(transformState.lastPlanarQuad().boundingBox());
return retval;
}
bool LayoutObject::mapToVisualRectInAncestorSpaceInternal(
const LayoutBoxModelObject* ancestor,
TransformState& transformState,
VisualRectFlags visualRectFlags) const {
// For any layout object that doesn't override this method (the main example
// is LayoutText), the rect is assumed to be in the parent's coordinate space,
// except for container flip.
if (ancestor == this)
return true;
if (LayoutObject* parent = this->parent()) {
if (parent->isBox()) {
LayoutBox* parentBox = toLayoutBox(parent);
// Never flip for SVG as it handles writing modes itself.
if (!isSVG()) {
transformState.flatten();
LayoutRect rect(transformState.lastPlanarQuad().boundingBox());
parentBox->flipForWritingMode(rect);
transformState.setQuad(FloatQuad(FloatRect(rect)));
}
bool preserve3D = parent->style()->preserves3D() && !parent->isText();
TransformState::TransformAccumulation accumulation =
preserve3D ? TransformState::AccumulateTransform
: TransformState::FlattenTransform;
if (parent != ancestor &&
!parentBox->mapScrollingContentsRectToBoxSpace(
transformState, accumulation, visualRectFlags))
return false;
}
return parent->mapToVisualRectInAncestorSpaceInternal(
ancestor, transformState, visualRectFlags);
}
return true;
}
void LayoutObject::dirtyLinesFromChangedChild(LayoutObject*, MarkingBehavior) {}
#ifndef NDEBUG
void LayoutObject::showTreeForThis() const {
if (node())
::showTree(node());
}
void LayoutObject::showLayoutTreeForThis() const {
showLayoutTree(this, 0);
}
void LayoutObject::showLineTreeForThis() const {
if (LayoutBlock* cb = containingBlock()) {
if (cb->isLayoutBlockFlow())
toLayoutBlockFlow(cb)->showLineTreeAndMark(nullptr, nullptr, nullptr,
nullptr, this);
}
}
void LayoutObject::showLayoutObject() const {
StringBuilder stringBuilder;
showLayoutObject(stringBuilder);
}
void LayoutObject::showLayoutObject(StringBuilder& stringBuilder) const {
stringBuilder.append(
String::format("%s %p", decoratedName().ascii().data(), this));
if (isText() && toLayoutText(this)->isTextFragment())
stringBuilder.append(
String::format(" \"%s\" ", toLayoutText(this)->text().ascii().data()));
if (virtualContinuation())
stringBuilder.append(
String::format(" continuation=%p", virtualContinuation()));
if (node()) {
while (stringBuilder.length() < showTreeCharacterOffset)
stringBuilder.append(' ');
stringBuilder.append('\t');
WTFLogAlways("%s%s", stringBuilder.toString().utf8().data(),
node()->toString().utf8().data());
} else {
WTFLogAlways("%s", stringBuilder.toString().utf8().data());
}
}
void LayoutObject::showLayoutTreeAndMark(const LayoutObject* markedObject1,
const char* markedLabel1,
const LayoutObject* markedObject2,
const char* markedLabel2,
unsigned depth) const {
StringBuilder stringBuilder;
if (markedObject1 == this && markedLabel1)
stringBuilder.append(markedLabel1);
if (markedObject2 == this && markedLabel2)
stringBuilder.append(markedLabel2);
while (stringBuilder.length() < depth * 2)
stringBuilder.append(' ');
showLayoutObject(stringBuilder);
for (const LayoutObject* child = slowFirstChild(); child;
child = child->nextSibling())
child->showLayoutTreeAndMark(markedObject1, markedLabel1, markedObject2,
markedLabel2, depth + 1);
}
#endif // NDEBUG
bool LayoutObject::isSelectable() const {
return !isInert() &&
!(style()->userSelect() == SELECT_NONE &&
style()->userModify() == READ_ONLY);
}
Color LayoutObject::selectionBackgroundColor() const {
if (!isSelectable())
return Color::transparent;
if (RefPtr<ComputedStyle> pseudoStyle = getUncachedSelectionStyle())
return resolveColor(*pseudoStyle, CSSPropertyBackgroundColor)
.blendWithWhite();
return frame()->selection().isFocusedAndActive()
? LayoutTheme::theme().activeSelectionBackgroundColor()
: LayoutTheme::theme().inactiveSelectionBackgroundColor();
}
Color LayoutObject::selectionColor(
int colorProperty,
const GlobalPaintFlags globalPaintFlags) const {
// If the element is unselectable, or we are only painting the selection,
// don't override the foreground color with the selection foreground color.
if (!isSelectable() || (globalPaintFlags & GlobalPaintSelectionOnly))
return resolveColor(colorProperty);
if (RefPtr<ComputedStyle> pseudoStyle = getUncachedSelectionStyle())
return resolveColor(*pseudoStyle, colorProperty);
if (!LayoutTheme::theme().supportsSelectionForegroundColors())
return resolveColor(colorProperty);
return frame()->selection().isFocusedAndActive()
? LayoutTheme::theme().activeSelectionForegroundColor()
: LayoutTheme::theme().inactiveSelectionForegroundColor();
}
Color LayoutObject::selectionForegroundColor(
const GlobalPaintFlags globalPaintFlags) const {
return selectionColor(CSSPropertyWebkitTextFillColor, globalPaintFlags);
}
Color LayoutObject::selectionEmphasisMarkColor(
const GlobalPaintFlags globalPaintFlags) const {
return selectionColor(CSSPropertyWebkitTextEmphasisColor, globalPaintFlags);
}
void LayoutObject::selectionStartEnd(int& spos, int& epos) const {
view()->selectionStartEnd(spos, epos);
}
// Called when an object that was floating or positioned becomes a normal flow
// object again. We have to make sure the layout tree updates as needed to
// accommodate the new normal flow object.
static inline void handleDynamicFloatPositionChange(LayoutObject* object) {
// We have gone from not affecting the inline status of the parent flow to
// suddenly having an impact. See if there is a mismatch between the parent
// flow's childrenInline() state and our state.
object->setInline(object->style()->isDisplayInlineType());
if (object->isInline() != object->parent()->childrenInline()) {
if (!object->isInline()) {
toLayoutBoxModelObject(object->parent())->childBecameNonInline(object);
} else {
// An anonymous block must be made to wrap this inline.
LayoutBlock* block =
toLayoutBlock(object->parent())->createAnonymousBlock();
LayoutObjectChildList* childlist = object->parent()->virtualChildren();
childlist->insertChildNode(object->parent(), block, object);
block->children()->appendChildNode(
block, childlist->removeChildNode(object->parent(), object));
}
}
}
StyleDifference LayoutObject::adjustStyleDifference(
StyleDifference diff) const {
if (diff.transformChanged() && isSVG()) {
// Skip a full layout for transforms at the html/svg boundary which do not
// affect sizes inside SVG.
if (!isSVGRoot())
diff.setNeedsFullLayout();
}
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
// If transform changed, and the layer does not paint into its own separate
// backing, then we need to invalidate paints.
if (diff.transformChanged()) {
// Text nodes share style with their parents but transforms don't apply to
// them, hence the !isText() check.
if (!isText() && (!hasLayer() ||
!toLayoutBoxModelObject(this)
->layer()
->hasStyleDeterminedDirectCompositingReasons()))
diff.setNeedsPaintInvalidationSubtree();
}
// If opacity or zIndex changed, and the layer does not paint into its own
// separate backing, then we need to invalidate paints (also
// ignoring text nodes).
if (diff.opacityChanged() || diff.zIndexChanged()) {
if (!isText() && (!hasLayer() ||
!toLayoutBoxModelObject(this)
->layer()
->hasStyleDeterminedDirectCompositingReasons()))
diff.setNeedsPaintInvalidationSubtree();
}
// If filter changed, and the layer does not paint into its own separate
// backing or it paints with filters, then we need to invalidate paints.
if (diff.filterChanged() && hasLayer()) {
PaintLayer* layer = toLayoutBoxModelObject(this)->layer();
if (!layer->hasStyleDeterminedDirectCompositingReasons() ||
layer->paintsWithFilters())
diff.setNeedsPaintInvalidationSubtree();
}
// If backdrop filter changed, and the layer does not paint into its own
// separate backing or it paints with filters, then we need to invalidate
// paints.
if (diff.backdropFilterChanged() && hasLayer()) {
PaintLayer* layer = toLayoutBoxModelObject(this)->layer();
if (!layer->hasStyleDeterminedDirectCompositingReasons() ||
layer->paintsWithBackdropFilters())
diff.setNeedsPaintInvalidationSubtree();
}
}
// TODO(wangxianzhu): We may avoid subtree paint invalidation on CSS clip
// change for SPv2.
if (diff.cssClipChanged())
diff.setNeedsPaintInvalidationSubtree();
// Optimization: for decoration/color property changes, invalidation is only
// needed if we have style or text affected by these properties.
if (diff.textDecorationOrColorChanged() &&
!diff.needsFullPaintInvalidation()) {
if (style()->hasBorder() || style()->hasOutline() ||
style()->hasBackgroundRelatedColorReferencingCurrentColor() ||
// Skip any text nodes that do not contain text boxes. Whitespace cannot
// be skipped or we will miss invalidating decorations (e.g.,
// underlines).
(isText() && !isBR() && toLayoutText(this)->hasTextBoxes()) ||
(isSVG() && style()->svgStyle().isFillColorCurrentColor()) ||
(isSVG() && style()->svgStyle().isStrokeColorCurrentColor()) ||
isListMarker())
diff.setNeedsPaintInvalidationObject();
}
// The answer to layerTypeRequired() 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.needsFullLayout() && style() && isBoxModelObject()) {
bool requiresLayer =
toLayoutBoxModelObject(this)->layerTypeRequired() != NoPaintLayer;
if (hasLayer() != requiresLayer)
diff.setNeedsFullLayout();
}
return diff;
}
void LayoutObject::setPseudoStyle(PassRefPtr<ComputedStyle> pseudoStyle) {
ASSERT(pseudoStyle->styleType() == PseudoIdBefore ||
pseudoStyle->styleType() == PseudoIdAfter ||
pseudoStyle->styleType() == PseudoIdFirstLetter);
// FIXME: We should consider just making all pseudo items use an inherited
// style.
// Images are special and must inherit the pseudoStyle so the width and height
// of the pseudo element doesn't change the size of the image. In all other
// cases we can just share the style.
//
// Quotes are also LayoutInline, so we need to create an inherited style to
// avoid getting an inline with positioning or an invalid display.
//
if (isImage() || isQuote()) {
RefPtr<ComputedStyle> style = ComputedStyle::create();
style->inheritFrom(*pseudoStyle);
setStyle(std::move(style));
return;
}
setStyle(std::move(pseudoStyle));
}
void LayoutObject::firstLineStyleDidChange(const ComputedStyle& oldStyle,
const ComputedStyle& newStyle) {
StyleDifference diff = oldStyle.visualInvalidationDiff(newStyle);
if (diff.needsFullPaintInvalidation() ||
diff.textDecorationOrColorChanged()) {
// We need to invalidate all inline boxes in the first line, because they
// need to be repainted with the new style, e.g. background, font style,
// etc.
LayoutBlockFlow* firstLineContainer = nullptr;
if (behavesLikeBlockContainer()) {
// This object is a LayoutBlock having PseudoIdFirstLine pseudo style
// changed.
firstLineContainer =
toLayoutBlock(this)->nearestInnerBlockWithFirstLine();
} else if (isLayoutInline()) {
// This object is a LayoutInline having FIRST_LINE_INHERITED pesudo style
// changed. This method can be called even if the LayoutInline doesn't
// intersect the first line, but we only need to invalidate if it does.
if (InlineBox* firstLineBox =
toLayoutInline(this)->firstLineBoxIncludingCulling()) {
if (firstLineBox->isFirstLineStyle())
firstLineContainer = toLayoutBlockFlow(containingBlock());
}
}
if (firstLineContainer)
firstLineContainer->setShouldDoFullPaintInvalidationForFirstLine();
}
if (diff.needsLayout())
setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::StyleChange);
}
void LayoutObject::markAncestorsForOverflowRecalcIfNeeded() {
LayoutObject* object = this;
do {
// Cell and row need to propagate the flag to their containing section and
// row as their containing block is the table wrapper.
// This enables us to only recompute overflow the modified sections / rows.
object = object->isTableCell() || object->isTableRow()
? object->parent()
: object->containingBlock();
if (object)
object->setChildNeedsOverflowRecalcAfterStyleChange();
} while (object);
}
void LayoutObject::setNeedsOverflowRecalcAfterStyleChange() {
bool neededRecalc = needsOverflowRecalcAfterStyleChange();
setSelfNeedsOverflowRecalcAfterStyleChange();
if (!neededRecalc)
markAncestorsForOverflowRecalcIfNeeded();
}
DISABLE_CFI_PERF
void LayoutObject::setStyle(PassRefPtr<ComputedStyle> style) {
ASSERT(style);
if (m_style == style) {
// 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(!isLayoutIFrame() && !isEmbeddedObject() && !isCanvas());
return;
}
StyleDifference diff;
if (m_style)
diff = m_style->visualInvalidationDiff(*style);
diff = adjustStyleDifference(diff);
styleWillChange(diff, *style);
RefPtr<ComputedStyle> oldStyle = std::move(m_style);
setStyleInternal(std::move(style));
updateFillImages(oldStyle ? &oldStyle->backgroundLayers() : 0,
m_style->backgroundLayers());
updateFillImages(oldStyle ? &oldStyle->maskLayers() : 0,
m_style->maskLayers());
updateImage(oldStyle ? oldStyle->borderImage().image() : 0,
m_style->borderImage().image());
updateImage(oldStyle ? oldStyle->maskBoxImage().image() : 0,
m_style->maskBoxImage().image());
StyleImage* newContentImage =
m_style->contentData() && m_style->contentData()->isImage()
? toImageContentData(m_style->contentData())->image()
: nullptr;
StyleImage* oldContentImage =
oldStyle && oldStyle->contentData() && oldStyle->contentData()->isImage()
? toImageContentData(oldStyle->contentData())->image()
: nullptr;
updateImage(oldContentImage, newContentImage);
StyleImage* newBoxReflectMaskImage =
m_style->boxReflect() ? m_style->boxReflect()->mask().image() : nullptr;
StyleImage* oldBoxReflectMaskImage =
oldStyle && oldStyle->boxReflect()
? oldStyle->boxReflect()->mask().image()
: nullptr;
updateImage(oldBoxReflectMaskImage, newBoxReflectMaskImage);
updateShapeImage(oldStyle ? oldStyle->shapeOutside() : 0,
m_style->shapeOutside());
updateCursorImages(oldStyle ? oldStyle->cursors() : nullptr,
m_style->cursors());
bool doesNotNeedLayoutOrPaintInvalidation = !m_parent;
styleDidChange(diff, oldStyle.get());
// FIXME: |this| might be destroyed here. This can currently happen for a
// LayoutTextFragment when its first-letter block gets an update in
// LayoutTextFragment::styleDidChange. For LayoutTextFragment(s),
// we will safely bail out with the doesNotNeedLayoutOrPaintInvalidation flag.
// We might want to broaden this condition in the future as we move
// layoutObject changes out of layout and into style changes.
if (doesNotNeedLayoutOrPaintInvalidation)
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
// invalidate paints.
StyleDifference updatedDiff = adjustStyleDifference(diff);
if (!diff.needsFullLayout()) {
if (updatedDiff.needsFullLayout())
setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::StyleChange);
else if (updatedDiff.needsPositionedMovementLayout())
setNeedsPositionedMovementLayout();
}
if (diff.transformChanged() && !needsLayout()) {
if (LayoutBlock* container = containingBlock())
container->setNeedsOverflowRecalcAfterStyleChange();
}
if (diff.needsRecomputeOverflow() && !needsLayout()) {
// TODO(rhogan): Make inlines capable of recomputing overflow too.
if (isLayoutBlock())
setNeedsOverflowRecalcAfterStyleChange();
else
setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::StyleChange);
}
if (diff.needsPaintInvalidationSubtree() ||
updatedDiff.needsPaintInvalidationSubtree())
setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants();
else if (diff.needsPaintInvalidationObject() ||
updatedDiff.needsPaintInvalidationObject())
setShouldDoFullPaintInvalidation();
// Text nodes share style with their parents but the paint properties don't
// apply to them, hence the !isText() check.
if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() && !isText() &&
(diff.transformChanged() || diff.opacityChanged() ||
diff.zIndexChanged() || diff.filterChanged() ||
diff.backdropFilterChanged() || diff.cssClipChanged())) {
setNeedsPaintPropertyUpdate();
// We don't need to invalidate paint of objects on SPv2 when only paint
// property or paint order change. Mark the painting layer needing repaint
// for changed paint property or paint order. Raster invalidation will be
// issued if needed during paint.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() &&
!shouldDoFullPaintInvalidation())
ObjectPaintInvalidator(*this).slowSetPaintingLayerNeedsRepaint();
}
}
void LayoutObject::styleWillChange(StyleDifference diff,
const ComputedStyle& newStyle) {
if (m_style) {
// 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->isStackingContext() != newStyle.isStackingContext();
if (visibilityChanged) {
document().setAnnotatedRegionsDirty(true);
if (AXObjectCache* cache = document().existingAXObjectCache())
cache->childrenChanged(parent());
}
// Keep layer hierarchy visibility bits up to date if visibility changes.
if (m_style->visibility() != newStyle.visibility()) {
// We might not have an enclosing layer yet because we might not be in the
// tree.
if (PaintLayer* layer = enclosingLayer())
layer->dirtyVisibleContentStatus();
}
if (isFloating() && (m_style->floating() != newStyle.floating())) {
// For changes in float styles, we need to conceivably remove ourselves
// from the floating objects list.
toLayoutBox(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.
toLayoutBox(this)->removeFloatingOrPositionedChildFromBlockLists();
}
s_affectsParentBlock =
isFloatingOrOutOfFlowPositioned() &&
(!newStyle.isFloating() && !newStyle.hasOutOfFlowPosition()) &&
parent() &&
(parent()->isLayoutBlockFlow() || parent()->isLayoutInline());
// Clearing these bits is required to avoid leaving stale layoutObjects.
// FIXME: We shouldn't need that hack if our logic was totally correct.
if (diff.needsLayout()) {
setFloating(false);
clearPositionedState();
}
} else {
s_affectsParentBlock = false;
}
// Elements with non-auto touch-action will send a SetTouchAction message
// on touchstart in EventHandler::handleTouchEvent, and so effectively have
// a touchstart handler that must be reported.
//
// Since a CSS property cannot be applied directly to a text node, a
// handler will have already been added for its parent so ignore it.
// TODO: Remove this blocking event handler; crbug.com/318381
TouchAction oldTouchAction =
m_style ? m_style->getTouchAction() : TouchActionAuto;
if (node() && !node()->isTextNode() &&
(oldTouchAction == TouchActionAuto) !=
(newStyle.getTouchAction() == TouchActionAuto)) {
EventHandlerRegistry& registry = document().page()->eventHandlerRegistry();
if (newStyle.getTouchAction() != TouchActionAuto)
registry.didAddEventHandler(
*node(), EventHandlerRegistry::TouchStartOrMoveEventBlocking);
else
registry.didRemoveEventHandler(
*node(), EventHandlerRegistry::TouchStartOrMoveEventBlocking);
}
}
void LayoutObject::clearBaseComputedStyle() {
if (!node())
return;
if (!node()->isElementNode())
return;
if (ElementAnimations* animations = toElement(node())->elementAnimations())
animations->clearBaseComputedStyle();
}
static bool areNonIdenticalCursorListsEqual(const ComputedStyle* a,
const ComputedStyle* b) {
ASSERT(a->cursors() != b->cursors());
return a->cursors() && b->cursors() && *a->cursors() == *b->cursors();
}
static inline bool areCursorsEqual(const ComputedStyle* a,
const ComputedStyle* b) {
return a->cursor() == b->cursor() && (a->cursors() == b->cursors() ||
areNonIdenticalCursorListsEqual(a, b));
}
void LayoutObject::setScrollAnchorDisablingStyleChangedOnAncestor() {
// Walk up the parent chain and find the first scrolling block to disable
// scroll anchoring on.
LayoutObject* object = parent();
Element* viewportDefiningElement = document().viewportDefiningElement();
while (object) {
if (object->isLayoutBlock()) {
LayoutBlock* block = toLayoutBlock(object);
if (block->hasOverflowClip() ||
block->node() == viewportDefiningElement) {
block->setScrollAnchorDisablingStyleChanged(true);
return;
}
}
object = object->parent();
}
}
void LayoutObject::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
// First assume the outline will be affected. It may be updated when we know
// it's not affected.
bool hasOutline = m_style->hasOutline();
setOutlineMayBeAffectedByDescendants(hasOutline);
if (!hasOutline)
setPreviousOutlineMayBeAffectedByDescendants(false);
if (s_affectsParentBlock)
handleDynamicFloatPositionChange(this);
if (!m_parent)
return;
if (diff.needsFullLayout()) {
LayoutCounter::layoutObjectStyleChanged(*this, oldStyle, *m_style);
// If the in-flow state of an element is changed, disable scroll
// anchoring on the containing scroller.
if (oldStyle->hasOutOfFlowPosition() != m_style->hasOutOfFlowPosition())
setScrollAnchorDisablingStyleChangedOnAncestor();
// 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())
markContainerChainForLayout();
// Ditto.
if (needsOverflowRecalcAfterStyleChange() &&
oldStyle->position() != m_style->position())
markAncestorsForOverflowRecalcIfNeeded();
setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::StyleChange);
} else if (diff.needsPositionedMovementLayout()) {
setNeedsPositionedMovementLayout();
}
if (diff.scrollAnchorDisablingPropertyChanged())
setScrollAnchorDisablingStyleChanged(true);
// Don't check for paint invalidation here; we need to wait until the layer
// has been updated by subclasses before we know if we have to invalidate
// paints (in setStyle()).
if (oldStyle && !areCursorsEqual(oldStyle, style())) {
if (LocalFrame* frame = this->frame()) {
// Cursor update scheduling is done by the local root, which is the main
// frame if there are no RemoteFrame ancestors in the frame tree. Use of
// localFrameRoot() is discouraged but will change when cursor update
// scheduling is moved from EventHandler to PageEventHandler.
frame->localFrameRoot()->eventHandler().scheduleCursorUpdate();
}
}
if (diff.needsFullPaintInvalidation() && oldStyle) {
if (resolveColor(*oldStyle, CSSPropertyBackgroundColor) !=
resolveColor(CSSPropertyBackgroundColor) ||
oldStyle->backgroundLayers() != styleRef().backgroundLayers())
setBackgroundChangedSinceLastPaintInvalidation();
}
if (oldStyle && oldStyle->styleType() == PseudoIdNone)
applyPseudoStyleChanges(*oldStyle);
}
void LayoutObject::applyPseudoStyleChanges(const ComputedStyle& oldStyle) {
if (oldStyle.hasPseudoStyle(PseudoIdFirstLine) ||
styleRef().hasPseudoStyle(PseudoIdFirstLine))
applyFirstLineChanges(oldStyle);
if (oldStyle.hasPseudoStyle(PseudoIdSelection) ||
styleRef().hasPseudoStyle(PseudoIdSelection))
invalidatePaintForSelection();
}
void LayoutObject::applyFirstLineChanges(const ComputedStyle& oldStyle) {
if (oldStyle.hasPseudoStyle(PseudoIdFirstLine)) {
RefPtr<ComputedStyle> oldPseudoStyle =
oldStyle.getCachedPseudoStyle(PseudoIdFirstLine);
if (styleRef().hasPseudoStyle(PseudoIdFirstLine) && oldPseudoStyle) {
RefPtr<ComputedStyle> newPseudoStyle =
uncachedFirstLineStyle(mutableStyle());
if (newPseudoStyle) {
firstLineStyleDidChange(*oldPseudoStyle, *newPseudoStyle);
return;
}
}
}
setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::StyleChange);
}
void LayoutObject::propagateStyleToAnonymousChildren() {
// FIXME: We could save this call when the change only affected non-inherited
// properties.
for (LayoutObject* child = slowFirstChild(); child;
child = child->nextSibling()) {
if (!child->isAnonymous() || child->style()->styleType() != PseudoIdNone)
continue;
if (child->anonymousHasStylePropagationOverride())
continue;
RefPtr<ComputedStyle> newStyle =
ComputedStyle::createAnonymousStyleWithDisplay(
styleRef(), child->style()->display());
// Preserve the position style of anonymous block continuations as they can
// have relative position when they contain block descendants of relative
// positioned inlines.
if (child->isInFlowPositioned() && child->isLayoutBlockFlow() &&
toLayoutBlockFlow(child)->isAnonymousBlockContinuation())
newStyle->setPosition(child->style()->position());
updateAnonymousChildStyle(*child, *newStyle);
child->setStyle(std::move(newStyle));
}
}
void LayoutObject::setStyleWithWritingModeOf(PassRefPtr<ComputedStyle> style,
LayoutObject* parent) {
if (parent)
style->setWritingMode(parent->styleRef().getWritingMode());
setStyle(std::move(style));
}
void LayoutObject::setStyleWithWritingModeOfParent(
PassRefPtr<ComputedStyle> style) {
setStyleWithWritingModeOf(std::move(style), parent());
}
void LayoutObject::addChildWithWritingModeOfParent(LayoutObject* newChild,
LayoutObject* beforeChild) {
const WritingMode oldWritingMode =
newChild->mutableStyleRef().getWritingMode();
const WritingMode newWritingMode = styleRef().getWritingMode();
if (oldWritingMode != newWritingMode && newChild->isBoxModelObject()) {
newChild->mutableStyleRef().setWritingMode(newWritingMode);
newChild->setHorizontalWritingMode(isHorizontalWritingMode());
}
addChild(newChild, beforeChild);
}
void LayoutObject::updateFillImages(const FillLayer* oldLayers,
const FillLayer& newLayers) {
// Optimize the common case
if (FillLayer::imagesIdentical(oldLayers, &newLayers))
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 LayoutObject::updateCursorImages(const CursorList* oldCursors,
const CursorList* newCursors) {
if (oldCursors && newCursors && *oldCursors == *newCursors)
return;
if (newCursors) {
for (const CursorData& cursorNew : *newCursors) {
if (cursorNew.image())
cursorNew.image()->addClient(this);
}
}
removeCursorImageClient(oldCursors);
}
void LayoutObject::updateImage(StyleImage* oldImage, StyleImage* newImage) {
if (oldImage != newImage) {
if (oldImage)
oldImage->removeClient(this);
if (newImage)
newImage->addClient(this);
}
}
void LayoutObject::updateShapeImage(const ShapeValue* oldShapeValue,
const ShapeValue* newShapeValue) {
if (oldShapeValue || newShapeValue)
updateImage(oldShapeValue ? oldShapeValue->image() : 0,
newShapeValue ? newShapeValue->image() : 0);
}
LayoutRect LayoutObject::viewRect() const {
return view()->viewRect();
}
FloatPoint LayoutObject::localToAbsolute(const FloatPoint& localPoint,
MapCoordinatesFlags mode) const {
TransformState transformState(TransformState::ApplyTransformDirection,
localPoint);
mapLocalToAncestor(0, transformState, mode | ApplyContainerFlip);
transformState.flatten();
return transformState.lastPlanarPoint();
}
FloatPoint LayoutObject::ancestorToLocal(LayoutBoxModelObject* ancestor,
const FloatPoint& containerPoint,
MapCoordinatesFlags mode) const {
TransformState transformState(
TransformState::UnapplyInverseTransformDirection, containerPoint);
mapAncestorToLocal(ancestor, transformState, mode);
transformState.flatten();
return transformState.lastPlanarPoint();
}
FloatQuad LayoutObject::ancestorToLocalQuad(LayoutBoxModelObject* ancestor,
const FloatQuad& quad,
MapCoordinatesFlags mode) const {
TransformState transformState(
TransformState::UnapplyInverseTransformDirection,
quad.boundingBox().center(), quad);
mapAncestorToLocal(ancestor, transformState, mode);
transformState.flatten();
return transformState.lastPlanarQuad();
}
void LayoutObject::mapLocalToAncestor(const LayoutBoxModelObject* ancestor,
TransformState& transformState,
MapCoordinatesFlags mode) const {
if (ancestor == this)
return;
AncestorSkipInfo skipInfo(ancestor);
const LayoutObject* container = this->container(&skipInfo);
if (!container)
return;
if (mode & ApplyContainerFlip) {
if (isBox()) {
mode &= ~ApplyContainerFlip;
} else if (container->isBox()) {
if (container->style()->isFlippedBlocksWritingMode()) {
IntPoint centerPoint = roundedIntPoint(transformState.mappedPoint());
transformState.move(toLayoutBox(container)->flipForWritingMode(
LayoutPoint(centerPoint)) -
centerPoint);
}
mode &= ~ApplyContainerFlip;
}
}
LayoutSize containerOffset = offsetFromContainer(container);
// TODO(smcgruer): This is inefficient. Instead we should avoid including
// offsetForInFlowPosition in offsetFromContainer when ignoring sticky.
if (mode & IgnoreStickyOffset && isStickyPositioned()) {
containerOffset -= toLayoutBoxModelObject(this)->offsetForInFlowPosition();
}
if (isLayoutFlowThread()) {
// So far the point has been in flow thread coordinates (i.e. as if
// everything in the fragmentation context lived in one tall single column).
// Convert it to a visual point now, since we're about to escape the flow
// thread.
containerOffset += columnOffset(LayoutPoint(transformState.mappedPoint()));
}
// Text objects just copy their parent's computed style, so we need to ignore
// them.
bool preserve3D =
mode & UseTransforms &&
((container->style()->preserves3D() && !container->isText()) ||
(style()->preserves3D() && !isText()));
if (mode & UseTransforms && shouldUseTransformFromContainer(container)) {
TransformationMatrix t;
getTransformFromContainer(container, containerOffset, t);
transformState.applyTransform(t, preserve3D
? TransformState::AccumulateTransform
: TransformState::FlattenTransform);
} else {
transformState.move(containerOffset.width(), containerOffset.height(),
preserve3D ? TransformState::AccumulateTransform
: TransformState::FlattenTransform);
}
if (skipInfo.ancestorSkipped()) {
// There can't be a transform between |ancestor| and |o|, because transforms
// create containers, so it should be safe to just subtract the delta
// between the ancestor and |o|.
LayoutSize containerOffset =
ancestor->offsetFromAncestorContainer(container);
transformState.move(-containerOffset.width(), -containerOffset.height(),
preserve3D ? TransformState::AccumulateTransform
: TransformState::FlattenTransform);
// If the ancestor is fixed, then the rect is already in its coordinates so
// doesn't need viewport-adjusting.
if (ancestor->style()->position() != EPosition::kFixed &&
container->isLayoutView() &&
styleRef().position() == EPosition::kFixed) {
LayoutSize adjustment = toLayoutView(container)->offsetForFixedPosition();
transformState.move(adjustment.width(), adjustment.height());
}
return;
}
container->mapLocalToAncestor(ancestor, transformState, mode);
}
const LayoutObject* LayoutObject::pushMappingToContainer(
const LayoutBoxModelObject* ancestorToStopAt,
LayoutGeometryMap& geometryMap) const {
ASSERT_NOT_REACHED();
return nullptr;
}
void LayoutObject::mapAncestorToLocal(const LayoutBoxModelObject* ancestor,
TransformState& transformState,
MapCoordinatesFlags mode) const {
if (this == ancestor)
return;
AncestorSkipInfo skipInfo(ancestor);
LayoutObject* container = this->container(&skipInfo);
if (!container)
return;
bool applyContainerFlip = false;
if (mode & ApplyContainerFlip) {
if (isBox()) {
mode &= ~ApplyContainerFlip;
} else if (container->isBox()) {
applyContainerFlip = container->style()->isFlippedBlocksWritingMode();
mode &= ~ApplyContainerFlip;
}
}
if (!skipInfo.ancestorSkipped())
container->mapAncestorToLocal(ancestor, transformState, mode);
LayoutSize containerOffset = offsetFromContainer(container);
bool preserve3D =
mode & UseTransforms &&
(container->style()->preserves3D() || style()->preserves3D());
if (mode & UseTransforms && shouldUseTransformFromContainer(container)) {
TransformationMatrix t;
getTransformFromContainer(container, containerOffset, t);
transformState.applyTransform(t, preserve3D
? TransformState::AccumulateTransform
: TransformState::FlattenTransform);
} else {
transformState.move(containerOffset.width(), containerOffset.height(),
preserve3D ? TransformState::AccumulateTransform
: TransformState::FlattenTransform);
}
if (isLayoutFlowThread()) {
// Descending into a flow thread. Convert to the local coordinate space,
// i.e. flow thread coordinates.
LayoutPoint visualPoint = LayoutPoint(transformState.mappedPoint());
transformState.move(
visualPoint -
toLayoutFlowThread(this)->visualPointToFlowThreadPoint(visualPoint));
}
if (applyContainerFlip) {
IntPoint centerPoint = roundedIntPoint(transformState.mappedPoint());
transformState.move(
centerPoint -
toLayoutBox(container)->flipForWritingMode(LayoutPoint(centerPoint)));
}
if (skipInfo.ancestorSkipped()) {
containerOffset = ancestor->offsetFromAncestorContainer(container);
transformState.move(-containerOffset.width(), -containerOffset.height());
// If the ancestor is fixed, then the rect is already in its coordinates so
// doesn't need viewport-adjusting.
if (ancestor->style()->position() != EPosition::kFixed &&
container->isLayoutView() &&
styleRef().position() == EPosition::kFixed) {
LayoutSize adjustment = toLayoutView(container)->offsetForFixedPosition();
transformState.move(adjustment.width(), adjustment.height());
}
}
}
bool LayoutObject::shouldUseTransformFromContainer(
const LayoutObject* containerObject) const {
// hasTransform() indicates whether the object has transform, transform-style
// or perspective. We just care about transform, so check the layer's
// transform directly.
return (hasLayer() && toLayoutBoxModelObject(this)->layer()->transform()) ||
(containerObject && containerObject->style()->hasPerspective());
}
void LayoutObject::getTransformFromContainer(
const LayoutObject* containerObject,
const LayoutSize& offsetInContainer,
TransformationMatrix& transform) const {
transform.makeIdentity();
transform.translate(offsetInContainer.width().toFloat(),
offsetInContainer.height().toFloat());
PaintLayer* layer = hasLayer() ? toLayoutBoxModelObject(this)->layer() : 0;
if (layer && layer->transform())
transform.multiply(layer->currentTransform());
if (containerObject && containerObject->hasLayer() &&
containerObject->style()->hasPerspective()) {
// Perpsective on the container affects us, so we have to factor it in here.
ASSERT(containerObject->hasLayer());
FloatPoint perspectiveOrigin =
toLayoutBoxModelObject(containerObject)->layer()->perspectiveOrigin();
TransformationMatrix perspectiveMatrix;
perspectiveMatrix.applyPerspective(containerObject->style()->perspective());
transform.translateRight3d(-perspectiveOrigin.x(), -perspectiveOrigin.y(),
0);
transform = perspectiveMatrix * transform;
transform.translateRight3d(perspectiveOrigin.x(), perspectiveOrigin.y(), 0);
}
}
FloatQuad LayoutObject::localToAncestorQuad(
const FloatQuad& localQuad,
const LayoutBoxModelObject* ancestor,
MapCoordinatesFlags mode) const {
return localToAncestorQuadInternal(localQuad, ancestor, mode | UseTransforms);
}
FloatQuad LayoutObject::localToAncestorQuadWithoutTransforms(
const FloatQuad& localQuad,
const LayoutBoxModelObject* ancestor,
MapCoordinatesFlags mode) const {
DCHECK(!(mode & UseTransforms));
return localToAncestorQuadInternal(localQuad, ancestor, mode);
}
FloatQuad LayoutObject::localToAncestorQuadInternal(
const FloatQuad& localQuad,
const LayoutBoxModelObject* ancestor,
MapCoordinatesFlags mode) const {
// Track the point at the center of the quad's bounding box. As
// mapLocalToAncestor() calls offsetFromContainer(), it will use that point
// as the reference point to decide which column's transform to apply in
// multiple-column blocks.
// TODO(chrishtr): the second argument to this constructor is unnecessary,
// since we never call lastPlanarPoint().
TransformState transformState(TransformState::ApplyTransformDirection,
localQuad.boundingBox().center(), localQuad);
mapLocalToAncestor(ancestor, transformState, mode | ApplyContainerFlip);
transformState.flatten();
return transformState.lastPlanarQuad();
}
FloatPoint LayoutObject::localToAncestorPoint(
const FloatPoint& localPoint,
const LayoutBoxModelObject* ancestor,
MapCoordinatesFlags mode) const {
TransformState transformState(TransformState::ApplyTransformDirection,
localPoint);
mapLocalToAncestor(ancestor, transformState,
mode | ApplyContainerFlip | UseTransforms);
transformState.flatten();
return transformState.lastPlanarPoint();
}
void LayoutObject::localToAncestorRects(Vector<LayoutRect>& rects,
const LayoutBoxModelObject* ancestor,
const LayoutPoint& preOffset,
const LayoutPoint& postOffset) const {
for (size_t i = 0; i < rects.size(); ++i) {
LayoutRect& rect = rects[i];
rect.moveBy(preOffset);
FloatQuad containerQuad =
localToAncestorQuad(FloatQuad(FloatRect(rect)), ancestor);
LayoutRect containerRect = LayoutRect(containerQuad.boundingBox());
if (containerRect.isEmpty()) {
rects.remove(i--);
continue;
}
containerRect.moveBy(postOffset);
rects[i] = containerRect;
}
}
TransformationMatrix LayoutObject::localToAncestorTransform(
const LayoutBoxModelObject* ancestor,
MapCoordinatesFlags mode) const {
TransformState transformState(TransformState::ApplyTransformDirection);
mapLocalToAncestor(ancestor, transformState,
mode | ApplyContainerFlip | UseTransforms);
return transformState.accumulatedTransform();
}
LayoutSize LayoutObject::offsetFromContainer(const LayoutObject* o) const {
ASSERT(o == container());
return o->hasOverflowClip()
? LayoutSize(-toLayoutBox(o)->scrolledContentOffset())
: LayoutSize();
}
LayoutSize LayoutObject::offsetFromAncestorContainer(
const LayoutObject* ancestorContainer) const {
if (ancestorContainer == this)
return LayoutSize();
LayoutSize offset;
LayoutPoint referencePoint;
const LayoutObject* currContainer = this;
do {
const LayoutObject* nextContainer = currContainer->container();
// This means we reached the top without finding container.
DCHECK(nextContainer);
if (!nextContainer)
break;
ASSERT(!currContainer->hasTransformRelatedProperty());
LayoutSize currentOffset =
currContainer->offsetFromContainer(nextContainer);
offset += currentOffset;
referencePoint.move(currentOffset);
currContainer = nextContainer;
} while (currContainer != ancestorContainer);
return offset;
}
LayoutRect LayoutObject::localCaretRect(InlineBox*,
int,
LayoutUnit* extraWidthToEndOfLine) {
if (extraWidthToEndOfLine)
*extraWidthToEndOfLine = LayoutUnit();
return LayoutRect();
}
void LayoutObject::computeLayerHitTestRects(
LayerHitTestRects& layerRects) const {
// Figure out what layer our container is in. Any offset (or new layer) for
// this layoutObject within it's container will be applied in
// addLayerHitTestRects.
LayoutPoint layerOffset;
const PaintLayer* currentLayer = nullptr;
if (!hasLayer()) {
LayoutObject* container = this->container();
currentLayer = container->enclosingLayer();
if (container && currentLayer->layoutObject() != container) {
layerOffset.move(container->offsetFromAncestorContainer(
&currentLayer->layoutObject()));
// If the layer itself is scrolled, we have to undo the subtraction of its
// scroll offset since we want the offset relative to the scrolling
// content, not the element itself.
if (currentLayer->layoutObject().hasOverflowClip())
layerOffset.move(currentLayer->layoutBox()->scrolledContentOffset());
}
}
this->addLayerHitTestRects(layerRects, currentLayer, layerOffset,
LayoutRect());
}
void LayoutObject::addLayerHitTestRects(LayerHitTestRects& layerRects,
const PaintLayer* currentLayer,
const LayoutPoint& layerOffset,
const LayoutRect& containerRect) const {
ASSERT(currentLayer);
ASSERT(currentLayer == this->enclosingLayer());
// Compute the rects for this layoutObject only and add them to the results.
// Note that we could avoid passing the offset and instead adjust each result,
// but this seems slightly simpler.
Vector<LayoutRect> ownRects;
LayoutRect newContainerRect;
computeSelfHitTestRects(ownRects, layerOffset);
// When we get to have a lot of rects on a layer, the performance cost of
// tracking those rects outweighs the benefit of doing compositor thread hit
// testing.
// FIXME: This limit needs to be low due to the O(n^2) algorithm in
// WebLayer::setTouchEventHandlerRegion - crbug.com/300282.
const size_t maxRectsPerLayer = 100;
LayerHitTestRects::iterator iter = layerRects.find(currentLayer);
Vector<LayoutRect>* iterValue;
if (iter == layerRects.end())
iterValue = &layerRects.insert(currentLayer, Vector<LayoutRect>())
.storedValue->value;
else
iterValue = &iter->value;
for (size_t i = 0; i < ownRects.size(); i++) {
if (!containerRect.contains(ownRects[i])) {
iterValue->push_back(ownRects[i]);
if (iterValue->size() > maxRectsPerLayer) {
// Just mark the entire layer instead, and switch to walking the layer
// tree instead of the layout tree.
layerRects.erase(currentLayer);
currentLayer->addLayerHitTestRects(layerRects);
return;
}
if (newContainerRect.isEmpty())
newContainerRect = ownRects[i];
}
}
if (newContainerRect.isEmpty())
newContainerRect = containerRect;
// If it's possible for children to have rects outside our bounds, then we
// need to descend into the children and compute them.
// Ideally there would be other cases where we could detect that children
// couldn't have rects outside our bounds and prune the tree walk.
// Note that we don't use Region here because Union is O(N) - better to just
// keep a list of partially redundant rectangles. If we find examples where
// this is expensive, then we could rewrite Region to be more efficient. See
// https://bugs.webkit.org/show_bug.cgi?id=100814.
if (!isLayoutView()) {
for (LayoutObject* curr = slowFirstChild(); curr;
curr = curr->nextSibling()) {
curr->addLayerHitTestRects(layerRects, currentLayer, layerOffset,
newContainerRect);
}
}
}
bool LayoutObject::isRooted() const {
const LayoutObject* object = this;
while (object->parent() && !object->hasLayer())
object = object->parent();
if (object->hasLayer())
return toLayoutBoxModelObject(object)->layer()->root()->isRootLayer();
return false;
}
RespectImageOrientationEnum LayoutObject::shouldRespectImageOrientation(
const LayoutObject* layoutObject) {
if (!layoutObject)
return DoNotRespectImageOrientation;
// Respect the image's orientation if it's being used as a full-page image or
// it's an <img> and the setting to respect it everywhere is set or the <img>
// has image-orientation: from-image style. FIXME: crbug.com/498233
if (layoutObject->document().isImageDocument())
return RespectImageOrientation;
if (!isHTMLImageElement(layoutObject->node()))
return DoNotRespectImageOrientation;
if (layoutObject->document().settings() &&
layoutObject->document().settings()->getShouldRespectImageOrientation())
return RespectImageOrientation;
if (layoutObject->style() &&
layoutObject->style()->respectImageOrientation() ==
RespectImageOrientation)
return RespectImageOrientation;
return DoNotRespectImageOrientation;
}
LayoutObject* LayoutObject::container(AncestorSkipInfo* skipInfo) const {
// TODO(mstensho): Get rid of this. Nobody should call this method with those
// flags already set.
if (skipInfo)
skipInfo->resetOutput();
if (isTextOrSVGChild())
return parent();
EPosition pos = m_style->position();
if (pos == EPosition::kFixed)
return containerForFixedPosition(skipInfo);
if (pos == EPosition::kAbsolute) {
return containerForAbsolutePosition(skipInfo);
}
if (isColumnSpanAll()) {
LayoutObject* multicolContainer = spannerPlaceholder()->container();
if (skipInfo) {
// We jumped directly from the spanner to the multicol container. Need to
// check if we skipped |ancestor| or filter/reflection on the way.
for (LayoutObject* walker = parent();
walker && walker != multicolContainer; walker = walker->parent())
skipInfo->update(*walker);
}
return multicolContainer;
}
if (isFloating())
return containingBlock(skipInfo);
return parent();
}
inline LayoutObject* LayoutObject::paintInvalidationParent() const {
if (isLayoutView())
return LayoutAPIShim::layoutObjectFrom(frame()->ownerLayoutItem());
return parent();
}
LayoutObject* LayoutObject::slowPaintInvalidationParentForTesting() const {
return paintInvalidationParent();
}
bool LayoutObject::isSelectionBorder() const {
SelectionState st = getSelectionState();
return st == SelectionStart || st == SelectionEnd || st == SelectionBoth;
}
inline void LayoutObject::clearLayoutRootIfNeeded() const {
if (FrameView* view = frameView()) {
if (!documentBeingDestroyed())
view->clearLayoutSubtreeRoot(*this);
}
}
void LayoutObject::willBeDestroyed() {
// Destroy any leftover anonymous children.
LayoutObjectChildList* children = virtualChildren();
if (children)
children->destroyLeftoverChildren();
if (LocalFrame* frame = this->frame()) {
// If this layoutObject is being autoscrolled, stop the autoscrolling.
if (frame->page())
frame->page()->autoscrollController().stopAutoscrollIfNeeded(this);
}
// For accessibility management, notify the parent of the imminent change to
// its child set.
// We do it now, before remove(), while the parent pointer is still available.
if (AXObjectCache* cache = document().existingAXObjectCache())
cache->childrenChanged(this->parent());
remove();
// The remove() call above may invoke axObjectCache()->childrenChanged() on
// the parent, which may require the AX layout object for this layoutObject.
// So we remove the AX layout object now, after the layoutObject is removed.
if (AXObjectCache* cache = document().existingAXObjectCache())
cache->remove(this);
// If this layoutObject had a parent, remove should have destroyed any
// counters attached to this layoutObject and marked the affected other
// counters for reevaluation. This apparently redundant check is here for the
// case when this layoutObject had no parent at the time remove() was called.
if (hasCounterNodeMap())
LayoutCounter::destroyCounterNodes(*this);
// Remove the handler if node had touch-action set. Handlers are not added
// for text nodes so don't try removing for one too. Need to check if
// m_style is null in cases of partial construction. Any handler we added
// previously may have already been removed by the Document independently.
if (node() && !node()->isTextNode() && m_style &&
m_style->getTouchAction() != TouchActionAuto) {
EventHandlerRegistry& registry = document().page()->eventHandlerRegistry();
if (registry
.eventHandlerTargets(
EventHandlerRegistry::TouchStartOrMoveEventBlocking)
->contains(node()))
registry.didRemoveEventHandler(
*node(), EventHandlerRegistry::TouchStartOrMoveEventBlocking);
}
setAncestorLineBoxDirty(false);
ObjectPaintInvalidator::objectWillBeDestroyed(*this);
clearLayoutRootIfNeeded();
if (m_style) {
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 (m_style->contentData() && m_style->contentData()->isImage())
toImageContentData(m_style->contentData())->image()->removeClient(this);
if (m_style->boxReflect() && m_style->boxReflect()->mask().image())
m_style->boxReflect()->mask().image()->removeClient(this);
removeShapeImageClient(m_style->shapeOutside());
removeCursorImageClient(m_style->cursors());
}
if (frameView())
setIsBackgroundAttachmentFixedObject(false);
}
DISABLE_CFI_PERF
void LayoutObject::insertedIntoTree() {
// FIXME: We should ASSERT(isRooted()) here but generated content makes some
// out-of-order insertion.
// 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.
PaintLayer* layer = nullptr;
if (slowFirstChild() || 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() != EVisibility::kVisible &&
style()->visibility() == EVisibility::kVisible && !hasLayer()) {
if (!layer)
layer = parent()->enclosingLayer();
if (layer)
layer->dirtyVisibleContentStatus();
}
if (parent()->childrenInline())
parent()->dirtyLinesFromChangedChild(this);
if (LayoutFlowThread* flowThread = flowThreadContainingBlock())
flowThread->flowThreadDescendantWasInserted(this);
}
enum FindReferencingScrollAnchorsBehavior { DontClear, Clear };
static bool findReferencingScrollAnchors(
LayoutObject* layoutObject,
FindReferencingScrollAnchorsBehavior behavior) {
PaintLayer* layer = nullptr;
if (LayoutObject* parent = layoutObject->parent())
layer = parent->enclosingLayer();
bool found = false;
// Walk up the layer tree to clear any scroll anchors that reference us.
while (layer) {
if (PaintLayerScrollableArea* scrollableArea = layer->getScrollableArea()) {
ScrollAnchor* anchor = scrollableArea->scrollAnchor();
DCHECK(anchor);
if (anchor->refersTo(layoutObject)) {
found = true;
if (behavior == Clear)
anchor->notifyRemoved(layoutObject);
else
return true;
}
}
layer = layer->parent();
}
if (FrameView* view = layoutObject->frameView()) {
ScrollAnchor* anchor = view->scrollAnchor();
DCHECK(anchor);
if (anchor->refersTo(layoutObject)) {
found = true;
if (behavior == Clear)
anchor->notifyRemoved(layoutObject);
}
}
return found;
}
void LayoutObject::willBeRemovedFromTree() {
// FIXME: We should ASSERT(isRooted()) but we have some out-of-order removals
// which would need to be fixed first.
// If we remove a visible child from an invisible parent, we don't know the
// layer visibility any more.
PaintLayer* layer = nullptr;
if (parent()->style()->visibility() != EVisibility::kVisible &&
style()->visibility() == EVisibility::kVisible && !hasLayer()) {
layer = parent()->enclosingLayer();
if (layer)
layer->dirtyVisibleContentStatus();
}
// Keep our layer hierarchy updated.
if (slowFirstChild() || hasLayer()) {
if (!layer)
layer = parent()->enclosingLayer();
removeLayers(layer);
}
if (isOutOfFlowPositioned() && parent()->childrenInline())
parent()->dirtyLinesFromChangedChild(this);
removeFromLayoutFlowThread();
// Update cached boundaries in SVG layoutObjects if a child is removed.
if (parent()->isSVG())
parent()->setNeedsBoundariesUpdate();
if (RuntimeEnabledFeatures::scrollAnchoringEnabled() &&
m_bitfields.isScrollAnchorObject()) {
// Clear the bit first so that anchor.clear() doesn't recurse into
// findReferencingScrollAnchors.
m_bitfields.setIsScrollAnchorObject(false);
findReferencingScrollAnchors(this, Clear);
}
}
void LayoutObject::setNeedsPaintPropertyUpdate() {
m_bitfields.setNeedsPaintPropertyUpdate(true);
LayoutObject* ancestor = paintInvalidationParent();
while (ancestor && !ancestor->descendantNeedsPaintPropertyUpdate()) {
ancestor->m_bitfields.setDescendantNeedsPaintPropertyUpdate(true);
ancestor = ancestor->paintInvalidationParent();
}
}
void LayoutObject::setAncestorsNeedPaintPropertyUpdateForMainThreadScrolling() {
LayoutObject* ancestor = paintInvalidationParent();
while (ancestor) {
ancestor->setNeedsPaintPropertyUpdate();
ancestor = ancestor->paintInvalidationParent();
}
}
void LayoutObject::maybeClearIsScrollAnchorObject() {
if (!m_bitfields.isScrollAnchorObject())
return;
m_bitfields.setIsScrollAnchorObject(
findReferencingScrollAnchors(this, DontClear));
}
void LayoutObject::removeFromLayoutFlowThread() {
if (!isInsideFlowThread())
return;
// Sometimes we remove the element from the flow, but it's not destroyed at
// that time.
// It's only until later when we actually destroy it and remove all the
// children from it.
// Currently, that happens for firstLetter elements and list markers.
// Pass in the flow thread so that we don't have to look it up for all the
// children.
// If we're a column spanner, we need to use our parent to find the flow
// thread, since a spanner doesn't have the flow thread in its containing
// block chain. We still need to notify the flow thread when the layoutObject
// removed happens to be a spanner, so that we get rid of the spanner
// placeholder, and column sets around the placeholder get merged.
LayoutFlowThread* flowThread = isColumnSpanAll()
? parent()->flowThreadContainingBlock()
: flowThreadContainingBlock();
removeFromLayoutFlowThreadRecursive(flowThread);
}
void LayoutObject::removeFromLayoutFlowThreadRecursive(
LayoutFlowThread* layoutFlowThread) {
if (const LayoutObjectChildList* children = virtualChildren()) {
for (LayoutObject* child = children->firstChild(); child;
child = child->nextSibling()) {
if (child->isLayoutFlowThread())
continue; // Don't descend into inner fragmentation contexts.
child->removeFromLayoutFlowThreadRecursive(child->isLayoutFlowThread()
? toLayoutFlowThread(child)
: layoutFlowThread);
}
}
if (layoutFlowThread && layoutFlowThread != this)
layoutFlowThread->flowThreadDescendantWillBeRemoved(this);
setIsInsideFlowThread(false);
RELEASE_ASSERT(!spannerPlaceholder());
}
void LayoutObject::destroyAndCleanupAnonymousWrappers() {
// If the tree is destroyed, there is no need for a clean-up phase.
if (documentBeingDestroyed()) {
destroy();
return;
}
LayoutObject* destroyRoot = this;
for (LayoutObject *destroyRootParent = destroyRoot->parent();
destroyRootParent && destroyRootParent->isAnonymous();
destroyRoot = destroyRootParent,
destroyRootParent = destroyRootParent->parent()) {
// Anonymous block continuations are tracked and destroyed elsewhere (see
// the bottom of LayoutBlock::removeChild)
if (destroyRootParent->isLayoutBlockFlow() &&
toLayoutBlockFlow(destroyRootParent)->isAnonymousBlockContinuation())
break;
// A flow thread is tracked by its containing block. Whether its children
// are removed or not is irrelevant.
if (destroyRootParent->isLayoutFlowThread())
break;
if (destroyRootParent->slowFirstChild() != destroyRoot ||
destroyRootParent->slowLastChild() != destroyRoot)
break; // Need to keep the anonymous parent, since it won't become empty
// by the removal of this layoutObject.
}
destroyRoot->destroy();
// WARNING: |this| is deleted here.
}
void LayoutObject::destroy() {
willBeDestroyed();
delete this;
}
DISABLE_CFI_PERF
void LayoutObject::removeShapeImageClient(ShapeValue* shapeValue) {
if (!shapeValue)
return;
if (StyleImage* shapeImage = shapeValue->image())
shapeImage->removeClient(this);
}
void LayoutObject::removeCursorImageClient(const CursorList* cursorList) {
if (!cursorList)
return;
for (const CursorData& cursor : *cursorList) {
if (cursor.image())
cursor.image()->removeClient(this);
}
}
PositionWithAffinity LayoutObject::positionForPoint(const LayoutPoint&) {
return createPositionWithAffinity(caretMinOffset());
}
CompositingState LayoutObject::compositingState() const {
return hasLayer() ? toLayoutBoxModelObject(this)->layer()->compositingState()
: NotComposited;
}
CompositingReasons LayoutObject::additionalCompositingReasons() const {
return CompositingReasonNone;
}
bool LayoutObject::hitTest(HitTestResult& result,
const HitTestLocation& locationInContainer,
const LayoutPoint& accumulatedOffset,
HitTestFilter hitTestFilter) {
bool inside = false;
if (hitTestFilter != HitTestSelf) {
// First test the foreground layer (lines and inlines).
inside = nodeAtPoint(result, locationInContainer, accumulatedOffset,
HitTestForeground);
// Test floats next.
if (!inside)
inside = nodeAtPoint(result, locationInContainer, accumulatedOffset,
HitTestFloat);
// Finally test to see if the mouse is in the background (within a child
// block's background).
if (!inside)
inside = nodeAtPoint(result, locationInContainer, accumulatedOffset,
HitTestChildBlockBackgrounds);
}
// See if the mouse is inside us but not any of our descendants
if (hitTestFilter != HitTestDescendants && !inside)
inside = nodeAtPoint(result, locationInContainer, accumulatedOffset,
HitTestBlockBackground);
return inside;
}
void LayoutObject::updateHitTestResult(HitTestResult& result,
const LayoutPoint& point) {
if (result.innerNode())
return;
Node* node = this->node();
// If we hit the anonymous layoutObjects inside generated content we should
// actually hit the generated content so walk up to the PseudoElement.
if (!node && parent() && parent()->isBeforeOrAfterContent()) {
for (LayoutObject* layoutObject = parent(); layoutObject && !node;
layoutObject = layoutObject->parent())
node = layoutObject->node();
}
if (node)
result.setNodeAndPosition(node, point);
}
bool LayoutObject::nodeAtPoint(HitTestResult&,
const HitTestLocation& /*locationInContainer*/,
const LayoutPoint& /*accumulatedOffset*/,
HitTestAction) {
return false;
}
void LayoutObject::scheduleRelayout() {
if (isLayoutView()) {
FrameView* view = toLayoutView(this)->frameView();
if (view)
view->scheduleRelayout();
} else {
if (isRooted()) {
if (LayoutView* layoutView = view()) {
if (FrameView* frameView = layoutView->frameView())
frameView->scheduleRelayoutOfSubtree(this);
}
}
}
}
void LayoutObject::forceLayout() {
setSelfNeedsLayout(true);
setShouldDoFullPaintInvalidation();
layout();
}
// FIXME: Does this do anything different than forceLayout given that we don't
// walk the containing block chain. If not, we should change all callers to use
// forceLayout.
void LayoutObject::forceChildLayout() {
setNormalChildNeedsLayout(true);
layout();
}
enum StyleCacheState { Cached, Uncached };
static PassRefPtr<ComputedStyle> firstLineStyleForCachedUncachedType(
StyleCacheState type,
const LayoutObject* layoutObject,
ComputedStyle* style) {
const LayoutObject* layoutObjectForFirstLineStyle = layoutObject;
if (layoutObject->isBeforeOrAfterContent())
layoutObjectForFirstLineStyle = layoutObject->parent();
if (layoutObjectForFirstLineStyle->behavesLikeBlockContainer()) {
if (const LayoutBlock* firstLineBlock =
toLayoutBlock(layoutObjectForFirstLineStyle)
->enclosingFirstLineStyleBlock()) {
if (type == Cached)
return firstLineBlock->getCachedPseudoStyle(PseudoIdFirstLine, style);
return firstLineBlock->getUncachedPseudoStyle(
PseudoStyleRequest(PseudoIdFirstLine), style,
firstLineBlock == layoutObject ? style : 0);
}
} else if (!layoutObjectForFirstLineStyle->isAnonymous() &&
layoutObjectForFirstLineStyle->isLayoutInline() &&
!layoutObjectForFirstLineStyle->node()
->isFirstLetterPseudoElement()) {
const ComputedStyle* parentStyle =
layoutObjectForFirstLineStyle->parent()->firstLineStyle();
if (parentStyle != layoutObjectForFirstLineStyle->parent()->style()) {
if (type == Cached) {
// A first-line style is in effect. Cache a first-line style for
// ourselves.
layoutObjectForFirstLineStyle->mutableStyleRef().setHasPseudoStyle(
PseudoIdFirstLineInherited);
return layoutObjectForFirstLineStyle->getCachedPseudoStyle(
PseudoIdFirstLineInherited, parentStyle);
}
return layoutObjectForFirstLineStyle->getUncachedPseudoStyle(
PseudoStyleRequest(PseudoIdFirstLineInherited), parentStyle, style);
}
}
return nullptr;
}
PassRefPtr<ComputedStyle> LayoutObject::uncachedFirstLineStyle(
ComputedStyle* style) const {
if (!document().styleEngine().usesFirstLineRules())
return nullptr;
ASSERT(!isText());
return firstLineStyleForCachedUncachedType(Uncached, this, style);
}
ComputedStyle* LayoutObject::cachedFirstLineStyle() const {
ASSERT(document().styleEngine().usesFirstLineRules());
if (RefPtr<ComputedStyle> style = firstLineStyleForCachedUncachedType(
Cached, isText() ? parent() : this, m_style.get()))
return style.get();
return m_style.get();
}
ComputedStyle* LayoutObject::getCachedPseudoStyle(
PseudoId pseudo,
const ComputedStyle* parentStyle) const {
if (pseudo < FirstInternalPseudoId && !style()->hasPseudoStyle(pseudo))
return nullptr;
ComputedStyle* cachedStyle = style()->getCachedPseudoStyle(pseudo);
if (cachedStyle)
return cachedStyle;
RefPtr<ComputedStyle> result =
getUncachedPseudoStyle(PseudoStyleRequest(pseudo), parentStyle);
if (result)
return mutableStyleRef().addCachedPseudoStyle(result.release());
return nullptr;
}
PassRefPtr<ComputedStyle> LayoutObject::getUncachedPseudoStyle(
const PseudoStyleRequest& pseudoStyleRequest,
const ComputedStyle* parentStyle,
const ComputedStyle* ownStyle) const {
if (pseudoStyleRequest.pseudoId < FirstInternalPseudoId && !ownStyle &&
!style()->hasPseudoStyle(pseudoStyleRequest.pseudoId))
return nullptr;
if (!parentStyle) {
ASSERT(!ownStyle);
parentStyle = style();
}
if (!node())
return nullptr;
Element* element = Traversal<Element>::firstAncestorOrSelf(*node());
if (!element)
return nullptr;
if (pseudoStyleRequest.pseudoId == PseudoIdFirstLineInherited) {
RefPtr<ComputedStyle> result =
document().ensureStyleResolver().styleForElement(
element, parentStyle, parentStyle, DisallowStyleSharing);
result->setStyleType(PseudoIdFirstLineInherited);
return result.release();
}
return document().ensureStyleResolver().pseudoStyleForElement(
element, pseudoStyleRequest, parentStyle, parentStyle);
}
PassRefPtr<ComputedStyle> LayoutObject::getUncachedSelectionStyle() const {
if (!node())
return nullptr;
// In Blink, ::selection only applies to direct children of the element on
// which ::selection is matched. In order to be able to style ::selection
// inside elements implemented with a UA shadow tree, like input::selection,
// we calculate ::selection style on the shadow host for elements inside the
// UA shadow.
if (ShadowRoot* root = node()->containingShadowRoot()) {
if (root->type() == ShadowRootType::UserAgent) {
if (Element* shadowHost = node()->ownerShadowHost()) {
return shadowHost->layoutObject()->getUncachedPseudoStyle(
PseudoStyleRequest(PseudoIdSelection));
}
}
}
// If we request ::selection style for LayoutText, query ::selection style on
// the parent element instead, as that is the node for which ::selection
// matches.
const LayoutObject* selectionLayoutObject = this;
Element* element = Traversal<Element>::firstAncestorOrSelf(*node());
if (!element)
return nullptr;
if (element != node()) {
selectionLayoutObject = element->layoutObject();
if (!selectionLayoutObject)
return nullptr;
}
return selectionLayoutObject->getUncachedPseudoStyle(
PseudoStyleRequest(PseudoIdSelection));
}
void LayoutObject::addAnnotatedRegions(Vector<AnnotatedRegionValue>& regions) {
// Convert the style regions to absolute coordinates.
if (style()->visibility() != EVisibility::kVisible || !isBox())
return;
if (style()->getDraggableRegionMode() == DraggableRegionNone)
return;
LayoutBox* box = toLayoutBox(this);
FloatRect localBounds(FloatPoint(), FloatSize(box->size()));
FloatRect absBounds = localToAbsoluteQuad(localBounds).boundingBox();
AnnotatedRegionValue region;
region.draggable = style()->getDraggableRegionMode() == DraggableRegionDrag;
region.bounds = LayoutRect(absBounds);
regions.push_back(region);
}
bool LayoutObject::willRenderImage() {
// Without visibility we won't render (and therefore don't care about
// animation).
if (style()->visibility() != EVisibility::kVisible)
return false;
// We will not render a new image when SuspendableObjects is suspended
if (document().isContextSuspended())
return false;
// If we're not in a window (i.e., we're dormant from being in a background
// tab) then we don't want to render either.
return document().view()->isVisible();
}
bool LayoutObject::getImageAnimationPolicy(ImageAnimationPolicy& policy) {
if (!document().settings())
return false;
policy = document().settings()->getImageAnimationPolicy();
return true;
}
int LayoutObject::caretMinOffset() const {
return 0;
}
int LayoutObject::caretMaxOffset() const {
if (isAtomicInlineLevel())
return node() ? std::max(1U, node()->countChildren()) : 1;
if (isHR())
return 1;
return 0;
}
bool LayoutObject::isInert() const {
const LayoutObject* layoutObject = this;
while (!layoutObject->node())
layoutObject = layoutObject->parent();
return layoutObject->node()->isInert();
}
void LayoutObject::imageChanged(ImageResourceContent* image,
const IntRect* rect) {
ASSERT(m_node);
// Image change notifications should not be received during paint because
// the resulting invalidations will be cleared following paint. This can also
// lead to modifying the tree out from under paint(), see: crbug.com/616700.
DCHECK(document().lifecycle().state() !=
DocumentLifecycle::LifecycleState::InPaint);
imageChanged(static_cast<WrappedImagePtr>(image), rect);
}
Element* LayoutObject::offsetParent(const Element* base) const {
if (isDocumentElement() || isBody())
return nullptr;
if (isFixedPositioned())
return nullptr;
float effectiveZoom = style()->effectiveZoom();
Node* node = nullptr;
for (LayoutObject* ancestor = parent(); ancestor;
ancestor = ancestor->parent()) {
// Spec: http://www.w3.org/TR/cssom-view/#offset-attributes
node = ancestor->node();
if (!node)
continue;
// TODO(kochi): If |base| or |node| is nested deep in shadow roots, this
// loop may get expensive, as isUnclosedNodeOf() can take up to O(N+M) time
// (N and M are depths).
if (base &&
(node->isClosedShadowHiddenFrom(*base) ||
(node->isInShadowTree() &&
node->containingShadowRoot()->type() == ShadowRootType::UserAgent))) {
// If 'position: fixed' node is found while traversing up, terminate the
// loop and return null.
if (ancestor->isFixedPositioned())
return nullptr;
continue;
}
if (ancestor->canContainAbsolutePositionObjects())
break;
if (isHTMLBodyElement(*node))
break;
if (!isPositioned() &&
(isHTMLTableElement(*node) || isHTMLTableCellElement(*node)))
break;
// Webkit specific extension where offsetParent stops at zoom level changes.
if (effectiveZoom != ancestor->style()->effectiveZoom())
break;
}
return node && node->isElementNode() ? toElement(node) : nullptr;
}
PositionWithAffinity LayoutObject::createPositionWithAffinity(
int offset,
TextAffinity affinity) {
// If this is a non-anonymous layoutObject in an editable area, then it's
// simple.
if (Node* node = nonPseudoNode()) {
if (!hasEditableStyle(*node)) {
// If it can be found, we prefer a visually equivalent position that is
// editable.
const Position position = Position(node, offset);
Position candidate =
mostForwardCaretPosition(position, CanCrossEditingBoundary);
if (hasEditableStyle(*candidate.anchorNode()))
return PositionWithAffinity(candidate, affinity);
candidate = mostBackwardCaretPosition(position, CanCrossEditingBoundary);
if (hasEditableStyle(*candidate.anchorNode()))
return PositionWithAffinity(candidate, affinity);
}
// FIXME: Eliminate legacy editing positions
return PositionWithAffinity(Position::editingPositionOf(node, offset),
affinity);
}
// We don't want to cross the boundary between editable and non-editable
// regions of the document, but that is either impossible or at least
// extremely unlikely in any normal case because we stop as soon as we
// find a single non-anonymous layoutObject.
// Find a nearby non-anonymous layoutObject.
LayoutObject* child = this;
while (LayoutObject* parent = child->parent()) {
// Find non-anonymous content after.
for (LayoutObject* layoutObject = child->nextInPreOrder(parent);
layoutObject; layoutObject = layoutObject->nextInPreOrder(parent)) {
if (Node* node = layoutObject->nonPseudoNode())
return PositionWithAffinity(firstPositionInOrBeforeNode(node));
}
// Find non-anonymous content before.
for (LayoutObject* layoutObject = child->previousInPreOrder(); layoutObject;
layoutObject = layoutObject->previousInPreOrder()) {
if (layoutObject == parent)
break;
if (Node* node = layoutObject->nonPseudoNode())
return PositionWithAffinity(lastPositionInOrAfterNode(node));
}
// Use the parent itself unless it too is anonymous.
if (Node* node = parent->nonPseudoNode())
return PositionWithAffinity(firstPositionInOrBeforeNode(node));
// Repeat at the next level up.
child = parent;
}
// Everything was anonymous. Give up.
return PositionWithAffinity();
}
PositionWithAffinity LayoutObject::createPositionWithAffinity(int offset) {
return createPositionWithAffinity(offset, TextAffinity::Downstream);
}
PositionWithAffinity LayoutObject::createPositionWithAffinity(
const Position& position) {
if (position.isNotNull())
return PositionWithAffinity(position);
ASSERT(!node());
return createPositionWithAffinity(0);
}
CursorDirective LayoutObject::getCursor(const LayoutPoint&, Cursor&) const {
return SetCursorBasedOnStyle;
}
bool LayoutObject::canUpdateSelectionOnRootLineBoxes() const {
if (needsLayout())
return false;
const LayoutBlock* containingBlock = this->containingBlock();
return containingBlock ? !containingBlock->needsLayout() : false;
}
void LayoutObject::setNeedsBoundariesUpdate() {
if (LayoutObject* layoutObject = parent())
layoutObject->setNeedsBoundariesUpdate();
}
FloatRect LayoutObject::objectBoundingBox() const {
NOTREACHED();
return FloatRect();
}
FloatRect LayoutObject::strokeBoundingBox() const {
NOTREACHED();
return FloatRect();
}
FloatRect LayoutObject::visualRectInLocalSVGCoordinates() const {
NOTREACHED();
return FloatRect();
}
AffineTransform LayoutObject::localSVGTransform() const {
return AffineTransform();
}
bool LayoutObject::nodeAtFloatPoint(HitTestResult&,
const FloatPoint&,
HitTestAction) {
ASSERT_NOT_REACHED();
return false;
}
bool LayoutObject::isRelayoutBoundaryForInspector() const {
return objectIsRelayoutBoundary(this);
}
static PaintInvalidationReason documentLifecycleBasedPaintInvalidationReason(
const DocumentLifecycle& documentLifecycle) {
switch (documentLifecycle.state()) {
case DocumentLifecycle::InStyleRecalc:
return PaintInvalidationStyleChange;
case DocumentLifecycle::InPreLayout:
case DocumentLifecycle::InPerformLayout:
case DocumentLifecycle::AfterPerformLayout:
return PaintInvalidationForcedByLayout;
case DocumentLifecycle::InCompositingUpdate:
return PaintInvalidationCompositingUpdate;
default:
return PaintInvalidationFull;
}
}
inline void LayoutObject::markAncestorsForPaintInvalidation() {
for (
LayoutObject* parent = this->paintInvalidationParent();
parent &&
!parent
->shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState();
parent = parent->paintInvalidationParent())
parent->m_bitfields.setChildShouldCheckForPaintInvalidation(true);
}
void LayoutObject::setShouldInvalidateSelection() {
if (!canUpdateSelectionOnRootLineBoxes())
return;
m_bitfields.setShouldInvalidateSelection(true);
markAncestorsForPaintInvalidation();
frameView()->scheduleVisualUpdateForPaintInvalidationIfNeeded();
}
void LayoutObject::setShouldDoFullPaintInvalidation(
PaintInvalidationReason reason) {
// Only full invalidation reasons are allowed.
ASSERT(isFullPaintInvalidationReason(reason));
bool isUpgradingDelayedFullToFull =
m_bitfields.fullPaintInvalidationReason() ==
PaintInvalidationDelayedFull &&
reason != PaintInvalidationDelayedFull;
if (m_bitfields.fullPaintInvalidationReason() == PaintInvalidationNone ||
isUpgradingDelayedFullToFull) {
if (reason == PaintInvalidationFull)
reason =
documentLifecycleBasedPaintInvalidationReason(document().lifecycle());
m_bitfields.setFullPaintInvalidationReason(reason);
if (!isUpgradingDelayedFullToFull)
markAncestorsForPaintInvalidation();
}
frameView()->scheduleVisualUpdateForPaintInvalidationIfNeeded();
}
void LayoutObject::setMayNeedPaintInvalidation() {
if (mayNeedPaintInvalidation())
return;
m_bitfields.setMayNeedPaintInvalidation(true);
markAncestorsForPaintInvalidation();
frameView()->scheduleVisualUpdateForPaintInvalidationIfNeeded();
}
void LayoutObject::setMayNeedPaintInvalidationSubtree() {
if (mayNeedPaintInvalidationSubtree())
return;
m_bitfields.setMayNeedPaintInvalidationSubtree(true);
setMayNeedPaintInvalidation();
}
void LayoutObject::setMayNeedPaintInvalidationAnimatedBackgroundImage() {
if (mayNeedPaintInvalidationAnimatedBackgroundImage())
return;
m_bitfields.setMayNeedPaintInvalidationAnimatedBackgroundImage(true);
setMayNeedPaintInvalidation();
}
void LayoutObject::clearPaintInvalidationFlags() {
// paintInvalidationStateIsDirty should be kept in sync with the
// booleans that are cleared below.
#if DCHECK_IS_ON()
DCHECK(!shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState() ||
paintInvalidationStateIsDirty());
#endif
clearShouldDoFullPaintInvalidation();
m_bitfields.setChildShouldCheckForPaintInvalidation(false);
m_bitfields.setMayNeedPaintInvalidation(false);
m_bitfields.setMayNeedPaintInvalidationSubtree(false);
m_bitfields.setMayNeedPaintInvalidationAnimatedBackgroundImage(false);
m_bitfields.setShouldInvalidateSelection(false);
m_bitfields.setBackgroundChangedSinceLastPaintInvalidation(false);
}
bool LayoutObject::isAllowedToModifyLayoutTreeStructure(Document& document) {
return DeprecatedDisableModifyLayoutTreeStructureAsserts::
canModifyLayoutTreeStateInAnyState() ||
document.lifecycle().stateAllowsLayoutTreeMutations();
}
DeprecatedDisableModifyLayoutTreeStructureAsserts::
DeprecatedDisableModifyLayoutTreeStructureAsserts()
: m_disabler(&gModifyLayoutTreeStructureAnyState, true) {}
bool DeprecatedDisableModifyLayoutTreeStructureAsserts::
canModifyLayoutTreeStateInAnyState() {
return gModifyLayoutTreeStructureAnyState;
}
void LayoutObject::
setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants() {
// Clear first because PaintInvalidationSubtree overrides other full paint
// invalidation reasons.
clearShouldDoFullPaintInvalidation();
setShouldDoFullPaintInvalidation(PaintInvalidationSubtree);
}
void LayoutObject::setIsBackgroundAttachmentFixedObject(
bool isBackgroundAttachmentFixedObject) {
ASSERT(frameView());
if (m_bitfields.isBackgroundAttachmentFixedObject() ==
isBackgroundAttachmentFixedObject)
return;
m_bitfields.setIsBackgroundAttachmentFixedObject(
isBackgroundAttachmentFixedObject);
if (isBackgroundAttachmentFixedObject)
frameView()->addBackgroundAttachmentFixedObject(this);
else
frameView()->removeBackgroundAttachmentFixedObject(this);
}
const ObjectPaintProperties* LayoutObject::paintProperties() const {
DCHECK(RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled());
return m_paintProperties.get();
}
ObjectPaintProperties& LayoutObject::ensurePaintProperties() {
DCHECK(RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled());
if (!m_paintProperties)
m_paintProperties = ObjectPaintProperties::create();
return *m_paintProperties;
}
LayoutRect LayoutObject::debugRect() const {
LayoutRect rect;
LayoutBlock* block = containingBlock();
if (block)
block->adjustChildDebugRect(rect);
return rect;
}
void LayoutObject::invalidatePaintForSelection() {
// setSelectionState() propagates the state up the containing block chain to
// tell if a block contains selected nodes or not. If this layout object is
// not a block, we need to get the selection state from the containing block
// to tell if we have any selected node children.
LayoutBlock* block =
isLayoutBlock() ? toLayoutBlock(this) : containingBlock();
if (!block)
return;
if (!block->hasSelectedChildren())
return;
// ::selection style only applies to direct selection leaf children of the
// element on which the ::selection style is set. Thus, we only walk the
// direct children here.
for (LayoutObject* child = slowFirstChild(); child;
child = child->nextSibling()) {
if (!child->canBeSelectionLeaf())
continue;
if (child->getSelectionState() == SelectionNone)
continue;
child->setShouldInvalidateSelection();
}
}
} // namespace blink
#ifndef NDEBUG
void showTree(const blink::LayoutObject* object) {
if (object)
object->showTreeForThis();
else
WTFLogAlways("%s", "Cannot showTree. Root is (nil)");
}
void showLineTree(const blink::LayoutObject* object) {
if (object)
object->showLineTreeForThis();
else
WTFLogAlways("%s", "Cannot showLineTree. Root is (nil)");
}
void showLayoutTree(const blink::LayoutObject* object1) {
showLayoutTree(object1, 0);
}
void showLayoutTree(const blink::LayoutObject* object1,
const blink::LayoutObject* object2) {
if (object1) {
const blink::LayoutObject* root = object1;
while (root->parent())
root = root->parent();
root->showLayoutTreeAndMark(object1, "*", object2, "-", 0);
} else {
WTFLogAlways("%s", "Cannot showLayoutTree. Root is (nil)");
}
}
#endif