blob: 3bb45426382b0a4182c3c7595287cdb6e0f03690 [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 "third_party/blink/renderer/core/layout/layout_object.h"
#include <algorithm>
#include <memory>
#include "third_party/blink/public/platform/web_scroll_into_view_params.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/animation/element_animations.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/layout_selection.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/frame/deprecated_schedule_style_recalc_during_layout.h"
#include "third_party/blink/renderer/core/frame/event_handler_registry.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_html_element.h"
#include "third_party/blink/renderer/core/html/html_table_cell_element.h"
#include "third_party/blink/renderer/core/html/html_table_element.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/layout/custom/layout_custom.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_counter.h"
#include "third_party/blink/renderer/core/layout/layout_deprecated_flexible_box.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_fieldset.h"
#include "third_party/blink/renderer/core/layout/layout_flexible_box.h"
#include "third_party/blink/renderer/core/layout/layout_flow_thread.h"
#include "third_party/blink/renderer/core/layout/layout_grid.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/layout_image_resource_style_image.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_list_item.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h"
#include "third_party/blink/renderer/core/layout/layout_object_factory.h"
#include "third_party/blink/renderer/core/layout/layout_scrollbar_part.h"
#include "third_party/blink/renderer/core/layout/layout_table_caption.h"
#include "third_party/blink/renderer/core/layout/layout_table_cell.h"
#include "third_party/blink/renderer/core/layout/layout_table_col.h"
#include "third_party/blink/renderer/core/layout/layout_table_row.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
#include "third_party/blink/renderer/core/page/autoscroll_controller.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
#include "third_party/blink/renderer/core/style/content_data.h"
#include "third_party/blink/renderer/core/style/cursor_data.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
#include "third_party/blink/renderer/platform/graphics/touch_action.h"
#include "third_party/blink/renderer/platform/instance_counters.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/transforms/transform_state.h"
#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#ifndef NDEBUG
#include <stdio.h>
#endif
namespace blink {
namespace {
template <typename Predicate>
LayoutObject* FindAncestorByPredicate(const LayoutObject* descendant,
LayoutObject::AncestorSkipInfo* skip_info,
Predicate predicate) {
for (auto* object = descendant->Parent(); object; object = object->Parent()) {
if (predicate(object))
return object;
if (skip_info)
skip_info->Update(*object);
}
return nullptr;
}
LayoutBlock* FindContainingBlock(LayoutObject* container,
LayoutObject::AncestorSkipInfo* skip_info) {
// For 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
// LayoutObject::Container() method can actually be used to obtain the inline
// directly.
if (container && container->IsInline() && !container->IsAtomicInlineLevel()) {
DCHECK(container->StyleRef().HasInFlowPosition() ||
container->StyleRef().HasFilter());
container = container->ContainingBlock(skip_info);
}
if (container && !container->IsLayoutBlock())
container = container->ContainingBlock(skip_info);
while (container && container->IsAnonymousBlock())
container = container->ContainingBlock(skip_info);
if (!container || !container->IsLayoutBlock())
return nullptr; // This can still happen in case of an orphaned tree
return ToLayoutBlock(container);
}
} // namespace
#if DCHECK_IS_ON()
LayoutObject::SetLayoutNeededForbiddenScope::SetLayoutNeededForbiddenScope(
LayoutObject& layout_object)
: layout_object_(layout_object),
preexisting_forbidden_(layout_object_.IsSetNeedsLayoutForbidden()) {
layout_object_.SetNeedsLayoutIsForbidden(true);
}
LayoutObject::SetLayoutNeededForbiddenScope::~SetLayoutNeededForbiddenScope() {
layout_object_.SetNeedsLayoutIsForbidden(preexisting_forbidden_);
}
#endif
struct SameSizeAsLayoutObject : DisplayItemClient {
// Normally this field uses the gap between DisplayItemClient and
// LayoutObject's other fields.
uint8_t paint_invalidation_reason_;
void* pointers[5];
Member<void*> members[1];
#if DCHECK_IS_ON()
unsigned debug_bitfields_ : 2;
#endif
unsigned bitfields_;
unsigned bitfields2_;
unsigned bitfields3_;
// The following fields are in FragmentData.
LayoutRect visual_rect_;
LayoutPoint paint_offset_;
std::unique_ptr<int> rare_data_;
std::unique_ptr<FragmentData> next_fragment_;
};
static_assert(sizeof(LayoutObject) == sizeof(SameSizeAsLayoutObject),
"LayoutObject should stay small");
bool LayoutObject::affects_parent_block_ = false;
void* LayoutObject::operator new(size_t sz) {
DCHECK(IsMainThread());
return WTF::Partitions::LayoutPartition()->Alloc(
sz, WTF_HEAP_PROFILER_TYPE_NAME(LayoutObject));
}
void LayoutObject::operator delete(void* ptr) {
DCHECK(IsMainThread());
base::PartitionFree(ptr);
}
LayoutObject* LayoutObject::CreateObject(Element* element,
const ComputedStyle& style) {
DCHECK(IsAllowedToModifyLayoutTreeStructure(element->GetDocument()));
// 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* content_data = style.GetContentData();
if (content_data && !content_data->Next() && content_data->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* style_image =
ToImageContentData(content_data)->GetImage()) {
image->SetImageResource(LayoutImageResourceStyleImage::Create(
const_cast<StyleImage*>(style_image)));
image->SetIsGeneratedContent();
} else {
image->SetImageResource(LayoutImageResource::Create());
}
image->SetStyleInternal(nullptr);
return image;
}
switch (style.Display()) {
case EDisplay::kNone:
case EDisplay::kContents:
return nullptr;
case EDisplay::kInline:
return new LayoutInline(element);
case EDisplay::kBlock:
case EDisplay::kFlowRoot:
case EDisplay::kInlineBlock:
return LayoutObjectFactory::CreateBlockFlow(*element, style);
case EDisplay::kListItem:
return LayoutObjectFactory::CreateListItem(*element, style);
case EDisplay::kTable:
case EDisplay::kInlineTable:
return new LayoutTable(element);
case EDisplay::kTableRowGroup:
case EDisplay::kTableHeaderGroup:
case EDisplay::kTableFooterGroup:
return new LayoutTableSection(element);
case EDisplay::kTableRow:
return new LayoutTableRow(element);
case EDisplay::kTableColumnGroup:
case EDisplay::kTableColumn:
return new LayoutTableCol(element);
case EDisplay::kTableCell:
return LayoutObjectFactory::CreateTableCell(*element, style);
case EDisplay::kTableCaption:
return LayoutObjectFactory::CreateTableCaption(*element, style);
case EDisplay::kWebkitBox:
case EDisplay::kWebkitInlineBox:
return new LayoutDeprecatedFlexibleBox(*element);
case EDisplay::kFlex:
case EDisplay::kInlineFlex:
UseCounter::Count(element->GetDocument(), WebFeature::kCSSFlexibleBox);
return LayoutObjectFactory::CreateFlexibleBox(*element, style);
case EDisplay::kGrid:
case EDisplay::kInlineGrid:
UseCounter::Count(element->GetDocument(), WebFeature::kCSSGridLayout);
return new LayoutGrid(element);
case EDisplay::kLayoutCustom:
case EDisplay::kInlineLayoutCustom:
return new LayoutCustom(element);
}
NOTREACHED();
return nullptr;
}
LayoutObject::LayoutObject(Node* node)
: full_paint_invalidation_reason_(PaintInvalidationReason::kNone),
style_(nullptr),
node_(node),
parent_(nullptr),
previous_(nullptr),
next_(nullptr),
#if DCHECK_IS_ON()
has_ax_object_(false),
set_needs_layout_forbidden_(false),
#endif
bitfields_(node) {
InstanceCounters::IncrementCounter(InstanceCounters::kLayoutObjectCounter);
if (node_)
GetFrameView()->IncrementLayoutObjectCount();
}
LayoutObject::~LayoutObject() {
#if DCHECK_IS_ON()
DCHECK(!has_ax_object_);
#endif
InstanceCounters::DecrementCounter(InstanceCounters::kLayoutObjectCounter);
}
bool LayoutObject::IsDescendantOf(const LayoutObject* obj) const {
for (const LayoutObject* r = this; r; r = r->parent_) {
if (r == obj)
return true;
}
return false;
}
bool LayoutObject::IsHR() const {
return IsHTMLHRElement(GetNode());
}
void LayoutObject::SetIsInsideFlowThreadIncludingDescendants(
bool inside_flow_thread) {
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);
DCHECK_NE(inside_flow_thread, object->IsInsideFlowThread());
object->SetIsInsideFlowThread(inside_flow_thread);
}
}
bool LayoutObject::RequiresAnonymousTableWrappers(
const LayoutObject* new_child) 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 (new_child->IsLayoutTableCol()) {
const LayoutTableCol* new_table_column = ToLayoutTableCol(new_child);
bool is_column_in_column_group =
new_table_column->IsTableColumn() && IsLayoutTableCol();
return !IsTable() && !is_column_in_column_group;
}
if (new_child->IsTableCaption())
return !IsTable();
if (new_child->IsTableSection())
return !IsTable();
if (new_child->IsTableRow())
return !IsTableSection();
if (new_child->IsTableCell())
return !IsTableRow();
return false;
}
DISABLE_CFI_PERF
void LayoutObject::AddChild(LayoutObject* new_child,
LayoutObject* before_child) {
DCHECK(IsAllowedToModifyLayoutTreeStructure(GetDocument()));
LayoutObjectChildList* children = VirtualChildren();
DCHECK(children);
if (!children)
return;
if (RequiresAnonymousTableWrappers(new_child)) {
// 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* after_child =
before_child ? before_child->PreviousSibling() : children->LastChild();
if (after_child && after_child->IsAnonymous() && after_child->IsTable() &&
!after_child->IsBeforeContent()) {
table = ToLayoutTable(after_child);
} else {
table = LayoutTable::CreateAnonymousWithParent(this);
children->InsertChildNode(this, table, before_child);
}
table->AddChild(new_child);
} else {
children->InsertChildNode(this, new_child, before_child);
}
if (new_child->IsText() &&
new_child->StyleRef().TextTransform() == ETextTransform::kCapitalize)
ToLayoutText(new_child)->TransformText();
}
void LayoutObject::RemoveChild(LayoutObject* old_child) {
DCHECK(IsAllowedToModifyLayoutTreeStructure(GetDocument()));
LayoutObjectChildList* children = VirtualChildren();
DCHECK(children);
if (!children)
return;
children->RemoveChildNode(this, old_child);
}
void LayoutObject::SetDangerousOneWayParent(LayoutObject* parent) {
DCHECK(!PreviousSibling());
DCHECK(!NextSibling());
DCHECK(!parent || !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 (bitfields_.SubtreeChangeListenerRegistered() == value)
return;
bitfields_.SetSubtreeChangeListenerRegistered(value);
for (LayoutObject* curr = SlowFirstChild(); curr; curr = curr->NextSibling())
curr->RegisterSubtreeChangeListenerOnDescendants(value);
}
void LayoutObject::NotifyAncestorsOfSubtreeChange() {
if (bitfields_.NotifiedOfSubtreeChange())
return;
bitfields_.SetNotifiedOfSubtreeChange(true);
if (Parent())
Parent()->NotifyAncestorsOfSubtreeChange();
}
void LayoutObject::NotifyOfSubtreeChange() {
if (!bitfields_.SubtreeChangeListenerRegistered())
return;
if (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(GetDocument().Lifecycle());
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
void LayoutObject::HandleSubtreeModifications() {
DCHECK(WasNotifiedOfSubtreeChange());
DCHECK(GetDocument().Lifecycle().StateAllowsLayoutTreeNotifications());
if (ConsumesSubtreeChangeNotification())
SubtreeDidChange();
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();
}
bool LayoutObject::HasClipRelatedProperty() const {
// TODO(trchen): Refactor / remove this function.
// This function detects a bunch of properties that can potentially affect
// clip inheritance chain. However such generalization is practially useless
// because these properties change clip inheritance in different way that
// needs to be handled explicitly.
// CSS clip applies clip to the current element and all descendants.
// CSS overflow clip applies only to containg-block descendants.
// CSS contain:paint applies to all descendants by making itself a containing
// block for all descendants.
// CSS clip-path/mask/filter induces a stacking context and applies inherited
// clip to that stacking context, while resetting clip for descendants. This
// special behavior is already handled elsewhere.
if (HasClip() || HasOverflowClip() || ShouldApplyPaintContainment())
return true;
if (IsBox() && ToLayoutBox(this)->HasControlClip())
return true;
return false;
}
bool LayoutObject::IsRenderedLegend() const {
if (!IsBox() || !IsHTMLLegendElement(GetNode()))
return false;
if (IsFloatingOrOutOfFlowPositioned())
return false;
const auto* parent = Parent();
if (RuntimeEnabledFeatures::LayoutNGFieldsetEnabled()) {
// If there is a rendered legend, it will be found inside the anonymous
// fieldset wrapper.
if (parent->IsAnonymous() && parent->Parent()->IsLayoutNGFieldset())
parent = parent->Parent();
}
return parent && parent->IsLayoutBlock() &&
IsHTMLFieldSetElement(parent->GetNode()) &&
LayoutFieldset::FindInFlowLegend(*ToLayoutBlock(parent)) == this;
}
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* stay_within) const {
if (LayoutObject* o = SlowFirstChild())
return o;
return NextInPreOrderAfterChildren(stay_within);
}
LayoutObject* LayoutObject::NextInPreOrderAfterChildren(
const LayoutObject* stay_within) const {
if (this == stay_within)
return nullptr;
const LayoutObject* current = this;
LayoutObject* next = current->NextSibling();
for (; !next; next = current->NextSibling()) {
current = current->Parent();
if (!current || current == stay_within)
return nullptr;
}
return next;
}
LayoutObject* LayoutObject::PreviousInPreOrder() const {
if (LayoutObject* o = PreviousSibling()) {
while (LayoutObject* last_child = o->SlowLastChild())
o = last_child;
return o;
}
return Parent();
}
LayoutObject* LayoutObject::PreviousInPreOrder(
const LayoutObject* stay_within) const {
if (this == stay_within)
return nullptr;
return PreviousInPreOrder();
}
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* parent_layer,
LayoutObject*& new_object,
PaintLayer*& before_child) {
if (obj->HasLayer()) {
if (!before_child && new_object) {
// 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.
before_child =
new_object->Parent()->FindNextLayer(parent_layer, new_object);
new_object = nullptr;
}
parent_layer->AddChild(ToLayoutBoxModelObject(obj)->Layer(), before_child);
return;
}
for (LayoutObject* curr = obj->SlowFirstChild(); curr;
curr = curr->NextSibling())
AddLayers(curr, parent_layer, new_object, before_child);
}
void LayoutObject::AddLayers(PaintLayer* parent_layer) {
if (!parent_layer)
return;
LayoutObject* object = this;
PaintLayer* before_child = nullptr;
blink::AddLayers(this, parent_layer, object, before_child);
}
void LayoutObject::RemoveLayers(PaintLayer* parent_layer) {
if (!parent_layer)
return;
if (HasLayer()) {
parent_layer->RemoveChild(ToLayoutBoxModelObject(this)->Layer());
return;
}
for (LayoutObject* curr = SlowFirstChild(); curr; curr = curr->NextSibling())
curr->RemoveLayers(parent_layer);
}
void LayoutObject::MoveLayers(PaintLayer* old_parent, PaintLayer* new_parent) {
if (!new_parent)
return;
if (HasLayer()) {
PaintLayer* layer = ToLayoutBoxModelObject(this)->Layer();
DCHECK_EQ(old_parent, layer->Parent());
if (old_parent)
old_parent->RemoveChild(layer);
new_parent->AddChild(layer);
return;
}
for (LayoutObject* curr = SlowFirstChild(); curr; curr = curr->NextSibling())
curr->MoveLayers(old_parent, new_parent);
}
PaintLayer* LayoutObject::FindNextLayer(PaintLayer* parent_layer,
LayoutObject* start_point,
bool check_parent) {
// Error check the parent layer passed in. If it's null, we can't find
// anything.
if (!parent_layer)
return nullptr;
// Step 1: If our layer is a child of the desired parent, then return our
// layer.
PaintLayer* our_layer =
HasLayer() ? ToLayoutBoxModelObject(this)->Layer() : nullptr;
if (our_layer && our_layer->Parent() == parent_layer)
return our_layer;
// 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 (!our_layer || our_layer == parent_layer) {
for (LayoutObject* curr = start_point ? start_point->NextSibling()
: SlowFirstChild();
curr; curr = curr->NextSibling()) {
PaintLayer* next_layer =
curr->FindNextLayer(parent_layer, nullptr, false);
if (next_layer)
return next_layer;
}
}
// Step 3: If our layer is the desired parent layer, then we're finished. We
// didn't find anything.
if (parent_layer == our_layer)
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 (check_parent && Parent())
return Parent()->FindNextLayer(parent_layer, 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 parentCrossingFrames for floating
// objects to omit any self-painting layers of inline objects that don't
// paint the floating object.
current = current->IsFloating() ? current->ContainingBlock()
: current->ParentCrossingFrames()) {
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::IsFixedPositionObjectInPagedMedia() const {
if (StyleRef().GetPosition() != EPosition::kFixed)
return false;
LayoutView* view = View();
return Container() == view && view->PageLogicalHeight() &&
// TODO(crbug.com/619094): Figure out the correct behaviour for fixed
// position objects in paged media with vertical writing modes.
view->IsHorizontalWritingMode();
}
LayoutRect LayoutObject::ScrollRectToVisible(
const LayoutRect& rect,
const WebScrollIntoViewParams& params) {
LayoutBox* enclosing_box = EnclosingBox();
if (!enclosing_box)
return rect;
GetDocument().GetFrame()->GetSmoothScrollSequencer().AbortAnimations();
WebScrollIntoViewParams new_params(params);
new_params.is_for_scroll_sequence |=
params.GetScrollType() == kProgrammaticScroll;
LayoutRect new_location =
enclosing_box->ScrollRectToVisibleRecursive(rect, new_params);
GetDocument().GetFrame()->GetSmoothScrollSequencer().RunQueuedAnimations();
return new_location;
}
LayoutBox* LayoutObject::EnclosingBox() const {
LayoutObject* curr = const_cast<LayoutObject*>(this);
while (curr) {
if (curr->IsBox())
return ToLayoutBox(curr);
curr = curr->Parent();
}
NOTREACHED();
return nullptr;
}
LayoutBlockFlow* LayoutObject::ContainingNGBlockFlow() const {
DCHECK(IsInline());
if (!RuntimeEnabledFeatures::LayoutNGEnabled())
return nullptr;
if (LayoutObject* parent = Parent()) {
LayoutBox* box = parent->EnclosingBox();
DCHECK(box);
if (NGBlockNode::CanUseNewLayout(*box)) {
DCHECK(box->IsLayoutBlockFlow());
return ToLayoutBlockFlow(box);
}
}
return nullptr;
}
const NGPhysicalBoxFragment* LayoutObject::ContainingBlockFlowFragment() const {
DCHECK(IsInline() || IsText());
LayoutBlockFlow* const block_flow = ContainingNGBlockFlow();
if (!block_flow || !block_flow->ChildrenInline())
return nullptr;
// TODO(kojii): CurrentFragment isn't always available after layout clean.
// Investigate why.
return block_flow->CurrentFragment();
}
LayoutBox* LayoutObject::EnclosingScrollableBox() const {
for (LayoutObject* ancestor = Parent(); ancestor;
ancestor = ancestor->Parent()) {
if (!ancestor->IsBox())
continue;
LayoutBox* ancestor_box = ToLayoutBox(ancestor);
if (ancestor_box->CanBeScrolledAndHasScrollableArea())
return ancestor_box;
}
return nullptr;
}
LayoutFlowThread* LayoutObject::LocateFlowThreadContainingBlock() const {
DCHECK(IsInsideFlowThread());
// See if we have the thread cached because we're in the middle of layout.
if (LayoutView* view = View()) {
if (LayoutState* layout_state = view->GetLayoutState()) {
// TODO(mstensho): We should really just return whatever
// layoutState->flowThread() returns here, also if the value is nullptr.
if (LayoutFlowThread* flow_thread = layout_state->FlowThread())
return flow_thread;
}
}
// Not in the middle of layout so have to find the thread the slow way.
return LayoutFlowThread::LocateFlowThreadContainingBlockOf(
*this, LayoutFlowThread::kAnyAncestor);
}
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 (object->ShouldApplyLayoutContainment() &&
object->ShouldApplySizeContainment())
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;
}
// NGInlineNode::ColectInlines() collects inline children into NGInlineItem.
// This function marks NeedsCollectInlines() to let it re-collect.
void LayoutObject::MarkContainerNeedsCollectInlines() {
if (!RuntimeEnabledFeatures::LayoutNGEnabled())
return;
// Mark only if this is a LayoutObject collected by CollectInlines().
if (!IsInline() && !IsFloatingOrOutOfFlowPositioned()) {
// If this is the container box of inline children, mark it.
if (IsLayoutBlockFlow())
SetNeedsCollectInlines(true);
return;
}
for (LayoutObject* object = this; !object->NeedsCollectInlines();) {
object->SetNeedsCollectInlines(true);
object = object->Parent();
if (!object || object->IsLayoutBlockFlow())
break;
}
}
void LayoutObject::MarkContainerChainForLayout(bool schedule_relayout,
SubtreeLayoutScope* layouter) {
#if DCHECK_IS_ON()
DCHECK(!IsSetNeedsLayoutForbidden());
#endif
DCHECK(!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 LocalFrameView::layout_subtree_root_list_.
schedule_relayout &= !GetFrameView()->IsInPerformLayout();
LayoutObject* object = Container();
LayoutObject* last = this;
bool simplified_normal_flow_layout = NeedsSimplifiedNormalFlowLayout() &&
!SelfNeedsLayout() &&
!NormalChildNeedsLayout();
// We need to set NeedsCollectInlines() only if LayoutNGEnabled, but setting a
// flag in non-LayoutNG is harmless.
// When we set a flag, setting another flag should be zero-cost.
if (object)
object->SetNeedsCollectInlines(true);
while (object) {
if (object->SelfNeedsLayout() || object->LayoutBlockedByDisplayLock())
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->StyleRef().HasOutOfFlowPosition()) {
object = last->ContainingBlock();
if (object->PosChildNeedsLayout())
return;
container = object->Container();
object->SetPosChildNeedsLayout(true);
object->SetNeedsCollectInlines(true);
simplified_normal_flow_layout = true;
} else if (simplified_normal_flow_layout) {
if (object->NeedsSimplifiedNormalFlowLayout())
return;
object->SetNeedsSimplifiedNormalFlowLayout(true);
object->SetNeedsCollectInlines(true);
} else {
if (object->NormalChildNeedsLayout())
return;
object->SetNormalChildNeedsLayout(true);
object->SetNeedsCollectInlines(true);
}
#if DCHECK_IS_ON()
DCHECK(!object->IsSetNeedsLayoutForbidden());
#endif
if (layouter) {
layouter->RecordObjectMarkedForLayout(object);
if (object == layouter->Root())
return;
}
last = object;
if (schedule_relayout && ObjectIsRelayoutBoundary(last))
break;
object = container;
}
if (schedule_relayout)
last->ScheduleRelayout();
}
#if DCHECK_IS_ON()
void LayoutObject::CheckBlockPositionedObjectsNeedLayout() {
DCHECK(!NeedsLayout());
if (IsLayoutBlock())
ToLayoutBlock(this)->CheckPositionedObjectsNeedLayout();
}
#endif
void LayoutObject::SetPreferredLogicalWidthsDirty(
MarkingBehavior mark_parents) {
bitfields_.SetPreferredLogicalWidthsDirty(true);
if (mark_parents == kMarkContainerChain &&
(IsText() || !StyleRef().HasOutOfFlowPosition()))
InvalidateContainerPreferredLogicalWidths();
}
void LayoutObject::ClearPreferredLogicalWidthsDirty() {
bitfields_.SetPreferredLogicalWidthsDirty(false);
}
static inline bool NGKeepInvalidatingBeyond(LayoutObject* o) {
// Because LayoutNG does not work on individual inline objects, we can't
// use a dirty width on an inline as a signal that it is safe to stop --
// inlines never get marked as clean. Instead, we need to keep going to the
// next block container.
// Atomic inlines do not have this problem as they are treated like blocks
// in this context.
if (!RuntimeEnabledFeatures::LayoutNGEnabled())
return false;
if (o->IsLayoutInline() || o->IsText())
return true;
return 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() || NGKeepInvalidatingBeyond(o))) {
// 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->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->StyleRef().HasOutOfFlowPosition())
break;
o = container;
}
}
LayoutObject* LayoutObject::ContainerForAbsolutePosition(
AncestorSkipInfo* skip_info) const {
return FindAncestorByPredicate(this, skip_info, [](LayoutObject* candidate) {
if (!candidate->StyleRef().CanContainAbsolutePositionObjects() &&
candidate->ShouldApplyLayoutContainment()) {
UseCounter::Count(candidate->GetDocument(),
WebFeature::kCSSContainLayoutPositionedDescendants);
}
return candidate->CanContainAbsolutePositionObjects();
});
}
LayoutObject* LayoutObject::ContainerForFixedPosition(
AncestorSkipInfo* skip_info) const {
DCHECK(!IsText());
return FindAncestorByPredicate(this, skip_info, [](LayoutObject* candidate) {
if (!candidate->StyleRef().CanContainFixedPositionObjects(
candidate->IsDocumentElement()) &&
candidate->ShouldApplyLayoutContainment()) {
UseCounter::Count(candidate->GetDocument(),
WebFeature::kCSSContainLayoutPositionedDescendants);
}
return candidate->CanContainFixedPositionObjects();
});
}
LayoutBlock* LayoutObject::ContainingBlockForAbsolutePosition(
AncestorSkipInfo* skip_info) const {
auto* container = ContainerForAbsolutePosition(skip_info);
return FindContainingBlock(container, skip_info);
}
LayoutBlock* LayoutObject::ContainingBlockForFixedPosition(
AncestorSkipInfo* skip_info) const {
auto* container = ContainerForFixedPosition(skip_info);
return FindContainingBlock(container, skip_info);
}
const LayoutBlock* LayoutObject::InclusiveContainingBlock() const {
if (IsLayoutBlock())
return ToLayoutBlock(this);
return ContainingBlock();
}
LayoutBlock* LayoutObject::ContainingBlock(AncestorSkipInfo* skip_info) const {
LayoutObject* object = Parent();
if (!object && IsLayoutScrollbarPart())
object = ToLayoutScrollbarPart(this)->GetScrollableArea()->GetLayoutBox();
if (!IsTextOrSVGChild()) {
if (style_->GetPosition() == EPosition::kFixed)
return ContainingBlockForFixedPosition(skip_info);
if (style_->GetPosition() == EPosition::kAbsolute)
return ContainingBlockForAbsolutePosition(skip_info);
}
if (IsColumnSpanAll()) {
object = SpannerPlaceholder()->ContainingBlock();
} else {
while (object && ((object->IsInline() && !object->IsAtomicInlineLevel()) ||
!object->IsLayoutBlock())) {
if (skip_info)
skip_info->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(
MapCoordinatesFlags flags) const {
Vector<FloatQuad> quads;
AbsoluteQuads(quads, flags);
wtf_size_t n = quads.size();
if (n == 0)
return FloatRect();
FloatRect result = quads[0].BoundingBox();
for (wtf_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);
wtf_size_t n = quads.size();
if (!n)
return IntRect();
IntRect result = quads[0].EnclosingBoundingBox();
for (wtf_size_t i = 1; i < n; ++i)
result.Unite(quads[i].EnclosingBoundingBox());
return result;
}
IntRect LayoutObject::AbsoluteBoundingBoxRectIgnoringTransforms() const {
FloatPoint abs_pos = LocalToAbsolute();
Vector<IntRect> rects;
AbsoluteRects(rects, FlooredLayoutPoint(abs_pos));
wtf_size_t n = rects.size();
if (!n)
return IntRect();
IntRect result = rects[0];
for (wtf_size_t i = 1; i < n; ++i)
result.Unite(rects[i]);
return result;
}
LayoutRect LayoutObject::AbsoluteBoundingBoxRectHandlingEmptyAnchor() const {
return AbsoluteBoundingBoxRectHelper(ExpandScrollMargin::kIgnore);
}
LayoutRect LayoutObject::AbsoluteBoundingBoxRectForScrollIntoView() const {
return AbsoluteBoundingBoxRectHelper(ExpandScrollMargin::kExpand);
}
LayoutRect LayoutObject::AbsoluteBoundingBoxRectHelper(
ExpandScrollMargin expand) const {
FloatPoint upper_left, lower_right;
bool found_upper_left = GetUpperLeftCorner(expand, upper_left);
bool found_lower_right = GetLowerRightCorner(expand, lower_right);
// If we've found one corner, but not the other,
// then we should just return a point at the corner that we did find.
if (found_upper_left != found_lower_right) {
if (found_upper_left)
lower_right = upper_left;
else
upper_left = lower_right;
}
FloatSize size = lower_right.ExpandedTo(upper_left) - upper_left;
if (std::isnan(size.Width()) || std::isnan(size.Height()))
return LayoutRect();
return EnclosingLayoutRect(FloatRect(upper_left, size));
}
namespace {
enum class MarginCorner { kTopLeft, kBottomRight };
void MovePointByScrollMargin(const LayoutObject* layout_object,
MarginCorner corner,
FloatPoint& point) {
FloatSize offset;
const ComputedStyle* style = layout_object->Style();
if (corner == MarginCorner::kTopLeft)
offset = FloatSize(-style->ScrollMarginLeft(), -style->ScrollMarginTop());
else
offset = FloatSize(style->ScrollMarginRight(), style->ScrollMarginBottom());
point.Move(offset);
}
inline const LayoutObject* EndOfContinuations(
const LayoutObject* layout_object) {
const LayoutObject* prev = nullptr;
const LayoutObject* cur = layout_object;
if (!cur->IsLayoutInline() && !cur->IsLayoutBlockFlow())
return nullptr;
while (cur) {
prev = cur;
if (cur->IsLayoutInline())
cur = ToLayoutInline(cur)->Continuation();
else
cur = ToLayoutBlockFlow(cur)->Continuation();
}
return prev;
}
} // namespace
bool LayoutObject::GetUpperLeftCorner(ExpandScrollMargin expand,
FloatPoint& point) const {
if (IsSVGChild()) {
point = LocalToAbsoluteQuad(StrokeBoundingBox(), kUseTransforms)
.BoundingBox()
.MinXMinYCorner();
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(this, MarginCorner::kTopLeft, point);
return true;
}
if (!IsInline() || IsAtomicInlineLevel()) {
point = LocalToAbsolute(FloatPoint(), kUseTransforms);
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(this, MarginCorner::kTopLeft, point);
return true;
}
// Find the next text/image child, to get a position.
const LayoutObject* runner = this;
while (runner) {
const LayoutObject* const previous = runner;
if (LayoutObject* runner_first_child = runner->SlowFirstChild()) {
runner = runner_first_child;
} else if (runner->NextSibling()) {
runner = runner->NextSibling();
} else {
LayoutObject* next = nullptr;
while (!next && runner->Parent()) {
runner = runner->Parent();
next = runner->NextSibling();
}
runner = next;
if (!runner)
break;
}
DCHECK(runner);
if (!runner->IsInline() || runner->IsAtomicInlineLevel()) {
point = runner->LocalToAbsolute(FloatPoint(), kUseTransforms);
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(runner, MarginCorner::kTopLeft, point);
return true;
}
if (runner->IsText() && !runner->IsBR()) {
const base::Optional<FloatPoint> maybe_point =
ToLayoutText(runner)->GetUpperLeftCorner();
if (maybe_point.has_value()) {
point = runner->LocalToAbsolute(maybe_point.value(), kUseTransforms);
return true;
}
if (previous->GetNode() == GetNode()) {
// Do nothing - skip unrendered whitespace that is a child or next
// sibling of the anchor.
// FIXME: This fails to skip a whitespace sibling when there was also a
// whitespace child (because |previous| has moved).
continue;
}
point = runner->LocalToAbsolute(FloatPoint(), kUseTransforms);
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(runner, MarginCorner::kTopLeft, point);
return true;
}
if (runner->IsAtomicInlineLevel()) {
DCHECK(runner->IsBox());
const LayoutBox* box = ToLayoutBox(runner);
point = FloatPoint(box->Location());
point = runner->Container()->LocalToAbsolute(point, kUseTransforms);
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(box, MarginCorner::kTopLeft, point);
return true;
}
}
// If the target doesn't have any children or siblings that could be used to
// calculate the scroll position, we must be at the end of the
// document. Scroll to the bottom.
// FIXME: who said anything about scrolling?
if (!runner && GetDocument().View()) {
point = FloatPoint(
0, GetDocument().View()->LayoutViewport()->ContentsSize().Height());
return true;
}
return false;
}
bool LayoutObject::GetLowerRightCorner(ExpandScrollMargin expand,
FloatPoint& point) const {
if (IsSVGChild()) {
point = LocalToAbsoluteQuad(StrokeBoundingBox(), kUseTransforms)
.BoundingBox()
.MaxXMaxYCorner();
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(this, MarginCorner::kBottomRight, point);
return true;
}
if (!IsInline() || IsAtomicInlineLevel()) {
const LayoutBox* box = ToLayoutBox(this);
point = LocalToAbsolute(FloatPoint(box->Size()), kUseTransforms);
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(this, MarginCorner::kBottomRight, point);
return true;
}
const LayoutObject* runner = this;
const LayoutObject* start_continuation = nullptr;
// Find the last text/image child, to get a position.
while (runner) {
if (LayoutObject* runner_last_child = runner->SlowLastChild()) {
runner = runner_last_child;
} else if (runner != this && runner->PreviousSibling()) {
runner = runner->PreviousSibling();
} else {
const LayoutObject* prev = nullptr;
while (!prev) {
// Check if the current layoutObject has contiunation and move the
// location for finding the layoutObject to the end of continuations if
// there is the continuation. Skip to check the contiunation on
// contiunations section
if (start_continuation == runner) {
start_continuation = nullptr;
} else if (!start_continuation) {
if (const LayoutObject* continuation = EndOfContinuations(runner)) {
start_continuation = runner;
prev = continuation;
break;
}
}
// Prevent to overrun out of own layout tree
if (runner == this) {
return false;
}
runner = runner->Parent();
if (!runner)
return false;
prev = runner->PreviousSibling();
}
runner = prev;
}
DCHECK(runner);
if (runner->IsText() || runner->IsAtomicInlineLevel()) {
point = FloatPoint();
if (runner->IsText()) {
const LayoutText* text = ToLayoutText(runner);
IntRect lines_box = EnclosingIntRect(text->LinesBoundingBox());
if (!lines_box.MaxX() && !lines_box.MaxY())
continue;
point.MoveBy(lines_box.MaxXMaxYCorner());
point = runner->LocalToAbsolute(point, kUseTransforms);
} else {
const LayoutBox* box = ToLayoutBox(runner);
point.MoveBy(box->FrameRect().MaxXMaxYCorner());
point = runner->Container()->LocalToAbsolute(point, kUseTransforms);
if (expand == ExpandScrollMargin::kExpand)
MovePointByScrollMargin(box, MarginCorner::kBottomRight, point);
}
return true;
}
}
return true;
}
FloatRect LayoutObject::AbsoluteBoundingBoxRectForRange(
const EphemeralRange& range) {
if (range.IsNull() || !range.StartPosition().ComputeContainerNode())
return FloatRect();
range.GetDocument().UpdateStyleAndLayout();
return ComputeTextFloatRect(range);
}
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 {}
const LayoutBoxModelObject& LayoutObject::ContainerForPaintInvalidation()
const {
CHECK(IsRooted());
if (const LayoutBoxModelObject* paint_invalidation_container =
EnclosingCompositedContainer())
return *paint_invalidation_container;
// 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* layout_view = View();
while (const LayoutObject* owner_object =
layout_view->GetFrame()->OwnerLayoutObject())
layout_view = owner_object->View();
DCHECK(layout_view);
return *layout_view;
}
bool LayoutObject::RecalcOverflow() {
if (!ChildNeedsOverflowRecalc())
return false;
bool children_overflow_changed = false;
for (LayoutObject* current = SlowFirstChild(); current;
current = current->NextSibling()) {
if (current->RecalcOverflow())
children_overflow_changed = true;
}
return children_overflow_changed;
}
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* painting_layer = PaintingLayer()) {
if (PaintLayer* compositing_layer =
painting_layer
->EnclosingLayerForPaintInvalidationCrossingFrameBoundaries())
container = &compositing_layer->GetLayoutObject();
}
return container;
}
bool LayoutObject::HasDistortingVisualEffects() const {
// TODO(szager): Check occlusion information propagated from out-of-process
// parent frame.
PropertyTreeState paint_properties = EnclosingLayer()
->GetLayoutObject()
.FirstFragment()
.LocalBorderBoxProperties();
// No filters, no blends, no opacity < 100%.
for (const auto* effect = SafeUnalias(paint_properties.Effect()); effect;
effect = SafeUnalias(effect->Parent())) {
if (!effect->Filter().IsEmpty() || !effect->BackdropFilter().IsEmpty() ||
effect->GetColorFilter() != kColorFilterNone ||
effect->BlendMode() != SkBlendMode::kSrcOver ||
effect->Opacity() != 1.0) {
return true;
}
}
PropertyTreeState root_properties = GetDocument()
.GetFrame()
->LocalFrameRoot()
.ContentLayoutObject()
->FirstFragment()
.LocalBorderBoxProperties();
// The only allowed transforms are 2D translation and proportional up-scaling.
const TransformationMatrix& matrix =
GeometryMapper::SourceToDestinationProjection(
paint_properties.Transform(), root_properties.Transform());
if (!matrix.Is2DProportionalUpscaleAndOr2DTranslation())
return true;
return false;
}
bool LayoutObject::HasNonZeroEffectiveOpacity() const {
PropertyTreeState paint_properties = EnclosingLayer()
->GetLayoutObject()
.FirstFragment()
.LocalBorderBoxProperties();
for (const auto* effect = SafeUnalias(paint_properties.Effect()); effect;
effect = SafeUnalias(effect->Parent())) {
if (effect->Opacity() == 0.0)
return false;
}
return true;
}
String LayoutObject::DecoratedName() const {
StringBuilder name;
name.Append(GetName());
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 = GetNode()) {
name.Append(' ');
name.Append(node->DebugName());
}
return name.ToString();
}
LayoutRect LayoutObject::FragmentsVisualRectBoundingBox() const {
if (!fragment_.NextFragment())
return fragment_.VisualRect();
LayoutRect visual_rect;
for (auto* fragment = &fragment_; fragment;
fragment = fragment->NextFragment())
visual_rect.Unite(fragment->VisualRect());
return visual_rect;
}
LayoutRect LayoutObject::VisualRect() const {
return FragmentsVisualRectBoundingBox();
}
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& paint_invalidation_container) const {
return paint_invalidation_container.UsesCompositedScrolling() &&
this != &paint_invalidation_container;
}
void LayoutObject::InvalidatePaintRectangle(const LayoutRect& dirty_rect) {
DCHECK_NE(GetDocument().Lifecycle().GetState(), DocumentLifecycle::kInPaint);
if (dirty_rect.IsEmpty())
return;
fragment_.SetPartialInvalidationLocalRect(
UnionRect(dirty_rect, fragment_.PartialInvalidationLocalRect()));
// Not using the WithoutGeometryChange version because we need to map the
// partial invalidated rect to visual rect in backing or the containing
// transform node.
SetShouldCheckForPaintInvalidation();
}
LayoutRect LayoutObject::AbsoluteSelectionRect() const {
LayoutRect selection_rect = LocalSelectionRect();
if (!selection_rect.IsEmpty())
MapToVisualRectInAncestorSpace(View(), selection_rect);
if (LocalFrameView* frame_view = GetFrameView())
selection_rect = frame_view->DocumentToFrame(selection_rect);
return selection_rect;
}
DISABLE_CFI_PERF
void LayoutObject::InvalidatePaint(
const PaintInvalidatorContext& context) const {
ObjectPaintInvalidatorWithContext(*this, context).InvalidatePaint();
}
void LayoutObject::AdjustVisualRectForCompositedScrolling(
LayoutRect& rect,
const LayoutBoxModelObject& paint_invalidation_container) const {
if (CompositedScrollsWithRespectTo(paint_invalidation_container)) {
LayoutSize offset(
-ToLayoutBox(&paint_invalidation_container)->ScrolledContentOffset());
rect.Move(offset);
}
}
LayoutRect LayoutObject::VisualRectIncludingCompositedScrolling(
const LayoutBoxModelObject& paint_invalidation_container) const {
LayoutRect rect = VisualRect();
AdjustVisualRectForCompositedScrolling(rect, paint_invalidation_container);
return rect;
}
void LayoutObject::ClearPreviousVisualRects() {
DCHECK(!RuntimeEnabledFeatures::SlimmingPaintV2Enabled());
for (auto* fragment = &fragment_; fragment;
fragment = fragment->NextFragment()) {
fragment->SetVisualRect(LayoutRect());
fragment->SetSelectionVisualRect(LayoutRect());
}
if (IsInline()) {
auto fragments = NGPaintFragment::InlineFragmentsFor(this);
if (fragments.IsInLayoutNGInlineFormattingContext()) {
for (auto* fragment : fragments) {
fragment->SetVisualRect(LayoutRect());
fragment->SetSelectionVisualRect(LayoutRect());
}
}
}
// After clearing ("invalidating") the visual rects, mark this object as
// needing to re-compute them.
SetShouldDoFullPaintInvalidation();
}
LayoutRect LayoutObject::VisualRectInDocument() const {
LayoutRect rect = LocalVisualRect();
MapToVisualRectInAncestorSpace(View(), rect);
return rect;
}
LayoutRect LayoutObject::LocalVisualRectIgnoringVisibility() const {
NOTREACHED();
return LayoutRect();
}
bool LayoutObject::MapToVisualRectInAncestorSpaceInternalFastPath(
const LayoutBoxModelObject* ancestor,
LayoutRect& rect,
VisualRectFlags visual_rect_flags,
bool& intersects) const {
if (!(visual_rect_flags & kUseGeometryMapper) ||
!FirstFragment().HasLocalBorderBoxProperties() || !ancestor ||
!ancestor->FirstFragment().HasLocalBorderBoxProperties()) {
intersects = true;
return false;
}
if (ancestor == this) {
intersects = true;
return true;
}
rect.MoveBy(FirstFragment().PaintOffset());
FloatClipRect clip_rect((FloatRect(rect)));
intersects = GeometryMapper::LocalToAncestorVisualRect(
FirstFragment().LocalBorderBoxProperties(),
ancestor->FirstFragment().ContentsProperties(), clip_rect,
kIgnorePlatformOverlayScrollbarSize,
(visual_rect_flags & kEdgeInclusive) ? kInclusiveIntersect
: kNonInclusiveIntersect);
rect = LayoutRect(clip_rect.Rect());
rect.MoveBy(-ancestor->FirstFragment().PaintOffset());
return true;
}
bool LayoutObject::MapToVisualRectInAncestorSpace(
const LayoutBoxModelObject* ancestor,
LayoutRect& rect,
VisualRectFlags visual_rect_flags) const {
bool intersects = true;
if (MapToVisualRectInAncestorSpaceInternalFastPath(
ancestor, rect, visual_rect_flags, intersects))
return intersects;
TransformState transform_state(TransformState::kApplyTransformDirection,
FloatQuad(FloatRect(rect)));
intersects = MapToVisualRectInAncestorSpaceInternal(ancestor, transform_state,
visual_rect_flags);
transform_state.Flatten();
rect = LayoutRect(transform_state.LastPlanarQuad().BoundingBox());
return intersects;
}
bool LayoutObject::MapToVisualRectInAncestorSpaceInternal(
const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
VisualRectFlags visual_rect_flags) 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 = Parent()) {
if (parent->IsBox()) {
LayoutBox* parent_box = ToLayoutBox(parent);
// Never flip for SVG as it handles writing modes itself.
if (!IsSVG()) {
transform_state.Flatten();
LayoutRect rect(transform_state.LastPlanarQuad().BoundingBox());
parent_box->FlipForWritingMode(rect);
transform_state.SetQuad(FloatQuad(FloatRect(rect)));
}
bool preserve3d = parent->StyleRef().Preserves3D() && !parent->IsText();
TransformState::TransformAccumulation accumulation =
preserve3d ? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform;
if (parent != ancestor &&
!parent_box->MapContentsRectToBoxSpace(transform_state, accumulation,
*this, visual_rect_flags))
return false;
}
return parent->MapToVisualRectInAncestorSpaceInternal(
ancestor, transform_state, visual_rect_flags);
}
return true;
}
HitTestResult LayoutObject::HitTestForOcclusion(
const LayoutRect& hit_rect) const {
LocalFrame* frame = GetDocument().GetFrame();
DCHECK(!frame->View()->NeedsLayout());
HitTestRequest::HitTestRequestType hit_type =
HitTestRequest::kIgnorePointerEventsNone | HitTestRequest::kReadOnly |
HitTestRequest::kIgnoreClipping |
HitTestRequest::kIgnoreZeroOpacityObjects |
HitTestRequest::kHitTestVisualOverflow;
HitTestLocation location(hit_rect);
return frame->GetEventHandler().HitTestResultAtLocation(location, hit_type,
this, true);
}
void LayoutObject::DirtyLinesFromChangedChild(LayoutObject*, MarkingBehavior) {}
std::ostream& operator<<(std::ostream& out, const LayoutObject& object) {
StringBuilder string_builder;
object.DumpLayoutObject(string_builder, false, 0);
return out << static_cast<const void*>(&object) << ":"
<< string_builder.ToString().Utf8().data();
}
std::ostream& operator<<(std::ostream& out, const LayoutObject* object) {
if (!object)
return out << "<null>";
return out << *object;
}
#ifndef NDEBUG
void LayoutObject::ShowTreeForThis() const {
if (GetNode())
::showTree(GetNode());
}
void LayoutObject::ShowLayoutTreeForThis() const {
showLayoutTree(this, nullptr);
}
void LayoutObject::ShowLineTreeForThis() const {
if (const LayoutBlock* cb = InclusiveContainingBlock()) {
if (cb->IsLayoutBlockFlow())
ToLayoutBlockFlow(cb)->ShowLineTreeAndMark(nullptr, nullptr, nullptr,
nullptr, this);
}
}
void LayoutObject::ShowLayoutObject() const {
StringBuilder string_builder;
DumpLayoutObject(string_builder, true, kShowTreeCharacterOffset);
DLOG(INFO) << "\n" << string_builder.ToString().Utf8().data();
}
#endif // NDEBUG
void LayoutObject::DumpLayoutObject(StringBuilder& string_builder,
bool dump_address,
unsigned show_tree_character_offset) const {
string_builder.Append(DecoratedName());
if (dump_address)
string_builder.Append(String::Format(" %p", this));
if (IsText() && ToLayoutText(this)->IsTextFragment())
string_builder.Append(String::Format(
" \"%s\" ", ToLayoutText(this)->GetText().Ascii().data()));
if (VirtualContinuation())
string_builder.Append(
String::Format(" continuation=%p", VirtualContinuation()));
if (GetNode()) {
while (string_builder.length() < show_tree_character_offset)
string_builder.Append(' ');
string_builder.Append('\t');
string_builder.Append(GetNode()->ToString().Utf8().data());
}
}
#ifndef NDEBUG
void LayoutObject::DumpLayoutTreeAndMark(StringBuilder& string_builder,
const LayoutObject* marked_object1,
const char* marked_label1,
const LayoutObject* marked_object2,
const char* marked_label2,
unsigned depth) const {
StringBuilder object_info;
if (marked_object1 == this && marked_label1)
object_info.Append(marked_label1);
if (marked_object2 == this && marked_label2)
object_info.Append(marked_label2);
while (object_info.length() < depth * 2)
object_info.Append(' ');
DumpLayoutObject(object_info, true, kShowTreeCharacterOffset);
string_builder.Append(object_info);
for (const LayoutObject* child = SlowFirstChild(); child;
child = child->NextSibling()) {
string_builder.Append('\n');
child->DumpLayoutTreeAndMark(string_builder, marked_object1, marked_label1,
marked_object2, marked_label2, depth + 1);
}
}
#endif // NDEBUG
bool LayoutObject::IsSelected() const {
return LayoutSelection::IsSelected(*this);
}
bool LayoutObject::IsSelectable() const {
return !IsInert() && !(StyleRef().UserSelect() == EUserSelect::kNone &&
StyleRef().UserModify() == EUserModify::kReadOnly);
}
// 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->StyleRef().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();
}
// 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 (StyleRef().HasBorderColorReferencingCurrentColor() ||
StyleRef().HasOutlineWithCurrentColor() ||
StyleRef().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() && StyleRef().SvgStyle().IsFillColorCurrentColor()) ||
(IsSVG() && StyleRef().SvgStyle().IsStrokeColorCurrentColor()) ||
IsListMarker() || IsDetailsMarker())
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 requires_layer =
ToLayoutBoxModelObject(this)->LayerTypeRequired() != kNoPaintLayer;
if (HasLayer() != requires_layer)
diff.SetNeedsFullLayout();
}
return diff;
}
void LayoutObject::SetPseudoStyle(scoped_refptr<ComputedStyle> pseudo_style) {
DCHECK(pseudo_style->StyleType() == kPseudoIdBefore ||
pseudo_style->StyleType() == kPseudoIdAfter ||
pseudo_style->StyleType() == kPseudoIdFirstLetter);
// 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()) {
scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
style->InheritFrom(*pseudo_style);
SetStyle(std::move(style));
return;
}
SetStyle(std::move(pseudo_style));
}
void LayoutObject::FirstLineStyleDidChange(const ComputedStyle& old_style,
const ComputedStyle& new_style) {
StyleDifference diff =
old_style.VisualInvalidationDiff(GetDocument(), new_style);
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* first_line_container = nullptr;
if (BehavesLikeBlockContainer()) {
// This object is a LayoutBlock having PseudoIdFirstLine pseudo style
// changed.
first_line_container =
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* first_line_box =
ToLayoutInline(this)->FirstLineBoxIncludingCulling()) {
if (first_line_box->IsFirstLineStyle())
first_line_container = ToLayoutBlockFlow(ContainingBlock());
}
}
if (first_line_container)
first_line_container->SetShouldDoFullPaintInvalidationForFirstLine();
}
if (diff.NeedsLayout())
SetNeedsLayoutAndPrefWidthsRecalc(layout_invalidation_reason::kStyleChange);
}
void LayoutObject::MarkContainerChainForOverflowRecalcIfNeeded() {
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->Container();
if (object) {
object->SetChildNeedsLayoutOverflowRecalc();
object->SetChildNeedsVisualOverflowRecalc();
}
} while (object);
}
void LayoutObject::SetNeedsOverflowRecalc() {
bool needed_recalc = NeedsLayoutOverflowRecalc();
SetSelfNeedsLayoutOverflowRecalc();
SetSelfNeedsVisualOverflowRecalc();
SetShouldCheckForPaintInvalidation();
if (!needed_recalc)
MarkContainerChainForOverflowRecalcIfNeeded();
}
DISABLE_CFI_PERF
void LayoutObject::SetStyle(scoped_refptr<ComputedStyle> style) {
DCHECK(style);
if (style_ == style)
return;
StyleDifference diff;
if (style_) {
diff = style_->VisualInvalidationDiff(GetDocument(), *style);
} else {
// If there was no previous style, set the object as at least needing
// paint invalidation, to prevent diff.HasDifference() from returning
// false.
// TODO(chrishtr): shouldn't this set all of the bits? crbug.com/817610.
diff.SetNeedsPaintInvalidationObject();
}
diff = AdjustStyleDifference(diff);
StyleWillChange(diff, *style);
scoped_refptr<ComputedStyle> old_style = std::move(style_);
SetStyleInternal(std::move(style));
UpdateFillImages(old_style ? &old_style->BackgroundLayers() : nullptr,
style_->BackgroundLayers());
UpdateFillImages(old_style ? &old_style->MaskLayers() : nullptr,
style_->MaskLayers());
UpdateImage(old_style ? old_style->BorderImage().GetImage() : nullptr,
style_->BorderImage().GetImage());
UpdateImage(old_style ? old_style->MaskBoxImage().GetImage() : nullptr,
style_->MaskBoxImage().GetImage());
StyleImage* new_content_image =
style_->GetContentData() && style_->GetContentData()->IsImage()
? ToImageContentData(style_->GetContentData())->GetImage()
: nullptr;
StyleImage* old_content_image =
old_style && old_style->GetContentData() &&
old_style->GetContentData()->IsImage()
? ToImageContentData(old_style->GetContentData())->GetImage()
: nullptr;
UpdateImage(old_content_image, new_content_image);
StyleImage* new_box_reflect_mask_image =
style_->BoxReflect() ? style_->BoxReflect()->Mask().GetImage() : nullptr;
StyleImage* old_box_reflect_mask_image =
old_style && old_style->BoxReflect()
? old_style->BoxReflect()->Mask().GetImage()
: nullptr;
UpdateImage(old_box_reflect_mask_image, new_box_reflect_mask_image);
UpdateShapeImage(old_style ? old_style->ShapeOutside() : nullptr,
style_->ShapeOutside());
UpdateCursorImages(old_style ? old_style->Cursors() : nullptr,
style_->Cursors());
CheckCounterChanges(old_style.get(), style_.get());
bool does_not_need_layout_or_paint_invalidation = !parent_;
StyleDidChange(diff, old_style.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 (does_not_need_layout_or_paint_invalidation)
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 updated_diff = AdjustStyleDifference(diff);
if (!diff.NeedsFullLayout()) {
if (updated_diff.NeedsFullLayout()) {
SetNeedsLayoutAndPrefWidthsRecalc(
layout_invalidation_reason::kStyleChange);
} else if (updated_diff.NeedsPositionedMovementLayout()) {
SetNeedsPositionedMovementLayout();
}
}
if (diff.TransformChanged() && !NeedsLayout()) {
if (LayoutBlock* container = ContainingBlock())
container->SetNeedsOverflowRecalc();
}
if (diff.NeedsRecomputeOverflow() && !NeedsLayout()) {
// TODO(rhogan): Make inlines capable of recomputing overflow too.
if (IsLayoutBlock()) {
SetNeedsOverflowRecalc();
} else {
SetNeedsLayoutAndPrefWidthsRecalc(
layout_invalidation_reason::kStyleChange);
}
}
if (diff.NeedsPaintInvalidationSubtree() ||
updated_diff.NeedsPaintInvalidationSubtree()) {
SetSubtreeShouldDoFullPaintInvalidation();
} else if (diff.NeedsPaintInvalidationObject() ||
updated_diff.NeedsPaintInvalidationObject()) {
// TODO(wangxianzhu): For now LayoutSVGRoot::localVisualRect() depends on
// several styles. Refactor to avoid this special case.
if (IsSVGRoot())
SetShouldDoFullPaintInvalidation();
else
SetShouldDoFullPaintInvalidationWithoutGeometryChange();
}
if ((diff.NeedsPaintInvalidationObject() ||
diff.NeedsPaintInvalidationSubtree()) &&
old_style && !old_style->ClipPathDataEquivalent(*style_))
InvalidateClipPathCache();
if (diff.NeedsVisualRectUpdate())
SetShouldCheckForPaintInvalidation();
// Text nodes share style with their parents but the paint properties don't
// apply to them, hence the !isText() check. If property nodes are added or
// removed as a result of these style changes, PaintPropertyTreeBuilder will
// call SetNeedsRepaint to cause re-generation of PaintChunks.
if (!IsText() && !HasLayer() &&
(diff.TransformChanged() || diff.OpacityChanged() ||
diff.ZIndexChanged() || diff.FilterChanged() ||
diff.BackdropFilterChanged() || diff.CssClipChanged() ||
diff.BlendModeChanged() || diff.MaskChanged())) {
SetNeedsPaintPropertyUpdate();
}
}
void LayoutObject::StyleWillChange(StyleDifference diff,
const ComputedStyle& new_style) {
if (style_) {
bool visibility_changed = style_->Visibility() != new_style.Visibility();
// If our z-index changes value or our visibility changes,
// we need to dirty our stacking context's z-order list.
if (visibility_changed || style_->ZIndex() != new_style.ZIndex() ||
style_->IsStackingContext() != new_style.IsStackingContext()) {
GetDocument().SetAnnotatedRegionsDirty(true);
if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
if (GetNode())
cache->ChildrenChanged(GetNode()->parentNode());
else
cache->ChildrenChanged(Parent());
}
}
if (diff.TextDecorationOrColorChanged() ||
style_->InsideLink() != new_style.InsideLink()) {
if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
cache->TextChanged(this);
}
if (diff.TransformChanged()) {
if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
cache->LocationChanged(this);
}
// Keep layer hierarchy visibility bits up to date if visibility changes.
if (visibility_changed) {
// We might not have an enclosing layer yet because we might not be in the
// tree.
if (PaintLayer* layer = EnclosingLayer())
layer->DirtyVisibleContentStatus();
}
if (IsFloating() && (style_->Floating() != new_style.Floating())) {
// For changes in float styles, we need to conceivably remove ourselves
// from the floating objects list.
ToLayoutBox(this)->RemoveFloatingOrPositionedChildFromBlockLists();
} else if (IsOutOfFlowPositioned() &&
(style_->GetPosition() != new_style.GetPosition())) {
// For changes in positioning styles, we need to conceivably remove
// ourselves from the positioned objects list.
ToLayoutBox(this)->RemoveFloatingOrPositionedChildFromBlockLists();
}
affects_parent_block_ =
IsFloatingOrOutOfFlowPositioned() &&
(!new_style.IsFloating() && !new_style.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 {
affects_parent_block_ = 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.
//
// Elements may inherit touch action from parent frame, so we need to report
// touchstart handler if the root layout object has non-auto effective touch
// action.
TouchAction old_touch_action = TouchAction::kTouchActionAuto;
bool is_document_element = GetNode() && IsDocumentElement();
if (style_) {
old_touch_action = is_document_element ? style_->GetEffectiveTouchAction()
: style_->GetTouchAction();
}
TouchAction new_touch_action = is_document_element
? new_style.GetEffectiveTouchAction()
: new_style.GetTouchAction();
if (GetNode() && !GetNode()->IsTextNode() &&
(old_touch_action == TouchAction::kTouchActionAuto) !=
(new_touch_action == TouchAction::kTouchActionAuto)) {
EventHandlerRegistry& registry =
GetDocument().GetFrame()->GetEventHandlerRegistry();
if (new_touch_action != TouchAction::kTouchActionAuto) {
registry.DidAddEventHandler(*GetNode(),
EventHandlerRegistry::kTouchAction);
} else {
registry.DidRemoveEventHandler(*GetNode(),
EventHandlerRegistry::kTouchAction);
}
if (RuntimeEnabledFeatures::PaintTouchActionRectsEnabled())
MarkEffectiveWhitelistedTouchActionChanged();
}
}
void LayoutObject::ClearBaseComputedStyle() {
if (!GetNode())
return;
if (!GetNode()->IsElementNode())
return;
if (ElementAnimations* animations =
ToElement(GetNode())->GetElementAnimations())
animations->ClearBaseComputedStyle();
}
static bool AreNonIdenticalCursorListsEqual(const ComputedStyle* a,
const ComputedStyle* b) {
DCHECK_NE(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* viewport_defining_element = GetDocument().ViewportDefiningElement();
while (object) {
if (object->IsLayoutBlock()) {
LayoutBlock* block = ToLayoutBlock(object);
if (block->HasOverflowClip() ||
block->GetNode() == viewport_defining_element) {
block->SetScrollAnchorDisablingStyleChanged(true);
return;
}
}
object = object->Parent();
}
}
void LayoutObject::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
// First assume the outline will be affected. It may be updated when we know
// it's not affected.
bool has_outline = style_->HasOutline();
SetOutlineMayBeAffectedByDescendants(has_outline);
if (!has_outline)
SetPreviousOutlineMayBeAffectedByDescendants(false);
if (affects_parent_block_)
HandleDynamicFloatPositionChange(this);
if (diff.NeedsFullLayout()) {
// If the in-flow state of an element is changed, disable scroll
// anchoring on the containing scroller.
if (old_style->HasOutOfFlowPosition() != 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() && old_style->GetPosition() != style_->GetPosition())
MarkContainerChainForLayout();
// Ditto.
if (NeedsOverflowRecalc() &&
old_style->GetPosition() != style_->GetPosition())
MarkContainerChainForOverflowRecalcIfNeeded();
SetNeedsLayoutAndPrefWidthsRecalc(layout_invalidation_reason::kStyleChange);
} 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 (old_style && !AreCursorsEqual(old_style, Style())) {
if (LocalFrame* frame = GetFrame()) {
// 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().GetEventHandler().ScheduleCursorUpdate();
}
}
if (diff.NeedsFullPaintInvalidation() && old_style) {
if (ResolveColor(*old_style, GetCSSPropertyBackgroundColor()) !=
ResolveColor(GetCSSPropertyBackgroundColor()) ||
old_style->BackgroundLayers() != StyleRef().BackgroundLayers())
SetBackgroundNeedsFullPaintInvalidation();
}
if (old_style && old_style->StyleType() == kPseudoIdNone)
ApplyPseudoStyleChanges(*old_style);
if (old_style &&
old_style->UsedTransformStyle3D() != StyleRef().UsedTransformStyle3D()) {
// Change of transform-style may affect descendant transform property nodes.
AddSubtreePaintPropertyUpdateReason(
SubtreePaintPropertyUpdateReason::kTransformStyleChanged);
}
}
void LayoutObject::ApplyPseudoStyleChanges(const ComputedStyle& old_style) {
if (old_style.HasPseudoStyle(kPseudoIdFirstLine) ||
StyleRef().HasPseudoStyle(kPseudoIdFirstLine))
ApplyFirstLineChanges(old_style);
if (old_style.HasPseudoStyle(kPseudoIdSelection) ||
StyleRef().HasPseudoStyle(kPseudoIdSelection))
InvalidateSelectedChildrenOnStyleChange();
}
void LayoutObject::ApplyFirstLineChanges(const ComputedStyle& old_style) {
if (old_style.HasPseudoStyle(kPseudoIdFirstLine)) {
scoped_refptr<const ComputedStyle> old_pseudo_style =
old_style.GetCachedPseudoStyle(kPseudoIdFirstLine);
if (StyleRef().HasPseudoStyle(kPseudoIdFirstLine) && old_pseudo_style) {
scoped_refptr<const ComputedStyle> new_pseudo_style =
UncachedFirstLineStyle();
if (new_pseudo_style) {
FirstLineStyleDidChange(*old_pseudo_style, *new_pseudo_style);
return;
}
}
}
SetNeedsLayoutAndPrefWidthsRecalc(layout_invalidation_reason::kStyleChange);
}
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->StyleRef().StyleType() != kPseudoIdNone)
continue;
if (child->AnonymousHasStylePropagationOverride())
continue;
scoped_refptr<ComputedStyle> new_style =
ComputedStyle::CreateAnonymousStyleWithDisplay(
StyleRef(), child->StyleRef().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())
new_style->SetPosition(child->StyleRef().GetPosition());
if (child->IsLayoutNGListMarker())
new_style->SetWhiteSpace(child->StyleRef().WhiteSpace());
UpdateAnonymousChildStyle(child, *new_style);
child->SetStyle(std::move(new_style));
}
if (StyleRef().StyleType() == kPseudoIdNone)
return;
// Propagate style from pseudo elements to generated content. We skip children
// with pseudo element StyleType() in the for-loop above and skip over
// descendants which are not generated content in this subtree traversal.
//
// TODO(futhark): It's possible we could propagate anonymous style from pseudo
// elements through anonymous table layout objects in the recursive
// implementation above, but it would require propagating the StyleType()
// somehow because there is code relying on generated content having a certain
// StyleType().
LayoutObject* child = NextInPreOrder(this);
while (child) {
if (!child->IsAnonymous()) {
// Don't propagate into non-anonymous descendants of pseudo elements. This
// can typically happen for ::first-letter inside ::before. The
// ::first-letter will propagate to its anonymous children separately.
child = child->NextInPreOrderAfterChildren(this);
continue;
}
if (child->IsText() || child->IsQuote() || child->IsImage())
child->SetPseudoStyle(MutableStyle());
child = child->NextInPreOrder(this);
}
}
void LayoutObject::SetStyleWithWritingModeOf(scoped_refptr<ComputedStyle> style,
LayoutObject* parent) {
if (parent) {
style->SetWritingMode(parent->StyleRef().GetWritingMode());
style->UpdateFontOrientation();
}
SetStyle(std::move(style));
}
void LayoutObject::SetStyleWithWritingModeOfParent(
scoped_refptr<ComputedStyle> style) {
SetStyleWithWritingModeOf(std::move(style), Parent());
}
void LayoutObject::UpdateFillImages(const FillLayer* old_layers,
const FillLayer& new_layers) {
// Optimize the common case
if (FillLayer::ImagesIdentical(old_layers, &new_layers))
return;
// Go through the new layers and addClients first, to avoid removing all
// clients of an image.
for (const FillLayer* curr_new = &new_layers; curr_new;
curr_new = curr_new->Next()) {
if (curr_new->GetImage())
curr_new->GetImage()->AddClient(this);
}
for (const FillLayer* curr_old = old_layers; curr_old;
curr_old = curr_old->Next()) {
if (curr_old->GetImage())
curr_old->GetImage()->RemoveClient(this);
}
}
void LayoutObject::UpdateCursorImages(const CursorList* old_cursors,
const CursorList* new_cursors) {
if (old_cursors && new_cursors && *old_cursors == *new_cursors)
return;
if (new_cursors) {
for (const CursorData& cursor_new : *new_cursors) {
if (cursor_new.GetImage())
cursor_new.GetImage()->AddClient(this);
}
}
RemoveCursorImageClient(old_cursors);
}
void LayoutObject::UpdateImage(StyleImage* old_image, StyleImage* new_image) {
if (old_image != new_image) {
if (old_image)
old_image->RemoveClient(this);
if (new_image)
new_image->AddClient(this);
}
}
void LayoutObject::UpdateShapeImage(const ShapeValue* old_shape_value,
const ShapeValue* new_shape_value) {
if (old_shape_value || new_shape_value) {
UpdateImage(old_shape_value ? old_shape_value->GetImage() : nullptr,
new_shape_value ? new_shape_value->GetImage() : nullptr);
}
}
void LayoutObject::CheckCounterChanges(const ComputedStyle* old_style,
const ComputedStyle* new_style) {
DCHECK(new_style);
if (old_style) {
if (old_style->CounterDirectivesEqual(*new_style))
return;
} else {
if (!new_style->GetCounterDirectives())
return;
}
LayoutCounter::LayoutObjectStyleChanged(*this, old_style, *new_style);
View()->SetNeedsCounterUpdate();
}
LayoutRect LayoutObject::ViewRect() const {
return View()->ViewRect();
}
FloatPoint LayoutObject::LocalToAbsolute(const FloatPoint& local_point,
MapCoordinatesFlags mode) const {
TransformState transform_state(TransformState::kApplyTransformDirection,
local_point);
MapLocalToAncestor(nullptr, transform_state, mode | kApplyContainerFlip);
transform_state.Flatten();
return transform_state.LastPlanarPoint();
}
FloatPoint LayoutObject::AncestorToLocal(LayoutBoxModelObject* ancestor,
const FloatPoint& container_point,
MapCoordinatesFlags mode) const {
TransformState transform_state(
TransformState::kUnapplyInverseTransformDirection, container_point);
MapAncestorToLocal(ancestor, transform_state, mode);
transform_state.Flatten();
return transform_state.LastPlanarPoint();
}
FloatQuad LayoutObject::AncestorToLocalQuad(LayoutBoxModelObject* ancestor,
const FloatQuad& quad,
MapCoordinatesFlags mode) const {
TransformState transform_state(
TransformState::kUnapplyInverseTransformDirection,
quad.BoundingBox().Center(), quad);
MapAncestorToLocal(ancestor, transform_state, mode);
transform_state.Flatten();
return transform_state.LastPlanarQuad();
}
void LayoutObject::MapLocalToAncestor(const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
MapCoordinatesFlags mode) const {
if (ancestor == this)
return;
AncestorSkipInfo skip_info(ancestor);
const LayoutObject* container = Container(&skip_info);
if (!container)
return;
if (mode & kApplyContainerFlip) {
if (IsBox()) {
mode &= ~kApplyContainerFlip;
} else if (container->IsBox()) {
if (container->StyleRef().IsFlippedBlocksWritingMode()) {
IntPoint center_point = RoundedIntPoint(transform_state.MappedPoint());
transform_state.Move(ToLayoutBox(container)->FlipForWritingMode(
LayoutPoint(center_point)) -
center_point);
}
mode &= ~kApplyContainerFlip;
}
}
LayoutSize container_offset =
OffsetFromContainer(container, mode & kIgnoreScrollOffset);
// TODO(smcgruer): This is inefficient. Instead we should avoid including
// offsetForInFlowPosition in offsetFromContainer when ignoring sticky.
if (mode & kIgnoreStickyOffset && IsStickyPositioned()) {
container_offset -= 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.
container_offset +=
ColumnOffset(LayoutPoint(transform_state.MappedPoint()));
}
// Text objects just copy their parent's computed style, so we need to ignore
// them.
bool preserve3d =
mode & kUseTransforms &&
((container->StyleRef().Preserves3D() && !container->IsText()) ||
(StyleRef().Preserves3D() && !IsText()));
if (mode & kUseTransforms && ShouldUseTransformFromContainer(container)) {
TransformationMatrix t;
GetTransformFromContainer(container, container_offset, t);
transform_state.ApplyTransform(t, preserve3d
? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform);
} else {
transform_state.Move(container_offset.Width(), container_offset.Height(),
preserve3d ? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform);
}
if (skip_info.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 container_offset =
ancestor->OffsetFromAncestor(container);
transform_state.Move(-container_offset.Width(), -container_offset.Height(),
preserve3d ? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform);
// If the ancestor is fixed, then the rect is already in its coordinates so
// doesn't need viewport-adjusting.
if (ancestor->StyleRef().GetPosition() != EPosition::kFixed &&
container->IsLayoutView() &&
StyleRef().GetPosition() == EPosition::kFixed) {
LayoutSize adjustment = ToLayoutView(container)->OffsetForFixedPosition();
transform_state.Move(adjustment.Width(), adjustment.Height());
}
return;
}