blob: fbcd36efee51565c6c379c77cc1380d5cf0b918b [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
* (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc.
* All rights reserved.
* Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include <math.h>
#include <algorithm>
#include <utility>
#include "base/memory/values_equivalent.h"
#include "cc/input/scroll_snap_data.h"
#include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink.h"
#include "third_party/blink/public/platform/web_theme_engine.h"
#include "third_party/blink/public/strings/grit/blink_strings.h"
#include "third_party/blink/renderer/core/css/properties/longhands.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/ime/input_method_controller.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.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/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h"
#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
#include "third_party/blink/renderer/core/html/html_div_element.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_frame_element_base.h"
#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
#include "third_party/blink/renderer/core/html/shadow/shadow_element_utils.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/layout/anchor_position_scroll_data.h"
#include "third_party/blink/renderer/core/layout/box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/constraint_space.h"
#include "third_party/blink/renderer/core/layout/constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/custom/custom_layout_child.h"
#include "third_party/blink/renderer/core/layout/custom/layout_custom.h"
#include "third_party/blink/renderer/core/layout/custom/layout_worklet.h"
#include "third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope_proxy.h"
#include "third_party/blink/renderer/core/layout/custom_scrollbar.h"
#include "third_party/blink/renderer/core/layout/disable_layout_side_effects_scope.h"
#include "third_party/blink/renderer/core/layout/forms/layout_fieldset.h"
#include "third_party/blink/renderer/core/layout/forms/layout_text_control.h"
#include "third_party/blink/renderer/core/layout/fragmentation_utils.h"
#include "third_party/blink/renderer/core/layout/geometry/box_strut.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/inline/inline_cursor.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_object_inlines.h"
#include "third_party/blink/renderer/core/layout/layout_result.h"
#include "third_party/blink/renderer/core/layout/layout_utils.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/legacy_layout_tree_walking.h"
#include "third_party/blink/renderer/core/layout/length_utils.h"
#include "third_party/blink/renderer/core/layout/logical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/measure_cache.h"
#include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
#include "third_party/blink/renderer/core/layout/table/layout_table.h"
#include "third_party/blink/renderer/core/layout/table/layout_table_cell.h"
#include "third_party/blink/renderer/core/layout/text_utils.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/page/autoscroll_controller.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/box_paint_invalidator.h"
#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
#include "third_party/blink/renderer/core/paint/outline_painter.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/rounded_border_geometry.h"
#include "third_party/blink/renderer/core/resize_observer/resize_observer_size.h"
#include "third_party/blink/renderer/core/scroll/scroll_into_view_util.h"
#include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
#include "third_party/blink/renderer/core/style/shadow_list.h"
#include "third_party/blink/renderer/core/style/style_overflow_clip_margin.h"
#include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
#include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include "third_party/blink/renderer/platform/theme/web_theme_engine_helper.h"
#include "third_party/blink/renderer/platform/wtf/size_assertions.h"
#include "ui/gfx/geometry/quad_f.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace blink {
using mojom::blink::FormControlType;
// Used by flexible boxes when flexing this element and by table cells.
typedef WTF::HashMap<const LayoutBox*, LayoutUnit> OverrideSizeMap;
// Size of border belt for autoscroll. When mouse pointer in border belt,
// autoscroll is started.
static const int kAutoscrollBeltSize = 20;
static const unsigned kBackgroundObscurationTestMaxDepth = 4;
struct SameSizeAsLayoutBox : public LayoutBoxModelObject {
DeprecatedLayoutRect frame_rect;
PhysicalSize previous_size;
MinMaxSizes intrinsic_logical_widths;
Member<void*> min_max_sizes_cache;
Member<void*> cache;
HeapVector<Member<const LayoutResult>, 1> layout_results;
wtf_size_t first_fragment_item_index_;
Member<void*> members[2];
};
ASSERT_SIZE(LayoutBox, SameSizeAsLayoutBox);
namespace {
LayoutUnit TextAreaIntrinsicInlineSize(const HTMLTextAreaElement& textarea,
const LayoutBox& box) {
// Always add the scrollbar thickness for 'overflow:auto'.
const auto& style = box.StyleRef();
int scrollbar_thickness = 0;
if (style.OverflowBlockDirection() == EOverflow::kScroll ||
style.OverflowBlockDirection() == EOverflow::kAuto) {
scrollbar_thickness = layout_text_control::ScrollbarThickness(box);
}
return LayoutUnit(ceilf(layout_text_control::GetAvgCharWidth(style) *
textarea.cols())) +
scrollbar_thickness;
}
LayoutUnit TextFieldIntrinsicInlineSize(const HTMLInputElement& input,
const LayoutBox& box) {
int factor;
const bool includes_decoration = input.SizeShouldIncludeDecoration(factor);
if (factor <= 0)
factor = 20;
const float char_width = layout_text_control::GetAvgCharWidth(box.StyleRef());
float float_result = char_width * factor;
float max_char_width = 0.f;
const Font& font = box.StyleRef().GetFont();
if (layout_text_control::HasValidAvgCharWidth(font)) {
max_char_width = font.PrimaryFont()->MaxCharWidth();
}
// For text inputs, IE adds some extra width.
if (max_char_width > char_width)
float_result += max_char_width - char_width;
LayoutUnit result(ceilf(float_result));
if (includes_decoration) {
const auto* spin_button =
To<HTMLElement>(input.UserAgentShadowRoot()->getElementById(
shadow_element_names::kIdSpinButton));
if (LayoutBox* spin_box =
spin_button ? spin_button->GetLayoutBox() : nullptr) {
const Length& logical_width = spin_box->StyleRef().LogicalWidth();
result += spin_box->BorderAndPaddingLogicalWidth();
// Since the width of spin_box is not calculated yet,
// spin_box->LogicalWidth() returns 0. Use the computed logical
// width instead.
if (logical_width.IsPercent()) {
if (logical_width.Value() != 100.f) {
result +=
result * logical_width.Value() / (100 - logical_width.Value());
}
} else {
result += logical_width.Value();
}
}
}
return result;
}
LayoutUnit TextAreaIntrinsicBlockSize(const HTMLTextAreaElement& textarea,
const LayoutBox& box) {
// Only add the scrollbar thickness for 'overflow: scroll'.
int scrollbar_thickness = 0;
if (box.StyleRef().OverflowInlineDirection() == EOverflow::kScroll) {
scrollbar_thickness = layout_text_control::ScrollbarThickness(box);
}
const auto* inner_editor = textarea.InnerEditorElement();
const LayoutUnit line_height =
inner_editor && inner_editor->GetLayoutBox()
? inner_editor->GetLayoutBox()->FirstLineHeight()
: box.FirstLineHeight();
return line_height * textarea.rows() + scrollbar_thickness;
}
LayoutUnit TextFieldIntrinsicBlockSize(const HTMLInputElement& input,
const LayoutBox& box) {
const auto* inner_editor = input.InnerEditorElement();
// inner_editor's LayoutBox can be nullptr because web authors can set
// display:none to ::-webkit-textfield-decoration-container element.
const LayoutBox& target_box = (inner_editor && inner_editor->GetLayoutBox())
? *inner_editor->GetLayoutBox()
: box;
return target_box.FirstLineHeight();
}
LayoutUnit FileUploadControlIntrinsicInlineSize(const HTMLInputElement& input,
const LayoutBox& box) {
// This should match to margin-inline-end of ::-webkit-file-upload-button UA
// style.
constexpr int kAfterButtonSpacing = 4;
// Figure out how big the filename space needs to be for a given number of
// characters (using "0" as the nominal character).
constexpr int kDefaultWidthNumChars = 34;
constexpr UChar kCharacter = '0';
const String character_as_string = String(&kCharacter, 1u);
const float min_default_label_width =
kDefaultWidthNumChars *
ComputeTextWidth(character_as_string, box.StyleRef());
const String label =
input.GetLocale().QueryString(IDS_FORM_FILE_NO_FILE_LABEL);
float default_label_width = ComputeTextWidth(label, box.StyleRef());
if (HTMLInputElement* button = input.UploadButton()) {
if (auto* button_box = button->GetLayoutBox()) {
const ComputedStyle& button_style = button_box->StyleRef();
WritingMode mode = button_style.GetWritingMode();
ConstraintSpaceBuilder builder(mode, button_style.GetWritingDirection(),
/* is_new_fc */ true);
LayoutUnit max =
BlockNode(button_box)
.ComputeMinMaxSizes(mode, MinMaxSizesType::kIntrinsic,
builder.ToConstraintSpace())
.sizes.max_size;
default_label_width +=
max + (kAfterButtonSpacing * box.StyleRef().EffectiveZoom());
}
}
return LayoutUnit(
ceilf(std::max(min_default_label_width, default_label_width)));
}
LayoutUnit SliderIntrinsicInlineSize(const LayoutBox& box) {
constexpr int kDefaultTrackLength = 129;
return LayoutUnit(kDefaultTrackLength * box.StyleRef().EffectiveZoom());
}
LogicalSize ThemePartIntrinsicSize(const LayoutBox& box,
WebThemeEngine::Part part) {
const auto& style = box.StyleRef();
PhysicalSize size(
WebThemeEngineHelper::GetNativeThemeEngine()->GetSize(part));
size.Scale(style.EffectiveZoom());
return size.ConvertToLogical(style.GetWritingMode());
}
LayoutUnit ListBoxDefaultItemHeight(const LayoutBox& box) {
constexpr int kDefaultPaddingBottom = 1;
const SimpleFontData* font_data = box.StyleRef().GetFont().PrimaryFont();
if (!font_data)
return LayoutUnit();
return LayoutUnit(font_data->GetFontMetrics().Height() +
kDefaultPaddingBottom);
}
// TODO(crbug.com/1040826): This function is written in LayoutObject API
// so that this works in both of the legacy layout and LayoutNG. We
// should have LayoutNG-specific code.
LayoutUnit ListBoxItemBlockSize(const HTMLSelectElement& select,
const LayoutBox& box) {
const auto& items = select.GetListItems();
if (items.empty() || box.ShouldApplySizeContainment())
return ListBoxDefaultItemHeight(box);
LayoutUnit max_block_size;
for (Element* element : items) {
if (auto* optgroup = DynamicTo<HTMLOptGroupElement>(element))
element = &optgroup->OptGroupLabelElement();
LayoutUnit item_block_size;
if (auto* layout_box = element->GetLayoutBox()) {
item_block_size = box.StyleRef().IsHorizontalWritingMode()
? layout_box->Size().height
: layout_box->Size().width;
} else {
item_block_size = ListBoxDefaultItemHeight(box);
}
max_block_size = std::max(max_block_size, item_block_size);
}
return max_block_size;
}
LayoutUnit MenuListIntrinsicInlineSize(const HTMLSelectElement& select,
const LayoutBox& box) {
const ComputedStyle& style = box.StyleRef();
float max_option_width = 0;
if (!box.ShouldApplySizeContainment()) {
for (auto* const option : select.GetOptionList()) {
String text =
style.ApplyTextTransform(option->TextIndentedToRespectGroupLabel());
// We apply SELECT's style, not OPTION's style because max_option_width is
// used to determine intrinsic width of the menulist box.
max_option_width =
std::max(max_option_width, ComputeTextWidth(text, style));
}
}
LayoutTheme& theme = LayoutTheme::GetTheme();
int paddings = theme.PopupInternalPaddingStart(style) +
theme.PopupInternalPaddingEnd(box.GetFrame(), style);
return LayoutUnit(ceilf(max_option_width)) + LayoutUnit(paddings);
}
LayoutUnit MenuListIntrinsicBlockSize(const HTMLSelectElement& select,
const LayoutBox& box) {
if (!box.StyleRef().HasEffectiveAppearance())
return kIndefiniteSize;
const SimpleFontData* font_data = box.StyleRef().GetFont().PrimaryFont();
DCHECK(font_data);
const LayoutBox* inner_box =
select.InnerElementForAppearanceAuto().GetLayoutBox();
return (font_data ? font_data->GetFontMetrics().Height() : 0) +
(inner_box ? inner_box->BorderAndPaddingLogicalHeight()
: LayoutUnit());
}
#if DCHECK_IS_ON()
void CheckDidAddFragment(const LayoutBox& box,
const PhysicalBoxFragment& new_fragment,
wtf_size_t new_fragment_index = kNotFound) {
// If |HasFragmentItems|, |ChildrenInline()| should be true.
// |HasFragmentItems| uses this condition to optimize .
if (new_fragment.HasItems())
DCHECK(box.ChildrenInline());
wtf_size_t index = 0;
for (const PhysicalBoxFragment& fragment : box.PhysicalFragments()) {
DCHECK_EQ(fragment.IsFirstForNode(), index == 0);
if (const FragmentItems* fragment_items = fragment.Items()) {
fragment_items->CheckAllItemsAreValid();
}
// Don't check past the fragment just added. Those entries may be invalid at
// this point.
if (index == new_fragment_index)
break;
++index;
}
}
#else
inline void CheckDidAddFragment(const LayoutBox& box,
const PhysicalBoxFragment& fragment,
wtf_size_t new_fragment_index = kNotFound) {}
#endif
// Applies the overflow clip to |result|. For any axis that is clipped, |result|
// is reset to |no_overflow_rect|. If neither axis is clipped, nothing is
// changed.
void ApplyOverflowClip(OverflowClipAxes overflow_clip_axes,
const PhysicalRect& no_overflow_rect,
PhysicalRect& result) {
if (overflow_clip_axes & kOverflowClipX) {
result.SetX(no_overflow_rect.X());
result.SetWidth(no_overflow_rect.Width());
}
if (overflow_clip_axes & kOverflowClipY) {
result.SetY(no_overflow_rect.Y());
result.SetHeight(no_overflow_rect.Height());
}
}
int HypotheticalScrollbarThickness(const LayoutBox& box,
ScrollbarOrientation scrollbar_orientation,
bool should_include_overlay_thickness) {
box.CheckIsNotDestroyed();
if (PaintLayerScrollableArea* scrollable_area = box.GetScrollableArea()) {
return scrollable_area->HypotheticalScrollbarThickness(
scrollbar_orientation, should_include_overlay_thickness);
} else {
Page* page = box.GetFrame()->GetPage();
ScrollbarTheme& theme = page->GetScrollbarTheme();
if (theme.UsesOverlayScrollbars() && !should_include_overlay_thickness) {
return 0;
} else {
ChromeClient& chrome_client = page->GetChromeClient();
Document& document = box.GetDocument();
float scale_from_dip =
chrome_client.WindowToViewportScalar(document.GetFrame(), 1.0f);
return theme.ScrollbarThickness(scale_from_dip,
box.StyleRef().ScrollbarWidth());
}
}
}
void RecalcFragmentScrollableOverflow(RecalcScrollableOverflowResult& result,
const PhysicalFragment& fragment) {
for (const auto& child : fragment.PostLayoutChildren()) {
if (child->GetLayoutObject()) {
if (const auto* box = DynamicTo<PhysicalBoxFragment>(child.get())) {
if (LayoutBox* owner_box = box->MutableOwnerLayoutBox())
result.Unite(owner_box->RecalcScrollableOverflow());
}
} else {
// We enter this branch when the |child| is a fragmentainer.
RecalcFragmentScrollableOverflow(result, *child.get());
}
}
}
// Returns the logical offset in the LocationContainer() coordination system,
// and its WritingMode.
std::tuple<LogicalOffset, WritingMode> LogicalLocation(const LayoutBox& box) {
LayoutBox* container = box.LocationContainer();
WritingMode writing_mode = container->StyleRef().GetWritingMode();
WritingModeConverter converter({writing_mode, TextDirection::kLtr},
PhysicalSize(container->Size()));
return {converter.ToLogical(box.PhysicalLocation(), PhysicalSize(box.Size())),
writing_mode};
}
} // namespace
LayoutBoxRareData::LayoutBoxRareData()
: spanner_placeholder_(nullptr),
// TODO(rego): We should store these based on physical direction.
has_override_containing_block_content_logical_width_(false),
has_previous_content_box_rect_(false) {}
void LayoutBoxRareData::Trace(Visitor* visitor) const {
visitor->Trace(spanner_placeholder_);
visitor->Trace(layout_child_);
}
LayoutBox::LayoutBox(ContainerNode* node) : LayoutBoxModelObject(node) {
if (blink::IsA<HTMLLegendElement>(node))
SetIsHTMLLegendElement();
}
void LayoutBox::Trace(Visitor* visitor) const {
visitor->Trace(min_max_sizes_cache_);
visitor->Trace(measure_cache_);
visitor->Trace(layout_results_);
visitor->Trace(overflow_);
visitor->Trace(rare_data_);
LayoutBoxModelObject::Trace(visitor);
}
LayoutBox::~LayoutBox() = default;
PaintLayerType LayoutBox::LayerTypeRequired() const {
NOT_DESTROYED();
if (IsStacked() || HasHiddenBackface() ||
(StyleRef().SpecifiesColumns() && !IsLayoutNGObject()))
return kNormalPaintLayer;
if (HasNonVisibleOverflow() && !IsLayoutReplaced()) {
return kOverflowClipPaintLayer;
}
return kNoPaintLayer;
}
void LayoutBox::WillBeDestroyed() {
NOT_DESTROYED();
ClearOverrideContainingBlockContentSize();
ShapeOutsideInfo::RemoveInfo(*this);
if (!DocumentBeingDestroyed()) {
DisassociatePhysicalFragments();
}
LayoutBoxModelObject::WillBeDestroyed();
}
void LayoutBox::DisassociatePhysicalFragments() {
if (FirstInlineFragmentItemIndex()) {
FragmentItems::LayoutObjectWillBeDestroyed(*this);
ClearFirstInlineFragmentItemIndex();
}
if (measure_cache_) {
measure_cache_->LayoutObjectWillBeDestroyed();
}
for (auto result : layout_results_)
result->GetPhysicalFragment().LayoutObjectWillBeDestroyed();
}
void LayoutBox::InsertedIntoTree() {
NOT_DESTROYED();
LayoutBoxModelObject::InsertedIntoTree();
AddCustomLayoutChildIfNeeded();
}
void LayoutBox::WillBeRemovedFromTree() {
NOT_DESTROYED();
ClearCustomLayoutChild();
LayoutBoxModelObject::WillBeRemovedFromTree();
}
void LayoutBox::StyleWillChange(StyleDifference diff,
const ComputedStyle& new_style) {
NOT_DESTROYED();
const ComputedStyle* old_style = Style();
if (old_style) {
if (IsDocumentElement() || IsBody()) {
// The background of the root element or the body element could propagate
// up to the canvas. Just dirty the entire canvas when our style changes
// substantially.
if (diff.NeedsNormalPaintInvalidation() || diff.NeedsLayout()) {
View()->SetShouldDoFullPaintInvalidation();
}
}
// When a layout hint happens and an object's position style changes, we
// have to do a layout to dirty the layout tree using the old position
// value now.
if (diff.NeedsFullLayout() && Parent()) {
bool will_move_out_of_ifc = false;
if (old_style->GetPosition() != new_style.GetPosition()) {
if (!old_style->HasOutOfFlowPosition() &&
new_style.HasOutOfFlowPosition()) {
// We're about to go out of flow. Before that takes place, we need to
// mark the current containing block chain for preferred widths
// recalculation.
SetNeedsLayoutAndIntrinsicWidthsRecalc(
layout_invalidation_reason::kStyleChange);
// Grid placement is different for out-of-flow elements, so if the
// containing block is a grid, dirty the grid's placement. The
// converse (going from out of flow to in flow) is handled in
// LayoutBox::UpdateGridPositionAfterStyleChange.
LayoutBlock* containing_block = ContainingBlock();
if (containing_block && containing_block->IsLayoutGrid()) {
containing_block->SetGridPlacementDirty(true);
}
// Out of flow are not part of |FragmentItems|, and that further
// changes including destruction cannot be tracked. We need to mark it
// is moved out from this IFC.
will_move_out_of_ifc = true;
} else {
MarkContainerChainForLayout();
}
if (old_style->GetPosition() == EPosition::kStatic)
SetShouldDoFullPaintInvalidation();
else if (new_style.HasOutOfFlowPosition())
Parent()->SetChildNeedsLayout();
}
bool will_become_inflow = false;
if ((old_style->IsFloating() || old_style->HasOutOfFlowPosition()) &&
!new_style.IsFloating() && !new_style.HasOutOfFlowPosition()) {
// As a float or OOF, this object may have been part of an inline
// formatting context, but that's definitely no longer the case.
will_become_inflow = true;
will_move_out_of_ifc = true;
}
if (will_move_out_of_ifc && FirstInlineFragmentItemIndex()) {
FragmentItems::LayoutObjectWillBeMoved(*this);
ClearFirstInlineFragmentItemIndex();
}
if (will_become_inflow)
SetIsInLayoutNGInlineFormattingContext(false);
}
// FIXME: This branch runs when !oldStyle, which means that layout was never
// called so what's the point in invalidating the whole view that we never
// painted?
} else if (IsBody()) {
View()->SetShouldDoFullPaintInvalidation();
}
LayoutBoxModelObject::StyleWillChange(diff, new_style);
}
void LayoutBox::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
NOT_DESTROYED();
LayoutBoxModelObject::StyleDidChange(diff, old_style);
// Reflection works through PaintLayer. Some child classes e.g. LayoutSVGBlock
// don't create layers and ignore reflections.
if (HasReflection() && !HasLayer())
SetHasReflection(false);
if (auto* parent_flow_block = DynamicTo<LayoutBlockFlow>(Parent())) {
if (IsFloatingOrOutOfFlowPositioned() && old_style &&
!old_style->IsFloating() && !old_style->HasOutOfFlowPosition()) {
// Note that |parent_flow_block| may have been destroyed after this call.
parent_flow_block->ChildBecameFloatingOrOutOfFlow(this);
}
}
SetOverflowClipAxes(ComputeOverflowClipAxes());
// If our zoom factor changes and we have a defined scrollLeft/Top, we need to
// adjust that value into the new zoomed coordinate space. Note that the new
// scroll offset may be outside the normal min/max range of the scrollable
// area, which is weird but OK, because the scrollable area will update its
// min/max in updateAfterLayout().
const ComputedStyle& new_style = StyleRef();
if (IsScrollContainer() && old_style &&
old_style->EffectiveZoom() != new_style.EffectiveZoom()) {
PaintLayerScrollableArea* scrollable_area = GetScrollableArea();
DCHECK(scrollable_area);
// We use GetScrollOffset() rather than ScrollPosition(), because scroll
// offset is the distance from the beginning of flow for the box, which is
// the dimension we want to preserve.
ScrollOffset offset = scrollable_area->GetScrollOffset();
if (!offset.IsZero()) {
offset.Scale(new_style.EffectiveZoom() / old_style->EffectiveZoom());
scrollable_area->SetScrollOffsetUnconditionally(offset);
}
}
if (old_style && old_style->IsScrollContainer() != IsScrollContainer()) {
if (auto* layer = EnclosingLayer())
layer->ScrollContainerStatusChanged();
}
UpdateShapeOutsideInfoAfterStyleChange(*Style(), old_style);
UpdateGridPositionAfterStyleChange(old_style);
if (old_style) {
// Regular column content (i.e. non-spanners) have a hook into the flow
// thread machinery before (StyleWillChange()) and after (here in
// StyleDidChange()) the style has changed. Column spanners, on the other
// hand, only have a hook here. The LayoutMultiColumnSpannerPlaceholder code
// will do all the necessary things, including removing it as a spanner, if
// it should no longer be one. Therefore, make sure that we skip
// FlowThreadDescendantStyleDidChange() in such cases, as that might trigger
// a duplicate flow thread insertion notification, if the spanner no longer
// is a spanner.
if (LayoutMultiColumnSpannerPlaceholder* placeholder =
SpannerPlaceholder()) {
placeholder->LayoutObjectInFlowThreadStyleDidChange(old_style);
}
UpdateScrollSnapMappingAfterStyleChange(*old_style);
if (ShouldClipOverflowAlongEitherAxis()) {
// The overflow clip paint property depends on border sizes through
// overflowClipRect(), and border radii, so we update properties on
// border size or radii change.
//
// For some controls, it depends on paddings.
if (!old_style->BorderSizeEquals(new_style) ||
!old_style->BorderRadiusEqual(new_style) ||
(HasControlClip() && !old_style->PaddingEqual(new_style))) {
SetNeedsPaintPropertyUpdate();
}
}
if (old_style->OverscrollBehaviorX() != new_style.OverscrollBehaviorX() ||
old_style->OverscrollBehaviorY() != new_style.OverscrollBehaviorY()) {
SetNeedsPaintPropertyUpdate();
}
if (old_style->OverflowX() != new_style.OverflowX() ||
old_style->OverflowY() != new_style.OverflowY()) {
SetNeedsPaintPropertyUpdate();
}
if (old_style->OverflowClipMargin() != new_style.OverflowClipMargin())
SetNeedsPaintPropertyUpdate();
if (IsInLayoutNGInlineFormattingContext() && IsAtomicInlineLevel() &&
old_style->Direction() != new_style.Direction()) {
SetNeedsCollectInlines();
}
if (IsBackgroundAttachmentFixedObject() &&
new_style.BackgroundLayers().Clip() !=
old_style->BackgroundLayers().Clip()) {
SetNeedsPaintPropertyUpdate();
}
}
// Update the script style map, from the new computed style.
if (IsCustomItem())
GetCustomLayoutChild()->styleMap()->UpdateStyle(GetDocument(), StyleRef());
// Non-atomic inlines should be LayoutInline or LayoutText, not LayoutBox.
DCHECK(!IsInline() || IsAtomicInlineLevel());
}
void LayoutBox::UpdateShapeOutsideInfoAfterStyleChange(
const ComputedStyle& style,
const ComputedStyle* old_style) {
NOT_DESTROYED();
const ShapeValue* shape_outside = style.ShapeOutside();
const ShapeValue* old_shape_outside =
old_style ? old_style->ShapeOutside()
: ComputedStyleInitialValues::InitialShapeOutside();
const Length& shape_margin = style.ShapeMargin();
Length old_shape_margin =
old_style ? old_style->ShapeMargin()
: ComputedStyleInitialValues::InitialShapeMargin();
float shape_image_threshold = style.ShapeImageThreshold();
float old_shape_image_threshold =
old_style ? old_style->ShapeImageThreshold()
: ComputedStyleInitialValues::InitialShapeImageThreshold();
// FIXME: A future optimization would do a deep comparison for equality. (bug
// 100811)
if (shape_outside == old_shape_outside && shape_margin == old_shape_margin &&
shape_image_threshold == old_shape_image_threshold)
return;
if (!shape_outside)
ShapeOutsideInfo::RemoveInfo(*this);
else
ShapeOutsideInfo::EnsureInfo(*this).MarkShapeAsDirty();
if (!IsFloating()) {
return;
}
if (shape_outside || shape_outside != old_shape_outside) {
if (auto* containing_block = ContainingBlock()) {
containing_block->SetChildNeedsLayout();
}
}
}
namespace {
bool GridStyleChanged(const ComputedStyle* old_style,
const ComputedStyle& current_style) {
return old_style->GridColumnStart() != current_style.GridColumnStart() ||
old_style->GridColumnEnd() != current_style.GridColumnEnd() ||
old_style->GridRowStart() != current_style.GridRowStart() ||
old_style->GridRowEnd() != current_style.GridRowEnd() ||
old_style->Order() != current_style.Order() ||
old_style->HasOutOfFlowPosition() !=
current_style.HasOutOfFlowPosition();
}
bool AlignmentChanged(const ComputedStyle* old_style,
const ComputedStyle& current_style) {
return old_style->AlignSelf() != current_style.AlignSelf() ||
old_style->JustifySelf() != current_style.JustifySelf();
}
} // namespace
void LayoutBox::UpdateGridPositionAfterStyleChange(
const ComputedStyle* old_style) {
NOT_DESTROYED();
if (!old_style)
return;
LayoutObject* parent = Parent();
const bool was_out_of_flow = old_style->HasOutOfFlowPosition();
const bool is_out_of_flow = StyleRef().HasOutOfFlowPosition();
LayoutBlock* containing_block = ContainingBlock();
if ((containing_block && containing_block->IsLayoutGrid()) &&
GridStyleChanged(old_style, StyleRef())) {
// Out-of-flow items do not impact grid placement.
// TODO(kschmi): Scope this so that it only dirties the grid when track
// sizing depends on grid item sizes.
if (!was_out_of_flow || !is_out_of_flow)
containing_block->SetGridPlacementDirty(true);
// For out-of-flow elements with grid container as containing block, we need
// to run the entire algorithm to place and size them correctly. As a
// result, we trigger a full layout for GridNG.
if (is_out_of_flow) {
containing_block->SetNeedsLayout(layout_invalidation_reason::kGridChanged,
kMarkContainerChain);
}
}
// GridNG computes static positions for out-of-flow elements at layout time,
// with alignment offsets baked in. So if alignment changes, we need to
// schedule a layout.
if (is_out_of_flow && AlignmentChanged(old_style, StyleRef())) {
LayoutObject* grid_ng_ancestor = nullptr;
if (containing_block && containing_block->IsLayoutGrid()) {
grid_ng_ancestor = containing_block;
} else if (parent && parent->IsLayoutGrid()) {
grid_ng_ancestor = parent;
}
if (grid_ng_ancestor) {
grid_ng_ancestor->SetNeedsLayout(layout_invalidation_reason::kGridChanged,
kMarkContainerChain);
}
}
}
void LayoutBox::UpdateScrollSnapMappingAfterStyleChange(
const ComputedStyle& old_style) {
NOT_DESTROYED();
DCHECK(Style());
// scroll-snap-type and scroll-padding invalidate the snap container.
if (old_style.GetScrollSnapType() != StyleRef().GetScrollSnapType() ||
old_style.ScrollPaddingBottom() != StyleRef().ScrollPaddingBottom() ||
old_style.ScrollPaddingLeft() != StyleRef().ScrollPaddingLeft() ||
old_style.ScrollPaddingTop() != StyleRef().ScrollPaddingTop() ||
old_style.ScrollPaddingRight() != StyleRef().ScrollPaddingRight()) {
if (!NeedsLayout() && IsScrollContainer()) {
GetScrollableArea()->EnqueueForSnapUpdateIfNeeded();
}
}
// scroll-snap-align invalidates layout as we need to propagate the
// snap-areas up the fragment-tree.
if (old_style.GetScrollSnapAlign() != StyleRef().GetScrollSnapAlign()) {
if (auto* containing_block = ContainingBlock()) {
containing_block->SetNeedsLayout(layout_invalidation_reason::kStyleChange,
kMarkContainerChain);
}
}
auto SnapAreaDidChange = [&]() {
auto* snap_container = ContainingScrollContainer();
if (snap_container && !snap_container->NeedsLayout()) {
snap_container->GetScrollableArea()->EnqueueForSnapUpdateIfNeeded();
}
};
// scroll-snap-stop and scroll-margin invalidate the snap area.
if (old_style.ScrollSnapStop() != StyleRef().ScrollSnapStop() ||
old_style.ScrollMarginBottom() != StyleRef().ScrollMarginBottom() ||
old_style.ScrollMarginLeft() != StyleRef().ScrollMarginLeft() ||
old_style.ScrollMarginTop() != StyleRef().ScrollMarginTop() ||
old_style.ScrollMarginRight() != StyleRef().ScrollMarginRight()) {
SnapAreaDidChange();
}
// Transform invalidates the snap area.
if (old_style.Transform() != StyleRef().Transform())
SnapAreaDidChange();
}
void LayoutBox::UpdateFromStyle() {
NOT_DESTROYED();
LayoutBoxModelObject::UpdateFromStyle();
const ComputedStyle& style_to_use = StyleRef();
SetFloating(style_to_use.IsFloating() && !IsOutOfFlowPositioned() &&
!style_to_use.IsInsideDisplayIgnoringFloatingChildren());
SetHasTransformRelatedProperty(
IsSVGChild() ? style_to_use.HasTransformRelatedPropertyForSVG()
: style_to_use.HasTransformRelatedProperty());
SetHasReflection(style_to_use.BoxReflect());
bool should_clip_overflow = (!StyleRef().IsOverflowVisibleAlongBothAxes() ||
ShouldApplyPaintContainment()) &&
RespectsCSSOverflow();
if (should_clip_overflow != HasNonVisibleOverflow()) {
// The overflow clip paint property depends on whether overflow clip is
// present so we need to update paint properties if this changes.
SetNeedsPaintPropertyUpdate();
if (Layer())
Layer()->SetNeedsCompositingInputsUpdate();
}
SetHasNonVisibleOverflow(should_clip_overflow);
}
void LayoutBox::LayoutSubtreeRoot() {
NOT_DESTROYED();
// Our own style may have changed which would disqualify us as a layout root
// (e.g. our containment/writing-mode/formatting-context status/etc changed).
// Skip subtree layout, and ensure our container chain needs layout.
if (SelfNeedsFullLayout()) {
MarkContainerChainForLayout();
return;
}
const auto* previous_result = GetSingleCachedLayoutResult();
DCHECK(previous_result);
auto space = previous_result->GetConstraintSpaceForCaching();
DCHECK_EQ(space.GetWritingMode(), StyleRef().GetWritingMode());
const LayoutResult* result = BlockNode(this).Layout(space);
GetDocument().GetFrame()->GetInputMethodController().DidLayoutSubtree(*this);
if (IsOutOfFlowPositioned()) {
result->CopyMutableOutOfFlowData(*previous_result);
}
// Even if we are a subtree layout root we need to mark our containing-block
// for layout if:
// - Our baselines have shifted.
// - We've propagated any layout-objects (which affect our container chain).
//
// NOTE: We could weaken the constraints in ObjectIsRelayoutBoundary, and use
// this technique to detect size-changes, etc if we wanted to expand this
// optimization.
const auto& previous_fragment =
To<PhysicalBoxFragment>(previous_result->GetPhysicalFragment());
const auto& fragment = To<PhysicalBoxFragment>(result->GetPhysicalFragment());
if (previous_fragment.FirstBaseline() != fragment.FirstBaseline() ||
previous_fragment.LastBaseline() != fragment.LastBaseline() ||
fragment.HasPropagatedLayoutObjects()) {
if (auto* containing_block = ContainingBlock()) {
containing_block->SetNeedsLayout(
layout_invalidation_reason::kChildChanged, kMarkContainerChain);
}
}
}
// ClientWidth and ClientHeight represent the interior of an object excluding
// border and scrollbar.
DISABLE_CFI_PERF
LayoutUnit LayoutBox::ClientWidth() const {
NOT_DESTROYED();
// We need to clamp negative values. This function may be called during layout
// before frame_size_ gets the final proper value. Another reason: While
// border side values are currently limited to 2^20px (a recent change in the
// code), if this limit is raised again in the future, we'd have ill effects
// of saturated arithmetic otherwise.
LayoutUnit width = Size().width;
if (CanSkipComputeScrollbars()) {
return (width - BorderLeft() - BorderRight()).ClampNegativeToZero();
} else {
return (width - BorderLeft() - BorderRight() -
ComputeScrollbarsInternal(kClampToContentBox).HorizontalSum())
.ClampNegativeToZero();
}
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::ClientHeight() const {
NOT_DESTROYED();
// We need to clamp negative values. This function can be called during layout
// before frame_size_ gets the final proper value. The scrollbar may be wider
// than the padding box. Another reason: While border side values are
// currently limited to 2^20px (a recent change in the code), if this limit is
// raised again in the future, we'd have ill effects of saturated arithmetic
// otherwise.
LayoutUnit height = Size().height;
if (CanSkipComputeScrollbars()) {
return (height - BorderTop() - BorderBottom()).ClampNegativeToZero();
} else {
return (height - BorderTop() - BorderBottom() -
ComputeScrollbarsInternal(kClampToContentBox).VerticalSum())
.ClampNegativeToZero();
}
}
LayoutUnit LayoutBox::ClientWidthFrom(LayoutUnit width) const {
NOT_DESTROYED();
if (CanSkipComputeScrollbars()) {
return (width - BorderLeft() - BorderRight()).ClampNegativeToZero();
} else {
return (width - BorderLeft() - BorderRight() -
ComputeScrollbarsInternal(kClampToContentBox).HorizontalSum())
.ClampNegativeToZero();
}
}
LayoutUnit LayoutBox::ClientHeightFrom(LayoutUnit height) const {
NOT_DESTROYED();
if (CanSkipComputeScrollbars()) {
return (height - BorderTop() - BorderBottom()).ClampNegativeToZero();
} else {
return (height - BorderTop() - BorderBottom() -
ComputeScrollbarsInternal(kClampToContentBox).VerticalSum())
.ClampNegativeToZero();
}
}
LayoutUnit LayoutBox::ClientWidthWithTableSpecialBehavior() const {
NOT_DESTROYED();
// clientWidth/Height is the visual portion of the box content, not including
// borders or scroll bars, but includes padding. And per
// https://www.w3.org/TR/CSS2/tables.html#model,
// table wrapper box is a principal block box that contains the table box
// itself and any caption boxes, and table grid box is a block-level box that
// contains the table's internal table boxes. When table's border is specified
// in CSS, the border is added to table grid box, not table wrapper box.
// Currently, Blink doesn't have table wrapper box, and we are supposed to
// retrieve clientWidth/Height from table wrapper box, not table grid box. So
// when we retrieve clientWidth/Height, it includes table's border size.
if (IsTable())
return ClientWidth() + BorderLeft() + BorderRight();
return ClientWidth();
}
LayoutUnit LayoutBox::ClientHeightWithTableSpecialBehavior() const {
NOT_DESTROYED();
// clientWidth/Height is the visual portion of the box content, not including
// borders or scroll bars, but includes padding. And per
// https://www.w3.org/TR/CSS2/tables.html#model,
// table wrapper box is a principal block box that contains the table box
// itself and any caption boxes, and table grid box is a block-level box that
// contains the table's internal table boxes. When table's border is specified
// in CSS, the border is added to table grid box, not table wrapper box.
// Currently, Blink doesn't have table wrapper box, and we are supposed to
// retrieve clientWidth/Height from table wrapper box, not table grid box. So
// when we retrieve clientWidth/Height, it includes table's border size.
if (IsTable())
return ClientHeight() + BorderTop() + BorderBottom();
return ClientHeight();
}
bool LayoutBox::UsesOverlayScrollbars() const {
NOT_DESTROYED();
if (StyleRef().HasCustomScrollbarStyle(GetDocument())) {
return false;
}
if (GetFrame()->GetPage()->GetScrollbarTheme().UsesOverlayScrollbars())
return true;
return false;
}
LayoutUnit LayoutBox::ScrollWidth() const {
NOT_DESTROYED();
if (IsScrollContainer())
return GetScrollableArea()->ScrollWidth();
if (StyleRef().IsScrollbarGutterStable() &&
StyleRef().OverflowBlockDirection() == EOverflow::kHidden) {
if (auto* scrollable_area = GetScrollableArea())
return scrollable_area->ScrollWidth();
else
return ScrollableOverflowRect().Width();
}
// For objects with scrollable overflow, this matches IE.
PhysicalRect overflow_rect = ScrollableOverflowRect();
if (!StyleRef().GetWritingDirection().IsFlippedX()) {
return std::max(ClientWidth(), overflow_rect.Right() - BorderLeft());
}
return ClientWidth() -
std::min(LayoutUnit(), overflow_rect.X() - BorderLeft());
}
LayoutUnit LayoutBox::ScrollHeight() const {
NOT_DESTROYED();
if (IsScrollContainer())
return GetScrollableArea()->ScrollHeight();
if (StyleRef().IsScrollbarGutterStable() &&
StyleRef().OverflowBlockDirection() == EOverflow::kHidden) {
if (auto* scrollable_area = GetScrollableArea())
return scrollable_area->ScrollHeight();
else
return ScrollableOverflowRect().Height();
}
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
return std::max(ClientHeight(),
ScrollableOverflowRect().Bottom() - BorderTop());
}
PhysicalBoxStrut LayoutBox::MarginBoxOutsets() const {
NOT_DESTROYED();
if (PhysicalFragmentCount()) {
// We get margin data from the first physical fragment. Margins are
// per-LayoutBox data, and we don't need to take care of block
// fragmentation.
return GetPhysicalFragment(0)->Margins();
}
return PhysicalBoxStrut();
}
void LayoutBox::AbsoluteQuads(Vector<gfx::QuadF>& quads,
MapCoordinatesFlags mode) const {
NOT_DESTROYED();
if (LayoutFlowThread* flow_thread = FlowThreadContainingBlock()) {
flow_thread->AbsoluteQuadsForDescendant(*this, quads, mode);
return;
}
quads.push_back(LocalRectToAbsoluteQuad(PhysicalBorderBoxRect(), mode));
}
gfx::RectF LayoutBox::LocalBoundingBoxRectForAccessibility() const {
NOT_DESTROYED();
PhysicalSize size = Size();
return gfx::RectF(0, 0, size.width.ToFloat(), size.height.ToFloat());
}
void LayoutBox::UpdateAfterLayout() {
NOT_DESTROYED();
// Transform-origin depends on box size, so we need to update the layer
// transform after layout.
if (HasLayer()) {
Layer()->UpdateTransform();
Layer()->UpdateScrollingAfterLayout();
}
GetFrame()->GetInputMethodController().DidUpdateLayout(*this);
if (IsPositioned())
GetFrame()->GetInputMethodController().DidLayoutSubtree(*this);
}
bool LayoutBox::ShouldUseAutoIntrinsicSize() const {
DisplayLockContext* context = GetDisplayLockContext();
return context && context->IsLocked();
}
bool LayoutBox::HasOverrideIntrinsicContentWidth() const {
NOT_DESTROYED();
// We only override a size contained dimension.
if (!ShouldApplyWidthContainment())
return false;
const StyleIntrinsicLength& intrinsic_length =
StyleRef().ContainIntrinsicWidth();
if (intrinsic_length.IsNoOp()) {
return false;
}
// If we have a length specified, we have an override in any case.
if (intrinsic_length.GetLength()) {
return true;
}
// Now we must be in the "auto none" case, so we only have an override if we
// have a last remembered size in the appropriate dimension and we should use
// auto size.
DCHECK(intrinsic_length.HasAuto());
if (!ShouldUseAutoIntrinsicSize()) {
return false;
}
const Element* element = DynamicTo<Element>(GetNode());
if (!element) {
return false;
}
return StyleRef().IsHorizontalWritingMode()
? element->LastRememberedInlineSize().has_value()
: element->LastRememberedBlockSize().has_value();
}
bool LayoutBox::HasOverrideIntrinsicContentHeight() const {
NOT_DESTROYED();
// We only override a size contained dimension.
if (!ShouldApplyHeightContainment())
return false;
const StyleIntrinsicLength& intrinsic_length =
StyleRef().ContainIntrinsicHeight();
if (intrinsic_length.IsNoOp()) {
return false;
}
// If we have a length specified, we have an override in any case.
if (intrinsic_length.GetLength()) {
return true;
}
// Now we must be in the "auto none" case, so we only have an override if we
// have a last remembered size in the appropriate dimension and we should use
// auto size.
DCHECK(intrinsic_length.HasAuto());
if (!ShouldUseAutoIntrinsicSize()) {
return false;
}
const Element* element = DynamicTo<Element>(GetNode());
if (!element) {
return false;
}
return StyleRef().IsHorizontalWritingMode()
? element->LastRememberedBlockSize().has_value()
: element->LastRememberedInlineSize().has_value();
}
LayoutUnit LayoutBox::OverrideIntrinsicContentWidth() const {
NOT_DESTROYED();
DCHECK(HasOverrideIntrinsicContentWidth());
const auto& style = StyleRef();
const StyleIntrinsicLength& intrinsic_length = style.ContainIntrinsicWidth();
DCHECK(!intrinsic_length.IsNoOp());
if (intrinsic_length.HasAuto() && ShouldUseAutoIntrinsicSize()) {
if (const Element* elem = DynamicTo<Element>(GetNode())) {
const std::optional<LayoutUnit> width =
StyleRef().IsHorizontalWritingMode()
? elem->LastRememberedInlineSize()
: elem->LastRememberedBlockSize();
if (width) {
// ResizeObserverSize is adjusted to be in CSS space, we need to adjust
// it back to Layout space by applying the effective zoom.
return LayoutUnit::FromFloatRound(*width * style.EffectiveZoom());
}
}
}
// We must have a length because HasOverrideIntrinsicContentWidth() is true.
DCHECK(intrinsic_length.GetLength().has_value());
DCHECK(intrinsic_length.GetLength()->IsFixed());
return LayoutUnit(intrinsic_length.GetLength()->Value());
}
LayoutUnit LayoutBox::OverrideIntrinsicContentHeight() const {
NOT_DESTROYED();
DCHECK(HasOverrideIntrinsicContentHeight());
const auto& style = StyleRef();
const StyleIntrinsicLength& intrinsic_length = style.ContainIntrinsicHeight();
DCHECK(!intrinsic_length.IsNoOp());
if (intrinsic_length.HasAuto() && ShouldUseAutoIntrinsicSize()) {
if (const Element* elem = DynamicTo<Element>(GetNode())) {
const std::optional<LayoutUnit> height =
StyleRef().IsHorizontalWritingMode()
? elem->LastRememberedBlockSize()
: elem->LastRememberedInlineSize();
if (height) {
// ResizeObserverSize is adjusted to be in CSS space, we need to adjust
// it back to Layout space by applying the effective zoom.
return LayoutUnit::FromFloatRound(*height * style.EffectiveZoom());
}
}
}
// We must have a length because HasOverrideIntrinsicContentHeight() is true.
DCHECK(intrinsic_length.GetLength().has_value());
DCHECK(intrinsic_length.GetLength()->IsFixed());
return LayoutUnit(intrinsic_length.GetLength()->Value());
}
LayoutUnit LayoutBox::DefaultIntrinsicContentInlineSize() const {
NOT_DESTROYED();
// If the intrinsic-inline-size is specified, then we shouldn't ever need to
// get here.
DCHECK(!HasOverrideIntrinsicContentLogicalWidth());
if (!IsA<Element>(GetNode()))
return kIndefiniteSize;
const Element& element = *To<Element>(GetNode());
const bool apply_fixed_size = StyleRef().ApplyControlFixedSize(&element);
const auto* select = DynamicTo<HTMLSelectElement>(element);
if (UNLIKELY(select && select->UsesMenuList() &&
!select->IsAppearanceBaseSelect())) {
return apply_fixed_size ? MenuListIntrinsicInlineSize(*select, *this)
: kIndefiniteSize;
}
const auto* input = DynamicTo<HTMLInputElement>(element);
if (UNLIKELY(input)) {
if (input->IsTextField() && apply_fixed_size) {
return TextFieldIntrinsicInlineSize(*input, *this);
}
FormControlType type = input->FormControlType();
if (type == FormControlType::kInputFile && apply_fixed_size) {
return FileUploadControlIntrinsicInlineSize(*input, *this);
}
if (type == FormControlType::kInputRange) {
return SliderIntrinsicInlineSize(*this);
}
auto effective_appearance = StyleRef().EffectiveAppearance();
if (effective_appearance == kCheckboxPart) {
return ThemePartIntrinsicSize(*this, WebThemeEngine::kPartCheckbox)
.inline_size;
}
if (effective_appearance == kRadioPart) {
return ThemePartIntrinsicSize(*this, WebThemeEngine::kPartRadio)
.inline_size;
}
return kIndefiniteSize;
}
const auto* textarea = DynamicTo<HTMLTextAreaElement>(element);
if (UNLIKELY(textarea) && apply_fixed_size) {
return TextAreaIntrinsicInlineSize(*textarea, *this);
}
if (IsSliderContainer(element))
return SliderIntrinsicInlineSize(*this);
return kIndefiniteSize;
}
LayoutUnit LayoutBox::DefaultIntrinsicContentBlockSize() const {
NOT_DESTROYED();
// If the intrinsic-block-size is specified, then we shouldn't ever need to
// get here.
DCHECK(!HasOverrideIntrinsicContentLogicalHeight());
auto effective_appearance = StyleRef().EffectiveAppearance();
if (effective_appearance == kCheckboxPart) {
return ThemePartIntrinsicSize(*this, WebThemeEngine::kPartCheckbox)
.block_size;
}
if (effective_appearance == kRadioPart) {
return ThemePartIntrinsicSize(*this, WebThemeEngine::kPartRadio).block_size;
}
if (!StyleRef().ApplyControlFixedSize(GetNode())) {
return kIndefiniteSize;
}
if (const auto* select = DynamicTo<HTMLSelectElement>(GetNode())) {
if (!select->IsAppearanceBaseSelect()) {
if (select->UsesMenuList()) {
return MenuListIntrinsicBlockSize(*select, *this);
}
return ListBoxItemBlockSize(*select, *this) * select->ListBoxSize() -
ComputeLogicalScrollbars().BlockSum();
}
}
if (IsTextField()) {
return TextFieldIntrinsicBlockSize(*To<HTMLInputElement>(GetNode()), *this);
}
if (IsTextArea()) {
return TextAreaIntrinsicBlockSize(*To<HTMLTextAreaElement>(GetNode()),
*this);
}
return kIndefiniteSize;
}
LayoutUnit LayoutBox::LogicalLeft() const {
NOT_DESTROYED();
auto [offset, container_writing_mode] = LogicalLocation(*this);
return IsParallelWritingMode(container_writing_mode,
StyleRef().GetWritingMode())
? offset.inline_offset
: offset.block_offset;
}
LayoutUnit LayoutBox::LogicalTop() const {
NOT_DESTROYED();
auto [offset, container_writing_mode] = LogicalLocation(*this);
return IsParallelWritingMode(container_writing_mode,
StyleRef().GetWritingMode())
? offset.block_offset
: offset.inline_offset;
}
gfx::QuadF LayoutBox::AbsoluteContentQuad(MapCoordinatesFlags flags) const {
NOT_DESTROYED();
PhysicalRect rect = PhysicalContentBoxRect();
return LocalRectToAbsoluteQuad(rect, flags);
}
PhysicalRect LayoutBox::PhysicalBackgroundRect(
BackgroundRectType rect_type) const {
NOT_DESTROYED();
// If the background transfers to view, the used background of this object
// is transparent.
if (rect_type == kBackgroundKnownOpaqueRect && BackgroundTransfersToView())
return PhysicalRect();
std::optional<EFillBox> background_box;
Color background_color = ResolveColor(GetCSSPropertyBackgroundColor());
// Find the largest background rect of the given opaqueness.
for (const FillLayer* cur = &(StyleRef().BackgroundLayers()); cur;
cur = cur->Next()) {
EFillBox current_clip = cur->Clip();
if (rect_type == kBackgroundKnownOpaqueRect) {
if (current_clip == EFillBox::kText)
continue;
if (cur->GetBlendMode() != BlendMode::kNormal ||
cur->Composite() != kCompositeSourceOver)
continue;
bool layer_known_opaque = false;
// Check if the image is opaque and fills the clip.
if (const StyleImage* image = cur->GetImage()) {
if ((cur->Repeat().x == EFillRepeat::kRepeatFill ||
cur->Repeat().x == EFillRepeat::kRoundFill) &&
(cur->Repeat().y == EFillRepeat::kRepeatFill ||
cur->Repeat().y == EFillRepeat::kRoundFill) &&
image->KnownToBeOpaque(GetDocument(), StyleRef())) {
layer_known_opaque = true;
}
}
// The background color is painted into the last layer.
if (!cur->Next() && background_color.IsOpaque()) {
layer_known_opaque = true;
}
// If neither the image nor the color are opaque then skip this layer.
if (!layer_known_opaque)
continue;
} else {
// Ignore invisible background layers for kBackgroundPaintedExtent.
DCHECK_EQ(rect_type, kBackgroundPaintedExtent);
if (!cur->GetImage() &&
(cur->Next() || background_color.IsFullyTransparent())) {
continue;
}
// A content-box clipped fill layer can be scrolled into the padding box
// of the overflow container.
if (current_clip == EFillBox::kContent &&
cur->Attachment() == EFillAttachment::kLocal) {
current_clip = EFillBox::kPadding;
}
}
// Restrict clip if attachment is local.
if (current_clip == EFillBox::kBorder &&
cur->Attachment() == EFillAttachment::kLocal)
current_clip = EFillBox::kPadding;
background_box = background_box
? EnclosingFillBox(*background_box, current_clip)
: current_clip;
}
if (!background_box)
return PhysicalRect();
if (*background_box == EFillBox::kText) {
DCHECK_NE(rect_type, kBackgroundKnownOpaqueRect);
*background_box = EFillBox::kBorder;
}
if (rect_type == kBackgroundPaintedExtent &&
*background_box == EFillBox::kBorder &&
BackgroundClipBorderBoxIsEquivalentToPaddingBox()) {
*background_box = EFillBox::kPadding;
}
switch (*background_box) {
case EFillBox::kBorder:
return PhysicalBorderBoxRect();
case EFillBox::kPadding:
return PhysicalPaddingBoxRect();
case EFillBox::kContent:
return PhysicalContentBoxRect();
default:
NOTREACHED();
}
return PhysicalRect();
}
void LayoutBox::AddOutlineRects(OutlineRectCollector& collector,
OutlineInfo* info,
const PhysicalOffset& additional_offset,
OutlineType) const {
NOT_DESTROYED();
collector.AddRect(PhysicalRect(additional_offset, Size()));
if (info)
*info = OutlineInfo::GetFromStyle(StyleRef());
}
bool LayoutBox::CanResize() const {
NOT_DESTROYED();
// We need a special case for <iframe> because they never have
// hasOverflowClip(). However, they do "implicitly" clip their contents, so
// we want to allow resizing them also.
return (IsScrollContainer() || IsLayoutIFrame()) && StyleRef().HasResize();
}
bool LayoutBox::HasScrollbarGutters(ScrollbarOrientation orientation) const {
NOT_DESTROYED();
if (StyleRef().IsScrollbarGutterAuto())
return false;
DCHECK(StyleRef().IsScrollbarGutterStable());
// Scrollbar-gutter propagates to the viewport
// (see:|StyleResolver::PropagateStyleToViewport|).
if (orientation == kVerticalScrollbar) {
EOverflow overflow = StyleRef().OverflowY();
return StyleRef().IsHorizontalWritingMode() &&
(overflow == EOverflow::kAuto || overflow == EOverflow::kScroll ||
overflow == EOverflow::kHidden) &&
!UsesOverlayScrollbars() &&
GetNode() != GetDocument().ViewportDefiningElement();
} else {
EOverflow overflow = StyleRef().OverflowX();
return !StyleRef().IsHorizontalWritingMode() &&
(overflow == EOverflow::kAuto || overflow == EOverflow::kScroll ||
overflow == EOverflow::kHidden) &&
!UsesOverlayScrollbars() &&
GetNode() != GetDocument().ViewportDefiningElement();
}
}
PhysicalBoxStrut LayoutBox::ComputeScrollbarsInternal(
ShouldClampToContentBox clamp_to_content_box,
OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior,
ShouldIncludeScrollbarGutter include_scrollbar_gutter) const {
NOT_DESTROYED();
PhysicalBoxStrut scrollbars;
PaintLayerScrollableArea* scrollable_area = GetScrollableArea();
if (include_scrollbar_gutter == kIncludeScrollbarGutter &&
HasScrollbarGutters(kVerticalScrollbar)) {
LayoutUnit gutter_size = LayoutUnit(HypotheticalScrollbarThickness(
*this, kVerticalScrollbar, /* include_overlay_thickness */ true));
if (ShouldPlaceVerticalScrollbarOnLeft()) {
scrollbars.left = gutter_size;
if (StyleRef().IsScrollbarGutterBothEdges())
scrollbars.right = gutter_size;
} else {
scrollbars.right = gutter_size;
if (StyleRef().IsScrollbarGutterBothEdges())
scrollbars.left = gutter_size;
}
} else if (scrollable_area) {
if (ShouldPlaceVerticalScrollbarOnLeft()) {
scrollbars.left = LayoutUnit(scrollable_area->VerticalScrollbarWidth(
overlay_scrollbar_clip_behavior));
} else {
scrollbars.right = LayoutUnit(scrollable_area->VerticalScrollbarWidth(
overlay_scrollbar_clip_behavior));
}
}
if (include_scrollbar_gutter == kIncludeScrollbarGutter &&
HasScrollbarGutters(kHorizontalScrollbar)) {
LayoutUnit gutter_size = LayoutUnit(
HypotheticalScrollbarThickness(*this, kHorizontalScrollbar,
/* include_overlay_thickness */ true));
scrollbars.bottom = gutter_size;
if (StyleRef().IsScrollbarGutterBothEdges())
scrollbars.top = gutter_size;
} else if (scrollable_area) {
scrollbars.bottom = LayoutUnit(scrollable_area->HorizontalScrollbarHeight(
overlay_scrollbar_clip_behavior));
}
// Use the width of the vertical scrollbar, unless it's larger than the
// logical width of the content box, in which case we'll use that instead.
// Scrollbar handling is quite bad in such situations, and this code here
// is just to make sure that left-hand scrollbars don't mess up
// scrollWidth. For the full story, visit http://crbug.com/724255.
if (scrollbars.left > 0 && clamp_to_content_box == kClampToContentBox) {
LayoutUnit max_width = Size().width - BorderAndPaddingWidth();
scrollbars.left =
std::min(scrollbars.left, max_width.ClampNegativeToZero());
}
return scrollbars;
}
void LayoutBox::Autoscroll(const PhysicalOffset& position_in_root_frame) {
NOT_DESTROYED();
LocalFrame* frame = GetFrame();
if (!frame)
return;
LocalFrameView* frame_view = frame->View();
if (!frame_view)
return;
PhysicalOffset absolute_position =
frame_view->ConvertFromRootFrame(position_in_root_frame);
mojom::blink::ScrollIntoViewParamsPtr params =
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(),
mojom::blink::ScrollType::kUser);
scroll_into_view_util::ScrollRectToVisible(
*this,
PhysicalRect(absolute_position,
PhysicalSize(LayoutUnit(1), LayoutUnit(1))),
std::move(params));
}
// If specified point is outside the border-belt-excluded box (the border box
// inset by the autoscroll activation threshold), returned offset denotes
// direction of scrolling.
PhysicalOffset LayoutBox::CalculateAutoscrollDirection(
const gfx::PointF& point_in_root_frame) const {
NOT_DESTROYED();
if (!GetFrame())
return PhysicalOffset();
LocalFrameView* frame_view = GetFrame()->View();
if (!frame_view)
return PhysicalOffset();
PhysicalRect absolute_scrolling_box(AbsoluteBoundingBoxRect());
// Exclude scrollbars so the border belt (activation area) starts from the
// scrollbar-content edge rather than the window edge.
ExcludeScrollbars(absolute_scrolling_box,
kExcludeOverlayScrollbarSizeForHitTesting);
PhysicalRect belt_box =
View()->GetFrameView()->ConvertToRootFrame(absolute_scrolling_box);
belt_box.Inflate(LayoutUnit(-kAutoscrollBeltSize));
gfx::PointF point = point_in_root_frame;
if (point.x() < belt_box.X())
point.Offset(-kAutoscrollBeltSize, 0);
else if (point.x() > belt_box.Right())
point.Offset(kAutoscrollBeltSize, 0);
if (point.y() < belt_box.Y())
point.Offset(0, -kAutoscrollBeltSize);
else if (point.y() > belt_box.Bottom())
point.Offset(0, kAutoscrollBeltSize);
return PhysicalOffset::FromVector2dFRound(point - point_in_root_frame);
}
LayoutBox* LayoutBox::FindAutoscrollable(LayoutObject* layout_object,
bool is_middle_click_autoscroll) {
while (layout_object && !(layout_object->IsBox() &&
To<LayoutBox>(layout_object)->IsUserScrollable())) {
// Do not start selection-based autoscroll when the node is inside a
// fixed-position element.
if (!is_middle_click_autoscroll && layout_object->IsBox() &&
To<LayoutBox>(layout_object)->IsFixedToView()) {
return nullptr;
}
if (!layout_object->Parent() &&
layout_object->GetNode() == layout_object->GetDocument() &&
layout_object->GetDocument().LocalOwner()) {
layout_object =
layout_object->GetDocument().LocalOwner()->GetLayoutObject();
} else {
layout_object = layout_object->Parent();
}
}
return DynamicTo<LayoutBox>(layout_object);
}
bool LayoutBox::HasHorizontallyScrollableAncestor(LayoutObject* layout_object) {
while (layout_object) {
if (layout_object->IsBox() &&
To<LayoutBox>(layout_object)->HasScrollableOverflowX())
return true;
// Scroll is not propagating.
if (layout_object->StyleRef().OverscrollBehaviorX() !=
EOverscrollBehavior::kAuto)
break;
if (!layout_object->Parent() &&
layout_object->GetNode() == layout_object->GetDocument() &&
layout_object->GetDocument().LocalOwner()) {
layout_object =
layout_object->GetDocument().LocalOwner()->GetLayoutObject();
} else {
layout_object = layout_object->Parent();
}
}
return false;
}
gfx::Vector2d LayoutBox::OriginAdjustmentForScrollbars() const {
NOT_DESTROYED();
if (CanSkipComputeScrollbars())
return gfx::Vector2d();
PhysicalBoxStrut scrollbars = ComputeScrollbarsInternal(kClampToContentBox);
return gfx::Vector2d(scrollbars.left.ToInt(), scrollbars.top.ToInt());
}
gfx::Point LayoutBox::ScrollOrigin() const {
NOT_DESTROYED();
return GetScrollableArea() ? GetScrollableArea()->ScrollOrigin()
: gfx::Point();
}
PhysicalOffset LayoutBox::ScrolledContentOffset() const {
NOT_DESTROYED();
DCHECK(IsScrollContainer());
DCHECK(GetScrollableArea());
return PhysicalOffset::FromVector2dFFloor(
GetScrollableArea()->GetScrollOffset());
}
gfx::Vector2d LayoutBox::PixelSnappedScrolledContentOffset() const {
NOT_DESTROYED();
DCHECK(IsScrollContainer());
DCHECK(GetScrollableArea());
return GetScrollableArea()->ScrollOffsetInt();
}
PhysicalRect LayoutBox::ClippingRect(const PhysicalOffset& location) const {
NOT_DESTROYED();
PhysicalRect result(InfiniteIntRect());
if (ShouldClipOverflowAlongEitherAxis())
result = OverflowClipRect(location);
if (HasClip())
result.Intersect(ClipRect(location));
return result;
}
gfx::PointF LayoutBox::PerspectiveOrigin(const PhysicalSize* size) const {
if (!HasTransformRelatedProperty())
return gfx::PointF();
// Use the |size| parameter instead of |Size()| if present.
gfx::SizeF float_size = size ? gfx::SizeF(*size) : gfx::SizeF(Size());
return PointForLengthPoint(StyleRef().PerspectiveOrigin(), float_size);
}
bool LayoutBox::MapVisualRectToContainer(
const LayoutObject* container_object,
const PhysicalOffset& container_offset,
const LayoutObject* ancestor,
VisualRectFlags visual_rect_flags,
TransformState& transform_state) const {
NOT_DESTROYED();
bool container_preserve_3d = container_object->StyleRef().Preserves3D() &&
container_object == NearestAncestorForElement();
TransformState::TransformAccumulation accumulation =
container_preserve_3d ? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform;
// If there is no transform on this box, adjust for container offset and
// container scrolling, then apply container clip.
if (!ShouldUseTransformFromContainer(container_object)) {
transform_state.Move(container_offset, accumulation);
if (container_object->IsBox() && container_object != ancestor &&
!To<LayoutBox>(container_object)
->MapContentsRectToBoxSpace(transform_state, accumulation, *this,
visual_rect_flags)) {
return false;
}
return true;
}
// Otherwise, do the following:
// 1. Expand for pixel snapping.
// 2. Generate transformation matrix combining, in this order
// a) transform,
// b) container offset,
// c) container scroll offset,
// d) perspective applied by container.
// 3. Apply transform Transform+flattening.
// 4. Apply container clip.
// 1. Expand for pixel snapping.
// Use EnclosingBoundingBox because we cannot properly compute pixel
// snapping for painted elements within the transform since we don't know
// the desired subpixel accumulation at this point, and the transform may
// include a scale. This only makes sense for non-preserve3D.
//
// TODO(dbaron): Does the flattening here need to be done for the
// early return case above as well?
// (Why is this flattening needed in addition to the flattening done by
// using TransformState::kAccumulateTransform?)
if (!StyleRef().Preserves3D()) {
transform_state.Flatten();
transform_state.SetQuad(gfx::QuadF(gfx::RectF(
gfx::ToEnclosingRect(transform_state.LastPlanarQuad().BoundingBox()))));
}
// 2. Generate transformation matrix.
// a) Transform.
gfx::Transform transform;
if (Layer() && Layer()->Transform())
transform.PreConcat(Layer()->CurrentTransform());
// b) Container offset.
transform.PostTranslate(container_offset.left.ToFloat(),
container_offset.top.ToFloat());
// c) Container scroll offset.
if (container_object->IsBox() && container_object != ancestor &&
To<LayoutBox>(container_object)->ContainedContentsScroll(*this)) {
PhysicalOffset offset(
-To<LayoutBox>(container_object)->ScrolledContentOffset());
transform.PostTranslate(offset.left, offset.top);
}
bool has_perspective = container_object && container_object->HasLayer() &&
container_object->StyleRef().HasPerspective();
if (has_perspective && container_object != NearestAncestorForElement()) {
has_perspective = false;
if (StyleRef().Preserves3D() || transform.Creates3d()) {
UseCounter::Count(GetDocument(),
WebFeature::kDifferentPerspectiveCBOrParent);
}
}
// d) Perspective applied by container.
if (has_perspective) {
// Perspective on the container affects us, so we have to factor it in here.
DCHECK(container_object->HasLayer());
gfx::PointF perspective_origin;
if (const auto* container_box = DynamicTo<LayoutBox>(container_object))
perspective_origin = container_box->PerspectiveOrigin();
gfx::Transform perspective_matrix;
perspective_matrix.ApplyPerspectiveDepth(
container_object->StyleRef().UsedPerspective());
perspective_matrix.ApplyTransformOrigin(perspective_origin.x(),
perspective_origin.y(), 0);
transform = perspective_matrix * transform;
}
// 3. Apply transform and flatten.
transform_state.ApplyTransform(transform, accumulation);
if (!container_preserve_3d)
transform_state.Flatten();
// 4. Apply container clip.
if (container_object->IsBox() && container_object != ancestor &&
container_object->HasClipRelatedProperty()) {
return To<LayoutBox>(container_object)
->ApplyBoxClips(transform_state, accumulation, visual_rect_flags);
}
return true;
}
bool LayoutBox::MapContentsRectToBoxSpace(
TransformState& transform_state,
TransformState::TransformAccumulation accumulation,
const LayoutObject& contents,
VisualRectFlags visual_rect_flags) const {
NOT_DESTROYED();
if (!HasClipRelatedProperty())
return true;
if (ContainedContentsScroll(contents))
transform_state.Move(-ScrolledContentOffset());
return ApplyBoxClips(transform_state, accumulation, visual_rect_flags);
}
bool LayoutBox::ContainedContentsScroll(const LayoutObject& contents) const {
NOT_DESTROYED();
if (IsA<LayoutView>(this) &&
contents.StyleRef().GetPosition() == EPosition::kFixed) {
return false;
}
return IsScrollContainer();
}
bool LayoutBox::ApplyBoxClips(
TransformState& transform_state,
TransformState::TransformAccumulation accumulation,
VisualRectFlags visual_rect_flags) const {
NOT_DESTROYED();
// This won't work fully correctly for fixed-position elements, who should
// receive CSS clip but for whom the current object is not in the containing
// block chain.
PhysicalRect clip_rect = ClippingRect(PhysicalOffset());
transform_state.Flatten();
PhysicalRect rect(
gfx::ToEnclosingRect(transform_state.LastPlanarQuad().BoundingBox()));
bool does_intersect;
if (visual_rect_flags & kEdgeInclusive) {
does_intersect = rect.InclusiveIntersect(clip_rect);
} else {
rect.Intersect(clip_rect);
does_intersect = !rect.IsEmpty();
}
transform_state.SetQuad(gfx::QuadF(gfx::RectF(rect)));
return does_intersect;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
LayoutUnit LayoutBox::OverrideContainingBlockContentLogicalWidth() const {
NOT_DESTROYED();
DCHECK(HasOverrideContainingBlockContentLogicalWidth());
return rare_data_->override_containing_block_content_logical_width_;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
bool LayoutBox::HasOverrideContainingBlockContentLogicalWidth() const {
NOT_DESTROYED();
return rare_data_ &&
rare_data_->has_override_containing_block_content_logical_width_;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::SetOverrideContainingBlockContentLogicalWidth(
LayoutUnit logical_width) {
NOT_DESTROYED();
DCHECK_GE(logical_width, LayoutUnit(-1));
EnsureRareData().override_containing_block_content_logical_width_ =
logical_width;
EnsureRareData().has_override_containing_block_content_logical_width_ = true;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::ClearOverrideContainingBlockContentSize() {
NOT_DESTROYED();
if (!rare_data_)
return;
EnsureRareData().has_override_containing_block_content_logical_width_ = false;
}
bool LayoutBox::HitTestAllPhases(HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset) {
NOT_DESTROYED();
if (!MayIntersect(result, hit_test_location, accumulated_offset))
return false;
return LayoutObject::HitTestAllPhases(result, hit_test_location,
accumulated_offset);
}
bool LayoutBox::HitTestOverflowControl(
HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& adjusted_location) const {
NOT_DESTROYED();
auto* scrollable_area = GetScrollableArea();
if (!scrollable_area)
return false;
if (!VisibleToHitTestRequest(result.GetHitTestRequest()))
return false;
PhysicalOffset local_point = hit_test_location.Point() - adjusted_location;
if (!scrollable_area->HitTestOverflowControls(result,
ToRoundedPoint(local_point)))
return false;
UpdateHitTestResult(result, local_point);
return result.AddNodeToListBasedTestResult(
NodeForHitTest(), hit_test_location) == kStopHitTesting;
}
bool LayoutBox::NodeAtPoint(HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestPhase phase) {
NOT_DESTROYED();
if (!MayIntersect(result, hit_test_location, accumulated_offset))
return false;
if (phase == HitTestPhase::kForeground && !HasSelfPaintingLayer() &&
HitTestOverflowControl(result, hit_test_location, accumulated_offset))
return true;
bool skip_children = (result.GetHitTestRequest().GetStopNode() == this) ||
ChildPaintBlockedByDisplayLock();
if (!skip_children && ShouldClipOverflowAlongEitherAxis()) {
// PaintLayer::HitTestFragmentsWithPhase() checked the fragments'
// foreground rect for intersection if a layer is self painting,
// so only do the overflow clip check here for non-self-painting layers.
if (!HasSelfPaintingLayer() &&
!hit_test_location.Intersects(OverflowClipRect(
accumulated_offset, kExcludeOverlayScrollbarSizeForHitTesting))) {
skip_children = true;
}
if (!skip_children && StyleRef().HasBorderRadius()) {
PhysicalRect bounds_rect(accumulated_offset, Size());
skip_children = !hit_test_location.Intersects(
RoundedBorderGeometry::PixelSnappedRoundedInnerBorder(StyleRef(),
bounds_rect));
}
}
if (!skip_children &&
HitTestChildren(result, hit_test_location, accumulated_offset, phase)) {
return true;
}
if (StyleRef().HasBorderRadius() &&
HitTestClippedOutByBorder(hit_test_location, accumulated_offset))
return false;
// Now hit test ourselves.
if (IsInSelfHitTestingPhase(phase) &&
VisibleToHitTestRequest(result.GetHitTestRequest())) {
PhysicalRect bounds_rect;
if (UNLIKELY(result.GetHitTestRequest().IsHitTestVisualOverflow())) {
bounds_rect = VisualOverflowRectIncludingFilters();
} else {
bounds_rect = PhysicalBorderBoxRect();
}
bounds_rect.Move(accumulated_offset);
if (hit_test_location.Intersects(bounds_rect)) {
UpdateHitTestResult(result,
hit_test_location.Point() - accumulated_offset);
if (result.AddNodeToListBasedTestResult(NodeForHitTest(),
hit_test_location,
bounds_rect) == kStopHitTesting)
return true;
}
}
return false;
}
bool LayoutBox::HitTestChildren(HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestPhase phase) {
NOT_DESTROYED();
for (LayoutObject* child = SlowLastChild(); child;
child = child->PreviousSibling()) {
if (child->HasLayer() &&
To<LayoutBoxModelObject>(child)->Layer()->IsSelfPaintingLayer())
continue;
PhysicalOffset child_accumulated_offset = accumulated_offset;
if (auto* box = DynamicTo<LayoutBox>(child))
child_accumulated_offset += box->PhysicalLocation(this);
if (child->NodeAtPoint(result, hit_test_location, child_accumulated_offset,
phase))
return true;
}
return false;
}
bool LayoutBox::HitTestClippedOutByBorder(
const HitTestLocation& hit_test_location,
const PhysicalOffset& border_box_location) const {
NOT_DESTROYED();
PhysicalRect border_rect = PhysicalBorderBoxRect();
border_rect.Move(border_box_location);
return !hit_test_location.Intersects(
RoundedBorderGeometry::PixelSnappedRoundedBorder(StyleRef(),
border_rect));
}
void LayoutBox::Paint(const PaintInfo& paint_info) const {
NOT_DESTROYED();
NOTREACHED_NORETURN();
}
PhysicalRect LayoutBox::BackgroundPaintedExtent() const {
NOT_DESTROYED();
return PhysicalBackgroundRect(kBackgroundPaintedExtent);
}
bool LayoutBox::BackgroundIsKnownToBeOpaqueInRect(
const PhysicalRect& local_rect) const {
NOT_DESTROYED();
// If the element has appearance, it might be painted by theme.
// We cannot be sure if theme paints the background opaque.
// In this case it is safe to not assume opaqueness.
// FIXME: May be ask theme if it paints opaque.
if (StyleRef().HasEffectiveAppearance())
return false;
// FIXME: Check the opaqueness of background images.
// FIXME: Use rounded rect if border radius is present.
if (StyleRef().HasBorderRadius())
return false;
if (HasClipPath())
return false;
if (StyleRef().HasBlendMode())
return false;
return PhysicalBackgroundRect(kBackgroundKnownOpaqueRect)
.Contains(local_rect);
}
// Note that callers are responsible for checking
// ChildPaintBlockedByDisplayLock(), since that is a property of the parent
// rather than of the child.
static bool IsCandidateForOpaquenessTest(const LayoutBox& child_box) {
// Skip all layers to simplify ForegroundIsKnownToBeOpaqueInRect(). This
// covers cases of clipped, transformed, translucent, composited, etc.
if (child_box.HasLayer())
return false;
const ComputedStyle& child_style = child_box.StyleRef();
if (child_style.Visibility() != EVisibility::kVisible ||
child_style.ShapeOutside())
return false;
if (child_box.Size().IsZero())
return false;
// A replaced element with border-radius always clips the content.
if (child_box.IsLayoutReplaced() && child_style.HasBorderRadius())
return false;
return true;
}
bool LayoutBox::ForegroundIsKnownToBeOpaqueInRect(
const PhysicalRect& local_rect,
unsigned max_depth_to_test) const {
NOT_DESTROYED();
if (!max_depth_to_test)
return false;
if (ChildPaintBlockedByDisplayLock())
return false;
for (LayoutObject* child = SlowFirstChild(); child;
child = child->NextSibling()) {
// We do not bother checking descendants of |LayoutInline|, including
// block-in-inline, because the cost of checking them overweights the
// benefits.
if (!child->IsBox())
continue;
auto* child_box = To<LayoutBox>(child);
if (!IsCandidateForOpaquenessTest(*child_box))
continue;
DCHECK(!child_box->IsPositioned());
PhysicalRect child_local_rect = local_rect;
child_local_rect.Move(-child_box->PhysicalLocation());
if (child_local_rect.Y() < 0 || child_local_rect.X() < 0) {
// If there is unobscured area above/left of a static positioned box then
// the rect is probably not covered. This can cause false-negative in
// non-horizontal-tb writing mode but is allowed.
return false;
}
if (child_local_rect.Bottom() > child_box->Size().height ||
child_local_rect.Right() > child_box->Size().width) {
continue;
}
if (RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled() &&
child->Style()->HasCurrentBackgroundColorAnimation()) {
return false;
}
if (child_box->BackgroundIsKnownToBeOpaqueInRect(child_local_rect))
return true;
if (child_box->ForegroundIsKnownToBeOpaqueInRect(child_local_rect,
max_depth_to_test - 1))
return true;
}
return false;
}
DISABLE_CFI_PERF
bool LayoutBox::ComputeBackgroundIsKnownToBeObscured() const {
NOT_DESTROYED();
if (ScrollsOverflow())
return false;
// Test to see if the children trivially obscure the background.
if (!StyleRef().HasBackground())
return false;
// Root background painting is special.
if (IsA<LayoutView>(this))
return false;
if (StyleRef().BoxShadow())
return false;
return ForegroundIsKnownToBeOpaqueInRect(BackgroundPaintedExtent(),
kBackgroundObscurationTestMaxDepth);
}
void LayoutBox::ImageChanged(WrappedImagePtr image,
CanDeferInvalidation defer) {
NOT_DESTROYED();
bool is_box_reflect_image =
(StyleRef().BoxReflect() && StyleRef().BoxReflect()->Mask().GetImage() &&
StyleRef().BoxReflect()->Mask().GetImage()->Data() == image);
if (is_box_reflect_image && HasLayer()) {
Layer()->SetFilterOnEffectNodeDirty();
SetNeedsPaintPropertyUpdate();
}
// TODO(chrishtr): support delayed paint invalidation for animated border
// images.
if ((StyleRef().BorderImage().GetImage() &&
StyleRef().BorderImage().GetImage()->Data() == image) ||
(StyleRef().MaskBoxImage().GetImage() &&
StyleRef().MaskBoxImage().GetImage()->Data() == image) ||
is_box_reflect_image) {
SetShouldDoFullPaintInvalidationWithoutLayoutChange(
PaintInvalidationReason::kImage);
} else {
for (const FillLayer* layer = &StyleRef().MaskLayers(); layer;
layer = layer->Next()) {
if (layer->GetImage() && image == layer->GetImage()->Data()) {
SetShouldDoFullPaintInvalidationWithoutLayoutChange(
PaintInvalidationReason::kImage);
if (layer->GetImage()->IsMaskSource() && IsSVGChild()) {
// Since an invalid <mask> reference does not yield a paint property
// on SVG content (see CSSMaskPainter), we need to update paint
// properties when such a reference changes.
SetNeedsPaintPropertyUpdate();
}
break;
}
}
}
if (!BackgroundTransfersToView()) {
for (const FillLayer* layer = &StyleRef().BackgroundLayers(); layer;
layer = layer->Next()) {
if (layer->GetImage() && image == layer->GetImage()->Data()) {
bool maybe_animated =
layer->GetImage()->CachedImage() &&
layer->GetImage()->CachedImage()->GetImage() &&
layer->GetImage()->CachedImage()->GetImage()->MaybeAnimated();
if (defer == CanDeferInvalidation::kYes && maybe_animated)
SetMayNeedPaintInvalidationAnimatedBackgroundImage();
else
SetBackgroundNeedsFullPaintInvalidation();
break;
}
}
}
ShapeValue* shape_outside_value = StyleRef().ShapeOutside();
if (!GetFrameView()->IsInPerformLayout() && IsFloating() &&
shape_outside_value && shape_outside_value->GetImage() &&
shape_outside_value->GetImage()->Data() == image) {
ShapeOutsideInfo& info = ShapeOutsideInfo::EnsureInfo(*this);
if (!info.IsComputingShape()) {
info.MarkShapeAsDirty();
if (auto* containing_block = ContainingBlock()) {
containing_block->SetChildNeedsLayout();
}
}
}
}
ResourcePriority LayoutBox::ComputeResourcePriority() const {
NOT_DESTROYED();
PhysicalRect view_bounds = ViewRect();
PhysicalRect object_bounds = PhysicalContentBoxRect();
// TODO(japhet): Is this IgnoreTransforms correct? Would it be better to use
// the visual rect (which has ancestor clips and transforms applied)? Should
// we map to the top-level viewport instead of the current (sub) frame?
object_bounds.Move(LocalToAbsolutePoint(PhysicalOffset(), kIgnoreTransforms));
// The object bounds might be empty right now, so intersects will fail since
// it doesn't deal with empty rects. Use LayoutRect::contains in that case.
bool is_visible;
if (!object_bounds.IsEmpty())
is_visible = view_bounds.Intersects(object_bounds);
else
is_visible = view_bounds.Contains(object_bounds);
PhysicalRect screen_rect;
if (!object_bounds.IsEmpty()) {
screen_rect = view_bounds;
screen_rect.Intersect(object_bounds);
}
int screen_area = 0;
if (!screen_rect.IsEmpty() && is_visible)
screen_area = (screen_rect.Width() * screen_rect.Height()).ToInt();
return ResourcePriority(
is_visible ? ResourcePriority::kVisible : ResourcePriority::kNotVisible,
screen_area);
}
void LayoutBox::LocationChanged() {
NOT_DESTROYED();
// The location may change because of layout of other objects. Should check
// this object for paint invalidation.
if (!NeedsLayout())
SetShouldCheckForPaintInvalidation();
}
void LayoutBox::SizeChanged() {
NOT_DESTROYED();
SetScrollableAreaSizeChanged(true);
// The size may change because of layout of other objects. Should check this
// object for paint invalidation.
if (!NeedsLayout())
SetShouldCheckForPaintInvalidation();
// In flipped blocks writing mode, our children can change physical location,
// but their flipped location remains the same.
if (HasFlippedBlocksWritingMode()) {
if (ChildrenInline())
SetSubtreeShouldDoFullPaintInvalidation();
else
SetSubtreeShouldCheckForPaintInvalidation();
}
}
bool LayoutBox::IntersectsVisibleViewport() const {
NOT_DESTROYED();
LayoutView* layout_view = View();
while (auto* owner = layout_view->GetFrame()->OwnerLayoutObject()) {
layout_view = owner->View();
}
// If this is the outermost LayoutView then it will always intersect. (`rect`
// will be the viewport in that case.)
if (this == layout_view) {
return true;
}
PhysicalRect rect = VisualOverflowRect();
MapToVisualRectInAncestorSpace(layout_view, rect);
return rect.Intersects(PhysicalRect(
layout_view->GetFrameView()->GetScrollableArea()->VisibleContentRect()));
}
void LayoutBox::EnsureIsReadyForPaintInvalidation() {
NOT_DESTROYED();
LayoutBoxModelObject::EnsureIsReadyForPaintInvalidation();
bool new_obscured = ComputeBackgroundIsKnownToBeObscured();
if (BackgroundIsKnownToBeObscured() != new_obscured) {
SetBackgroundIsKnownToBeObscured(new_obscured);
SetBackgroundNeedsFullPaintInvalidation();
}
if (MayNeedPaintInvalidationAnimatedBackgroundImage() &&
!BackgroundIsKnownToBeObscured()) {
SetBackgroundNeedsFullPaintInvalidation();
SetShouldDelayFullPaintInvalidation();
}
if (ShouldDelayFullPaintInvalidation() && IntersectsVisibleViewport()) {
// Do regular full paint invalidation if the object with delayed paint
// invalidation is on screen.
ClearShouldDelayFullPaintInvalidation();
DCHECK(ShouldDoFullPaintInvalidation());
}
}
void LayoutBox::InvalidatePaint(const PaintInvalidatorContext& context) const {
NOT_DESTROYED();
BoxPaintInvalidator(*this, context).InvalidatePaint();
}
void LayoutBox::ClearPaintFlags() {
NOT_DESTROYED();
LayoutObject::ClearPaintFlags();
if (auto* scrollable_area = GetScrollableArea()) {
if (auto* scrollbar =
DynamicTo<CustomScrollbar>(scrollable_area->HorizontalScrollbar()))
scrollbar->ClearPaintFlags();
if (auto* scrollbar =
DynamicTo<CustomScrollbar>(scrollable_area->VerticalScrollbar()))
scrollbar->ClearPaintFlags();
}
}
PhysicalRect LayoutBox::OverflowClipRect(
const PhysicalOffset& location,
OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const {
NOT_DESTROYED();
PhysicalRect clip_rect;
if (IsEffectiveRootScroller()) {
// If this box is the effective root scroller, use the viewport clipping
// rect since it will account for the URL bar correctly which the border
// box does not. We can do this because the effective root scroller is
// restricted such that it exactly fills the viewport. See
// RootScrollerController::IsValidRootScroller()
clip_rect = PhysicalRect(location, View()->ViewRect().size);
} else {
clip_rect = PhysicalBorderBoxRect();
clip_rect.Contract(BorderOutsets());
clip_rect.Move(location);
// Videos need to be pre-snapped so that they line up with the
// display_rect and can enable hardware overlays.
// Embedded objects are always sized to fit the content rect, but they
// could overflow by 1px due to pre-snapping. Adjust clip rect to
// match pre-snapped box as a special case.
if (IsVideo() || IsLayoutEmbeddedContent())
clip_rect = LayoutReplaced::PreSnappedRectForPersistentSizing(clip_rect);
if (HasNonVisibleOverflow()) {
const auto overflow_clip = GetOverflowClipAxes();
if (overflow_clip != kOverflowClipBothAxis) {
ApplyVisibleOverflowToClipRect(overflow_clip, clip_rect);
} else if (ShouldApplyOverflowClipMargin()) {
switch (StyleRef().OverflowClipMargin()->GetReferenceBox()) {
case StyleOverflowClipMargin::ReferenceBox::kBorderBox:
clip_rect.Expand(BorderOutsets());
break;
case StyleOverflowClipMargin::ReferenceBox::kPaddingBox:
break;
case StyleOverflowClipMargin::ReferenceBox::kContentBox:
clip_rect.Contract(PaddingOutsets());
break;
}
clip_rect.Inflate(StyleRef().OverflowClipMargin()->GetMargin());
}
}
}
if (IsScrollContainer()) {
// The additional gutters created by scrollbar-gutter don't occlude the
// content underneath, so they should not be clipped out here.
// See https://crbug.com/710214
ExcludeScrollbars(clip_rect, overlay_scrollbar_clip_behavior,
kExcludeScrollbarGutter);
}
if (UNLIKELY(IsA<HTMLInputElement>(GetNode()))) {
// We only apply a clip to <input> buttons, and not regular <button>s.
if (IsTextField() || IsInputButton()) {
DCHECK(HasControlClip());
PhysicalRect control_clip = PhysicalPaddingBoxRect();
control_clip.Move(location);
clip_rect.Intersect(control_clip);
}
} else if (UNLIKELY(IsMenuList())) {
DCHECK(HasControlClip());
PhysicalRect control_clip = PhysicalContentBoxRect();
control_clip.Move(location);
clip_rect.Intersect(control_clip);
} else {
DCHECK(!HasControlClip());
}
return clip_rect;
}
bool LayoutBox::HasControlClip() const {
NOT_DESTROYED();
return UNLIKELY(IsTextField() || IsMenuList() || IsInputButton());
}
void LayoutBox::ExcludeScrollbars(
PhysicalRect& rect,
OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior,
ShouldIncludeScrollbarGutter include_scrollbar_gutter) const {
NOT_DESTROYED();
if (CanSkipComputeScrollbars())
return;
PhysicalBoxStrut scrollbars = ComputeScrollbarsInternal(
kDoNotClampToContentBox, overlay_scrollbar_clip_behavior,
include_scrollbar_gutter);
rect.offset.top += scrollbars.top;
rect.offset.left += scrollbars.left;
rect.size.width -= scrollbars.HorizontalSum();
rect.size.height -= scrollbars.VerticalSum();
rect.size.ClampNegativeToZero();
}
PhysicalRect LayoutBox::ClipRect(const PhysicalOffset& location) const {
NOT_DESTROYED();
PhysicalRect clip_rect(location, Size());
LayoutUnit width = Size().width;
LayoutUnit height = Size().height;
if (!StyleRef().ClipLeft().IsAuto()) {
LayoutUnit c = ValueForLength(StyleRef().ClipLeft(), width);
clip_rect.offset.left += c;
clip_rect.size.width -= c;
}
if (!StyleRef().ClipRight().IsAuto()) {
clip_rect.size.width -=
width - ValueForLength(StyleRef().ClipRight(), width);
}
if (!StyleRef().ClipTop().IsAuto()) {
LayoutUnit c = ValueForLength(StyleRef().ClipTop(), height);
clip_rect.offset.top += c;
clip_rect.size.height -= c;
}
if (!StyleRef().ClipBottom().IsAuto()) {
clip_rect.size.height -=
height - ValueForLength(StyleRef().ClipBottom(), height);
}
return clip_rect;
}
LayoutUnit LayoutBox::ContainingBlockLogicalHeightForRelPositioned() const {
NOT_DESTROYED();
DCHECK(IsRelPositioned());
// TODO(ikilpatrick): This is resolving percentages against incorrectly if
// the container is an inline.
auto* cb = To<LayoutBoxModelObject>(Container());
return ContainingBlockLogicalHeightForPositioned(cb) -
cb->PaddingLogicalHeight();
}
LayoutUnit LayoutBox::ContainingBlockLogicalWidthForContent() const {
NOT_DESTROYED();
if (HasOverrideContainingBlockContentLogicalWidth())
return OverrideContainingBlockContentLogicalWidth();
LayoutBlock* cb = ContainingBlock();
if (IsOutOfFlowPositioned())
return cb->ClientLogicalWidth();
return cb->AvailableLogicalWidth();
}
PhysicalOffset LayoutBox::OffsetFromContainerInternal(
const LayoutObject* o,
MapCoordinatesFlags mode) const {
NOT_DESTROYED();
DCHECK_EQ(o, Container());
PhysicalOffset offset = PhysicalLocation();
if (IsStickyPositioned() && !(mode & kIgnoreStickyOffset)) {
offset += StickyPositionOffset();
}
if (o->IsScrollContainer())
offset += OffsetFromScrollableContainer(o, mode & kIgnoreScrollOffset);
if (NeedsAnchorPositionScrollAdjustment()) {
offset += AnchorPositionScrollTranslationOffset();
}
return offset;
}
bool LayoutBox::HasInlineFragments() const {
NOT_DESTROYED();
return first_fragment_item_index_;
}
void LayoutBox::ClearFirstInlineFragmentItemIndex() {
NOT_DESTROYED();
CHECK(IsInLayoutNGInlineFormattingContext()) << *this;
first_fragment_item_index_ = 0u;
}
void LayoutBox::SetFirstInlineFragmentItemIndex(wtf_size_t index) {
NOT_DESTROYED();
CHECK(IsInLayoutNGInlineFormattingContext()) << *this;
DCHECK_NE(index, 0u);
first_fragment_item_index_ = index;
}
void LayoutBox::InLayoutNGInlineFormattingContextWillChange(bool new_value) {
NOT_DESTROYED();
if (IsInLayoutNGInlineFormattingContext())
ClearFirstInlineFragmentItemIndex();
}
bool LayoutBox::PhysicalFragmentList::MayHaveFragmentItems() const {
return !IsEmpty() && front().IsInlineFormattingContext();
}
bool LayoutBox::PhysicalFragmentList::SlowHasFragmentItems() const {
for (const PhysicalBoxFragment& fragment : *this) {
if (fragment.HasItems())
return true;
}
return false;
}
wtf_size_t LayoutBox::PhysicalFragmentList::IndexOf(
const PhysicalBoxFragment& fragment) const {
wtf_size_t index = 0;
for (const auto& result : layout_results_) {
if (&result->GetPhysicalFragment() == &fragment) {
return index;
}
++index;
}
return kNotFound;
}
bool LayoutBox::PhysicalFragmentList::Contains(
const PhysicalBoxFragment& fragment) const {
return IndexOf(fragment) != kNotFound;
}
void LayoutBox::AddMeasureLayoutResult(const LayoutResult* result) {
// Ensure the given result is valid for the measure cache.
if (result->Status() != LayoutResult::kSuccess) {
return;
}
if (result->GetConstraintSpaceForCaching().CacheSlot() !=
LayoutResultCacheSlot::kMeasure) {
return;
}
DCHECK(
To<PhysicalBoxFragment>(result->GetPhysicalFragment()).IsOnlyForNode());
if (!measure_cache_) {
measure_cache_ = MakeGarbageCollected<MeasureCache>();
}
// Clear out old measure results if we need non-simplifed layout.
if (NeedsLayout() && !NeedsSimplifiedLayoutOnly()) {
measure_cache_->Clear();
}
measure_cache_->Add(result);
}
void LayoutBox::SetCachedLayoutResult(const LayoutResult* result,
wtf_size_t index) {
NOT_DESTROYED();
if (result->GetConstraintSpaceForCaching().CacheSlot() ==
LayoutResultCacheSlot::kMeasure) {
DCHECK(!result->GetPhysicalFragment().GetBreakToken());
DCHECK(
To<PhysicalBoxFragment>(result->GetPhysicalFragment()).IsOnlyForNode());
DCHECK_EQ(index, 0u);
// We don't early return here, when setting the "measure" result we also
// set the "layout" result.
if (measure_cache_) {
measure_cache_->InvalidateItems();
}
AddMeasureLayoutResult(result);
if (IsTableCell()) {
To<LayoutTableCell>(this)->InvalidateLayoutResultCacheAfterMeasure();
}
} else {
// We have a "layout" result, and we may need to clear the old "measure"
// result if we needed non-simplified layout.
if (NeedsLayout() && !NeedsSimplifiedLayoutOnly()) {
if (measure_cache_) {
measure_cache_->Clear();
}
}
}
// If we're about to cache a layout result that is different than the measure
// result, mark the measure result's fragment as no longer having valid
// children. It can still be used to query information about this box's
// fragment from the measure pass, but children might be out of sync with the
// latest version of the tree.
if (measure_cache_) {
measure_cache_->SetFragmentChildrenInvalid(result);
}
SetLayoutResult(result, index);
}
void LayoutBox::SetLayoutResult(const LayoutResult* result, wtf_size_t index) {
NOT_DESTROYED();
DCHECK_EQ(result->Status(), LayoutResult::kSuccess);
const auto& box_fragment =
To<PhysicalBoxFragment>(result->GetPhysicalFragment());
if (index != WTF::kNotFound && layout_results_.size() > index) {
if (layout_results_.size() > index + 1) {
// If we have reached the end, remove surplus results from previous
// layout.
//
// Note: When an OOF is fragmented, we wait to lay it out at the
// fragmentation context root. If the OOF lives above a column spanner,
// though, we may lay it out early to make sure the OOF contributes to the
// correct column block-size. Thus, if an item broke as a result of a
// spanner, remove subsequent sibling items so that OOFs don't try to
// access old fragments.
//
// Additionally, if an outer multicol has a spanner break, we may try
// to access old fragments of the inner multicol if it hasn't completed
// layout yet. Remove subsequent multicol fragments to avoid OOFs from
// trying to access old fragments.
//
// TODO(layout-dev): Other solutions to handling interactions between OOFs
// and spanner breaks may need to be considered.
if (!box_fragment.GetBreakToken() ||
box_fragment.GetBreakToken()->IsCausedByColumnSpanner() ||
box_fragment.IsFragmentationContextRoot()) {
// Before forgetting any old fragments and their items, we need to clear
// associations.
if (box_fragment.IsInlineFormattingContext())
FragmentItems::ClearAssociatedFragments(this);
ShrinkLayoutResults(index + 1);
}
}
ReplaceLayoutResult(std::move(result), index);
return;
}
DCHECK(index == layout_results_.size() || index == kNotFound);
AppendLayoutResult(result);
if (!box_fragment.GetBreakToken()) {
FinalizeLayoutResults();
}
}
void LayoutBox::AppendLayoutResult(const LayoutResult* result) {
const auto& fragment = To<PhysicalBoxFragment>(result->GetPhysicalFragment());
// |layout_results_| is particularly critical when side effects are disabled.
DCHECK(!DisableLayoutSideEffectsScope::IsDisabled());
layout_results_.push_back(std::move(result));
InvalidateCachedGeometry();
CheckDidAddFragment(*this, fragment);
}
void LayoutBox::ReplaceLayoutResult(const LayoutResult* result,
wtf_size_t index) {
NOT_DESTROYED();
DCHECK_LE(index, layout_results_.size());
const LayoutResult* old_result = layout_results_[index];
if (old_result == result)
return;
const auto& fragment = To<PhysicalBoxFragment>(result->GetPhysicalFragment());
const auto& old_fragment = old_result->GetPhysicalFragment();
bool got_new_fragment = &old_fragment != &fragment;
if (got_new_fragment) {
if (HasFragmentItems()) {
if (!index)
InvalidateItems(*old_result);
FragmentItems::ClearAssociatedFragments(this);
}
// We are about to replace a fragment, and the size may have changed. The
// inline-size and total stitched block-size may still remain unchanged,
// though, and pre-paint can only detect changes in the total stitched
// size. So this is our last chance to detect any size changes at the
// fragment itself. Only do this if we're fragmented, though. Otherwise
// leave it to pre-paint to figure out if invalidation is really required,
// since it's fine to just check the stitched sizes when not fragmented.
// Unconditionally requiring full paint invalidation at size changes may be
// unnecessary and expensive.
if (layout_results_.size() > 1 && fragment.Size() != old_fragment.Size()) {
SetShouldDoFullPaintInvalidation();
}
}
// |layout_results_| is particularly critical when side effects are disabled.
DCHECK(!DisableLayoutSideEffectsScope::IsDisabled());
layout_results_[index] = std::move(result);
InvalidateCachedGeometry();
CheckDidAddFragment(*this, fragment, index);
if (got_new_fragment && !fragment.GetBreakToken()) {
// If this is the last result, the results vector better agree on that.
DCHECK_EQ(index, layout_results_.size() - 1);
FinalizeLayoutResults();
}
}
void LayoutBox::FinalizeLayoutResults() {
DCHECK(!layout_results_.empty());
DCHECK(!layout_results_.back()->GetPhysicalFragment().GetBreakToken());
#if EXPENSIVE_DCHECKS_ARE_ON()
CheckMayHaveFragmentItems();
#endif
// If we've added all the results we were going to, and the node establishes
// an inline formatting context, we have some finalization to do.
if (HasFragmentItems()) {
FragmentItems::FinalizeAfterLayout(layout_results_,
*To<LayoutBlockFlow>(this));
}
}
void LayoutBox::RebuildFragmentTreeSpine() {
DCHECK(PhysicalFragmentCount());
SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES(
"Blink.Layout.RebuildFragmentTreeSpine");
// If this box has an associated layout-result, rebuild the spine of the
// fragment-tree to ensure consistency.
LayoutBox* container = this;
while (container && container->PhysicalFragmentCount() &&
!container->NeedsLayout()) {
for (auto& result : container->layout_results_)
result = LayoutResult::CloneWithPostLayoutFragments(*result);
container = container->ContainingNGBox();
}
if (container && container->NeedsLayout()) {
// We stopped walking upwards because this container needs layout. This
// typically means that updating the associated layout results is waste of
// time, since we're probably going to lay it out anyway. However, in some
// cases the container is going to hit the cache and therefore not perform
// actual layout. If this happens, we need to update the layout results at
// that point.
container->SetHasBrokenSpine();
}
}
void LayoutBox::ShrinkLayoutResults(wtf_size_t results_to_keep) {
NOT_DESTROYED();
DCHECK_GE(layout_results_.size(), results_to_keep);
// Invalidate if inline |DisplayItemClient|s will be destroyed.
for (wtf_size_t i = results_to_keep; i < layout_results_.size(); i++)
InvalidateItems(*layout_results_[i]);
// |layout_results_| is particularly critical when side effects are disabled.
DCHECK(!DisableLayoutSideEffectsScope::IsDisabled());
layout_results_.Shrink(results_to_keep);
InvalidateCachedGeometry();
}
#if EXPENSIVE_DCHECKS_ARE_ON()
void LayoutBox::CheckMayHaveFragmentItems() const {
NOT_DESTROYED();
if (!MayHaveFragmentItems()) {
DCHECK(!PhysicalFragments().SlowHasFragmentItems());
}
}
#endif
void LayoutBox::InvalidateCachedGeometry() {
NOT_DESTROYED();
SetHasValidCachedGeometry(false);
if (auto* block_flow = DynamicTo<LayoutBlockFlow>(this)) {
if (auto* flow_thread = block_flow->MultiColumnFlowThread()) {
flow_thread->SetHasValidCachedGeometry(false);
for (auto* sibling = flow_thread->NextSiblingBox(); sibling;
sibling = sibling->NextSiblingBox()) {
sibling->SetHasValidCachedGeometry(false);
}
}
}
}
// static
void LayoutBox::InvalidateItems(const LayoutResult& result) {
// Invalidate if inline |DisplayItemClient|s will be destroyed.
const auto& box_fragment =
To<PhysicalBoxFragment>(result.GetPhysicalFragment());
if (!box_fragment.HasItems())
return;
ObjectPaintInvalidator(*box_fragment.GetLayoutObject())
.SlowSetPaintingLayerNeedsRepaint();
}
const LayoutResult* LayoutBox::GetCachedLayoutResult(
const BlockBreakToken* break_token) const {
NOT_DESTROYED();
wtf_size_t index = FragmentIndex(break_token);
if (index >= layout_results_.size())
return nullptr;
const LayoutResult* result = layout_results_[index];
DCHECK(!result->GetPhysicalFragment().IsLayoutObjectDestroyedOrMoved() ||
BeingDestroyed());
return result;
}
const LayoutResult* LayoutBox::GetCachedMeasureResult(
const ConstraintSpace& space,
std::optional<FragmentGeometry>* fragment_geometry) const {
NOT_DESTROYED();
if (!measure_cache_) {
return nullptr;
}
// If we've already had an actual layout pass, and the node fragmented, we
// cannot reliably re-use the measure result. What we want to avoid here is
// simplified layout inside a measure-result, as that would descend into a
// fragment subtree generated by actual (fragmented) layout, which is
// invalid. But it seems safer to stop such attempts here, so that we don't
// hand out results that may cause problems if we end up with simplified
// layout inside.
if (!layout_results_.empty()) {
const PhysicalBoxFragment* first_fragment = GetPhysicalFragment(0);
if (first_fragment->GetBreakToken()) {
return nullptr;
}
}
return measure_cache_
? measure_cache_->Find(BlockNode(const_cast<LayoutBox*>(this)),
space, fragment_geometry)
: nullptr;
}
const LayoutResult* LayoutBox::GetSingleCachedLayoutResult() const {
DCHECK_LE(layout_results_.size(), 1u);
return GetCachedLayoutResult(nullptr);
}
const LayoutResult* LayoutBox::GetSingleCachedMeasureResultForTesting() const {
return measure_cache_ ? measure_cache_->GetLastForTesting() : nullptr;
}
const LayoutResult* LayoutBox::GetLayoutResult(wtf_size_t i) const {
NOT_DESTROYED();
return layout_results_[i].Get();
}
const PhysicalBoxFragment&
LayoutBox::PhysicalFragmentList::Iterator::operator*() const {
return To<PhysicalBoxFragment>((*iterator_)->GetPhysicalFragment());
}
const PhysicalBoxFragment& LayoutBox::PhysicalFragmentList::front() const {
return To<PhysicalBoxFragment>(
layout_results_.front()->GetPhysicalFragment());
}
const PhysicalBoxFragment& LayoutBox::PhysicalFragmentList::back() const {
return To<PhysicalBoxFragment>(layout_results_.back()->GetPhysicalFragment());
}
const FragmentData* LayoutBox::FragmentDataFromPhysicalFragment(
const PhysicalBoxFragment& physical_fragment) const {
NOT_DESTROYED();
return &FragmentList().at(BoxFragmentIndex(physical_fragment));
}
void LayoutBox::SetSpannerPlaceholder(
LayoutMultiColumnSpannerPlaceholder& placeholder) {
NOT_DESTROYED();
// Not expected to change directly from one spanner to another.
CHECK(!rare_data_ || !rare_data_->spanner_placeholder_);
EnsureRareData().spanner_placeholder_ = &placeholder;
}
void LayoutBox::ClearSpannerPlaceholder() {
NOT_DESTROYED();
if (!rare_data_)
return;
rare_data_->spanner_placeholder_ = nullptr;
}
PhysicalRect LayoutBox::LocalVisualRectIgnoringVisibility() const {
NOT_DESTROYED();
return SelfVisualOverflowRect();
}
void LayoutBox::InflateVisualRectForFilterUnderContainer(
TransformState& transform_state,
const LayoutObject& container,
const LayoutBoxModelObject* ancestor_to_stop_at) const {
NOT_DESTROYED();
transform_state.Flatten();
// Apply visual overflow caused by reflections and filters defined on objects
// between this object and container (not included) or ancestorToStopAt
// (included).
PhysicalOffset offset_from_container = OffsetFromContainer(&container);
transform_state.Move(offset_from_container);
for (LayoutObject* parent = Parent(); parent && parent != container;
parent = parent->Parent()) {
if (parent->IsBox()) {
// Convert rect into coordinate space of parent to apply parent's
// reflection and filter.
PhysicalOffset parent_offset = parent->OffsetFromAncestor(&container);
transform_state.Move(-parent_offset);
To<LayoutBox>(parent)->InflateVisualRectForFilter(transform_state);
transform_state.Move(parent_offset);
}
if (parent == ancestor_to_stop_at)
break;
}
transform_state.Move(-offset_from_container);
}
bool LayoutBox::MapToVisualRectInAncestorSpaceInternal(
const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
VisualRectFlags visual_rect_flags) const {
NOT_DESTROYED();
if (ancestor == this)
return true;
if (!(visual_rect_flags & kIgnoreFilters)) {
InflateVisualRectForFilter(transform_state);
}
AncestorSkipInfo skip_info(ancestor, true);
LayoutObject* container = Container(&skip_info);
if (!container)
return true;
PhysicalOffset container_offset;
if (auto* box = DynamicTo<LayoutBox>(container)) {
container_offset += PhysicalLocation(box);
} else {
container_offset += PhysicalLocation();
}
if (IsStickyPositioned()) {
container_offset += StickyPositionOffset();
} else if (UNLIKELY(NeedsAnchorPositionScrollAdjustment())) {
container_offset += AnchorPositionScrollTranslationOffset();
}
if (skip_info.FilterSkipped() && !(visual_rect_flags & kIgnoreFilters)) {
InflateVisualRectForFilterUnderContainer(transform_state, *container,
ancestor);
}
if (!MapVisualRectToContainer(container, container_offset, ancestor,
visual_rect_flags, transform_state))
return false;
if (skip_info.AncestorSkipped()) {
bool preserve3D = container->StyleRef().Preserves3D();
TransformState::TransformAccumulation accumulation =
preserve3D ? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform;
// If the ancestor is below the container, then we need to map the rect into
// ancestor's coordinates.
PhysicalOffset ancestor_container_offset =
ancestor->OffsetFromAncestor(container);
transform_state.Move(-ancestor_container_offset, accumulation);
return true;
}
if (IsFixedPositioned() && container == ancestor && container->IsLayoutView())
transform_state.Move(To<LayoutView>(container)->OffsetForFixedPosition());
return container->MapToVisualRectInAncestorSpaceInternal(
ancestor, transform_state, visual_rect_flags);
}
void LayoutBox::InflateVisualRectForFilter(
TransformState& transform_state) const {
NOT_DESTROYED();
if (!Layer() || !Layer()->PaintsWithFilters())
return;
transform_state.Flatten();
PhysicalRect rect = PhysicalRect::EnclosingRect(
transform_state.LastPlanarQuad().BoundingBox());
transform_state.SetQuad(
gfx::QuadF(gfx::RectF(Layer()->MapRectForFilter(rect))));
}
bool LayoutBox::AutoWidthShouldFitContent() const {
NOT_DESTROYED();
return GetNode() &&
(IsA<HTMLInputElement>(*GetNode()) ||
IsA<HTMLSelectElement>(*GetNode()) ||
IsA<HTMLButtonElement>(*GetNode()) ||
IsA<HTMLTextAreaElement>(*GetNode()) || IsRenderedLegend());
}
bool LayoutBox::SkipContainingBlockForPercentHeightCalculation(
const LayoutBox* containing_block) {
const bool in_quirks_mode = containing_block->GetDocument().InQuirksMode();
// Anonymous blocks should not impede percentage resolution on a child.
// Examples of such anonymous blocks are blocks wrapped around inlines that
// have block siblings (from the CSS spec) and multicol flow threads (an
// implementation detail). Another implementation detail, ruby columns, create
// anonymous inline-blocks, so skip those too. All other types of anonymous
// objects, such as table-cells, will be treated just as if they were
// non-anonymous.
if (containing_block->IsAnonymous()) {
if (!in_quirks_mode && containing_block->Parent() &&
containing_block->Parent()->IsFieldset()) {
return false;
}
EDisplay display = containing_block->StyleRef().Display();
return display == EDisplay::kBlock || display == EDisplay::kInlineBlock ||
display == EDisplay::kFlowRoot;
}
// For quirks mode, we skip most auto-height containing blocks when computing
// percentages.
if (!in_quirks_mode ||
!containing_block->StyleRef().LogicalHeight().HasAuto()) {
return false;
}
const Node* node = containing_block->GetNode();
if (UNLIKELY(node->IsInUserAgentShadowRoot())) {
const Element* host = node->OwnerShadowHost();
if (const auto* input = DynamicTo<HTMLInputElement>(host)) {
// In web_tests/fast/forms/range/range-thumb-height-percentage.html, a
// percent height for the slider thumb element should refer to the height
// of the INPUT box.
if (input->FormControlType() == FormControlType::kInputRange) {
return true;
}
}
}
return !containing_block->IsLayoutReplaced() &&
!containing_block->IsTableCell() &&
!containing_block->IsOutOfFlowPositioned() &&
!containing_block->IsLayoutGrid() &&
!containing_block->IsFlexibleBox() &&
!containing_block->IsLayoutCustom();
}
LayoutUnit LayoutBox::ContainingBlockLogicalHeightForPositioned(
const LayoutBoxModelObject* containing_block) const {
NOT_DESTROYED();
// Use viewport as container for top-level fixed-position elements.
const auto* view = DynamicTo<LayoutView>(containing_block);
if (StyleRef().GetPosition() == EPosition::kFixed && view &&
!GetDocument().Printing()) {
if (LocalFrameView* frame_view = view->GetFrameView()) {
// Don't use visibleContentRect since the PaintLayer's size has not been
// set yet.
gfx::Size viewport_size =
frame_view->LayoutViewport()->ExcludeScrollbars(frame_view->Size());
return LayoutUnit(containing_block->IsHorizontalWritingMode()
? viewport_size.height()
: viewport_size.width());
}
}
if (containing_block->IsBox())
return To<LayoutBox>(containing_block)->ClientLogicalHeight();
DCHECK(containing_block->IsLayoutInline());
DCHECK(containing_block->CanContainOutOfFlowPositionedElement(
StyleRef().GetPosition()));
const auto* flow = To<LayoutInline>(containing_block);
// If the containing block is empty, return a height of 0.
if (!flow->HasInlineFragments())
return LayoutUnit();
LayoutUnit height_result;
auto bounding_box_size = flow->PhysicalLinesBoundingBox().size;
if (containing_block->IsHorizontalWritingMode())
height_result = bounding_box_size.height;
else
height_result = bounding_box_size.width;
height_result -= (containing_block->BorderBlockStart() +
containing_block->BorderBlockEnd());
return height_result;
}
PhysicalRect LayoutBox::LocalCaretRect(
int caret_offset,
LayoutUnit* extra_width_to_end_of_line) const {
NOT_DESTROYED();
// VisiblePositions at offsets inside containers either a) refer to the
// positions before/after those containers (tables and select elements) or
// b) refer to the position inside an empty block.
// They never refer to children.
// FIXME: Paint the carets inside empty blocks differently than the carets
// before/after elements.
LayoutUnit caret_width = GetFrameView()->CaretWidth();
LogicalSize size(LogicalWidth(), LogicalHeight());
const bool is_horizontal = IsHorizontalWritingMode();
PhysicalOffset offset = PhysicalLocation();
PhysicalRect rect(offset, is_horizontal
? PhysicalSize(caret_width, size.block_size)
: PhysicalSize(size.block_size, caret_width));
bool ltr = StyleRef().IsLeftToRightDirection();
if ((!caret_offset) ^ ltr) {
rect.Move(
is_horizontal
? PhysicalOffset(size.inline_size - caret_width, LayoutUnit())
: PhysicalOffset(LayoutUnit(), size.inline_size - caret_width));
}
// If height of box is smaller than font height, use the latter one,
// otherwise the caret might become invisible.
//
// Also, if the box is not an atomic inline-level element, always use the font
// height. This prevents the "big caret" bug described in:
// <rdar://problem/3777804> Deleting all content in a document can result in
// giant tall-as-window insertion point
//
// FIXME: ignoring :first-line, missing good reason to take care of
const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont();
LayoutUnit font_height =
LayoutUnit(font_data ? font_data->GetFontMetrics().Height() : 0);
if (font_height > size.block_size || (!IsAtomicInlineLevel() && !IsTable())) {
if (is_horizontal) {
rect.SetHeight(font_height);
} else {
rect.SetWidth(font_height);
}
}
if (extra_width_to_end_of_line) {
*extra_width_to_end_of_line =
is_horizontal ? (offset.left + Size().width - rect.Right())
: (offset.top + Size().height - rect.Bottom());
}
// Move to local coords
rect.Move(-offset);
// FIXME: Border/padding should be added for all elements but this workaround
// is needed because we use offsets inside an "atomic" element to represent
// positions before and after the element in deprecated editing offsets.
if (GetNode() &&
!(EditingIgnoresContent(*GetNode()) || IsDisplayInsideTable(GetNode()))) {
rect.SetX(rect.X() + BorderLeft() + PaddingLeft());
rect.SetY(rect.Y() + PaddingTop() + BorderTop());
}
return rect;
}
PositionWithAffinity LayoutBox::PositionForPointInFragments(
const PhysicalOffset& target) const {
NOT_DESTROYED();
DCHECK_GE(GetDocument().Lifecycle().GetState(),
DocumentLifecycle::kPrePaintClean);
DCHECK_GT(PhysicalFragmentCount(), 0u);
if (PhysicalFragmentCount() == 1) {
const PhysicalBoxFragment* fragment = GetPhysicalFragment(0);
return fragment->PositionForPoint(target);
}
// When |this| is block fragmented, find the closest fragment.
const PhysicalBoxFragment* closest_fragment = nullptr;
PhysicalOffset closest_fragment_offset;
LayoutUnit shortest_square_distance = LayoutUnit::Max();
for (const PhysicalBoxFragment& fragment : PhysicalFragments()) {
// If |fragment| contains |target|, call its |PositionForPoint|.
const PhysicalOffset fragment_offset = fragment.OffsetFromOwnerLayoutBox();
const PhysicalSize distance =
PhysicalRect(fragment_offset, fragment.Size()).DistanceAsSize(target);
if (distance.IsZero())
return fragment.PositionForPoint(target - fragment_offset);
// Otherwise find the closest fragment.
const LayoutUnit square_distance =
distance.width * distance.width + distance.height * distance.height;
if (square_distance < shortest_square_distance || !closest_fragment) {
shortest_square_distance = square_distance;
closest_fragment = &fragment;
closest_fragment_offset = fragment_offset;
}
}
DCHECK(closest_fragment);
return closest_fragment->PositionForPoint(target - closest_fragment_offset);
}
DISABLE_CFI_PERF
bool LayoutBox::ShouldBeConsideredAsReplaced() const {
NOT_DESTROYED();
if (IsAtomicInlineLevel())
return true;
// We need to detect all types of objects that should be treated as replaced.
// Callers of this method will use the result for various things, such as
// determining how to size the object, or whether it needs to avoid adjacent
// floats, just like objects that establish a new formatting context.
// IsAtomicInlineLevel() will not catch all the cases. Objects may be
// block-level and still replaced, and we cannot deduce this from the
// LayoutObject type. Checkboxes and radio buttons are such examples. We need
// to check the Element type. This also applies to images, since we may have
// created a block-flow LayoutObject for the ALT text (which still counts as
// replaced).
auto* element = DynamicTo<Element>(GetNode());
if (!element)
return false;
if (element->IsFormControlElement()) {
// Form control elements are generally replaced objects. Fieldsets are not,
// though. A fieldset is (almost) a regular block container, and should be
// treated as such.
return !IsA<HTMLFieldSetElement>(element);
}
return IsA<HTMLImageElement>(element);
}
// Children of LayoutCustom object's are only considered "items" when it has a
// loaded algorithm.
bool LayoutBox::IsCustomItem() const {
NOT_DESTROYED();
auto* parent_layout_box = DynamicTo<LayoutCustom>(Parent());
return parent_layout_box && parent_layout_box->IsLoaded();
}
PhysicalBoxStrut LayoutBox::ComputeVisualEffectOverflowOutsets() {
NOT_DESTROYED();
const ComputedStyle& style = StyleRef();
DCHECK(style.HasVisualOverflowingEffect());
PhysicalBoxStrut outsets = style.BoxDecorationOutsets();
if (style.HasOutline()) {
OutlineInfo info;
Vector<PhysicalRect> outline_rects =
OutlineRects(&info, PhysicalOffset(),
style.OutlineRectsShouldIncludeBlockInkOverflow());
PhysicalRect rect = UnionRect(outline_rects);
bool outline_affected = rect.size != Size();
SetOutlineMayBeAffectedByDescendants(outline_affected);
rect.Inflate(LayoutUnit(OutlinePainter::OutlineOutsetExtent(style, info)));
outsets.Unite(PhysicalBoxStrut(-rect.Y(), rect.Right() - Size().width,
rect.Bottom() - Size().height, -rect.X()));
}
return outsets;
}
bool LayoutBox::HasTopOverflow() const {
NOT_DESTROYED();
return !StyleRef().IsLeftToRightDirection() && !IsHorizontalWritingMode();
}
bool LayoutBox::HasLeftOverflow() const {
NOT_DESTROYED();
if (IsHorizontalWritingMode())
return !StyleRef().IsLeftToRightDirection();
return StyleRef().GetWritingMode() == WritingMode::kVerticalRl;
}
void LayoutBox::SetScrollableOverflowFromLayoutResults() {
NOT_DESTROYED();
ClearSelfNeedsScrollableOverflowRecalc();
ClearChildNeedsScrollableOverflowRecalc();
if (overflow_) {
overflow_->scrollable_overflow.reset();
}
if (IsLayoutReplaced()) {
return;
}
const WritingMode writing_mode = StyleRef().GetWritingMode();
std::optional<PhysicalRect> scrollable_overflow;
LayoutUnit consumed_block_size;
LayoutUnit fragment_width_sum;
// Iterate over all the fragments and unite their individual
// scrollable-overflow to determine the final scrollable-overflow.
for (const auto& layout_result : layout_results_) {
const auto& fragment =
To<PhysicalBoxFragment>(layout_result->GetPhysicalFragment());
// In order to correctly unite the overflow, we need to shift an individual
// fragment's scrollable-overflow by previously consumed block-size so far.
PhysicalOffset offset_adjust;
switch (writing_mode) {
case WritingMode::kHorizontalTb:
offset_adjust = {LayoutUnit(), consumed_block_size};
break;
case WritingMode::kVerticalRl:
case WritingMode::kSidewaysRl:
// For flipped-blocks writing-modes, we build the total overflow rect
// from right-to-left (adding with negative offsets). At the end we
// need to make the origin relative to the LHS, so we add the total
// fragment width.
fragment_width_sum += fragment.Size().width;
offset_adjust = {-fragment.Size().width - consumed_block_size,
LayoutUnit()};
break;
case WritingMode::kVerticalLr:
case WritingMode::kSidewaysLr:
offset_adjust = {consumed_block_size, LayoutUnit()};
break;
default:
NOTREACHED();
break;
}
PhysicalRect fragment_scrollable_overflow = fragment.ScrollableOverflow();
fragment_scrollable_overflow.offset += offset_adjust;
// If we are the first fragment just set the scrollable-overflow.
if (!scrollable_overflow) {
scrollable_overflow = fragment_scrollable_overflow;
} else {
scrollable_overflow->UniteEvenIfEmpty(fragment_scrollable_overflow);
}
if (const auto* break_token = fragment.GetBreakToken()) {
// The legacy engine doesn't understand our concept of repeated
// fragments. Stop now. The overflow rectangle will represent the
// fragment(s) generated under the first repeated root.
if (break_token->IsRepeated())
break;
consumed_block_size = break_token->ConsumedBlockSize();
}
}
if (!scrollable_overflow) {
return;
}
if (IsFlippedBlocksWritingMode(writing_mode)) {
scrollable_overflow->offset.left += fragment_width_sum;
}
if (scrollable_overflow->IsEmpty() ||
PhysicalPaddingBoxRect().Contains(*scrollable_overflow)) {
return;
}
DCHECK(!ScrollableOverflowIsSet());
if (!overflow_)
overflow_ = MakeGarbageCollected<BoxOverflowModel>();
overflow_->scrollable_overflow.emplace(*scrollable_overflow);
}
RecalcScrollableOverflowResult LayoutBox::RecalcScrollableOverflowNG() {
NOT_DESTROYED();
RecalcScrollableOverflowResult child_result;
// Don't attempt to rebuild the fragment tree or recalculate
// scrollable-overflow, layout will do this for us.
if (NeedsLayout())
return RecalcScrollableOverflowResult();
if (ChildNeedsScrollableOverflowRecalc()) {
child_result = RecalcChildScrollableOverflowNG();
}
bool should_recalculate_scrollable_overflow =
SelfNeedsScrollableOverflowRecalc() ||
child_result.scrollable_overflow_changed;
bool rebuild_fragment_tree = child_result.rebuild_fragment_tree;
bool scrollable_overflow_changed = false;
if (rebuild_fragment_tree || should_recalculate_scrollable_overflow) {
for (auto& layout_result : layout_results_) {
const auto& fragment =
To<PhysicalBoxFragment>(layout_result->GetPhysicalFragment());
std::optional<PhysicalRect> scrollable_overflow;
// Recalculate our scrollable-overflow if a child had its
// scrollable-overflow changed, or if we are marked as dirty.
if (should_recalculate_scrollable_overflow) {
const PhysicalRect old_scrollable_overflow =
fragment.ScrollableOverflow();
const bool has_block_fragmentation =
layout_result->GetConstraintSpaceForCaching()
.HasBlockFragmentation();
#if DCHECK_IS_ON()
PhysicalBoxFragment::AllowPostLayoutScope allow_post_layout_scope;
#endif
const PhysicalRect new_scrollable_overflow =
ScrollableOverflowCalculator::
RecalculateScrollableOverflowForFragment(
fragment, has_block_fragmentation);
// Set the appropriate flags if the scrollable-overflow changed.
if (old_scrollable_overflow != new_scrollable_overflow) {
scrollable_overflow = new_scrollable_overflow;
scrollable_overflow_changed = true;
rebuild_fragment_tree = true;
}
}
if (scrollable_overflow) {
fragment.GetMutableForStyleRecalc().SetScrollableOverflow(
*scrollable_overflow);
}
}
SetScrollableOverflowFromLayoutResults();
}
if (scrollable_overflow_changed && IsScrollContainer()) {
Layer()->GetScrollableArea()->UpdateAfterOverflowRecalc();
}
// Only indicate to our parent that our scrollable overflow changed if we
// have:
// - No layout containment applied.
// - No clipping (in both axes).
scrollable_overflow_changed = scrollable_overflow_changed &&
!ShouldApplyLayoutContainment() &&
!ShouldClipOverflowAlongBothAxis();
return {scrollable_overflow_changed, rebuild_fragment_tree};
}
RecalcScrollableOverflowResult LayoutBox::RecalcChildScrollableOverflowNG() {
NOT_DESTROYED();
DCHECK(ChildNeedsScrollableOverflowRecalc());
ClearChildNeedsScrollableOverflowRecalc();
#if DCHECK_IS_ON()
// We use PostLayout methods to navigate the fragment tree and reach the
// corresponding LayoutObjects, so we need to use AllowPostLayoutScope here.
PhysicalBoxFragment::AllowPostLayoutScope allow_post_layout_scope;
#endif
RecalcScrollableOverflowResult result;
for (auto& layout_result : layout_results_) {
const auto& fragment =
To<PhysicalBoxFragment>(layout_result->GetPhysicalFragment());
if (fragment.HasItems()) {
for (InlineCursor cursor(fragment); cursor; cursor.MoveToNext()) {
const PhysicalBoxFragment* child =
cursor.Current()->PostLayoutBoxFragment();
if (!child || !child->GetLayoutObject()->IsBox())
continue;
result.Unite(
child->MutableOwnerLayoutBox()->RecalcScrollableOverflow());
}
}
RecalcFragmentScrollableOverflow(result, fragment);
}
return result;
}
void LayoutBox::AddSelfVisualOverflow(const PhysicalRect& rect) {
NOT_DESTROYED();
if (rect.IsEmpty())
return;
PhysicalRect border_box = PhysicalBorderBoxRect();
if (border_box.Contains(rect))
return;
if (!VisualOverflowIsSet()) {
if (!overflow_)
overflow_ = MakeGarbageCollected<BoxOverflowModel>();
overflow_->visual_overflow.emplace(border_box);
}
overflow_->visual_overflow->AddSelfVisualOverflow(rect);
}
void LayoutBox::AddContentsVisualOverflow(const PhysicalRect& rect) {
NOT_DESTROYED();
if (rect.IsEmpty())
return;
// If hasOverflowClip() we always save contents visual overflow because we
// need it
// e.g. to determine whether to apply rounded corner clip on contents.
// Otherwise we save contents visual overflow only if it overflows the border
// box.
PhysicalRect border_box = PhysicalBorderBoxRect();
if (!HasNonVisibleOverflow() && border_box.Contains(rect))
return;
if (!VisualOverflowIsSet()) {
if (!overflow_)
overflow_ = MakeGarbageCollected<BoxOverflowModel>();
overflow_->visual_overflow.emplace(border_box);
}
overflow_->visual_overflow->AddContentsVisualOverflow(rect);
}
void LayoutBox::UpdateHasSubpixelVisualEffectOutsets(
const PhysicalBoxStrut& outsets) {
if (!VisualOverflowIsSet()) {
return;
}
overflow_->visual_overflow->SetHasSubpixelVisualEffectOutsets(
!IsIntegerValue(outsets.top) || !IsIntegerValue(outsets.right) ||
!IsIntegerValue(outsets.bottom) || !IsIntegerValue(outsets.left));
}
void LayoutBox::SetVisualOverflow(const PhysicalRect& self,
const PhysicalRect& contents) {
ClearVisualOverflow();
AddSelfVisualOverflow(self);
AddContentsVisualOverflow(contents);
if (!VisualOverflowIsSet())
return;
const PhysicalRect overflow_rect =
overflow_->visual_overflow->SelfVisualOverflowRect();
const PhysicalSize box_size = Size();
const PhysicalBoxStrut outsets(
-overflow_rect.Y(), overflow_rect.Right() - box_size.width,
overflow_rect.Bottom() - box_size.height, -overflow_rect.X());
UpdateHasSubpixelVisualEffectOutsets(outsets);
// |OutlineMayBeAffectedByDescendants| is set whenever outline style
// changes. Update to the actual value here.
const ComputedStyle& style = StyleRef();
if (style.HasOutline()) {
const LayoutUnit outline_extent(OutlinePainter::OutlineOutsetExtent(
style, OutlineInfo::GetFromStyle(style)));
SetOutlineMayBeAffectedByDescendants(
outsets.top != outline_extent || outsets.right != outline_extent ||
outsets.bottom != outline_extent || outsets.left != outline_extent);
}
}
void LayoutBox::ClearVisualOverflow() {
NOT_DESTROYED();
if (overflow_)
overflow_->visual_overflow.reset();
// overflow_ will be reset by MutableForPainting::ClearPreviousOverflowData()
// if we don't need it to store previous overflow data.
}
bool LayoutBox::CanUseFragmentsForVisualOverflow() const {
NOT_DESTROYED();
// TODO(crbug.com/1144203): Legacy, or no-fragments-objects such as
// table-column. What to do with them is TBD.
if (!PhysicalFragmentCount())
return false;
const PhysicalBoxFragment& fragment = *GetPhysicalFragment(0);
if (!fragment.CanUseFragmentsForInkOverflow())
return false;
return true;
}
// Copy visual overflow from |PhysicalFragments()|.
void LayoutBox::CopyVisualOverflowFromFragments() {
NOT_DESTROYED();
DCHECK(CanUseFragmentsForVisualOverflow());
const PhysicalRect previous_visual_overflow =
VisualOverflowRectAllowingUnset();
CopyVisualOverflowFromFragmentsWithoutInvalidations();
const PhysicalRect visual_overflow = VisualOverflowRect();
if (visual_overflow == previous_visual_overflow)
return;
if (!RuntimeEnabledFeatures::IntersectionOptimizationEnabled()) {
DeprecatedInvalidateIntersectionObserverCachedRects();
}
SetShouldCheckForPaintInvalidation();
}
void LayoutBox::CopyVisualOverflowFromFragmentsWithoutInvalidations() {
NOT_DESTROYED();
DCHECK(CanUseFragmentsForVisualOverflow());
if (UNLIKELY(!PhysicalFragmentCount())) {
DCHECK(IsLayoutTableCol());
ClearVisualOverflow();
return;
}
if (PhysicalFragmentCount() == 1) {
const PhysicalBoxFragment& fragment = *GetPhysicalFragment(0);
DCHECK(fragment.CanUseFragmentsForInkOverflow());
if (!fragment.HasInkOverflow()) {
ClearVisualOverflow();
return;
}
SetVisualOverflow(fragment.SelfInkOverflowRect(),
fragment.ContentsInkOverflowRect());
return;
}
// When block-fragmented, stitch visual overflows from all fragments.
const LayoutBlock* cb = ContainingBlock();
DCHECK(cb);
const WritingMode writing_mode = cb->StyleRef().GetWritingMode();
bool has_overflow = false;
PhysicalRect self_rect;
PhysicalRect contents_rect;
const PhysicalBoxFragment* last_fragment = nullptr;
for (const PhysicalBoxFragment& fragment : PhysicalFragments()) {
DCHECK(fragment.CanUseFragmentsForInkOverflow());
if (!fragment.HasInkOverflow()) {
last_fragment = &fragment;
continue;
}
has_overflow = true;
PhysicalRect fragment_self_rect = fragment.SelfInkOverflowRect();
PhysicalRect fragment_contents_rect = fragment.ContentsInkOverflowRect();
// Stitch this fragment to the bottom of the last one in horizontal
// writing mode, or to the right in vertical. Flipped blocks is handled
// later, after the loop.
if (last_fragment) {
const BlockBreakToken* break_token = last_fragment->GetBreakToken();
DCHECK(break_token);
const LayoutUnit block_offset = break_token->ConsumedBlockSize();
if (blink::IsHorizontalWritingMode(writing_mode)) {
fragment_self_rect.offset.top += block_offset;
fragment_contents_rect.offset.top += block_offset;
} else {
fragment_self_rect.offset.left += block_offset;
fragment_contents_rect.offset.left += block_offset;
}
}
last_fragment = &fragment;
self_rect.Unite(fragment_self_rect);
contents_rect.Unite(fragment_contents_rect);
// The legacy engine doesn't understand our concept of repeated
// fragments. Stop now. The overflow rectangle will represent the
// fragment(s) generated under the first repeated root.
if (fragment.GetBreakToken() && fragment.GetBreakToken()->IsRepeated()) {
break;
}
}
if (!has_overflow) {
ClearVisualOverflow();
return;
}
SetVisualOverflow(self_rect, contents_rect);
}
DISABLE_CFI_PERF
bool LayoutBox::HasUnsplittableScrollingOverflow() const {
NOT_DESTROYED();
// Fragmenting scrollbars is only problematic in interactive media, e.g.
// multicol on a screen. If we're printing, which is non-interactive media, we
// should allow objects with non-visible overflow to be paginated as normally.
if (GetDocument().Printing())
return false;
// Treat any scrollable container as monolithic.
return IsScrollContainer();
}
bool LayoutBox::IsMonolithic() const {
NOT_DESTROYED();
// TODO(almaher): Don't consider a writing mode root monolitic if
// IsFlexibleBox(). The breakability should be handled at the item
// level. (Likely same for Table and Grid).
if (ShouldBeConsideredAsReplaced() || HasUnsplittableScrollingOverflow() ||
(Parent() && IsWritingModeRoot()) ||
(IsFixedPositioned() && GetDocument().Printing() &&
IsA<LayoutView>(Container())) ||
ShouldApplySizeContainment() || IsFrameSet() ||
StyleRef().HasLineClamp()) {
return true;
}
return false;
}
LayoutUnit LayoutBox::FirstLineHeight() const {
if (IsAtomicInlineLevel()) {
return FirstLineStyle()->IsHorizontalWritingMode()
? MarginHeight() + Size().height
: MarginWidth() + Size().width;
}
return LayoutUnit();
}
PhysicalBoxStrut LayoutBox::BorderOutsetsForClipping() const {
auto padding_box = -BorderOutsets();
if (!ShouldApplyOverflowClipMargin())
return padding_box;
PhysicalBoxStrut overflow_clip_margin;
switch (StyleRef().OverflowClipMargin()->GetReferenceBox()) {
case StyleOverflowClipMargin::ReferenceBox::kBorderBox:
break;
case StyleOverflowClipMargin::ReferenceBox::kPaddingBox:
overflow_clip_margin = padding_box;
break;
case StyleOverflowClipMargin::ReferenceBox::kContentBox:
overflow_clip_margin = padding_box - PaddingOutsets();
break;
}
return overflow_clip_margin.Inflate(
StyleRef().OverflowClipMargin()->GetMargin());
}
PhysicalRect LayoutBox::VisualOverflowRect() const {
NOT_DESTROYED();
DCHECK(!IsLayoutMultiColumnSet());
if (!VisualOverflowIsSet())
return PhysicalBorderBoxRect();
const PhysicalRect& self_visual_overflow_rect =
overflow_->visual_overflow->SelfVisualOverflowRect();
if (HasMask()) {
return self_visual_overflow_rect;
}
const OverflowClipAxes overflow_clip_axes = GetOverflowClipAxes();
if (ShouldApplyOverflowClipMargin()) {
// We should apply overflow clip margin only if we clip overflow on both
// axis.
DCHECK_EQ(overflow_clip_axes, kOverflowClipBothAxis);
const PhysicalRect& contents_visual_overflow_rect =
overflow_->visual_overflow->ContentsVisualOverflowRect();
if (!contents_visual_overflow_rect.IsEmpty()) {
PhysicalRect result = PhysicalBorderBoxRect();
PhysicalBoxStrut outsets = BorderOutsetsForClipping();
result.ExpandEdges(outsets.top, outsets.right, outsets.bottom,
outsets.left);
result.Intersect(contents_visual_overflow_rect);
result.Unite(self_visual_overflow_rect);
return result;
}
}
if (overflow_clip_axes == kOverflowClipBothAxis)
return self_visual_overflow_rect;
PhysicalRect result =
overflow_->visual_overflow->ContentsVisualOverflowRect();
result.Unite(self_visual_overflow_rect);
ApplyOverflowClip(overflow_clip_axes, self_visual_overflow_rect, result);
return result;
}
#if DCHECK_IS_ON()
PhysicalRect LayoutBox::VisualOverflowRectAllowingUnset() const {
NOT_DESTROYED();
InkOverflow::ReadUnsetAsNoneScope read_unset_as_none;
return VisualOverflowRect();
}
void LayoutBox::CheckIsVisualOverflowComputed() const {
// TODO(crbug.com/1205708): There are still too many failures. Disable the
// the check for now. Need to investigate the reason.
return;
/*
if (InkOverflow::ReadUnsetAsNoneScope::IsActive())
return;
if (!CanUseFragmentsForVisualOverflow())
return;
// TODO(crbug.com/1203402): MathML needs some more work.
if (IsMathML())
return;
for (const PhysicalBoxFragment& fragment : PhysicalFragments())
DCHECK(fragment.IsInkOverflowComputed());
*/
}
#endif
PhysicalOffset LayoutBox::OffsetPoint(const Element* parent) const {
NOT_DESTROYED();
return AdjustedPositionRelativeTo(PhysicalLocation(), parent);
}
LayoutUnit LayoutBox::OffsetLeft(const Element* parent) const {
NOT_DESTROYED();
return OffsetPoint(parent).left;
}
LayoutUnit LayoutBox::OffsetTop(const Element* parent) const {
NOT_DESTROYED();
return OffsetPoint(parent).top;
}
PhysicalSize LayoutBox::Size() const {
NOT_DESTROYED();
if (!HasValidCachedGeometry()) {
// const_cast in order to update the cached value.
const_cast<LayoutBox*>(this)->SetHasValidCachedGeometry(true);
const_cast<LayoutBox*>(this)->frame_size_ = ComputeSize();
}
return frame_size_;
}
PhysicalSize LayoutBox::ComputeSize() const {
NOT_DESTROYED();
const auto& results = GetLayoutResults();
if (results.size() == 0) {
return PhysicalSize();
}
const auto& first_fragment = results[0]->GetPhysicalFragment();
if (results.size() == 1u) {
return first_fragment.Size();
}
WritingModeConverter converter(first_fragment.Style().GetWritingDirection());
const BlockBreakToken* previous_break_token = nullptr;
LogicalSize size;
for (const auto& result : results) {
const auto& physical_fragment =
To<PhysicalBoxFragment>(result->GetPhysicalFragment());
LogicalSize fragment_logical_size =
converter.ToLogical(physical_fragment.Size());
if (physical_fragment.IsFirstForNode()) {
// Inline-size will only be set at the first fragment. Subsequent
// fragments may have different inline-size (either because fragmentainer
// inline-size is variable, or e.g. because available inline-size is
// affected by floats). The legacy engine doesn't handle variable
// inline-size (since it doesn't really understand fragmentation). This
// means that things like offsetWidth won't work correctly (since that's
// still being handled by the legacy engine), but at least layout,
// painting and hit-testing will be correct.
size = fragment_logical_size;
} else {
DCHECK(previous_break_token);
size.block_size = fragment_logical_size.block_size +
previous_break_token->ConsumedBlockSizeForLegacy();
}
previous_break_token = physical_fragment.GetBreakToken();
// Continue in order to update logical height, unless this fragment is
// past the block-end of the generating node (happens with overflow) or
// is a repeated one.
if (!previous_break_token || previous_break_token->IsRepeated() ||
previous_break_token->IsAtBlockEnd()) {
break;
}
}
return converter.ToPhysical(size);
}
LayoutBox* LayoutBox::LocationContainer() const {
NOT_DESTROYED();
// Location of a non-root SVG object derived from LayoutBox should not be
// affected by writing-mode of the containing box (SVGRoot).
if (IsSVGChild())
return nullptr;
// Normally the box's location is relative to its containing box.
LayoutObject* container = Container();
while (container && !container->IsBox())
container = container->Container();
return To<LayoutBox>(container);
}
ShapeOutsideInfo* LayoutBox::GetShapeOutsideInfo() const {
NOT_DESTROYED();
return ShapeOutsideInfo::Info(*this);
}
CustomLayoutChild* LayoutBox::GetCustomLayoutChild() const {
NOT_DESTROYED();
DCHECK(rare_data_);
DCHECK(rare_data_->layout_child_);
return rare_data_->layout_child_.Get();
}
void LayoutBox::AddCustomLayoutChildIfNeeded() {
NOT_DESTROYED();
if (!IsCustomItem())
return;
const AtomicString& name = Parent()->StyleRef().DisplayLayoutCustomName();
LayoutWorklet* worklet = LayoutWorklet::From(*GetDocument().domWindow());
const CSSLayoutDefinition* definition =
worklet->Proxy()->FindDefinition(name);
// If there isn't a definition yet, the web developer defined layout isn't
// loaded yet (or is invalid). The layout tree will get re-attached when
// loaded, so don't bother creating a script representation of this node yet.
if (!definition)
return;
EnsureRareData().layout_child_ =
MakeGarbageCollected<CustomLayoutChild>(*definition, BlockNode(this));
}
void LayoutBox::ClearCustomLayoutChild() {
NOT_DESTROYED();
if (!rare_data_)
return;
if (rare_data_->layout_child_)
rare_data_->layout_child_->ClearLayoutNode();
rare_data_->layout_child_ = nullptr;
}
PhysicalRect LayoutBox::DebugRect() const {
NOT_DESTROYED();
return PhysicalRect(PhysicalLocation(), Size());
}
OverflowClipAxes LayoutBox::ComputeOverflowClipAxes() const {
NOT_DESTROYED();
if (ShouldApplyPaintContainment() || HasControlClip())
return kOverflowClipBothAxis;
if (!RespectsCSSOverflow() || !HasNonVisibleOverflow())
return kNoOverflowClip;
if (IsScrollContainer())
return kOverflowClipBothAxis;
return (StyleRef().OverflowX() == EOverflow::kVisible ? kNoOverflowClip
: kOverflowClipX) |
(StyleRef().OverflowY() == EOverflow::kVisible ? kNoOverflowClip
: kOverflowClipY);
}
void LayoutBox::MutableForPainting::SavePreviousOverflowData() {
if (!GetLayoutBox().overflow_)
GetLayoutBox().overflow_ = MakeGarbageCollected<BoxOverflowModel>();
auto& previous_overflow = GetLayoutBox().overflow_->previous_overflow_data;
if (!previous_overflow)
previous_overflow.emplace();
previous_overflow->previous_scrollable_overflow_rect =
GetLayoutBox().ScrollableOverflowRect();
previous_overflow->previous_visual_overflow_rect =
GetLayoutBox().VisualOverflowRect();
previous_overflow->previous_self_visual_overflow_rect =
GetLayoutBox().SelfVisualOverflowRect();
}
void LayoutBox::MutableForPainting::SetPreviousGeometryForLayoutShiftTracking(
const PhysicalOffset& paint_offset,
const PhysicalSize& size,
const PhysicalRect& visual_overflow_rect) {
FirstFragment().SetPaintOffset(paint_offset);
GetLayoutBox().previous_size_ = size;
if (PhysicalRect(PhysicalOffset(), size).Contains(visual_overflow_rect))
return;
if (!GetLayoutBox().overflow_)
GetLayoutBox().overflow_ = MakeGarbageCollected<BoxOverflowModel>();
auto& previous_overflow = GetLayoutBox().overflow_->previous_overflow_data;
if (!previous_overflow)
previous_overflow.emplace();
previous_overflow->previous_visual_overflow_rect = visual_overflow_rect;
// Other previous rects don't matter because they are used for paint
// invalidation and we always do full paint invalidation on reattachment.
}
void LayoutBox::MutableForPainting::UpdateBackgroundPaintLocation() {
GetLayoutBox().SetBackgroundPaintLocation(
GetLayoutBox().ComputeBackgroundPaintLocation());
}
RasterEffectOutset LayoutBox::VisualRectOutsetForRasterEffects() const {
NOT_DESTROYED();
// If the box has subpixel visual effect outsets, as the visual effect may be
// painted along the pixel-snapped border box, the pixels on the anti-aliased
// edge of the effect may overflow the calculated visual rect. Expand visual
// rect by one pixel in the case.
return VisualOverflowIsSet() &&
overflow_->visual_overflow->HasSubpixelVisualEffectOutsets()
? RasterEffectOutset::kWholePixel
: RasterEffectOutset::kNone;
}
TextDirection LayoutBox::ResolvedDirection() const {
NOT_DESTROYED();
if (IsInline() && IsAtomicInlineLevel() &&
IsInLayoutNGInlineFormattingContext()) {
InlineCursor cursor;
cursor.MoveTo(*this);
if (cursor) {
return cursor.Current().ResolvedDirection();
}
}
return StyleRef().Direction();
}
bool LayoutBox::NeedsScrollNode(
CompositingReasons direct_compositing_reasons) const {
NOT_DESTROYED();
if (!IsScrollContainer())
return false;
if (direct_compositing_reasons & CompositingReason::kRootScroller)
return true;
return GetScrollableArea()->ScrollsOverflow();
}
bool LayoutBox::UsesCompositedScrolling() const {
NOT_DESTROYED();
const auto* properties = FirstFragment().PaintProperties();
if (!properties || !properties->Scroll()) {
return false;
}
const auto* paint_artifact_compositor =
GetFrameView()->GetPaintArtifactCompositor();
return paint_artifact_compositor &&
paint_artifact_compositor->UsesCompositedScrolling(
*properties->Scroll());
}
void LayoutBox::OverrideTickmarks(Vector<gfx::Rect> tickmarks) {
NOT_DESTROYED();
GetScrollableArea()->SetTickmarksOverride(std::move(tickmarks));
InvalidatePaintForTickmarks();
}
void LayoutBox::InvalidatePaintForTickmarks() {
NOT_DESTROYED();
ScrollableArea* scrollable_area = GetScrollableArea();
if (!scrollable_area)
return;
Scrollbar* scrollbar = scrollable_area->VerticalScrollbar();
if (!scrollbar)
return;
scrollbar->SetNeedsPaintInvalidation(static_cast<ScrollbarPart>(~kThumbPart));
}
static bool HasInsetBoxShadow(const ComputedStyle& style) {
if (!style.BoxShadow())
return false;
for (const ShadowData& shadow : style.BoxShadow()->Shadows()) {
if (shadow.Style() == ShadowStyle::kInset)
return true;
}
return false;
}
// If all borders and scrollbars are opaque, then background-clip: border-box
// is equivalent to background-clip: padding-box.
bool LayoutBox::BackgroundClipBorderBoxIsEquivalentToPaddingBox() const {
const auto* scrollable_area = GetScrollableArea();
if (scrollable_area) {
if (auto* scrollbar = scrollable_area->HorizontalScrollbar()) {
if (!scrollbar->IsOverlayScrollbar() && !scrollbar->IsOpaque()) {
return false;
}
}
if (auto* scrollbar = scrollable_area->VerticalScrollbar()) {
if (!scrollbar->IsOverlayScrollbar() && !scrollbar->IsOpaque()) {
return false;
}
}
}
if (StyleRef().BorderTopWidth() &&
(!ResolveColor(GetCSSPropertyBorderTopColor()).IsOpaque() ||
StyleRef().BorderTopStyle() != EBorderStyle::kSolid)) {
return false;
}
if (StyleRef().BorderRightWidth() &&
(!ResolveColor(GetCSSPropertyBorderRightColor()).IsOpaque() ||
StyleRef().BorderRightStyle() != EBorderStyle::kSolid)) {
return false;
}
if (StyleRef().BorderBottomWidth() &&
(!ResolveColor(GetCSSPropertyBorderBottomColor()).IsOpaque() ||
StyleRef().BorderBottomStyle() != EBorderStyle::kSolid)) {
return false;
}
if (StyleRef().BorderLeftWidth() &&
(!ResolveColor(GetCSSPropertyBorderLeftColor()).IsOpaque() ||
StyleRef().BorderLeftStyle() != EBorderStyle::kSolid)) {
return false;
}
if (!StyleRef().IsScrollbarGutterAuto()) {
return false;
}
return true;
}
BackgroundPaintLocation LayoutBox::ComputeBackgroundPaintLocation() const {
NOT_DESTROYED();
bool may_have_scrolling_layers_without_scrolling = IsA<LayoutView>(this);
const auto* scrollable_area = GetScrollableArea();
bool scrolls_overflow = scrollable_area && scrollable_area->ScrollsOverflow();
if (!scrolls_overflow && !may_have_scrolling_layers_without_scrolling)
return kBackgroundPaintInBorderBoxSpace;
// If we care about LCD text, paint root backgrounds into scrolling contents
// layer even if style suggests otherwise. (For non-root scrollers, we just
// avoid compositing - see PLSA::ComputeNeedsCompositedScrolling.)
if (IsA<LayoutView>(this) &&
GetDocument().GetSettings()->GetLCDTextPreference() ==
LCDTextPreference::kStronglyPreferred) {
return kBackgroundPaintInContentsSpace;
}
// Inset box shadow is painted in the scrolling area above the background, and
// it doesn't scroll, so the background can only be painted in the main layer.
if (HasInsetBoxShadow(StyleRef()))
return kBackgroundPaintInBorderBoxSpace;
// For simplicity, assume any border image can have inset, like the above.
if (StyleRef().BorderImage().GetImage()) {
return kBackgroundPaintInBorderBoxSpace;
}
// Assume optimistically that the background can be painted in the scrolling
// contents until we find otherwise.
BackgroundPaintLocation paint_location = kBackgroundPaintInContentsSpace;
Color background_color = ResolveColor(GetCSSPropertyBackgroundColor());
const FillLayer* layer = &(StyleRef().BackgroundLayers());
for (; layer; layer = layer->Next()) {
if (layer->Attachment() == EFillAttachment::kLocal)
continue;
// The background color is either the only background or it's the
// bottommost value from the background property (see final-bg-layer in
// https://drafts.csswg.org/css-backgrounds/#the-background).
if (!layer->GetImage() && !layer->Next() &&
!background_color.IsFullyTransparent() &&
StyleRef().IsScrollbarGutterAuto()) {
// Solid color layers with an effective background clip of the padding box
// can be treated as local.
EFillBox clip = layer->Clip();
if (clip == EFillBox::kPadding)
continue;
// A border box can be treated as a padding box if the border is opaque or
// there is no border and we don't have custom scrollbars.
if (clip == EFillBox::kBorder) {
if (BackgroundClipBorderBoxIsEquivalentToPaddingBox())
continue;
// If we have an opaque background color, we can safely paint it into
// both the scrolling contents layer and the graphics layer to preserve
// LCD text. The background color is either the only background or
// behind background-attachment:local images (ensured by previous
// iterations of the loop). For the latter case, the first paint of the
// images doesn't matter because it will be covered by the second paint
// of the opaque color.
if (background_color.IsOpaque()) {
paint_location = kBackgroundPaintInBothSpaces;
continue;
}
} else if (clip == EFillBox::kContent &&
StyleRef().PaddingTop().IsZero() &&
StyleRef().PaddingLeft().IsZero() &&
StyleRef().PaddingRight().IsZero() &&
StyleRef().PaddingBottom().IsZero()) {
// A content fill box can be treated as a padding fill box if there is
// no padding.
continue;
}
}
return kBackgroundPaintInBorderBoxSpace;
}
// It can't paint in the scrolling contents because it has different 3d
// context than the scrolling contents.
if (!StyleRef().Preserves3D() && Parent() &&
Parent()->StyleRef().Preserves3D()) {
return kBackgroundPaintInBorderBoxSpace;
}
return paint_location;
}
bool LayoutBox::ComputeCanCompositeBackgroundAttachmentFixed() const {
NOT_DESTROYED();
DCHECK(IsBackgroundAttachmentFixedObject());
if (GetDocument().GetSettings()->GetLCDTextPreference() ==
LCDTextPreference::kStronglyPreferred) {
return false;
}
// The fixed attachment background must be the only background layer.
if (StyleRef().BackgroundLayers().Next() ||
StyleRef().BackgroundLayers().Clip() == EFillBox::kText) {
return false;
}
// To support box shadow, we'll need to paint the outset and inset box
// shadows in separate display items in case there are outset box shadow,
// background, inset box shadow and border in paint order.
if (StyleRef().BoxShadow()) {
return false;
}
// The theme may paint the background differently for an appearance.
if (StyleRef().HasEffectiveAppearance()) {
return false;
}
// For now the BackgroundClip paint property node doesn't support rounded
// corners. If we want to support this, we need to ensure
// - there is no obvious bleeding issues, and
// - both the fast path and the slow path of composited rounded clip work.
if (StyleRef().HasBorderRadius()) {
return false;
}
return true;
}
bool LayoutBox::IsFixedToView(
const LayoutObject* container_for_fixed_position) const {
if (!IsFixedPositioned())
return false;
const auto* container = container_for_fixed_position;
if (!container)
container = Container();
else
DCHECK_EQ(container, Container());
return container->IsLayoutView();
}
PhysicalRect LayoutBox::ComputeStickyConstrainingRect() const {
NOT_DESTROYED();
DCHECK(IsScrollContainer());
PhysicalRect constraining_rect(OverflowClipRect(PhysicalOffset()));
constraining_rect.Move(PhysicalOffset(-BorderLeft() + PaddingLeft(),
-BorderTop() + PaddingTop()));
constraining_rect.ContractEdges(LayoutUnit(), PaddingLeft() + PaddingRight(),
PaddingTop() + PaddingBottom(), LayoutUnit());
return constraining_rect;
}
AnchorPositionScrollData* LayoutBox::GetAnchorPositionScrollData() const {
if (Element* element = DynamicTo<Element>(GetNode())) {
return element->GetAnchorPositionScrollData();
}
return nullptr;
}
bool LayoutBox::NeedsAnchorPositionScrollAdjustment() const {
if (auto* data = GetAnchorPositionScrollData()) {
return data->NeedsScrollAdjustment();
}
return false;
}
bool LayoutBox::AnchorPositionScrollAdjustmentAfectedByViewportScrolling()
const {
if (auto* data = GetAnchorPositionScrollData()) {
return data->NeedsScrollAdjustment() &&
data->IsAffectedByViewportScrolling();
}
return false;
}
PhysicalOffset LayoutBox::AnchorPositionScrollTranslationOffset() const {
if (auto* data = GetAnchorPositionScrollData()) {
return data->TranslationAsPhysicalOffset();
}
return PhysicalOffset();
}
namespace {
template <typename Function>
void ForEachAnchorQueryOnContainer(const LayoutBox& box, Function func) {
const LayoutObject* container = box.Container();
if (container->IsLayoutBlock()) {
for (const PhysicalBoxFragment& fragment :
To<LayoutBlock>(container)->PhysicalFragments()) {
if (const PhysicalAnchorQuery* anchor_query = fragment.AnchorQuery()) {
func(*anchor_query);
}
}
return;
}
// Now the container is an inline box that's also an abspos containing block.
CHECK(container->IsLayoutInline());
const LayoutInline* inline_container = To<LayoutInline>(container);
if (!inline_container->HasInlineFragments()) {
return;
}
InlineCursor cursor;
cursor.MoveTo(*container);
for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
if (const PhysicalBoxFragment* fragment = cursor.Current().BoxFragment()) {
if (const PhysicalAnchorQuery* anchor_query = fragment->AnchorQuery()) {
func(*anchor_query);
}
}
}
}
#if EXPENSIVE_DCHECKS_ARE_ON()
template <typename Function>
void AssertSameDataOnLayoutResults(
const LayoutBox::LayoutResultList& layout_results,
Function func) {
// When an out-of-flow box is fragmented, the position fallback results on all
// fragments should be the same.
for (wtf_size_t i = 1; i < layout_results.size(); ++i) {
DCHECK(func(layout_results[i]) == func(layout_results[i - 1]));
}
}
#endif
} // namespace
const LayoutObject* LayoutBox::FindTargetAnchor(
const ScopedCSSName& anchor_name) const {
if (!IsOutOfFlowPositioned()) {
return nullptr;
}
// Go through the already built PhysicalAnchorQuery to avoid tree traversal.
const LayoutObject* anchor = nullptr;
auto search_for_anchor = [&](const PhysicalAnchorQuery& anchor_query) {
if (const LayoutObject* current =
anchor_query.AnchorLayoutObject(*this, &anchor_name)) {
if (!anchor ||
(anchor != current && anchor->IsBeforeInPreOrder(*current))) {
anchor = current;
}
}
};
ForEachAnchorQueryOnContainer(*this, search_for_anchor);
return anchor;
}
const LayoutObject* LayoutBox::AcceptableImplicitAnchor() const {
if (!IsOutOfFlowPositioned()) {
return nullptr;
}
Element* element = DynamicTo<Element>(GetNode());
Element* anchor_element =
element ? element->ImplicitAnchorElement() : nullptr;
LayoutObject* anchor_layout_object =
anchor_element ? anchor_element->GetLayoutObject() : nullptr;
if (!anchor_layout_object) {
return nullptr;
}
// Go through the already built PhysicalAnchorQuery to avoid tree traversal.
bool is_acceptable_anchor = false;
auto validate_anchor = [&](const PhysicalAnchorQuery& anchor_query) {
if (anchor_query.AnchorLayoutObject(*this, anchor_layout_object)) {
is_acceptable_anchor = true;
}
};
ForEachAnchorQueryOnContainer(*this, validate_anchor);
return is_acceptable_anchor ? anchor_layout_object : nullptr;
}
const Vector<NonOverflowingScrollRange>* LayoutBox::NonOverflowingScrollRanges()
const {
const auto& layout_results = GetLayoutResults();
if (layout_results.empty()) {
return nullptr;
}
// We only need to check the first fragment, because when the box is
// fragmented, position fallback results are duplicated on all fragments.
#if EXPENSIVE_DCHECKS_ARE_ON()
for (wtf_size_t i = 1; i < layout_results.size(); ++i) {
DCHECK(base::ValuesEquivalent(
layout_results[i]->NonOverflowingScrollRanges(),
layout_results[i - 1]->NonOverflowingScrollRanges()));
}
#endif
return layout_results.front()->NonOverflowingScrollRanges();
}
const BoxStrut& LayoutBox::OutOfFlowInsetsForGetComputedStyle() const {
const auto& layout_results = GetLayoutResults();
// We should call this function only after the node is laid out.
CHECK(layout_results.size());
// We only need to check the first fragment, because when the box is
// fragmented, insets are duplicated on all fragments.
#if EXPENSIVE_DCHECKS_ARE_ON()
AssertSameDataOnLayoutResults(layout_results, [](const auto& result) {
return result->OutOfFlowInsetsForGetComputedStyle();
});
#endif
return GetLayoutResults().front()->OutOfFlowInsetsForGetComputedStyle();
}
bool LayoutBox::NeedsAnchorPositionScrollAdjustmentInX() const {
const auto& layout_results = GetLayoutResults();
if (layout_results.empty()) {
return false;
}
// We only need to check the first fragment, because when the box is
// fragmented, position fallback results are duplicated on all fragments.
#if EXPENSIVE_DCHECKS_ARE_ON()
AssertSameDataOnLayoutResults(layout_results, [](const auto& result) {
return result->NeedsAnchorPositionScrollAdjustmentInX();
});
#endif
return layout_results.front()->NeedsAnchorPositionScrollAdjustmentInX();
}
bool LayoutBox::NeedsAnchorPositionScrollAdjustmentInY() const {
const auto& layout_results = GetLayoutResults();
if (layout_results.empty()) {
return false;
}
// We only need to check the first fragment, because when the box is
// fragmented, position fallback results are duplicated on all fragments.
#if EXPENSIVE_DCHECKS_ARE_ON()
AssertSameDataOnLayoutResults(layout_results, [](const auto& result) {
return result->NeedsAnchorPositionScrollAdjustmentInY();
});
#endif
return layout_results.front()->NeedsAnchorPositionScrollAdjustmentInY();
}
WritingModeConverter LayoutBox::CreateWritingModeConverter() const {
return WritingModeConverter({Style()->GetWritingMode(), TextDirection::kLtr},
Size());
}
} // namespace blink