diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-16 11:45:35 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-17 08:59:23 +0000 |
commit | 552906b0f222c5d5dd11b9fd73829d510980461a (patch) | |
tree | 3a11e6ed0538a81dd83b20cf3a4783e297f26d91 /chromium/third_party/blink/renderer/core/layout/ng | |
parent | 1b05827804eaf047779b597718c03e7d38344261 (diff) | |
download | qtwebengine-chromium-552906b0f222c5d5dd11b9fd73829d510980461a.tar.gz |
BASELINE: Update Chromium to 83.0.4103.122
Change-Id: Ie3a82f5bb0076eec2a7c6a6162326b4301ee291e
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/layout/ng')
196 files changed, 12245 insertions, 5532 deletions
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc index 3ac1c1fbcb5..b662f819135 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc @@ -13,6 +13,8 @@ #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_fragment_result_options.h" #include "third_party/blink/renderer/bindings/core/v8/v8_function.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_intrinsic_sizes_callback.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_intrinsic_sizes_result_options.h" #include "third_party/blink/renderer/bindings/core/v8/v8_layout_callback.h" #include "third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.h" #include "third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h" @@ -23,20 +25,41 @@ #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_edges.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h" -#include "third_party/blink/renderer/core/layout/ng/custom/fragment_result_options.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" #include "third_party/blink/renderer/platform/bindings/microtask.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/v8_binding_macros.h" #include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h" +#include "third_party/blink/renderer/platform/heap/heap.h" namespace blink { +namespace { + +void GatherChildren(const NGBlockNode& node, + CustomLayoutScope* custom_layout_scope, + HeapVector<Member<CustomLayoutChild>>* children) { + // TODO(ikilpatrick): Determine if knowing the size of the array ahead of + // time improves performance in any noticeable way. + for (NGLayoutInputNode child = node.FirstChild(); child; + child = child.NextSibling()) { + if (child.IsOutOfFlowPositioned()) + continue; + + CustomLayoutChild* layout_child = child.GetCustomLayoutChild(); + layout_child->SetCustomLayoutToken(custom_layout_scope->Token()); + DCHECK(layout_child); + children->push_back(layout_child); + } +} + +} // anonymous namespace + CSSLayoutDefinition::CSSLayoutDefinition( ScriptState* script_state, V8NoArgumentConstructor* constructor, - V8Function* intrinsic_sizes, + V8IntrinsicSizesCallback* intrinsic_sizes, V8LayoutCallback* layout, const Vector<CSSPropertyID>& native_invalidation_properties, const Vector<AtomicString>& custom_invalidation_properties, @@ -44,7 +67,7 @@ CSSLayoutDefinition::CSSLayoutDefinition( const Vector<AtomicString>& child_custom_invalidation_properties) : script_state_(script_state), constructor_(constructor), - unused_intrinsic_sizes_(intrinsic_sizes), + intrinsic_sizes_(intrinsic_sizes), layout_(layout), native_invalidation_properties_(native_invalidation_properties), custom_invalidation_properties_(custom_invalidation_properties), @@ -66,8 +89,9 @@ bool CSSLayoutDefinition::Instance::Layout( const NGBlockNode& node, const LogicalSize& border_box_size, const NGBoxStrut& border_scrollbar_padding, + const LayoutUnit child_percentage_resolution_block_size_for_min_max, CustomLayoutScope* custom_layout_scope, - FragmentResultOptions* fragment_result_options, + FragmentResultOptions*& fragment_result_options, scoped_refptr<SerializedScriptValue>* fragment_result_data) { ScriptState* script_state = definition_->GetScriptState(); v8::Isolate* isolate = script_state->GetIsolate(); @@ -77,19 +101,8 @@ bool CSSLayoutDefinition::Instance::Layout( ScriptState::Scope scope(script_state); - // TODO(ikilpatrick): Determine if knowing the size of the array ahead of - // time improves performance in any noticeable way. HeapVector<Member<CustomLayoutChild>> children; - for (NGLayoutInputNode child = node.FirstChild(); child; - child = child.NextSibling()) { - if (child.IsOutOfFlowPositioned()) - continue; - - CustomLayoutChild* layout_child = child.GetCustomLayoutChild(); - layout_child->SetCustomLayoutToken(custom_layout_scope->Token()); - DCHECK(layout_child); - children.push_back(layout_child); - } + GatherChildren(node, custom_layout_scope, &children); CustomLayoutEdges* edges = MakeGarbageCollected<CustomLayoutEdges>(border_scrollbar_padding); @@ -121,18 +134,20 @@ bool CSSLayoutDefinition::Instance::Layout( v8::Local<v8::Value> v8_return_value = return_value.V8Value(); if (v8_return_value.IsEmpty() || !v8_return_value->IsPromise()) { - execution_context->AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kJavaScript, - mojom::ConsoleMessageLevel::kInfo, - "The layout function must be async or return a " - "promise, falling back to block layout.")); + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kInfo, + "The layout function must be async or return a " + "promise, falling back to block layout.")); return false; } // Run the work queue until exhaustion. while (!custom_layout_scope->Queue()->IsEmpty()) { - for (auto& task : *custom_layout_scope->Queue()) - task.Run(space, node.Style()); + for (auto& task : *custom_layout_scope->Queue()) { + task.Run(space, node.Style(), + child_percentage_resolution_block_size_for_min_max); + } custom_layout_scope->Queue()->clear(); { v8::MicrotasksScope microtasks_scope(isolate, microtask_queue, @@ -149,27 +164,28 @@ bool CSSLayoutDefinition::Instance::Layout( v8::Local<v8::Promise>::Cast(v8_return_value); if (v8_result_promise->State() != v8::Promise::kFulfilled) { - execution_context->AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kJavaScript, - mojom::ConsoleMessageLevel::kInfo, - "The layout function promise must resolve, " - "falling back to block layout.")); + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kInfo, + "The layout function promise must resolve, " + "falling back to block layout.")); return false; } v8::Local<v8::Value> inner_value = v8_result_promise->Result(); // Attempt to convert the result. - V8FragmentResultOptions::ToImpl(isolate, inner_value, fragment_result_options, - exception_state); + fragment_result_options = + NativeValueTraits<FragmentResultOptions>::NativeValue( + isolate, inner_value, exception_state); if (exception_state.HadException()) { V8ScriptRunner::ReportException(isolate, exception_state.GetException()); exception_state.ClearException(); - execution_context->AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kJavaScript, - mojom::ConsoleMessageLevel::kInfo, - "Unable to parse the layout function " - "result, falling back to block layout.")); + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kInfo, + "Unable to parse the layout function " + "result, falling back to block layout.")); return false; } @@ -188,11 +204,114 @@ bool CSSLayoutDefinition::Instance::Layout( if (exception_state.HadException()) { V8ScriptRunner::ReportException(isolate, exception_state.GetException()); exception_state.ClearException(); - execution_context->AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kJavaScript, - mojom::ConsoleMessageLevel::kInfo, - "Unable to serialize the data provided in the " - "result, falling back to block layout.")); + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kInfo, + "Unable to serialize the data provided in the " + "result, falling back to block layout.")); + return false; + } + + return true; +} + +bool CSSLayoutDefinition::Instance::IntrinsicSizes( + const NGConstraintSpace& space, + const Document& document, + const NGBlockNode& node, + const LogicalSize& border_box_size, + const NGBoxStrut& border_scrollbar_padding, + const LayoutUnit child_percentage_resolution_block_size_for_min_max, + CustomLayoutScope* custom_layout_scope, + IntrinsicSizesResultOptions*& intrinsic_sizes_result_options) { + ScriptState* script_state = definition_->GetScriptState(); + v8::Isolate* isolate = script_state->GetIsolate(); + + if (!script_state->ContextIsValid()) + return false; + + ScriptState::Scope scope(script_state); + + HeapVector<Member<CustomLayoutChild>> children; + GatherChildren(node, custom_layout_scope, &children); + + CustomLayoutEdges* edges = + MakeGarbageCollected<CustomLayoutEdges>(border_scrollbar_padding); + + // TODO(ikilpatrick): Instead of creating a new style_map each time here, + // store on LayoutCustom, and update when the style changes. + StylePropertyMapReadOnly* style_map = + MakeGarbageCollected<PrepopulatedComputedStylePropertyMap>( + document, node.Style(), definition_->native_invalidation_properties_, + definition_->custom_invalidation_properties_); + + ScriptValue return_value; + if (!definition_->intrinsic_sizes_ + ->Invoke(instance_.NewLocal(isolate), children, edges, style_map) + .To(&return_value)) + return false; + + ExecutionContext* execution_context = ExecutionContext::From(script_state); + v8::MicrotaskQueue* microtask_queue = ToMicrotaskQueue(execution_context); + DCHECK(microtask_queue); + + ExceptionState exception_state(isolate, ExceptionState::kExecutionContext, + "CSSLayoutAPI", "IntrinsicSizes"); + + v8::Local<v8::Value> v8_return_value = return_value.V8Value(); + if (v8_return_value.IsEmpty() || !v8_return_value->IsPromise()) { + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kInfo, + "The intrinsicSizes function must be async or return a " + "promise, falling back to block layout.")); + return false; + } + + // Run the work queue until exhaustion. + while (!custom_layout_scope->Queue()->IsEmpty()) { + for (auto& task : *custom_layout_scope->Queue()) { + task.Run(space, node.Style(), + child_percentage_resolution_block_size_for_min_max); + } + custom_layout_scope->Queue()->clear(); + { + v8::MicrotasksScope microtasks_scope(isolate, microtask_queue, + v8::MicrotasksScope::kRunMicrotasks); + } + } + + if (exception_state.HadException()) { + ReportException(&exception_state); + return false; + } + + v8::Local<v8::Promise> v8_result_promise = + v8::Local<v8::Promise>::Cast(v8_return_value); + + if (v8_result_promise->State() != v8::Promise::kFulfilled) { + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kInfo, + "The intrinsicSizes function promise must resolve, " + "falling back to block layout.")); + return false; + } + v8::Local<v8::Value> inner_value = v8_result_promise->Result(); + + // Attempt to convert the result. + intrinsic_sizes_result_options = + NativeValueTraits<IntrinsicSizesResultOptions>::NativeValue( + isolate, inner_value, exception_state); + + if (exception_state.HadException()) { + V8ScriptRunner::ReportException(isolate, exception_state.GetException()); + exception_state.ClearException(); + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kInfo, + "Unable to parse the intrinsicSizes function " + "result, falling back to block layout.")); return false; } @@ -209,7 +328,7 @@ void CSSLayoutDefinition::Instance::ReportException( // again (as the callbacks are invoked directly by the UA). V8ScriptRunner::ReportException(isolate, exception_state->GetException()); exception_state->ClearException(); - execution_context->AddConsoleMessage(ConsoleMessage::Create( + execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kInfo, "The layout function failed, falling back to block layout.")); @@ -234,14 +353,14 @@ CSSLayoutDefinition::Instance* CSSLayoutDefinition::CreateInstance() { return MakeGarbageCollected<Instance>(this, instance.V8Value()); } -void CSSLayoutDefinition::Instance::Trace(blink::Visitor* visitor) { +void CSSLayoutDefinition::Instance::Trace(Visitor* visitor) { visitor->Trace(definition_); visitor->Trace(instance_); } void CSSLayoutDefinition::Trace(Visitor* visitor) { visitor->Trace(constructor_); - visitor->Trace(unused_intrinsic_sizes_); + visitor->Trace(intrinsic_sizes_); visitor->Trace(layout_); visitor->Trace(script_state_); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h index b8690a9e84f..4c1ff77f733 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h @@ -17,13 +17,15 @@ namespace blink { class CustomLayoutScope; class FragmentResultOptions; +class IntrinsicSizesResultOptions; +class LayoutUnit; struct LogicalSize; class NGBlockNode; struct NGBoxStrut; class NGConstraintSpace; class ScriptState; class SerializedScriptValue; -class V8Function; +class V8IntrinsicSizesCallback; class V8LayoutCallback; class V8NoArgumentConstructor; @@ -36,7 +38,7 @@ class CSSLayoutDefinition final : public GarbageCollected<CSSLayoutDefinition>, CSSLayoutDefinition( ScriptState*, V8NoArgumentConstructor* constructor, - V8Function* intrinsic_sizes, + V8IntrinsicSizesCallback* intrinsic_sizes, V8LayoutCallback* layout, const Vector<CSSPropertyID>& native_invalidation_properties, const Vector<AtomicString>& custom_invalidation_properties, @@ -53,16 +55,30 @@ class CSSLayoutDefinition final : public GarbageCollected<CSSLayoutDefinition>, // Runs the web developer defined layout, returns true if everything // succeeded. It populates the FragmentResultOptions dictionary, and // fragment_result_data. - bool Layout(const NGConstraintSpace&, - const Document&, - const NGBlockNode&, - const LogicalSize& border_box_size, - const NGBoxStrut& border_scrollbar_padding, - CustomLayoutScope*, - FragmentResultOptions*, - scoped_refptr<SerializedScriptValue>* fragment_result_data); - - void Trace(blink::Visitor*); + bool Layout( + const NGConstraintSpace&, + const Document&, + const NGBlockNode&, + const LogicalSize& border_box_size, + const NGBoxStrut& border_scrollbar_padding, + const LayoutUnit child_percentage_resolution_block_size_for_min_max, + CustomLayoutScope*, + FragmentResultOptions*&, + scoped_refptr<SerializedScriptValue>* fragment_result_data); + + // Runs the web developer defined intrinsicSizes, returns true if everything + // succeeded. It populates the IntrinsicSizesResultOptions dictionary. + bool IntrinsicSizes( + const NGConstraintSpace&, + const Document&, + const NGBlockNode&, + const LogicalSize& border_box_size, + const NGBoxStrut& border_scrollbar_padding, + const LayoutUnit child_percentage_resolution_block_size_for_min_max, + CustomLayoutScope*, + IntrinsicSizesResultOptions*&); + + void Trace(Visitor*); private: void ReportException(ExceptionState*); @@ -90,7 +106,7 @@ class CSSLayoutDefinition final : public GarbageCollected<CSSLayoutDefinition>, ScriptState* GetScriptState() const { return script_state_; } - virtual void Trace(blink::Visitor* visitor); + virtual void Trace(Visitor* visitor); const char* NameInHeapSnapshot() const override { return "CSSLayoutDefinition"; @@ -103,7 +119,7 @@ class CSSLayoutDefinition final : public GarbageCollected<CSSLayoutDefinition>, // sizes function, and layout function alive. It participates in wrapper // tracing as it holds onto V8 wrappers. Member<V8NoArgumentConstructor> constructor_; - Member<V8Function> unused_intrinsic_sizes_; + Member<V8IntrinsicSizesCallback> intrinsic_sizes_; Member<V8LayoutCallback> layout_; // If a constructor call ever fails, we'll refuse to create any more diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.cc new file mode 100644 index 00000000000..7dd9c992f83 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.cc @@ -0,0 +1,30 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h" + +#include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h" + +namespace blink { + +CustomIntrinsicSizes::CustomIntrinsicSizes(CustomLayoutChild* child, + CustomLayoutToken* token, + double min_content_size, + double max_content_size) + : child_(child), + token_(token), + min_content_size_(min_content_size), + max_content_size_(max_content_size) {} + +const NGLayoutInputNode& CustomIntrinsicSizes::GetLayoutNode() const { + return child_->GetLayoutNode(); +} + +void CustomIntrinsicSizes::Trace(Visitor* visitor) { + visitor->Trace(child_); + visitor->Trace(token_); + ScriptWrappable::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h new file mode 100644 index 00000000000..1bfa9cea738 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h @@ -0,0 +1,53 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_CUSTOM_CUSTOM_INTRINSIC_SIZES_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_CUSTOM_CUSTOM_INTRINSIC_SIZES_H_ + +#include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h" +#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" + +namespace blink { + +class NGLayoutInputNode; +class CustomLayoutChild; + +// This represents the result of intrinsicSizes (on a LayoutChild). +// +// This should mirror the information in a MinMaxSize, and it has the +// additional capability that it is exposed to web developers. +class CustomIntrinsicSizes : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + CustomIntrinsicSizes(CustomLayoutChild*, + CustomLayoutToken*, + double min_content_size, + double max_content_size); + ~CustomIntrinsicSizes() override = default; + + CustomIntrinsicSizes(const CustomIntrinsicSizes&) = delete; + CustomIntrinsicSizes& operator=(const CustomIntrinsicSizes&) = delete; + + double minContentSize() const { return min_content_size_; } + double maxContentSize() const { return max_content_size_; } + + const NGLayoutInputNode& GetLayoutNode() const; + + bool IsValid() const { return token_->IsValid(); } + + void Trace(Visitor*) override; + + private: + Member<CustomLayoutChild> child_; + Member<CustomLayoutToken> token_; + + // The min and max content sizes on this object should never change. + const double min_content_size_; + const double max_content_size_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_CUSTOM_CUSTOM_INTRINSIC_SIZES_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc index 043110aec8f..11a2762ad9d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.cc @@ -11,9 +11,14 @@ #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.h" +#include "third_party/blink/renderer/platform/bindings/exception_state.h" namespace blink { +namespace { +const char kInvalidLayoutChild[] = "The LayoutChild is not valid."; +} // namespace + CustomLayoutChild::CustomLayoutChild(const CSSLayoutDefinition& definition, NGLayoutInputNode node) : node_(node), @@ -23,6 +28,24 @@ CustomLayoutChild::CustomLayoutChild(const CSSLayoutDefinition& definition, definition.ChildNativeInvalidationProperties(), definition.ChildCustomInvalidationProperties())) {} +ScriptPromise CustomLayoutChild::intrinsicSizes( + ScriptState* script_state, + ExceptionState& exception_state) { + // A layout child may be invalid if it has been removed from the tree (it is + // possible for a web developer to hold onto a LayoutChild object after its + // underlying LayoutObject has been destroyed). + if (!node_ || !token_->IsValid()) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + kInvalidLayoutChild); + return ScriptPromise(); + } + + auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); + CustomLayoutScope::Current()->Queue()->emplace_back( + this, token_, resolver, CustomLayoutWorkTask::TaskType::kIntrinsicSizes); + return resolver->Promise(); +} + ScriptPromise CustomLayoutChild::layoutNextFragment( ScriptState* script_state, const CustomLayoutConstraintsOptions* options, @@ -31,10 +54,9 @@ ScriptPromise CustomLayoutChild::layoutNextFragment( // possible for a web developer to hold onto a LayoutChild object after its // underlying LayoutObject has been destroyed). if (!node_ || !token_->IsValid()) { - return ScriptPromise::RejectWithDOMException( - script_state, - MakeGarbageCollected<DOMException>(DOMExceptionCode::kInvalidStateError, - "The LayoutChild is not valid.")); + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + kInvalidLayoutChild); + return ScriptPromise(); } // Serialize the provided data if needed. @@ -54,11 +76,12 @@ ScriptPromise CustomLayoutChild::layoutNextFragment( auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); CustomLayoutScope::Current()->Queue()->emplace_back( - this, token_, resolver, options, std::move(constraint_data)); + this, token_, resolver, options, std::move(constraint_data), + CustomLayoutWorkTask::TaskType::kLayoutFragment); return resolver->Promise(); } -void CustomLayoutChild::Trace(blink::Visitor* visitor) { +void CustomLayoutChild::Trace(Visitor* visitor) { visitor->Trace(style_map_); visitor->Trace(token_); ScriptWrappable::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h index 5b7c1c435f3..7943adc0ac3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h @@ -16,6 +16,7 @@ namespace blink { class CSSLayoutDefinition; class CustomLayoutConstraintsOptions; class CustomLayoutToken; +class ExceptionState; // Represents a "CSS box" for use by a web developer. This is passed into the // web developer defined layout and intrinsicSizes functions so that they can @@ -32,6 +33,7 @@ class CustomLayoutChild : public ScriptWrappable { // LayoutChild.idl PrepopulatedComputedStylePropertyMap* styleMap() const { return style_map_; } + ScriptPromise intrinsicSizes(ScriptState*, ExceptionState&); ScriptPromise layoutNextFragment(ScriptState*, const CustomLayoutConstraintsOptions*, ExceptionState&); @@ -44,7 +46,7 @@ class CustomLayoutChild : public ScriptWrappable { void SetCustomLayoutToken(CustomLayoutToken* token) { token_ = token; } - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: NGLayoutInputNode node_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc index ec4f27e6957..268d0e72343 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.cc @@ -25,6 +25,13 @@ CustomLayoutConstraints::CustomLayoutConstraints( CustomLayoutConstraints::~CustomLayoutConstraints() = default; +base::Optional<double> CustomLayoutConstraints::fixedBlockSize() const { + // Check if we've been passed an indefinite block-size. + if (fixed_block_size_ < 0.0) + return base::nullopt; + return fixed_block_size_; +} + double CustomLayoutConstraints::fixedBlockSize(bool& is_null) const { // Check if we've been passed an indefinite block-size. if (fixed_block_size_ < 0.0) { @@ -50,7 +57,7 @@ ScriptValue CustomLayoutConstraints::data(ScriptState* script_state) const { layout_worklet_world_v8_data_.NewLocal(script_state->GetIsolate())); } -void CustomLayoutConstraints::Trace(blink::Visitor* visitor) { +void CustomLayoutConstraints::Trace(Visitor* visitor) { visitor->Trace(layout_worklet_world_v8_data_); ScriptWrappable::Trace(visitor); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h index 1c66b7c437f..42ce5a8fa06 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints.h @@ -29,10 +29,12 @@ class CustomLayoutConstraints : public ScriptWrappable { // LayoutConstraints.idl double fixedInlineSize() const { return fixed_inline_size_; } - double fixedBlockSize(bool& is_null) const; + base::Optional<double> fixedBlockSize() const; + // TODO(crbug.com/1060971): Remove |is_null| version. + double fixedBlockSize(bool& is_null) const; // DEPRECATED ScriptValue data(ScriptState*) const; - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: double fixed_inline_size_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc index 1019fd242bb..476d990a0b4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.cc @@ -15,12 +15,14 @@ CustomLayoutFragment::CustomLayoutFragment( CustomLayoutToken* token, scoped_refptr<const NGLayoutResult> layout_result, const LogicalSize& size, + const base::Optional<LayoutUnit> baseline, v8::Isolate* isolate) : child_(child), token_(token), layout_result_(std::move(layout_result)), inline_size_(size.inline_size.ToDouble()), - block_size_(size.block_size.ToDouble()) { + block_size_(size.block_size.ToDouble()), + baseline_(baseline) { // Immediately store the result data, so that it remains immutable between // layout calls to the child. if (SerializedScriptValue* data = layout_result_->CustomLayoutData()) @@ -36,6 +38,11 @@ const NGLayoutInputNode& CustomLayoutFragment::GetLayoutNode() const { return child_->GetLayoutNode(); } +double CustomLayoutFragment::baseline(bool& is_null) const { + is_null = !baseline_.has_value(); + return baseline_.value_or(0.0); +} + ScriptValue CustomLayoutFragment::data(ScriptState* script_state) const { // "data" is *only* exposed to the LayoutWorkletGlobalScope, and we are able // to return the same deserialized object. We don't need to check which world @@ -51,7 +58,7 @@ ScriptValue CustomLayoutFragment::data(ScriptState* script_state) const { layout_worklet_world_v8_data_.NewLocal(script_state->GetIsolate())); } -void CustomLayoutFragment::Trace(blink::Visitor* visitor) { +void CustomLayoutFragment::Trace(Visitor* visitor) { visitor->Trace(child_); visitor->Trace(token_); visitor->Trace(layout_worklet_world_v8_data_); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h index bc9cb8ee03e..29adc05f5a9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h @@ -39,6 +39,7 @@ class CustomLayoutFragment : public ScriptWrappable { CustomLayoutToken*, scoped_refptr<const NGLayoutResult>, const LogicalSize& size, + const base::Optional<LayoutUnit> baseline, v8::Isolate*); ~CustomLayoutFragment() override = default; @@ -51,6 +52,10 @@ class CustomLayoutFragment : public ScriptWrappable { void setInlineOffset(double inline_offset) { inline_offset_ = inline_offset; } void setBlockOffset(double block_offset) { block_offset_ = block_offset; } + base::Optional<double> baseline() const { return baseline_; } + // TODO(crbug.com/1060971): Remove |is_null| version. + double baseline(bool& is_null) const; // DEPRECATED + ScriptValue data(ScriptState*) const; const NGLayoutResult& GetLayoutResult() const; @@ -58,7 +63,7 @@ class CustomLayoutFragment : public ScriptWrappable { bool IsValid() const { return token_->IsValid(); } - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: Member<CustomLayoutChild> child_; @@ -88,6 +93,9 @@ class CustomLayoutFragment : public ScriptWrappable { double inline_offset_ = 0; double block_offset_ = 0; + // The first-line baseline. + const base::Optional<double> baseline_; + TraceWrapperV8Reference<v8::Value> layout_worklet_world_v8_data_; DISALLOW_COPY_AND_ASSIGN(CustomLayoutFragment); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h index 319ea6b778b..3cc70061f06 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h @@ -64,7 +64,7 @@ class CustomLayoutScope { CustomLayoutScope* prev_scope_; CustomLayoutWorkQueue queue_; - Member<CustomLayoutToken> token_; + CustomLayoutToken* token_; }; inline bool CustomLayoutToken::IsValid() const { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc index b23852b8139..3e9dc834b29 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.cc @@ -5,37 +5,68 @@ #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.h" #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" +#include "third_party/blink/renderer/core/layout/ng/custom/custom_intrinsic_sizes.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_child.h" -#include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints_options.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" namespace blink { +CustomLayoutWorkTask::CustomLayoutWorkTask(CustomLayoutChild* child, + CustomLayoutToken* token, + ScriptPromiseResolver* resolver, + const TaskType type) + : CustomLayoutWorkTask(child, token, resolver, nullptr, nullptr, type) {} + CustomLayoutWorkTask::CustomLayoutWorkTask( CustomLayoutChild* child, CustomLayoutToken* token, ScriptPromiseResolver* resolver, const CustomLayoutConstraintsOptions* options, - scoped_refptr<SerializedScriptValue> constraint_data) + scoped_refptr<SerializedScriptValue> constraint_data, + const TaskType type) : child_(child), token_(token), resolver_(resolver), options_(options), - constraint_data_(std::move(constraint_data)) {} + constraint_data_(std::move(constraint_data)), + type_(type) {} CustomLayoutWorkTask::~CustomLayoutWorkTask() = default; -void CustomLayoutWorkTask::Run(const NGConstraintSpace& parent_space, - const ComputedStyle& parent_style) { +void CustomLayoutWorkTask::Run( + const NGConstraintSpace& parent_space, + const ComputedStyle& parent_style, + const LayoutUnit child_percentage_resolution_block_size_for_min_max) { DCHECK(token_->IsValid()); - NGLayoutInputNode node = child_->GetLayoutNode(); - NGConstraintSpaceBuilder builder(parent_space, node.Style().GetWritingMode(), + NGLayoutInputNode child = child_->GetLayoutNode(); + + if (type_ == CustomLayoutWorkTask::TaskType::kIntrinsicSizes) { + RunIntrinsicSizesTask(parent_style, + child_percentage_resolution_block_size_for_min_max, + child); + } else { + DCHECK_EQ(type_, CustomLayoutWorkTask::TaskType::kLayoutFragment); + RunLayoutFragmentTask(parent_space, parent_style, child); + } +} + +void CustomLayoutWorkTask::RunLayoutFragmentTask( + const NGConstraintSpace& parent_space, + const ComputedStyle& parent_style, + NGLayoutInputNode child) { + DCHECK_EQ(type_, CustomLayoutWorkTask::TaskType::kLayoutFragment); + DCHECK(options_ && resolver_); + + NGConstraintSpaceBuilder builder(parent_space, child.Style().GetWritingMode(), /* is_new_fc */ true); - SetOrthogonalFallbackInlineSizeIfNeeded(parent_style, node, &builder); + SetOrthogonalFallbackInlineSizeIfNeeded(parent_style, child, &builder); bool is_fixed_inline_size = false; bool is_fixed_block_size = false; @@ -88,24 +119,40 @@ void CustomLayoutWorkTask::Run(const NGConstraintSpace& parent_space, percentage_size.block_size = kIndefiniteSize; } - builder.SetTextDirection(node.Style().Direction()); + builder.SetTextDirection(child.Style().Direction()); builder.SetAvailableSize(available_size); builder.SetPercentageResolutionSize(percentage_size); builder.SetReplacedPercentageResolutionSize(percentage_size); - builder.SetIsShrinkToFit(node.Style().LogicalWidth().IsAuto()); + builder.SetIsShrinkToFit(child.Style().LogicalWidth().IsAuto()); builder.SetIsFixedInlineSize(is_fixed_inline_size); builder.SetIsFixedBlockSize(is_fixed_block_size); - if (node.IsLayoutNGCustom()) + builder.SetNeedsBaseline(true); + if (child.IsLayoutNGCustom()) builder.SetCustomLayoutData(std::move(constraint_data_)); auto space = builder.ToConstraintSpace(); - auto result = To<NGBlockNode>(node).Layout(space, nullptr /* break_token */); + auto result = To<NGBlockNode>(child).Layout(space, nullptr /* break_token */); - LogicalSize size = result->PhysicalFragment().Size().ConvertToLogical( - parent_space.GetWritingMode()); + NGBoxFragment fragment(parent_space.GetWritingMode(), + parent_space.Direction(), + To<NGPhysicalBoxFragment>(result->PhysicalFragment())); resolver_->Resolve(MakeGarbageCollected<CustomLayoutFragment>( - child_, token_, std::move(result), size, + child_, token_, std::move(result), fragment.Size(), fragment.Baseline(), resolver_->GetScriptState()->GetIsolate())); } +void CustomLayoutWorkTask::RunIntrinsicSizesTask( + const ComputedStyle& parent_style, + const LayoutUnit child_percentage_resolution_block_size_for_min_max, + NGLayoutInputNode child) { + DCHECK_EQ(type_, CustomLayoutWorkTask::TaskType::kIntrinsicSizes); + DCHECK(resolver_); + + MinMaxSizesInput input(child_percentage_resolution_block_size_for_min_max); + MinMaxSizes sizes = + ComputeMinAndMaxContentContribution(parent_style, child, input); + resolver_->Resolve(MakeGarbageCollected<CustomIntrinsicSizes>( + child_, token_, sizes.min_size, sizes.max_size)); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.h index 5038048ed31..7aacc7fce5c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/custom_layout_work_task.h @@ -5,7 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_CUSTOM_CUSTOM_LAYOUT_WORK_TASK_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_CUSTOM_CUSTOM_LAYOUT_WORK_TASK_H_ -#include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_constraints_options.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_custom_layout_constraints_options.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h" #include "third_party/blink/renderer/platform/heap/handle.h" @@ -14,7 +14,9 @@ namespace blink { class ComputedStyle; class CustomLayoutChild; class CustomLayoutToken; +class LayoutUnit; class NGConstraintSpace; +class NGLayoutInputNode; class SerializedScriptValue; class ScriptPromiseResolver; @@ -22,16 +24,30 @@ class ScriptPromiseResolver; // intrinsic-sizes. class CustomLayoutWorkTask { public: + enum TaskType { + kLayoutFragment, + kIntrinsicSizes, + }; + + // Used when resolving a promise with intrinsic-sizes. + CustomLayoutWorkTask(CustomLayoutChild*, + CustomLayoutToken*, + ScriptPromiseResolver*, + const TaskType type); + + // Used when resolving a promise with a fragment. CustomLayoutWorkTask(CustomLayoutChild*, CustomLayoutToken*, ScriptPromiseResolver*, const CustomLayoutConstraintsOptions*, - scoped_refptr<SerializedScriptValue> constraint_data); + scoped_refptr<SerializedScriptValue> constraint_data, + const TaskType type); ~CustomLayoutWorkTask(); // Runs this work task. void Run(const NGConstraintSpace& parent_space, - const ComputedStyle& parent_style); + const ComputedStyle& parent_style, + const LayoutUnit child_percentage_resolution_block_size_for_min_max); private: Persistent<CustomLayoutChild> child_; @@ -39,6 +55,15 @@ class CustomLayoutWorkTask { Persistent<ScriptPromiseResolver> resolver_; Persistent<const CustomLayoutConstraintsOptions> options_; scoped_refptr<SerializedScriptValue> constraint_data_; + TaskType type_; + + void RunLayoutFragmentTask(const NGConstraintSpace& parent_space, + const ComputedStyle& parent_style, + NGLayoutInputNode child); + void RunIntrinsicSizesTask( + const ComputedStyle& parent_style, + const LayoutUnit child_percentage_resolution_block_size_for_min_max, + NGLayoutInputNode child); }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc index 3d60b887d1c..c68765b9107 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.cc @@ -33,7 +33,7 @@ bool DocumentLayoutDefinition::IsEqual(const CSSLayoutDefinition& other) { other.ChildCustomInvalidationProperties(); } -void DocumentLayoutDefinition::Trace(blink::Visitor* visitor) { +void DocumentLayoutDefinition::Trace(Visitor* visitor) { visitor->Trace(layout_definition_); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h index 2a8e359b1cf..0c2d8bce246 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/document_layout_definition.h @@ -37,7 +37,7 @@ class DocumentLayoutDefinition final return registered_definitions_count_; } - virtual void Trace(blink::Visitor*); + virtual void Trace(Visitor*); private: bool IsEqual(const CSSLayoutDefinition&); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/fragment_result_options.idl b/chromium/third_party/blink/renderer/core/layout/ng/custom/fragment_result_options.idl index 376cc5be01c..28867d89dc6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/fragment_result_options.idl +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/fragment_result_options.idl @@ -6,6 +6,7 @@ dictionary FragmentResultOptions { double autoBlockSize = 0; + double baseline; sequence<LayoutFragment> childFragments = []; any data = null; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/intrinsic_sizes.idl b/chromium/third_party/blink/renderer/core/layout/ng/custom/intrinsic_sizes.idl new file mode 100644 index 00000000000..5864a0720a0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/intrinsic_sizes.idl @@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://drafts.css-houdini.org/css-layout-api/#intrinsicsizes + +[ + Exposed=LayoutWorklet, + ImplementedAs=CustomIntrinsicSizes, + RuntimeEnabled=CSSLayoutAPI +] +interface IntrinsicSizes { + readonly attribute double minContentSize; + readonly attribute double maxContentSize; +}; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/intrinsic_sizes_result_options.idl b/chromium/third_party/blink/renderer/core/layout/ng/custom/intrinsic_sizes_result_options.idl new file mode 100644 index 00000000000..aa3ae7d453b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/intrinsic_sizes_result_options.idl @@ -0,0 +1,10 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://drafts.css-houdini.org/css-layout-api/#dictdef-intrinsicsizesresultoptions + +dictionary IntrinsicSizesResultOptions { + double minContentSize = 0; + double maxContentSize = 0; +}; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_child.idl b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_child.idl index 3940849802c..76ba3c49d42 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_child.idl +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_child.idl @@ -12,7 +12,7 @@ interface LayoutChild { readonly attribute StylePropertyMapReadOnly styleMap; - // IntrinsicSizesRequest intrinsicSizes(); - [CallWith=ScriptState, RaisesException] Promise<LayoutFragment> layoutNextFragment(optional CustomLayoutConstraintsOptions options); + [CallWith=ScriptState, RaisesException] Promise<IntrinsicSizes> intrinsicSizes(); + [CallWith=ScriptState, RaisesException] Promise<LayoutFragment> layoutNextFragment(optional CustomLayoutConstraintsOptions options = {}); }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_fragment.idl b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_fragment.idl index e142bcd24b3..110744df579 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_fragment.idl +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_fragment.idl @@ -16,5 +16,7 @@ interface LayoutFragment { attribute double inlineOffset; attribute double blockOffset; + readonly attribute double? baseline; + [CallWith=ScriptState] readonly attribute any data; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.cc index 254c5354891..b49d3c51ad4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.cc @@ -15,6 +15,27 @@ LayoutNGCustom::LayoutNGCustom(Element* element) DCHECK(element); } +void LayoutNGCustom::AddChild(LayoutObject* new_child, + LayoutObject* before_child) { + // Only use the block-flow AddChild logic when we are unloaded, i.e. we + // should behave exactly like a block-flow. + if (state_ == kUnloaded) { + LayoutNGBlockFlow::AddChild(new_child, before_child); + return; + } + LayoutBlock::AddChild(new_child, before_child); +} + +void LayoutNGCustom::RemoveChild(LayoutObject* child) { + // Only use the block-flow RemoveChild logic when we are unloaded, i.e. we + // should behave exactly like a block-flow. + if (state_ == kUnloaded) { + LayoutNGBlockFlow::RemoveChild(child); + return; + } + LayoutBlock::RemoveChild(child); +} + void LayoutNGCustom::StyleDidChange(StyleDifference diff, const ComputedStyle* old_style) { if (state_ == kUnloaded) { @@ -35,6 +56,11 @@ void LayoutNGCustom::StyleDidChange(StyleDifference diff, } } + // Make our children "block-level" before invoking StyleDidChange. As the + // current multi-col logic may invoke a call to AddChild, failing a DCHECK. + if (state_ != kUnloaded) + SetChildrenInline(false); + // TODO(ikilpatrick): Investigate reducing the properties which // LayoutNGBlockFlow::StyleDidChange invalidates upon. (For example margins). LayoutNGBlockFlow::StyleDidChange(diff, old_style); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h index 3b80651d205..95f0f3d0dae 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h @@ -30,6 +30,9 @@ class LayoutNGCustom final : public LayoutNGBlockFlow { bool IsLoaded() { return state_ != kUnloaded; } + void AddChild(LayoutObject* new_child, LayoutObject* before_child) override; + void RemoveChild(LayoutObject* child) override; + void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; private: diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc index 6986d303d97..71e6bb85888 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.cc @@ -45,7 +45,7 @@ LayoutWorkletGlobalScopeProxy* LayoutWorklet::Proxy() { return LayoutWorkletGlobalScopeProxy::From(FindAvailableGlobalScope()); } -void LayoutWorklet::Trace(blink::Visitor* visitor) { +void LayoutWorklet::Trace(Visitor* visitor) { visitor->Trace(document_definition_map_); visitor->Trace(pending_layout_registry_); Worklet::Trace(visitor); @@ -59,8 +59,9 @@ bool LayoutWorklet::NeedsToCreateGlobalScope() { WorkletGlobalScopeProxy* LayoutWorklet::CreateGlobalScope() { DCHECK(NeedsToCreateGlobalScope()); return MakeGarbageCollected<LayoutWorkletGlobalScopeProxy>( - To<Document>(GetExecutionContext())->GetFrame(), ModuleResponsesMap(), - pending_layout_registry_, GetNumberOfGlobalScopes() + 1); + To<LocalDOMWindow>(GetExecutionContext())->GetFrame(), + ModuleResponsesMap(), pending_layout_registry_, + GetNumberOfGlobalScopes() + 1); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h index 3c433e4920b..19d91dd9a97 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h @@ -10,6 +10,7 @@ #include "third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h" #include "third_party/blink/renderer/core/workers/worklet.h" #include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/supplementable.h" namespace blink { @@ -46,7 +47,7 @@ class CORE_EXPORT LayoutWorklet : public Worklet, void AddPendingLayout(const AtomicString& name, Node*); LayoutWorkletGlobalScopeProxy* Proxy(); - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; protected: // TODO(ikilpatrick): Make selection of the global scope non-deterministic. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc index a953e2ef0e8..77870594359 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h" #include "third_party/blink/renderer/bindings/core/v8/v8_function.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_intrinsic_sizes_callback.h" #include "third_party/blink/renderer/bindings/core/v8/v8_layout_callback.h" #include "third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.h" #include "third_party/blink/renderer/bindings/core/v8/v8_object_parser.h" @@ -36,12 +37,12 @@ LayoutWorkletGlobalScope* LayoutWorkletGlobalScope::Create( auto* isolate = ToIsolate(frame); auto microtask_queue = v8::MicrotaskQueue::New(isolate, v8::MicrotasksPolicy::kScoped); - auto* agent = Agent::CreateForWorkerOrWorklet( - isolate, - creation_params->agent_cluster_id.is_empty() - ? base::UnguessableToken::Create() - : creation_params->agent_cluster_id, - std::move(microtask_queue)); + auto* agent = + MakeGarbageCollected<Agent>(isolate, + creation_params->agent_cluster_id.is_empty() + ? base::UnguessableToken::Create() + : creation_params->agent_cluster_id, + std::move(microtask_queue)); auto* global_scope = MakeGarbageCollected<LayoutWorkletGlobalScope>( frame, std::move(creation_params), reporting_proxy, pending_layout_registry, agent); @@ -103,7 +104,8 @@ void LayoutWorkletGlobalScope::registerLayout( Vector<AtomicString> custom_invalidation_properties; if (!V8ObjectParser::ParseCSSPropertyList( - current_context, layout_ctor->CallbackObject(), "inputProperties", + current_context, GetFrame()->DomWindow(), + layout_ctor->CallbackObject(), "inputProperties", &native_invalidation_properties, &custom_invalidation_properties, &exception_state)) return; @@ -112,8 +114,9 @@ void LayoutWorkletGlobalScope::registerLayout( Vector<AtomicString> child_custom_invalidation_properties; if (!V8ObjectParser::ParseCSSPropertyList( - current_context, layout_ctor->CallbackObject(), - "childInputProperties", &child_native_invalidation_properties, + current_context, GetFrame()->DomWindow(), + layout_ctor->CallbackObject(), "childInputProperties", + &child_native_invalidation_properties, &child_custom_invalidation_properties, &exception_state)) return; @@ -126,7 +129,8 @@ void LayoutWorkletGlobalScope::registerLayout( retriever.GetMethodOrThrow("intrinsicSizes", exception_state); if (exception_state.HadException()) return; - V8Function* intrinsic_sizes = V8Function::Create(v8_intrinsic_sizes); + V8IntrinsicSizesCallback* intrinsic_sizes = + V8IntrinsicSizesCallback::Create(v8_intrinsic_sizes); v8::Local<v8::Function> v8_layout = retriever.GetMethodOrThrow("layout", exception_state); @@ -141,8 +145,7 @@ void LayoutWorkletGlobalScope::registerLayout( child_custom_invalidation_properties); layout_definitions_.Set(name, definition); - LayoutWorklet* layout_worklet = - LayoutWorklet::From(*GetFrame()->GetDocument()->domWindow()); + LayoutWorklet* layout_worklet = LayoutWorklet::From(*GetFrame()->DomWindow()); LayoutWorklet::DocumentDefinitionMap* document_definition_map = layout_worklet->GetDocumentDefinitionMap(); if (document_definition_map->Contains(name)) { @@ -177,7 +180,7 @@ CSSLayoutDefinition* LayoutWorkletGlobalScope::FindDefinition( return layout_definitions_.at(name); } -void LayoutWorkletGlobalScope::Trace(blink::Visitor* visitor) { +void LayoutWorkletGlobalScope::Trace(Visitor* visitor) { visitor->Trace(layout_definitions_); visitor->Trace(pending_layout_registry_); WorkletGlobalScope::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h index 642e9af4527..d920e67f8d3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h @@ -45,7 +45,7 @@ class CORE_EXPORT LayoutWorkletGlobalScope final : public WorkletGlobalScope { CSSLayoutDefinition* FindDefinition(const AtomicString& name); - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: // https://drafts.css-houdini.org/css-layout-api/#layout-definitions diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.idl b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.idl index 5ce4982e351..63a233f5461 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.idl +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.idl @@ -15,3 +15,7 @@ // Blink-specific type for layout function // https://drafts.css-houdini.org/css-layout-api/#layout-definition-layout-function callback LayoutCallback = any (sequence<LayoutChild> children, LayoutEdges edges, LayoutConstraints constraints, StylePropertyMapReadOnly styleMap); + +// Blink-specific type for intrinsicSizes function +// https://drafts.css-houdini.org/css-layout-api/#layout-definition-intrinsic-sizes-function +callback IntrinsicSizesCallback = any (sequence<LayoutChild> children, LayoutEdges edges, StylePropertyMapReadOnly styleMap); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc index 781452633d4..b71ceac3b78 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.cc @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/bindings/core/v8/script_source_code.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/core/dom/document.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/loader/worker_fetch_context.h" @@ -40,15 +41,16 @@ LayoutWorkletGlobalScopeProxy::LayoutWorkletGlobalScopeProxy( StringView("LayoutWorklet #") + String::Number(global_scope_number); auto creation_params = std::make_unique<GlobalScopeCreationParams>( - document->Url(), mojom::ScriptType::kModule, - OffMainThreadWorkerScriptFetchOption::kEnabled, global_scope_name, - document->UserAgent(), frame->Client()->CreateWorkerFetchContext(), + document->Url(), mojom::blink::ScriptType::kModule, global_scope_name, + document->UserAgent(), frame->Client()->UserAgentMetadata(), + frame->Client()->CreateWorkerFetchContext(), document->GetContentSecurityPolicy()->Headers(), document->GetReferrerPolicy(), document->GetSecurityOrigin(), document->IsSecureContext(), document->GetHttpsState(), nullptr /* worker_clients */, frame->Client()->CreateWorkerContentSettingsClient(), - document->AddressSpace(), OriginTrialContext::GetTokens(document).get(), + document->GetSecurityContext().AddressSpace(), + OriginTrialContext::GetTokens(frame->DomWindow()).get(), base::UnguessableToken::Create(), nullptr /* worker_settings */, kV8CacheOptionsDefault, module_responses_map, mojo::NullRemote() /* browser_interface_broker */, @@ -92,7 +94,7 @@ CSSLayoutDefinition* LayoutWorkletGlobalScopeProxy::FindDefinition( return global_scope_->FindDefinition(name); } -void LayoutWorkletGlobalScopeProxy::Trace(blink::Visitor* visitor) { +void LayoutWorkletGlobalScopeProxy::Trace(Visitor* visitor) { visitor->Trace(global_scope_); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h index 46837b1ea3a..de993758af3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h @@ -46,7 +46,7 @@ class CORE_EXPORT LayoutWorkletGlobalScopeProxy LayoutWorkletGlobalScope* global_scope() const { return global_scope_.Get(); } - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: std::unique_ptr<MainThreadWorkletReportingProxy> reporting_proxy_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc index 2e7297a2fd7..c763349959b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.cc @@ -5,11 +5,12 @@ #include "third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h" #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_fragment_result_options.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_intrinsic_sizes_result_options.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_fragment.h" #include "third_party/blink/renderer/core/layout/ng/custom/custom_layout_scope.h" -#include "third_party/blink/renderer/core/layout/ng/custom/fragment_result_options.h" #include "third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h" #include "third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" @@ -30,13 +31,55 @@ NGCustomLayoutAlgorithm::NGCustomLayoutAlgorithm( container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); + const NGConstraintSpace& space = ConstraintSpace(); + child_percentage_resolution_block_size_for_min_max_ = + CalculateChildPercentageBlockSizeForMinMax( + space, Node(), border_padding_, + space.PercentageResolutionBlockSize()); } -base::Optional<MinMaxSize> NGCustomLayoutAlgorithm::ComputeMinMaxSize( - const MinMaxSizeInput& input) const { - // TODO(ikilpatrick): Invoke the web-developer defined "intrinsicSizes" - // method. - return FallbackMinMaxSize(input); +base::Optional<MinMaxSizes> NGCustomLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + if (!Node().IsCustomLayoutLoaded()) + return FallbackMinMaxSizes(input); + + ScriptForbiddenScope::AllowUserAgentScript allow_script; + CustomLayoutScope scope; + + const AtomicString& name = Style().DisplayLayoutCustomName(); + const Document& document = Node().GetDocument(); + LayoutWorklet* worklet = LayoutWorklet::From(*document.domWindow()); + CSSLayoutDefinition* definition = worklet->Proxy()->FindDefinition(name); + + // TODO(ikilpatrick): Cache the instance of the layout class. + CSSLayoutDefinition::Instance* instance = definition->CreateInstance(); + + if (!instance) { + // TODO(ikilpatrick): Report this error to the developer. + return FallbackMinMaxSizes(input); + } + + IntrinsicSizesResultOptions* intrinsic_sizes_result_options = nullptr; + if (!instance->IntrinsicSizes( + ConstraintSpace(), document, Node(), + container_builder_.InitialBorderBoxSize(), border_scrollbar_padding_, + child_percentage_resolution_block_size_for_min_max_, &scope, + intrinsic_sizes_result_options)) { + // TODO(ikilpatrick): Report this error to the developer. + return FallbackMinMaxSizes(input); + } + + MinMaxSizes sizes; + sizes.max_size = LayoutUnit::FromDoubleRound( + intrinsic_sizes_result_options->maxContentSize()); + sizes.min_size = std::min( + sizes.max_size, LayoutUnit::FromDoubleRound( + intrinsic_sizes_result_options->minContentSize())); + + sizes.min_size.ClampNegativeToZero(); + sizes.max_size.ClampNegativeToZero(); + + return sizes; } scoped_refptr<const NGLayoutResult> NGCustomLayoutAlgorithm::Layout() { @@ -61,13 +104,13 @@ scoped_refptr<const NGLayoutResult> NGCustomLayoutAlgorithm::Layout() { return FallbackLayout(); } - FragmentResultOptions* fragment_result_options = - FragmentResultOptions::Create(); + FragmentResultOptions* fragment_result_options = nullptr; scoped_refptr<SerializedScriptValue> fragment_result_data; - if (!instance->Layout(ConstraintSpace(), document, Node(), - container_builder_.InitialBorderBoxSize(), - border_scrollbar_padding_, &scope, - fragment_result_options, &fragment_result_data)) { + if (!instance->Layout( + ConstraintSpace(), document, Node(), + container_builder_.InitialBorderBoxSize(), border_scrollbar_padding_, + child_percentage_resolution_block_size_for_min_max_, &scope, + fragment_result_options, &fragment_result_data)) { // TODO(ikilpatrick): Report this error to the developer. return FallbackLayout(); } @@ -123,6 +166,12 @@ scoped_refptr<const NGLayoutResult> NGCustomLayoutAlgorithm::Layout() { LayoutUnit block_size = ComputeBlockSizeForFragment( ConstraintSpace(), Style(), border_padding_, auto_block_size); + if (fragment_result_options->hasBaseline()) { + LayoutUnit baseline = + LayoutUnit::FromDoubleRound(fragment_result_options->baseline()); + container_builder_.SetBaseline(baseline); + } + container_builder_.SetCustomLayoutData(std::move(fragment_result_data)); container_builder_.SetIntrinsicBlockSize(auto_block_size); container_builder_.SetBlockSize(block_size); @@ -151,10 +200,10 @@ void NGCustomLayoutAlgorithm::AddAnyOutOfFlowPositionedChildren( } } -base::Optional<MinMaxSize> NGCustomLayoutAlgorithm::FallbackMinMaxSize( - const MinMaxSizeInput& input) const { +base::Optional<MinMaxSizes> NGCustomLayoutAlgorithm::FallbackMinMaxSizes( + const MinMaxSizesInput& input) const { NGBlockLayoutAlgorithm algorithm(params_); - return algorithm.ComputeMinMaxSize(input); + return algorithm.ComputeMinMaxSizes(input); } scoped_refptr<const NGLayoutResult> NGCustomLayoutAlgorithm::FallbackLayout() { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h index 7725387252a..5a43340b4a0 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h @@ -20,18 +20,20 @@ class CORE_EXPORT NGCustomLayoutAlgorithm public: NGCustomLayoutAlgorithm(const NGLayoutAlgorithmParams& params); - base::Optional<MinMaxSize> ComputeMinMaxSize( - const MinMaxSizeInput&) const override; + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const override; scoped_refptr<const NGLayoutResult> Layout() override; private: void AddAnyOutOfFlowPositionedChildren(NGLayoutInputNode* child); - base::Optional<MinMaxSize> FallbackMinMaxSize(const MinMaxSizeInput&) const; + base::Optional<MinMaxSizes> FallbackMinMaxSizes( + const MinMaxSizesInput&) const; scoped_refptr<const NGLayoutResult> FallbackLayout(); const NGLayoutAlgorithmParams& params_; const NGBoxStrut border_padding_; const NGBoxStrut border_scrollbar_padding_; + LayoutUnit child_percentage_resolution_block_size_for_min_max_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc index 4239b4da920..644f324ac9c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.cc @@ -42,7 +42,7 @@ void PendingLayoutRegistry::AddPendingLayout(const AtomicString& name, set->insert(node); } -void PendingLayoutRegistry::Trace(blink::Visitor* visitor) { +void PendingLayoutRegistry::Trace(Visitor* visitor) { visitor->Trace(pending_layouts_); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h index ba74e988d1b..8afe379befd 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/custom/pending_layout_registry.h @@ -23,7 +23,7 @@ class PendingLayoutRegistry : public GarbageCollected<PendingLayoutRegistry> { void NotifyLayoutReady(const AtomicString& name); void AddPendingLayout(const AtomicString& name, Node*); - void Trace(blink::Visitor*); + void Trace(Visitor*); private: // This is a map of Nodes which are waiting for a CSSLayoutDefinition to be diff --git a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.cc b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.cc index 0672d751fc4..250278fdfba 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.cc @@ -96,17 +96,6 @@ bool NGLayoutOpportunity::IsBlockDeltaBelowShapes( return true; } -NGLineLayoutOpportunity NGLayoutOpportunity::ComputeLineLayoutOpportunity( - const NGConstraintSpace& space, - LayoutUnit line_block_size, - LayoutUnit block_delta) const { - return NGLineLayoutOpportunity( - ComputeLineLeftOffset(space, line_block_size, block_delta), - ComputeLineRightOffset(space, line_block_size, block_delta), - rect.LineStartOffset(), rect.LineEndOffset(), - rect.BlockStartOffset() + block_delta, line_block_size); -} - LayoutUnit NGLayoutOpportunity::ComputeLineLeftOffset( const NGConstraintSpace& space, LayoutUnit line_block_size, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.h b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.h index abf46f88fff..58734340c0b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/exclusions/ng_layout_opportunity.h @@ -47,9 +47,15 @@ struct CORE_EXPORT NGLayoutOpportunity { // Calculates a line layout opportunity which takes into account any shapes // which may affect the available inline size for the line breaker. NGLineLayoutOpportunity ComputeLineLayoutOpportunity( - const NGConstraintSpace&, + const NGConstraintSpace& space, LayoutUnit line_block_size, - LayoutUnit block_delta) const; + LayoutUnit block_delta) const { + return NGLineLayoutOpportunity( + ComputeLineLeftOffset(space, line_block_size, block_delta), + ComputeLineRightOffset(space, line_block_size, block_delta), + rect.LineStartOffset(), rect.LineEndOffset(), + rect.BlockStartOffset() + block_delta, line_block_size); + } private: LayoutUnit ComputeLineLeftOffset(const NGConstraintSpace&, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h index f5c78a92dad..3c01e9f6085 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h @@ -118,6 +118,10 @@ struct CORE_EXPORT NGLineBoxStrut { LayoutUnit InlineSum() const { return inline_start + inline_end; } LayoutUnit BlockSum() const { return line_over + line_under; } + bool IsEmpty() const { + return !inline_start && !inline_end && !line_over && !line_under; + } + bool operator==(const NGLineBoxStrut& other) const { return inline_start == other.inline_start && inline_end == other.inline_end && line_over == other.line_over && diff --git a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h index 8236afa14ee..759d7091b66 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h @@ -26,8 +26,7 @@ struct CORE_EXPORT NGMarginStrut { // See comment inside NGBlockLayoutAlgorithm for when this occurs. bool is_quirky_container_start = false; - // If set, we will discard all adjoining margins, which is the - // effect of -webkit-margin-collapse:discard. + // If set, we will discard all adjoining margins. bool discard_margins = false; // Appends negative or positive value to the current margin strut. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc index bd55b43700a..da28e31bfcf 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_test.cc @@ -47,6 +47,20 @@ class LayoutNGTextTest : public PageTestBase { } }; +TEST_F(LayoutNGTextTest, SetTextWithOffsetAppendBidi) { + if (!RuntimeEnabledFeatures::LayoutNGEnabled()) + return; + + SetBodyInnerHTML(u"<div dir=rtl id=target>\u05D0\u05D1\u05BC\u05D2</div>"); + Text& text = To<Text>(*GetElementById("target")->firstChild()); + text.appendData(u"\u05D0\u05D1\u05BC\u05D2"); + + EXPECT_EQ(String(u"*{'\u05D0\u05D1\u05BC\u05D2\u05D0\u05D1\u05BC\u05D2', " + u"ShapeResult=0+8}\n") + .Utf8(), + GetItemsAsString(*text.GetLayoutObject())); +} + TEST_F(LayoutNGTextTest, SetTextWithOffsetAppendControl) { if (!RuntimeEnabledFeatures::LayoutNGEnabled()) return; @@ -136,8 +150,11 @@ TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteRTL) { Text& text = To<Text>(*GetElementById("target")->firstChild()); text.deleteData(2, 2, ASSERT_NO_EXCEPTION); // remove "23" - EXPECT_EQ("*{'0 4', ShapeResult=0+3}\n", - GetItemsAsString(*text.GetLayoutObject())); + EXPECT_EQ( + "*{'0', ShapeResult=0+1}\n" + "*{' ', ShapeResult=1+1}\n" + "*{'4', ShapeResult=2+1}\n", + GetItemsAsString(*text.GetLayoutObject())); } // http://crbug.com/1000685 @@ -149,7 +166,26 @@ TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteRTL2) { Text& text = To<Text>(*GetElementById("target")->firstChild()); text.deleteData(0, 1, ASSERT_NO_EXCEPTION); // remove "0" - EXPECT_EQ("*{'(xy)5', ShapeResult=0+5}\n", + EXPECT_EQ( + "*{'(', ShapeResult=0+1}\n" + "*{'xy', ShapeResult=1+2}\n" + "*{')', ShapeResult=3+1}\n" + "*{'5', ShapeResult=4+1}\n", + GetItemsAsString(*text.GetLayoutObject())); +} + +// http://crbug.com/1039143 +TEST_F(LayoutNGTextTest, SetTextWithOffsetDeleteWithBidiControl) { + if (!RuntimeEnabledFeatures::LayoutNGEnabled()) + return; + + // In text content, we have bidi control codes: + // U+2066 U+2069 \n U+2066 abc U+2066 + SetBodyInnerHTML(u"<pre><b id=target dir=ltr>\nabc</b></pre>"); + Text& text = To<Text>(*GetElementById("target")->firstChild()); + text.deleteData(0, 1, ASSERT_NO_EXCEPTION); // remove "\n" + + EXPECT_EQ("LayoutText has NeedsCollectInlines", GetItemsAsString(*text.GetLayoutObject())); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc index 8a5f0a7201d..34643cd55f2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.cc @@ -5,7 +5,10 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" @@ -16,38 +19,90 @@ namespace blink { -NGAbstractInlineTextBox::FragmentToNGAbstractInlineTextBoxHashMap* - NGAbstractInlineTextBox::g_abstract_inline_text_box_map_ = nullptr; +namespace { + +// Mapping from NGFragmentItem/NGPaintFragment to NGAbstractInlineTextBox +// TODO(yosin): Once we get rid of |NGPaintFragment|, we should not use +// template class for |NGAbstractInlineTextBoxCache|. +template <typename Fragment> +class NGAbstractInlineTextBoxCache final { + public: + static scoped_refptr<AbstractInlineTextBox> GetOrCreate( + const Fragment& fragment) { + if (!s_instance_) + s_instance_ = new NGAbstractInlineTextBoxCache(); + return s_instance_->GetOrCreateInternal(fragment); + } + + static void WillDestroy(const Fragment* fragment) { + if (!s_instance_) + return; + s_instance_->WillDestroyInternal(fragment); + } + + private: + scoped_refptr<AbstractInlineTextBox> GetOrCreateInternal( + const Fragment& fragment) { + const auto it = map_.find(&fragment); + LayoutText* const layout_text = + ToLayoutText(fragment.GetMutableLayoutObject()); + if (it != map_.end()) { + CHECK(layout_text->HasAbstractInlineTextBox()); + return it->value; + } + scoped_refptr<AbstractInlineTextBox> obj = base::AdoptRef( + new NGAbstractInlineTextBox(LineLayoutText(layout_text), fragment)); + map_.Set(&fragment, obj); + layout_text->SetHasAbstractInlineTextBox(); + return obj; + } + + void WillDestroyInternal(const Fragment* fragment) { + const auto it = map_.find(fragment); + if (it == map_.end()) + return; + it->value->Detach(); + map_.erase(fragment); + } + + static NGAbstractInlineTextBoxCache* s_instance_; + + HashMap<const Fragment*, scoped_refptr<AbstractInlineTextBox>> map_; +}; + +template <typename Fragment> +NGAbstractInlineTextBoxCache<Fragment>* + NGAbstractInlineTextBoxCache<Fragment>::s_instance_ = nullptr; + +} // namespace scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::GetOrCreate( - const NGPaintFragment& fragment) { - DCHECK(fragment.GetLayoutObject()->IsText()) << fragment.GetLayoutObject(); - if (!g_abstract_inline_text_box_map_) { - g_abstract_inline_text_box_map_ = - new FragmentToNGAbstractInlineTextBoxHashMap(); + const NGInlineCursor& cursor) { + if (const NGPaintFragment* paint_fragment = cursor.CurrentPaintFragment()) { + return NGAbstractInlineTextBoxCache<NGPaintFragment>::GetOrCreate( + *paint_fragment); } - const auto it = g_abstract_inline_text_box_map_->find(&fragment); - LayoutText* const layout_text = - ToLayoutText(fragment.GetMutableLayoutObject()); - if (it != g_abstract_inline_text_box_map_->end()) { - CHECK(layout_text->HasAbstractInlineTextBox()); - return it->value; + if (const NGFragmentItem* fragment_item = cursor.CurrentItem()) { + return NGAbstractInlineTextBoxCache<NGFragmentItem>::GetOrCreate( + *fragment_item); } - scoped_refptr<AbstractInlineTextBox> obj = base::AdoptRef( - new NGAbstractInlineTextBox(LineLayoutText(layout_text), fragment)); - g_abstract_inline_text_box_map_->Set(&fragment, obj); - layout_text->SetHasAbstractInlineTextBox(); - return obj; + return nullptr; } -void NGAbstractInlineTextBox::WillDestroy(NGPaintFragment* fragment) { - if (!g_abstract_inline_text_box_map_) - return; - const auto it = g_abstract_inline_text_box_map_->find(fragment); - if (it != g_abstract_inline_text_box_map_->end()) { - it->value->Detach(); - g_abstract_inline_text_box_map_->erase(fragment); +void NGAbstractInlineTextBox::WillDestroy(const NGInlineCursor& cursor) { + if (const NGPaintFragment* paint_fragment = cursor.CurrentPaintFragment()) { + return NGAbstractInlineTextBoxCache<NGPaintFragment>::WillDestroy( + paint_fragment); + } + if (const NGFragmentItem* fragment_item = cursor.CurrentItem()) { + return NGAbstractInlineTextBoxCache<NGFragmentItem>::WillDestroy( + fragment_item); } + NOTREACHED(); +} + +void NGAbstractInlineTextBox::WillDestroy(const NGPaintFragment* fragment) { + NGAbstractInlineTextBoxCache<NGPaintFragment>::WillDestroy(fragment); } NGAbstractInlineTextBox::NGAbstractInlineTextBox( @@ -57,6 +112,13 @@ NGAbstractInlineTextBox::NGAbstractInlineTextBox( DCHECK(fragment_->PhysicalFragment().IsText()) << fragment_; } +NGAbstractInlineTextBox::NGAbstractInlineTextBox( + LineLayoutText line_layout_item, + const NGFragmentItem& fragment_item) + : AbstractInlineTextBox(line_layout_item), fragment_item_(&fragment_item) { + DCHECK(fragment_item_->IsText()) << fragment_item_; +} + NGAbstractInlineTextBox::~NGAbstractInlineTextBox() { DCHECK(!fragment_); } @@ -70,107 +132,128 @@ void NGAbstractInlineTextBox::Detach() { fragment_ = nullptr; } -const NGPhysicalTextFragment& NGAbstractInlineTextBox::PhysicalTextFragment() - const { - return To<NGPhysicalTextFragment>(fragment_->PhysicalFragment()); +NGInlineCursor NGAbstractInlineTextBox::GetCursor() const { + if (!fragment_item_) + return NGInlineCursor(); + NGInlineCursor cursor; + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + cursor.MoveTo(*fragment_item_); + else + cursor.MoveTo(*fragment_); + DCHECK(!cursor.Current().GetLayoutObject()->NeedsLayout()); + return cursor; +} + +NGInlineCursor NGAbstractInlineTextBox::GetCursorOnLine() const { + NGInlineCursor current = GetCursor(); + NGInlineCursor line_box = current; + line_box.MoveToContainingLine(); + NGInlineCursor cursor = line_box.CursorForDescendants(); + cursor.MoveTo(current); + return cursor; } -bool NGAbstractInlineTextBox::NeedsLayout() const { - return fragment_->GetLayoutObject()->NeedsLayout(); +String NGAbstractInlineTextBox::GetTextContent() const { + const NGInlineCursor& cursor = GetCursor(); + if (cursor.Current().IsGeneratedTextType()) + return cursor.Current().Text(cursor).ToString(); + if (const NGPaintFragment* paint_fragment = cursor.CurrentPaintFragment()) { + return To<NGPhysicalTextFragment>(paint_fragment->PhysicalFragment()) + .TextContent(); + } + return cursor.Items().Text(cursor.Current().UsesFirstLineStyle()); } bool NGAbstractInlineTextBox::NeedsTrailingSpace() const { - if (!fragment_->Style().CollapseWhiteSpace()) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor.Current().Style().CollapseWhiteSpace()) return false; - const NGPaintFragment& line_box = *fragment_->ContainerLineBox(); - if (!To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment()) - .HasSoftWrapToNextLine()) + NGInlineCursor line_box = cursor; + line_box.MoveToContainingLine(); + if (!line_box.HasSoftWrapToNextLine()) return false; - const NGPhysicalTextFragment& text_fragment = PhysicalTextFragment(); - if (text_fragment.EndOffset() >= text_fragment.TextContent().length()) + const String text_content = GetTextContent(); + const unsigned end_offset = cursor.Current().TextEndOffset(); + if (end_offset >= text_content.length()) return false; - if (text_fragment.TextContent()[text_fragment.EndOffset()] != ' ') + if (text_content[end_offset] != ' ') return false; - const NGInlineBreakToken& break_token = *To<NGInlineBreakToken>( - To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment()).BreakToken()); + const NGInlineBreakToken* break_token = line_box.Current().InlineBreakToken(); + DCHECK(break_token); // TODO(yosin): We should support OOF fragments between |fragment_| and // break token. - if (break_token.TextOffset() != text_fragment.EndOffset() + 1) + if (break_token->TextOffset() != end_offset + 1) return false; // Check a character in text content after |fragment_| comes from same // layout text of |fragment_|. - const NGOffsetMapping* mapping = - NGOffsetMapping::GetFor(fragment_->GetLayoutObject()); + const LayoutObject* const layout_object = cursor.Current().GetLayoutObject(); + const NGOffsetMapping* mapping = NGOffsetMapping::GetFor(layout_object); // TODO(kojii): There's not much we can do for dirty-tree. crbug.com/946004 if (!mapping) return false; const base::span<const NGOffsetMappingUnit> mapping_units = - mapping->GetMappingUnitsForTextContentOffsetRange( - text_fragment.EndOffset(), text_fragment.EndOffset() + 1); + mapping->GetMappingUnitsForTextContentOffsetRange(end_offset, + end_offset + 1); if (mapping_units.begin() == mapping_units.end()) return false; const NGOffsetMappingUnit& mapping_unit = mapping_units.front(); - return mapping_unit.GetLayoutObject() == fragment_->GetLayoutObject(); -} - -const NGPaintFragment* -NGAbstractInlineTextBox::NextTextFragmentForSameLayoutObject() const { - const auto fragments = - NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject()); - const auto it = - std::find_if(fragments.begin(), fragments.end(), - [&](const auto& sibling) { return fragment_ == sibling; }); - DCHECK(it != fragments.end()); - const auto next_it = std::next(it); - return next_it == fragments.end() ? nullptr : *next_it; + return mapping_unit.GetLayoutObject() == layout_object; } scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::NextInlineTextBox() const { - if (!fragment_) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return nullptr; - DCHECK(!NeedsLayout()); - const NGPaintFragment* next_fragment = NextTextFragmentForSameLayoutObject(); - if (!next_fragment) + NGInlineCursor next; + next.MoveTo(*cursor.Current().GetLayoutObject()); + while (next != cursor) + next.MoveToNextForSameLayoutObject(); + next.MoveToNextForSameLayoutObject(); + if (!next) return nullptr; - return GetOrCreate(*next_fragment); + return GetOrCreate(next); } LayoutRect NGAbstractInlineTextBox::LocalBounds() const { - if (!fragment_ || !GetLineLayoutItem()) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return LayoutRect(); - return LayoutRect(fragment_->InlineOffsetToContainerBox().ToLayoutPoint(), - fragment_->Size().ToLayoutSize()); + return cursor.Current().RectInContainerBlock().ToLayoutRect(); } unsigned NGAbstractInlineTextBox::Len() const { - if (!fragment_) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return 0; if (NeedsTrailingSpace()) - return PhysicalTextFragment().TextLength() + 1; - return PhysicalTextFragment().TextLength(); + return cursor.Current().Text(cursor).length() + 1; + return cursor.Current().Text(cursor).length(); } unsigned NGAbstractInlineTextBox::TextOffsetInContainer(unsigned offset) const { - if (!fragment_) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return 0; - return PhysicalTextFragment().StartOffset() + offset; + return cursor.Current().TextStartOffset() + offset; } AbstractInlineTextBox::Direction NGAbstractInlineTextBox::GetDirection() const { - if (!fragment_ || !GetLineLayoutItem()) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return kLeftToRight; - const TextDirection text_direction = - PhysicalTextFragment().ResolvedDirection(); + const TextDirection text_direction = cursor.Current().ResolvedDirection(); if (GetLineLayoutItem().Style()->IsHorizontalWritingMode()) return IsLtr(text_direction) ? kLeftToRight : kRightToLeft; return IsLtr(text_direction) ? kTopToBottom : kBottomToTop; } void NGAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const { - if (!fragment_) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return; - if (!PhysicalTextFragment().TextShapeResult()) { + const ShapeResultView* shape_result_view = cursor.Current().TextShapeResult(); + if (!shape_result_view) { // When |fragment_| for BR, we don't have shape result. // "aom-computed-boolean-properties.html" reaches here. widths.resize(Len()); @@ -178,8 +261,8 @@ void NGAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const { } // TODO(layout-dev): Add support for IndividualCharacterRanges to // ShapeResultView to avoid the copy below. - auto shape_result = - PhysicalTextFragment().TextShapeResult()->CreateShapeResult(); + scoped_refptr<ShapeResult> shape_result = + shape_result_view->CreateShapeResult(); Vector<CharacterRange> ranges; shape_result->IndividualCharacterRanges(&ranges); widths.ReserveCapacity(ranges.size()); @@ -193,10 +276,11 @@ void NGAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const { } String NGAbstractInlineTextBox::GetText() const { - if (!fragment_ || !GetLineLayoutItem()) - return String(); + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) + return g_empty_string; - String result = PhysicalTextFragment().Text().ToString(); + String result = cursor.Current().Text(cursor).ToString(); // For compatibility with |InlineTextBox|, we should have a space character // for soft line break. @@ -217,56 +301,51 @@ String NGAbstractInlineTextBox::GetText() const { } bool NGAbstractInlineTextBox::IsFirst() const { - if (!fragment_) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return true; - DCHECK(!NeedsLayout()); - const auto fragments = - NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject()); - return fragment_ == &fragments.front(); + NGInlineCursor first_fragment; + first_fragment.MoveTo(*cursor.Current().GetLayoutObject()); + return cursor == first_fragment; } bool NGAbstractInlineTextBox::IsLast() const { - if (!fragment_) + const NGInlineCursor& cursor = GetCursor(); + if (!cursor) return true; - DCHECK(!NeedsLayout()); - const auto fragments = - NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject()); - return fragment_ == &fragments.back(); + NGInlineCursor last_fragment; + last_fragment.MoveTo(*cursor.Current().GetLayoutObject()); + last_fragment.MoveToLastForSameLayoutObject(); + return cursor == last_fragment; } scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::NextOnLine() const { - if (!fragment_) + NGInlineCursor cursor = GetCursorOnLine(); + if (!cursor) return nullptr; - DCHECK(!NeedsLayout()); - DCHECK(fragment_->ContainerLineBox()); - NGPaintFragmentTraversal cursor(*fragment_->ContainerLineBox(), *fragment_); - for (cursor.MoveToNext(); !cursor.IsAtEnd(); cursor.MoveToNext()) { - if (cursor->GetLayoutObject()->IsText()) - return GetOrCreate(*cursor); + for (cursor.MoveToNext(); cursor; cursor.MoveToNext()) { + if (cursor.Current().GetLayoutObject()->IsText()) + return GetOrCreate(cursor); } return nullptr; } scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::PreviousOnLine() const { - if (!fragment_) + NGInlineCursor cursor = GetCursorOnLine(); + if (!cursor) return nullptr; - DCHECK(!NeedsLayout()); - DCHECK(fragment_->ContainerLineBox()); - NGPaintFragmentTraversal cursor(*fragment_->ContainerLineBox(), *fragment_); - for (cursor.MoveToPrevious(); !cursor.IsAtEnd(); cursor.MoveToPrevious()) { - if (cursor->GetLayoutObject()->IsText()) - return GetOrCreate(*cursor); + for (cursor.MoveToPrevious(); cursor; cursor.MoveToPrevious()) { + if (cursor.Current().GetLayoutObject()->IsText()) + return GetOrCreate(cursor); } return nullptr; } bool NGAbstractInlineTextBox::IsLineBreak() const { - if (!fragment_) - return false; - DCHECK(!NeedsLayout()); - return PhysicalTextFragment().IsLineBreak(); + const NGInlineCursor& cursor = GetCursor(); + return cursor && cursor.Current().IsLineBreak(); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h index 1f1d57dcef5..85fec4b6631 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_abstract_inline_text_box.h @@ -9,34 +9,36 @@ namespace blink { +class NGFragmentItem; +class NGInlineCursor; class NGPaintFragment; -class NGPhysicalTextFragment; // The implementation of |AbstractInlineTextBox| for LayoutNG. // See also |LegacyAbstractInlineTextBox| for legacy layout. class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox { private: // Returns existing or newly created |NGAbstractInlineTextBox|. - // * |fragment| should be attached to |NGPhysicalTextFragment|. + // * |cursor| should be attached to |NGPhysicalTextFragment|. static scoped_refptr<AbstractInlineTextBox> GetOrCreate( - const NGPaintFragment& fragment); - static void WillDestroy(NGPaintFragment*); + const NGInlineCursor& cursor); + static void WillDestroy(const NGInlineCursor& cursor); + static void WillDestroy(const NGPaintFragment* fragment); friend class LayoutText; - friend class NGPaintFragment; public: - ~NGAbstractInlineTextBox() final; - - private: NGAbstractInlineTextBox(LineLayoutText line_layout_item, const NGPaintFragment& fragment); + NGAbstractInlineTextBox(LineLayoutText line_layout_item, + const NGFragmentItem& fragment); - const NGPhysicalTextFragment& PhysicalTextFragment() const; - bool NeedsLayout() const; + ~NGAbstractInlineTextBox() final; + + private: + NGInlineCursor GetCursor() const; + NGInlineCursor GetCursorOnLine() const; + String GetTextContent() const; bool NeedsTrailingSpace() const; - // Returns next fragment associated to |LayoutText|. - const NGPaintFragment* NextTextFragmentForSameLayoutObject() const; // Implementations of AbstractInlineTextBox member functions. void Detach() final; @@ -53,12 +55,10 @@ class CORE_EXPORT NGAbstractInlineTextBox final : public AbstractInlineTextBox { scoped_refptr<AbstractInlineTextBox> PreviousOnLine() const final; bool IsLineBreak() const final; - const NGPaintFragment* fragment_; - - using FragmentToNGAbstractInlineTextBoxHashMap = - HashMap<const NGPaintFragment*, scoped_refptr<AbstractInlineTextBox>>; - static FragmentToNGAbstractInlineTextBoxHashMap* - g_abstract_inline_text_box_map_; + union { + const NGPaintFragment* fragment_; + const NGFragmentItem* fragment_item_; + }; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc deleted file mode 100644 index f9059f05c5f..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" - -#include "third_party/blink/renderer/core/layout/layout_block.h" -#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" -#include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" - -namespace blink { - -const unsigned NGBaselineRequest::kTypeIdCount; -const LayoutUnit NGBaselineList::kEmptyOffset; - -bool NGBaselineRequest::operator==(const NGBaselineRequest& other) const { - return algorithm_type_ == other.algorithm_type_ && - baseline_type_ == other.baseline_type_; -} - -bool NGBaselineRequestList::operator==( - const NGBaselineRequestList& other) const { - return type_id_mask_ == other.type_id_mask_; -} - -void NGBaselineRequestList::push_back(const NGBaselineRequest& request) { - type_id_mask_ |= 1 << request.TypeId(); -} - -void NGBaselineRequestList::AppendVector( - const NGBaselineRequestList& requests) { - type_id_mask_ |= requests.type_id_mask_; -} - -bool NGBaseline::ShouldPropagateBaselines(const NGLayoutInputNode node) { - if (node.IsInline()) - return true; - - return ShouldPropagateBaselines(node.GetLayoutBox()); -} - -bool NGBaseline::ShouldPropagateBaselines(LayoutBox* layout_box) { - // Test if this node should use its own box to synthesize the baseline. - if (!layout_box->IsLayoutBlock() || - layout_box->IsFloatingOrOutOfFlowPositioned() || - layout_box->IsWritingModeRoot()) - return false; - - // If this node is LayoutBlock that uses old layout, this may be a subclass - // that overrides baseline functions. Propagate baseline requests so that we - // call virtual functions. - if (!NGBlockNode(layout_box).CanUseNewLayout()) - return true; - - return true; -} - -NGBaselineList::NGBaselineList() { - std::fill(std::begin(offsets_), std::end(offsets_), kEmptyOffset); -} - -bool NGBaselineList::IsEmpty() const { - for (LayoutUnit offset : offsets_) { - if (offset != kEmptyOffset) - return false; - } - return true; -} - -base::Optional<LayoutUnit> NGBaselineList::Offset( - const NGBaselineRequest request) const { - LayoutUnit offset = offsets_[request.TypeId()]; - if (offset != kEmptyOffset) - return offset; - return base::nullopt; -} - -void NGBaselineList::emplace_back(NGBaselineRequest request, - LayoutUnit offset) { - // Round LayoutUnit::Min() because we use it for an empty value. - DCHECK_EQ(kEmptyOffset, LayoutUnit::Min()) - << "Change the rounding if kEmptyOffset was changed"; - if (UNLIKELY(offset == LayoutUnit::Min())) - offset = LayoutUnit::NearlyMin(); - DCHECK_NE(offset, kEmptyOffset); - offsets_[request.TypeId()] = offset; -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h deleted file mode 100644 index 64426201e06..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_BASELINE_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_BASELINE_H_ - -#include "base/optional.h" -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/platform/fonts/font_baseline.h" -#include "third_party/blink/renderer/platform/geometry/layout_unit.h" -#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" - -namespace blink { - -class LayoutBox; -class NGLayoutInputNode; - -enum class NGBaselineAlgorithmType { - // Compute baselines for atomic inlines. - kAtomicInline, - // Compute baseline of first line box. - kFirstLine -}; - -// Baselines are products of layout. -// To compute baseline, add requests to NGConstraintSpace and run Layout(). -class CORE_EXPORT NGBaselineRequest { - DISALLOW_NEW(); - - public: - NGBaselineRequest(NGBaselineAlgorithmType algorithm_type, - FontBaseline baseline_type) - : algorithm_type_(static_cast<unsigned>(algorithm_type)), - baseline_type_(static_cast<unsigned>(baseline_type)) {} - - NGBaselineAlgorithmType AlgorithmType() const { - return static_cast<NGBaselineAlgorithmType>(algorithm_type_); - } - - FontBaseline BaselineType() const { - return static_cast<FontBaseline>(baseline_type_); - } - - bool operator==(const NGBaselineRequest& other) const; - bool operator!=(const NGBaselineRequest& other) const { - return !(*this == other); - } - - private: - // TypeId is an integer that identifies all combinations of - // |NGBaselineRequest|. Visible only to |NGBaselineRequestList| and - // |NGBaselineList|. - static constexpr unsigned kTypeIdCount = 4; - unsigned TypeId() const { return algorithm_type_ | (baseline_type_ << 1); } - static NGBaselineRequest FromTypeId(unsigned type_id) { - DCHECK_LE(type_id, kTypeIdCount); - return NGBaselineRequest(static_cast<NGBaselineAlgorithmType>(type_id & 1), - static_cast<FontBaseline>((type_id >> 1) & 1)); - } - friend class NGBaselineList; - friend class NGBaselineRequestList; - friend class NGBaselineTest; - - unsigned algorithm_type_ : 1; // NGBaselineAlgorithmType - unsigned baseline_type_ : 1; // FontBaseline -}; - -// A list of |NGBaselineRequest| in a packed format, with similar interface as -// |Vector|. -class CORE_EXPORT NGBaselineRequestList { - DISALLOW_NEW(); - - public: - NGBaselineRequestList() = default; - - bool IsEmpty() const { return !type_id_mask_; } - - bool operator==(const NGBaselineRequestList& other) const; - - void push_back(const NGBaselineRequest& request); - void AppendVector(const NGBaselineRequestList& requests); - - class const_iterator { - DISALLOW_NEW(); - - public: - const_iterator() : type_id_(NGBaselineRequest::kTypeIdCount), mask_(0) {} - explicit const_iterator(unsigned mask) : type_id_(0), mask_(mask) { - if (!(mask_ & 1)) - ++(*this); - } - - const NGBaselineRequest operator*() const { - return NGBaselineRequest::FromTypeId(type_id_); - } - bool operator!=(const const_iterator& other) const { - return type_id_ != other.type_id_; - } - void operator++() { - while (type_id_ < NGBaselineRequest::kTypeIdCount) { - ++type_id_; - mask_ >>= 1; - if (mask_ & 1) - break; - } - } - - private: - unsigned type_id_; - unsigned mask_; - }; - - const_iterator begin() const { return const_iterator(type_id_mask_); } - const_iterator end() const { return const_iterator(); } - - private: - // Serialize/deserialize to a bit fields. - static constexpr unsigned kSerializedBits = NGBaselineRequest::kTypeIdCount; - unsigned Serialize() const { return type_id_mask_; } - explicit NGBaselineRequestList(unsigned serialized) - : type_id_mask_(serialized) {} - friend class NGConstraintSpace; - friend class NGConstraintSpaceBuilder; - - unsigned type_id_mask_ = 0; -}; - -// Represents a computed baseline position. -struct CORE_EXPORT NGBaseline { - NGBaselineRequest request; - LayoutUnit offset; - - // @return if the node needs to propagate baseline requests/results. - static bool ShouldPropagateBaselines(const NGLayoutInputNode); - static bool ShouldPropagateBaselines(LayoutBox*); -}; - -// A list of |NGBaseline| in a packed format, with similar interface as -// |Vector|. -class CORE_EXPORT NGBaselineList { - DISALLOW_NEW(); - - public: - NGBaselineList(); - - bool IsEmpty() const; - - base::Optional<LayoutUnit> Offset(const NGBaselineRequest request) const; - - void emplace_back(NGBaselineRequest request, LayoutUnit offset); - -#if DCHECK_IS_ON() - bool operator==(const NGBaselineList& other) const { - for (wtf_size_t i = 0; i < NGBaselineRequest::kTypeIdCount; ++i) { - if (offsets_[i] != other.offsets_[i]) - return false; - } - - return true; - } -#endif - - class const_iterator { - public: - explicit const_iterator(unsigned type_id, const LayoutUnit* offset) - : type_id_(type_id), offset_(offset) { - DCHECK(offset); - if (*offset == kEmptyOffset) - ++(*this); - } - const_iterator() - : type_id_(NGBaselineRequest::kTypeIdCount), offset_(nullptr) {} - - const NGBaseline operator*() const { - return NGBaseline{NGBaselineRequest::FromTypeId(type_id_), *offset_}; - } - bool operator!=(const const_iterator& other) const { - return type_id_ != other.type_id_; - } - void operator++() { - while (type_id_ < NGBaselineRequest::kTypeIdCount) { - ++type_id_; - ++offset_; - if (type_id_ < NGBaselineRequest::kTypeIdCount && - *offset_ != kEmptyOffset) - break; - } - } - - private: - unsigned type_id_; - const LayoutUnit* offset_; - }; - - const_iterator begin() const { return const_iterator(0, offsets_); } - const_iterator end() const { return const_iterator(); } - - private: - static constexpr LayoutUnit kEmptyOffset = LayoutUnit::Min(); - - LayoutUnit offsets_[NGBaselineRequest::kTypeIdCount]; -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_BASELINE_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline_test.cc deleted file mode 100644 index 5278637e097..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_baseline_test.cc +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" - -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/renderer/platform/wtf/vector.h" - -namespace blink { - -using ::testing::ElementsAre; -using ::testing::ElementsAreArray; - -class NGBaselineTest : public testing::Test { - public: - static NGBaselineRequest RequestFromTypeId(unsigned type_id) { - return NGBaselineRequest::FromTypeId(type_id); - } - - static Vector<NGBaselineRequest> ToList(NGBaselineRequestList requests) { - Vector<NGBaselineRequest> list; - for (const NGBaselineRequest request : requests) - list.push_back(request); - return list; - } -}; - -struct NGBaselineRequestListTestData { - unsigned count; - unsigned type_ids[4]; -} baseline_request_list_test_data[] = { - {0, {}}, {1, {0}}, {1, {1}}, {1, {2}}, - {1, {3}}, {2, {0, 1}}, {2, {0, 2}}, {2, {1, 3}}, - {3, {0, 1, 2}}, {3, {0, 2, 3}}, {3, {1, 2, 3}}, {4, {0, 1, 2, 3}}, -}; - -class NGBaselineRequestListDataTest - : public NGBaselineTest, - public testing::WithParamInterface<NGBaselineRequestListTestData> {}; - -INSTANTIATE_TEST_SUITE_P(NGBaselineTest, - NGBaselineRequestListDataTest, - testing::ValuesIn(baseline_request_list_test_data)); - -TEST_P(NGBaselineRequestListDataTest, Data) { - const auto& data = GetParam(); - NGBaselineRequestList requests; - Vector<NGBaselineRequest> expected; - for (unsigned i = 0; i < data.count; i++) { - NGBaselineRequest request = RequestFromTypeId(data.type_ids[i]); - requests.push_back(request); - expected.push_back(request); - } - - EXPECT_EQ(requests.IsEmpty(), !data.count); - - Vector<NGBaselineRequest> actual = ToList(requests); - EXPECT_THAT(actual, expected); -} - -TEST_F(NGBaselineTest, BaselineList) { - NGBaselineList list; - EXPECT_TRUE(list.IsEmpty()); - - NGBaselineRequest request(NGBaselineAlgorithmType::kFirstLine, - FontBaseline::kAlphabeticBaseline); - list.emplace_back(request, LayoutUnit(123)); - EXPECT_FALSE(list.IsEmpty()); - EXPECT_EQ(list.Offset(request), LayoutUnit(123)); - EXPECT_FALSE(list.Offset({NGBaselineAlgorithmType::kFirstLine, - FontBaseline::kIdeographicBaseline})); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc index b01d591bf48..ed88fbe192b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc @@ -90,11 +90,11 @@ CaretPositionResolution TryResolveCaretPositionInTextFragment( const NGInlineCursor& cursor, unsigned offset, TextAffinity affinity) { - if (cursor.IsGeneratedText()) + if (cursor.Current().IsGeneratedText()) return CaretPositionResolution(); const NGOffsetMapping& mapping = - *NGOffsetMapping::GetFor(cursor.CurrentLayoutObject()); + *NGOffsetMapping::GetFor(cursor.Current().GetLayoutObject()); // A text fragment natually allows caret placement in offset range // [StartOffset(), EndOffset()], i.e., from before the first character to @@ -106,12 +106,13 @@ CaretPositionResolution TryResolveCaretPositionInTextFragment( // Note that we don't ignore other characters that are not in fragments. For // example, a trailing space of a line is not in any fragment, but its two // sides are still different caret positions, so we don't ignore it. - const unsigned start_offset = cursor.CurrentTextStartOffset(); - const unsigned end_offset = cursor.CurrentTextEndOffset(); + const NGTextOffset current_offset = cursor.Current().TextOffset(); + const unsigned start_offset = current_offset.start; + const unsigned end_offset = current_offset.end; if (offset < start_offset && !mapping.HasBidiControlCharactersOnly(offset, start_offset)) return CaretPositionResolution(); - if (offset > cursor.CurrentTextEndOffset() && + if (offset > current_offset.end && !mapping.HasBidiControlCharactersOnly(end_offset, offset)) return CaretPositionResolution(); @@ -129,7 +130,7 @@ CaretPositionResolution TryResolveCaretPositionInTextFragment( return {ResolutionType::kResolved, candidate}; } - if (offset == end_offset && !cursor.IsLineBreak() && + if (offset == end_offset && !cursor.Current().IsLineBreak() && CanResolveCaretPositionAfterFragment(cursor, affinity)) { return {ResolutionType::kResolved, candidate}; } @@ -157,7 +158,7 @@ CaretPositionResolution TryResolveCaretPositionByBoxFragmentSide( const NGInlineCursor& cursor, unsigned offset, TextAffinity affinity) { - const Node* const node = cursor.CurrentNode(); + const Node* const node = cursor.Current().GetNode(); // There is no caret position at a pseudo or generated box side. if (!node || node->IsPseudoElement()) { // TODO(xiaochengh): This leads to false negatives for, e.g., RUBY, where an @@ -192,9 +193,9 @@ CaretPositionResolution TryResolveCaretPositionWithFragment( const NGInlineCursor& cursor, unsigned offset, TextAffinity affinity) { - if (cursor.IsText()) + if (cursor.Current().IsText()) return TryResolveCaretPositionInTextFragment(cursor, offset, affinity); - if (cursor.IsAtomicInline()) + if (cursor.Current().IsAtomicInline()) return TryResolveCaretPositionByBoxFragmentSide(cursor, offset, affinity); return CaretPositionResolution(); } @@ -207,8 +208,9 @@ bool NeedsBidiAdjustment(const NGCaretPosition& caret_position) { if (caret_position.position_type != NGCaretPositionType::kAtTextOffset) return true; DCHECK(caret_position.text_offset.has_value()); - const unsigned start_offset = caret_position.cursor.CurrentTextStartOffset(); - const unsigned end_offset = caret_position.cursor.CurrentTextEndOffset(); + const NGTextOffset offset = caret_position.cursor.Current().TextOffset(); + const unsigned start_offset = offset.start; + const unsigned end_offset = offset.end; DCHECK_GE(*caret_position.text_offset, start_offset); DCHECK_LE(*caret_position.text_offset, end_offset); // Bidi adjustment is needed only for caret positions at bidi boundaries. @@ -232,10 +234,10 @@ bool IsUpstreamAfterLineBreak(const NGCaretPosition& caret_position) { DCHECK(caret_position.cursor.IsNotNull()); DCHECK(caret_position.text_offset.has_value()); - if (!caret_position.cursor.IsLineBreak()) + if (!caret_position.cursor.Current().IsLineBreak()) return false; return *caret_position.text_offset == - caret_position.cursor.CurrentTextEndOffset(); + caret_position.cursor.Current().TextEndOffset(); } NGCaretPosition BetterCandidateBetween(const NGCaretPosition& current, @@ -320,22 +322,24 @@ PositionWithAffinity NGCaretPosition::ToPositionInDOMTreeWithAffinity() const { return PositionWithAffinity(); switch (position_type) { case NGCaretPositionType::kBeforeBox: - if (cursor.CurrentNode()) + if (cursor.Current().GetNode()) return PositionWithAffinity(); - return PositionWithAffinity(Position::BeforeNode(*cursor.CurrentNode()), - TextAffinity::kDownstream); + return PositionWithAffinity( + Position::BeforeNode(*cursor.Current().GetNode()), + TextAffinity::kDownstream); case NGCaretPositionType::kAfterBox: - if (cursor.CurrentNode()) + if (cursor.Current().GetNode()) return PositionWithAffinity(); - return PositionWithAffinity(Position::AfterNode(*cursor.CurrentNode()), - TextAffinity::kUpstreamIfPossible); + return PositionWithAffinity( + Position::AfterNode(*cursor.Current().GetNode()), + TextAffinity::kUpstreamIfPossible); case NGCaretPositionType::kAtTextOffset: - // In case of ::first-letter, |cursor.CurrentNode()| is null. + // In case of ::first-letter, |cursor.Current().GetNode()| is null. DCHECK(text_offset.has_value()); const NGOffsetMapping* mapping = - NGOffsetMapping::GetFor(cursor.CurrentLayoutObject()); + NGOffsetMapping::GetFor(cursor.Current().GetLayoutObject()); const TextAffinity affinity = - *text_offset == cursor.CurrentTextEndOffset() + *text_offset == cursor.Current().TextEndOffset() ? TextAffinity::kUpstreamIfPossible : TextAffinity::kDownstream; const Position position = affinity == TextAffinity::kDownstream diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc index 7977e5227cf..bccd83f3551 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.cc @@ -17,28 +17,29 @@ namespace { PhysicalRect ComputeLocalCaretRectByBoxSide(const NGInlineCursor& cursor, NGCaretPositionType position_type) { - const bool is_horizontal = cursor.CurrentStyle().IsHorizontalWritingMode(); + const bool is_horizontal = cursor.Current().Style().IsHorizontalWritingMode(); NGInlineCursor line_box(cursor); line_box.MoveToContainingLine(); DCHECK(line_box); const PhysicalOffset offset_to_line_box = - cursor.CurrentOffset() - line_box.CurrentOffset(); - LayoutUnit caret_height = is_horizontal ? line_box.CurrentSize().height - : line_box.CurrentSize().width; + cursor.Current().OffsetInContainerBlock() - + line_box.Current().OffsetInContainerBlock(); + LayoutUnit caret_height = is_horizontal ? line_box.Current().Size().height + : line_box.Current().Size().width; LayoutUnit caret_top = is_horizontal ? -offset_to_line_box.top : -offset_to_line_box.left; const LocalFrameView* frame_view = - cursor.CurrentLayoutObject()->GetDocument().View(); + cursor.Current().GetLayoutObject()->GetDocument().View(); LayoutUnit caret_width = frame_view->CaretWidth(); - const bool is_ltr = IsLtr(cursor.CurrentResolvedDirection()); + const bool is_ltr = IsLtr(cursor.Current().ResolvedDirection()); LayoutUnit caret_left; if (is_ltr != (position_type == NGCaretPositionType::kBeforeBox)) { if (is_horizontal) - caret_left = cursor.CurrentSize().width - caret_width; + caret_left = cursor.Current().Size().width - caret_width; else - caret_left = cursor.CurrentSize().height - caret_width; + caret_left = cursor.Current().Size().height - caret_width; } if (!is_horizontal) { @@ -53,22 +54,22 @@ PhysicalRect ComputeLocalCaretRectByBoxSide(const NGInlineCursor& cursor, PhysicalRect ComputeLocalCaretRectAtTextOffset(const NGInlineCursor& cursor, unsigned offset) { - DCHECK(cursor.IsText()); - DCHECK_GE(offset, cursor.CurrentTextStartOffset()); - DCHECK_LE(offset, cursor.CurrentTextEndOffset()); + DCHECK(cursor.Current().IsText()); + DCHECK_GE(offset, cursor.Current().TextStartOffset()); + DCHECK_LE(offset, cursor.Current().TextEndOffset()); const LocalFrameView* frame_view = - cursor.CurrentLayoutObject()->GetDocument().View(); + cursor.Current().GetLayoutObject()->GetDocument().View(); LayoutUnit caret_width = frame_view->CaretWidth(); - const bool is_horizontal = cursor.CurrentStyle().IsHorizontalWritingMode(); + const bool is_horizontal = cursor.Current().Style().IsHorizontalWritingMode(); - LayoutUnit caret_height = - is_horizontal ? cursor.CurrentSize().height : cursor.CurrentSize().width; + LayoutUnit caret_height = is_horizontal ? cursor.Current().Size().height + : cursor.Current().Size().width; LayoutUnit caret_top; LayoutUnit caret_left = cursor.InlinePositionForOffset(offset); - if (!cursor.IsLineBreak()) + if (!cursor.Current().IsLineBreak()) caret_left -= caret_width / 2; if (!is_horizontal) { @@ -77,16 +78,17 @@ PhysicalRect ComputeLocalCaretRectAtTextOffset(const NGInlineCursor& cursor, } // Adjust the location to be relative to the inline formatting context. - PhysicalOffset caret_location = - PhysicalOffset(caret_left, caret_top) + cursor.CurrentOffset(); + PhysicalOffset caret_location = PhysicalOffset(caret_left, caret_top) + + cursor.Current().OffsetInContainerBlock(); const PhysicalSize caret_size(caret_width, caret_height); const NGPhysicalBoxFragment& fragmentainer = - *cursor.CurrentLayoutObject()->ContainingBlockFlowFragment(); + *cursor.Current().GetLayoutObject()->ContainingBlockFlowFragment(); NGInlineCursor line_box(cursor); line_box.MoveToContainingLine(); - const PhysicalOffset line_box_offset = line_box.CurrentOffset(); - const PhysicalRect line_box_rect(line_box_offset, line_box.CurrentSize()); + const PhysicalOffset line_box_offset = + line_box.Current().OffsetInContainerBlock(); + const PhysicalRect line_box_rect(line_box_offset, line_box.Current().Size()); // For horizontal text, adjust the location in the x direction to ensure that // it completely falls in the union of line box and containing block, and @@ -116,17 +118,17 @@ LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) { return LocalCaretRect(); const LayoutObject* layout_object = - caret_position.cursor.CurrentLayoutObject(); + caret_position.cursor.Current().GetLayoutObject(); switch (caret_position.position_type) { case NGCaretPositionType::kBeforeBox: case NGCaretPositionType::kAfterBox: { - DCHECK(!caret_position.cursor.IsText()); + DCHECK(!caret_position.cursor.Current().IsText()); const PhysicalRect fragment_local_rect = ComputeLocalCaretRectByBoxSide( caret_position.cursor, caret_position.position_type); return {layout_object, fragment_local_rect}; } case NGCaretPositionType::kAtTextOffset: { - DCHECK(caret_position.cursor.IsText()); + DCHECK(caret_position.cursor.Current().IsText()); DCHECK(caret_position.text_offset.has_value()); const PhysicalRect caret_rect = ComputeLocalCaretRectAtTextOffset( caret_position.cursor, *caret_position.text_offset); @@ -151,12 +153,12 @@ LocalCaretRect ComputeLocalSelectionRect( DCHECK(line_box); PhysicalRect rect = caret_rect.rect; - if (caret_position.cursor.CurrentStyle().IsHorizontalWritingMode()) { - rect.SetY(line_box.CurrentOffset().top); - rect.SetHeight(line_box.CurrentSize().height); + if (caret_position.cursor.Current().Style().IsHorizontalWritingMode()) { + rect.SetY(line_box.Current().OffsetInContainerBlock().top); + rect.SetHeight(line_box.Current().Size().height); } else { - rect.SetX(line_box.CurrentOffset().left); - rect.SetHeight(line_box.CurrentSize().width); + rect.SetX(line_box.Current().OffsetInContainerBlock().left); + rect.SetHeight(line_box.Current().Size().width); } return {caret_rect.layout_object, rect}; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc index c184209b8f2..63a23dff82b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.cc @@ -9,13 +9,14 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" namespace blink { NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) : layout_object_(text.GetLayoutObject()), - text_({text.TextShapeResult(), text.StartOffset(), text.EndOffset()}), + text_({text.TextShapeResult(), text.TextOffset()}), rect_({PhysicalOffset(), text.Size()}), type_(kText), sub_type_(static_cast<unsigned>(text.TextType())), @@ -23,12 +24,12 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) is_generated_text_(text.IsGeneratedText()), is_hidden_for_paint_(text.IsHiddenForPaint()), text_direction_(static_cast<unsigned>(text.ResolvedDirection())), - ink_overflow_computed_(false) { - DCHECK_LE(text_.start_offset, text_.end_offset); + ink_overflow_computed_(false), + is_first_for_node_(text.IsFirstForNode()) { #if DCHECK_IS_ON() if (text_.shape_result) { - DCHECK_EQ(text_.shape_result->StartIndex(), text_.start_offset); - DCHECK_EQ(text_.shape_result->EndIndex(), text_.end_offset); + DCHECK_EQ(text_.shape_result->StartIndex(), StartOffset()); + DCHECK_EQ(text_.shape_result->EndIndex(), EndOffset()); } #endif if (text.TextType() == NGPhysicalTextFragment::kGeneratedText) { @@ -38,6 +39,7 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalTextFragment& text) // |generated_text_.text_| instead copying, |generated_text_.text = ...|. new (&generated_text_.text) String(text.Text().ToString()); } + DCHECK(!IsFormattingContextRoot()); } NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line, @@ -46,22 +48,45 @@ NGFragmentItem::NGFragmentItem(const NGPhysicalLineBoxFragment& line, line_({&line, item_count}), rect_({PhysicalOffset(), line.Size()}), type_(kLine), + sub_type_(static_cast<unsigned>(line.LineBoxType())), style_variant_(static_cast<unsigned>(line.StyleVariant())), is_hidden_for_paint_(false), text_direction_(static_cast<unsigned>(line.BaseDirection())), - ink_overflow_computed_(false) {} + ink_overflow_computed_(false), + is_first_for_node_(true) { + DCHECK(!IsFormattingContextRoot()); +} NGFragmentItem::NGFragmentItem(const NGPhysicalBoxFragment& box, - wtf_size_t item_count, TextDirection resolved_direction) : layout_object_(box.GetLayoutObject()), - box_({&box, item_count}), + box_({&box, 1}), rect_({PhysicalOffset(), box.Size()}), type_(kBox), style_variant_(static_cast<unsigned>(box.StyleVariant())), is_hidden_for_paint_(box.IsHiddenForPaint()), text_direction_(static_cast<unsigned>(resolved_direction)), - ink_overflow_computed_(false) {} + ink_overflow_computed_(false), + is_first_for_node_(box.IsFirstForNode()) { + DCHECK_EQ(IsFormattingContextRoot(), box.IsFormattingContextRoot()); +} + +NGFragmentItem::NGFragmentItem(const NGInlineItem& inline_item, + const PhysicalSize& size) + : layout_object_(inline_item.GetLayoutObject()), + box_({nullptr, 1}), + rect_({PhysicalOffset(), size}), + type_(kBox), + style_variant_(static_cast<unsigned>(inline_item.StyleVariant())), + is_hidden_for_paint_(false), + text_direction_(static_cast<unsigned>(TextDirection::kLtr)), + ink_overflow_computed_(false), + is_first_for_node_(true) { + DCHECK_EQ(inline_item.Type(), NGInlineItem::kOpenTag); + DCHECK(layout_object_); + DCHECK(layout_object_->IsLayoutInline()); + DCHECK(!IsFormattingContextRoot()); +} NGFragmentItem::~NGFragmentItem() { switch (Type()) { @@ -80,12 +105,32 @@ NGFragmentItem::~NGFragmentItem() { } } -bool NGFragmentItem::HasSameParent(const NGFragmentItem& other) const { +bool NGFragmentItem::IsSiblingOf(const NGFragmentItem& other) const { if (!GetLayoutObject()) return !other.GetLayoutObject(); if (!other.GetLayoutObject()) return false; - return GetLayoutObject()->Parent() == other.GetLayoutObject()->Parent(); + if (GetLayoutObject()->Parent() == other.GetLayoutObject()->Parent()) + return true; + // To traverse list marker and line box of <li> with |MoveToNextSibling()|, + // we think list marker and <li> are sibling. + // See hittesting/culled-inline-crash.html (skip list marker) + // See fast/events/onclick-list-marker.html (hit on list marker) + if (IsListMarker()) + return GetLayoutObject()->Parent() == other.GetLayoutObject(); + if (other.IsListMarker()) + return other.GetLayoutObject()->Parent() == GetLayoutObject(); + return false; +} + +bool NGFragmentItem::IsInlineBox() const { + if (Type() == kBox) { + if (const NGPhysicalBoxFragment* box = BoxFragment()) + return box->IsInlineBox(); + DCHECK(GetLayoutObject()->IsLayoutInline()); + return true; + } + return false; } bool NGFragmentItem::IsAtomicInline() const { @@ -96,11 +141,16 @@ bool NGFragmentItem::IsAtomicInline() const { return false; } -bool NGFragmentItem::IsEmptyLineBox() const { - // TODO(yosin): Implement |NGFragmentItem::IsEmptyLineBox()|. +bool NGFragmentItem::IsFloating() const { + if (const NGPhysicalBoxFragment* box = BoxFragment()) + return box->IsFloating(); return false; } +bool NGFragmentItem::IsEmptyLineBox() const { + return LineBoxType() == NGLineBoxType::kEmptyLineBox; +} + bool NGFragmentItem::IsGeneratedText() const { if (Type() == kText || Type() == kGeneratedText) return is_generated_text_; @@ -109,8 +159,7 @@ bool NGFragmentItem::IsGeneratedText() const { } bool NGFragmentItem::IsListMarker() const { - // TODO(yosin): Implement |NGFragmentItem::IsListMarker()|. - return false; + return layout_object_ && layout_object_->IsLayoutNGOutsideListMarker(); } bool NGFragmentItem::HasOverflowClip() const { @@ -162,11 +211,6 @@ PhysicalRect NGFragmentItem::InkOverflow() const { return container_ink_overflow.SelfAndContentsInkOverflow(); } -PositionWithAffinity NGFragmentItem::PositionForPoint( - const PhysicalOffset&) const { - return PositionWithAffinity(); -} - const ShapeResultView* NGFragmentItem::TextShapeResult() const { if (Type() == kText) return text_.shape_result.get(); @@ -176,29 +220,19 @@ const ShapeResultView* NGFragmentItem::TextShapeResult() const { return nullptr; } -unsigned NGFragmentItem::StartOffset() const { +NGTextOffset NGFragmentItem::TextOffset() const { if (Type() == kText) - return text_.start_offset; + return text_.text_offset; if (Type() == kGeneratedText) - return 0; + return {0, generated_text_.text.length()}; NOTREACHED(); - return 0; -} - -unsigned NGFragmentItem::EndOffset() const { - if (Type() == kText) - return text_.end_offset; - if (Type() == kGeneratedText) - return generated_text_.text.length(); - NOTREACHED(); - return 0; + return {}; } StringView NGFragmentItem::Text(const NGFragmentItems& items) const { if (Type() == kText) { - DCHECK_LE(text_.start_offset, text_.end_offset); - return StringView(items.Text(UsesFirstLineStyle()), text_.start_offset, - text_.end_offset - text_.start_offset); + return StringView(items.Text(UsesFirstLineStyle()), text_.text_offset.start, + text_.text_offset.Length()); } if (Type() == kGeneratedText) return GeneratedText(); @@ -209,8 +243,8 @@ StringView NGFragmentItem::Text(const NGFragmentItems& items) const { NGTextFragmentPaintInfo NGFragmentItem::TextPaintInfo( const NGFragmentItems& items) const { if (Type() == kText) { - return {items.Text(UsesFirstLineStyle()), text_.start_offset, - text_.end_offset, text_.shape_result.get()}; + return {items.Text(UsesFirstLineStyle()), text_.text_offset.start, + text_.text_offset.end, text_.shape_result.get()}; } if (Type() == kGeneratedText) { return {generated_text_.text, 0, generated_text_.text.length(), @@ -231,6 +265,24 @@ TextDirection NGFragmentItem::ResolvedDirection() const { } String NGFragmentItem::DebugName() const { + // TODO(yosin): Once |NGPaintFragment| is removed, we should get rid of + // following if-statements. + // For ease of rebasing, we use same |DebugName()| as |NGPaintFrgment|. + if (Type() == NGFragmentItem::kBox) { + StringBuilder name; + name.Append("NGPhysicalBoxFragment "); + name.Append(layout_object_->DebugName()); + return name.ToString(); + } + if (Type() == NGFragmentItem::kText) { + StringBuilder name; + name.Append("NGPhysicalTextFragment '"); + name.Append(Text(*layout_object_->ContainingBlockFlowFragment()->Items())); + name.Append('\''); + return name.ToString(); + } + if (Type() == NGFragmentItem::kLine) + return "NGPhysicalLineBoxFragment"; return "NGFragmentItem"; } @@ -241,17 +293,28 @@ IntRect NGFragmentItem::VisualRect() const { return GetLayoutObject()->VisualRectForInlineBox(); } +IntRect NGFragmentItem::PartialInvalidationVisualRect() const { + // TODO(yosin): Need to reconsider the storage of |VisualRect|, to integrate + // better with |FragmentData| and to avoid dependency to |LayoutObject|. + DCHECK(GetLayoutObject()); + return GetLayoutObject()->PartialInvalidationVisualRectForInlineBox(); +} + PhysicalRect NGFragmentItem::LocalVisualRectFor( const LayoutObject& layout_object) { DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); DCHECK(layout_object.IsInLayoutNGInlineFormattingContext()); PhysicalRect visual_rect; - for (const NGFragmentItem& item : ItemsFor(layout_object)) { + NGInlineCursor cursor; + for (cursor.MoveTo(layout_object); cursor; + cursor.MoveToNextForSameLayoutObject()) { + DCHECK(cursor.Current().Item()); + const NGFragmentItem& item = *cursor.Current().Item(); if (UNLIKELY(item.IsHiddenForPaint())) continue; PhysicalRect child_visual_rect = item.SelfInkOverflow(); - child_visual_rect.offset += item.Offset(); + child_visual_rect.offset += item.OffsetInContainerBlock(); visual_rect.Unite(child_visual_rect); } return visual_rect; @@ -269,7 +332,7 @@ PhysicalRect NGFragmentItem::RecalcInkOverflowForCursor( if (item->HasSelfPaintingLayer()) continue; if (!child_rect.IsEmpty()) { - child_rect.offset += item->Offset(); + child_rect.offset += item->OffsetInContainerBlock(); contents_ink_overflow.Unite(child_rect); } } @@ -322,14 +385,22 @@ void NGFragmentItem::RecalcInkOverflow( cursor->MoveToNextSibling(); PhysicalRect contents_rect = RecalcInkOverflowForCursor(&descendants_cursor); + // |contents_rect| is relative to the inline formatting context. Make it + // relative to |this|. + contents_rect.offset -= OffsetInContainerBlock(); + // Compute the self ink overflow. PhysicalRect self_rect; if (Type() == kLine) { // Line boxes don't have self overflow. Compute content overflow only. *self_and_contents_rect_out = contents_rect; - } else if (const NGPhysicalBoxFragment* box_fragment = BoxFragment()) { - DCHECK(box_fragment->IsInlineBox()); - self_rect = box_fragment->ComputeSelfInkOverflow(); + } else if (Type() == kBox) { + if (const NGPhysicalBoxFragment* box_fragment = BoxFragment()) { + DCHECK(box_fragment->IsInlineBox()); + self_rect = box_fragment->ComputeSelfInkOverflow(); + } else { + self_rect = LocalRect(); + } *self_and_contents_rect_out = UnionRect(self_rect, contents_rect); } else { NOTREACHED(); @@ -403,49 +474,6 @@ unsigned NGFragmentItem::TextOffsetForPoint( return inline_offset <= size.inline_size / 2 ? StartOffset() : EndOffset(); } -NGFragmentItem::ItemsForLayoutObject NGFragmentItem::ItemsFor( - const LayoutObject& layout_object) { - DCHECK(layout_object.IsInLayoutNGInlineFormattingContext()); - DCHECK(layout_object.IsText() || layout_object.IsLayoutInline() || - (layout_object.IsBox() && layout_object.IsInline())); - - if (const LayoutBlockFlow* block_flow = - layout_object.RootInlineFormattingContext()) { - if (const NGPhysicalBoxFragment* fragment = block_flow->CurrentFragment()) { - if (wtf_size_t index = layout_object.FirstInlineFragmentItemIndex()) { - const auto& items = fragment->Items()->Items(); - return ItemsForLayoutObject(items, index, items[index].get()); - } - // TODO(yosin): Once we update all usages of |FirstInlineFragment()|, - // we should get rid of below code. - if (const NGFragmentItems* items = fragment->Items()) { - for (unsigned i = 0; i < items->Items().size(); ++i) { - const NGFragmentItem* item = items->Items()[i].get(); - if (item->GetLayoutObject() == &layout_object) - return ItemsForLayoutObject(items->Items(), i, item); - } - } - } - } - - return ItemsForLayoutObject(); -} - -NGFragmentItem::ItemsForLayoutObject::Iterator& -NGFragmentItem::ItemsForLayoutObject::Iterator::operator++() { - // TODO(kojii): This is a hot function needed by paint and several other - // operations. Make this fast, by not iterating. - if (!current_) - return *this; - if (!current_->delta_to_next_for_same_layout_object_) { - current_ = nullptr; - return *this; - } - index_ += current_->delta_to_next_for_same_layout_object_; - current_ = (*items_)[index_].get(); - return *this; -} - std::ostream& operator<<(std::ostream& ostream, const NGFragmentItem& item) { ostream << "{"; switch (item.Type()) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h index 112464bdc50..c07af90d3d8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h @@ -11,6 +11,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h" #include "third_party/blink/renderer/core/layout/ng/ng_ink_overflow.h" #include "third_party/blink/renderer/platform/graphics/paint/display_item_client.h" @@ -18,6 +19,7 @@ namespace blink { class NGFragmentItems; class NGInlineBreakToken; +class NGInlineItem; struct NGTextFragmentPaintInfo; // This class represents a text run or a box in an inline formatting context. @@ -32,8 +34,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { // TODO(kojii): |start_offset| and |end_offset| should match to the offset // in |shape_result|. Consider if we should remove them, or if keeping them // is easier. - const unsigned start_offset; - const unsigned end_offset; + const NGTextOffset text_offset; }; // Represents text generated by the layout engine, e.g., hyphen or ellipsis. struct GeneratedTextItem { @@ -64,8 +65,8 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { // TODO(kojii): Should be able to create without once creating fragments. NGFragmentItem(const NGPhysicalTextFragment& text); NGFragmentItem(const NGPhysicalBoxFragment& box, - wtf_size_t item_count, TextDirection resolved_direction); + NGFragmentItem(const NGInlineItem& inline_item, const PhysicalSize& size); NGFragmentItem(const NGPhysicalLineBoxFragment& line, wtf_size_t item_count); ~NGFragmentItem() final; @@ -74,11 +75,29 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { bool IsText() const { return Type() == kText || Type() == kGeneratedText; } bool IsContainer() const { return Type() == kBox || Type() == kLine; } + bool IsInlineBox() const; bool IsAtomicInline() const; + bool IsFloating() const; bool IsEmptyLineBox() const; bool IsHiddenForPaint() const { return is_hidden_for_paint_; } bool IsListMarker() const; + // Return true if this is the first fragment generated from a node. + bool IsFirstForNode() const { + DCHECK(Type() != kLine); + DCHECK(!IsInlineBox() || BoxFragment()); + return is_first_for_node_; + } + + // Return true if this is the last fragment generated from a node. + bool IsLastForNode() const { + // TODO(layout-dev): This doesn't work if the LayoutObject continues in a + // next fragmentainer (we get a false negative here then). + DCHECK(Type() != kLine); + DCHECK(!IsInlineBox() || BoxFragment()); + return !DeltaToNextForSameLayoutObject(); + } + NGStyleVariant StyleVariant() const { return static_cast<NGStyleVariant>(style_variant_); } @@ -100,15 +119,15 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { } Node* GetNode() const { return layout_object_->GetNode(); } Node* NodeForHitTest() const { return layout_object_->NodeForHitTest(); } - bool HasSameParent(const NGFragmentItem& other) const; + bool IsSiblingOf(const NGFragmentItem& other) const; wtf_size_t DeltaToNextForSameLayoutObject() const { return delta_to_next_for_same_layout_object_; } void SetDeltaToNextForSameLayoutObject(wtf_size_t delta); - const PhysicalRect& Rect() const { return rect_; } - const PhysicalOffset& Offset() const { return rect_.offset; } + const PhysicalRect& RectInContainerBlock() const { return rect_; } + const PhysicalOffset& OffsetInContainerBlock() const { return rect_.offset; } const PhysicalSize& Size() const { return rect_.size; } PhysicalRect LocalRect() const { return {PhysicalOffset(), Size()}; } void SetOffset(const PhysicalOffset& offset) { rect_.offset = offset; } @@ -128,6 +147,10 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { return 0; } bool HasChildren() const { return DescendantsCount() > 1; } + void SetDescendantsCount(wtf_size_t count) { + CHECK_EQ(Type(), kBox); + box_.descendants_count = count; + } // Returns |NGPhysicalBoxFragment| if one is associated with this item. const NGPhysicalBoxFragment* BoxFragment() const { @@ -158,58 +181,19 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { return nullptr; } - NGTextFragmentPaintInfo TextPaintInfo(const NGFragmentItems& items) const; + using NGLineBoxType = NGPhysicalLineBoxFragment::NGLineBoxType; + NGLineBoxType LineBoxType() const { + if (Type() == kLine) + return static_cast<NGLineBoxType>(sub_type_); + NOTREACHED() << this; + return NGLineBoxType::kNormalLineBox; + } // DisplayItemClient overrides String DebugName() const override; IntRect VisualRect() const override; + IntRect PartialInvalidationVisualRect() const override; - // Find |NGFragmentItem|s that are associated with a |LayoutObject|. - class CORE_EXPORT ItemsForLayoutObject { - STACK_ALLOCATED(); - - public: - ItemsForLayoutObject() = default; - ItemsForLayoutObject(const Vector<std::unique_ptr<NGFragmentItem>>& items, - unsigned first_index, - const NGFragmentItem* first_item) - : items_(&items), first_item_(first_item), first_index_(first_index) {} - - bool IsEmpty() const { return !items_; } - - class CORE_EXPORT Iterator { - public: - Iterator(const Vector<std::unique_ptr<NGFragmentItem>>* items, - unsigned index, - const NGFragmentItem* item) - : current_(item), items_(items), index_(index) {} - const NGFragmentItem& operator*() const { return *current_; } - const NGFragmentItem& operator->() const { return *current_; } - Iterator& operator++(); - bool operator==(const Iterator& other) const { - return current_ == other.current_; - } - bool operator!=(const Iterator& other) const { - return current_ != other.current_; - } - - private: - const NGFragmentItem* current_; - const Vector<std::unique_ptr<NGFragmentItem>>* items_; - unsigned index_; - }; - using iterator = Iterator; - iterator begin() const { - return Iterator(items_, first_index_, first_item_); - } - iterator end() const { return Iterator(nullptr, 0, nullptr); } - - private: - const Vector<std::unique_ptr<NGFragmentItem>>* items_; - const NGFragmentItem* first_item_; - unsigned first_index_; - }; - static ItemsForLayoutObject ItemsFor(const LayoutObject& layout_object); static PhysicalRect LocalVisualRectFor(const LayoutObject& layout_object); // Re-compute the ink overflow for the |cursor| until its end. @@ -292,16 +276,21 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { return TextType() == NGTextType::kSymbolMarker; } - const ShapeResultView* TextShapeResult() const; + bool IsFormattingContextRoot() const { + return BoxFragment() && !IsInlineBox(); + } - unsigned StartOffset() const; - unsigned EndOffset() const; - unsigned TextLength() const { return EndOffset() - StartOffset(); } + const ShapeResultView* TextShapeResult() const; + NGTextOffset TextOffset() const; + unsigned StartOffset() const { return TextOffset().start; } + unsigned EndOffset() const { return TextOffset().end; } + unsigned TextLength() const { return TextOffset().Length(); } StringView Text(const NGFragmentItems& items) const; String GeneratedText() const { DCHECK_EQ(Type(), kGeneratedText); return generated_text_.text; } + NGTextFragmentPaintInfo TextPaintInfo(const NGFragmentItems& items) const; // Compute the inline position from text offset, in logical coordinate // relative to this fragment. @@ -338,7 +327,6 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { // Converts the given point, relative to the fragment itself, into a position // in DOM tree. - PositionWithAffinity PositionForPoint(const PhysicalOffset&) const; PositionWithAffinity PositionForPointInText( const PhysicalOffset& point, const NGInlineCursor& cursor) const; @@ -370,7 +358,7 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { // Note: We should not add |bidi_level_| because it is used only for layout. unsigned type_ : 2; // ItemType - unsigned sub_type_ : 3; // NGTextType + unsigned sub_type_ : 3; // NGTextType or NGLineBoxType unsigned style_variant_ : 2; // NGStyleVariant // TODO(yosin): We'll remove |is_generated_text_| field when we construct // |NGFragmentItem| without |NGPhysicalTextFragment| because usage of this @@ -383,6 +371,8 @@ class CORE_EXPORT NGFragmentItem : public DisplayItemClient { // Used only when |IsText()| to avoid re-computing ink overflow. unsigned ink_overflow_computed_ : 1; + + unsigned is_first_for_node_ : 1; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc index bc573b0d0d1..3e8f4853a6b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item_test.cc @@ -8,6 +8,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" @@ -22,9 +23,12 @@ class NGFragmentItemTest : public NGLayoutTest, Vector<const NGFragmentItem*> ItemsForAsVector( const LayoutObject& layout_object) { - const auto items = NGFragmentItem::ItemsFor(layout_object); Vector<const NGFragmentItem*> list; - for (const NGFragmentItem& item : items) { + NGInlineCursor cursor; + for (cursor.MoveTo(layout_object); cursor; + cursor.MoveToNextForSameLayoutObject()) { + DCHECK(cursor.Current().Item()); + const NGFragmentItem& item = *cursor.Current().Item(); EXPECT_EQ(item.GetLayoutObject(), &layout_object); list.push_back(&item); } @@ -67,12 +71,12 @@ TEST_F(NGFragmentItemTest, BasicText) { const NGFragmentItem& text1 = *items_for_text[0]; EXPECT_EQ(text1.Type(), NGFragmentItem::kText); EXPECT_EQ(text1.GetLayoutObject(), layout_text); - EXPECT_EQ(text1.Offset(), PhysicalOffset()); + EXPECT_EQ(text1.OffsetInContainerBlock(), PhysicalOffset()); const NGFragmentItem& text2 = *items_for_text[1]; EXPECT_EQ(text2.Type(), NGFragmentItem::kText); EXPECT_EQ(text2.GetLayoutObject(), layout_text); - EXPECT_EQ(text2.Offset(), PhysicalOffset(0, 10)); + EXPECT_EQ(text2.OffsetInContainerBlock(), PhysicalOffset(0, 10)); EXPECT_EQ(IntRect(0, 0, 70, 20), layout_text->FragmentsVisualRectBoundingBox()); @@ -108,7 +112,6 @@ TEST_F(NGFragmentItemTest, BasicInlineBox) { ASSERT_NE(span1, nullptr); Vector<const NGFragmentItem*> items_for_span1 = ItemsForAsVector(*span1); EXPECT_EQ(items_for_span1.size(), 2u); - EXPECT_EQ(IntRect(0, 0, 80, 20), span1->FragmentsVisualRectBoundingBox()); // "span2" doesn't wrap, produces only one fragment. @@ -116,8 +119,52 @@ TEST_F(NGFragmentItemTest, BasicInlineBox) { ASSERT_NE(span2, nullptr); Vector<const NGFragmentItem*> items_for_span2 = ItemsForAsVector(*span2); EXPECT_EQ(items_for_span2.size(), 1u); + EXPECT_EQ(IntRect(0, 20, 80, 10), span2->FragmentsVisualRectBoundingBox()); +} +// Same as |BasicInlineBox| but `<span>`s do not have background. +// They will not need box fragments, but all operations should work the same. +TEST_F(NGFragmentItemTest, CulledInlineBox) { + LoadAhem(); + SetBodyInnerHTML(R"HTML( + <style> + html, body { + margin: 0; + font-family: Ahem; + font-size: 10px; + line-height: 1; + } + #container { + width: 10ch; + } + </style> + <div id="container"> + 000 + <span id="span1">1234 5678</span> + 999 + <span id="span2">12345678</span> + </div> + )HTML"); + + // "span1" wraps, produces two fragments. + const LayoutObject* span1 = GetLayoutObjectByElementId("span1"); + ASSERT_NE(span1, nullptr); + Vector<const NGFragmentItem*> items_for_span1 = ItemsForAsVector(*span1); + EXPECT_EQ(items_for_span1.size(), 2u); + EXPECT_EQ(IntRect(0, 0, 80, 20), span1->FragmentsVisualRectBoundingBox()); + + // "span2" doesn't wrap, produces only one fragment. + const LayoutObject* span2 = GetLayoutObjectByElementId("span2"); + ASSERT_NE(span2, nullptr); + Vector<const NGFragmentItem*> items_for_span2 = ItemsForAsVector(*span2); + EXPECT_EQ(items_for_span2.size(), 1u); EXPECT_EQ(IntRect(0, 20, 80, 10), span2->FragmentsVisualRectBoundingBox()); + + // Except that they do not produce box fragments. + for (const NGFragmentItem* item : items_for_span1) + EXPECT_EQ(item->BoxFragment(), nullptr); + for (const NGFragmentItem* item : items_for_span2) + EXPECT_EQ(item->BoxFragment(), nullptr); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc index 87aba81920f..794995bd7bc 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.cc @@ -13,4 +13,35 @@ NGFragmentItems::NGFragmentItems(NGFragmentItemsBuilder* builder) text_content_(std::move(builder->text_content_)), first_line_text_content_(std::move(builder->first_line_text_content_)) {} +// static +void NGFragmentItems::AssociateWithLayoutObject( + Vector<std::unique_ptr<NGFragmentItem>>* items) { + // items_[0] can be: + // - kBox for list marker, e.g. <li>abc</li> + // - kLine for line, e.g. <div>abc</div> + // Calling get() is necessary below because operator<< in std::unique_ptr is + // a C++20 feature. + // TODO(https://crbug.com/980914): Drop .get() once we move to C++20. + DCHECK(items->IsEmpty() || (*items)[0]->IsContainer()) << (*items)[0].get(); + HashMap<const LayoutObject*, wtf_size_t> last_fragment_map; + for (wtf_size_t index = 1u; index < items->size(); ++index) { + const NGFragmentItem& item = *(*items)[index]; + if (item.Type() == NGFragmentItem::kLine) + continue; + LayoutObject* const layout_object = item.GetMutableLayoutObject(); + DCHECK(layout_object->IsInLayoutNGInlineFormattingContext()) << item; + auto insert_result = last_fragment_map.insert(layout_object, index); + if (insert_result.is_new_entry) { + layout_object->SetFirstInlineFragmentItemIndex(index); + continue; + } + const wtf_size_t last_index = insert_result.stored_value->value; + insert_result.stored_value->value = index; + DCHECK_GT(last_index, 0u) << item; + DCHECK_LT(last_index, items->size()); + DCHECK_LT(last_index, index); + (*items)[last_index]->SetDeltaToNextForSameLayoutObject(index - last_index); + } +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h index fe62f758e81..76c95f180a5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h @@ -28,6 +28,9 @@ class CORE_EXPORT NGFragmentItems { return UNLIKELY(first_line) ? first_line_text_content_ : text_content_; } + static void AssociateWithLayoutObject( + Vector<std::unique_ptr<NGFragmentItem>>* items); + private: // TODO(kojii): inline capacity TBD. Vector<std::unique_ptr<NGFragmentItem>> items_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc index fd54d34056f..243c9d3622c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc @@ -30,8 +30,8 @@ void NGFragmentItemsBuilder::SetCurrentLine( void NGFragmentItemsBuilder::AddLine(const NGPhysicalLineBoxFragment& line, const LogicalOffset& offset) { DCHECK_EQ(items_.size(), offsets_.size()); -#if DCHECK_IS_ON() DCHECK(!is_converted_to_physical_); +#if DCHECK_IS_ON() DCHECK_EQ(current_line_fragment_, &line); #endif @@ -65,36 +65,40 @@ void NGFragmentItemsBuilder::AddLine(const NGPhysicalLineBoxFragment& line, void NGFragmentItemsBuilder::AddItems(Child* child_begin, Child* child_end) { DCHECK_EQ(items_.size(), offsets_.size()); + DCHECK(!is_converted_to_physical_); for (Child* child_iter = child_begin; child_iter != child_end;) { Child& child = *child_iter; if (const NGPhysicalTextFragment* text = child.fragment.get()) { items_.push_back(std::make_unique<NGFragmentItem>(*text)); - offsets_.push_back(child.offset); + offsets_.push_back(child.rect.offset); ++child_iter; continue; } - if (child.layout_result) { + if (child.layout_result || child.inline_item) { // Create an item if this box has no inline children. - const NGPhysicalBoxFragment& box = - To<NGPhysicalBoxFragment>(child.layout_result->PhysicalFragment()); - // Floats are in the fragment tree, not in the fragment item list. - DCHECK(!box.IsFloating()); + std::unique_ptr<NGFragmentItem> item; + if (child.layout_result) { + const NGPhysicalBoxFragment& box = + To<NGPhysicalBoxFragment>(child.layout_result->PhysicalFragment()); + item = std::make_unique<NGFragmentItem>(box, child.ResolvedDirection()); + } else { + DCHECK(child.inline_item); + item = std::make_unique<NGFragmentItem>( + *child.inline_item, + ToPhysicalSize(child.rect.size, + child.inline_item->Style()->GetWritingMode())); + } + // Take the fast path when we know |child| does not have child items. if (child.children_count <= 1) { - // Compute |has_floating_descendants_for_paint_| to optimize tree - // traversal in paint. - if (!has_floating_descendants_for_paint_ && box.IsFloating()) - has_floating_descendants_for_paint_ = true; - - DCHECK(child.HasBidiLevel()); - items_.push_back(std::make_unique<NGFragmentItem>( - box, 1, DirectionFromLevel(child.bidi_level))); - offsets_.push_back(child.offset); + items_.push_back(std::move(item)); + offsets_.push_back(child.rect.offset); ++child_iter; continue; } + DCHECK(!item->IsFloating()); // Children of inline boxes are flattened and added to |items_|, with the // count of descendant items to preserve the tree structure. @@ -102,7 +106,7 @@ void NGFragmentItemsBuilder::AddItems(Child* child_begin, Child* child_end) { // Add an empty item so that the start of the box can be set later. wtf_size_t box_start_index = items_.size(); items_.Grow(box_start_index + 1); - offsets_.push_back(child.offset); + offsets_.push_back(child.rect.offset); // Add all children, including their desendants, skipping this item. CHECK_GE(child.children_count, 1u); // 0 will loop infinitely. @@ -116,9 +120,8 @@ void NGFragmentItemsBuilder::AddItems(Child* child_begin, Child* child_end) { wtf_size_t item_count = items_.size() - box_start_index; // Create an item for the start of the box. - DCHECK(child.HasBidiLevel()); - items_[box_start_index] = std::make_unique<NGFragmentItem>( - box, item_count, DirectionFromLevel(child.bidi_level)); + item->SetDescendantsCount(item_count); + items_[box_start_index] = std::move(item); continue; } @@ -132,23 +135,36 @@ void NGFragmentItemsBuilder::AddItems(Child* child_begin, Child* child_end) { void NGFragmentItemsBuilder::AddListMarker( const NGPhysicalBoxFragment& marker_fragment, const LogicalOffset& offset) { + DCHECK(!is_converted_to_physical_); + // Resolved direction matters only for inline items, and outside list markers // are not inline. const TextDirection resolved_direction = TextDirection::kLtr; items_.push_back( - std::make_unique<NGFragmentItem>(marker_fragment, 1, resolved_direction)); + std::make_unique<NGFragmentItem>(marker_fragment, resolved_direction)); offsets_.push_back(offset); } +const Vector<std::unique_ptr<NGFragmentItem>>& NGFragmentItemsBuilder::Items( + WritingMode writing_mode, + TextDirection direction, + const PhysicalSize& outer_size) { + ConvertToPhysical(writing_mode, direction, outer_size); + return items_; +} + // Convert internal logical offsets to physical. Items are kept with logical // offset until outer box size is determined. void NGFragmentItemsBuilder::ConvertToPhysical(WritingMode writing_mode, TextDirection direction, const PhysicalSize& outer_size) { CHECK_EQ(items_.size(), offsets_.size()); -#if DCHECK_IS_ON() - DCHECK(!is_converted_to_physical_); -#endif + if (is_converted_to_physical_) + return; + + // Children of lines have line-relative offsets. Use line-writing mode to + // convert their logical offsets. + const WritingMode line_writing_mode = ToLineWritingMode(writing_mode); std::unique_ptr<NGFragmentItem>* item_iter = items_.begin(); const LogicalOffset* offset = offsets_.begin(); @@ -165,7 +181,7 @@ void NGFragmentItemsBuilder::ConvertToPhysical(WritingMode writing_mode, unsigned descendants_count = item->DescendantsCount(); DCHECK(descendants_count); if (descendants_count) { - const PhysicalRect line_box_bounds = item->Rect(); + const PhysicalRect line_box_bounds = item->RectInContainerBlock(); while (--descendants_count) { ++offset; ++item_iter; @@ -175,7 +191,7 @@ void NGFragmentItemsBuilder::ConvertToPhysical(WritingMode writing_mode, // Use `kLtr` because inline items are after bidi-reoder, and that // their offset is visual, not logical. item->SetOffset( - offset->ConvertToPhysical(writing_mode, TextDirection::kLtr, + offset->ConvertToPhysical(line_writing_mode, TextDirection::kLtr, line_box_bounds.size, item->Size()) + line_box_bounds.offset); } @@ -183,9 +199,17 @@ void NGFragmentItemsBuilder::ConvertToPhysical(WritingMode writing_mode, } } -#if DCHECK_IS_ON() is_converted_to_physical_ = true; -#endif +} + +base::Optional<LogicalOffset> NGFragmentItemsBuilder::LogicalOffsetFor( + const LayoutObject& layout_object) const { + DCHECK_EQ(items_.size(), offsets_.size()); + for (const std::unique_ptr<NGFragmentItem>& item : items_) { + if (item->GetLayoutObject() == &layout_object) + return offsets_[&item - items_.begin()]; + } + return base::nullopt; } void NGFragmentItemsBuilder::ToFragmentItems(WritingMode writing_mode, @@ -193,39 +217,8 @@ void NGFragmentItemsBuilder::ToFragmentItems(WritingMode writing_mode, const PhysicalSize& outer_size, void* data) { ConvertToPhysical(writing_mode, direction, outer_size); - AssociateNextForSameLayoutObject(); + NGFragmentItems::AssociateWithLayoutObject(&items_); new (data) NGFragmentItems(this); } -void NGFragmentItemsBuilder::AssociateNextForSameLayoutObject() { - // items_[0] can be: - // - kBox for list marker, e.g. <li>abc</li> - // - kLine for line, e.g. <div>abc</div> - // Calling get() is necessary below because operator<< in std::unique_ptr is - // a C++20 feature. - // TODO(https://crbug.com/980914): Drop .get() once we move to C++20. - DCHECK(items_.IsEmpty() || items_[0]->IsContainer()) << items_[0].get(); - HashMap<const LayoutObject*, wtf_size_t> last_fragment_map; - for (wtf_size_t index = 1u; index < items_.size(); ++index) { - const NGFragmentItem& item = *items_[index]; - if (item.Type() == NGFragmentItem::kLine) - continue; - LayoutObject* const layout_object = item.GetMutableLayoutObject(); - DCHECK(layout_object->IsInLayoutNGInlineFormattingContext()) << item; - auto insert_result = last_fragment_map.insert(layout_object, index); - if (insert_result.is_new_entry) { - // TDOO(yosin): Once we update all |LayoutObject::FirstInlineFragment()|, - // we should enable below. - // layout_object->SetFirstInlineFragmentItemIndex(index); - continue; - } - const wtf_size_t last_index = insert_result.stored_value->value; - insert_result.stored_value->value = index; - DCHECK_GT(last_index, 0u) << item; - DCHECK_LT(last_index, items_.size()); - DCHECK_LT(last_index, index); - items_[last_index]->SetDeltaToNextForSameLayoutObject(index - last_index); - } -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h index eef2fc77231..84be85e4132 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h @@ -59,10 +59,21 @@ class CORE_EXPORT NGFragmentItemsBuilder { void AddListMarker(const NGPhysicalBoxFragment& marker_fragment, const LogicalOffset& offset); + // Find |LogicalOffset| of the first |NGFragmentItem| for |LayoutObject|. + base::Optional<LogicalOffset> LogicalOffsetFor(const LayoutObject&) const; + + // Converts the |NGFragmentItem| vector to the physical coordinate space and + // returns the result. This should only be used for determining the inline + // containing block geometry for OOF-positioned nodes. + // + // Once this method has been called, new items cannot be added. + const Vector<std::unique_ptr<NGFragmentItem>>& + Items(WritingMode, TextDirection, const PhysicalSize& outer_size); + // Build a |NGFragmentItems|. The builder cannot build twice because data set // to this builder may be cleared. - void ToFragmentItems(WritingMode writing_mode, - TextDirection direction, + void ToFragmentItems(WritingMode, + TextDirection, const PhysicalSize& outer_size, void* data); @@ -73,8 +84,6 @@ class CORE_EXPORT NGFragmentItemsBuilder { TextDirection direction, const PhysicalSize& outer_size); - void AssociateNextForSameLayoutObject(); - Vector<std::unique_ptr<NGFragmentItem>> items_; Vector<LogicalOffset> offsets_; String text_content_; @@ -84,10 +93,10 @@ class CORE_EXPORT NGFragmentItemsBuilder { ChildList current_line_; bool has_floating_descendants_for_paint_ = false; + bool is_converted_to_physical_ = false; #if DCHECK_IS_ON() const NGPhysicalLineBoxFragment* current_line_fragment_ = nullptr; - bool is_converted_to_physical_ = false; #endif friend class NGFragmentItems; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc index f2c2f03927e..cc0d8d8d3b4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc @@ -105,7 +105,8 @@ bool NGInlineBoxState::CanAddTextOfStyle( NGInlineBoxState* NGInlineLayoutStateStack::OnBeginPlaceItems( const ComputedStyle& line_style, FontBaseline baseline_type, - bool line_height_quirk) { + bool line_height_quirk, + NGLineBoxFragmentBuilder::ChildList* line_box) { if (stack_.IsEmpty()) { // For the first line, push a box state for the line itself. stack_.resize(1); @@ -114,7 +115,9 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnBeginPlaceItems( } else { // For the following lines, clear states that are not shared across lines. for (NGInlineBoxState& box : stack_) { - box.fragment_start = 0; + box.fragment_start = line_box->size(); + if (&box != stack_.begin()) + AddBoxFragmentPlaceholder(&box, line_box, baseline_type); if (!line_height_quirk) box.metrics = box.text_metrics; else @@ -133,15 +136,15 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnBeginPlaceItems( DCHECK(box_data_list_.IsEmpty()); // Initialize the box state for the line box. - NGInlineBoxState& line_box = LineBoxState(); - if (line_box.style != &line_style) { - line_box.style = &line_style; + NGInlineBoxState& line_box_state = LineBoxState(); + if (line_box_state.style != &line_style) { + line_box_state.style = &line_style; // Use a "strut" (a zero-width inline box with the element's font and // line height properties) as the initial metrics for the line box. // https://drafts.csswg.org/css2/visudet.html#strut if (!line_height_quirk) - line_box.ComputeTextMetrics(line_style, baseline_type); + line_box_state.ComputeTextMetrics(line_style, baseline_type); } return &stack_.back(); @@ -150,31 +153,32 @@ NGInlineBoxState* NGInlineLayoutStateStack::OnBeginPlaceItems( NGInlineBoxState* NGInlineLayoutStateStack::OnOpenTag( const NGInlineItem& item, const NGInlineItemResult& item_result, - const NGLineBoxFragmentBuilder::ChildList& line_box) { - DCHECK(item.Style()); - NGInlineBoxState* box = OnOpenTag(*item.Style(), line_box); - box->item = &item; - - if (item.ShouldCreateBoxFragment()) - box->SetNeedsBoxFragment(); - - // Compute box properties regardless of needs_box_fragment since close tag may - // also set needs_box_fragment. - box->has_start_edge = item_result.has_edge; - box->margin_inline_start = item_result.margins.inline_start; - box->margin_inline_end = item_result.margins.inline_end; - box->borders = item_result.borders; - box->padding = item_result.padding; + FontBaseline baseline_type, + NGLineBoxFragmentBuilder::ChildList* line_box) { + NGInlineBoxState* box = + OnOpenTag(item, item_result, baseline_type, *line_box); + box->needs_box_fragment = item.ShouldCreateBoxFragment(); + AddBoxFragmentPlaceholder(box, line_box, baseline_type); return box; } NGInlineBoxState* NGInlineLayoutStateStack::OnOpenTag( - const ComputedStyle& style, + const NGInlineItem& item, + const NGInlineItemResult& item_result, + FontBaseline baseline_type, const NGLineBoxFragmentBuilder::ChildList& line_box) { + DCHECK(item.Style()); + const ComputedStyle& style = *item.Style(); stack_.resize(stack_.size() + 1); NGInlineBoxState* box = &stack_.back(); box->fragment_start = line_box.size(); box->style = &style; + box->item = &item; + box->has_start_edge = item_result.has_edge; + box->margin_inline_start = item_result.margins.inline_start; + box->margin_inline_end = item_result.margins.inline_end; + box->borders = item_result.borders; + box->padding = item_result.padding; return box; } @@ -209,9 +213,9 @@ void NGInlineLayoutStateStack::OnEndPlaceItems( // Copy the final offset to |box_data_list_|. for (BoxData& box_data : box_data_list_) { const NGLineBoxFragmentBuilder::Child& placeholder = - (*line_box)[box_data.fragment_end]; - DCHECK(!placeholder.HasFragment()); - box_data.offset = placeholder.offset; + (*line_box)[box_data.fragment_start]; + DCHECK(placeholder.IsPlaceholder()); + box_data.rect.offset = placeholder.rect.offset; } } @@ -219,8 +223,13 @@ void NGInlineLayoutStateStack::EndBoxState( NGInlineBoxState* box, NGLineBoxFragmentBuilder::ChildList* line_box, FontBaseline baseline_type) { - if (box->needs_box_fragment) - AddBoxFragmentPlaceholder(box, line_box, baseline_type); + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + if (box->needs_box_fragment) + AddBoxData(box, line_box); + } else { + if (box->has_box_placeholder) + AddBoxData(box, line_box); + } PositionPending position_pending = ApplyBaselineShift(box, line_box, baseline_type); @@ -237,12 +246,6 @@ void NGInlineLayoutStateStack::EndBoxState( parent_box.metrics.Unite(box->metrics); } -void NGInlineBoxState::SetNeedsBoxFragment() { - DCHECK(item); - DCHECK(!needs_box_fragment); - needs_box_fragment = true; -} - // Crete a placeholder for a box fragment. // We keep a flat list of fragments because it is more suitable for operations // such as ApplyBaselineShift. Later, CreateBoxFragments() creates box fragments @@ -251,12 +254,14 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder( NGInlineBoxState* box, NGLineBoxFragmentBuilder::ChildList* line_box, FontBaseline baseline_type) { - DCHECK(box->needs_box_fragment); + DCHECK(box != stack_.begin() && + box->item->Type() != NGInlineItem::kAtomicInline); + box->has_box_placeholder = true; DCHECK(box->style); const ComputedStyle& style = *box->style; - LogicalOffset offset; - LogicalSize size; + LayoutUnit block_offset; + LayoutUnit block_size; if (!is_empty_line_) { // The inline box should have the height of the font metrics without the // line-height property. Compute from style because |box->metrics| includes @@ -265,63 +270,78 @@ void NGInlineLayoutStateStack::AddBoxFragmentPlaceholder( // Extend the block direction of the box by borders and paddings. Inline // direction is already included into positions in NGLineBreaker. - offset.block_offset = + block_offset = -metrics.ascent - (box->borders.line_over + box->padding.line_over); - size.block_size = metrics.LineHeight() + box->borders.BlockSum() + - box->padding.BlockSum(); + block_size = metrics.LineHeight() + box->borders.BlockSum() + + box->padding.BlockSum(); } + line_box->AddChild(block_offset, block_size); + DCHECK((*line_box)[line_box->size() - 1].IsPlaceholder()); +} - unsigned fragment_end = line_box->size(); +// Add a |BoxData|, for each close-tag that needs a box fragment. +void NGInlineLayoutStateStack::AddBoxData( + NGInlineBoxState* box, + NGLineBoxFragmentBuilder::ChildList* line_box) { + DCHECK(box->needs_box_fragment || + (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled() && + box->has_box_placeholder && box != stack_.begin() && + box->item->Type() != NGInlineItem::kAtomicInline)); + DCHECK(box->style); + const ComputedStyle& style = *box->style; + NGLineBoxFragmentBuilder::Child& placeholder = + (*line_box)[box->fragment_start]; + DCHECK(placeholder.IsPlaceholder()); + const unsigned fragment_end = line_box->size(); DCHECK(box->item); BoxData& box_data = box_data_list_.emplace_back( - box->fragment_start, fragment_end, box->item, size); - box_data.padding = box->padding; - if (box->has_start_edge) { - box_data.has_line_left_edge = true; - box_data.margin_line_left = box->margin_inline_start; - box_data.margin_border_padding_line_left = box->margin_inline_start + - box->borders.inline_start + - box->padding.inline_start; - } - if (box->has_end_edge) { - box_data.has_line_right_edge = true; - box_data.margin_line_right = box->margin_inline_end; - box_data.margin_border_padding_line_right = box->margin_inline_end + - box->borders.inline_end + - box->padding.inline_end; - } - if (IsRtl(style.Direction())) { - std::swap(box_data.has_line_left_edge, box_data.has_line_right_edge); - std::swap(box_data.margin_line_left, box_data.margin_line_right); - std::swap(box_data.margin_border_padding_line_left, - box_data.margin_border_padding_line_right); - } - - if (fragment_end > box->fragment_start) { - // The start is marked only in BoxData, while end is marked - // in both BoxData and the list itself. - // With a list of 4 text fragments: - // | 0 | 1 | 2 | 3 | - // |text0|text1|text2|text3| - // By adding a BoxData(2,4) (end is exclusive), it becomes: - // | 0 | 1 | 2 | 3 | 4 | - // |text0|text1|text2|text3|null | - // The "null" is added to the list to compute baseline shift of the box - // separately from text fragments. - line_box->AddChild(offset); + box->fragment_start, fragment_end, box->item, placeholder.Size()); + if (box->needs_box_fragment) { + box_data.padding = box->padding; + if (box->has_start_edge) { + box_data.has_line_left_edge = true; + box_data.margin_line_left = box->margin_inline_start; + box_data.margin_border_padding_line_left = box->margin_inline_start + + box->borders.inline_start + + box->padding.inline_start; + } + if (box->has_end_edge) { + box_data.has_line_right_edge = true; + box_data.margin_line_right = box->margin_inline_end; + box_data.margin_border_padding_line_right = box->margin_inline_end + + box->borders.inline_end + + box->padding.inline_end; + } + if (IsRtl(style.Direction())) { + std::swap(box_data.has_line_left_edge, box_data.has_line_right_edge); + std::swap(box_data.margin_line_left, box_data.margin_line_right); + std::swap(box_data.margin_border_padding_line_left, + box_data.margin_border_padding_line_right); + } } else { - // Do not defer creating a box fragment if this is an empty inline box. - // An empty box fragment is still flat that we do not have to defer. - // Also, placeholders cannot be reordred if empty. - offset.inline_offset += box_data.margin_line_left; - LayoutUnit advance = box_data.margin_border_padding_line_left + - box_data.margin_border_padding_line_right; - box_data.size.inline_size = - advance - box_data.margin_line_left - box_data.margin_line_right; - line_box->AddChild(box_data.CreateBoxFragment(line_box), offset, advance, - /* bidi_level */ 0); - box_data_list_.pop_back(); + DCHECK_EQ(box->margin_inline_start, 0); + DCHECK_EQ(box->margin_inline_end, 0); + DCHECK(box->padding.IsEmpty()); + DCHECK(box->borders.IsEmpty()); } + + DCHECK((*line_box)[box->fragment_start].IsPlaceholder()); + DCHECK_GT(fragment_end, box->fragment_start); + if (fragment_end > box->fragment_start + 1) + return; + + // Do not defer creating a box fragment if this is an empty inline box. + // An empty box fragment is still flat that we do not have to defer. + // Also, placeholders cannot be reordred if empty. + placeholder.rect.offset.inline_offset += box_data.margin_line_left; + LayoutUnit advance = box_data.margin_border_padding_line_left + + box_data.margin_border_padding_line_right; + box_data.rect.size.inline_size = + advance - box_data.margin_line_left - box_data.margin_line_right; + placeholder.layout_result = box_data.CreateBoxFragment(line_box); + placeholder.inline_size = advance; + DCHECK(!placeholder.children_count); + box_data_list_.pop_back(); } void NGInlineLayoutStateStack::ChildInserted(unsigned index) { @@ -348,20 +368,26 @@ void NGInlineLayoutStateStack::PrepareForReorder( unsigned box_data_index = 0; for (const BoxData& box_data : box_data_list_) { box_data_index++; + DCHECK((*line_box)[box_data.fragment_start].IsPlaceholder()); for (unsigned i = box_data.fragment_start; i < box_data.fragment_end; i++) { NGLineBoxFragmentBuilder::Child& child = (*line_box)[i]; - if (!child.box_data_index) + unsigned child_box_data_index = child.box_data_index; + if (!child_box_data_index) { child.box_data_index = box_data_index; - } - } + continue; + } - // When boxes are nested, placeholders have indexes to which box it should be - // added. Copy them to BoxData. - for (BoxData& box_data : box_data_list_) { - const NGLineBoxFragmentBuilder::Child& placeholder = - (*line_box)[box_data.fragment_end]; - DCHECK(!placeholder.HasFragment()); - box_data.parent_box_data_index = placeholder.box_data_index; + // This |box_data| has child boxes. Set up |parent_box_data_index| to + // represent the box nesting structure. + while (child_box_data_index != box_data_index) { + BoxData* child_box_data = &box_data_list_[child_box_data_index - 1]; + child_box_data_index = child_box_data->parent_box_data_index; + if (!child_box_data_index) { + child_box_data->parent_box_data_index = box_data_index; + break; + } + } + } } } @@ -491,8 +517,8 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( // origins at (0, 0). Accumulate inline offset from left to right. LayoutUnit position; for (NGLineBoxFragmentBuilder::Child& child : *line_box) { - child.margin_line_left = child.offset.inline_offset; - child.offset.inline_offset += position; + child.margin_line_left = child.rect.offset.inline_offset; + child.rect.offset.inline_offset += position; // Box margins/boders/paddings will be processed later. // TODO(kojii): we could optimize this if the reordering did not occur. if (!child.HasFragment()) @@ -538,7 +564,7 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( unsigned start = box_data.fragment_start; NGLineBoxFragmentBuilder::Child& start_child = (*line_box)[start]; LayoutUnit line_left_offset = - start_child.offset.inline_offset - start_child.margin_line_left; + start_child.rect.offset.inline_offset - start_child.margin_line_left; LinePadding& start_padding = accumulated_padding[start]; start_padding.line_left += box_data.margin_border_padding_line_left; line_left_offset -= start_padding.line_left - box_data.margin_line_left; @@ -546,15 +572,15 @@ LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions( DCHECK_GT(box_data.fragment_end, start); unsigned last = box_data.fragment_end - 1; NGLineBoxFragmentBuilder::Child& last_child = (*line_box)[last]; - LayoutUnit line_right_offset = last_child.offset.inline_offset - + LayoutUnit line_right_offset = last_child.rect.offset.inline_offset - last_child.margin_line_left + last_child.inline_size; LinePadding& last_padding = accumulated_padding[last]; last_padding.line_right += box_data.margin_border_padding_line_right; line_right_offset += last_padding.line_right - box_data.margin_line_right; - box_data.offset.inline_offset = line_left_offset; - box_data.size.inline_size = line_right_offset - line_left_offset; + box_data.rect.offset.inline_offset = line_left_offset; + box_data.rect.size.inline_size = line_right_offset - line_left_offset; } return position; @@ -569,24 +595,38 @@ void NGInlineLayoutStateStack::CreateBoxFragments( unsigned end = box_data.fragment_end; DCHECK_GT(end, start); NGLineBoxFragmentBuilder::Child* child = &(*line_box)[start]; + if (box_data.item->ShouldCreateBoxFragment()) { + scoped_refptr<const NGLayoutResult> box_fragment = + box_data.CreateBoxFragment(line_box); + if (child->IsPlaceholder()) { + child->layout_result = std::move(box_fragment); + child->rect = box_data.rect; + child->children_count = end - start; + continue; + } - scoped_refptr<const NGLayoutResult> box_fragment = - box_data.CreateBoxFragment(line_box); - if (!child->HasFragment()) { - child->layout_result = std::move(box_fragment); - child->offset = box_data.offset; - child->children_count = end - start; - } else { - // In most cases, |start_child| is moved to the children of the box, and - // is empty. It's not empty when it's out-of-flow. Insert in such case. - // TODO(kojii): With |NGFragmentItem|, all cases hit this code. Consider - // creating an empty item beforehand to avoid inserting. - line_box->InsertChild(start, std::move(box_fragment), box_data.offset, - LayoutUnit(), 0); + // |AddBoxFragmentPlaceholder| adds a placeholder at |fragment_start|, but + // bidi reordering may move it. Insert in such case. + line_box->InsertChild(start, std::move(box_fragment), box_data.rect, + end - start + 1); ChildInserted(start + 1); - child = &(*line_box)[start]; - child->children_count = end - start + 1; + continue; + } + + DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); + DCHECK(box_data.item); + if (child->IsPlaceholder()) { + child->inline_item = box_data.item; + child->rect = box_data.rect; + child->children_count = end - start; + continue; } + + // |AddBoxFragmentPlaceholder| adds a placeholder at |fragment_start|, but + // bidi reordering may move it. Insert in such case. + line_box->InsertChild(start, *box_data.item, box_data.rect, + end - start + 1); + ChildInserted(start + 1); } box_data_list_.clear(); @@ -600,8 +640,8 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( const ComputedStyle& style = *item->Style(); NGFragmentGeometry fragment_geometry; - fragment_geometry.border_box_size = {size.inline_size.ClampNegativeToZero(), - size.block_size}; + fragment_geometry.border_box_size = { + rect.size.inline_size.ClampNegativeToZero(), rect.size.block_size}; fragment_geometry.padding = NGBoxStrut(padding, IsFlippedLinesWritingMode(style.GetWritingMode())); @@ -618,6 +658,8 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( // supported today. box.SetBorderEdges({true, has_line_right_edge, true, has_line_left_edge}); + box.SetIsFirstForNode(has_line_left_edge); + for (unsigned i = fragment_start; i < fragment_end; i++) { NGLineBoxFragmentBuilder::Child& child = (*line_box)[i]; if (child.out_of_flow_positioned_box) { @@ -627,7 +669,7 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( // child.offset is the static position wrt. the linebox. As we are adding // this as a child of an inline level fragment, we adjust the static // position to be relative to this fragment. - LogicalOffset static_offset = child.offset - offset; + LogicalOffset static_offset = child.rect.offset - rect.offset; box.AddOutOfFlowInlineChildCandidate(oof_box, static_offset, child.container_direction); @@ -636,24 +678,33 @@ NGInlineLayoutStateStack::BoxData::CreateBoxFragment( } if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { - // |NGFragmentItems| has a flat list of all descendants, except OOF - // objects. Still creates |NGPhysicalBoxFragment|, but don't add children - // to it and keep them in the flat list. + // Propagate any OOF-positioned descendants from any atomic-inlines, etc. + if (child.layout_result) { + box.PropagateChildData(child.layout_result->PhysicalFragment(), + child.rect.offset - rect.offset); + } + + // |NGFragmentItems| has a flat list of all descendants, except + // OOF-positioned descendants. + // We still create a |NGPhysicalBoxFragment|, but don't add children to + // it and keep them in the flat list. continue; } if (child.layout_result) { box.AddChild(child.layout_result->PhysicalFragment(), - child.offset - offset); + child.rect.offset - rect.offset); child.layout_result.reset(); } else if (child.fragment) { - box.AddChild(std::move(child.fragment), child.offset - offset); + box.AddChild(std::move(child.fragment), child.rect.offset - rect.offset); } } - // Inline boxes that produce DisplayItemClient should do full paint - // invalidations. - item->GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + // Inline boxes that produce DisplayItemClient should do full paint + // invalidations. + item->GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + } box.MoveOutOfFlowDescendantCandidatesToDescendants(); return box.ToInlineBoxFragment(); @@ -826,10 +877,12 @@ NGLineHeightMetrics NGInlineLayoutStateStack::MetricsForTopAndBottomAlign( continue; // |block_offset| is the top position when the baseline is at 0. - LayoutUnit box_ascent = - -line_box[box_data.fragment_end].offset.block_offset; + const NGLineBoxFragmentBuilder::Child& placeholder = + line_box[box_data.fragment_start]; + DCHECK(placeholder.IsPlaceholder()); + LayoutUnit box_ascent = -placeholder.rect.offset.block_offset; NGLineHeightMetrics box_metrics(box_ascent, - box_data.size.block_size - box_ascent); + box_data.rect.size.block_size - box_ascent); // The top/bottom of inline boxes should not include their paddings. box_metrics.ascent -= box_data.padding.line_over; box_metrics.descent -= box_data.padding.line_under; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h index 066225e80ec..2431f010067 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h @@ -5,7 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_BOX_STATE_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_BOX_STATE_H_ -#include "third_party/blink/renderer/core/layout/geometry/logical_size.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_rect.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" @@ -67,6 +67,7 @@ struct NGInlineBoxState { Vector<NGPendingPositions> pending_descendants; bool include_used_fonts = false; + bool has_box_placeholder = false; bool needs_box_fragment = false; // True if this box has a metrics, including pending ones. Pending metrics @@ -88,9 +89,6 @@ struct NGInlineBoxState { // 'text-top' offset for 'vertical-align'. LayoutUnit TextTop(FontBaseline baseline_type) const; - // Create a box fragment for this box. - void SetNeedsBoxFragment(); - // Returns if the text style can be added without open-tag. // Text with different font or vertical-align needs to be wrapped with an // inline box. @@ -116,14 +114,22 @@ class CORE_EXPORT NGInlineLayoutStateStack { // Initialize the box state stack for a new line. // @return The initial box state for the line. - NGInlineBoxState* OnBeginPlaceItems(const ComputedStyle&, FontBaseline, bool); + NGInlineBoxState* OnBeginPlaceItems( + const ComputedStyle&, + FontBaseline, + bool line_height_quirk, + NGLineBoxFragmentBuilder::ChildList* line_box); // Push a box state stack. NGInlineBoxState* OnOpenTag(const NGInlineItem&, const NGInlineItemResult&, + FontBaseline baseline_type, const NGLineBoxFragmentBuilder::ChildList&); - NGInlineBoxState* OnOpenTag(const ComputedStyle&, - const NGLineBoxFragmentBuilder::ChildList&); + // This variation adds a box placeholder to |line_box|. + NGInlineBoxState* OnOpenTag(const NGInlineItem&, + const NGInlineItemResult&, + FontBaseline baseline_type, + NGLineBoxFragmentBuilder::ChildList* line_box); // Pop a box state stack. NGInlineBoxState* OnCloseTag(NGLineBoxFragmentBuilder::ChildList*, @@ -182,6 +188,7 @@ class CORE_EXPORT NGInlineLayoutStateStack { void AddBoxFragmentPlaceholder(NGInlineBoxState*, NGLineBoxFragmentBuilder::ChildList*, FontBaseline); + void AddBoxData(NGInlineBoxState*, NGLineBoxFragmentBuilder::ChildList*); enum PositionPending { kPositionNotPending, kPositionPending }; @@ -208,14 +215,16 @@ class CORE_EXPORT NGInlineLayoutStateStack { unsigned end, const NGInlineItem* item, LogicalSize size) - : fragment_start(start), fragment_end(end), item(item), size(size) {} + : fragment_start(start), + fragment_end(end), + item(item), + rect(LogicalOffset(), size) {} BoxData(const BoxData& other, unsigned start, unsigned end) : fragment_start(start), fragment_end(end), item(other.item), - size(other.size), - offset(other.offset) {} + rect(other.rect) {} void SetFragmentRange(unsigned start_index, unsigned end_index) { fragment_start = start_index; @@ -227,7 +236,7 @@ class CORE_EXPORT NGInlineLayoutStateStack { unsigned fragment_end; const NGInlineItem* item; - LogicalSize size; + LogicalRect rect; bool has_line_left_edge = false; bool has_line_right_edge = false; @@ -238,7 +247,6 @@ class CORE_EXPORT NGInlineLayoutStateStack { LayoutUnit margin_border_padding_line_left; LayoutUnit margin_border_padding_line_right; - LogicalOffset offset; unsigned parent_box_data_index = 0; unsigned fragmented_box_data_index = 0; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc index 3a9b6f7814d..0485ed7dbb4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.cc @@ -22,6 +22,7 @@ static_assert(sizeof(NGInlineBreakToken) == } // namespace NGInlineBreakToken::NGInlineBreakToken( + PassKey key, NGInlineNode node, const ComputedStyle* style, unsigned item_index, @@ -34,7 +35,7 @@ NGInlineBreakToken::NGInlineBreakToken( flags_ = flags; } -NGInlineBreakToken::NGInlineBreakToken(NGLayoutInputNode node) +NGInlineBreakToken::NGInlineBreakToken(PassKey key, NGLayoutInputNode node) : NGBreakToken(kInlineBreakToken, kFinished, node), item_index_(0), text_offset_(0) {} diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h index fdf2ebdccde..6558270e9cd 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h @@ -31,13 +31,13 @@ class CORE_EXPORT NGInlineBreakToken final : public NGBreakToken { unsigned item_index, unsigned text_offset, unsigned flags /* NGInlineBreakTokenFlags */) { - return base::AdoptRef( - new NGInlineBreakToken(node, style, item_index, text_offset, flags)); + return base::AdoptRef(new NGInlineBreakToken( + PassKey(), node, style, item_index, text_offset, flags)); } // Creates a break token for a node which cannot produce any more fragments. static scoped_refptr<NGInlineBreakToken> Create(NGLayoutInputNode node) { - return base::AdoptRef(new NGInlineBreakToken(node)); + return base::AdoptRef(new NGInlineBreakToken(PassKey(), node)); } ~NGInlineBreakToken() override; @@ -69,19 +69,21 @@ class CORE_EXPORT NGInlineBreakToken final : public NGBreakToken { return flags_ & kIsForcedBreak; } -#if DCHECK_IS_ON() - String ToString() const override; -#endif - - private: - NGInlineBreakToken(NGInlineNode node, + using PassKey = util::PassKey<NGInlineBreakToken>; + NGInlineBreakToken(PassKey, + NGInlineNode node, const ComputedStyle*, unsigned item_index, unsigned text_offset, unsigned flags /* NGInlineBreakTokenFlags */); - explicit NGInlineBreakToken(NGLayoutInputNode node); + explicit NGInlineBreakToken(PassKey, NGLayoutInputNode node); +#if DCHECK_IS_ON() + String ToString() const override; +#endif + + private: scoped_refptr<const ComputedStyle> style_; unsigned item_index_; unsigned text_offset_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc index df7664e661e..bb39e1b44f1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc @@ -18,8 +18,8 @@ namespace blink { void NGInlineCursor::MoveToItem(const ItemsSpan::iterator& iter) { DCHECK(IsItemCursor()); DCHECK(iter >= items_.begin() && iter <= items_.end()); - item_iter_ = iter; - current_item_ = iter == items_.end() ? nullptr : iter->get(); + current_.item_iter_ = iter; + current_.item_ = iter == items_.end() ? nullptr : iter->get(); } void NGInlineCursor::SetRoot(const NGFragmentItems& fragment_items, @@ -28,6 +28,8 @@ void NGInlineCursor::SetRoot(const NGFragmentItems& fragment_items, DCHECK(!HasRoot()); fragment_items_ = &fragment_items; items_ = items; + DCHECK(items_.empty() || (items_.data() >= fragment_items_->Items().data() && + items_.data() < fragment_items_->Items().end())); MoveToItem(items_.begin()); } @@ -39,7 +41,7 @@ void NGInlineCursor::SetRoot(const NGPaintFragment& root_paint_fragment) { DCHECK(&root_paint_fragment); DCHECK(!HasRoot()); root_paint_fragment_ = &root_paint_fragment; - current_paint_fragment_ = root_paint_fragment.FirstChild(); + current_.paint_fragment_ = root_paint_fragment.FirstChild(); } void NGInlineCursor::SetRoot(const LayoutBlockFlow& block_flow) { @@ -80,28 +82,22 @@ NGInlineCursor::NGInlineCursor(const NGPaintFragment& root_paint_fragment) { SetRoot(root_paint_fragment); } -NGInlineCursor::NGInlineCursor(const NGInlineCursor& other) - : items_(other.items_), - item_iter_(other.item_iter_), - current_item_(other.current_item_), - fragment_items_(other.fragment_items_), - root_paint_fragment_(other.root_paint_fragment_), - current_paint_fragment_(other.current_paint_fragment_), - layout_inline_(other.layout_inline_) {} - -NGInlineCursor::NGInlineCursor() = default; +NGInlineCursor::NGInlineCursor(const NGInlineBackwardCursor& backward_cursor) + : NGInlineCursor(backward_cursor.cursor_) { + MoveTo(backward_cursor.Current()); +} bool NGInlineCursor::operator==(const NGInlineCursor& other) const { if (root_paint_fragment_) { return root_paint_fragment_ == other.root_paint_fragment_ && - current_paint_fragment_ == other.current_paint_fragment_; + current_.paint_fragment_ == other.current_.paint_fragment_; } - if (current_item_ != other.current_item_) + if (current_.item_ != other.current_.item_) return false; DCHECK_EQ(items_.data(), other.items_.data()); DCHECK_EQ(items_.size(), other.items_.size()); DCHECK_EQ(fragment_items_, other.fragment_items_); - DCHECK(item_iter_ == other.item_iter_); + DCHECK(current_.item_iter_ == other.current_.item_iter_); return true; } @@ -122,34 +118,35 @@ const LayoutBlockFlow* NGInlineCursor::GetLayoutBlockFlow() const { return layout_object->RootInlineFormattingContext(); } if (IsItemCursor()) { - for (const auto& item : items_) { - const LayoutObject* layout_object = item->GetLayoutObject(); - if (layout_object && layout_object->IsInline()) - return layout_object->RootInlineFormattingContext(); - } + const NGFragmentItem& item = *fragment_items_->Items().front(); + const LayoutObject* layout_object = item.GetLayoutObject(); + if (item.Type() == NGFragmentItem::kLine) + return To<LayoutBlockFlow>(layout_object); + return layout_object->RootInlineFormattingContext(); } NOTREACHED(); return nullptr; } bool NGInlineCursor::HasChildren() const { - if (current_paint_fragment_) - return current_paint_fragment_->FirstChild(); - if (current_item_) - return current_item_->HasChildren(); + if (current_.paint_fragment_) + return current_.paint_fragment_->FirstChild(); + if (current_.item_) + return current_.item_->HasChildren(); NOTREACHED(); return false; } NGInlineCursor NGInlineCursor::CursorForDescendants() const { - if (current_paint_fragment_) - return NGInlineCursor(*current_paint_fragment_); - if (current_item_) { - unsigned descendants_count = current_item_->DescendantsCount(); + if (current_.paint_fragment_) + return NGInlineCursor(*current_.paint_fragment_); + if (current_.item_) { + unsigned descendants_count = current_.item_->DescendantsCount(); if (descendants_count > 1) { DCHECK(fragment_items_); - return NGInlineCursor(*fragment_items_, ItemsSpan(&*(item_iter_ + 1), - descendants_count - 1)); + return NGInlineCursor( + *fragment_items_, + ItemsSpan(&*(current_.item_iter_ + 1), descendants_count - 1)); } return NGInlineCursor(); } @@ -157,96 +154,119 @@ NGInlineCursor NGInlineCursor::CursorForDescendants() const { return NGInlineCursor(); } +void NGInlineCursor::ExpandRootToContainingBlock() { + if (root_paint_fragment_) { + root_paint_fragment_ = root_paint_fragment_->Root(); + return; + } + if (fragment_items_) { + const unsigned index_diff = items_.data() - fragment_items_->Items().data(); + DCHECK_LT(index_diff, fragment_items_->Items().size()); + const unsigned item_index = current_.item_iter_ - items_.begin(); + items_ = fragment_items_->Items(); + // Update the iterator to the one for the new span. + MoveToItem(items_.begin() + item_index + index_diff); + return; + } + NOTREACHED(); +} + bool NGInlineCursor::HasSoftWrapToNextLine() const { - DCHECK(IsLineBox()); - const NGInlineBreakToken& break_token = CurrentInlineBreakToken(); - return !break_token.IsFinished() && !break_token.IsForcedBreak(); + DCHECK(Current().IsLineBox()); + const NGInlineBreakToken* break_token = Current().InlineBreakToken(); + DCHECK(break_token); + return !break_token->IsFinished() && !break_token->IsForcedBreak(); } -bool NGInlineCursor::IsAtomicInline() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().IsAtomicInline(); - if (current_item_) - return current_item_->IsAtomicInline(); +bool NGInlineCursorPosition::IsInlineBox() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().IsInlineBox(); + if (item_) + return item_->IsInlineBox(); NOTREACHED(); return false; } -bool NGInlineCursor::IsEllipsis() const { - if (current_paint_fragment_) - return current_paint_fragment_->IsEllipsis(); - if (current_item_) - return current_item_->IsEllipsis(); +bool NGInlineCursorPosition::IsAtomicInline() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().IsAtomicInline(); + if (item_) + return item_->IsAtomicInline(); NOTREACHED(); return false; } -bool NGInlineCursor::IsGeneratedText() const { - if (current_paint_fragment_) { +bool NGInlineCursorPosition::IsEllipsis() const { + if (paint_fragment_) + return paint_fragment_->IsEllipsis(); + if (item_) + return item_->IsEllipsis(); + NOTREACHED(); + return false; +} + +bool NGInlineCursorPosition::IsGeneratedText() const { + if (paint_fragment_) { if (auto* text_fragment = DynamicTo<NGPhysicalTextFragment>( - current_paint_fragment_->PhysicalFragment())) + paint_fragment_->PhysicalFragment())) return text_fragment->IsGeneratedText(); return false; } - if (current_item_) - return current_item_->IsGeneratedText(); + if (item_) + return item_->IsGeneratedText(); NOTREACHED(); return false; } -bool NGInlineCursor::IsGeneratedTextType() const { - if (current_paint_fragment_) { +bool NGInlineCursorPosition::IsGeneratedTextType() const { + if (paint_fragment_) { if (auto* text_fragment = DynamicTo<NGPhysicalTextFragment>( - current_paint_fragment_->PhysicalFragment())) { + paint_fragment_->PhysicalFragment())) { return text_fragment->TextType() == NGPhysicalTextFragment::kGeneratedText; } return false; } - if (current_item_) - return current_item_->Type() == NGFragmentItem::kGeneratedText; + if (item_) + return item_->Type() == NGFragmentItem::kGeneratedText; NOTREACHED(); return false; } -bool NGInlineCursor::IsHiddenForPaint() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().IsHiddenForPaint(); - if (current_item_) - return current_item_->IsHiddenForPaint(); +bool NGInlineCursorPosition::IsHiddenForPaint() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().IsHiddenForPaint(); + if (item_) + return item_->IsHiddenForPaint(); NOTREACHED(); return false; } -bool NGInlineCursor::IsHorizontal() const { - return CurrentStyle().GetWritingMode() == WritingMode::kHorizontalTb; -} - bool NGInlineCursor::IsInlineLeaf() const { - if (IsHiddenForPaint()) + if (Current().IsHiddenForPaint()) return false; - if (IsText()) - return !IsGeneratedTextType(); - if (!IsAtomicInline()) + if (Current().IsText()) + return !Current().IsGeneratedTextType(); + if (!Current().IsAtomicInline()) return false; - return !IsListMarker(); + return !Current().IsListMarker(); } bool NGInlineCursor::IsPartOfCulledInlineBox( const LayoutInline& layout_inline) const { - const LayoutObject* const layout_object = CurrentLayoutObject(); + const LayoutObject* const layout_object = Current().GetLayoutObject(); // We use |IsInline()| to exclude floating and out-of-flow objects. if (!layout_object || !layout_object->IsInline() || layout_object->IsAtomicInlineLevel()) return false; DCHECK(!layout_object->IsFloatingOrOutOfFlowPositioned()); - DCHECK(!CurrentBoxFragment() || - !CurrentBoxFragment()->IsBlockFormattingContextRoot()); + DCHECK(!Current().BoxFragment() || + !Current().BoxFragment()->IsFormattingContextRoot()); return layout_object->IsDescendantOf(&layout_inline); } bool NGInlineCursor::IsLastLineInInlineBlock() const { - DCHECK(IsLineBox()); + DCHECK(Current().IsLineBox()); if (!GetLayoutBlockFlow()->IsAtomicInlineLevel()) return false; NGInlineCursor next_sibling(*this); @@ -254,45 +274,45 @@ bool NGInlineCursor::IsLastLineInInlineBlock() const { next_sibling.MoveToNextSibling(); if (!next_sibling) return true; - if (next_sibling.IsLineBox()) + if (next_sibling.Current().IsLineBox()) return false; // There maybe other top-level objects such as floats, OOF, or list-markers. } } -bool NGInlineCursor::IsLineBreak() const { - if (current_paint_fragment_) { +bool NGInlineCursorPosition::IsLineBreak() const { + if (paint_fragment_) { if (auto* text_fragment = DynamicTo<NGPhysicalTextFragment>( - current_paint_fragment_->PhysicalFragment())) + paint_fragment_->PhysicalFragment())) return text_fragment->IsLineBreak(); return false; } - if (current_item_) - return IsText() && current_item_->IsLineBreak(); + if (item_) + return IsText() && item_->IsLineBreak(); NOTREACHED(); return false; } -bool NGInlineCursor::IsListMarker() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().IsListMarker(); - if (current_item_) - return current_item_->IsListMarker(); +bool NGInlineCursorPosition::IsListMarker() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().IsListMarker(); + if (item_) + return item_->IsListMarker(); NOTREACHED(); return false; } -bool NGInlineCursor::IsText() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().IsText(); - if (current_item_) - return current_item_->IsText(); +bool NGInlineCursorPosition::IsText() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().IsText(); + if (item_) + return item_->IsText(); NOTREACHED(); return false; } bool NGInlineCursor::IsBeforeSoftLineBreak() const { - if (IsLineBreak()) + if (Current().IsLineBreak()) return false; // Inline block is not be container line box. // See paint/selection/text-selection-inline-block.html. @@ -315,57 +335,55 @@ bool NGInlineCursor::IsBeforeSoftLineBreak() const { // Even If |fragment| is before linebreak, if its direction differs to line // direction, we don't paint line break. See // paint/selection/text-selection-newline-mixed-ltr-rtl.html. - return line.CurrentBaseDirection() == CurrentResolvedDirection(); + return line.Current().BaseDirection() == Current().ResolvedDirection(); } bool NGInlineCursor::CanHaveChildren() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().IsContainer(); - if (current_item_) { - return current_item_->Type() == NGFragmentItem::kLine || - (current_item_->Type() == NGFragmentItem::kBox && - !current_item_->IsAtomicInline()); + if (current_.paint_fragment_) + return current_.paint_fragment_->PhysicalFragment().IsContainer(); + if (current_.item_) { + return current_.item_->Type() == NGFragmentItem::kLine || + (current_.item_->Type() == NGFragmentItem::kBox && + !current_.item_->IsAtomicInline()); } NOTREACHED(); return false; } -bool NGInlineCursor::IsEmptyLineBox() const { +bool NGInlineCursorPosition::IsEmptyLineBox() const { DCHECK(IsLineBox()); - if (current_paint_fragment_) { - return To<NGPhysicalLineBoxFragment>( - current_paint_fragment_->PhysicalFragment()) + if (paint_fragment_) { + return To<NGPhysicalLineBoxFragment>(paint_fragment_->PhysicalFragment()) .IsEmptyLineBox(); } - if (current_item_) - return current_item_->IsEmptyLineBox(); + if (item_) + return item_->IsEmptyLineBox(); NOTREACHED(); return false; } -bool NGInlineCursor::IsLineBox() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().IsLineBox(); - if (current_item_) - return current_item_->Type() == NGFragmentItem::kLine; +bool NGInlineCursorPosition::IsLineBox() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().IsLineBox(); + if (item_) + return item_->Type() == NGFragmentItem::kLine; NOTREACHED(); return false; } -TextDirection NGInlineCursor::CurrentBaseDirection() const { +TextDirection NGInlineCursorPosition::BaseDirection() const { DCHECK(IsLineBox()); - if (current_paint_fragment_) { - return To<NGPhysicalLineBoxFragment>( - current_paint_fragment_->PhysicalFragment()) + if (paint_fragment_) { + return To<NGPhysicalLineBoxFragment>(paint_fragment_->PhysicalFragment()) .BaseDirection(); } - if (current_item_) - return current_item_->BaseDirection(); + if (item_) + return item_->BaseDirection(); NOTREACHED(); return TextDirection::kLtr; } -UBiDiLevel NGInlineCursor::CurrentBidiLevel() const { +UBiDiLevel NGInlineCursorPosition::BidiLevel() const { if (IsText()) { if (IsGeneratedTextType()) { // TODO(yosin): Until we have clients, we don't support bidi-level for @@ -373,17 +391,18 @@ UBiDiLevel NGInlineCursor::CurrentBidiLevel() const { NOTREACHED() << this; return 0; } - const LayoutText& layout_text = *ToLayoutText(CurrentLayoutObject()); + const LayoutText& layout_text = *ToLayoutText(GetLayoutObject()); DCHECK(!layout_text.NeedsLayout()) << this; const auto* const items = layout_text.GetNGInlineItems(); if (!items || items->size() == 0) { // In case of <br>, <wbr>, text-combine-upright, etc. return 0; } + const NGTextOffset offset = TextOffset(); const auto& item = std::find_if( - items->begin(), items->end(), [this](const NGInlineItem& item) { - return item.StartOffset() <= CurrentTextStartOffset() && - item.EndOffset() >= CurrentTextEndOffset(); + items->begin(), items->end(), [offset](const NGInlineItem& item) { + return item.StartOffset() <= offset.start && + item.EndOffset() >= offset.end; }); DCHECK(item != items->end()) << this; return item->BidiLevel(); @@ -391,13 +410,13 @@ UBiDiLevel NGInlineCursor::CurrentBidiLevel() const { if (IsAtomicInline()) { const NGPhysicalBoxFragment* fragmentainer = - CurrentLayoutObject()->ContainingBlockFlowFragment(); + GetLayoutObject()->ContainingBlockFlowFragment(); DCHECK(fragmentainer); const LayoutBlockFlow& block_flow = *To<LayoutBlockFlow>(fragmentainer->GetLayoutObject()); const Vector<NGInlineItem> items = block_flow.GetNGInlineNodeData()->ItemsData(UsesFirstLineStyle()).items; - const LayoutObject* const layout_object = CurrentLayoutObject(); + const LayoutObject* const layout_object = GetLayoutObject(); const auto* const item = std::find_if( items.begin(), items.end(), [layout_object](const NGInlineItem& item) { return item.GetLayoutObject() == layout_object; @@ -410,237 +429,469 @@ UBiDiLevel NGInlineCursor::CurrentBidiLevel() const { return 0; } -const NGPhysicalBoxFragment* NGInlineCursor::CurrentBoxFragment() const { - if (current_paint_fragment_) { +const NGPhysicalBoxFragment* NGInlineCursorPosition::BoxFragment() const { + if (paint_fragment_) { return DynamicTo<NGPhysicalBoxFragment>( - ¤t_paint_fragment_->PhysicalFragment()); + &paint_fragment_->PhysicalFragment()); } - if (current_item_) - return current_item_->BoxFragment(); + if (item_) + return item_->BoxFragment(); NOTREACHED(); return nullptr; } -const DisplayItemClient* NGInlineCursor::CurrentDisplayItemClient() const { - if (current_paint_fragment_) - return current_paint_fragment_; - if (current_item_) - return current_item_; +const DisplayItemClient* NGInlineCursorPosition::GetDisplayItemClient() const { + if (paint_fragment_) + return paint_fragment_; + if (item_) + return item_; NOTREACHED(); return nullptr; } -const NGInlineBreakToken& NGInlineCursor::CurrentInlineBreakToken() const { +const NGInlineBreakToken* NGInlineCursorPosition::InlineBreakToken() const { DCHECK(IsLineBox()); - if (current_paint_fragment_) { + if (paint_fragment_) { return To<NGInlineBreakToken>( - *To<NGPhysicalLineBoxFragment>( - current_paint_fragment_->PhysicalFragment()) - .BreakToken()); + To<NGPhysicalLineBoxFragment>(paint_fragment_->PhysicalFragment()) + .BreakToken()); } - DCHECK(current_item_); - return *current_item_->InlineBreakToken(); + DCHECK(item_); + return item_->InlineBreakToken(); } -const LayoutObject* NGInlineCursor::CurrentLayoutObject() const { - if (current_paint_fragment_) - return current_paint_fragment_->GetLayoutObject(); - if (current_item_) - return current_item_->GetLayoutObject(); +const LayoutObject* NGInlineCursorPosition::GetLayoutObject() const { + if (paint_fragment_) + return paint_fragment_->GetLayoutObject(); + if (item_) + return item_->GetLayoutObject(); NOTREACHED(); return nullptr; } -LayoutObject* NGInlineCursor::CurrentMutableLayoutObject() const { - if (current_paint_fragment_) - return current_paint_fragment_->GetMutableLayoutObject(); - if (current_item_) - return current_item_->GetMutableLayoutObject(); +LayoutObject* NGInlineCursorPosition::GetMutableLayoutObject() const { + if (paint_fragment_) + return paint_fragment_->GetMutableLayoutObject(); + if (item_) + return item_->GetMutableLayoutObject(); NOTREACHED(); return nullptr; } -Node* NGInlineCursor::CurrentNode() const { - if (const LayoutObject* layout_object = CurrentLayoutObject()) +const Node* NGInlineCursorPosition::GetNode() const { + if (const LayoutObject* layout_object = GetLayoutObject()) return layout_object->GetNode(); return nullptr; } -const PhysicalRect NGInlineCursor::CurrentInkOverflow() const { - if (current_paint_fragment_) - return current_paint_fragment_->InkOverflow(); - if (current_item_) - return current_item_->InkOverflow(); +const PhysicalRect NGInlineCursorPosition::InkOverflow() const { + if (paint_fragment_) + return paint_fragment_->InkOverflow(); + if (item_) + return item_->InkOverflow(); NOTREACHED(); return PhysicalRect(); } -const PhysicalOffset NGInlineCursor::CurrentOffset() const { - if (current_paint_fragment_) - return current_paint_fragment_->InlineOffsetToContainerBox(); - if (current_item_) - return current_item_->Offset(); +const PhysicalOffset NGInlineCursorPosition::OffsetInContainerBlock() const { + if (paint_fragment_) + return paint_fragment_->OffsetInContainerBlock(); + if (item_) + return item_->OffsetInContainerBlock(); NOTREACHED(); return PhysicalOffset(); } -const PhysicalRect NGInlineCursor::CurrentRect() const { - return PhysicalRect(CurrentOffset(), CurrentSize()); +const PhysicalSize NGInlineCursorPosition::Size() const { + if (paint_fragment_) + return paint_fragment_->Size(); + if (item_) + return item_->Size(); + NOTREACHED(); + return PhysicalSize(); } -TextDirection NGInlineCursor::CurrentResolvedDirection() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().ResolvedDirection(); - if (current_item_) - return current_item_->ResolvedDirection(); +const PhysicalRect NGInlineCursorPosition::RectInContainerBlock() const { + if (paint_fragment_) { + return {paint_fragment_->OffsetInContainerBlock(), paint_fragment_->Size()}; + } + if (item_) + return item_->RectInContainerBlock(); NOTREACHED(); - return TextDirection::kLtr; + return PhysicalRect(); } -const PhysicalSize NGInlineCursor::CurrentSize() const { - if (current_paint_fragment_) - return current_paint_fragment_->Size(); - if (current_item_) - return current_item_->Size(); +const PhysicalRect NGInlineCursorPosition::SelfInkOverflow() const { + if (paint_fragment_) + return paint_fragment_->SelfInkOverflow(); + if (item_) + return item_->SelfInkOverflow(); NOTREACHED(); - return PhysicalSize(); + return PhysicalRect(); +} + +TextDirection NGInlineCursorPosition::ResolvedDirection() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().ResolvedDirection(); + if (item_) + return item_->ResolvedDirection(); + NOTREACHED(); + return TextDirection::kLtr; } -const ComputedStyle& NGInlineCursor::CurrentStyle() const { - if (current_paint_fragment_) - return current_paint_fragment_->Style(); - return current_item_->Style(); +const ComputedStyle& NGInlineCursorPosition::Style() const { + if (paint_fragment_) + return paint_fragment_->Style(); + return item_->Style(); } -NGStyleVariant NGInlineCursor::CurrentStyleVariant() const { - if (current_paint_fragment_) - return current_paint_fragment_->PhysicalFragment().StyleVariant(); - return current_item_->StyleVariant(); +NGStyleVariant NGInlineCursorPosition::StyleVariant() const { + if (paint_fragment_) + return paint_fragment_->PhysicalFragment().StyleVariant(); + return item_->StyleVariant(); } -bool NGInlineCursor::UsesFirstLineStyle() const { - return CurrentStyleVariant() == NGStyleVariant::kFirstLine; +bool NGInlineCursorPosition::UsesFirstLineStyle() const { + return StyleVariant() == NGStyleVariant::kFirstLine; } -NGTextOffset NGInlineCursor::CurrentTextOffset() const { - if (current_paint_fragment_) { +NGTextOffset NGInlineCursorPosition::TextOffset() const { + if (paint_fragment_) { const auto& text_fragment = - To<NGPhysicalTextFragment>(current_paint_fragment_->PhysicalFragment()); - return {text_fragment.StartOffset(), text_fragment.EndOffset()}; + To<NGPhysicalTextFragment>(paint_fragment_->PhysicalFragment()); + return text_fragment.TextOffset(); } - if (current_item_) - return {current_item_->StartOffset(), current_item_->EndOffset()}; + if (item_) + return item_->TextOffset(); NOTREACHED(); return {}; } -StringView NGInlineCursor::CurrentText() const { +StringView NGInlineCursorPosition::Text(const NGInlineCursor& cursor) const { DCHECK(IsText()); - if (current_paint_fragment_) { - return To<NGPhysicalTextFragment>( - current_paint_fragment_->PhysicalFragment()) + cursor.CheckValid(*this); + if (paint_fragment_) { + return To<NGPhysicalTextFragment>(paint_fragment_->PhysicalFragment()) .Text(); } - if (current_item_) - return current_item_->Text(*fragment_items_); + if (item_) + return item_->Text(cursor.Items()); NOTREACHED(); return ""; } -const ShapeResultView* NGInlineCursor::CurrentTextShapeResult() const { +const ShapeResultView* NGInlineCursorPosition::TextShapeResult() const { DCHECK(IsText()); - if (current_paint_fragment_) { - return To<NGPhysicalTextFragment>( - current_paint_fragment_->PhysicalFragment()) + if (paint_fragment_) { + return To<NGPhysicalTextFragment>(paint_fragment_->PhysicalFragment()) .TextShapeResult(); } - if (current_item_) - return current_item_->TextShapeResult(); + if (item_) + return item_->TextShapeResult(); NOTREACHED(); return nullptr; } PhysicalRect NGInlineCursor::CurrentLocalRect(unsigned start_offset, unsigned end_offset) const { - DCHECK(IsText()); - if (current_paint_fragment_) { + DCHECK(Current().IsText()); + if (current_.paint_fragment_) { return To<NGPhysicalTextFragment>( - current_paint_fragment_->PhysicalFragment()) + current_.paint_fragment_->PhysicalFragment()) .LocalRect(start_offset, end_offset); } - if (current_item_) { - return current_item_->LocalRect(current_item_->Text(*fragment_items_), - start_offset, end_offset); + if (current_.item_) { + return current_.item_->LocalRect(current_.item_->Text(*fragment_items_), + start_offset, end_offset); } NOTREACHED(); return PhysicalRect(); } LayoutUnit NGInlineCursor::InlinePositionForOffset(unsigned offset) const { - DCHECK(IsText()); - if (current_paint_fragment_) { + DCHECK(Current().IsText()); + if (current_.paint_fragment_) { return To<NGPhysicalTextFragment>( - current_paint_fragment_->PhysicalFragment()) + current_.paint_fragment_->PhysicalFragment()) .InlinePositionForOffset(offset); } - if (current_item_) { - return current_item_->InlinePositionForOffset( - current_item_->Text(*fragment_items_), offset); + if (current_.item_) { + return current_.item_->InlinePositionForOffset( + current_.item_->Text(*fragment_items_), offset); } NOTREACHED(); return LayoutUnit(); } PhysicalOffset NGInlineCursor::LineStartPoint() const { - DCHECK(IsLineBox()) << this; + DCHECK(Current().IsLineBox()) << this; const LogicalOffset logical_start; // (0, 0) const PhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1)); - return logical_start.ConvertToPhysical(CurrentStyle().GetWritingMode(), - CurrentBaseDirection(), CurrentSize(), - pixel_size); + return logical_start.ConvertToPhysical(Current().Style().GetWritingMode(), + Current().BaseDirection(), + Current().Size(), pixel_size); } PhysicalOffset NGInlineCursor::LineEndPoint() const { - DCHECK(IsLineBox()) << this; - const LayoutUnit inline_size = - IsHorizontal() ? CurrentSize().width : CurrentSize().height; + DCHECK(Current().IsLineBox()) << this; + const WritingMode writing_mode = Current().Style().GetWritingMode(); + const LayoutUnit inline_size = IsHorizontalWritingMode(writing_mode) + ? Current().Size().width + : Current().Size().height; const LogicalOffset logical_end(inline_size, LayoutUnit()); const PhysicalSize pixel_size(LayoutUnit(1), LayoutUnit(1)); - return logical_end.ConvertToPhysical(CurrentStyle().GetWritingMode(), - CurrentBaseDirection(), CurrentSize(), - pixel_size); + return logical_end.ConvertToPhysical(writing_mode, Current().BaseDirection(), + Current().Size(), pixel_size); } -PositionWithAffinity NGInlineCursor::PositionForPoint( - const PhysicalOffset& point) { - if (root_paint_fragment_) - return root_paint_fragment_->PositionForPoint(point); +PositionWithAffinity NGInlineCursor::PositionForPointInInlineFormattingContext( + const PhysicalOffset& point, + const NGPhysicalBoxFragment& container) { DCHECK(IsItemCursor()); + const ComputedStyle& container_style = container.Style(); + const WritingMode writing_mode = container_style.GetWritingMode(); + const TextDirection direction = container_style.Direction(); + const PhysicalSize& container_size = container.Size(); + const LayoutUnit point_block_offset = + point + .ConvertToLogical(writing_mode, direction, container_size, + // |point| is actually a pixel with size 1x1. + PhysicalSize(LayoutUnit(1), LayoutUnit(1))) + .block_offset; + + // Stores the closest line box child above |point| in the block direction. + // Used if we can't find any child |point| falls in to resolve the position. + NGInlineCursorPosition closest_line_before; + LayoutUnit closest_line_before_block_offset = LayoutUnit::Min(); + + // Stores the closest line box child below |point| in the block direction. + // Used if we can't find any child |point| falls in to resolve the position. + NGInlineCursorPosition closest_line_after; + LayoutUnit closest_line_after_block_offset = LayoutUnit::Max(); + while (*this) { - const NGFragmentItem* item = CurrentItem(); - DCHECK(item); - // TODO(kojii): Do more staff, when the point is not on any item but within - // line box, etc., see |NGPaintFragment::PositionForPoint|. - if (!item->Rect().Contains(point)) { + const NGFragmentItem* child_item = CurrentItem(); + DCHECK(child_item); + if (child_item->Type() == NGFragmentItem::kLine) { + // Try to resolve if |point| falls in a line box in block direction. + const LayoutUnit child_block_offset = + child_item->OffsetInContainerBlock() + .ConvertToLogical(writing_mode, direction, container_size, + child_item->Size()) + .block_offset; + if (point_block_offset < child_block_offset) { + if (child_block_offset < closest_line_after_block_offset) { + closest_line_after_block_offset = child_block_offset; + closest_line_after = Current(); + } + MoveToNextItemSkippingChildren(); + continue; + } + + // Hitting on line bottom doesn't count, to match legacy behavior. + const LayoutUnit child_block_end_offset = + child_block_offset + + child_item->Size().ConvertToLogical(writing_mode).block_size; + if (point_block_offset >= child_block_end_offset) { + if (child_block_end_offset > closest_line_before_block_offset) { + closest_line_before_block_offset = child_block_end_offset; + closest_line_before = Current(); + } + MoveToNextItemSkippingChildren(); + continue; + } + + if (const PositionWithAffinity child_position = + PositionForPointInInlineBox(point)) + return child_position; MoveToNextItemSkippingChildren(); continue; } - if (item->Type() == NGFragmentItem::kText) - return item->PositionForPointInText(point, *this); - MoveToNext(); + DCHECK_NE(child_item->Type(), NGFragmentItem::kText); + MoveToNextItem(); + } + + if (closest_line_after) { + MoveTo(closest_line_after); + if (const PositionWithAffinity child_position = + PositionForPointInInlineBox(point)) + return child_position; + } + + if (closest_line_before) { + MoveTo(closest_line_before); + if (const PositionWithAffinity child_position = + PositionForPointInInlineBox(point)) + return child_position; + } + + return PositionWithAffinity(); +} + +PositionWithAffinity NGInlineCursor::PositionForPointInInlineBox( + const PhysicalOffset& point) const { + if (const NGPaintFragment* paint_fragment = CurrentPaintFragment()) { + DCHECK(paint_fragment->PhysicalFragment().IsLineBox()); + return paint_fragment->PositionForPoint(point); + } + const NGFragmentItem* container = CurrentItem(); + DCHECK(container); + DCHECK(container->Type() == NGFragmentItem::kLine || + container->Type() == NGFragmentItem::kBox); + const ComputedStyle& container_style = container->Style(); + const WritingMode writing_mode = container_style.GetWritingMode(); + const TextDirection direction = container_style.Direction(); + const PhysicalSize& container_size = container->Size(); + const LayoutUnit point_inline_offset = + point + .ConvertToLogical(writing_mode, direction, container_size, + // |point| is actually a pixel with size 1x1. + PhysicalSize(LayoutUnit(1), LayoutUnit(1))) + .inline_offset; + + // Stores the closest child before |point| in the inline direction. Used if we + // can't find any child |point| falls in to resolve the position. + NGInlineCursorPosition closest_child_before; + LayoutUnit closest_child_before_inline_offset = LayoutUnit::Min(); + + // Stores the closest child after |point| in the inline direction. Used if we + // can't find any child |point| falls in to resolve the position. + NGInlineCursorPosition closest_child_after; + LayoutUnit closest_child_after_inline_offset = LayoutUnit::Max(); + + NGInlineCursor descendants = CursorForDescendants(); + for (; descendants; descendants.MoveToNext()) { + const NGFragmentItem* child_item = descendants.CurrentItem(); + DCHECK(child_item); + if (child_item->Type() == NGFragmentItem::kBox && + !child_item->BoxFragment()) { + // Skip virtually "culled" inline box, e.g. <span>foo</span> + // "editing/selection/shift-click.html" reaches here. + DCHECK(child_item->GetLayoutObject()->IsLayoutInline()) << child_item; + continue; + } + const LayoutUnit child_inline_offset = + child_item->OffsetInContainerBlock() + .ConvertToLogical(writing_mode, direction, container_size, + child_item->Size()) + .inline_offset; + if (point_inline_offset < child_inline_offset) { + if (child_inline_offset < closest_child_after_inline_offset) { + closest_child_after_inline_offset = child_inline_offset; + closest_child_after = descendants.Current(); + } + continue; + } + const LayoutUnit child_inline_end_offset = + child_inline_offset + + child_item->Size().ConvertToLogical(writing_mode).inline_size; + if (point_inline_offset > child_inline_end_offset) { + if (child_inline_end_offset > closest_child_before_inline_offset) { + closest_child_before_inline_offset = child_inline_end_offset; + closest_child_before = descendants.Current(); + } + continue; + } + + if (const PositionWithAffinity child_position = + descendants.PositionForPointInChild(point, *child_item)) + return child_position; + } + + if (closest_child_after) { + descendants.MoveTo(closest_child_after); + if (const PositionWithAffinity child_position = + descendants.PositionForPointInChild(point, *closest_child_after)) + return child_position; + // TODO(yosin): we should do like "closest_child_before" once we have a + // case. + } + + if (closest_child_before) { + descendants.MoveTo(closest_child_before); + if (const PositionWithAffinity child_position = + descendants.PositionForPointInChild(point, *closest_child_before)) + return child_position; + if (closest_child_before->BoxFragment()) { + // LayoutViewHitTest.HitTestHorizontal "Top-right corner (outside) of div" + // reach here. + return descendants.PositionForPointInInlineBox(point); + } + } + + return PositionWithAffinity(); +} + +PositionWithAffinity NGInlineCursor::PositionForPointInChild( + const PhysicalOffset& point, + const NGFragmentItem& child_item) const { + DCHECK_EQ(&child_item, CurrentItem()); + switch (child_item.Type()) { + case NGFragmentItem::kText: + return child_item.PositionForPointInText( + point - child_item.OffsetInContainerBlock(), *this); + case NGFragmentItem::kGeneratedText: + break; + case NGFragmentItem::kBox: + if (const NGPhysicalBoxFragment* box_fragment = + child_item.BoxFragment()) { + // We must fallback to legacy for old layout roots. We also fallback (to + // LayoutNGMixin::PositionForPoint()) for NG block layout, so that we + // can utilize LayoutBlock::PositionForPoint() that resolves the + // position in block layout. + // TODO(xiaochengh): Don't fallback to legacy for NG block layout. + if (box_fragment->IsBlockFlow() || box_fragment->IsLegacyLayoutRoot()) { + return child_item.GetLayoutObject()->PositionForPoint( + point - child_item.OffsetInContainerBlock()); + } + } else { + // |LayoutInline| used to be culled. + } + DCHECK(child_item.GetLayoutObject()->IsLayoutInline()) << child_item; + break; + case NGFragmentItem::kLine: + NOTREACHED(); + break; } return PositionWithAffinity(); } void NGInlineCursor::MakeNull() { if (root_paint_fragment_) { - current_paint_fragment_ = nullptr; + current_.paint_fragment_ = nullptr; return; } if (fragment_items_) return MoveToItem(items_.end()); } +void NGInlineCursor::MoveTo(const NGInlineCursorPosition& position) { + CheckValid(position); + current_ = position; +} + +inline unsigned NGInlineCursor::SpanIndexFromItemIndex(unsigned index) const { + DCHECK(IsItemCursor()); + DCHECK_GE(items_.data(), fragment_items_->Items().data()); + DCHECK_LT(items_.data(), fragment_items_->Items().end()); + if (items_.data() == fragment_items_->Items().data()) + return index; + unsigned span_index = fragment_items_->Items().data() - items_.data() + index; + DCHECK_LT(span_index, items_.size()); + return span_index; +} + +NGInlineCursor::ItemsSpan::iterator NGInlineCursor::SlowFirstItemIteratorFor( + const LayoutObject& layout_object) const { + DCHECK(IsItemCursor()); + for (ItemsSpan::iterator iter = items_.begin(); iter != items_.end(); + ++iter) { + if ((*iter)->GetLayoutObject() == &layout_object) + return iter; + } + return items_.end(); +} + void NGInlineCursor::InternalMoveTo(const LayoutObject& layout_object) { DCHECK(layout_object.IsInLayoutNGInlineFormattingContext()); // If this cursor is rootless, find the root of the inline formatting context. @@ -662,30 +913,18 @@ void NGInlineCursor::InternalMoveTo(const LayoutObject& layout_object) { } } if (fragment_items_) { - const wtf_size_t index = layout_object.FirstInlineFragmentItemIndex(); - if (!index) { + const wtf_size_t item_index = layout_object.FirstInlineFragmentItemIndex(); + if (!item_index) { // TODO(yosin): Once we update all |LayoutObject::FirstInlineFragment()| // clients, we should replace to |return MakeNull()| - item_iter_ = items_.begin(); - while (current_item_ && CurrentLayoutObject() != &layout_object) - MoveToNextItem(); + MoveToItem(SlowFirstItemIteratorFor(layout_object)); return; } - DCHECK_LT(index, items_.size()); - if (!had_root) - return MoveToItem(items_.begin() + index); - // Map |index| in |NGFragmentItems| to index of |items_|. - const LayoutBlockFlow& block_flow = - *layout_object.RootInlineFormattingContext(); - const auto items = - ItemsSpan(block_flow.CurrentFragment()->Items()->Items()); - // Note: We use address instead of iterator because we can't compare - // iterators in different span. See |base::CheckedContiguousIterator<T>|. - const ptrdiff_t adjusted_index = - &*(items.begin() + index) - &*items_.begin(); - DCHECK_GE(adjusted_index, 0); - DCHECK_LT(static_cast<size_t>(adjusted_index), items_.size()); - return MoveToItem(items_.begin() + adjusted_index); + const unsigned span_index = SpanIndexFromItemIndex(item_index); + DCHECK_EQ(span_index, + static_cast<unsigned>(SlowFirstItemIteratorFor(layout_object) - + items_.begin())); + return MoveToItem(items_.begin() + span_index); } if (root_paint_fragment_) { const auto fragments = NGPaintFragment::InlineFragmentsFor(&layout_object); @@ -698,13 +937,16 @@ void NGInlineCursor::InternalMoveTo(const LayoutObject& layout_object) { void NGInlineCursor::MoveTo(const LayoutObject& layout_object) { DCHECK(layout_object.IsInLayoutNGInlineFormattingContext()) << layout_object; InternalMoveTo(layout_object); - if (*this || !HasRoot()) { + if (*this || !HasRoot() || + RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { layout_inline_ = nullptr; return; } // This |layout_object| did not produce any fragments. - // + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; + // Try to find ancestors if this is a culled inline. layout_inline_ = ToLayoutInlineOrNull(&layout_object); if (!layout_inline_) @@ -715,17 +957,28 @@ void NGInlineCursor::MoveTo(const LayoutObject& layout_object) { MoveToNext(); } +void NGInlineCursor::MoveTo(const NGFragmentItem& fragment_item) { + DCHECK(!root_paint_fragment_ && !current_.paint_fragment_); + MoveTo(*fragment_item.GetLayoutObject()); + while (IsNotNull()) { + if (CurrentItem() == &fragment_item) + return; + MoveToNext(); + } + NOTREACHED(); +} + void NGInlineCursor::MoveTo(const NGInlineCursor& cursor) { if (const NGPaintFragment* paint_fragment = cursor.CurrentPaintFragment()) { MoveTo(*paint_fragment); return; } - if (cursor.current_item_) { + if (cursor.current_.item_) { if (!fragment_items_) SetRoot(*cursor.fragment_items_); // Note: We use address instead of iterato because we can't compare // iterators in different span. See |base::CheckedContiguousIterator<T>|. - const ptrdiff_t index = &*cursor.item_iter_ - &*items_.begin(); + const ptrdiff_t index = &*cursor.current_.item_iter_ - &*items_.begin(); DCHECK_GE(index, 0); DCHECK_LT(static_cast<size_t>(index), items_.size()); MoveToItem(items_.begin() + index); @@ -741,19 +994,26 @@ void NGInlineCursor::MoveTo(const NGPaintFragment& paint_fragment) { DCHECK(root_paint_fragment_); DCHECK(paint_fragment.IsDescendantOfNotSelf(*root_paint_fragment_)) << paint_fragment << " " << root_paint_fragment_; - current_paint_fragment_ = &paint_fragment; + current_.paint_fragment_ = &paint_fragment; +} + +void NGInlineCursor::MoveTo(const NGPaintFragment* paint_fragment) { + if (paint_fragment) { + MoveTo(*paint_fragment); + return; + } + MakeNull(); } void NGInlineCursor::MoveToContainingLine() { - DCHECK(!IsLineBox()); - if (current_paint_fragment_) { - current_paint_fragment_ = current_paint_fragment_->ContainerLineBox(); + DCHECK(!Current().IsLineBox()); + if (current_.paint_fragment_) { + current_.paint_fragment_ = current_.paint_fragment_->ContainerLineBox(); return; } - if (current_item_) { - do { + if (current_.item_) { + while (current_.item_ && !Current().IsLineBox()) MoveToPreviousItem(); - } while (current_item_ && !IsLineBox()); return; } NOTREACHED(); @@ -761,7 +1021,7 @@ void NGInlineCursor::MoveToContainingLine() { void NGInlineCursor::MoveToFirst() { if (root_paint_fragment_) { - current_paint_fragment_ = root_paint_fragment_->FirstChild(); + current_.paint_fragment_ = root_paint_fragment_->FirstChild(); return; } if (IsItemCursor()) { @@ -777,13 +1037,32 @@ void NGInlineCursor::MoveToFirstChild() { MakeNull(); } +void NGInlineCursor::MoveToFirstLine() { + if (root_paint_fragment_) { + MoveTo(root_paint_fragment_->FirstLineBox()); + return; + } + if (IsItemCursor()) { + auto iter = std::find_if( + items_.begin(), items_.end(), + [](const auto& item) { return item->Type() == NGFragmentItem::kLine; }); + if (iter != items_.end()) { + MoveToItem(iter); + return; + } + MakeNull(); + return; + } + NOTREACHED(); +} + void NGInlineCursor::MoveToFirstLogicalLeaf() { - DCHECK(IsLineBox()); + DCHECK(Current().IsLineBox()); // TODO(yosin): This isn't correct for mixed Bidi. Fix it. Besides, we // should compute and store it during layout. // TODO(yosin): We should check direction of each container instead of line // box. - if (IsLtr(CurrentStyle().Direction())) { + if (IsLtr(Current().Style().Direction())) { while (TryToMoveToFirstChild()) continue; return; @@ -799,21 +1078,23 @@ void NGInlineCursor::MoveToLastChild() { } void NGInlineCursor::MoveToLastForSameLayoutObject() { - NGInlineCursor last; - while (IsNotNull()) { - last = *this; + if (!Current()) + return; + NGInlineCursorPosition last; + do { + last = Current(); MoveToNextForSameLayoutObject(); - } - *this = last; + } while (Current()); + MoveTo(last); } void NGInlineCursor::MoveToLastLogicalLeaf() { - DCHECK(IsLineBox()); + DCHECK(Current().IsLineBox()); // TODO(yosin): This isn't correct for mixed Bidi. Fix it. Besides, we // should compute and store it during layout. // TODO(yosin): We should check direction of each container instead of line // box. - if (IsLtr(CurrentStyle().Direction())) { + if (IsLtr(Current().Style().Direction())) { while (TryToMoveToLastChild()) continue; return; @@ -830,15 +1111,16 @@ void NGInlineCursor::MoveToNext() { void NGInlineCursor::MoveToNextForSameLayoutObject() { if (layout_inline_) { + DCHECK(!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); // Move to next fragment in culled inline box undef |layout_inline_|. do { MoveToNext(); } while (IsNotNull() && !IsPartOfCulledInlineBox(*layout_inline_)); return; } - if (current_paint_fragment_) { + if (current_.paint_fragment_) { if (auto* paint_fragment = - current_paint_fragment_->NextForSameLayoutObject()) { + current_.paint_fragment_->NextForSameLayoutObject()) { // |paint_fragment| can be in another fragment tree rooted by // |root_paint_fragment_|, e.g. "multicol-span-all-restyle-002.html" root_paint_fragment_ = paint_fragment->Root(); @@ -846,11 +1128,11 @@ void NGInlineCursor::MoveToNextForSameLayoutObject() { } return MakeNull(); } - if (current_item_) { - const wtf_size_t delta = current_item_->DeltaToNextForSameLayoutObject(); + if (current_.item_) { + const wtf_size_t delta = current_.item_->DeltaToNextForSameLayoutObject(); if (delta == 0u) return MakeNull(); - return MoveToItem(item_iter_ + delta); + return MoveToItem(current_.item_iter_ + delta); } } @@ -864,7 +1146,7 @@ void NGInlineCursor::MoveToNextInlineLeaf() { void NGInlineCursor::MoveToNextInlineLeafIgnoringLineBreak() { do { MoveToNextInlineLeaf(); - } while (IsNotNull() && IsLineBreak()); + } while (Current() && Current().IsLineBreak()); } void NGInlineCursor::MoveToNextInlineLeafOnLine() { @@ -883,23 +1165,23 @@ void NGInlineCursor::MoveToNextInlineLeafOnLine() { } void NGInlineCursor::MoveToNextLine() { - DCHECK(IsLineBox()); - if (current_paint_fragment_) { - if (auto* paint_fragment = current_paint_fragment_->NextSibling()) + DCHECK(Current().IsLineBox()); + if (current_.paint_fragment_) { + if (auto* paint_fragment = current_.paint_fragment_->NextSibling()) return MoveTo(*paint_fragment); return MakeNull(); } - if (current_item_) { + if (current_.item_) { do { MoveToNextItem(); - } while (IsNotNull() && !IsLineBox()); + } while (Current() && !Current().IsLineBox()); return; } NOTREACHED(); } void NGInlineCursor::MoveToNextSibling() { - if (current_paint_fragment_) + if (current_.paint_fragment_) return MoveToNextSiblingPaintFragment(); return MoveToNextSiblingItem(); } @@ -926,7 +1208,7 @@ void NGInlineCursor::MoveToPreviousInlineLeaf() { void NGInlineCursor::MoveToPreviousInlineLeafIgnoringLineBreak() { do { MoveToPreviousInlineLeaf(); - } while (IsNotNull() && IsLineBreak()); + } while (Current() && Current().IsLineBreak()); } void NGInlineCursor::MoveToPreviousInlineLeafOnLine() { @@ -945,17 +1227,17 @@ void NGInlineCursor::MoveToPreviousInlineLeafOnLine() { void NGInlineCursor::MoveToPreviousLine() { // Note: List marker is sibling of line box. - DCHECK(IsLineBox()); - if (current_paint_fragment_) { + DCHECK(Current().IsLineBox()); + if (current_.paint_fragment_) { do { MoveToPreviousSiblingPaintFragment(); - } while (IsNotNull() && !IsLineBox()); + } while (Current() && !Current().IsLineBox()); return; } - if (current_item_) { + if (current_.item_) { do { MoveToPreviousItem(); - } while (IsNotNull() && !IsLineBox()); + } while (Current() && !Current().IsLineBox()); return; } NOTREACHED(); @@ -965,10 +1247,10 @@ bool NGInlineCursor::TryToMoveToFirstChild() { if (!HasChildren()) return false; if (root_paint_fragment_) { - MoveTo(*current_paint_fragment_->FirstChild()); + MoveTo(*current_.paint_fragment_->FirstChild()); return true; } - MoveToItem(item_iter_ + 1); + MoveToItem(current_.item_iter_ + 1); return true; } @@ -976,14 +1258,14 @@ bool NGInlineCursor::TryToMoveToLastChild() { if (!HasChildren()) return false; if (root_paint_fragment_) { - MoveTo(current_paint_fragment_->Children().back()); + MoveTo(current_.paint_fragment_->Children().back()); return true; } - const auto end = item_iter_ + CurrentItem()->DescendantsCount(); + const auto end = current_.item_iter_ + CurrentItem()->DescendantsCount(); MoveToNextItem(); DCHECK(!IsNull()); - for (auto it = item_iter_ + 1; it != end; ++it) { - if (CurrentItem()->HasSameParent(**it)) + for (auto it = current_.item_iter_ + 1; it != end; ++it) { + if (CurrentItem()->IsSiblingOf(**it)) MoveToItem(it); } return true; @@ -991,78 +1273,78 @@ bool NGInlineCursor::TryToMoveToLastChild() { void NGInlineCursor::MoveToNextItem() { DCHECK(IsItemCursor()); - if (UNLIKELY(!current_item_)) + if (UNLIKELY(!current_.item_)) return; - DCHECK(item_iter_ != items_.end()); - ++item_iter_; - MoveToItem(item_iter_); + DCHECK(current_.item_iter_ != items_.end()); + ++current_.item_iter_; + MoveToItem(current_.item_iter_); } void NGInlineCursor::MoveToNextItemSkippingChildren() { DCHECK(IsItemCursor()); - if (UNLIKELY(!current_item_)) + if (UNLIKELY(!current_.item_)) return; // If the current item has |DescendantsCount|, add it to move to the next // sibling, skipping all children and their descendants. - if (wtf_size_t descendants_count = current_item_->DescendantsCount()) - return MoveToItem(item_iter_ + descendants_count); + if (wtf_size_t descendants_count = current_.item_->DescendantsCount()) + return MoveToItem(current_.item_iter_ + descendants_count); return MoveToNextItem(); } void NGInlineCursor::MoveToNextSiblingItem() { DCHECK(IsItemCursor()); - if (UNLIKELY(!current_item_)) + if (UNLIKELY(!current_.item_)) return; const NGFragmentItem& item = *CurrentItem(); MoveToNextItemSkippingChildren(); - if (IsNull() || item.HasSameParent(*CurrentItem())) + if (IsNull() || item.IsSiblingOf(*CurrentItem())) return; MakeNull(); } void NGInlineCursor::MoveToPreviousItem() { DCHECK(IsItemCursor()); - if (UNLIKELY(!current_item_)) + if (UNLIKELY(!current_.item_)) return; - if (item_iter_ == items_.begin()) + if (current_.item_iter_ == items_.begin()) return MakeNull(); - --item_iter_; - current_item_ = item_iter_->get(); + --current_.item_iter_; + current_.item_ = current_.item_iter_->get(); } void NGInlineCursor::MoveToParentPaintFragment() { - DCHECK(IsPaintFragmentCursor() && current_paint_fragment_); - const NGPaintFragment* parent = current_paint_fragment_->Parent(); + DCHECK(IsPaintFragmentCursor() && current_.paint_fragment_); + const NGPaintFragment* parent = current_.paint_fragment_->Parent(); if (parent && parent != root_paint_fragment_) { - current_paint_fragment_ = parent; + current_.paint_fragment_ = parent; return; } - current_paint_fragment_ = nullptr; + current_.paint_fragment_ = nullptr; } void NGInlineCursor::MoveToNextPaintFragment() { - DCHECK(IsPaintFragmentCursor() && current_paint_fragment_); - if (const NGPaintFragment* child = current_paint_fragment_->FirstChild()) { - current_paint_fragment_ = child; + DCHECK(IsPaintFragmentCursor() && current_.paint_fragment_); + if (const NGPaintFragment* child = current_.paint_fragment_->FirstChild()) { + current_.paint_fragment_ = child; return; } MoveToNextPaintFragmentSkippingChildren(); } void NGInlineCursor::MoveToNextSiblingPaintFragment() { - DCHECK(IsPaintFragmentCursor() && current_paint_fragment_); - if (const NGPaintFragment* next = current_paint_fragment_->NextSibling()) { - current_paint_fragment_ = next; + DCHECK(IsPaintFragmentCursor() && current_.paint_fragment_); + if (const NGPaintFragment* next = current_.paint_fragment_->NextSibling()) { + current_.paint_fragment_ = next; return; } - current_paint_fragment_ = nullptr; + current_.paint_fragment_ = nullptr; } void NGInlineCursor::MoveToNextPaintFragmentSkippingChildren() { - DCHECK(IsPaintFragmentCursor() && current_paint_fragment_); - while (current_paint_fragment_) { - if (const NGPaintFragment* next = current_paint_fragment_->NextSibling()) { - current_paint_fragment_ = next; + DCHECK(IsPaintFragmentCursor() && current_.paint_fragment_); + while (current_.paint_fragment_) { + if (const NGPaintFragment* next = current_.paint_fragment_->NextSibling()) { + current_.paint_fragment_ = next; return; } MoveToParentPaintFragment(); @@ -1070,25 +1352,25 @@ void NGInlineCursor::MoveToNextPaintFragmentSkippingChildren() { } void NGInlineCursor::MoveToPreviousPaintFragment() { - DCHECK(IsPaintFragmentCursor() && current_paint_fragment_); - const NGPaintFragment* const parent = current_paint_fragment_->Parent(); + DCHECK(IsPaintFragmentCursor() && current_.paint_fragment_); + const NGPaintFragment* const parent = current_.paint_fragment_->Parent(); MoveToPreviousSiblingPaintFragment(); - if (current_paint_fragment_) { + if (current_.paint_fragment_) { while (TryToMoveToLastChild()) continue; return; } - current_paint_fragment_ = parent == root_paint_fragment_ ? nullptr : parent; + current_.paint_fragment_ = parent == root_paint_fragment_ ? nullptr : parent; } void NGInlineCursor::MoveToPreviousSiblingPaintFragment() { - DCHECK(IsPaintFragmentCursor() && current_paint_fragment_); - const NGPaintFragment* const current = current_paint_fragment_; - current_paint_fragment_ = nullptr; + DCHECK(IsPaintFragmentCursor() && current_.paint_fragment_); + const NGPaintFragment* const current = current_.paint_fragment_; + current_.paint_fragment_ = nullptr; for (auto* sibling : current->Parent()->Children()) { if (sibling == current) return; - current_paint_fragment_ = sibling; + current_.paint_fragment_ = sibling; } NOTREACHED(); } @@ -1096,28 +1378,35 @@ void NGInlineCursor::MoveToPreviousSiblingPaintFragment() { NGInlineBackwardCursor::NGInlineBackwardCursor(const NGInlineCursor& cursor) : cursor_(cursor) { if (cursor.root_paint_fragment_) { + DCHECK(!cursor.CurrentPaintFragment() || + cursor.CurrentPaintFragment()->Parent()->FirstChild() == + cursor.CurrentPaintFragment()); for (NGInlineCursor sibling(cursor); sibling; sibling.MoveToNextSibling()) sibling_paint_fragments_.push_back(sibling.CurrentPaintFragment()); current_index_ = sibling_paint_fragments_.size(); if (current_index_) - current_paint_fragment_ = sibling_paint_fragments_[--current_index_]; + current_.paint_fragment_ = sibling_paint_fragments_[--current_index_]; return; } if (cursor.IsItemCursor()) { - for (NGInlineCursor sibling(cursor); sibling; sibling.MoveToNextSibling()) - sibling_item_iterators_.push_back(sibling.item_iter_); + DCHECK(!cursor || cursor.items_.begin() == cursor.Current().item_iter_); + for (NGInlineCursor sibling(cursor); sibling; + sibling.MoveToNextSkippingChildren()) + sibling_item_iterators_.push_back(sibling.Current().item_iter_); current_index_ = sibling_item_iterators_.size(); - if (current_index_) - current_item_ = sibling_item_iterators_[--current_index_]->get(); + if (current_index_) { + current_.item_iter_ = sibling_item_iterators_[--current_index_]; + current_.item_ = current_.item_iter_->get(); + } return; } - NOTREACHED(); + DCHECK(!cursor); } NGInlineCursor NGInlineBackwardCursor::CursorForDescendants() const { - if (const NGPaintFragment* current_paint_fragment = CurrentPaintFragment()) + if (const NGPaintFragment* current_paint_fragment = Current().PaintFragment()) return NGInlineCursor(*current_paint_fragment); - if (current_item_) { + if (current_.item_) { NGInlineCursor cursor(cursor_); cursor.MoveToItem(sibling_item_iterators_[current_index_]); return cursor.CursorForDescendants(); @@ -1126,38 +1415,21 @@ NGInlineCursor NGInlineBackwardCursor::CursorForDescendants() const { return NGInlineCursor(); } -const PhysicalOffset NGInlineBackwardCursor::CurrentOffset() const { - if (current_paint_fragment_) - return current_paint_fragment_->InlineOffsetToContainerBox(); - if (current_item_) - return current_item_->Offset(); - NOTREACHED(); - return PhysicalOffset(); -} - -const PhysicalRect NGInlineBackwardCursor::CurrentSelfInkOverflow() const { - if (current_paint_fragment_) - return current_paint_fragment_->SelfInkOverflow(); - if (current_item_) - return current_item_->SelfInkOverflow(); - NOTREACHED(); - return PhysicalRect(); -} - void NGInlineBackwardCursor::MoveToPreviousSibling() { if (current_index_) { - if (current_paint_fragment_) { - current_paint_fragment_ = sibling_paint_fragments_[--current_index_]; + if (current_.paint_fragment_) { + current_.paint_fragment_ = sibling_paint_fragments_[--current_index_]; return; } - if (current_item_) { - current_item_ = sibling_item_iterators_[--current_index_]->get(); + if (current_.item_) { + current_.item_iter_ = sibling_item_iterators_[--current_index_]; + current_.item_ = current_.item_iter_->get(); return; } NOTREACHED(); } - current_paint_fragment_ = nullptr; - current_item_ = nullptr; + current_.paint_fragment_ = nullptr; + current_.item_ = nullptr; } std::ostream& operator<<(std::ostream& ostream, const NGInlineCursor& cursor) { @@ -1177,4 +1449,18 @@ std::ostream& operator<<(std::ostream& ostream, const NGInlineCursor* cursor) { return ostream << *cursor; } +#if DCHECK_IS_ON() +void NGInlineCursor::CheckValid(const NGInlineCursorPosition& position) const { + if (position.PaintFragment()) { + DCHECK(root_paint_fragment_); + DCHECK( + position.PaintFragment()->IsDescendantOfNotSelf(*root_paint_fragment_)); + } else if (position.Item()) { + DCHECK(IsItemCursor()); + const unsigned index = position.item_iter_ - items_.begin(); + DCHECK_LT(index, items_.size()); + } +} +#endif + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h index 9c23ab2daef..c9626d87b5e 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h @@ -25,7 +25,9 @@ class LayoutObject; class LayoutUnit; class NGFragmentItem; class NGFragmentItems; +class NGInlineBackwardCursor; class NGInlineBreakToken; +class NGInlineCursor; class NGPaintFragment; class NGPhysicalBoxFragment; class Node; @@ -35,6 +37,128 @@ struct PhysicalOffset; struct PhysicalRect; struct PhysicalSize; +// Represents a position of |NGInlineCursor|. This class: +// 1. Provides properties for the current position. +// 2. Allows to save |Current()|, and can move back later. Moving to |Position| +// is faster than moving to |NGFragmentItem|. +class CORE_EXPORT NGInlineCursorPosition { + STACK_ALLOCATED(); + + public: + using ItemsSpan = base::span<const std::unique_ptr<NGFragmentItem>>; + + const NGPaintFragment* PaintFragment() const { return paint_fragment_; } + const NGFragmentItem* Item() const { return item_; } + const NGFragmentItem* operator->() const { return item_; } + const NGFragmentItem& operator*() const { return *item_; } + + operator bool() const { return paint_fragment_ || item_; } + + bool operator==(const NGInlineCursorPosition& other) const { + return paint_fragment_ == other.paint_fragment_ && item_ == other.item_; + } + bool operator!=(const NGInlineCursorPosition& other) const { + return !operator==(other); + } + + // True if the current position is a text. It is error to call at end. + bool IsText() const; + + // True if the current position is a generatd text. It is error to call at + // end. + bool IsGeneratedText() const; + + // True if fragment is |NGFragmentItem::kGeneratedText| or + // |NGPhysicalTextFragment::kGeneratedText|. + // TODO(yosin): We should rename |IsGeneratedTextType()| to another name. + bool IsGeneratedTextType() const; + + // True if the current position is a line break. It is error to call at end. + bool IsLineBreak() const; + + // True if the current position is an ellipsis. It is error to call at end. + bool IsEllipsis() const; + + // True if the current position is a line box. It is error to call at end. + bool IsLineBox() const; + + // True if the current position is an empty line box. It is error to call + // other then line box. + bool IsEmptyLineBox() const; + + // True if the current position is an inline box. It is error to call at end. + bool IsInlineBox() const; + + // True if the current position is an atomic inline. It is error to call at + // end. + bool IsAtomicInline() const; + + // True if the current position is a list marker. + bool IsListMarker() const; + + // True if the current position is hidden for paint. It is error to call at + // end. + bool IsHiddenForPaint() const; + + // |ComputedStyle| and related functions. + NGStyleVariant StyleVariant() const; + bool UsesFirstLineStyle() const; + const ComputedStyle& Style() const; + + // Functions to get corresponding objects for this position. + const NGPhysicalBoxFragment* BoxFragment() const; + const LayoutObject* GetLayoutObject() const; + LayoutObject* GetMutableLayoutObject() const; + const Node* GetNode() const; + const DisplayItemClient* GetDisplayItemClient() const; + + // Returns break token for line box. It is error to call other than line box. + const NGInlineBreakToken* InlineBreakToken() const; + + // The offset relative to the root of the inline formatting context. + const PhysicalRect RectInContainerBlock() const; + const PhysicalOffset OffsetInContainerBlock() const; + const PhysicalSize Size() const; + + // InkOverflow of itself, including contents if they contribute to the ink + // overflow of this object (e.g. when not clipped,) in the local coordinate. + const PhysicalRect InkOverflow() const; + const PhysicalRect SelfInkOverflow() const; + + // Returns start/end of offset in text content of current text fragment. + // It is error when this cursor doesn't point to text fragment. + NGTextOffset TextOffset() const; + unsigned TextStartOffset() const { return TextOffset().start; } + unsigned TextEndOffset() const { return TextOffset().end; } + + // Returns text of the current position. It is error to call other than + // text. + StringView Text(const NGInlineCursor& cursor) const; + + // Returns |ShapeResultView| of the current position. It is error to call + // other than text. + const ShapeResultView* TextShapeResult() const; + + // Returns bidi level of current position. It is error to call other than + // text and atomic inline. It is also error to call |IsGeneratedTextType()|. + UBiDiLevel BidiLevel() const; + // Returns text direction of current text or atomic inline. It is error to + // call at other than text or atomic inline. Note: <span> doesn't have + // reserved direction. + TextDirection ResolvedDirection() const; + // Returns text direction of current line. It is error to call at other than + // line. + TextDirection BaseDirection() const; + + private: + const NGPaintFragment* paint_fragment_ = nullptr; + const NGFragmentItem* item_ = nullptr; + ItemsSpan::iterator item_iter_; + + friend class NGInlineBackwardCursor; + friend class NGInlineCursor; +}; + // This class traverses fragments in an inline formatting context. // // When constructed, the initial position is empty. Call |MoveToNext()| to move @@ -53,12 +177,13 @@ class CORE_EXPORT NGInlineCursor { explicit NGInlineCursor(const NGFragmentItems& fragment_items, ItemsSpan items); explicit NGInlineCursor(const NGPaintFragment& root_paint_fragment); - NGInlineCursor(const NGInlineCursor& other); + NGInlineCursor(const NGInlineCursor& other) = default; + NGInlineCursor(const NGInlineBackwardCursor& backward_cursor); // Creates an |NGInlineCursor| without the root. Even when callers don't know // the root of the inline formatting context, this cursor can |MoveTo()| // specific |LayoutObject|. - NGInlineCursor(); + NGInlineCursor() = default; bool operator==(const NGInlineCursor& other) const; bool operator!=(const NGInlineCursor& other) const { @@ -83,12 +208,13 @@ class CORE_EXPORT NGInlineCursor { // // Functions to query the current position. // + const NGInlineCursorPosition& Current() const { return current_; } // Returns true if cursor is out of fragment tree, e.g. before first fragment // or after last fragment in tree. - bool IsNull() const { return !current_item_ && !current_paint_fragment_; } - bool IsNotNull() const { return !IsNull(); } - explicit operator bool() const { return !IsNull(); } + bool IsNull() const { return !Current(); } + bool IsNotNull() const { return Current(); } + operator bool() const { return Current(); } // True if fragment at the current position can have children. bool CanHaveChildren() const; @@ -101,104 +227,38 @@ class CORE_EXPORT NGInlineCursor { // has no children, returns an empty cursor. NGInlineCursor CursorForDescendants() const; + // If |this| is created by |CursorForDescendants()| to traverse parts of an + // inline formatting context, expand the traversable range to the containing + // |LayoutBlockFlow|. Does nothing if |this| is for an inline formatting + // context. + void ExpandRootToContainingBlock(); + // True if current position has soft wrap to next line. It is error to call // other than line. bool HasSoftWrapToNextLine() const; - // True if the current position is a atomic inline. It is error to call at - // end. - bool IsAtomicInline() const; - // True if the current position is before soft line break. It is error to call // at end. bool IsBeforeSoftLineBreak() const; - // True if the current position is an ellipsis. It is error to call at end. - bool IsEllipsis() const; - - // True if the current position is an empty line box. It is error to call - // other then line box. - bool IsEmptyLineBox() const; - - // True if the current position is a generatd text. It is error to call at - // end. - bool IsGeneratedText() const; - - // True if fragment is |NGFragmentItem::kGeneratedText| or - // |NGPhysicalTextFragment::kGeneratedText|. - // TODO(yosin): We should rename |IsGeneratedTextType()| to another name. - bool IsGeneratedTextType() const; - - // True if the current position is hidden for paint. It is error to call at - // end. - bool IsHiddenForPaint() const; - - // True if the current position's writing mode in style is horizontal. - bool IsHorizontal() const; - // True if the current position is text or atomic inline box. // Note: Because of this function is used for caret rect, hit testing, etc, // this function returns false for hidden for paint, text overflow ellipsis, // and line break hyphen. bool IsInlineLeaf() const; - // True if the current position is a line box. It is error to call at end. - bool IsLineBox() const; - - // True if the current position is a line break. It is error to call at end. - bool IsLineBreak() const; - - // True if the current position is a list marker. - bool IsListMarker() const; - - // True if the current position is a text. It is error to call at end. - bool IsText() const; - // |Current*| functions return an object for the current position. - const NGFragmentItem* CurrentItem() const { return current_item_; } + const NGFragmentItem* CurrentItem() const { return Current().Item(); } const NGPaintFragment* CurrentPaintFragment() const { - return current_paint_fragment_; + return Current().PaintFragment(); + } + LayoutObject* CurrentMutableLayoutObject() const { + return Current().GetMutableLayoutObject(); } - // Returns text direction of current line. It is error to call at other than - // line. - TextDirection CurrentBaseDirection() const; - const NGPhysicalBoxFragment* CurrentBoxFragment() const; - const DisplayItemClient* CurrentDisplayItemClient() const; - const LayoutObject* CurrentLayoutObject() const; - LayoutObject* CurrentMutableLayoutObject() const; - Node* CurrentNode() const; - - // Returns bidi level of current position. It is error to call other than - // text and atomic inline. It is also error to call |IsGeneratedTextType()|. - UBiDiLevel CurrentBidiLevel() const; - - // Returns text direction of current text or atomic inline. It is error to - // call at other than text or atomic inline. Note: <span> doesn't have - // reserved direction. - TextDirection CurrentResolvedDirection() const; - const ComputedStyle& CurrentStyle() const; - - // InkOverflow of itself, including contents if they contribute to the ink - // overflow of this object (e.g. when not clipped,) in the local coordinate. - const PhysicalRect CurrentInkOverflow() const; - // The offset relative to the root of the inline formatting context. - const PhysicalOffset CurrentOffset() const; - const PhysicalRect CurrentRect() const; - const PhysicalSize CurrentSize() const; - - // Returns start/end of offset in text content of current text fragment. - // It is error when this cursor doesn't point to text fragment. - NGTextOffset CurrentTextOffset() const; - unsigned CurrentTextStartOffset() const { return CurrentTextOffset().start; } - unsigned CurrentTextEndOffset() const { return CurrentTextOffset().end; } // Returns text of the current position. It is error to call other than // text. - StringView CurrentText() const; - - // Returns |ShapeResultView| of the current position. It is error to call - // other than text. - const ShapeResultView* CurrentTextShapeResult() const; + StringView CurrentText() const { return Current().Text(*this); } // The layout box of text in (start, end) range in local coordinate. // Start and end offsets must be between |CurrentTextStartOffset()| and @@ -216,12 +276,24 @@ class CORE_EXPORT NGInlineCursor { PhysicalOffset LineEndPoint() const; // Converts the given point, relative to the fragment itself, into a position - // in DOM tree within the range of |this|. - PositionWithAffinity PositionForPoint(const PhysicalOffset&); + // in DOM tree within the range of |this|. This variation ignores the inline + // offset, and snaps to the nearest line in the block direction. + PositionWithAffinity PositionForPointInInlineFormattingContext( + const PhysicalOffset& point, + const NGPhysicalBoxFragment& container); + // Find the |Position| in the line box |Current()| points to. This variation + // ignores the block offset, and snaps to the nearest item in inline + // direction. + PositionWithAffinity PositionForPointInInlineBox( + const PhysicalOffset& point) const; // // Functions to move the current position. // + void MoveTo(const NGInlineCursorPosition& position); + + // Move the current position at |fragment_item|. + void MoveTo(const NGFragmentItem& fragment_item); // Move the current position at |cursor|. Unlinke copy constrcutr, this // function doesn't copy root. Note: The current position in |cursor| @@ -230,6 +302,7 @@ class CORE_EXPORT NGInlineCursor { // Move the current posint at |paint_fragment|. void MoveTo(const NGPaintFragment& paint_fragment); + void MoveTo(const NGPaintFragment* paint_fragment); // Move to first |NGFragmentItem| or |NGPaintFragment| associated to // |layout_object|. When |layout_object| has no associated fragments, this @@ -244,6 +317,9 @@ class CORE_EXPORT NGInlineCursor { // See also |TryToMoveToFirstChild()|. void MoveToFirstChild(); + // Move to the first line. + void MoveToFirstLine(); + // Move to first logical leaf of current line box. If current line box has // no children, curosr becomes null. void MoveToFirstLogicalLeaf(); @@ -253,6 +329,9 @@ class CORE_EXPORT NGInlineCursor { // See also |TryToMoveToFirstChild()|. void MoveToLastChild(); + // Move the current position to the last fragment on same layout object. + void MoveToLastForSameLayoutObject(); + // Move to last logical leaf of current line box. If current line box has // no children, curosr becomes null. void MoveToLastLogicalLeaf(); @@ -269,6 +348,7 @@ class CORE_EXPORT NGInlineCursor { void MoveToNextLine(); // Move the current position to next sibling fragment. + // |MoveToNextSibling()| is deprecated. New code should not be used. void MoveToNextSibling(); // Same as |MoveToNext| except that this skips children even if they exist. @@ -304,14 +384,13 @@ class CORE_EXPORT NGInlineCursor { // TODO(kojii): Add more variations as needed, NextSibling, // NextSkippingChildren, Previous, etc. - private: - // Returns break token for line box. It is error to call other than line box. - const NGInlineBreakToken& CurrentInlineBreakToken() const; - - // Returns style variant of the current position. - NGStyleVariant CurrentStyleVariant() const; - bool UsesFirstLineStyle() const; +#if DCHECK_IS_ON() + void CheckValid(const NGInlineCursorPosition& position) const; +#else + void CheckValid(const NGInlineCursorPosition&) const {} +#endif + private: // True if current position is part of culled inline box |layout_inline|. bool IsPartOfCulledInlineBox(const LayoutInline& layout_inline) const; @@ -326,9 +405,6 @@ class CORE_EXPORT NGInlineCursor { // Move the cursor position to the first fragment in tree. void MoveToFirst(); - // Move the current position to the last fragment on same layout object. - void MoveToLastForSameLayoutObject(); - // Same as |MoveTo()| but not support culled inline. void InternalMoveTo(const LayoutObject& layout_object); @@ -350,13 +426,20 @@ class CORE_EXPORT NGInlineCursor { void MoveToPreviousPaintFragment(); void MoveToPreviousSiblingPaintFragment(); + ItemsSpan::iterator SlowFirstItemIteratorFor( + const LayoutObject& layout_object) const; + unsigned SpanIndexFromItemIndex(unsigned index) const; + + PositionWithAffinity PositionForPointInChild( + const PhysicalOffset& point, + const NGFragmentItem& child_item) const; + + NGInlineCursorPosition current_; + ItemsSpan items_; - ItemsSpan::iterator item_iter_; - const NGFragmentItem* current_item_ = nullptr; const NGFragmentItems* fragment_items_ = nullptr; const NGPaintFragment* root_paint_fragment_ = nullptr; - const NGPaintFragment* current_paint_fragment_ = nullptr; // Used in |MoveToNextForSameLayoutObject()| to support culled inline. const LayoutInline* layout_inline_ = nullptr; @@ -370,31 +453,25 @@ class CORE_EXPORT NGInlineBackwardCursor { STACK_ALLOCATED(); public: + // |cursor| should be the first child of root or descendants, e.g. the first + // item in |NGInlineCursor::items_|. NGInlineBackwardCursor(const NGInlineCursor& cursor); - NGInlineCursor CursorForDescendants() const; - - explicit operator bool() const { - return current_paint_fragment_ || current_item_; - } - - const NGFragmentItem* CurrentItem() const { return current_item_; } - const NGPaintFragment* CurrentPaintFragment() const { - return current_paint_fragment_; - } + const NGInlineCursorPosition& Current() const { return current_; } + operator bool() const { return Current(); } - const PhysicalOffset CurrentOffset() const; - const PhysicalRect CurrentSelfInkOverflow() const; + NGInlineCursor CursorForDescendants() const; void MoveToPreviousSibling(); private: + NGInlineCursorPosition current_; const NGInlineCursor& cursor_; Vector<const NGPaintFragment*, 16> sibling_paint_fragments_; Vector<NGInlineCursor::ItemsSpan::iterator, 16> sibling_item_iterators_; - const NGPaintFragment* current_paint_fragment_ = nullptr; - const NGFragmentItem* current_item_ = nullptr; wtf_size_t current_index_; + + friend class NGInlineCursor; }; CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGInlineCursor&); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc index 8ded96613d1..8398da81ecb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor_test.cc @@ -53,7 +53,7 @@ class NGInlineCursorTest : public NGLayoutTest, Vector<const NGPaintFragment*> backwards; for (NGInlineBackwardCursor cursor(start); cursor; cursor.MoveToPreviousSibling()) - backwards.push_back(cursor.CurrentPaintFragment()); + backwards.push_back(cursor.Current().PaintFragment()); backwards.Reverse(); EXPECT_THAT(backwards, forwards); return; @@ -65,16 +65,16 @@ class NGInlineCursorTest : public NGLayoutTest, Vector<const NGFragmentItem*> backwards; for (NGInlineBackwardCursor cursor(start); cursor; cursor.MoveToPreviousSibling()) - backwards.push_back(cursor.CurrentItem()); + backwards.push_back(cursor.Current().Item()); backwards.Reverse(); EXPECT_THAT(backwards, forwards); } String ToDebugString(const NGInlineCursor& cursor) { - if (cursor.IsLineBox()) + if (cursor.Current().IsLineBox()) return "#linebox"; - if (cursor.IsGeneratedTextType()) { + if (cursor.Current().IsGeneratedTextType()) { StringBuilder result; result.Append("#'"); result.Append(cursor.CurrentText()); @@ -82,10 +82,11 @@ class NGInlineCursorTest : public NGLayoutTest, return result.ToString(); } - if (cursor.IsText()) + if (cursor.Current().IsText()) return cursor.CurrentText().ToString().StripWhiteSpace(); - if (const LayoutObject* layout_object = cursor.CurrentLayoutObject()) { + if (const LayoutObject* layout_object = + cursor.Current().GetLayoutObject()) { if (const Element* element = DynamicTo<Element>(layout_object->GetNode())) { if (const AtomicString& id = element->GetIdAttribute()) @@ -100,18 +101,22 @@ class NGInlineCursorTest : public NGLayoutTest, Vector<String> ToDebugStringListWithBidiLevel(const NGInlineCursor& start) { Vector<String> list; - for (NGInlineCursor cursor(start); cursor; cursor.MoveToNext()) + for (NGInlineCursor cursor(start); cursor; cursor.MoveToNext()) { + // Inline boxes do not have bidi level. + if (cursor.Current().IsInlineBox()) + continue; list.push_back(ToDebugStringWithBidiLevel(cursor)); + } return list; } String ToDebugStringWithBidiLevel(const NGInlineCursor& cursor) { - if (!cursor.IsText() && !cursor.IsAtomicInline()) + if (!cursor.Current().IsText() && !cursor.Current().IsAtomicInline()) return ToDebugString(cursor); StringBuilder result; result.Append(ToDebugString(cursor)); result.Append(':'); - result.AppendNumber(cursor.CurrentBidiLevel()); + result.AppendNumber(cursor.Current().BidiLevel()); return result.ToString(); } }; @@ -126,8 +131,8 @@ TEST_P(NGInlineCursorTest, BidiLevelInlineBoxLTR) { "<div id=root dir=ltr>" "abc<b id=def>def</b><bdo dir=rtl><b id=ghi>GHI</b></bdo>jkl</div>"); Vector<String> list = ToDebugStringListWithBidiLevel(cursor); - EXPECT_THAT(list, ElementsAre("#linebox", "abc:0", "#def:0", - "LayoutInline BDO", "#ghi:1", "jkl:0")); + EXPECT_THAT(list, + ElementsAre("#linebox", "abc:0", "#def:0", "#ghi:1", "jkl:0")); } TEST_P(NGInlineCursorTest, BidiLevelInlineBoxRTL) { @@ -136,8 +141,8 @@ TEST_P(NGInlineCursorTest, BidiLevelInlineBoxRTL) { "<div id=root dir=rtl>" "abc<b id=def>def</b><bdo dir=rtl><b id=ghi>GHI</b></bdo>jkl</div>"); Vector<String> list = ToDebugStringListWithBidiLevel(cursor); - EXPECT_THAT(list, ElementsAre("#linebox", "LayoutInline BDO", "#ghi:3", - "jkl:2", "#def:1", "abc:2")); + EXPECT_THAT(list, + ElementsAre("#linebox", "#ghi:3", "jkl:2", "#def:1", "abc:2")); } TEST_P(NGInlineCursorTest, BidiLevelSimpleLTR) { @@ -163,7 +168,7 @@ TEST_P(NGInlineCursorTest, BidiLevelSimpleRTL) { TEST_P(NGInlineCursorTest, GetLayoutBlockFlowWithScopedCursor) { NGInlineCursor line = SetupCursor("<div id=root>line1<br>line2</div>"); - ASSERT_TRUE(line.IsLineBox()) << line; + ASSERT_TRUE(line.Current().IsLineBox()) << line; NGInlineCursor cursor = line.CursorForDescendants(); EXPECT_EQ(line.GetLayoutBlockFlow(), cursor.GetLayoutBlockFlow()); } @@ -175,11 +180,11 @@ TEST_P(NGInlineCursorTest, ContainingLine) { SetupCursor("<div id=root>abc<a id=target>def</a>ghi<br>xyz</div>"); const LayoutBlockFlow& block_flow = *cursor.GetLayoutBlockFlow(); NGInlineCursor line1(cursor); - ASSERT_TRUE(line1.IsLineBox()); + ASSERT_TRUE(line1.Current().IsLineBox()); NGInlineCursor line2(line1); line2.MoveToNextSibling(); - ASSERT_TRUE(line2.IsLineBox()); + ASSERT_TRUE(line2.Current().IsLineBox()); cursor.MoveTo(*block_flow.FirstChild()); cursor.MoveToContainingLine(); @@ -212,9 +217,14 @@ TEST_P(NGInlineCursorTest, CulledInlineWithAtomicInline) { list.push_back(ToDebugString(cursor)); cursor.MoveToNextForSameLayoutObject(); } - EXPECT_THAT(list, ElementsAre("abc", "ABC", "", "XYZ", "xyz")); + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + EXPECT_THAT(list, ElementsAre("#culled", "#culled")); + else + EXPECT_THAT(list, ElementsAre("abc", "ABC", "", "XYZ", "xyz")); } +// We should not have float:right fragment, because it isn't in-flow in +// an inline formatting context. // For https://crbug.com/1026022 TEST_P(NGInlineCursorTest, CulledInlineWithFloat) { SetBodyInnerHTML( @@ -228,23 +238,27 @@ TEST_P(NGInlineCursorTest, CulledInlineWithFloat) { list.push_back(ToDebugString(cursor)); cursor.MoveToNextForSameLayoutObject(); } - EXPECT_THAT(list, ElementsAre("abc", "xyz")) - << "We should not have float:right fragment, because it isn't in-flow in " - "an inline formatting context."; + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + EXPECT_THAT(list, ElementsAre("#culled")); + else + EXPECT_THAT(list, ElementsAre("abc", "xyz")); } TEST_P(NGInlineCursorTest, CulledInlineWithRoot) { - NGInlineCursor cursor = - SetupCursor("<div id=root><a><b>abc</b><br><i>xyz</i></a></div>"); - const LayoutInline& layout_inline = - ToLayoutInline(*cursor.GetLayoutBlockFlow()->FirstChild()); - cursor.MoveTo(layout_inline); + NGInlineCursor cursor = SetupCursor(R"HTML( + <div id="root"><a id="a"><b>abc</b><br><i>xyz</i></a></div> + )HTML"); + const LayoutObject* layout_inline_a = GetLayoutObjectByElementId("a"); + cursor.MoveTo(*layout_inline_a); Vector<String> list; while (cursor) { list.push_back(ToDebugString(cursor)); cursor.MoveToNextForSameLayoutObject(); } - EXPECT_THAT(list, ElementsAre("abc", "", "xyz")); + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + EXPECT_THAT(list, ElementsAre("#a", "#a")); + else + EXPECT_THAT(list, ElementsAre("abc", "", "xyz")); } TEST_P(NGInlineCursorTest, CulledInlineWithoutRoot) { @@ -259,7 +273,10 @@ TEST_P(NGInlineCursorTest, CulledInlineWithoutRoot) { list.push_back(ToDebugString(cursor)); cursor.MoveToNextForSameLayoutObject(); } - EXPECT_THAT(list, ElementsAre("abc", "", "xyz")); + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + EXPECT_THAT(list, ElementsAre("#a", "#a")); + else + EXPECT_THAT(list, ElementsAre("abc", "", "xyz")); } TEST_P(NGInlineCursorTest, FirstChild) { @@ -367,6 +384,17 @@ TEST_P(NGInlineCursorTest, FirstLastLogicalLeafWithImages) { EXPECT_EQ("#last", ToDebugString(last_logical_leaf)); } +TEST_P(NGInlineCursorTest, IsEmptyLineBox) { + InsertStyleElement("b { margin-bottom: 1px; }"); + NGInlineCursor cursor = SetupCursor("<div id=root>abc<br><b></b></div>"); + + EXPECT_FALSE(cursor.Current().IsEmptyLineBox()) + << "'abc\\n' is in non-empty line box."; + cursor.MoveToNextLine(); + EXPECT_TRUE(cursor.Current().IsEmptyLineBox()) + << "<b></b> with margin produces empty line box."; +} + TEST_P(NGInlineCursorTest, LastChild) { // TDOO(yosin): Remove <style> once NGFragmentItem don't do culled inline. InsertStyleElement("a, b { background: gray; }"); @@ -431,11 +459,27 @@ TEST_P(NGInlineCursorTest, NextWithEllipsis) { EXPECT_THAT(list, ElementsAre("#linebox", "abcdefghi", "abcd", u"#'\u2026'")); } +TEST_P(NGInlineCursorTest, NextWithEllipsisInlineBoxOnly) { + LoadAhem(); + InsertStyleElement( + "#root {" + "font: 10px/1 Ahem;" + "width: 5ch;" + "overflow: hidden;" + "text-overflow: ellipsis;" + "}" + "span { border: solid 10ch blue; }"); + NGInlineCursor cursor = SetupCursor("<div id=root><span></span></div>"); + Vector<String> list = ToDebugStringList(cursor); + EXPECT_THAT(list, ElementsAre("#linebox", "LayoutInline SPAN")); +} + TEST_P(NGInlineCursorTest, NextWithListItem) { NGInlineCursor cursor = SetupCursor("<ul><li id=root>abc</li></ul>"); Vector<String> list = ToDebugStringList(cursor); - EXPECT_THAT(list, - ElementsAre("LayoutNGListMarker (anonymous)", "#linebox", "abc")); + EXPECT_THAT(list, ElementsAre("LayoutNGOutsideListMarker ::marker", + "#linebox", "abc")); + EXPECT_EQ(GetLayoutObjectByElementId("root"), cursor.GetLayoutBlockFlow()); } TEST_P(NGInlineCursorTest, NextWithSoftHyphens) { @@ -546,12 +590,12 @@ TEST_P(NGInlineCursorTest, NextInlineLeafIgnoringLineBreak) { TEST_P(NGInlineCursorTest, NextLine) { NGInlineCursor cursor = SetupCursor("<div id=root>abc<br>xyz</div>"); NGInlineCursor line1(cursor); - while (line1 && !line1.IsLineBox()) + while (line1 && !line1.Current().IsLineBox()) line1.MoveToNext(); ASSERT_TRUE(line1.IsNotNull()); NGInlineCursor line2(line1); line2.MoveToNext(); - while (line2 && !line2.IsLineBox()) + while (line2 && !line2.Current().IsLineBox()) line2.MoveToNext(); ASSERT_NE(line1, line2); @@ -576,6 +620,10 @@ TEST_P(NGInlineCursorTest, NextWithInlineBox) { SetupCursor("<div id=root>abc<b id=ib>def</b>xyz</div>"); Vector<String> list = ToDebugStringList(cursor); EXPECT_THAT(list, ElementsAre("#linebox", "abc", "#ib", "xyz")); + + NGInlineCursor cursor2; + cursor2.MoveTo(*GetElementById("ib")->firstChild()->GetLayoutObject()); + EXPECT_EQ(GetLayoutObjectByElementId("ib"), cursor2.GetLayoutBlockFlow()); } TEST_P(NGInlineCursorTest, NextForSameLayoutObject) { @@ -594,10 +642,10 @@ TEST_P(NGInlineCursorTest, Sibling) { InsertStyleElement("a, b { background: gray; }"); NGInlineCursor cursor = SetupCursor("<div id=root>abc<a>DEF<b>GHI</b></a>xyz</div>"); + TestPrevoiusSibling(cursor.CursorForDescendants()); cursor.MoveToFirstChild(); // go to "abc" Vector<String> list = SiblingsToDebugStringList(cursor); EXPECT_THAT(list, ElementsAre("abc", "LayoutInline A", "xyz")); - TestPrevoiusSibling(cursor); } TEST_P(NGInlineCursorTest, Sibling2) { @@ -606,10 +654,10 @@ TEST_P(NGInlineCursorTest, Sibling2) { NGInlineCursor cursor = SetupCursor("<div id=root><a>abc<b>def</b>xyz</a></div>"); cursor.MoveToFirstChild(); // go to <a>abc</a> + TestPrevoiusSibling(cursor.CursorForDescendants()); cursor.MoveToFirstChild(); // go to "abc" Vector<String> list = SiblingsToDebugStringList(cursor); EXPECT_THAT(list, ElementsAre("abc", "LayoutInline B", "xyz")); - TestPrevoiusSibling(cursor); } TEST_P(NGInlineCursorTest, NextSkippingChildren) { @@ -741,12 +789,12 @@ TEST_P(NGInlineCursorTest, PreviousInlineLeafOnLineFromLayoutText) { TEST_P(NGInlineCursorTest, PreviousLine) { NGInlineCursor cursor = SetupCursor("<div id=root>abc<br>xyz</div>"); NGInlineCursor line1(cursor); - while (line1 && !line1.IsLineBox()) + while (line1 && !line1.Current().IsLineBox()) line1.MoveToNext(); ASSERT_TRUE(line1.IsNotNull()); NGInlineCursor line2(line1); line2.MoveToNext(); - while (line2 && !line2.IsLineBox()) + while (line2 && !line2.Current().IsLineBox()) line2.MoveToNext(); ASSERT_NE(line1, line2); @@ -784,9 +832,9 @@ TEST_P(NGInlineCursorTest, CursorForDescendants) { LayoutBlockFlow* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("root")); NGInlineCursor cursor(*block_flow); - EXPECT_TRUE(cursor.IsLineBox()); + EXPECT_TRUE(cursor.Current().IsLineBox()); cursor.MoveToNext(); - EXPECT_TRUE(cursor.IsText()); + EXPECT_TRUE(cursor.Current().IsText()); EXPECT_THAT(ToDebugStringList(cursor.CursorForDescendants()), ElementsAre()); cursor.MoveToNext(); EXPECT_EQ(ToDebugString(cursor), "#span1"); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc index d9aa540d902..443c9070088 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.cc @@ -62,13 +62,13 @@ class NGPhysicalFragmentCollectorBase { // Traverse descendants unless the fragment is laid out separately from the // inline layout algorithm. - if (&fragment != root_fragment_ && fragment.IsBlockFormattingContextRoot()) + if (&fragment != root_fragment_ && fragment.IsFormattingContextRoot()) return; DCHECK(fragment.IsContainer()); DCHECK(fragment.IsInline() || fragment.IsLineBox() || (fragment.IsBlockFlow() && - To<NGPhysicalBoxFragment>(fragment).ChildrenInline())); + To<NGPhysicalBoxFragment>(fragment).IsInlineFormattingContext())); for (const auto& child : To<NGPhysicalContainerFragment>(fragment).Children()) { @@ -179,7 +179,7 @@ Vector<Result> NGInlineFragmentTraversal::SelfFragmentsOf( for (const NGPaintFragment* fragment : NGPaintFragment::InlineFragmentsFor(layout_object)) { result.push_back(Result{&fragment->PhysicalFragment(), - fragment->InlineOffsetToContainerBox()}); + fragment->OffsetInContainerBlock()}); } return result; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc index e9ab83060fe..d356f218bbf 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal_test.cc @@ -50,6 +50,10 @@ class NGInlineFragmentTraversalTest : public NGLayoutTest { } TEST_F(NGInlineFragmentTraversalTest, DescendantsOf) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + // NGFragmentItem doesn't use |NGInlineFragmentTraversal|. + return; + } SetBodyInnerHTML( "<style>* { border: 1px solid}</style>" "<div id=t>foo<b id=b>bar</b><br>baz</div>"); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc index 1e17b432948..bb809f51b2b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc @@ -60,7 +60,8 @@ bool IsInlineBoxEndEmpty(const ComputedStyle& style, NGInlineItem::NGInlineItem(NGInlineItemType type, unsigned start, unsigned end, - LayoutObject* layout_object) + LayoutObject* layout_object, + bool is_first_for_node) : start_offset_(start), end_offset_(end), layout_object_(layout_object), @@ -74,7 +75,8 @@ NGInlineItem::NGInlineItem(NGInlineItemType type, end_collapse_type_(kNotCollapsible), is_end_collapsible_newline_(false), is_symbol_marker_(false), - is_generated_for_line_break_(false) { + is_generated_for_line_break_(false), + is_first_for_node_(is_first_for_node) { DCHECK_GE(end, start); ComputeBoxProperties(); } @@ -97,7 +99,8 @@ NGInlineItem::NGInlineItem(const NGInlineItem& other, end_collapse_type_(other.end_collapse_type_), is_end_collapsible_newline_(other.is_end_collapsible_newline_), is_symbol_marker_(other.is_symbol_marker_), - is_generated_for_line_break_(other.is_generated_for_line_break_) { + is_generated_for_line_break_(other.is_generated_for_line_break_), + is_first_for_node_(other.is_first_for_node_) { DCHECK_GE(end, start); } @@ -197,6 +200,7 @@ void NGInlineItem::Split(Vector<NGInlineItem>& items, items.insert(index + 1, items[index]); items[index].end_offset_ = offset; items[index + 1].start_offset_ = offset; + items[index + 1].is_first_for_node_ = false; } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h index 018e02db8d5..5a8575d3a51 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h @@ -66,7 +66,8 @@ class CORE_EXPORT NGInlineItem { NGInlineItem(NGInlineItemType type, unsigned start, unsigned end, - LayoutObject* layout_object = nullptr); + LayoutObject* layout_object, + bool is_first_for_node); ~NGInlineItem(); // Copy constructor adjusting start/end and shape results. @@ -199,6 +200,11 @@ class CORE_EXPORT NGInlineItem { static void Split(Vector<NGInlineItem>&, unsigned index, unsigned offset); + // Return true if this is the first item created for the node. A node may be + // split into multiple inline items due e.g. hard line breaks or bidi + // segments. + bool IsFirstForNode() const { return is_first_for_node_; } + // RunSegmenter properties. unsigned SegmentData() const { return segment_data_; } static void SetSegmentData(const RunSegmenter::RunSegmenterRange& range, @@ -253,6 +259,7 @@ class CORE_EXPORT NGInlineItem { unsigned is_end_collapsible_newline_ : 1; unsigned is_symbol_marker_ : 1; unsigned is_generated_for_line_break_ : 1; + unsigned is_first_for_node_ : 1; friend class NGInlineNode; friend class NGInlineNodeDataEditor; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc index 801aaf305c2..b6e4388cefb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.cc @@ -220,18 +220,7 @@ LayoutUnit NGLineInfo::ComputeWidth() const { for (const NGInlineItemResult& item_result : Results()) inline_size += item_result.inline_size; - if (UNLIKELY(line_end_fragment_)) { - inline_size += line_end_fragment_->Size() - .ConvertToLogical(LineStyle().GetWritingMode()) - .inline_size; - } - return inline_size; } -void NGLineInfo::SetLineEndFragment( - scoped_refptr<const NGPhysicalTextFragment> fragment) { - line_end_fragment_ = std::move(fragment); -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h index c55dfae46de..2015830ed2c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h @@ -7,7 +7,6 @@ #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h" @@ -37,6 +36,15 @@ struct CORE_EXPORT NGInlineItemResult { return end_offset - start_offset; } + LayoutUnit HyphenInlineSize() const { + return hyphen_shape_result->SnappedWidth().ClampNegativeToZero(); + } + + void ClearHyphen() { + hyphen_string = String(); + hyphen_shape_result = nullptr; + } + // The NGInlineItem and its index. const NGInlineItem* item; unsigned item_index; @@ -52,6 +60,10 @@ struct CORE_EXPORT NGInlineItemResult { // is needed in the line breaker. scoped_refptr<const ShapeResultView> shape_result; + // Hyphen character and its |ShapeResult| if this text is hyphenated. + String hyphen_string; + scoped_refptr<const ShapeResult> hyphen_shape_result; + // NGLayoutResult for atomic inline items. scoped_refptr<const NGLayoutResult> layout_result; @@ -112,10 +124,6 @@ struct CORE_EXPORT NGInlineItemResult { // position) any unpositioned floats. bool has_unpositioned_floats = false; - // End effects for text items. - // The effects are included in |shape_result|, but not in text content. - NGTextEndEffect text_end_effect = NGTextEndEffect::kNone; - NGInlineItemResult(); NGInlineItemResult(const NGInlineItem*, unsigned index, @@ -139,7 +147,7 @@ using NGInlineItemResults = Vector<NGInlineItemResult, 32>; // // NGLineBreaker produces, and NGInlineLayoutAlgorithm consumes. class CORE_EXPORT NGLineInfo { - DISALLOW_NEW(); + STACK_ALLOCATED(); public: const NGInlineItemsData& ItemsData() const { @@ -208,7 +216,7 @@ class CORE_EXPORT NGLineInfo { // True if this line has overflow, excluding preserved trailing spaces. bool HasOverflow() const { return has_overflow_; } - void SetHasOverflow() { has_overflow_ = true; } + void SetHasOverflow(bool value = true) { has_overflow_ = value; } void SetBfcOffset(const NGBfcOffset& bfc_offset) { bfc_offset_ = bfc_offset; } void SetWidth(LayoutUnit available_width, LayoutUnit width) { @@ -242,12 +250,6 @@ class CORE_EXPORT NGLineInfo { // justify alignment. bool NeedsAccurateEndPosition() const { return needs_accurate_end_position_; } - // Fragment to append to the line end. Used by 'text-overflow: ellipsis'. - scoped_refptr<const NGPhysicalTextFragment>& LineEndFragment() { - return line_end_fragment_; - } - void SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>); - private: bool ComputeNeedsAccurateEndPosition() const; @@ -258,7 +260,6 @@ class CORE_EXPORT NGLineInfo { const NGInlineItemsData* items_data_ = nullptr; const ComputedStyle* line_style_ = nullptr; NGInlineItemResults results_; - scoped_refptr<const NGPhysicalTextFragment> line_end_fragment_; NGBfcOffset bfc_offset_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc index 744cff0d35a..84d422cbc20 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc @@ -128,8 +128,10 @@ void AppendItem(Vector<NGInlineItem>* items, NGInlineItem::NGInlineItemType type, unsigned start, unsigned end, - LayoutObject* layout_object = nullptr) { - items->push_back(NGInlineItem(type, start, end, layout_object)); + LayoutObject* layout_object, + bool is_first_for_node = true) { + items->push_back( + NGInlineItem(type, start, end, layout_object, is_first_for_node)); } inline bool ShouldIgnore(UChar c) { @@ -197,8 +199,8 @@ NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::BoxInfo::BoxInfo( const NGInlineItem& item) : item_index(item_index), should_create_box_fragment(item.ShouldCreateBoxFragment()), - style(*item.Style()), - text_metrics(NGLineHeightMetrics(style)) { + may_have_margin_(item.Style()->MayHaveMargin()), + text_metrics(NGLineHeightMetrics(*item.Style())) { DCHECK(item.Style()); } @@ -208,7 +210,7 @@ bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::BoxInfo:: ShouldCreateBoxFragmentForChild(const BoxInfo& child) const { // When a child inline box has margins, the parent has different width/height // from the union of children. - if (child.style.MayHaveMargin()) + if (child.may_have_margin_) return true; // Returns true when parent and child boxes have different font metrics, since @@ -231,21 +233,24 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::BoxInfo:: template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextItem( const StringView string, - LayoutText* layout_object) { + LayoutText* layout_object, + bool is_first_for_node) { DCHECK(layout_object); - AppendTextItem(NGInlineItem::kText, string, layout_object); + AppendTextItem(NGInlineItem::kText, string, layout_object, is_first_for_node); } template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendTextItem( NGInlineItem::NGInlineItemType type, const StringView string, - LayoutText* layout_object) { + LayoutText* layout_object, + bool is_first_for_node) { DCHECK(layout_object); unsigned start_offset = text_.length(); text_.Append(string); mapping_builder_.AppendIdentityMapping(string.length()); - AppendItem(items_, type, start_offset, text_.length(), layout_object); + AppendItem(items_, type, start_offset, text_.length(), layout_object, + is_first_for_node); DCHECK(!items_->back().IsEmptyItem()); // text item is not empty. is_empty_inline_ = false; @@ -501,7 +506,8 @@ template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate< OffsetMappingBuilder>::AppendCollapseWhitespace(const StringView string, const ComputedStyle* style, - LayoutText* layout_object) { + LayoutText* layout_object, + bool is_first_for_node) { DCHECK(!string.IsEmpty()); // This algorithm segments the input string at the collapsible space, and @@ -528,7 +534,7 @@ void NGInlineItemsBuilderTemplate< // LayoutBR does not set preserve_newline, but should be preserved. if (UNLIKELY(space_run_has_newline && string.length() == 1 && layout_object && layout_object->IsBR())) { - AppendForcedBreakCollapseWhitespace(layout_object); + AppendForcedBreakCollapseWhitespace(layout_object, is_first_for_node); return; } @@ -685,7 +691,7 @@ void NGInlineItemsBuilderTemplate< } AppendItem(items_, NGInlineItem::kText, start_offset, text_.length(), - layout_object); + layout_object, is_first_for_node); NGInlineItem& item = items_->back(); item.SetEndCollapseType(end_collapse, space_run_has_newline); DCHECK(!item.IsEmptyItem()); @@ -727,7 +733,8 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>:: do { ++end; } while (end < string.length() && string[end] == kSpaceCharacter); - AppendTextItem(StringView(string, *start, end - *start), layout_object); + AppendTextItem(StringView(string, *start, end - *start), layout_object, + /* is_first_for_node */ false); AppendGeneratedBreakOpportunity(layout_object); *start = end; } @@ -752,11 +759,12 @@ void NGInlineItemsBuilderTemplate< unsigned start = 0; InsertBreakOpportunityAfterLeadingPreservedSpaces(string, *style, layout_object, &start); - for (; start < string.length();) { + bool is_first_for_node = true; + for (; start < string.length(); is_first_for_node = false) { UChar c = string[start]; if (IsControlItemCharacter(c)) { if (c == kNewlineCharacter) { - AppendForcedBreak(layout_object); + AppendForcedBreak(layout_object, is_first_for_node); start++; // A forced break is not a collapsible space, but following collapsible // spaces are leading spaces and they need a special code in the line @@ -771,13 +779,14 @@ void NGInlineItemsBuilderTemplate< if (end == kNotFound) end = string.length(); AppendTextItem(NGInlineItem::kControl, - StringView(string, start, end - start), layout_object); + StringView(string, start, end - start), layout_object, + is_first_for_node); start = end; continue; } // ZWNJ splits item, but it should be text. if (c != kZeroWidthNonJoinerCharacter) { - Append(NGInlineItem::kControl, c, layout_object); + Append(NGInlineItem::kControl, c, layout_object, is_first_for_node); start++; continue; } @@ -786,7 +795,8 @@ void NGInlineItemsBuilderTemplate< wtf_size_t end = string.Find(IsControlItemCharacter, start + 1); if (end == kNotFound) end = string.length(); - AppendTextItem(StringView(string, start, end - start), layout_object); + AppendTextItem(StringView(string, start, end - start), layout_object, + is_first_for_node); start = end; } } @@ -796,9 +806,10 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendPreserveNewline( const String& string, const ComputedStyle* style, LayoutText* layout_object) { - for (unsigned start = 0; start < string.length();) { + bool is_first_for_node = true; + for (unsigned start = 0; start < string.length(); is_first_for_node = false) { if (string[start] == kNewlineCharacter) { - AppendForcedBreakCollapseWhitespace(layout_object); + AppendForcedBreakCollapseWhitespace(layout_object, is_first_for_node); start++; continue; } @@ -808,14 +819,15 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendPreserveNewline( end = string.length(); DCHECK_GE(end, start); AppendCollapseWhitespace(StringView(string, start, end - start), style, - layout_object); + layout_object, is_first_for_node); start = end; } } template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendForcedBreak( - LayoutObject* layout_object) { + LayoutObject* layout_object, + bool is_first_for_node) { DCHECK(layout_object); // At the forced break, add bidi controls to pop all contexts. // https://drafts.csswg.org/css-writing-modes-3/#bidi-embedding-breaks @@ -829,7 +841,8 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendForcedBreak( } } - Append(NGInlineItem::kControl, kNewlineCharacter, layout_object); + Append(NGInlineItem::kControl, kNewlineCharacter, layout_object, + is_first_for_node); // A forced break is not a collapsible space, but following collapsible spaces // are leading spaces and that they should be collapsed. @@ -849,11 +862,12 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendForcedBreak( template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>:: - AppendForcedBreakCollapseWhitespace(LayoutObject* layout_object) { + AppendForcedBreakCollapseWhitespace(LayoutObject* layout_object, + bool is_first_for_node) { // Remove collapsible spaces immediately before a preserved newline. RemoveTrailingCollapsibleSpaceIfExists(); - AppendForcedBreak(layout_object); + AppendForcedBreak(layout_object, is_first_for_node); } template <typename OffsetMappingBuilder> @@ -867,13 +881,15 @@ template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Append( NGInlineItem::NGInlineItemType type, UChar character, - LayoutObject* layout_object) { + LayoutObject* layout_object, + bool is_first_for_node) { DCHECK_NE(character, kSpaceCharacter); text_.Append(character); mapping_builder_.AppendIdentityMapping(1); unsigned end_offset = text_.length(); - AppendItem(items_, type, end_offset - 1, end_offset, layout_object); + AppendItem(items_, type, end_offset - 1, end_offset, layout_object, + is_first_for_node); is_empty_inline_ &= items_->back().IsEmptyItem(); is_block_level_ &= items_->back().IsBlockLevel(); @@ -887,7 +903,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendAtomicInline( layout_object); RestoreTrailingCollapsibleSpaceIfRemoved(); Append(NGInlineItem::kAtomicInline, kObjectReplacementCharacter, - layout_object); + layout_object, /* is_first_for_node */ true); // Mark dirty lines. Clear if marked, only the first dirty line is relevant. if (dirty_lines_ && @@ -1231,6 +1247,27 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Exit( } template <typename OffsetMappingBuilder> +bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::MayBeBidiEnabled() + const { + return !text_.Is8Bit() || HasBidiControls(); +} + +template <typename OffsetMappingBuilder> +void NGInlineItemsBuilderTemplate< + OffsetMappingBuilder>::DidFinishCollectInlines(NGInlineNodeData* data) { + data->text_content = ToString(); + + // Set |is_bidi_enabled_| for all UTF-16 strings for now, because at this + // point the string may or may not contain RTL characters. + // |SegmentText()| will analyze the text and reset |is_bidi_enabled_| if it + // doesn't contain any RTL characters. + data->is_bidi_enabled_ = MayBeBidiEnabled(); + data->is_empty_inline_ = IsEmptyInline(); + data->is_block_level_ = IsBlockLevel(); + data->changes_may_affect_earlier_lines_ = ChangesMayAffectEarlierLines(); +} + +template <typename OffsetMappingBuilder> void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::SetIsSymbolMarker( bool b) { DCHECK(!items_->IsEmpty()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h index 8b0939aa096..eb58909de1d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h @@ -134,6 +134,9 @@ class NGInlineItemsBuilderTemplate { void EnterInline(LayoutInline*); void ExitInline(LayoutObject*); + // Set collected inline items data to |data|. + void DidFinishCollectInlines(NGInlineNodeData* data); + OffsetMappingBuilder& GetOffsetMappingBuilder() { return mapping_builder_; } void SetIsSymbolMarker(bool b); @@ -160,9 +163,11 @@ class NGInlineItemsBuilderTemplate { // Keep track of inline boxes to compute ShouldCreateBoxFragment. struct BoxInfo { + DISALLOW_NEW(); + unsigned item_index; bool should_create_box_fragment; - const ComputedStyle& style; + bool may_have_margin_; NGLineHeightMetrics text_metrics; BoxInfo(unsigned item_index, const NGInlineItem& item); @@ -191,18 +196,21 @@ class NGInlineItemsBuilderTemplate { // LayoutObject. void Append(NGInlineItem::NGInlineItemType, UChar, - LayoutObject*); + LayoutObject*, + bool is_first_for_node); void AppendCollapseWhitespace(const StringView, const ComputedStyle*, - LayoutText*); + LayoutText*, + bool is_first_for_node = true); void AppendPreserveWhitespace(const String&, const ComputedStyle*, LayoutText*); void AppendPreserveNewline(const String&, const ComputedStyle*, LayoutText*); - void AppendForcedBreakCollapseWhitespace(LayoutObject*); - void AppendForcedBreak(LayoutObject*); + void AppendForcedBreakCollapseWhitespace(LayoutObject*, + bool is_first_for_node); + void AppendForcedBreak(LayoutObject*, bool is_first_for_node); void RemoveTrailingCollapsibleSpaceIfExists(); void RemoveTrailingCollapsibleSpace(NGInlineItem*); @@ -211,16 +219,20 @@ class NGInlineItemsBuilderTemplate { void RestoreTrailingCollapsibleSpace(NGInlineItem*); void AppendTextItem(const StringView, - LayoutText* layout_object); + LayoutText* layout_object, + bool is_first_for_node); void AppendTextItem(NGInlineItem::NGInlineItemType type, const StringView, - LayoutText* layout_object); + LayoutText* layout_object, + bool is_first_for_node); void AppendEmptyTextItem(LayoutText* layout_object); void AppendGeneratedBreakOpportunity(LayoutObject*); void Exit(LayoutObject*); + bool MayBeBidiEnabled() const; + bool ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces( const String&, const ComputedStyle&, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc index df95d2fd3f6..77aff76eecc 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc @@ -28,7 +28,6 @@ class NGInlineItemsBuilderTest : public NGLayoutTest { void SetUp() override { NGLayoutTest::SetUp(); style_ = ComputedStyle::Create(); - style_->GetFont().Update(nullptr); } void TearDown() override { @@ -448,11 +447,12 @@ TEST_F(NGInlineItemsBuilderTest, BidiBlockOverride) { builder.ToString()); } -static std::unique_ptr<LayoutInline> CreateLayoutInline( +static LayoutInline* CreateLayoutInline( + Document* document, void (*initialize_style)(ComputedStyle*)) { scoped_refptr<ComputedStyle> style(ComputedStyle::Create()); initialize_style(style.get()); - std::unique_ptr<LayoutInline> node = std::make_unique<LayoutInline>(nullptr); + LayoutInline* const node = LayoutInline::CreateAnonymous(document); node->SetModifiedStyleOutsideStyleRecalc( std::move(style), LayoutObject::ApplyStyleChanges::kNo); node->SetIsInLayoutNGInlineFormattingContext(true); @@ -463,14 +463,14 @@ TEST_F(NGInlineItemsBuilderTest, BidiIsolate) { Vector<NGInlineItem> items; NGInlineItemsBuilder builder(&items); AppendText("Hello ", &builder); - std::unique_ptr<LayoutInline> isolate_rtl( - CreateLayoutInline([](ComputedStyle* style) { + LayoutInline* const isolate_rtl = + CreateLayoutInline(&GetDocument(), [](ComputedStyle* style) { style->SetUnicodeBidi(UnicodeBidi::kIsolate); style->SetDirection(TextDirection::kRtl); - })); - builder.EnterInline(isolate_rtl.get()); + }); + builder.EnterInline(isolate_rtl); AppendText(u"\u05E2\u05D1\u05E8\u05D9\u05EA", &builder); - builder.ExitInline(isolate_rtl.get()); + builder.ExitInline(isolate_rtl); AppendText(" World", &builder); // Expected control characters as defined in: @@ -481,20 +481,21 @@ TEST_F(NGInlineItemsBuilderTest, BidiIsolate) { u"\u2069" u" World"), builder.ToString()); + isolate_rtl->Destroy(); } TEST_F(NGInlineItemsBuilderTest, BidiIsolateOverride) { Vector<NGInlineItem> items; NGInlineItemsBuilder builder(&items); AppendText("Hello ", &builder); - std::unique_ptr<LayoutInline> isolate_override_rtl( - CreateLayoutInline([](ComputedStyle* style) { + LayoutInline* const isolate_override_rtl = + CreateLayoutInline(&GetDocument(), [](ComputedStyle* style) { style->SetUnicodeBidi(UnicodeBidi::kIsolateOverride); style->SetDirection(TextDirection::kRtl); - })); - builder.EnterInline(isolate_override_rtl.get()); + }); + builder.EnterInline(isolate_override_rtl); AppendText(u"\u05E2\u05D1\u05E8\u05D9\u05EA", &builder); - builder.ExitInline(isolate_override_rtl.get()); + builder.ExitInline(isolate_override_rtl); AppendText(" World", &builder); // Expected control characters as defined in: @@ -505,6 +506,7 @@ TEST_F(NGInlineItemsBuilderTest, BidiIsolateOverride) { u"\u202C\u2069" u" World"), builder.ToString()); + isolate_override_rtl->Destroy(); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc index 732b84c0ba2..76a67f7e9eb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc @@ -6,8 +6,9 @@ #include <memory> +#include "base/compiler_specific.h" #include "base/containers/adapters.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" @@ -19,12 +20,13 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" @@ -65,7 +67,9 @@ NGInlineLayoutAlgorithm::NGInlineLayoutAlgorithm( context_(context), baseline_type_(container_builder_.Style().GetFontBaseline()), is_horizontal_writing_mode_( - blink::IsHorizontalWritingMode(space.GetWritingMode())) { + blink::IsHorizontalWritingMode(space.GetWritingMode())), + truncate_type_( + static_cast<unsigned>(TruncateTypeFromConstraintSpace(space))) { DCHECK(context); quirks_mode_ = inline_node.InLineHeightQuirksMode(); } @@ -77,8 +81,10 @@ NGInlineLayoutAlgorithm::~NGInlineLayoutAlgorithm() = default; NGInlineBoxState* NGInlineLayoutAlgorithm::HandleOpenTag( const NGInlineItem& item, const NGInlineItemResult& item_result, + NGLineBoxFragmentBuilder::ChildList* line_box, NGInlineLayoutStateStack* box_states) const { - NGInlineBoxState* box = box_states->OnOpenTag(item, item_result, line_box_); + NGInlineBoxState* box = + box_states->OnOpenTag(item, item_result, baseline_type_, line_box); // Compute text metrics for all inline boxes since even empty inlines // influence the line height, except when quirks mode and the box is empty // for the purpose of empty block calculation. @@ -103,10 +109,14 @@ NGInlineBoxState* NGInlineLayoutAlgorithm::HandleCloseTag( box->EnsureTextMetrics(*item.Style(), baseline_type_); box = box_states_->OnCloseTag(&line_box_, box, baseline_type_, item.HasEndEdge()); - // Just clear |NeedsLayout| flags. Culled inline boxes do not need paint - // invalidations. If this object produces box fragments, - // |NGInlineBoxStateStack| takes care of invalidations. - item.GetLayoutObject()->ClearNeedsLayoutWithoutPaintInvalidation(); + if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + // Just clear |NeedsLayout| flags. Culled inline boxes do not need paint + // invalidations. If this object produces box fragments, + // |NGInlineBoxStateStack| takes care of invalidations. + item.GetLayoutObject()->ClearNeedsLayoutWithoutPaintInvalidation(); + } else { + item.GetLayoutObject()->ClearNeedsLayout(); + } return box; } @@ -160,12 +170,13 @@ void NGInlineLayoutAlgorithm::RebuildBoxStates( } // Create box states for tags that are not closed yet. + NGLineBoxFragmentBuilder::ChildList line_box; box_states->OnBeginPlaceItems(line_info.LineStyle(), baseline_type_, - quirks_mode_); + quirks_mode_, &line_box); for (const NGInlineItem* item : open_items) { NGInlineItemResult item_result; NGLineBreaker::ComputeOpenTagResult(*item, ConstraintSpace(), &item_result); - HandleOpenTag(*item, item_result, box_states); + HandleOpenTag(*item, item_result, &line_box, box_states); } } @@ -175,9 +186,9 @@ void NGInlineLayoutAlgorithm::CheckBoxStates( const NGInlineBreakToken* break_token) const { NGInlineLayoutStateStack rebuilt; RebuildBoxStates(line_info, break_token, &rebuilt); - rebuilt.OnBeginPlaceItems(line_info.LineStyle(), baseline_type_, - quirks_mode_); - + NGLineBoxFragmentBuilder::ChildList line_box; + rebuilt.OnBeginPlaceItems(line_info.LineStyle(), baseline_type_, quirks_mode_, + &line_box); DCHECK(box_states_); box_states_->CheckSame(rebuilt); } @@ -201,8 +212,8 @@ void NGInlineLayoutAlgorithm::CreateLine( // The baseline is adjusted after the height of the line box is computed. const ComputedStyle& line_style = line_info->LineStyle(); box_states_->SetIsEmptyLine(line_info->IsEmptyLine()); - NGInlineBoxState* box = - box_states_->OnBeginPlaceItems(line_style, baseline_type_, quirks_mode_); + NGInlineBoxState* box = box_states_->OnBeginPlaceItems( + line_style, baseline_type_, quirks_mode_, &line_box_); #if DCHECK_IS_ON() if (is_box_states_from_context_) CheckBoxStates(*line_info, BreakToken()); @@ -227,6 +238,8 @@ void NGInlineLayoutAlgorithm::CreateLine( item.GetLayoutObject()->IsLayoutNGListItem()); DCHECK(item_result.shape_result); + text_builder.SetIsFirstForNode(IsFirstForNode(item, BreakToken())); + if (UNLIKELY(quirks_mode_)) box->EnsureTextMetrics(*item.Style(), baseline_type_); @@ -236,7 +249,7 @@ void NGInlineLayoutAlgorithm::CreateLine( baseline_type_); } - if (item.IsSymbolMarker()) { + if (UNLIKELY(item.IsSymbolMarker())) { text_builder.SetItem(NGPhysicalTextFragment::kSymbolMarker, line_info->ItemsData(), &item_result, box->text_height); @@ -245,14 +258,23 @@ void NGInlineLayoutAlgorithm::CreateLine( line_info->ItemsData(), &item_result, box->text_height); } - line_box_.AddChild(text_builder.ToTextFragment(), box->text_top, - item_result.inline_size, item.BidiLevel()); + if (UNLIKELY(item_result.hyphen_shape_result)) { + LayoutUnit hyphen_inline_size = item_result.HyphenInlineSize(); + line_box_.AddChild(text_builder.ToTextFragment(), box->text_top, + item_result.inline_size - hyphen_inline_size, + item.BidiLevel()); + PlaceHyphen(item_result, hyphen_inline_size, box); + } else { + line_box_.AddChild(text_builder.ToTextFragment(), box->text_top, + item_result.inline_size, item.BidiLevel()); + } // Text boxes always need full paint invalidations. item.GetLayoutObject()->ClearNeedsLayoutWithFullPaintInvalidation(); + } else if (item.Type() == NGInlineItem::kControl) { PlaceControlItem(item, *line_info, &item_result, box); } else if (item.Type() == NGInlineItem::kOpenTag) { - box = HandleOpenTag(item, item_result, box_states_); + box = HandleOpenTag(item, item_result, &line_box_, box_states_); } else if (item.Type() == NGInlineItem::kCloseTag) { box = HandleCloseTag(item, item_result, box); } else if (item.Type() == NGInlineItem::kAtomicInline) { @@ -284,13 +306,6 @@ void NGInlineLayoutAlgorithm::CreateLine( } } - if (line_info->LineEndFragment()) { - // Add a generated text fragment, hyphen or ellipsis, at the logical end. - // By using the paragraph bidi_level, it will appear at the visual end. - PlaceGeneratedContent(std::move(line_info->LineEndFragment()), - IsLtr(line_info->BaseDirection()) ? 0 : 1, box); - } - box_states_->OnEndPlaceItems(&line_box_, baseline_type_); if (UNLIKELY(Node().IsBidiEnabled())) { @@ -308,11 +323,21 @@ void NGInlineLayoutAlgorithm::CreateLine( } } - // Truncate the line if 'text-overflow: ellipsis' is set. - if (UNLIKELY(inline_size > line_info->AvailableWidth() && - node_.GetLayoutBlockFlow()->ShouldTruncateOverflowingText())) { - inline_size = NGLineTruncator(*line_info) - .TruncateLine(inline_size, &line_box_, box_states_); + // Truncate the line if 'text-overflow: ellipsis' is set, or for line-clamp. + if (UNLIKELY((inline_size > + line_info->AvailableWidth() - line_info->TextIndent() && + node_.GetLayoutBlockFlow()->ShouldTruncateOverflowingText()) || + ShouldTruncateForLineClamp(*line_info))) { + NGLineTruncator truncator(*line_info); + auto* input = + DynamicTo<HTMLInputElement>(node_.GetLayoutBlockFlow()->GetNode()); + if (input && input->ShouldApplyMiddleEllipsis()) { + inline_size = truncator.TruncateLineInTheMiddle(inline_size, &line_box_, + box_states_); + } else { + inline_size = + truncator.TruncateLine(inline_size, &line_box_, box_states_); + } } // Negative margins can make the position negative, but the inline size is @@ -421,36 +446,28 @@ void NGInlineLayoutAlgorithm::PlaceControlItem(const NGInlineItem& item, NGTextFragmentBuilder text_builder(ConstraintSpace().GetWritingMode()); text_builder.SetItem(type, line_info.ItemsData(), item_result, box->text_height); + text_builder.SetIsFirstForNode(IsFirstForNode(item, BreakToken())); line_box_.AddChild(text_builder.ToTextFragment(), box->text_top, item_result->inline_size, item.BidiLevel()); } -// Place a generated content that does not exist in DOM nor in LayoutObject -// tree. -void NGInlineLayoutAlgorithm::PlaceGeneratedContent( - scoped_refptr<const NGPhysicalTextFragment> fragment, - UBiDiLevel bidi_level, - NGInlineBoxState* box) { - LayoutUnit inline_size = IsHorizontalWritingMode() ? fragment->Size().width - : fragment->Size().height; - const ComputedStyle& style = fragment->Style(); - if (box->CanAddTextOfStyle(style)) { - if (UNLIKELY(quirks_mode_)) - box->EnsureTextMetrics(style, baseline_type_); - DCHECK(!box->text_metrics.IsEmpty()); - line_box_.AddChild(std::move(fragment), box->text_top, inline_size, - bidi_level); - } else { - scoped_refptr<ComputedStyle> text_style = - ComputedStyle::CreateAnonymousStyleWithDisplay(style, - EDisplay::kInline); - NGInlineBoxState* box = box_states_->OnOpenTag(*text_style, line_box_); - box->ComputeTextMetrics(*text_style, baseline_type_); - DCHECK(!box->text_metrics.IsEmpty()); - line_box_.AddChild(std::move(fragment), box->text_top, inline_size, - bidi_level); - box_states_->OnCloseTag(&line_box_, box, baseline_type_); - } +void NGInlineLayoutAlgorithm::PlaceHyphen(const NGInlineItemResult& item_result, + LayoutUnit hyphen_inline_size, + NGInlineBoxState* box) { + DCHECK(item_result.item); + DCHECK(item_result.hyphen_string); + DCHECK(item_result.hyphen_shape_result); + DCHECK_EQ(hyphen_inline_size, item_result.HyphenInlineSize()); + const NGInlineItem& item = *item_result.item; + const WritingMode writing_mode = ConstraintSpace().GetWritingMode(); + NGTextFragmentBuilder builder(writing_mode); + builder.SetText( + item.GetLayoutObject(), item_result.hyphen_string, item.Style(), + /* is_ellipsis_style */ false, + ShapeResultView::Create(item_result.hyphen_shape_result.get())); + DCHECK(!box->text_metrics.IsEmpty()); + line_box_.AddChild(builder.ToTextFragment(), box->text_top, + hyphen_inline_size, item.BidiLevel()); } NGInlineBoxState* NGInlineLayoutAlgorithm::PlaceAtomicInline( @@ -465,7 +482,8 @@ NGInlineBoxState* NGInlineLayoutAlgorithm::PlaceAtomicInline( // position += item_result->margins.LineLeft(style.Direction()); item_result->has_edge = true; - NGInlineBoxState* box = box_states_->OnOpenTag(item, *item_result, line_box_); + NGInlineBoxState* box = + box_states_->OnOpenTag(item, *item_result, baseline_type_, line_box_); PlaceLayoutResult(item_result, box, box->margin_inline_start); return box_states_->OnCloseTag(&line_box_, box, baseline_type_); } @@ -478,20 +496,20 @@ void NGInlineLayoutAlgorithm::PlaceLayoutResult(NGInlineItemResult* item_result, DCHECK(item_result->item); const NGInlineItem& item = *item_result->item; DCHECK(item.Style()); - NGBoxFragment fragment(ConstraintSpace().GetWritingMode(), - ConstraintSpace().Direction(), - To<NGPhysicalBoxFragment>( - item_result->layout_result->PhysicalFragment())); - NGLineHeightMetrics metrics = fragment.BaselineMetrics( - {NGBaselineAlgorithmType::kAtomicInline, baseline_type_}, - ConstraintSpace()); + NGLineHeightMetrics metrics = + NGBoxFragment(ConstraintSpace().GetWritingMode(), + ConstraintSpace().Direction(), + To<NGPhysicalBoxFragment>( + item_result->layout_result->PhysicalFragment())) + .BaselineMetrics(item_result->margins, baseline_type_); if (box) box->metrics.Unite(metrics); LayoutUnit line_top = item_result->margins.line_over - metrics.ascent; line_box_.AddChild(std::move(item_result->layout_result), LogicalOffset{inline_offset, line_top}, - item_result->inline_size, item.BidiLevel()); + item_result->inline_size, /* children_count */ 0, + item.BidiLevel()); } // Place all out-of-flow objects in |line_box_|. @@ -548,7 +566,7 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( if (box->StyleRef().IsOriginalDisplayInlineType()) { // An inline-level OOF element positions itself within the line, at the // position it would have been if it was in-flow. - static_offset.inline_offset = child.offset.inline_offset; + static_offset.inline_offset = child.rect.offset.inline_offset; // The static-position of inline-level OOF-positioned nodes depends on // previous floats (if any). @@ -572,7 +590,7 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( } } - child.offset = static_offset; + child.rect.offset = static_offset; } if (UNLIKELY(has_rtl_block_level_out_of_flow_objects)) { @@ -585,7 +603,7 @@ void NGInlineLayoutAlgorithm::PlaceOutOfFlowObjects( } if (has_preceding_inline_level_content && !box->StyleRef().IsOriginalDisplayInlineType()) { - child.offset.block_offset += line_height; + child.rect.offset.block_offset += line_height; } } } @@ -646,8 +664,8 @@ void NGInlineLayoutAlgorithm::PlaceFloatingObjects( block_offset = -fragment.BlockSize() - block_offset; } - child.offset = {child.bfc_offset.line_offset - bfc_line_offset, - block_offset}; + child.rect.offset = {child.bfc_offset.line_offset - bfc_line_offset, + block_offset}; } } @@ -660,8 +678,8 @@ void NGInlineLayoutAlgorithm::PlaceListMarker(const NGInlineItem& item, baseline_type_); } - container_builder_.SetUnpositionedListMarker( - NGUnpositionedListMarker(ToLayoutNGListMarker(item.GetLayoutObject()))); + container_builder_.SetUnpositionedListMarker(NGUnpositionedListMarker( + ToLayoutNGOutsideListMarker(item.GetLayoutObject()))); } // Justify the line. This changes the size of items by adding spacing. @@ -694,8 +712,8 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, // matches to the |ShapeResult|. DCHECK(!line_info->Results().IsEmpty()); const NGInlineItemResult& last_item_result = line_info->Results().back(); - if (last_item_result.text_end_effect == NGTextEndEffect::kHyphen) - line_text_builder.Append(last_item_result.item->Style()->HyphenString()); + if (last_item_result.hyphen_string) + line_text_builder.Append(last_item_result.hyphen_string); // Compute the spacing to justify. String line_text = line_text_builder.ToString(); @@ -714,14 +732,14 @@ bool NGInlineLayoutAlgorithm::ApplyJustify(LayoutUnit space, scoped_refptr<ShapeResult> shape_result = item_result.shape_result->CreateShapeResult(); DCHECK_GE(item_result.start_offset, line_info->StartOffset()); - // |shape_result| has more characters if it's hyphenated. - DCHECK(item_result.text_end_effect != NGTextEndEffect::kNone || - shape_result->NumCharacters() == - item_result.end_offset - item_result.start_offset); + DCHECK_EQ(shape_result->NumCharacters(), + item_result.end_offset - item_result.start_offset); shape_result->ApplySpacing(spacing, item_result.start_offset - line_info->StartOffset() - shape_result->StartIndex()); item_result.inline_size = shape_result->SnappedWidth(); + if (UNLIKELY(item_result.hyphen_shape_result)) + item_result.inline_size += item_result.HyphenInlineSize(); item_result.shape_result = ShapeResultView::Create(shape_result.get()); } else if (item_result.item->Type() == NGInlineItem::kAtomicInline) { float offset = 0.f; @@ -823,7 +841,7 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { // In order to get the correct list of layout opportunities, we need to // position any "leading" floats within the exclusion space first. - NGPositionedFloatVector leading_floats; + STACK_UNINITIALIZED NGPositionedFloatVector leading_floats; unsigned handled_leading_floats_index = PositionLeadingFloats(&initial_exclusion_space, &leading_floats); @@ -832,7 +850,7 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { // We query all the layout opportunities on the initial exclusion space up // front, as if the line breaker may add floats and change the opportunities. - const LayoutOpportunityVector opportunities = + const LayoutOpportunityVector& opportunities = initial_exclusion_space.AllLayoutOpportunities( {ConstraintSpace().BfcOffset().line_offset, is_empty_inline ? ConstraintSpace().ExpectedBfcBlockOffset() @@ -876,7 +894,7 @@ scoped_refptr<const NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { opportunity.ComputeLineLayoutOpportunity(ConstraintSpace(), line_block_size, block_delta); - NGLineInfo line_info; + STACK_UNINITIALIZED NGLineInfo line_info; NGLineBreaker line_breaker(Node(), NGLineBreakerMode::kContent, ConstraintSpace(), line_opportunity, leading_floats, handled_leading_floats_index, @@ -1092,22 +1110,9 @@ void NGInlineLayoutAlgorithm::BidiReorder(TextDirection base_direction) { // For opaque items, copy bidi levels from adjacent items. if (has_opaque_items) { - UBiDiLevel last_level = levels.front(); - if (last_level == kOpaqueBidiLevel) { - for (const UBiDiLevel level : levels) { - if (level != kOpaqueBidiLevel) { - last_level = level; - break; - } - } - } - // If all items are opaque, use the base direction. - if (last_level == kOpaqueBidiLevel) { - if (IsLtr(base_direction)) - return; - last_level = 1; - } - for (UBiDiLevel& level : levels) { + // Use the paragraph level for trailing opaque items. + UBiDiLevel last_level = IsLtr(base_direction) ? 0 : 1; + for (UBiDiLevel& level : base::Reversed(levels)) { if (level == kOpaqueBidiLevel) level = last_level; else @@ -1130,4 +1135,22 @@ void NGInlineLayoutAlgorithm::BidiReorder(TextDirection base_direction) { line_box_ = std::move(visual_items); } +// static +NGInlineLayoutAlgorithm::TruncateType +NGInlineLayoutAlgorithm::TruncateTypeFromConstraintSpace( + const NGConstraintSpace& space) { + if (space.LinesUntilClamp() != 1) + return TruncateType::kDefault; + return space.ForceTruncateAtLineClamp() ? TruncateType::kAlways + : TruncateType::kIfNotLastLine; +} + +bool NGInlineLayoutAlgorithm::ShouldTruncateForLineClamp( + const NGLineInfo& line_info) const { + const TruncateType truncate_type = static_cast<TruncateType>(truncate_type_); + return truncate_type == TruncateType::kAlways || + (truncate_type == TruncateType::kIfNotLastLine && + !line_info.IsLastLine()); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h index 6fe33fe9220..604eefb4041 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h @@ -52,6 +52,19 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final scoped_refptr<const NGLayoutResult> Layout() override; private: + enum class TruncateType { + // Indicates default behavior. The default truncates if the text doesn't + // fit and ShouldTruncateOverflowingText() returns true. + kDefault, + + // Truncate if NGLineInfo has more lines. + kIfNotLastLine, + + // Forces truncation. This is used when line-clamp is set and there are + // blocks after this. + kAlways, + }; + unsigned PositionLeadingFloats(NGExclusionSpace*, NGPositionedFloatVector*); NGPositionedFloat PositionFloat(LayoutUnit origin_block_bfc_offset, LayoutObject* floating_object, @@ -69,6 +82,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final NGInlineBoxState* HandleOpenTag(const NGInlineItem&, const NGInlineItemResult&, + NGLineBoxFragmentBuilder::ChildList*, NGInlineLayoutStateStack*) const; NGInlineBoxState* HandleCloseTag(const NGInlineItem&, const NGInlineItemResult&, @@ -80,9 +94,9 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final const NGLineInfo&, NGInlineItemResult*, NGInlineBoxState*); - void PlaceGeneratedContent(scoped_refptr<const NGPhysicalTextFragment>, - UBiDiLevel, - NGInlineBoxState*); + void PlaceHyphen(const NGInlineItemResult&, + LayoutUnit hyphen_inline_size, + NGInlineBoxState*); NGInlineBoxState* PlaceAtomicInline(const NGInlineItem&, const NGLineInfo&, NGInlineItemResult*); @@ -105,6 +119,13 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final const NGExclusionSpace&, LayoutUnit line_height); + static TruncateType TruncateTypeFromConstraintSpace( + const NGConstraintSpace& space); + + // Returns true if truncuation should happen as a result of line-clamp for + // |line_info|. + bool ShouldTruncateForLineClamp(const NGLineInfo& line_info) const; + NGLineBoxFragmentBuilder::ChildList line_box_; NGInlineLayoutStateStack* box_states_; NGInlineChildLayoutContext* context_; @@ -113,6 +134,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final unsigned is_horizontal_writing_mode_ : 1; unsigned quirks_mode_ : 1; + unsigned truncate_type_ : 2; #if DCHECK_IS_ON() // True if |box_states_| is taken from |context_|, to check the |box_states_| diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc index 3c1aa36a1bc..fbb4e323541 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc @@ -9,9 +9,11 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" @@ -50,6 +52,14 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) { NGConstraintSpace constraint_space = builder.ToConstraintSpace(); NGInlineChildLayoutContext context; + NGBoxFragmentBuilder container_builder(block_flow, block_flow->Style(), + block_flow->Style()->GetWritingMode(), + block_flow->Style()->Direction()); + NGFragmentItemsBuilder items_builder(&container_builder); + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + container_builder.SetItemsBuilder(&items_builder); + context.SetItemsBuilder(&items_builder); + } scoped_refptr<const NGLayoutResult> layout_result = inline_node.Layout(constraint_space, nullptr, &context); const auto& line1 = layout_result->PhysicalFragment(); @@ -68,95 +78,6 @@ TEST_F(NGInlineLayoutAlgorithmTest, BreakToken) { EXPECT_TRUE(line3.BreakToken()->IsFinished()); } -TEST_F(NGInlineLayoutAlgorithmTest, GenerateHyphen) { - LoadAhem(); - SetBodyInnerHTML(R"HTML( - <!DOCTYPE html> - <style> - html, body { margin: 0; } - #container { - font: 10px/1 Ahem; - width: 5ch; - } - </style> - <div id=container>abc­def</div> - )HTML"); - scoped_refptr<const NGPhysicalBoxFragment> block = - GetBoxFragmentByElementId("container"); - EXPECT_EQ(2u, block->Children().size()); - const NGPhysicalLineBoxFragment& line1 = - To<NGPhysicalLineBoxFragment>(*block->Children()[0].get()); - - // The hyphen is in its own NGPhysicalTextFragment. - EXPECT_EQ(2u, line1.Children().size()); - EXPECT_EQ(NGPhysicalFragment::kFragmentText, line1.Children()[1]->Type()); - const auto& hyphen = To<NGPhysicalTextFragment>(*line1.Children()[1].get()); - EXPECT_EQ(String(u"\u2010"), hyphen.Text().ToString()); - // It should have the same LayoutObject as the hyphened word. - EXPECT_EQ(line1.Children()[0]->GetLayoutObject(), hyphen.GetLayoutObject()); -} - -TEST_F(NGInlineLayoutAlgorithmTest, GenerateEllipsis) { - LoadAhem(); - SetBodyInnerHTML(R"HTML( - <!DOCTYPE html> - <style> - html, body { margin: 0; } - #container { - font: 10px/1 Ahem; - width: 5ch; - overflow: hidden; - text-overflow: ellipsis; - } - </style> - <div id=container>123456</div> - )HTML"); - scoped_refptr<const NGPhysicalBoxFragment> block = - GetBoxFragmentByElementId("container"); - EXPECT_EQ(1u, block->Children().size()); - const auto& line1 = - To<NGPhysicalLineBoxFragment>(*block->Children()[0].get()); - - // The ellipsis is in its own NGPhysicalTextFragment. - EXPECT_EQ(3u, line1.Children().size()); - const auto& ellipsis = To<NGPhysicalTextFragment>(*line1.Children().back()); - EXPECT_EQ(String(u"\u2026"), ellipsis.Text().ToString()); - // It should have the same LayoutObject as the clipped word. - EXPECT_EQ(line1.Children()[0]->GetLayoutObject(), ellipsis.GetLayoutObject()); -} - -TEST_F(NGInlineLayoutAlgorithmTest, EllipsisInlineBoxOnly) { - LoadAhem(); - SetBodyInnerHTML(R"HTML( - <!DOCTYPE html> - <style> - html, body { margin: 0; } - #container { - font: 10px/1 Ahem; - width: 5ch; - overflow: hidden; - text-overflow: ellipsis; - } - span { - border: solid 10ch blue; - } - </style> - <div id=container><span></span></div> - )HTML"); - scoped_refptr<const NGPhysicalBoxFragment> block = - GetBoxFragmentByElementId("container"); - EXPECT_EQ(1u, block->Children().size()); - const auto& line1 = - To<NGPhysicalLineBoxFragment>(*block->Children()[0].get()); - - // There should not be ellipsis in this line. - for (const auto& child : line1.Children()) { - if (const auto* text = DynamicTo<NGPhysicalTextFragment>(child.get())) { - EXPECT_FALSE(text->IsEllipsis()); - } - } -} - // This test ensures box fragments are generated when necessary, even when the // line is empty. One such case is when the line contains a containing box of an // out-of-flow object. @@ -179,7 +100,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, } </style> <div id=container> - <oof-container> + <oof-container id=target> <oof></oof> </oof-container> </div> @@ -190,15 +111,17 @@ TEST_F(NGInlineLayoutAlgorithmTest, ASSERT_TRUE(container); EXPECT_EQ(LayoutUnit(), container->Size().height); - EXPECT_EQ(2u, container->Children().size()); - const auto& linebox = - To<NGPhysicalLineBoxFragment>(*container->Children()[0]); - - EXPECT_EQ(1u, linebox.Children().size()); - EXPECT_EQ(PhysicalSize(), linebox.Size()); - - const auto& oof_container = To<NGPhysicalBoxFragment>(*linebox.Children()[0]); - EXPECT_EQ(PhysicalSize(), oof_container.Size()); + NGInlineCursor line_box(*block_flow); + ASSERT_TRUE(line_box); + ASSERT_TRUE(line_box.Current().IsLineBox()); + EXPECT_EQ(PhysicalSize(), line_box.Current().Size()); + + NGInlineCursor off_container(line_box); + off_container.MoveToNext(); + ASSERT_TRUE(off_container); + ASSERT_EQ(GetLayoutObjectByElementId("target"), + off_container.Current().GetLayoutObject()); + EXPECT_EQ(PhysicalSize(), off_container.Current().Size()); } // This test ensures that if an inline box generates (or does not generate) box @@ -219,21 +142,31 @@ TEST_F(NGInlineLayoutAlgorithmTest, BoxForEndMargin) { } </style> <!-- This line wraps, and only 2nd line has a border. --> - <div id=container>12 <span>3 45</span> 6</div> + <div id=container>12 <span id=span>3 45</span> 6</div> )HTML"); auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("container")); - const NGPhysicalBoxFragment* block_box = block_flow->CurrentFragment(); - ASSERT_TRUE(block_box); - EXPECT_EQ(2u, block_box->Children().size()); - const auto& line_box1 = - To<NGPhysicalLineBoxFragment>(*block_box->Children()[0].get()); - EXPECT_EQ(2u, line_box1.Children().size()); + NGInlineCursor line_box(*block_flow); + ASSERT_TRUE(line_box) << "line_box is at start of first line."; + ASSERT_TRUE(line_box.Current().IsLineBox()); + line_box.MoveToNextLine(); + ASSERT_TRUE(line_box) << "line_box is at start of second line."; + NGInlineCursor cursor(line_box); + ASSERT_TRUE(line_box.Current().IsLineBox()); + cursor.MoveToNext(); + ASSERT_TRUE(cursor); + EXPECT_EQ(GetLayoutObjectByElementId("span"), + cursor.Current().GetLayoutObject()); // The <span> generates a box fragment for the 2nd line because it has a // right border. It should also generate a box fragment for the 1st line even // though there's no borders on the 1st line. - EXPECT_EQ(NGPhysicalFragment::kFragmentBox, line_box1.Children()[1]->Type()); + const NGPhysicalBoxFragment* box_fragment = cursor.Current().BoxFragment(); + ASSERT_TRUE(box_fragment); + EXPECT_EQ(NGPhysicalFragment::kFragmentBox, box_fragment->Type()); + + line_box.MoveToNextLine(); + ASSERT_FALSE(line_box) << "block_flow has two lines."; } // A block with inline children generates fragment tree as follows: @@ -257,8 +190,8 @@ TEST_F(NGInlineLayoutAlgorithmTest, ContainerBorderPadding) { auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("container")); NGBlockNode block_node(block_flow); - NGConstraintSpace space = NGConstraintSpace::CreateFromLayoutObject( - *block_flow, false /* is_layout_root */); + NGConstraintSpace space = + NGConstraintSpace::CreateFromLayoutObject(*block_flow); scoped_refptr<const NGLayoutResult> layout_result = block_node.Layout(space); EXPECT_TRUE(layout_result->BfcBlockOffset().has_value()); @@ -289,17 +222,13 @@ TEST_F(NGInlineLayoutAlgorithmTest, MAYBE_VerticalAlignBottomReplaced) { )HTML"); auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("container")); - NGInlineNode inline_node(block_flow); - NGInlineChildLayoutContext context; - NGConstraintSpace space = NGConstraintSpace::CreateFromLayoutObject( - *block_flow, false /* is_layout_root */); - scoped_refptr<const NGLayoutResult> layout_result = - inline_node.Layout(space, nullptr, &context); - - const auto& line = layout_result->PhysicalFragment(); - EXPECT_EQ(LayoutUnit(96), line.Size().height); - PhysicalOffset img_offset = line.Children()[0].Offset(); - EXPECT_EQ(LayoutUnit(0), img_offset.top); + NGInlineCursor cursor(*block_flow); + ASSERT_TRUE(cursor); + EXPECT_EQ(LayoutUnit(96), cursor.Current().Size().height); + cursor.MoveToNext(); + ASSERT_TRUE(cursor); + EXPECT_EQ(LayoutUnit(0), cursor.Current().OffsetInContainerBlock().top) + << "Offset top of <img> should be zero."; } // Verifies that text can flow correctly around floats that were positioned @@ -393,7 +322,7 @@ TEST_F(NGInlineLayoutAlgorithmTest, TextFloatsAroundInlineFloatThatFitsOnLine) { ASSERT_TRUE(block_box); // Two lines. - EXPECT_EQ(2u, block_box->Children().size()); + ASSERT_EQ(2u, block_box->Children().size()); PhysicalOffset first_line_offset = block_box->Children()[1].Offset(); // 30 == narrow-float's width. @@ -521,20 +450,24 @@ TEST_F(NGInlineLayoutAlgorithmTest, InkOverflow) { auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("container")); const NGPaintFragment* paint_fragment = block_flow->PaintFragment(); - ASSERT_TRUE(paint_fragment); - const NGPhysicalFragment& box_fragment = paint_fragment->PhysicalFragment(); - + const NGPhysicalBoxFragment& box_fragment = *block_flow->CurrentFragment(); + if (paint_fragment) + ASSERT_EQ(&paint_fragment->PhysicalFragment(), &box_fragment); EXPECT_EQ(LayoutUnit(10), box_fragment.Size().height); - PhysicalRect ink_overflow = paint_fragment->InkOverflow(); + NGInlineCursor cursor(*block_flow); + PhysicalRect ink_overflow = cursor.Current().InkOverflow(); EXPECT_EQ(LayoutUnit(-5), ink_overflow.offset.top); EXPECT_EQ(LayoutUnit(20), ink_overflow.size.height); - // |ContentsInkOverflow| should match to |InkOverflow|, except the width - // because |<div id=container>| might be wider than the content. - EXPECT_EQ(ink_overflow.offset, paint_fragment->ContentsInkOverflow().offset); - EXPECT_EQ(ink_overflow.size.height, - paint_fragment->ContentsInkOverflow().size.height); + if (paint_fragment) { + // |ContentsInkOverflow| should match to |InkOverflow|, except the width + // because |<div id=container>| might be wider than the content. + const PhysicalRect contents_ink_overflow = + paint_fragment->ContentsInkOverflow(); + EXPECT_EQ(ink_overflow.offset, contents_ink_overflow.offset); + EXPECT_EQ(ink_overflow.size.height, contents_ink_overflow.size.height); + } } #undef MAYBE_VerticalAlignBottomReplaced diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc index 6f83ab768f0..740ab5a243f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc @@ -24,7 +24,6 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h" #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" @@ -285,7 +284,7 @@ void CollectInlinesInternal(LayoutBlockFlow* block, builder->ClearInlineFragment(node); } else if (node->IsAtomicInlineLevel()) { - if (node->IsListMarkerIncludingNG()) { + if (node->IsListMarkerIncludingNGOutside()) { // LayoutNGListItem produces the 'outside' list marker as an inline // block. This is an out-of-flow item whose position is computed // automatically. @@ -427,13 +426,6 @@ void TruncateOrPadText(String* text, unsigned length) { } } -template <typename OffsetMappingBuilder> -bool MayBeBidiEnabled( - const String& text_content, - const NGInlineItemsBuilderTemplate<OffsetMappingBuilder>& builder) { - return !text_content.Is8Bit() || builder.HasBidiControls(); -} - } // namespace NGInlineNode::NGInlineNode(LayoutBlockFlow* block) @@ -528,6 +520,11 @@ class NGInlineNodeDataEditor final { if (layout_text_.StyleRef().TextSecurity() != ETextSecurity::kNone) return nullptr; + // It is hard to figure differences of bidi control codes before/after + // editing. See http://crbug.com/1039143 + if (layout_text_.HasBidiControlInlineItems()) + return nullptr; + // Note: We should compute offset mapping before calling // |LayoutBlockFlow::TakeNGInlineNodeData()| const NGOffsetMapping* const offset_mapping = @@ -756,6 +753,10 @@ bool NGInlineNode::SetTextWithOffset(LayoutText* layout_text, if (!previous_data) return false; + // This function runs outside of the layout phase. Prevent purging font cache + // while shaping. + FontCachePurgePreventer fontCachePurgePreventer; + String new_text(std::move(new_text_in)); layout_text->StyleRef().ApplyTextTransform(&new_text, layout_text->PreviousCharacter()); @@ -769,7 +770,7 @@ bool NGInlineNode::SetTextWithOffset(LayoutText* layout_text, // inline items. layout_text->ClearInlineItems(); CollectInlinesInternal(node.GetLayoutBlockFlow(), &builder, previous_data); - data->text_content = builder.ToString(); + builder.DidFinishCollectInlines(data); // Relocates |ShapeResult| in |previous_data| after |offset|+|length| editor.Run(); node.SegmentText(data); @@ -877,17 +878,7 @@ void NGInlineNode::CollectInlines(NGInlineNodeData* data, data->items.ReserveCapacity(EstimateInlineItemsCount(*block)); NGInlineItemsBuilder builder(&data->items, dirty_lines); CollectInlinesInternal(block, &builder, previous_data); - data->text_content = builder.ToString(); - - // Set |is_bidi_enabled_| for all UTF-16 strings for now, because at this - // point the string may or may not contain RTL characters. - // |SegmentText()| will analyze the text and reset |is_bidi_enabled_| if it - // doesn't contain any RTL characters. - data->is_bidi_enabled_ = MayBeBidiEnabled(data->text_content, builder); - data->is_empty_inline_ = builder.IsEmptyInline(); - data->is_block_level_ = builder.IsBlockLevel(); - data->changes_may_affect_earlier_lines_ = - builder.ChangesMayAffectEarlierLines(); + builder.DidFinishCollectInlines(data); } void NGInlineNode::SegmentText(NGInlineNodeData* data) { @@ -1476,7 +1467,7 @@ String NGInlineNode::TextContentForStickyImagesQuirk( static LayoutUnit ComputeContentSize( NGInlineNode node, WritingMode container_writing_mode, - const MinMaxSizeInput& input, + const MinMaxSizesInput& input, NGLineBreakerMode mode, NGLineBreaker::MaxSizeCache* max_size_cache, base::Optional<LayoutUnit>* max_size_out) { @@ -1510,7 +1501,7 @@ static LayoutUnit ComputeContentSize( STACK_ALLOCATED(); public: - explicit FloatsMaxSize(const MinMaxSizeInput& input) + explicit FloatsMaxSize(const MinMaxSizesInput& input) : floats_inline_size_(input.float_left_inline_size + input.float_right_inline_size) { DCHECK_GE(floats_inline_size_, 0); @@ -1519,7 +1510,7 @@ static LayoutUnit ComputeContentSize( void AddFloat(const ComputedStyle& float_style, const ComputedStyle& style, LayoutUnit float_inline_max_size_with_margin) { - floating_objects_.push_back(FloatingObject{ + floating_objects_.push_back(NGInlineNode::FloatingObject{ float_style, style, float_inline_max_size_with_margin}); } @@ -1561,12 +1552,7 @@ static LayoutUnit ComputeContentSize( private: LayoutUnit floats_inline_size_; - struct FloatingObject { - const ComputedStyle& float_style; - const ComputedStyle& style; - LayoutUnit float_inline_max_size_with_margin; - }; - Vector<FloatingObject, 4> floating_objects_; + HeapVector<NGInlineNode::FloatingObject, 4> floating_objects_; }; // This struct computes the max size from the line break results for the min @@ -1711,8 +1697,8 @@ static LayoutUnit ComputeContentSize( const ComputedStyle& float_style = float_node.Style(); // Floats don't intrude into floats. - MinMaxSizeInput float_input(input.percentage_resolution_block_size); - MinMaxSize child_sizes = + MinMaxSizesInput float_input(input.percentage_resolution_block_size); + MinMaxSizes child_sizes = ComputeMinAndMaxContentContribution(style, float_node, float_input); LayoutUnit child_inline_margins = ComputeMinMaxMargins(style, float_node).InlineSum(); @@ -1751,9 +1737,9 @@ static LayoutUnit ComputeContentSize( return result; } -MinMaxSize NGInlineNode::ComputeMinMaxSize( +MinMaxSizes NGInlineNode::ComputeMinMaxSizes( WritingMode container_writing_mode, - const MinMaxSizeInput& input, + const MinMaxSizesInput& input, const NGConstraintSpace* constraint_space) { PrepareLayoutIfNeeded(); @@ -1761,7 +1747,7 @@ MinMaxSize NGInlineNode::ComputeMinMaxSize( // size. This gives the min-content, the width where lines wrap at every // break opportunity. NGLineBreaker::MaxSizeCache max_size_cache; - MinMaxSize sizes; + MinMaxSizes sizes; base::Optional<LayoutUnit> max_size; sizes.min_size = ComputeContentSize(*this, container_writing_mode, input, NGLineBreakerMode::kMinContent, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h index ddb8325722d..72a5dd5fa81 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h @@ -22,7 +22,6 @@ class NGInlineChildLayoutContext; class NGInlineNodeLegacy; class NGLayoutResult; class NGOffsetMapping; -struct MinMaxSize; struct NGInlineItemsData; // Represents an anonymous block box to be laid out, that contains consecutive @@ -55,9 +54,9 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { // Computes the value of min-content and max-content for this anonymous block // box. min-content is the inline size when lines wrap at every break // opportunity, and max-content is when lines do not wrap at all. - MinMaxSize ComputeMinMaxSize(WritingMode container_writing_mode, - const MinMaxSizeInput&, - const NGConstraintSpace* = nullptr); + MinMaxSizes ComputeMinMaxSizes(WritingMode container_writing_mode, + const MinMaxSizesInput&, + const NGConstraintSpace* = nullptr); // Instruct to re-compute |PrepareLayout| on the next layout. void InvalidatePrepareLayoutForTest() { @@ -72,10 +71,19 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { return Data().ItemsData(is_first_line); } + // There's a special intrinsic size measure quirk for images that are direct + // children of table cells that have auto inline-size: When measuring + // intrinsic min/max inline sizes, we pretend that it's not possible to break + // between images, or between text and images. Note that this only applies + // when measuring. During actual layout, on the other hand, standard breaking + // rules are to be followed. + // See https://quirks.spec.whatwg.org/#the-table-cell-width-calculation-quirk + bool IsStickyImagesQuirkForContentSize() const; + // Returns the text content to use for content sizing. This is normally the // same as |items_data.text_content|, except when sticky images quirk is // needed. - String TextContentForContentSize(const NGInlineItemsData& items_data) const; + static String TextContentForStickyImagesQuirk(const NGInlineItemsData&); // Clear associated fragments for LayoutObjects. // They are associated when NGPaintFragment is constructed, but when clearing, @@ -121,6 +129,16 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { String ToString() const; + struct FloatingObject { + DISALLOW_NEW(); + + void Trace(Visitor* visitor) {} + + const ComputedStyle& float_style; + const ComputedStyle& style; + LayoutUnit float_inline_max_size_with_margin; + }; + protected: bool IsPrepareLayoutFinished() const; @@ -160,8 +178,6 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { } const NGInlineNodeData& EnsureData(); - static String TextContentForStickyImagesQuirk(const NGInlineItemsData&); - static void ComputeOffsetMapping(LayoutBlockFlow* layout_block_flow, NGInlineNodeData* data); @@ -169,28 +185,14 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { friend class NGInlineNodeLegacy; }; -inline String NGInlineNode::TextContentForContentSize( - const NGInlineItemsData& items_data) const { - const String& text_content = items_data.text_content; - if (UNLIKELY(text_content.IsEmpty())) - return text_content; - - // There's a special intrinsic size measure quirk for images that are direct - // children of table cells that have auto inline-size: When measuring - // intrinsic min/max inline sizes, we pretend that it's not possible to break - // between images, or between text and images. Note that this only applies - // when measuring. During actual layout, on the other hand, standard breaking - // rules are to be followed. - // See https://quirks.spec.whatwg.org/#the-table-cell-width-calculation-quirk +inline bool NGInlineNode::IsStickyImagesQuirkForContentSize() const { if (UNLIKELY(GetDocument().InQuirksMode())) { const ComputedStyle& style = Style(); if (UNLIKELY(style.Display() == EDisplay::kTableCell && - style.LogicalWidth().IsIntrinsicOrAuto())) { - return TextContentForStickyImagesQuirk(items_data); - } + style.LogicalWidth().IsIntrinsicOrAuto())) + return true; } - - return text_content; + return false; } template <> @@ -202,4 +204,7 @@ struct DowncastTraits<NGInlineNode> { } // namespace blink +WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( + blink::NGInlineNode::FloatingObject) + #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_INLINE_NODE_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h index e77f4b569c9..5dda66cc9f2 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h @@ -11,6 +11,9 @@ namespace blink { +template <typename OffsetMappingBuilder> +class NGInlineItemsBuilderTemplate; + // Data which is required for inline nodes. struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData { public: @@ -40,6 +43,9 @@ struct CORE_EXPORT NGInlineNodeData : NGInlineItemsData { friend class NGInlineNodeForTest; friend class NGOffsetMappingTest; + template <typename OffsetMappingBuilder> + friend class NGInlineItemsBuilderTemplate; + // Items to use for the first line, when the node has :first-line rules. // // Items have different ComputedStyle, and may also have different diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc index 819493a0e6e..8d6cc679213 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc @@ -11,6 +11,7 @@ #include "third_party/blink/renderer/core/dom/text.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" @@ -48,7 +49,8 @@ class NGInlineNodeForTest : public NGInlineNode { unsigned start = data->text_content.length(); data->text_content = data->text_content + text; data->items.push_back(NGInlineItem(NGInlineItem::kText, start, - start + text.length(), layout_object)); + start + text.length(), layout_object, + /* is_first_for_node */ true)); data->is_empty_inline_ = false; } @@ -56,8 +58,9 @@ class NGInlineNodeForTest : public NGInlineNode { NGInlineNodeData* data = MutableData(); data->text_content = data->text_content + character; unsigned end = data->text_content.length(); - data->items.push_back( - NGInlineItem(NGInlineItem::kBidiControl, end - 1, end, nullptr)); + data->items.push_back(NGInlineItem(NGInlineItem::kBidiControl, end - 1, end, + nullptr, + /* is_first_for_node */ true)); data->is_bidi_enabled_ = true; data->is_empty_inline_ = false; } @@ -90,7 +93,6 @@ class NGInlineNodeTest : public NGLayoutTest { void SetUp() override { NGLayoutTest::SetUp(); style_ = ComputedStyle::Create(); - style_->GetFont().Update(nullptr); } void SetupHtml(const char* id, String html) { @@ -115,31 +117,10 @@ class NGInlineNodeTest : public NGLayoutTest { return node; } - MinMaxSize ComputeMinMaxSize(NGInlineNode node) { - return node.ComputeMinMaxSize( + MinMaxSizes ComputeMinMaxSizes(NGInlineNode node) { + return node.ComputeMinMaxSizes( node.Style().GetWritingMode(), - MinMaxSizeInput(/* percentage_resolution_block_size */ LayoutUnit())); - } - - void CreateLine( - NGInlineNode node, - Vector<scoped_refptr<const NGPhysicalTextFragment>>* fragments_out) { - NGConstraintSpaceBuilder builder(WritingMode::kHorizontalTb, - WritingMode::kHorizontalTb, - /* is_new_fc */ false); - builder.SetAvailableSize({LayoutUnit::Max(), LayoutUnit(-1)}); - NGConstraintSpace constraint_space = builder.ToConstraintSpace(); - NGInlineChildLayoutContext context; - scoped_refptr<const NGLayoutResult> result = - NGInlineLayoutAlgorithm(node, constraint_space, - nullptr /* break_token */, &context) - .Layout(); - - const auto& line = - To<NGPhysicalLineBoxFragment>(result->PhysicalFragment()); - for (const auto& child : line.Children()) { - fragments_out->push_back(To<NGPhysicalTextFragment>(child.get())); - } + MinMaxSizesInput(/* percentage_resolution_block_size */ LayoutUnit())); } const String& GetText() const { @@ -177,8 +158,8 @@ class NGInlineNodeTest : public NGLayoutTest { Vector<unsigned> ToEndOffsetList( NGInlineItemSegments::const_iterator segments) { Vector<unsigned> end_offsets; - for (const NGInlineItemSegment& segment : segments) - end_offsets.push_back(segment.EndOffset()); + for (const RunSegmenter::RunSegmenterRange& segment : segments) + end_offsets.push_back(segment.end); return end_offsets; } @@ -442,49 +423,27 @@ TEST_F(NGInlineNodeTest, SegmentBidiIsolate) { TEST_ITEM_OFFSET_DIR(items[8], 22u, 28u, TextDirection::kLtr); } -#define TEST_TEXT_FRAGMENT(fragment, start_offset, end_offset) \ - EXPECT_EQ(start_offset, fragment->StartOffset()); \ - EXPECT_EQ(end_offset, fragment->EndOffset()); - -TEST_F(NGInlineNodeTest, CreateLineBidiIsolate) { - UseLayoutObjectAndAhem(); - scoped_refptr<ComputedStyle> style = ComputedStyle::Create(); - style->SetLineHeight(Length::Fixed(1)); - style->GetFont().Update(nullptr); - NGInlineNodeForTest node = CreateInlineNode(); - node = CreateBidiIsolateNode(node, layout_object_); - node.ShapeText(); - Vector<scoped_refptr<const NGPhysicalTextFragment>> fragments; - CreateLine(node, &fragments); - EXPECT_EQ(5u, fragments.size()); - TEST_TEXT_FRAGMENT(fragments[0], 0u, 6u); - TEST_TEXT_FRAGMENT(fragments[1], 16u, 21u); - TEST_TEXT_FRAGMENT(fragments[2], 14u, 15u); - TEST_TEXT_FRAGMENT(fragments[3], 7u, 13u); - TEST_TEXT_FRAGMENT(fragments[4], 22u, 28u); -} - -TEST_F(NGInlineNodeTest, MinMaxSize) { +TEST_F(NGInlineNodeTest, MinMaxSizes) { LoadAhem(); SetupHtml("t", "<div id=t style='font:10px Ahem'>AB CDEF</div>"); NGInlineNodeForTest node = CreateInlineNode(); - MinMaxSize sizes = ComputeMinMaxSize(node); + MinMaxSizes sizes = ComputeMinMaxSizes(node); EXPECT_EQ(40, sizes.min_size); EXPECT_EQ(70, sizes.max_size); } -TEST_F(NGInlineNodeTest, MinMaxSizeElementBoundary) { +TEST_F(NGInlineNodeTest, MinMaxSizesElementBoundary) { LoadAhem(); SetupHtml("t", "<div id=t style='font:10px Ahem'>A B<span>C D</span></div>"); NGInlineNodeForTest node = CreateInlineNode(); - MinMaxSize sizes = ComputeMinMaxSize(node); + MinMaxSizes sizes = ComputeMinMaxSizes(node); // |min_content| should be the width of "BC" because there is an element // boundary between "B" and "C" but no break opportunities. EXPECT_EQ(20, sizes.min_size); EXPECT_EQ(60, sizes.max_size); } -TEST_F(NGInlineNodeTest, MinMaxSizeFloats) { +TEST_F(NGInlineNodeTest, MinMaxSizesFloats) { LoadAhem(); SetupHtml("t", R"HTML( <style> @@ -496,13 +455,13 @@ TEST_F(NGInlineNodeTest, MinMaxSizeFloats) { )HTML"); NGInlineNodeForTest node = CreateInlineNode(); - MinMaxSize sizes = ComputeMinMaxSize(node); + MinMaxSizes sizes = ComputeMinMaxSizes(node); EXPECT_EQ(50, sizes.min_size); EXPECT_EQ(130, sizes.max_size); } -TEST_F(NGInlineNodeTest, MinMaxSizeCloseTagAfterForcedBreak) { +TEST_F(NGInlineNodeTest, MinMaxSizesCloseTagAfterForcedBreak) { LoadAhem(); SetupHtml("t", R"HTML( <style> @@ -514,14 +473,14 @@ TEST_F(NGInlineNodeTest, MinMaxSizeCloseTagAfterForcedBreak) { )HTML"); NGInlineNodeForTest node = CreateInlineNode(); - MinMaxSize sizes = ComputeMinMaxSize(node); + MinMaxSizes sizes = ComputeMinMaxSizes(node); // The right border of the `</span>` is included in the line even if it // appears after `<br>`. crbug.com/991320. EXPECT_EQ(80, sizes.min_size); EXPECT_EQ(80, sizes.max_size); } -TEST_F(NGInlineNodeTest, MinMaxSizeFloatsClearance) { +TEST_F(NGInlineNodeTest, MinMaxSizesFloatsClearance) { LoadAhem(); SetupHtml("t", R"HTML( <style> @@ -534,13 +493,13 @@ TEST_F(NGInlineNodeTest, MinMaxSizeFloatsClearance) { )HTML"); NGInlineNodeForTest node = CreateInlineNode(); - MinMaxSize sizes = ComputeMinMaxSize(node); + MinMaxSizes sizes = ComputeMinMaxSizes(node); EXPECT_EQ(50, sizes.min_size); EXPECT_EQ(160, sizes.max_size); } -TEST_F(NGInlineNodeTest, MinMaxSizeTabulationWithBreakWord) { +TEST_F(NGInlineNodeTest, MinMaxSizesTabulationWithBreakWord) { LoadAhem(); SetupHtml("t", R"HTML( <style> @@ -554,7 +513,7 @@ TEST_F(NGInlineNodeTest, MinMaxSizeTabulationWithBreakWord) { )HTML"); NGInlineNodeForTest node = CreateInlineNode(); - MinMaxSize sizes = ComputeMinMaxSize(node); + MinMaxSizes sizes = ComputeMinMaxSizes(node); EXPECT_EQ(160, sizes.min_size); EXPECT_EQ(170, sizes.max_size); } @@ -882,7 +841,12 @@ TEST_F(NGInlineNodeTest, CollectInlinesShouldNotClearFirstInlineFragment) { // Running |CollectInlines| should not clear |FirstInlineFragment|. LayoutObject* first_child = container->firstChild()->GetLayoutObject(); - EXPECT_NE(first_child->FirstInlineFragment(), nullptr); + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + // TODO(yosin): We should use |FirstInlineItemFragmentIndex()| once we + // implement it. + } else { + EXPECT_NE(first_child->FirstInlineFragment(), nullptr); + } } TEST_F(NGInlineNodeTest, InvalidateAddSpan) { @@ -1371,7 +1335,7 @@ TEST_F(NGInlineNodeTest, MarkLineBoxesDirtyInInlineBlock) { // Inline block with auto-size calls |ComputeMinMaxSize|, which may call // |CollectInlines|. Emulate it to ensure it does not let tests to fail. GetDocument().UpdateStyleAndLayoutTree(); - ComputeMinMaxSize(NGInlineNode(layout_block_flow_)); + ComputeMinMaxSizes(NGInlineNode(layout_block_flow_)); auto lines = MarkLineBoxesDirty(); // TODO(kojii): Ideally, 0 should be false, or even 1 as well. @@ -1394,7 +1358,7 @@ TEST_F(NGInlineNodeTest, RemoveInlineNodeDataIfBlockBecomesEmpty2) { SetupHtml("container", "<div id=container><b><i>foo</i></b></div>"); ASSERT_TRUE(layout_block_flow_->HasNGInlineNodeData()); - GetElementById("container")->SetInnerHTMLFromString(""); + GetElementById("container")->setInnerHTML(""); UpdateAllLifecyclePhasesForTest(); EXPECT_FALSE(layout_block_flow_->HasNGInlineNodeData()); @@ -1425,9 +1389,9 @@ TEST_F(NGInlineNodeTest, ClearFirstInlineFragmentOnSplitFlow) { // Keep the text fragment to compare later. Element* inner_span = GetElementById("inner_span"); Node* text = inner_span->firstChild(); - scoped_refptr<NGPaintFragment> text_fragment_before_split = - text->GetLayoutObject()->FirstInlineFragment(); - EXPECT_NE(text_fragment_before_split.get(), nullptr); + NGInlineCursor text_fragment_before_split; + text_fragment_before_split.MoveTo(*text->GetLayoutObject()); + EXPECT_TRUE(text_fragment_before_split); // Append <div> to <span>. causing SplitFlow(). Element* outer_span = GetElementById("outer_span"); @@ -1435,30 +1399,29 @@ TEST_F(NGInlineNodeTest, ClearFirstInlineFragmentOnSplitFlow) { outer_span->appendChild(div); // Update tree but do NOT update layout. At this point, there's no guarantee, - // but there are some clients (e.g., Schroll Anchor) who try to read + // but there are some clients (e.g., Scroll Anchor) who try to read // associated fragments. // // NGPaintFragment is owned by LayoutNGBlockFlow. Because the original owner // no longer has an inline formatting context, the NGPaintFragment subtree is // destroyed, and should not be accessible. GetDocument().UpdateStyleAndLayoutTree(); - scoped_refptr<NGPaintFragment> text_fragment_before_layout = - text->GetLayoutObject()->FirstInlineFragment(); - EXPECT_EQ(text_fragment_before_layout, nullptr); + EXPECT_FALSE(text->GetLayoutObject()->IsInLayoutNGInlineFormattingContext()); // Update layout. There should be a different instance of the text fragment. UpdateAllLifecyclePhasesForTest(); - scoped_refptr<NGPaintFragment> text_fragment_after_layout = - text->GetLayoutObject()->FirstInlineFragment(); - EXPECT_NE(text_fragment_before_split, text_fragment_after_layout); + NGInlineCursor text_fragment_after_layout; + text_fragment_after_layout.MoveTo(*text->GetLayoutObject()); + EXPECT_NE(text_fragment_before_split.Current(), + text_fragment_after_layout.Current()); // Check it is the one owned by the new root inline formatting context. LayoutBlock* anonymous_block = inner_span->GetLayoutObject()->ContainingBlock(); EXPECT_TRUE(anonymous_block->IsAnonymous()); - const NGPaintFragment* block_fragment = anonymous_block->PaintFragment(); - const NGPaintFragment* line_box_fragment = block_fragment->FirstChild(); - EXPECT_EQ(line_box_fragment->FirstChild(), text_fragment_after_layout); + EXPECT_EQ(anonymous_block, text_fragment_after_layout.Current() + .GetLayoutObject() + ->ContainingBlock()); } TEST_F(NGInlineNodeTest, AddChildToSVGRoot) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc index af3b4b14df1..acddeab0eaf 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc @@ -57,14 +57,27 @@ NGLineBoxFragmentBuilder::ChildList::LastInFlowChild() { return nullptr; } +void NGLineBoxFragmentBuilder::ChildList::WillInsertChild( + unsigned insert_before) { + unsigned index = 0; + for (Child& child : children_) { + if (index >= insert_before) + break; + if (child.children_count && index + child.children_count > insert_before) + ++child.children_count; + ++index; + } +} + void NGLineBoxFragmentBuilder::ChildList::InsertChild(unsigned index) { + WillInsertChild(index); children_.insert(index, Child()); } void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( LayoutUnit delta) { for (auto& child : children_) - child.offset.inline_offset += delta; + child.rect.offset.inline_offset += delta; } void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( @@ -72,20 +85,20 @@ void NGLineBoxFragmentBuilder::ChildList::MoveInInlineDirection( unsigned start, unsigned end) { for (unsigned index = start; index < end; index++) - children_[index].offset.inline_offset += delta; + children_[index].rect.offset.inline_offset += delta; } void NGLineBoxFragmentBuilder::ChildList::MoveInBlockDirection( LayoutUnit delta) { for (auto& child : children_) - child.offset.block_offset += delta; + child.rect.offset.block_offset += delta; } void NGLineBoxFragmentBuilder::ChildList::MoveInBlockDirection(LayoutUnit delta, unsigned start, unsigned end) { for (unsigned index = start; index < end; index++) - children_[index].offset.block_offset += delta; + children_[index].rect.offset.block_offset += delta; } void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) { @@ -94,42 +107,39 @@ void NGLineBoxFragmentBuilder::AddChildren(ChildList& children) { for (auto& child : children) { if (child.layout_result) { DCHECK(!child.fragment); - AddChild(child.layout_result->PhysicalFragment(), child.offset); + AddChild(child.layout_result->PhysicalFragment(), child.Offset()); child.layout_result.reset(); } else if (child.fragment) { - AddChild(std::move(child.fragment), child.offset); + AddChild(std::move(child.fragment), child.Offset()); DCHECK(!child.fragment); } else if (child.out_of_flow_positioned_box) { AddOutOfFlowInlineChildCandidate( NGBlockNode(ToLayoutBox(child.out_of_flow_positioned_box)), - child.offset, child.container_direction); + child.Offset(), child.container_direction); child.out_of_flow_positioned_box = nullptr; } } } void NGLineBoxFragmentBuilder::PropagateChildrenData(ChildList& children) { - for (auto& child : children) { + for (unsigned index = 0; index < children.size(); ++index) { + auto& child = children[index]; if (child.layout_result) { DCHECK(!child.fragment); - const NGPhysicalContainerFragment& fragment = - child.layout_result->PhysicalFragment(); - if (fragment.IsFloating()) { - // Add positioned floating objects to the fragment tree, not to the - // fragment item list. Because they are not necessary for inline - // traversals, and leading floating objects are still in the fragment - // tree, this helps simplifying painting floats. - AddChild(fragment, child.offset); - child.layout_result.reset(); - continue; - } - PropagateChildData(child.layout_result->PhysicalFragment(), child.offset); + PropagateChildData(child.layout_result->PhysicalFragment(), + child.Offset()); + + // Skip over any children, the information should have already been + // propagated into this layout result. + if (child.children_count) + index += child.children_count - 1; + continue; } if (child.out_of_flow_positioned_box) { AddOutOfFlowInlineChildCandidate( NGBlockNode(ToLayoutBox(child.out_of_flow_positioned_box)), - child.offset, child.container_direction); + child.Offset(), child.container_direction); child.out_of_flow_positioned_box = nullptr; } } @@ -148,7 +158,9 @@ NGLineBoxFragmentBuilder::ToLineBoxFragment() { scoped_refptr<const NGPhysicalLineBoxFragment> fragment = NGPhysicalLineBoxFragment::Create(this); - return base::AdoptRef(new NGLayoutResult(std::move(fragment), this)); + return base::AdoptRef( + new NGLayoutResult(NGLayoutResult::NGLineBoxFragmentBuilderPassKey(), + std::move(fragment), this)); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h index 00807812e98..93550d8b80f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h @@ -5,7 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BOX_FRAGMENT_BUILDER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BOX_FRAGMENT_BUILDER_H_ -#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h" +#include "third_party/blink/renderer/core/layout/geometry/logical_rect.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_height_metrics.h" @@ -77,11 +77,12 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final scoped_refptr<const NGLayoutResult> layout_result; scoped_refptr<const NGPhysicalTextFragment> fragment; + const NGInlineItem* inline_item = nullptr; LayoutObject* out_of_flow_positioned_box = nullptr; LayoutObject* unpositioned_float = nullptr; // The offset of the border box, initially in this child coordinate system. // |ComputeInlinePositions()| converts it to the offset within the line box. - LogicalOffset offset; + LogicalRect rect; // The offset of a positioned float wrt. the root BFC. This should only be // set for positioned floats. NGBfcOffset bfc_offset; @@ -103,18 +104,35 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final Child() = default; // Create a placeholder. A placeholder does not have a fragment nor a bidi // level. - Child(LogicalOffset offset) : offset(offset) {} + Child(LayoutUnit block_offset, LayoutUnit block_size) + : rect(LayoutUnit(), block_offset, LayoutUnit(), block_size) {} + Child(const NGInlineItem& inline_item, + const LogicalRect& rect, + unsigned children_count) + : inline_item(&inline_item), + rect(rect), + children_count(children_count) {} // Crete a bidi control. A bidi control does not have a fragment, but has // bidi level and affects bidi reordering. Child(UBiDiLevel bidi_level) : bidi_level(bidi_level) {} // Create an in-flow |NGLayoutResult|. Child(scoped_refptr<const NGLayoutResult> layout_result, + const LogicalRect& rect, + unsigned children_count, + UBiDiLevel bidi_level) + : layout_result(std::move(layout_result)), + rect(rect), + children_count(children_count), + bidi_level(bidi_level) {} + Child(scoped_refptr<const NGLayoutResult> layout_result, LogicalOffset offset, LayoutUnit inline_size, + unsigned children_count, UBiDiLevel bidi_level) : layout_result(std::move(layout_result)), - offset(offset), + rect(offset, LogicalSize()), inline_size(inline_size), + children_count(children_count), bidi_level(bidi_level) {} // Create an in-flow |NGPhysicalTextFragment|. Child(scoped_refptr<const NGPhysicalTextFragment> fragment, @@ -122,7 +140,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final LayoutUnit inline_size, UBiDiLevel bidi_level) : fragment(std::move(fragment)), - offset(offset), + rect(offset, LogicalSize()), inline_size(inline_size), bidi_level(bidi_level) {} Child(scoped_refptr<const NGPhysicalTextFragment> fragment, @@ -130,7 +148,7 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final LayoutUnit inline_size, UBiDiLevel bidi_level) : fragment(std::move(fragment)), - offset({LayoutUnit(), block_offset}), + rect(LayoutUnit(), block_offset, LayoutUnit(), LayoutUnit()), inline_size(inline_size), bidi_level(bidi_level) {} // Create an out-of-flow positioned object. @@ -180,11 +198,20 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final } return false; } + const LogicalOffset& Offset() const { return rect.offset; } + LayoutUnit InlineOffset() const { return rect.offset.inline_offset; } + const LogicalSize& Size() const { return rect.size; } const NGPhysicalFragment* PhysicalFragment() const { if (layout_result) return &layout_result->PhysicalFragment(); return fragment.get(); } + TextDirection ResolvedDirection() const { + // Inline boxes are not leaves that they don't have directions. + DCHECK(HasBidiLevel() || layout_result->PhysicalFragment().IsInlineBox()); + return HasBidiLevel() ? DirectionFromLevel(bidi_level) + : TextDirection::kLtr; + } }; // A vector of Child. @@ -234,11 +261,18 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final void InsertChild(unsigned index); void InsertChild(unsigned index, scoped_refptr<const NGLayoutResult> layout_result, - const LogicalOffset& offset, - LayoutUnit inline_size, - UBiDiLevel bidi_level) { - children_.insert(index, Child{std::move(layout_result), offset, - inline_size, bidi_level}); + const LogicalRect& rect, + unsigned children_count) { + WillInsertChild(index); + children_.insert(index, Child(std::move(layout_result), rect, + children_count, /* bidi_level */ 0)); + } + void InsertChild(unsigned index, + const NGInlineItem& inline_item, + const LogicalRect& rect, + unsigned children_count) { + WillInsertChild(index); + children_.insert(index, Child(inline_item, rect, children_count)); } void MoveInInlineDirection(LayoutUnit); @@ -247,6 +281,8 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final void MoveInBlockDirection(LayoutUnit, unsigned start, unsigned end); private: + void WillInsertChild(unsigned index); + Vector<Child, 16> children_; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc index 31c45dd7290..494d7ae3167 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc @@ -132,22 +132,16 @@ LayoutUnit ComputeFloatAncestorInlineEndSize(const NGConstraintSpace& space, return inline_end_size; } -scoped_refptr<const NGPhysicalTextFragment> CreateHyphenFragment( - NGInlineNode node, - WritingMode writing_mode, - const NGInlineItem& item) { +void CreateHyphen(NGInlineNode node, + WritingMode writing_mode, + const NGInlineItem& item, + NGInlineItemResult* item_result) { DCHECK(item.Style()); const ComputedStyle& style = *item.Style(); TextDirection direction = style.Direction(); - String hyphen_string = style.HyphenString(); - HarfBuzzShaper shaper(hyphen_string); - scoped_refptr<ShapeResult> hyphen_result = - shaper.Shape(&style.GetFont(), direction); - NGTextFragmentBuilder builder(writing_mode); - builder.SetText(item.GetLayoutObject(), hyphen_string, &style, - /* is_ellipsis_style */ false, - ShapeResultView::Create(hyphen_result.get())); - return builder.ToTextFragment(); + item_result->hyphen_string = style.HyphenString(); + HarfBuzzShaper shaper(item_result->hyphen_string); + item_result->hyphen_shape_result = shaper.Shape(&style.GetFont(), direction); } inline void ClearNeedsLayout(const NGInlineItem& item) { @@ -183,10 +177,13 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node, use_first_line_style_(is_first_formatted_line_ && node.UseFirstLineStyle()), in_line_height_quirks_mode_(node.InLineHeightQuirksMode()), + sticky_images_quirk_(mode != NGLineBreakerMode::kContent && + node.IsStickyImagesQuirkForContentSize()), items_data_(node.ItemsData(use_first_line_style_)), - text_content_(mode == NGLineBreakerMode::kContent - ? items_data_.text_content - : node.TextContentForContentSize(items_data_)), + text_content_( + !sticky_images_quirk_ + ? items_data_.text_content + : NGInlineNode::TextContentForStickyImagesQuirk(items_data_)), constraint_space_(space), exclusion_space_(exclusion_space), break_token_(break_token), @@ -262,25 +259,6 @@ void NGLineBreaker::SetMaxSizeCache(MaxSizeCache* max_size_cache) { max_size_cache_ = max_size_cache; } -LayoutUnit NGLineBreaker::SetLineEndFragment( - scoped_refptr<const NGPhysicalTextFragment> fragment, - NGLineInfo* line_info) { - LayoutUnit inline_size; - bool is_horizontal = - IsHorizontalWritingMode(constraint_space_.GetWritingMode()); - if (line_info->LineEndFragment()) { - const PhysicalSize& size = line_info->LineEndFragment()->Size(); - inline_size = is_horizontal ? -size.width : -size.height; - } - if (fragment) { - const PhysicalSize& size = fragment->Size(); - inline_size = is_horizontal ? size.width : size.height; - } - line_info->SetLineEndFragment(std::move(fragment)); - position_ += inline_size; - return inline_size; -} - // Compute the base direction for bidi algorithm for this line. void NGLineBreaker::ComputeBaseDirection() { // If 'unicode-bidi' is not 'plaintext', use the base direction of the block. @@ -492,29 +470,15 @@ void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const { line_info->UpdateTextAlign(); } -// For Web-compatibility, allow break between an atomic inline and any adjacent -// U+00A0 NO-BREAK SPACE character. -// https://www.w3.org/TR/css-text-3/#line-break-details -bool NGLineBreaker::IsAtomicInlineBeforeNoBreakSpace( - const NGInlineItemResult& item_result) const { - DCHECK(auto_wrap_); - DCHECK_EQ(item_result.item->Type(), NGInlineItem::kAtomicInline); - const String& text = Text(); - DCHECK_GE(text.length(), item_result.end_offset); - return text.length() > item_result.end_offset && - text[item_result.end_offset] == kNoBreakSpaceCharacter && - // Except when sticky images quirk was applied. - text[item_result.start_offset] != kNoBreakSpaceCharacter; -} - -bool NGLineBreaker::IsAtomicInlineAfterNoBreakSpace( +// Atomic inlines have break opportunities before and after, even when the +// adjacent character is U+00A0 NO-BREAK SPACE character. +bool NGLineBreaker::ShouldForceCanBreakAfter( const NGInlineItemResult& item_result) const { DCHECK(auto_wrap_); DCHECK_EQ(item_result.item->Type(), NGInlineItem::kText); const String& text = Text(); DCHECK_GE(text.length(), item_result.end_offset); - if (text[item_result.end_offset - 1] != kNoBreakSpaceCharacter || - text.length() <= item_result.end_offset || + if (text.length() <= item_result.end_offset || text[item_result.end_offset] != kObjectReplacementCharacter) return false; // This kObjectReplacementCharacter can be any objects, such as a floating or @@ -598,8 +562,10 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, } // Try to break inside of this text item. - BreakResult break_result = BreakText(item_result, item, shape_result, - RemainingAvailableWidth(), line_info); + const LayoutUnit available_width = RemainingAvailableWidth(); + BreakResult break_result = + BreakText(item_result, item, shape_result, available_width, + available_width, line_info); DCHECK(item_result->shape_result || (break_result == kOverflow && break_anywhere_if_overflow_ && !override_break_anywhere_)); @@ -677,6 +643,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( const NGInlineItem& item, const ShapeResult& item_shape_result, LayoutUnit available_width, + LayoutUnit available_width_with_hyphens, NGLineInfo* line_info) { DCHECK(item.Type() == NGInlineItem::kText || (item.Type() == NGInlineItem::kControl && @@ -739,7 +706,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( // rewinded. Making this item long enough to overflow is enough. if (!shape_result) { DCHECK(options & ShapingLineBreaker::kNoResultIfOverflow); - item_result->inline_size = available_width + 1; + item_result->inline_size = available_width_with_hyphens + 1; item_result->end_offset = item.EndOffset(); return kOverflow; } @@ -750,25 +717,27 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( CHECK_GT(result.break_offset, item_result->start_offset); inline_size = shape_result->SnappedWidth().ClampNegativeToZero(); - item_result->inline_size = inline_size; if (UNLIKELY(result.is_hyphenated)) { const WritingMode writing_mode = constraint_space_.GetWritingMode(); - scoped_refptr<const NGPhysicalTextFragment> hyphen_fragment = - CreateHyphenFragment(node_, writing_mode, item); - LayoutUnit space_for_hyphen = available_width - inline_size; - LayoutUnit hyphen_inline_size = IsHorizontalWritingMode(writing_mode) - ? hyphen_fragment->Size().width - : hyphen_fragment->Size().height; + CreateHyphen(node_, writing_mode, item, item_result); + DCHECK(item_result->hyphen_shape_result); + DCHECK(item_result->hyphen_string); + LayoutUnit hyphen_inline_size = item_result->HyphenInlineSize(); // If the hyphen overflows, retry with the reduced available width. - if (space_for_hyphen >= 0 && hyphen_inline_size > space_for_hyphen) { - available_width -= hyphen_inline_size; - continue; + if (!result.is_overflow && inline_size <= available_width) { + LayoutUnit space_for_hyphen = + available_width_with_hyphens - inline_size; + if (space_for_hyphen >= 0 && hyphen_inline_size > space_for_hyphen) { + available_width -= hyphen_inline_size; + continue; + } } - inline_size += SetLineEndFragment(std::move(hyphen_fragment), line_info); - item_result->text_end_effect = NGTextEndEffect::kHyphen; + inline_size += hyphen_inline_size; + } else if (UNLIKELY(item_result->hyphen_shape_result)) { + item_result->hyphen_shape_result = nullptr; + item_result->hyphen_string = String(); } - item_result->inline_size = - shape_result->SnappedWidth().ClampNegativeToZero(); + item_result->inline_size = inline_size; item_result->end_offset = result.break_offset; item_result->shape_result = std::move(shape_result); break; @@ -796,7 +765,7 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( item_result->can_break_after = break_iterator_.IsBreakable(item_result->end_offset); if (!item_result->can_break_after && item.Type() == NGInlineItem::kText && - IsAtomicInlineAfterNoBreakSpace(*item_result)) + ShouldForceCanBreakAfter(*item_result)) item_result->can_break_after = true; trailing_whitespace_ = WhitespaceState::kUnknown; } @@ -1344,6 +1313,21 @@ void NGLineBreaker::HandleAtomicInline( DCHECK(item.Style()); const ComputedStyle& style = *item.Style(); + const LayoutUnit remaining_width = RemainingAvailableWidth(); + bool ignore_overflow_if_negative_margin = false; + if (state_ == LineBreakState::kContinue && remaining_width < 0) { + const unsigned item_index = item_index_; + DCHECK_EQ(item_index, static_cast<unsigned>(&item - Items().begin())); + DCHECK(!line_info->HasOverflow()); + HandleOverflow(line_info); + if (!line_info->HasOverflow() || item_index != item_index_) + return; + // Compute margins if this line overflows. Negative margins can put the + // position back. + DCHECK_NE(state_, LineBreakState::kContinue); + ignore_overflow_if_negative_margin = true; + } + // Compute margins before computing overflow, because even when the current // position is beyond the end, negative margins can bring this item back to on // the current line. @@ -1351,13 +1335,18 @@ void NGLineBreaker::HandleAtomicInline( item_result->margins = ComputeLineMarginsForVisualContainer(constraint_space_, style); LayoutUnit inline_margins = item_result->margins.InlineSum(); - LayoutUnit remaining_width = RemainingAvailableWidth(); - bool is_overflow_before = - state_ == LineBreakState::kContinue && remaining_width < 0; - if (UNLIKELY(is_overflow_before && inline_margins > remaining_width)) { - RemoveLastItem(line_info); - HandleOverflow(line_info); - return; + if (UNLIKELY(ignore_overflow_if_negative_margin)) { + DCHECK_LT(remaining_width, 0); + // The margin isn't negative, or the negative margin isn't large enough to + // put the position back. Break this line before this item. + if (inline_margins >= remaining_width) { + RemoveLastItem(line_info); + return; + } + // This line once overflowed, but the negative margin puts the position + // back. + state_ = LineBreakState::kContinue; + line_info->SetHasOverflow(false); } // When we're just computing min/max content sizes, we can skip the full @@ -1369,7 +1358,6 @@ void NGLineBreaker::HandleAtomicInline( item_result->layout_result = NGBlockNode(ToLayoutBox(item.GetLayoutObject())) .LayoutAtomicInline(constraint_space_, node_.Style(), - line_info->LineStyle().GetFontBaseline(), line_info->UseFirstLineStyle()); item_result->inline_size = NGFragment(constraint_space_.GetWritingMode(), @@ -1382,8 +1370,8 @@ void NGLineBreaker::HandleAtomicInline( } else { DCHECK(mode_ == NGLineBreakerMode::kMinContent || !max_size_cache_); NGBlockNode child(ToLayoutBox(item.GetLayoutObject())); - MinMaxSizeInput input(percentage_resolution_block_size_for_min_max); - MinMaxSize sizes = + MinMaxSizesInput input(percentage_resolution_block_size_for_min_max); + MinMaxSizes sizes = ComputeMinAndMaxContentContribution(node_.Style(), child, input); if (mode_ == NGLineBreakerMode::kMinContent) { item_result->inline_size = sizes.min_size + inline_margins; @@ -1399,10 +1387,11 @@ void NGLineBreaker::HandleAtomicInline( } item_result->should_create_line_box = true; - ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_); - if (!item_result->can_break_after && auto_wrap_ && - IsAtomicInlineBeforeNoBreakSpace(*item_result)) - item_result->can_break_after = true; + // Atomic inlines have break opportunities before and after, even when the + // adjacent character is U+00A0 NO-BREAK SPACE character, except when sticky + // images quirk is applied. + item_result->can_break_after = + auto_wrap_ && !(sticky_images_quirk_ && item.IsImage()); position_ += item_result->inline_size; trailing_whitespace_ = WhitespaceState::kNone; @@ -1448,6 +1437,10 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item, !leading_floats_.IsEmpty()) { DCHECK_LT(leading_floats_index_, leading_floats_.size()); item_result->positioned_float = leading_floats_[leading_floats_index_++]; + + // Don't break after leading floats if indented. + if (position_ != 0) + item_result->can_break_after = false; return; } @@ -1654,9 +1647,6 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { LayoutUnit width_to_rewind = position_ - available_width; DCHECK_GT(width_to_rewind, 0); - // Indicates positions of items may be changed and need to UpdatePosition(). - bool position_maybe_changed = false; - // Keep track of the shortest break opportunity. unsigned break_before = 0; @@ -1699,40 +1689,36 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { // If space is available, and if this text is breakable, part of the text // may fit. Try to break this item. if (width_to_rewind < 0 && item_result->may_break_inside) { - LayoutUnit item_available_width = -width_to_rewind; + const LayoutUnit item_available_width = -width_to_rewind; // Make sure the available width is smaller than the current width. The // break point must not be at the end when e.g., the text fits but its // right margin does not or following items do not. const LayoutUnit min_available_width = item_result->inline_size - 1; - if (item_available_width > min_available_width) { - item_available_width = min_available_width; - // If |inline_size| is zero (e.g., `font-size: 0`), |BreakText| cannot - // make it shorter. Take the previous break opportunity. - if (UNLIKELY(item_available_width <= 0)) { - if (BreakTextAtPreviousBreakOpportunity(item_result)) { - RewindOverflow(i + 1, line_info); - return; - } - continue; + // If |inline_size| is zero (e.g., `font-size: 0`), |BreakText| cannot + // make it shorter. Take the previous break opportunity. + if (UNLIKELY(min_available_width <= 0)) { + if (BreakTextAtPreviousBreakOpportunity(item_result)) { + RewindOverflow(i + 1, line_info); + return; } + continue; } - auto was_current_style = current_style_; + scoped_refptr<const ComputedStyle> was_current_style = current_style_; SetCurrentStyle(*item.Style()); - const unsigned end_offset_before = item_result->end_offset; - BreakResult break_result = - BreakText(item_result, item, *item.TextShapeResult(), - item_available_width, line_info); - DCHECK_LE(item_result->end_offset, end_offset_before); + const NGInlineItemResult item_result_before = *item_result; + BreakText(item_result, item, *item.TextShapeResult(), + std::min(item_available_width, min_available_width), + item_available_width, line_info); + DCHECK_LE(item_result->end_offset, item_result_before.end_offset); #if DCHECK_IS_ON() item_result->CheckConsistency(true); #endif + // If BreakText() changed this item small enough to fit, break here. - DCHECK_EQ(break_result == kSuccess, - item_result->inline_size <= item_available_width); - if (break_result == kSuccess) { - DCHECK_LE(item_result->inline_size, item_available_width); + if (item_result->can_break_after && + item_result->inline_size <= item_available_width && + item_result->end_offset < item_result_before.end_offset) { DCHECK_LT(item_result->end_offset, item.EndOffset()); - DCHECK(item_result->can_break_after); // If this is the last item, adjust it to accommodate the change. const unsigned new_end = i + 1; @@ -1740,8 +1726,6 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { if (new_end == item_results->size()) { position_ = available_width + width_to_rewind + item_result->inline_size; - if (line_info->LineEndFragment()) - SetLineEndFragment(nullptr, line_info); DCHECK_EQ(position_, line_info->ComputeWidth()); item_index_ = item_result->item_index; offset_ = item_result->end_offset; @@ -1757,8 +1741,10 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { HandleTrailingSpaces(item, line_info); return; } + + // Failed to break to fit. Restore to the original state. + *item_result = std::move(item_result_before); SetCurrentStyle(*was_current_style); - position_maybe_changed = true; } } } @@ -1786,11 +1772,6 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { return; } - if (position_maybe_changed) { - trailing_whitespace_ = WhitespaceState::kUnknown; - position_ = line_info->ComputeWidth(); - } - if (CanBreakAfterLast(*item_results)) { state_ = LineBreakState::kTrailing; return; @@ -1810,6 +1791,7 @@ void NGLineBreaker::RewindOverflow(unsigned new_end, NGLineInfo* line_info) { const Vector<NGInlineItem>& items = Items(); const NGInlineItemResults& item_results = line_info->Results(); DCHECK_LT(new_end, item_results.size()); + unsigned open_tag_count = 0; const String& text = Text(); for (unsigned index = new_end; index < item_results.size(); index++) { @@ -1819,16 +1801,20 @@ void NGLineBreaker::RewindOverflow(unsigned new_end, NGLineInfo* line_info) { if (item.Type() == NGInlineItem::kText) { // Text items are trailable if they start with trailable spaces. DCHECK_GT(item_result.Length(), 0u); - if (item_result.shape_result) { + if (item_result.shape_result || // kNoResultIfOverflow if 'break-word' + (break_anywhere_if_overflow_ && !override_break_anywhere_)) { DCHECK(item.Style()); const EWhiteSpace white_space = item.Style()->WhiteSpace(); if (ComputedStyle::AutoWrap(white_space) && white_space != EWhiteSpace::kBreakSpaces && IsBreakableSpace(text[item_result.start_offset])) { - // If all characters are trailable spaces, check the next item. - if (IsAllBreakableSpaces(text, item_result.start_offset + 1, - item_result.end_offset)) + // If more items left and all characters are trailable spaces, check + // the next item. + if (item_result.shape_result && index < item_results.size() - 1 && + IsAllBreakableSpaces(text, item_result.start_offset + 1, + item_result.end_offset)) { continue; + } // If this item starts with spaces followed by non-space characters, // rewind to before this item. |HandleText()| will include the spaces // and break there. @@ -1836,6 +1822,9 @@ void NGLineBreaker::RewindOverflow(unsigned new_end, NGLineInfo* line_info) { Rewind(index, line_info); DCHECK_EQ(static_cast<unsigned>(&item - items.begin()), item_index_); HandleTrailingSpaces(item, line_info); +#if DCHECK_IS_ON() + item_results.back().CheckConsistency(false); +#endif return; } } @@ -1962,7 +1951,6 @@ void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) { item_results.Shrink(new_end); trailing_collapsible_space_.reset(); - SetLineEndFragment(nullptr, line_info); position_ = line_info->ComputeWidth(); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h index d19c268bcae..ac8685caad5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h @@ -99,8 +99,6 @@ class CORE_EXPORT NGLineBreaker { unsigned end_offset, NGLineInfo*); NGInlineItemResult* AddItem(const NGInlineItem&, NGLineInfo*); - LayoutUnit SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>, - NGLineInfo*); void BreakLine(LayoutUnit percentage_resolution_block_size_for_min_max, NGLineInfo*); @@ -135,6 +133,7 @@ class CORE_EXPORT NGLineBreaker { const NGInlineItem&, const ShapeResult&, LayoutUnit available_width, + LayoutUnit available_width_with_hyphens, NGLineInfo*); bool BreakTextAtPreviousBreakOpportunity(NGInlineItemResult* item_result); bool HandleTextForFastMinContent(NGInlineItemResult*, @@ -166,10 +165,7 @@ class CORE_EXPORT NGLineBreaker { const NGInlineItem&, LayoutUnit percentage_resolution_block_size_for_min_max, NGLineInfo*); - bool IsAtomicInlineAfterNoBreakSpace( - const NGInlineItemResult& item_result) const; - bool IsAtomicInlineBeforeNoBreakSpace( - const NGInlineItemResult& item_result) const; + bool ShouldForceCanBreakAfter(const NGInlineItemResult& item_result) const; void HandleFloat(const NGInlineItem&, NGLineInfo*); void HandleOutOfFlowPositioned(const NGInlineItem&, NGLineInfo*); @@ -260,6 +256,10 @@ class CORE_EXPORT NGLineBreaker { // the next line. bool is_after_forced_break_ = false; + // Set in quirks mode when we're not supposed to break inside table cells + // between images, and between text and images. + bool sticky_images_quirk_ = false; + const NGInlineItemsData& items_data_; // The text content of this node. This is same as |items_data_.text_content| diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc index 34eba2935cd..4e53d79afa3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker_test.cc @@ -17,6 +17,17 @@ namespace blink { +String ToString(NGInlineItemResults line, NGInlineNode node) { + StringBuilder builder; + const String& text = node.ItemsData(false).text_content; + for (const auto& item_result : line) { + builder.Append( + StringView(text, item_result.start_offset, + item_result.end_offset - item_result.start_offset)); + } + return builder.ToString(); +} + class NGLineBreakerTest : public NGLayoutTest { protected: NGInlineNode CreateInlineNode(const String& html_content) { @@ -28,8 +39,10 @@ class NGLineBreakerTest : public NGLayoutTest { } // Break lines using the specified available width. - Vector<NGLineInfo> BreakToLineInfo(NGInlineNode node, - LayoutUnit available_width) { + Vector<std::pair<String, unsigned>> BreakLines( + NGInlineNode node, + LayoutUnit available_width, + bool fill_first_space_ = false) { DCHECK(node); node.PrepareLayoutIfNeeded(); @@ -42,13 +55,13 @@ class NGLineBreakerTest : public NGLayoutTest { scoped_refptr<NGInlineBreakToken> break_token; - Vector<NGLineInfo> line_infos; + Vector<std::pair<String, unsigned>> lines; trailing_whitespaces_.resize(0); NGExclusionSpace exclusion_space; NGPositionedFloatVector leading_floats; NGLineLayoutOpportunity line_opportunity(available_width); while (!break_token || !break_token->IsFinished()) { - NGLineInfo& line_info = line_infos.emplace_back(); + NGLineInfo line_info; NGLineBreaker line_breaker(node, NGLineBreakerMode::kContent, space, line_opportunity, leading_floats, 0u, break_token.get(), &exclusion_space); @@ -60,36 +73,25 @@ class NGLineBreakerTest : public NGLayoutTest { break; break_token = line_breaker.CreateBreakToken(line_info); + if (fill_first_space_ && lines.IsEmpty()) { + first_should_hang_trailing_space_ = + line_info.ShouldHangTrailingSpaces(); + first_hang_width_ = line_info.HangWidth(); + } + lines.push_back(std::make_pair(ToString(line_info.Results(), node), + line_info.Results().back().item_index)); } - return line_infos; - } - - Vector<NGInlineItemResults> BreakLines(NGInlineNode node, - LayoutUnit available_width) { - Vector<NGLineInfo> line_infos = BreakToLineInfo(node, available_width); - Vector<NGInlineItemResults> lines; - for (NGLineInfo& line_info : line_infos) - lines.push_back(std::move(line_info.Results())); return lines; } Vector<NGLineBreaker::WhitespaceState> trailing_whitespaces_; + bool first_should_hang_trailing_space_; + LayoutUnit first_hang_width_; }; namespace { -String ToString(NGInlineItemResults line, NGInlineNode node) { - StringBuilder builder; - const String& text = node.ItemsData(false).text_content; - for (const auto& item_result : line) { - builder.Append( - StringView(text, item_result.start_offset, - item_result.end_offset - item_result.start_offset)); - } - return builder.ToString(); -} - TEST_F(NGLineBreakerTest, SingleNode) { LoadAhem(); NGInlineNode node = CreateInlineNode(R"HTML( @@ -102,17 +104,17 @@ TEST_F(NGLineBreakerTest, SingleNode) { <div id=container>123 456 789</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(80)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("123 456", ToString(lines[0], node)); - EXPECT_EQ("789", ToString(lines[1], node)); + EXPECT_EQ("123 456", lines[0].first); + EXPECT_EQ("789", lines[1].first); lines = BreakLines(node, LayoutUnit(60)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("123", ToString(lines[0], node)); - EXPECT_EQ("456", ToString(lines[1], node)); - EXPECT_EQ("789", ToString(lines[2], node)); + EXPECT_EQ("123", lines[0].first); + EXPECT_EQ("456", lines[1].first); + EXPECT_EQ("789", lines[2].first); } TEST_F(NGLineBreakerTest, OverflowWord) { @@ -128,17 +130,17 @@ TEST_F(NGLineBreakerTest, OverflowWord) { )HTML"); // The first line overflows, but the last line does not. - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(40)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("12345", ToString(lines[0], node)); - EXPECT_EQ("678", ToString(lines[1], node)); + EXPECT_EQ("12345", lines[0].first); + EXPECT_EQ("678", lines[1].first); // Both lines overflow. lines = BreakLines(node, LayoutUnit(20)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("12345", ToString(lines[0], node)); - EXPECT_EQ("678", ToString(lines[1], node)); + EXPECT_EQ("12345", lines[0].first); + EXPECT_EQ("678", lines[1].first); } TEST_F(NGLineBreakerTest, OverflowTab) { @@ -156,11 +158,11 @@ TEST_F(NGLineBreakerTest, OverflowTab) { <div id=container>12345		678</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(100)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("12345\t\t", ToString(lines[0], node)); - EXPECT_EQ("678", ToString(lines[1], node)); + EXPECT_EQ("12345\t\t", lines[0].first); + EXPECT_EQ("678", lines[1].first); } TEST_F(NGLineBreakerTest, OverflowTabBreakWord) { @@ -179,11 +181,11 @@ TEST_F(NGLineBreakerTest, OverflowTabBreakWord) { <div id=container>12345		678</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(100)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("12345\t\t", ToString(lines[0], node)); - EXPECT_EQ("678", ToString(lines[1], node)); + EXPECT_EQ("12345\t\t", lines[0].first); + EXPECT_EQ("678", lines[1].first); } TEST_F(NGLineBreakerTest, OverflowAtomicInline) { @@ -203,28 +205,28 @@ TEST_F(NGLineBreakerTest, OverflowAtomicInline) { <div id=container>12345<span></span>678</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(80)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ(String(u"12345\uFFFC"), ToString(lines[0], node)); - EXPECT_EQ("678", ToString(lines[1], node)); + EXPECT_EQ(String(u"12345\uFFFC"), lines[0].first); + EXPECT_EQ("678", lines[1].first); lines = BreakLines(node, LayoutUnit(70)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("12345", ToString(lines[0], node)); - EXPECT_EQ(String(u"\uFFFC678"), ToString(lines[1], node)); + EXPECT_EQ("12345", lines[0].first); + EXPECT_EQ(String(u"\uFFFC678"), lines[1].first); lines = BreakLines(node, LayoutUnit(40)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("12345", ToString(lines[0], node)); - EXPECT_EQ(String(u"\uFFFC"), ToString(lines[1], node)); - EXPECT_EQ("678", ToString(lines[2], node)); + EXPECT_EQ("12345", lines[0].first); + EXPECT_EQ(String(u"\uFFFC"), lines[1].first); + EXPECT_EQ("678", lines[2].first); lines = BreakLines(node, LayoutUnit(20)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("12345", ToString(lines[0], node)); - EXPECT_EQ(String(u"\uFFFC"), ToString(lines[1], node)); - EXPECT_EQ("678", ToString(lines[2], node)); + EXPECT_EQ("12345", lines[0].first); + EXPECT_EQ(String(u"\uFFFC"), lines[1].first); + EXPECT_EQ("678", lines[2].first); } TEST_F(NGLineBreakerTest, OverflowMargin) { @@ -246,21 +248,21 @@ TEST_F(NGLineBreakerTest, OverflowMargin) { // While "123 456" can fit in a line, "456" has a right margin that cannot // fit. Since "456" and its right margin is not breakable, "456" should be on // the next line. - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(80)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("123", ToString(lines[0], node)); - EXPECT_EQ("456", ToString(lines[1], node)); - DCHECK_EQ(NGInlineItem::kCloseTag, items[lines[1].back().item_index].Type()); - EXPECT_EQ("789", ToString(lines[2], node)); + EXPECT_EQ("123", lines[0].first); + EXPECT_EQ("456", lines[1].first); + DCHECK_EQ(NGInlineItem::kCloseTag, items[lines[1].second].Type()); + EXPECT_EQ("789", lines[2].first); // Same as above, but this time "456" overflows the line because it is 70px. lines = BreakLines(node, LayoutUnit(60)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("123", ToString(lines[0], node)); - EXPECT_EQ("456", ToString(lines[1], node)); - DCHECK_EQ(NGInlineItem::kCloseTag, items[lines[1].back().item_index].Type()); - EXPECT_EQ("789", ToString(lines[2], node)); + EXPECT_EQ("123", lines[0].first); + EXPECT_EQ("456", lines[1].first); + DCHECK_EQ(NGInlineItem::kCloseTag, items[lines[1].second].Type()); + EXPECT_EQ("789", lines[2].first); } TEST_F(NGLineBreakerTest, OverflowAfterSpacesAcrossElements) { @@ -278,12 +280,12 @@ TEST_F(NGLineBreakerTest, OverflowAfterSpacesAcrossElements) { <div id=container><span>12345 </span> 1234567890123</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(100)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("12345 ", ToString(lines[0], node)); - EXPECT_EQ("1234567890", ToString(lines[1], node)); - EXPECT_EQ("123", ToString(lines[2], node)); + EXPECT_EQ("12345 ", lines[0].first); + EXPECT_EQ("1234567890", lines[1].first); + EXPECT_EQ("123", lines[2].first); } // Tests when the last word in a node wraps, and another node continues. @@ -299,11 +301,11 @@ TEST_F(NGLineBreakerTest, WrapLastWord) { <div id=container>AAA AAA AAA <span>BB</span> CC</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(100)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("AAA AAA", ToString(lines[0], node)); - EXPECT_EQ("AAA BB CC", ToString(lines[1], node)); + EXPECT_EQ("AAA AAA", lines[0].first); + EXPECT_EQ("AAA BB CC", lines[1].first); } TEST_F(NGLineBreakerTest, WrapLetterSpacing) { @@ -319,11 +321,11 @@ TEST_F(NGLineBreakerTest, WrapLetterSpacing) { <div id=container>Star Wars</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(100)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("Star", ToString(lines[0], node)); - EXPECT_EQ("Wars", ToString(lines[1], node)); + EXPECT_EQ("Star", lines[0].first); + EXPECT_EQ("Wars", lines[1].first); } TEST_F(NGLineBreakerTest, BoundaryInWord) { @@ -340,20 +342,20 @@ TEST_F(NGLineBreakerTest, BoundaryInWord) { // The element boundary within "456789" should not cause a break. // Since "789" does not fit, it should go to the next line along with "456". - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(80)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("123", ToString(lines[0], node)); - EXPECT_EQ("456789", ToString(lines[1], node)); - EXPECT_EQ("abc", ToString(lines[2], node)); + EXPECT_EQ("123", lines[0].first); + EXPECT_EQ("456789", lines[1].first); + EXPECT_EQ("abc", lines[2].first); // Same as above, but this time "456789" overflows the line because it is // 60px. lines = BreakLines(node, LayoutUnit(50)); EXPECT_EQ(3u, lines.size()); - EXPECT_EQ("123", ToString(lines[0], node)); - EXPECT_EQ("456789", ToString(lines[1], node)); - EXPECT_EQ("abc", ToString(lines[2], node)); + EXPECT_EQ("123", lines[0].first); + EXPECT_EQ("456789", lines[1].first); + EXPECT_EQ("abc", lines[2].first); } TEST_F(NGLineBreakerTest, BoundaryInFirstWord) { @@ -368,21 +370,21 @@ TEST_F(NGLineBreakerTest, BoundaryInFirstWord) { <div id=container><span>123</span>456 789</div> )HTML"); - Vector<NGInlineItemResults> lines; + Vector<std::pair<String, unsigned>> lines; lines = BreakLines(node, LayoutUnit(80)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("123456", ToString(lines[0], node)); - EXPECT_EQ("789", ToString(lines[1], node)); + EXPECT_EQ("123456", lines[0].first); + EXPECT_EQ("789", lines[1].first); lines = BreakLines(node, LayoutUnit(50)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("123456", ToString(lines[0], node)); - EXPECT_EQ("789", ToString(lines[1], node)); + EXPECT_EQ("123456", lines[0].first); + EXPECT_EQ("789", lines[1].first); lines = BreakLines(node, LayoutUnit(20)); EXPECT_EQ(2u, lines.size()); - EXPECT_EQ("123456", ToString(lines[0], node)); - EXPECT_EQ("789", ToString(lines[1], node)); + EXPECT_EQ("123456", lines[0].first); + EXPECT_EQ("789", lines[1].first); } struct WhitespaceStateTestData { @@ -451,7 +453,7 @@ TEST_P(NGWhitespaceStateTest, WhitespaceState) { R"HTML(</div> )HTML"); - Vector<NGLineInfo> line_infos = BreakToLineInfo(node, LayoutUnit(50)); + BreakLines(node, LayoutUnit(50)); EXPECT_EQ(trailing_whitespaces_[0], data.expected); } @@ -512,13 +514,11 @@ TEST_P(NGTrailingSpaceWidthTest, TrailingSpaceWidth) { R"HTML(</div> )HTML"); - Vector<NGLineInfo> line_infos = BreakToLineInfo(node, LayoutUnit(50)); - const NGLineInfo& line_info = line_infos[0]; - if (line_info.ShouldHangTrailingSpaces()) { - EXPECT_EQ(line_info.HangWidth(), - LayoutUnit(10) * data.trailing_space_width); + BreakLines(node, LayoutUnit(50), true); + if (first_should_hang_trailing_space_) { + EXPECT_EQ(first_hang_width_, LayoutUnit(10) * data.trailing_space_width); } else { - EXPECT_EQ(line_info.HangWidth(), LayoutUnit()); + EXPECT_EQ(first_hang_width_, LayoutUnit()); } } @@ -535,9 +535,9 @@ TEST_F(NGLineBreakerTest, MinMaxWithTrailingSpaces) { <div id=container>12345 6789 </div> )HTML"); - auto size = node.ComputeMinMaxSize( + auto size = node.ComputeMinMaxSizes( WritingMode::kHorizontalTb, - MinMaxSizeInput(/* percentage_resolution_block_size */ (LayoutUnit()))); + MinMaxSizesInput(/* percentage_resolution_block_size */ (LayoutUnit()))); EXPECT_EQ(size.min_size, LayoutUnit(60)); EXPECT_EQ(size.max_size, LayoutUnit(110)); } @@ -559,9 +559,9 @@ TEST_F(NGLineBreakerTest, TableCellWidthCalculationQuirkOutOfFlow) { GetDocument().SetCompatibilityMode(Document::kQuirksMode); EXPECT_TRUE(node.GetDocument().InQuirksMode()); - node.ComputeMinMaxSize( + node.ComputeMinMaxSizes( WritingMode::kHorizontalTb, - MinMaxSizeInput(/* percentage_resolution_block_size */ LayoutUnit())); + MinMaxSizesInput(/* percentage_resolution_block_size */ LayoutUnit())); // Pass if |ComputeMinMaxSize| doesn't hit DCHECK failures. } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc index 0014fe382c4..0306094c1f4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc @@ -14,112 +14,366 @@ namespace blink { +namespace { + +bool IsLeftMostOffset(const ShapeResult& shape_result, unsigned offset) { + if (shape_result.Rtl()) + return offset == shape_result.NumCharacters(); + return offset == 0; +} + +bool IsRightMostOffset(const ShapeResult& shape_result, unsigned offset) { + if (shape_result.Rtl()) + return offset == 0; + return offset == shape_result.NumCharacters(); +} + +} // namespace + NGLineTruncator::NGLineTruncator(const NGLineInfo& line_info) : line_style_(&line_info.LineStyle()), - available_width_(line_info.AvailableWidth()), + available_width_(line_info.AvailableWidth() - line_info.TextIndent()), line_direction_(line_info.BaseDirection()) {} -LayoutUnit NGLineTruncator::TruncateLine( - LayoutUnit line_width, - NGLineBoxFragmentBuilder::ChildList* line_box, - NGInlineLayoutStateStack* box_states) { - // Shape the ellipsis and compute its inline size. +const ComputedStyle& NGLineTruncator::EllipsisStyle() const { // The ellipsis is styled according to the line style. // https://drafts.csswg.org/css-ui/#ellipsing-details - const ComputedStyle* ellipsis_style = line_style_.get(); - const Font& font = ellipsis_style->GetFont(); - const SimpleFontData* font_data = font.PrimaryFont(); - DCHECK(font_data); - String ellipsis_text = - font_data && font_data->GlyphForCharacter(kHorizontalEllipsisCharacter) + return *line_style_; +} + +void NGLineTruncator::SetupEllipsis() { + const Font& font = EllipsisStyle().GetFont(); + ellipsis_font_data_ = font.PrimaryFont(); + DCHECK(ellipsis_font_data_); + ellipsis_text_ = + ellipsis_font_data_ && ellipsis_font_data_->GlyphForCharacter( + kHorizontalEllipsisCharacter) ? String(&kHorizontalEllipsisCharacter, 1) : String(u"..."); - HarfBuzzShaper shaper(ellipsis_text); - scoped_refptr<ShapeResultView> ellipsis_shape_result = + HarfBuzzShaper shaper(ellipsis_text_); + ellipsis_shape_result_ = ShapeResultView::Create(shaper.Shape(&font, line_direction_).get()); - LayoutUnit ellipsis_width = ellipsis_shape_result->SnappedWidth(); + ellipsis_width_ = ellipsis_shape_result_->SnappedWidth(); +} + +LayoutUnit NGLineTruncator::PlaceEllipsisNextTo( + NGLineBoxFragmentBuilder::ChildList* line_box, + NGLineBoxFragmentBuilder::Child* ellipsized_child) { + // Create the ellipsis, associating it with the ellipsized child. + DCHECK(ellipsized_child->HasInFlowFragment()); + LayoutObject* ellipsized_layout_object = + ellipsized_child->PhysicalFragment()->GetMutableLayoutObject(); + DCHECK(ellipsized_layout_object); + DCHECK(ellipsized_layout_object->IsInline()); + DCHECK(ellipsized_layout_object->IsText() || + ellipsized_layout_object->IsAtomicInlineLevel()); + NGTextFragmentBuilder builder(line_style_->GetWritingMode()); + builder.SetText(ellipsized_layout_object, ellipsis_text_, &EllipsisStyle(), + true /* is_ellipsis_style */, + std::move(ellipsis_shape_result_)); + + // Now the offset of the ellpisis is determined. Place the ellpisis into the + // line box. + LayoutUnit ellipsis_inline_offset = + IsLtr(line_direction_) + ? ellipsized_child->InlineOffset() + ellipsized_child->inline_size + : ellipsized_child->InlineOffset() - ellipsis_width_; + LayoutUnit ellpisis_ascent; + DCHECK(ellipsis_font_data_); + if (ellipsis_font_data_) { + FontBaseline baseline_type = line_style_->GetFontBaseline(); + NGLineHeightMetrics ellipsis_metrics(ellipsis_font_data_->GetFontMetrics(), + baseline_type); + ellpisis_ascent = ellipsis_metrics.ascent; + } + line_box->AddChild(builder.ToTextFragment(), + LogicalOffset{ellipsis_inline_offset, -ellpisis_ascent}, + ellipsis_width_, 0); + return ellipsis_inline_offset; +} + +wtf_size_t NGLineTruncator::AddTruncatedChild( + wtf_size_t source_index, + bool leave_one_character, + LayoutUnit position, + TextDirection edge, + NGLineBoxFragmentBuilder::ChildList* line_box, + NGInlineLayoutStateStack* box_states) { + NGLineBoxFragmentBuilder::ChildList& line = *line_box; + + scoped_refptr<ShapeResult> shape_result = + line[source_index].fragment->TextShapeResult()->CreateShapeResult(); + unsigned text_offset = shape_result->OffsetToFit(position, edge); + if (IsLtr(edge) ? IsLeftMostOffset(*shape_result, text_offset) + : IsRightMostOffset(*shape_result, text_offset)) { + if (!leave_one_character) + return kDidNotAddChild; + text_offset = + shape_result->OffsetToFit(shape_result->PositionForOffset( + IsRtl(edge) == shape_result->Rtl() + ? 1 + : shape_result->NumCharacters() - 1), + edge); + } + + const auto& fragment = line[source_index].fragment; + const bool keep_start = edge == fragment->ResolvedDirection(); + scoped_refptr<const NGPhysicalTextFragment> truncated_fragment = + keep_start ? fragment->TrimText(fragment->StartOffset(), + fragment->StartOffset() + text_offset) + : fragment->TrimText(fragment->StartOffset() + text_offset, + fragment->EndOffset()); + wtf_size_t new_index = line.size(); + line.AddChild(); + box_states->ChildInserted(new_index); + line[new_index] = line[source_index]; + line[new_index].inline_size = line_style_->IsHorizontalWritingMode() + ? truncated_fragment->Size().width + : truncated_fragment->Size().height; + line[new_index].fragment = std::move(truncated_fragment); + return new_index; +} + +LayoutUnit NGLineTruncator::TruncateLine( + LayoutUnit line_width, + NGLineBoxFragmentBuilder::ChildList* line_box, + NGInlineLayoutStateStack* box_states) { + // Shape the ellipsis and compute its inline size. + SetupEllipsis(); // Loop children from the logical last to the logical first to determine where // to place the ellipsis. Children maybe truncated or moved as part of the // process. - NGLineBoxFragmentBuilder::Child* ellpisized_child = nullptr; + NGLineBoxFragmentBuilder::Child* ellipsized_child = nullptr; scoped_refptr<const NGPhysicalTextFragment> truncated_fragment; if (IsLtr(line_direction_)) { NGLineBoxFragmentBuilder::Child* first_child = line_box->FirstInFlowChild(); for (auto it = line_box->rbegin(); it != line_box->rend(); it++) { auto& child = *it; - if (EllipsizeChild(line_width, ellipsis_width, &child == first_child, + if (EllipsizeChild(line_width, ellipsis_width_, &child == first_child, &child, &truncated_fragment)) { - ellpisized_child = &child; + ellipsized_child = &child; break; } } } else { NGLineBoxFragmentBuilder::Child* first_child = line_box->LastInFlowChild(); for (auto& child : *line_box) { - if (EllipsizeChild(line_width, ellipsis_width, &child == first_child, + if (EllipsizeChild(line_width, ellipsis_width_, &child == first_child, &child, &truncated_fragment)) { - ellpisized_child = &child; + ellipsized_child = &child; break; } } } // Abort if ellipsis could not be placed. - if (!ellpisized_child) + if (!ellipsized_child) return line_width; // Truncate the text fragment if needed. if (truncated_fragment) { - DCHECK(ellpisized_child->fragment); + DCHECK(ellipsized_child->fragment); // In order to preserve layout information before truncated, hide the // original fragment and insert a truncated one. - size_t child_index_to_truncate = ellpisized_child - line_box->begin(); + size_t child_index_to_truncate = ellipsized_child - line_box->begin(); line_box->InsertChild(child_index_to_truncate + 1); box_states->ChildInserted(child_index_to_truncate + 1); NGLineBoxFragmentBuilder::Child* child_to_truncate = &(*line_box)[child_index_to_truncate]; - ellpisized_child = std::next(child_to_truncate); - *ellpisized_child = *child_to_truncate; + ellipsized_child = std::next(child_to_truncate); + *ellipsized_child = *child_to_truncate; HideChild(child_to_truncate); LayoutUnit new_inline_size = line_style_->IsHorizontalWritingMode() ? truncated_fragment->Size().width : truncated_fragment->Size().height; - DCHECK_LE(new_inline_size, ellpisized_child->inline_size); + DCHECK_LE(new_inline_size, ellipsized_child->inline_size); if (UNLIKELY(IsRtl(line_direction_))) { - ellpisized_child->offset.inline_offset += - ellpisized_child->inline_size - new_inline_size; + ellipsized_child->rect.offset.inline_offset += + ellipsized_child->inline_size - new_inline_size; } - ellpisized_child->inline_size = new_inline_size; - ellpisized_child->fragment = std::move(truncated_fragment); + ellipsized_child->inline_size = new_inline_size; + ellipsized_child->fragment = std::move(truncated_fragment); } // Create the ellipsis, associating it with the ellipsized child. - LayoutObject* ellipsized_layout_object = - ellpisized_child->PhysicalFragment()->GetMutableLayoutObject(); - DCHECK(ellipsized_layout_object && ellipsized_layout_object->IsInline() && - (ellipsized_layout_object->IsText() || - ellipsized_layout_object->IsAtomicInlineLevel())); - NGTextFragmentBuilder builder(line_style_->GetWritingMode()); - builder.SetText(ellipsized_layout_object, ellipsis_text, ellipsis_style, - true /* is_ellipsis_style */, - std::move(ellipsis_shape_result)); - - // Now the offset of the ellpisis is determined. Place the ellpisis into the - // line box. LayoutUnit ellipsis_inline_offset = - IsLtr(line_direction_) - ? ellpisized_child->offset.inline_offset + - ellpisized_child->inline_size - : ellpisized_child->offset.inline_offset - ellipsis_width; - FontBaseline baseline_type = line_style_->GetFontBaseline(); - NGLineHeightMetrics ellipsis_metrics(font_data->GetFontMetrics(), - baseline_type); - line_box->AddChild( - builder.ToTextFragment(), - LogicalOffset{ellipsis_inline_offset, -ellipsis_metrics.ascent}, - ellipsis_width, 0); - return std::max(ellipsis_inline_offset + ellipsis_width, line_width); + PlaceEllipsisNextTo(line_box, ellipsized_child); + return std::max(ellipsis_inline_offset + ellipsis_width_, line_width); +} + +// This function was designed to work only with <input type=file>. +// We assume the line box contains: +// (Optional) children without in-flow fragments +// Children with in-flow fragments, and +// (Optional) children without in-flow fragments +// in this order, and the children with in-flow fragments have no padding, +// no border, and no margin. +// Children with IsPlaceholder() can appear anywhere. +LayoutUnit NGLineTruncator::TruncateLineInTheMiddle( + LayoutUnit line_width, + NGLineBoxFragmentBuilder::ChildList* line_box, + NGInlineLayoutStateStack* box_states) { + // Shape the ellipsis and compute its inline size. + SetupEllipsis(); + + NGLineBoxFragmentBuilder::ChildList& line = *line_box; + wtf_size_t initial_index_left = kNotFound; + wtf_size_t initial_index_right = kNotFound; + for (wtf_size_t i = 0; i < line_box->size(); ++i) { + auto& child = line[i]; + if (!child.fragment && child.IsPlaceholder()) + continue; + if (child.HasOutOfFlowFragment() || !child.fragment || + !child.fragment->TextShapeResult()) { + if (initial_index_right != kNotFound) + break; + continue; + } + if (initial_index_left == kNotFound) + initial_index_left = i; + initial_index_right = i; + } + // There are no truncatable children. + if (initial_index_left == kNotFound) + return line_width; + DCHECK_NE(initial_index_right, kNotFound); + DCHECK(line[initial_index_left].HasInFlowFragment()); + DCHECK(line[initial_index_right].HasInFlowFragment()); + + // line[]: + // s s s p f f p f f s s + // ^ ^ + // initial_index_left | + // initial_index_right + // s: child without in-flow fragment + // p: placeholder child + // f: child with in-flow fragment + + const LayoutUnit static_width_left = line[initial_index_left].InlineOffset(); + LayoutUnit static_width_right = LayoutUnit(0); + for (wtf_size_t i = initial_index_right + 1; i < line.size(); ++i) + static_width_right += line[i].inline_size; + const LayoutUnit available_width = + available_width_ - static_width_left - static_width_right; + if (available_width <= ellipsis_width_) + return line_width; + LayoutUnit available_width_left = (available_width - ellipsis_width_) / 2; + LayoutUnit available_width_right = available_width_left; + + // Children for ellipsis and truncated fragments will have index which + // is >= new_child_start. + const wtf_size_t new_child_start = line.size(); + + wtf_size_t index_left = initial_index_left; + wtf_size_t index_right = initial_index_right; + + if (IsLtr(line_direction_)) { + // Find truncation point at the left, truncate, and add an ellipsis. + while (available_width_left >= line[index_left].inline_size) + available_width_left -= line[index_left++].inline_size; + DCHECK_LE(index_left, index_right); + DCHECK(!line[index_left].IsPlaceholder()); + wtf_size_t new_index = AddTruncatedChild( + index_left, index_left == initial_index_left, available_width_left, + TextDirection::kLtr, line_box, box_states); + if (new_index == kDidNotAddChild) { + DCHECK_GT(index_left, initial_index_left); + DCHECK_GT(index_left, 0u); + wtf_size_t i = index_left; + while (!line[--i].HasInFlowFragment()) + DCHECK(line[i].IsPlaceholder()); + PlaceEllipsisNextTo(line_box, &line[i]); + available_width_right += available_width_left; + } else { + PlaceEllipsisNextTo(line_box, &line[new_index]); + available_width_right += + available_width_left - line[new_index].inline_size; + } + + // Find truncation point at the right. + while (available_width_right >= line[index_right].inline_size) + available_width_right -= line[index_right--].inline_size; + LayoutUnit new_modified_right_offset = + line[line.size() - 1].InlineOffset() + ellipsis_width_; + DCHECK_LE(index_left, index_right); + DCHECK(!line[index_right].IsPlaceholder()); + if (available_width_right > 0) { + new_index = AddTruncatedChild( + index_right, false, + line[index_right].inline_size - available_width_right, + TextDirection::kRtl, line_box, box_states); + if (new_index != kDidNotAddChild) { + line[new_index].rect.offset.inline_offset = new_modified_right_offset; + new_modified_right_offset += line[new_index].inline_size; + } + } + // Shift unchanged children at the right of the truncated child. + // It's ok to modify existing children's offsets because they are not + // web-exposed. + LayoutUnit offset_diff = line[index_right].InlineOffset() + + line[index_right].inline_size - + new_modified_right_offset; + for (wtf_size_t i = index_right + 1; i < new_child_start; ++i) + line[i].rect.offset.inline_offset -= offset_diff; + line_width -= offset_diff; + + } else { + // Find truncation point at the right, truncate, and add an ellipsis. + while (available_width_right >= line[index_right].inline_size) + available_width_right -= line[index_right--].inline_size; + DCHECK_LE(index_left, index_right); + DCHECK(!line[index_right].IsPlaceholder()); + wtf_size_t new_index = + AddTruncatedChild(index_right, index_right == initial_index_right, + line[index_right].inline_size - available_width_right, + TextDirection::kRtl, line_box, box_states); + if (new_index == kDidNotAddChild) { + DCHECK_LT(index_right, initial_index_right); + wtf_size_t i = index_right; + while (!line[++i].HasInFlowFragment()) + DCHECK(line[i].IsPlaceholder()); + PlaceEllipsisNextTo(line_box, &line[i]); + available_width_left += available_width_right; + } else { + line[new_index].rect.offset.inline_offset += + line[index_right].inline_size - line[new_index].inline_size; + PlaceEllipsisNextTo(line_box, &line[new_index]); + available_width_left += + available_width_right - line[new_index].inline_size; + } + LayoutUnit ellipsis_offset = line[line.size() - 1].InlineOffset(); + + // Find truncation point at the left. + while (available_width_left >= line[index_left].inline_size) + available_width_left -= line[index_left++].inline_size; + DCHECK_LE(index_left, index_right); + DCHECK(!line[index_left].IsPlaceholder()); + if (available_width_left > 0) { + new_index = AddTruncatedChild(index_left, false, available_width_left, + TextDirection::kLtr, line_box, box_states); + if (new_index != kDidNotAddChild) { + line[new_index].rect.offset.inline_offset = + ellipsis_offset - line[new_index].inline_size; + } + } + + // Shift unchanged children at the left of the truncated child. + // It's ok to modify existing children's offsets because they are not + // web-exposed. + LayoutUnit offset_diff = + line[line.size() - 1].InlineOffset() - line[index_left].InlineOffset(); + for (wtf_size_t i = index_left; i > 0; --i) + line[i - 1].rect.offset.inline_offset += offset_diff; + line_width -= offset_diff; + } + // Hide left/right truncated children and children between them. + for (wtf_size_t i = index_left; i <= index_right; ++i) { + if (line[i].HasInFlowFragment()) + HideChild(&line[i]); + } + + return line_width; } // Hide this child from being painted. Leaves a hidden fragment so that layout @@ -147,7 +401,7 @@ void NGLineTruncator::HideChild(NGLineBoxFragmentBuilder::Child* child) { // paddings, because clipping is at the content box but ellipsizing is at // the padding box. Just move to the max because we don't know paddings, // and max should do what we need. - child->offset.inline_offset = LayoutUnit::NearlyMax(); + child->rect.offset.inline_offset = LayoutUnit::NearlyMax(); return; } @@ -182,8 +436,8 @@ bool NGLineTruncator::EllipsizeChild( // Can't place ellipsis if this child is completely outside of the box. LayoutUnit child_inline_offset = IsLtr(line_direction_) - ? child->offset.inline_offset - : line_width - (child->offset.inline_offset + child->inline_size); + ? child->InlineOffset() + : line_width - (child->InlineOffset() + child->inline_size); LayoutUnit space_for_child = available_width_ - child_inline_offset; if (space_for_child <= 0) { // This child is outside of the content box, but we still need to hide it. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h index 0c507997ad2..c65fa3ce5a4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.h @@ -33,7 +33,40 @@ class CORE_EXPORT NGLineTruncator final { NGLineBoxFragmentBuilder::ChildList* line_box, NGInlineLayoutStateStack* box_states); + LayoutUnit TruncateLineInTheMiddle( + LayoutUnit line_width, + NGLineBoxFragmentBuilder::ChildList* line_box, + NGInlineLayoutStateStack* box_states); + private: + const ComputedStyle& EllipsisStyle() const; + + // Initialize four ellipsis_*_ data members. + void SetupEllipsis(); + + // Add a child for ellipsis next to |ellipsized_child|. + LayoutUnit PlaceEllipsisNextTo( + NGLineBoxFragmentBuilder::ChildList* line_box, + NGLineBoxFragmentBuilder::Child* ellipsized_child); + + static constexpr wtf_size_t kDidNotAddChild = WTF::kNotFound; + // Add a child with truncated text of (*line_box)[source_index]. + // This function returns the index of the new child. + // If the truncated text is empty, kDidNotAddChild is returned. + // + // |leave_one_character| - Force to leave at least one character regardless of + // |position|. + // |position| and |edge| - Indicate truncation point and direction. + // If |edge| is TextDirection::kLtr, the left side of + // |position| will be copied to the new child. + // Otherwise, the right side of |position| will be + // copied. + wtf_size_t AddTruncatedChild(wtf_size_t source_index, + bool leave_one_character, + LayoutUnit position, + TextDirection edge, + NGLineBoxFragmentBuilder::ChildList* line_box, + NGInlineLayoutStateStack* box_states); bool EllipsizeChild( LayoutUnit line_width, LayoutUnit ellipsis_width, @@ -50,6 +83,15 @@ class CORE_EXPORT NGLineTruncator final { scoped_refptr<const ComputedStyle> line_style_; LayoutUnit available_width_; TextDirection line_direction_; + + // The following 3 data members are available after SetupEllipsis(). + const SimpleFontData* ellipsis_font_data_; + String ellipsis_text_; + LayoutUnit ellipsis_width_; + + // This data member is available between SetupEllipsis() and + // PlaceEllipsisNextTo(). + scoped_refptr<ShapeResultView> ellipsis_shape_result_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc index 9e5ef9dc4aa..a17617b0d31 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc @@ -123,8 +123,6 @@ void NGOffsetMappingUnit::AssertValid() const { #endif } -NGOffsetMappingUnit::~NGOffsetMappingUnit() = default; - const Node* NGOffsetMappingUnit::AssociatedNode() const { if (const auto* text_fragment = ToLayoutTextFragmentOrNull(layout_object_)) return text_fragment->AssociatedTextNode(); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h index 40ab1e60d63..13b2eb52bbb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h @@ -47,7 +47,6 @@ class CORE_EXPORT NGOffsetMappingUnit { unsigned dom_end, unsigned text_content_start, unsigned text_content_end); - ~NGOffsetMappingUnit(); // Returns associated node for this unit or null if this unit is associated // to generated content. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc index 206226f2b87..3e2c8f22e7d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc @@ -97,7 +97,6 @@ class NGOffsetMappingTest : public NGLayoutTest { void SetUp() override { NGLayoutTest::SetUp(); style_ = ComputedStyle::Create(); - style_->GetFont().Update(nullptr); } void SetupHtml(const char* id, String html) { @@ -1496,7 +1495,8 @@ TEST_P(NGOffsetMappingGetterTest, Get) { // For the purpose of this test, ensure this is laid out by each layout // engine. - DCHECK_EQ(layout_block_flow->IsLayoutNGMixin(), GetParam()); + DCHECK_EQ(layout_block_flow->IsLayoutNGMixin(), + RuntimeEnabledFeatures::LayoutNGEnabled()); const NGOffsetMapping* mapping = NGInlineNode::GetOffsetMapping(layout_block_flow); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc index cab0866ce4c..ac726c4dea4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc @@ -6,9 +6,11 @@ #include "third_party/blink/renderer/core/editing/editing_utilities.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -37,11 +39,12 @@ NGPhysicalLineBoxFragment::Create(NGLineBoxFragmentBuilder* builder) { sizeof(NGPhysicalLineBoxFragment) + builder->children_.size() * sizeof(NGLink), ::WTF::GetStringWithTypeName<NGPhysicalLineBoxFragment>()); - new (data) NGPhysicalLineBoxFragment(builder); + new (data) NGPhysicalLineBoxFragment(PassKey(), builder); return base::AdoptRef(static_cast<NGPhysicalLineBoxFragment*>(data)); } NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment( + PassKey key, NGLineBoxFragmentBuilder* builder) : NGPhysicalContainerFragment(builder, builder->GetWritingMode(), @@ -51,48 +54,51 @@ NGPhysicalLineBoxFragment::NGPhysicalLineBoxFragment( metrics_(builder->metrics_) { // A line box must have a metrics unless it's an empty line box. DCHECK(!metrics_.IsEmpty() || IsEmptyLineBox()); - base_direction_ = static_cast<unsigned>(builder->base_direction_); + base_or_resolved_direction_ = static_cast<unsigned>(builder->base_direction_); has_hanging_ = builder->hang_inline_size_ != 0; has_propagated_descendants_ = has_floating_descendants_for_paint_ || HasOutOfFlowPositionedDescendants() || builder->unpositioned_list_marker_; } -NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics( - FontBaseline) const { +NGLineHeightMetrics NGPhysicalLineBoxFragment::BaselineMetrics() const { // TODO(kojii): Computing other baseline types than the used one is not // implemented yet. // TODO(kojii): We might need locale/script to look up OpenType BASE table. return metrics_; } +namespace { + +// Include the inline-size of the line-box in the overflow. +inline void AddInlineSizeToOverflow(const PhysicalRect& rect, + const WritingMode container_writing_mode, + PhysicalRect* overflow) { + PhysicalRect inline_rect; + inline_rect.offset = rect.offset; + if (IsHorizontalWritingMode(container_writing_mode)) + inline_rect.size.width = rect.size.width; + else + inline_rect.size.height = rect.size.height; + overflow->UniteEvenIfEmpty(inline_rect); +} + +} // namespace + PhysicalRect NGPhysicalLineBoxFragment::ScrollableOverflow( - const LayoutObject* container, - const ComputedStyle* container_style, - PhysicalSize container_physical_size) const { - WritingMode container_writing_mode = container_style->GetWritingMode(); - TextDirection container_direction = container_style->Direction(); + const NGPhysicalBoxFragment& container, + const ComputedStyle& container_style) const { + const WritingMode container_writing_mode = container_style.GetWritingMode(); + const TextDirection container_direction = container_style.Direction(); PhysicalRect overflow; for (const auto& child : Children()) { PhysicalRect child_scroll_overflow = child->ScrollableOverflowForPropagation(container); child_scroll_overflow.offset += child.Offset(); - // Chop the hanging part from scrollable overflow. Children overflow in - // inline direction should hang, which should not cause scroll. - // TODO(kojii): Should move to text fragment to make this more accurate. if (UNLIKELY(has_hanging_ && !child->IsFloatingOrOutOfFlowPositioned())) { - if (IsHorizontalWritingMode(container_writing_mode)) { - if (child_scroll_overflow.offset.left < 0) - child_scroll_overflow.offset.left = LayoutUnit(); - if (child_scroll_overflow.Right() > Size().width) - child_scroll_overflow.ShiftRightEdgeTo(Size().width); - } else { - if (child_scroll_overflow.offset.top < 0) - child_scroll_overflow.offset.top = LayoutUnit(); - if (child_scroll_overflow.Bottom() > Size().height) - child_scroll_overflow.ShiftBottomEdgeTo(Size().height); - } + AdjustScrollableOverflowForHanging(LocalRect(), container_writing_mode, + &child_scroll_overflow); } // For implementation reasons, text nodes inherit computed style from their @@ -102,18 +108,35 @@ PhysicalRect NGPhysicalLineBoxFragment::ScrollableOverflow( if (!child->IsText()) { child_scroll_overflow.offset += ComputeRelativeOffset(child->Style(), container_writing_mode, - container_direction, container_physical_size); + container_direction, container.Size()); } overflow.Unite(child_scroll_overflow); } // Make sure we include the inline-size of the line-box in the overflow. - PhysicalRect rect; - if (IsHorizontalWritingMode(container_writing_mode)) - rect.size.width = Size().width; - else - rect.size.height = Size().height; - overflow.UniteEvenIfEmpty(rect); + AddInlineSizeToOverflow(LocalRect(), container_writing_mode, &overflow); + + return overflow; +} + +PhysicalRect NGPhysicalLineBoxFragment::ScrollableOverflowForLine( + const NGPhysicalBoxFragment& container, + const ComputedStyle& container_style, + const NGFragmentItem& line, + const NGInlineCursor& cursor) const { + DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); + DCHECK_EQ(&line, cursor.CurrentItem()); + DCHECK_EQ(line.LineBoxFragment(), this); + + PhysicalRect overflow; + AddScrollableOverflowForInlineChild(container, container_style, line, + has_hanging_, cursor, &overflow); + + // Make sure we include the inline-size of the line-box in the overflow. + // Note, the bottom half-leading should not be included. crbug.com/996847 + const WritingMode container_writing_mode = container_style.GetWritingMode(); + AddInlineSizeToOverflow(line.RectInContainerBlock(), container_writing_mode, + &overflow); return overflow; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h index 6ee0a0a81fb..044007e1cde 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h @@ -13,6 +13,7 @@ namespace blink { +class NGFragmentItem; class NGLineBoxFragmentBuilder; class CORE_EXPORT NGPhysicalLineBoxFragment final @@ -31,6 +32,9 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final static scoped_refptr<const NGPhysicalLineBoxFragment> Create( NGLineBoxFragmentBuilder* builder); + using PassKey = util::PassKey<NGPhysicalLineBoxFragment>; + NGPhysicalLineBoxFragment(PassKey, NGLineBoxFragmentBuilder* builder); + ~NGPhysicalLineBoxFragment() { for (const NGLink& child : Children()) child.fragment->Release(); @@ -50,19 +54,22 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final // This may be different from the direction of the container box when // first-line style is used, or when 'unicode-bidi: plaintext' is used. TextDirection BaseDirection() const { - return static_cast<TextDirection>(base_direction_); + return static_cast<TextDirection>(base_or_resolved_direction_); } - // Compute baseline for the specified baseline type. - NGLineHeightMetrics BaselineMetrics(FontBaseline) const; + // Compute the baseline metrics for this linebox. + NGLineHeightMetrics BaselineMetrics() const; // Scrollable overflow. including contents, in the local coordinate. // |ScrollableOverflow| is not precomputed/cached because it cannot be // computed when LineBox is generated because it needs container dimensions // to resolve relative position of its children. - PhysicalRect ScrollableOverflow(const LayoutObject* container, - const ComputedStyle* container_style, - PhysicalSize container_physical_size) const; + PhysicalRect ScrollableOverflow(const NGPhysicalBoxFragment& container, + const ComputedStyle& container_style) const; + PhysicalRect ScrollableOverflowForLine(const NGPhysicalBoxFragment& container, + const ComputedStyle& container_style, + const NGFragmentItem& line, + const NGInlineCursor& cursor) const; // Whether the content soft-wraps to the next line. bool HasSoftWrapToNextLine() const; @@ -72,8 +79,6 @@ class CORE_EXPORT NGPhysicalLineBoxFragment final const LayoutObject* ContainerLayoutObject() const { return layout_object_; } private: - NGPhysicalLineBoxFragment(NGLineBoxFragmentBuilder* builder); - NGLineHeightMetrics metrics_; NGLink children_[]; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc index 834188b7add..a15a5429402 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc @@ -32,6 +32,7 @@ static_assert(sizeof(NGPhysicalTextFragment) == } // anonymous namespace NGPhysicalTextFragment::NGPhysicalTextFragment( + PassKey key, const NGPhysicalTextFragment& source, unsigned start_offset, unsigned end_offset, @@ -45,25 +46,29 @@ NGPhysicalTextFragment::NGPhysicalTextFragment( kFragmentText, source.TextType()), text_(source.text_), - start_offset_(start_offset), - end_offset_(end_offset), + text_offset_(start_offset, end_offset), shape_result_(std::move(shape_result)) { - DCHECK_GE(start_offset_, source.StartOffset()); - DCHECK_LE(end_offset_, source.EndOffset()); + DCHECK_GE(text_offset_.start, source.StartOffset()); + DCHECK_LE(text_offset_.end, source.EndOffset()); DCHECK(shape_result_ || IsFlowControl()) << *this; - is_generated_text_ = source.is_generated_text_; + base_or_resolved_direction_ = source.base_or_resolved_direction_; + is_generated_text_or_math_fraction_ = + source.is_generated_text_or_math_fraction_; ink_overflow_computed_ = false; + is_first_for_node_ = source.is_first_for_node_; } NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder) : NGPhysicalFragment(builder, kFragmentText, builder->text_type_), text_(builder->text_), - start_offset_(builder->start_offset_), - end_offset_(builder->end_offset_), + text_offset_({builder->start_offset_, builder->end_offset_}), shape_result_(std::move(builder->shape_result_)) { DCHECK(shape_result_ || IsFlowControl()) << *this; - is_generated_text_ = builder->IsGeneratedText(); + base_or_resolved_direction_ = + static_cast<unsigned>(builder->ResolvedDirection()); + is_generated_text_or_math_fraction_ = builder->IsGeneratedText(); ink_overflow_computed_ = false; + is_first_for_node_ = builder->is_first_for_node_; } LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset( @@ -214,8 +219,9 @@ scoped_refptr<const NGPhysicalTextFragment> NGPhysicalTextFragment::TrimText( DCHECK_LE(new_end_offset, EndOffset()); scoped_refptr<ShapeResultView> new_shape_result = ShapeResultView::Create( shape_result_.get(), new_start_offset, new_end_offset); - return base::AdoptRef(new NGPhysicalTextFragment( - *this, new_start_offset, new_end_offset, std::move(new_shape_result))); + return base::AdoptRef( + new NGPhysicalTextFragment(PassKey(), *this, new_start_offset, + new_end_offset, std::move(new_shape_result))); } unsigned NGPhysicalTextFragment::TextOffsetForPoint( @@ -263,10 +269,4 @@ UBiDiLevel NGPhysicalTextFragment::BidiLevel() const { return containing_item->BidiLevel(); } -TextDirection NGPhysicalTextFragment::ResolvedDirection() const { - if (TextShapeResult()) - return TextShapeResult()->Direction(); - return DirectionFromLevel(BidiLevel()); -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h index 7d0adc9bfce..dbd1bae97af 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h @@ -6,7 +6,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_PHYSICAL_TEXT_FRAGMENT_H_ #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h" #include "third_party/blink/renderer/core/layout/ng/ng_ink_overflow.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" #include "third_party/blink/renderer/platform/fonts/ng_text_fragment_paint_info.h" @@ -45,10 +45,18 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { NGPhysicalTextFragment(NGTextFragmentBuilder*); + using PassKey = util::PassKey<NGPhysicalTextFragment>; + // For use by TrimText only + NGPhysicalTextFragment(PassKey, + const NGPhysicalTextFragment& source, + unsigned start_offset, + unsigned end_offset, + scoped_refptr<const ShapeResultView> shape_result); + NGTextType TextType() const { return static_cast<NGTextType>(sub_type_); } // Returns true if the text is generated (from, e.g., list marker, // pseudo-element, ...) instead of from a DOM text node. - bool IsGeneratedText() const { return is_generated_text_; } + bool IsGeneratedText() const { return is_generated_text_or_math_fraction_; } // True if this is a forced line break. bool IsLineBreak() const { return TextType() == kForcedLineBreak; } // True if this is not for painting; i.e., a forced line break, a tabulation, @@ -63,18 +71,19 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { bool IsSymbolMarker() const { return TextType() == kSymbolMarker; } - unsigned TextLength() const { return end_offset_ - start_offset_; } - StringView Text() const { - return StringView(text_, start_offset_, TextLength()); - } const String& TextContent() const { return text_; } // ShapeResult may be nullptr if |IsFlowControl()|. const ShapeResultView* TextShapeResult() const { return shape_result_.get(); } // Start/end offset to the text of the block container. - unsigned StartOffset() const { return start_offset_; } - unsigned EndOffset() const { return end_offset_; } + const NGTextOffset& TextOffset() const { return text_offset_; } + unsigned StartOffset() const { return text_offset_.start; } + unsigned EndOffset() const { return text_offset_.end; } + unsigned TextLength() const { return text_offset_.Length(); } + StringView Text() const { + return StringView(text_, text_offset_.start, TextLength()); + } WritingMode GetWritingMode() const { return Style().GetWritingMode(); } bool IsHorizontal() const { @@ -113,7 +122,9 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { unsigned TextOffsetForPoint(const PhysicalOffset&) const; UBiDiLevel BidiLevel() const; - TextDirection ResolvedDirection() const; + TextDirection ResolvedDirection() const { + return static_cast<TextDirection>(base_or_resolved_direction_); + } // Compute line-relative coordinates for given offsets, this is not // flow-relative: @@ -123,12 +134,6 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { unsigned end_offset) const; private: - // For use by TrimText only - NGPhysicalTextFragment(const NGPhysicalTextFragment& source, - unsigned start_offset, - unsigned end_offset, - scoped_refptr<const ShapeResultView> shape_result); - LayoutUnit InlinePositionForOffset(unsigned offset, LayoutUnit (*round)(float), AdjustMidCluster) const; @@ -140,8 +145,7 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragment { const String text_; // Start and end offset of the parent block text. - const unsigned start_offset_; - const unsigned end_offset_; + const NGTextOffset text_offset_; const scoped_refptr<const ShapeResultView> shape_result_; // Fragments are immutable but allow certain expensive data, specifically ink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc index 7de65ceb791..ebb2a5abd83 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc @@ -43,6 +43,8 @@ class NGPhysicalTextFragmentTest : public NGLayoutTest { }; TEST_F(NGPhysicalTextFragmentTest, LocalRect) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> @@ -59,6 +61,8 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRect) { } TEST_F(NGPhysicalTextFragmentTest, LocalRectRTL) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> @@ -81,6 +85,8 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectRTL) { } TEST_F(NGPhysicalTextFragmentTest, LocalRectVLR) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> @@ -98,6 +104,8 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectVLR) { } TEST_F(NGPhysicalTextFragmentTest, LocalRectVRL) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> @@ -115,6 +123,8 @@ TEST_F(NGPhysicalTextFragmentTest, LocalRectVRL) { } TEST_F(NGPhysicalTextFragmentTest, NormalTextIsNotAnonymousText) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; SetBodyInnerHTML("<div id=div>text</div>"); auto text_fragments = CollectTextFragmentsInContainer("div"); @@ -125,6 +135,8 @@ TEST_F(NGPhysicalTextFragmentTest, NormalTextIsNotAnonymousText) { } TEST_F(NGPhysicalTextFragmentTest, FirstLetterIsNotAnonymousText) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; SetBodyInnerHTML( "<style>::first-letter {color:red}</style>" "<div id=div>text</div>"); @@ -139,6 +151,8 @@ TEST_F(NGPhysicalTextFragmentTest, FirstLetterIsNotAnonymousText) { } TEST_F(NGPhysicalTextFragmentTest, BeforeAndAfterAreAnonymousText) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; SetBodyInnerHTML( "<style>::before{content:'x'} ::after{content:'x'}</style>" "<div id=div>text</div>"); @@ -155,6 +169,8 @@ TEST_F(NGPhysicalTextFragmentTest, BeforeAndAfterAreAnonymousText) { } TEST_F(NGPhysicalTextFragmentTest, Ellipsis) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> @@ -191,6 +207,8 @@ TEST_F(NGPhysicalTextFragmentTest, Ellipsis) { } TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsGeneratedText) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; SetBodyInnerHTML( "<ol style='list-style-position:inside'>" "<li id=list>text</li>" @@ -206,6 +224,8 @@ TEST_F(NGPhysicalTextFragmentTest, ListMarkerIsGeneratedText) { } TEST_F(NGPhysicalTextFragmentTest, SoftHyphen) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> @@ -234,6 +254,8 @@ TEST_F(NGPhysicalTextFragmentTest, SoftHyphen) { } TEST_F(NGPhysicalTextFragmentTest, QuotationMarksAreAnonymousText) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; SetBodyInnerHTML("<div id=div><q>text</q></div>"); auto text_fragments = CollectTextFragmentsInContainer("div"); @@ -248,6 +270,8 @@ TEST_F(NGPhysicalTextFragmentTest, QuotationMarksAreAnonymousText) { } TEST_F(NGPhysicalTextFragmentTest, TextOffsetForPointForTabulation) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> @@ -270,6 +294,8 @@ TEST_F(NGPhysicalTextFragmentTest, TextOffsetForPointForTabulation) { } TEST_F(NGPhysicalTextFragmentTest, TextOffsetForPointForTabulationRtl) { + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) + return; LoadAhem(); SetBodyInnerHTML(R"HTML( <style> diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h deleted file mode 100644 index 0dbc9f84a77..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_TEXT_END_EFFECT_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_TEXT_END_EFFECT_H_ - -namespace blink { - -// Effects at the end of text fragments. -enum class NGTextEndEffect { - kNone, - kHyphen, - - // When adding new values, ensure NGPhysicalTextFragment has enough bits. -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_TEXT_END_EFFECT_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc index ea7efc7acb4..0b85af665ca 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.cc @@ -36,6 +36,7 @@ void NGTextFragmentBuilder::SetItem( text_ = items_data.text_content; start_offset_ = item_result->start_offset; end_offset_ = item_result->end_offset; + resolved_direction_ = item_result->item->Direction(); SetStyle(item_result->item->Style(), item_result->item->StyleVariant()); size_ = {item_result->inline_size, line_height}; shape_result_ = std::move(item_result->shape_result); @@ -56,6 +57,7 @@ void NGTextFragmentBuilder::SetText( text_ = text; start_offset_ = shape_result->StartIndex(); end_offset_ = shape_result->EndIndex(); + resolved_direction_ = shape_result->Direction(); SetStyle(style, is_ellipsis_style ? NGStyleVariant::kEllipsis : NGStyleVariant::kStandard); size_ = {shape_result->SnappedWidth(), diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h index 422fcd3aa3e..3cb38c1af32 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h @@ -8,7 +8,6 @@ #include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_end_effect.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" @@ -27,6 +26,8 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGFragmentBuilder { NGTextFragmentBuilder(const NGPhysicalTextFragment& fragment); + TextDirection ResolvedDirection() const { return resolved_direction_; } + // NOTE: Takes ownership of the shape result within the item result. void SetItem(NGPhysicalTextFragment::NGTextType, const NGInlineItemsData&, @@ -56,6 +57,9 @@ class CORE_EXPORT NGTextFragmentBuilder final : public NGFragmentBuilder { NGPhysicalTextFragment::NGTextType text_type_ = NGPhysicalTextFragment::kNormalText; + // Set from |NGInlineItem| by |SetItem()|. + TextDirection resolved_direction_ = TextDirection::kLtr; + friend class NGPhysicalTextFragment; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h index e8132013463..0d6c7251095 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_text_offset.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_TEXT_OFFSET_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_TEXT_OFFSET_H_ +#include "base/logging.h" #include "third_party/blink/renderer/core/core_export.h" namespace blink { @@ -13,10 +14,13 @@ namespace blink { struct CORE_EXPORT NGTextOffset { NGTextOffset() = default; NGTextOffset(unsigned start, unsigned end) : start(start), end(end) { - DCHECK_GE(end, start); + AssertValid(); } - unsigned Length() const { return end - start; } + unsigned Length() const { + AssertValid(); + return end - start; + } void AssertValid() const { DCHECK_GE(end, start); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc index eac09620636..ef990b7ce3f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc @@ -72,21 +72,6 @@ void LayoutNGBlockFlowMixin<Base>::ClearNGInlineNodeData() { ng_inline_node_data_.reset(); } -// The current fragment from the last layout cycle for this box. -// When pre-NG layout calls functions of this block flow, fragment and/or -// LayoutResult are required to compute the result. -// TODO(kojii): Use the cached result for now, we may need to reconsider as the -// cache evolves. -template <typename Base> -const NGPhysicalBoxFragment* LayoutNGBlockFlowMixin<Base>::CurrentFragment() - const { - const NGLayoutResult* cached_layout_result = Base::GetCachedLayoutResult(); - if (!cached_layout_result) - return nullptr; - - return &To<NGPhysicalBoxFragment>(cached_layout_result->PhysicalFragment()); -} - template <typename Base> void LayoutNGBlockFlowMixin<Base>::AddLayoutOverflowFromChildren() { if (Base::LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)) @@ -104,83 +89,15 @@ void LayoutNGBlockFlowMixin<Base>::AddLayoutOverflowFromChildren() { template <typename Base> void LayoutNGBlockFlowMixin<Base>::AddScrollingOverflowFromChildren() { - const NGPhysicalBoxFragment* physical_fragment = CurrentFragment(); DCHECK(physical_fragment); - if (physical_fragment->Children().empty()) - return; - - const ComputedStyle& style = Base::StyleRef(); - const WritingMode writing_mode = style.GetWritingMode(); - const TextDirection direction = style.Direction(); - const LayoutUnit border_inline_start = LayoutUnit(style.BorderStartWidth()); - const LayoutUnit border_block_start = LayoutUnit(style.BorderBeforeWidth()); - const PhysicalSize& size = physical_fragment->Size(); - - // End and under padding are added to scroll overflow of inline children. - // https://github.com/w3c/csswg-drafts/issues/129 - base::Optional<NGPhysicalBoxStrut> padding_strut; - if (Base::HasOverflowClip()) { - padding_strut = NGBoxStrut(LayoutUnit(), Base::PaddingEnd(), LayoutUnit(), - Base::PaddingUnder()) - .ConvertToPhysical(writing_mode, direction); - } - - // Rectangles not reachable by scroll should not be added to overflow. - auto IsRectReachableByScroll = [&border_inline_start, &border_block_start, - &writing_mode, &direction, - &size](const PhysicalRect& rect) { - LogicalOffset rect_logical_end = - rect.offset.ConvertToLogical(writing_mode, direction, size, rect.size) + - rect.size.ConvertToLogical(writing_mode); - return (rect_logical_end.inline_offset > border_inline_start && - rect_logical_end.block_offset > border_block_start); - }; - - bool children_inline = Base::ChildrenInline(); - PhysicalRect children_overflow; - base::Optional<PhysicalRect> lineboxes_enclosing_rect; - // Only add overflow for fragments NG has not reflected into Legacy. - // These fragments are: - // - inline fragments, - // - out of flow fragments whose css container is inline box. - // TODO(layout-dev) Transforms also need to be applied to compute overflow - // correctly. NG is not yet transform-aware. crbug.com/855965 - for (const auto& child : physical_fragment->Children()) { - PhysicalRect child_scrollable_overflow; - if (child->IsFloatingOrOutOfFlowPositioned()) { - child_scrollable_overflow = child->ScrollableOverflowForPropagation(this); - child_scrollable_overflow.offset += - ComputeRelativeOffset(child->Style(), writing_mode, direction, size); - } else if (children_inline && child->IsLineBox()) { - DCHECK(child->IsLineBox()); - child_scrollable_overflow = - To<NGPhysicalLineBoxFragment>(*child).ScrollableOverflow(this, &style, - size); - if (padding_strut) { - PhysicalRect linebox_rect(child.Offset(), child->Size()); - if (lineboxes_enclosing_rect) - lineboxes_enclosing_rect->Unite(linebox_rect); - else - lineboxes_enclosing_rect = linebox_rect; - } - } else { - continue; - } - child_scrollable_overflow.offset += child.Offset(); - // Do not add overflow if fragment is not reachable by scrolling. - if (IsRectReachableByScroll(child_scrollable_overflow)) - children_overflow.Unite(child_scrollable_overflow); - } - if (lineboxes_enclosing_rect) { - lineboxes_enclosing_rect->Expand(*padding_strut); - if (IsRectReachableByScroll(*lineboxes_enclosing_rect)) - children_overflow.Unite(*lineboxes_enclosing_rect); - } + PhysicalRect children_overflow = + physical_fragment->ScrollableOverflowFromChildren(); // LayoutOverflow takes flipped blocks coordinates, adjust as needed. + const ComputedStyle& style = physical_fragment->Style(); LayoutRect children_flipped_overflow = - children_overflow.ToLayoutFlippedRect(style, size); + children_overflow.ToLayoutFlippedRect(style, physical_fragment->Size()); Base::AddLayoutOverflow(children_flipped_overflow); } @@ -193,60 +110,44 @@ void LayoutNGBlockFlowMixin<Base>::AddOutlineRects( To<NGPhysicalBoxFragment>(PaintFragment()->PhysicalFragment()) .AddSelfOutlineRects(additional_offset, include_block_overflows, &rects); - } else { - Base::AddOutlineRects(rects, additional_offset, include_block_overflows); + return; } -} - -template <typename Base> -bool LayoutNGBlockFlowMixin< - Base>::PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const { - // LayoutNGBlockFlowMixin is in charge of paint invalidation of the first - // line. - if (PaintFragment()) - return false; - if (Base::StyleRef().HasColumnRule()) - return false; + if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { + if (fragment->HasItems()) { + fragment->AddSelfOutlineRects(additional_offset, include_block_overflows, + &rects); + return; + } + } - return Base::PaintedOutputOfObjectHasNoEffectRegardlessOfSize(); + Base::AddOutlineRects(rects, additional_offset, include_block_overflows); } // Retrieve NGBaseline from the current fragment. template <typename Base> -base::Optional<LayoutUnit> LayoutNGBlockFlowMixin<Base>::FragmentBaseline( - NGBaselineAlgorithmType type) const { +base::Optional<LayoutUnit> LayoutNGBlockFlowMixin<Base>::FragmentBaseline() + const { if (Base::ShouldApplyLayoutContainment()) return base::nullopt; - if (const NGPhysicalFragment* physical_fragment = CurrentFragment()) { - FontBaseline baseline_type = Base::StyleRef().GetFontBaseline(); - return To<NGPhysicalBoxFragment>(physical_fragment) - ->Baseline({type, baseline_type}); - } + if (const NGPhysicalFragment* physical_fragment = CurrentFragment()) + return To<NGPhysicalBoxFragment>(physical_fragment)->Baseline(); return base::nullopt; } template <typename Base> LayoutUnit LayoutNGBlockFlowMixin<Base>::FirstLineBoxBaseline() const { - if (Base::ChildrenInline()) { - if (base::Optional<LayoutUnit> offset = - FragmentBaseline(NGBaselineAlgorithmType::kFirstLine)) { - return *offset; - } - } + if (base::Optional<LayoutUnit> offset = FragmentBaseline()) + return *offset; return Base::FirstLineBoxBaseline(); } template <typename Base> LayoutUnit LayoutNGBlockFlowMixin<Base>::InlineBlockBaseline( LineDirectionMode line_direction) const { - if (Base::ChildrenInline()) { - if (base::Optional<LayoutUnit> offset = - FragmentBaseline(NGBaselineAlgorithmType::kAtomicInline)) { - return *offset; - } - } + if (base::Optional<LayoutUnit> offset = FragmentBaseline()) + return *offset; return Base::InlineBlockBaseline(line_direction); } @@ -300,11 +201,9 @@ void LayoutNGBlockFlowMixin<Base>::Paint(const PaintInfo& paint_info) const { return; } - if (RuntimeEnabledFeatures::LayoutNGFragmentPaintEnabled()) { - if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { - NGBoxFragmentPainter(*fragment).Paint(paint_info); - return; - } + if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { + NGBoxFragmentPainter(*fragment).Paint(paint_info); + return; } Base::Paint(paint_info); @@ -317,7 +216,7 @@ bool LayoutNGBlockFlowMixin<Base>::NodeAtPoint( const PhysicalOffset& accumulated_offset, HitTestAction action) { if (const NGPaintFragment* paint_fragment = PaintFragment()) { - if (!this->IsEffectiveRootScroller()) { + if (!Base::IsEffectiveRootScroller()) { // Check if we need to do anything at all. // If we have clipping, then we can't have any spillout. PhysicalRect overflow_box = Base::HasOverflowClip() @@ -338,7 +237,11 @@ bool LayoutNGBlockFlowMixin<Base>::NodeAtPoint( if (UNLIKELY(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled())) { if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { - if (fragment->HasItems()) { + if (fragment->HasItems() || + // Check descendants of this fragment because floats may be in the + // |NGFragmentItems| of the descendants. + (action == kHitTestFloat && + fragment->HasFloatingDescendantsForPaint())) { return NGBoxFragmentPainter(*fragment).NodeAtPoint( result, hit_test_location, accumulated_offset, action); } @@ -370,15 +273,18 @@ PositionWithAffinity LayoutNGBlockFlowMixin<Base>::PositionForPoint( if (const PositionWithAffinity position = paint_fragment->PositionForPoint(point_in_contents)) return position; - } else if (const NGFragmentItems* items = Base::FragmentItems()) { - // The given offset is relative to this |LayoutBlockFlow|. Convert to the - // contents offset. - PhysicalOffset point_in_contents = point; - Base::OffsetForContents(point_in_contents); - NGInlineCursor cursor(*items); - if (const PositionWithAffinity position = - cursor.PositionForPoint(point_in_contents)) - return position; + } else if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { + if (const NGFragmentItems* items = fragment->Items()) { + // The given offset is relative to this |LayoutBlockFlow|. Convert to the + // contents offset. + PhysicalOffset point_in_contents = point; + Base::OffsetForContents(point_in_contents); + NGInlineCursor cursor(*items); + if (const PositionWithAffinity position = + cursor.PositionForPointInInlineFormattingContext( + point_in_contents, *fragment)) + return position; + } } return Base::CreatePositionWithAffinity(0); @@ -402,26 +308,16 @@ void LayoutNGBlockFlowMixin<Base>::UpdateNGBlockLayout() { LayoutAnalyzer::BlockScope analyzer(*this); if (Base::IsOutOfFlowPositioned()) { - this->UpdateOutOfFlowBlockLayout(); + LayoutNGMixin<Base>::UpdateOutOfFlowBlockLayout(); return; } - NGConstraintSpace constraint_space = - NGConstraintSpace::CreateFromLayoutObject( - *this, !Base::View()->GetLayoutState()->Next() /* is_layout_root */); - - scoped_refptr<const NGLayoutResult> result = - NGBlockNode(this).Layout(constraint_space); - - for (const auto& descendant : - result->PhysicalFragment().OutOfFlowPositionedDescendants()) - descendant.node.UseLegacyOutOfFlowPositioning(); - this->UpdateMargins(constraint_space); + LayoutNGMixin<Base>::UpdateInFlowBlockLayout(); + UpdateMargins(); } template <typename Base> -void LayoutNGBlockFlowMixin<Base>::UpdateMargins( - const NGConstraintSpace& space) { +void LayoutNGBlockFlowMixin<Base>::UpdateMargins() { const LayoutBlock* containing_block = Base::ContainingBlock(); if (!containing_block || !containing_block->IsLayoutBlockFlow()) return; @@ -434,13 +330,13 @@ void LayoutNGBlockFlowMixin<Base>::UpdateMargins( const ComputedStyle& cb_style = containing_block->StyleRef(); const auto writing_mode = cb_style.GetWritingMode(); const auto direction = cb_style.Direction(); - LayoutUnit percentage_resolution_size = - space.PercentageResolutionInlineSizeForParentWritingMode(); - NGBoxStrut margins = ComputePhysicalMargins(style, percentage_resolution_size) + LayoutUnit available_logical_width = + LayoutBoxUtils::AvailableLogicalWidth(*this, containing_block); + NGBoxStrut margins = ComputePhysicalMargins(style, available_logical_width) .ConvertToLogical(writing_mode, direction); - ResolveInlineMargins(style, cb_style, space.AvailableSize().inline_size, + ResolveInlineMargins(style, cb_style, available_logical_width, Base::LogicalWidth(), &margins); - this->SetMargin(margins.ConvertToPhysical(writing_mode, direction)); + Base::SetMargin(margins.ConvertToPhysical(writing_mode, direction)); } template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutBlockFlow>; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h index 6b5d874ce5d..2e808c039bb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h @@ -60,20 +60,18 @@ class LayoutNGBlockFlowMixin : public LayoutNGMixin<Base> { void SetPaintFragment(const NGBlockBreakToken*, scoped_refptr<const NGPhysicalFragment>) final; + using LayoutNGMixin<Base>::CurrentFragment; + protected: void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; - const NGPhysicalBoxFragment* CurrentFragment() const final; - void AddLayoutOverflowFromChildren() final; void AddOutlineRects(Vector<PhysicalRect>&, const PhysicalOffset& additional_offset, NGOutlineType) const final; - bool PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const final; - - base::Optional<LayoutUnit> FragmentBaseline(NGBaselineAlgorithmType) const; + base::Optional<LayoutUnit> FragmentBaseline() const; void DirtyLinesFromChangedChild(LayoutObject* child, MarkingBehavior marking_behavior) final; @@ -89,7 +87,7 @@ class LayoutNGBlockFlowMixin : public LayoutNGMixin<Base> { private: void AddScrollingOverflowFromChildren(); - void UpdateMargins(const NGConstraintSpace& space); + void UpdateMargins(); }; // If you edit these export templates, also update templates in diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc index a39b2b4dee0..907d45bb278 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc @@ -73,17 +73,4 @@ bool LayoutNGFieldset::IsOfType(LayoutObjectType type) const { return type == kLayoutObjectNGFieldset || LayoutNGBlockFlow::IsOfType(type); } -void LayoutNGFieldset::Paint(const PaintInfo& paint_info) const { - // TODO(crbug.com/988015): This override should not be needed when painting - // fragment is enabled in parent classes. - if (!RuntimeEnabledFeatures::LayoutNGFragmentPaintEnabled()) { - if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { - NGBoxFragmentPainter(*fragment, PaintFragment()).Paint(paint_info); - return; - } - } - - LayoutNGBlockFlow::Paint(paint_info); -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h index 4076cdbc70f..5178d12f26d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h @@ -21,14 +21,10 @@ class CORE_EXPORT LayoutNGFieldset final : public LayoutNGBlockFlow { bool CreatesNewFormattingContext() const final { return true; } - void Paint(const PaintInfo&) const final; - protected: bool IsOfType(LayoutObjectType) const override; }; -DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGFieldset, IsLayoutNGFieldset()); - } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_FIELDSET_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc index dddadee04b5..a74fdfe086d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.cc @@ -17,6 +17,21 @@ namespace blink { LayoutNGFlexibleBox::LayoutNGFlexibleBox(Element* element) : LayoutNGMixin<LayoutBlock>(element) {} +bool LayoutNGFlexibleBox::HasTopOverflow() const { + if (IsHorizontalWritingMode()) + return StyleRef().ResolvedIsColumnReverseFlexDirection(); + return StyleRef().IsLeftToRightDirection() == + StyleRef().ResolvedIsRowReverseFlexDirection(); +} + +bool LayoutNGFlexibleBox::HasLeftOverflow() const { + if (IsHorizontalWritingMode()) { + return StyleRef().IsLeftToRightDirection() == + StyleRef().ResolvedIsRowReverseFlexDirection(); + } + return StyleRef().ResolvedIsColumnReverseFlexDirection(); +} + void LayoutNGFlexibleBox::UpdateBlockLayout(bool relayout_children) { LayoutAnalyzer::BlockScope analyzer(*this); @@ -25,16 +40,34 @@ void LayoutNGFlexibleBox::UpdateBlockLayout(bool relayout_children) { return; } - NGConstraintSpace constraint_space = - NGConstraintSpace::CreateFromLayoutObject( - *this, !View()->GetLayoutState()->Next() /* is_layout_root */); + UpdateInFlowBlockLayout(); +} + +namespace { + +void MergeAnonymousFlexItems(LayoutObject* remove_child) { + // When we remove a flex item, and the previous and next siblings of the item + // are text nodes wrapped in anonymous flex items, the adjacent text nodes + // need to be merged into the same flex item. + LayoutObject* prev = remove_child->PreviousSibling(); + if (!prev || !prev->IsAnonymousBlock()) + return; + LayoutObject* next = remove_child->NextSibling(); + if (!next || !next->IsAnonymousBlock()) + return; + ToLayoutBoxModelObject(next)->MoveAllChildrenTo(ToLayoutBoxModelObject(prev)); + To<LayoutBlockFlow>(next)->DeleteLineBoxTree(); + next->Destroy(); +} + +} // namespace - scoped_refptr<const NGLayoutResult> result = - NGBlockNode(this).Layout(constraint_space); +void LayoutNGFlexibleBox::RemoveChild(LayoutObject* child) { + if (!DocumentBeingDestroyed() && + !StyleRef().IsDeprecatedFlexboxUsingFlexLayout()) + MergeAnonymousFlexItems(child); - for (const auto& descendant : - result->PhysicalFragment().OutOfFlowPositionedDescendants()) - descendant.node.UseLegacyOutOfFlowPositioning(); + LayoutBlock::RemoveChild(child); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h index c4c0fcfd227..f1a361e1b56 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h @@ -15,6 +15,9 @@ class CORE_EXPORT LayoutNGFlexibleBox : public LayoutNGMixin<LayoutBlock> { public: explicit LayoutNGFlexibleBox(Element*); + bool HasTopOverflow() const override; + bool HasLeftOverflow() const override; + void UpdateBlockLayout(bool relayout_children) override; bool IsFlexibleBoxIncludingDeprecatedAndNG() const final { return true; } @@ -22,6 +25,8 @@ class CORE_EXPORT LayoutNGFlexibleBox : public LayoutNGMixin<LayoutBlock> { const char* GetName() const override { return "LayoutNGFlexibleBox"; } protected: + void RemoveChild(LayoutObject*) override; + bool IsOfType(LayoutObjectType type) const override { return type == kLayoutObjectNGFlexibleBox || LayoutNGMixin<LayoutBlock>::IsOfType(type); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc index bc31a16375c..89ad70133c1 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc @@ -11,9 +11,11 @@ #include "third_party/blink/renderer/core/layout/ng/layout_box_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" +#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" +#include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" namespace blink { @@ -30,27 +32,65 @@ template <typename Base> LayoutNGMixin<Base>::~LayoutNGMixin() = default; template <typename Base> +void LayoutNGMixin<Base>::Paint(const PaintInfo& paint_info) const { + // Avoid painting dirty objects because descendants maybe already destroyed. + if (UNLIKELY(Base::NeedsLayout() && + !Base::LayoutBlockedByDisplayLock( + DisplayLockLifecycleTarget::kChildren))) { + NOTREACHED(); + return; + } + + if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) + NGBoxFragmentPainter(*fragment).Paint(paint_info); +} + +template <typename Base> +bool LayoutNGMixin<Base>::NodeAtPoint(HitTestResult& result, + const HitTestLocation& hit_test_location, + const PhysicalOffset& accumulated_offset, + HitTestAction action) { + if (const NGPhysicalBoxFragment* fragment = CurrentFragment()) { + DCHECK_EQ(Base::PhysicalFragmentCount(), 1u); + return NGBoxFragmentPainter(*fragment).NodeAtPoint( + result, hit_test_location, accumulated_offset, action); + } + + return false; +} + +// The current fragment from the last layout cycle for this box. +// When pre-NG layout calls functions of this block flow, fragment and/or +// LayoutResult are required to compute the result. +// TODO(kojii): Use the cached result for now, we may need to reconsider as the +// cache evolves. +template <typename Base> +const NGPhysicalBoxFragment* LayoutNGMixin<Base>::CurrentFragment() const { + const NGLayoutResult* cached_layout_result = Base::GetCachedLayoutResult(); + if (!cached_layout_result) + return nullptr; + + return &To<NGPhysicalBoxFragment>(cached_layout_result->PhysicalFragment()); +} + +template <typename Base> bool LayoutNGMixin<Base>::IsOfType(LayoutObject::LayoutObjectType type) const { return type == LayoutObject::kLayoutObjectNGMixin || Base::IsOfType(type); } template <typename Base> -void LayoutNGMixin<Base>::ComputeIntrinsicLogicalWidths( - LayoutUnit& min_logical_width, - LayoutUnit& max_logical_width) const { +MinMaxSizes LayoutNGMixin<Base>::ComputeIntrinsicLogicalWidths() const { NGBlockNode node(const_cast<LayoutNGMixin<Base>*>(this)); - if (!node.CanUseNewLayout()) { - Base::ComputeIntrinsicLogicalWidths(min_logical_width, max_logical_width); - return; - } + if (!node.CanUseNewLayout()) + return Base::ComputeIntrinsicLogicalWidths(); LayoutUnit available_logical_height = LayoutBoxUtils::AvailableLogicalHeight(*this, Base::ContainingBlock()); - MinMaxSizeInput input(available_logical_height); - // This function returns content-box plus scrollbar. - input.size_type = NGMinMaxSizeType::kContentBoxSize; - MinMaxSize sizes = - node.ComputeMinMaxSize(node.Style().GetWritingMode(), input); + + NGConstraintSpace space = ConstraintSpaceForMinMaxSizes(); + MinMaxSizes sizes = node.ComputeMinMaxSizes( + node.Style().GetWritingMode(), MinMaxSizesInput(available_logical_height), + &space); if (Base::IsTableCell()) { // If a table cell, or the column that it belongs to, has a specified fixed @@ -61,14 +101,34 @@ void LayoutNGMixin<Base>::ComputeIntrinsicLogicalWidths( Length table_cell_width = cell->StyleOrColLogicalWidth(); if (table_cell_width.IsFixed() && table_cell_width.Value() > 0) { sizes.max_size = std::max(sizes.min_size, - Base::AdjustContentBoxLogicalWidthForBoxSizing( + Base::AdjustBorderBoxLogicalWidthForBoxSizing( LayoutUnit(table_cell_width.Value()))); } } - sizes += LayoutUnit(Base::ScrollbarLogicalWidth()); - min_logical_width = sizes.min_size; - max_logical_width = sizes.max_size; + return sizes; +} + +template <typename Base> +NGConstraintSpace LayoutNGMixin<Base>::ConstraintSpaceForMinMaxSizes() const { + const ComputedStyle& style = Base::StyleRef(); + const WritingMode writing_mode = style.GetWritingMode(); + + NGConstraintSpaceBuilder builder(writing_mode, writing_mode, + /* is_new_fc */ true); + builder.SetTextDirection(style.Direction()); + builder.SetAvailableSize( + {Base::ContainingBlockLogicalWidthForContent(), kIndefiniteSize}); + + // Table cells borders may be collapsed, we can't calculate these directly + // from the style. + if (Base::IsTableCell()) { + builder.SetIsTableCell(true); + builder.SetTableCellBorders({Base::BorderStart(), Base::BorderEnd(), + Base::BorderBefore(), Base::BorderAfter()}); + } + + return builder.ToConstraintSpace(); } template <typename Base> @@ -79,8 +139,7 @@ void LayoutNGMixin<Base>::UpdateOutOfFlowBlockLayout() { : Base::ContainingBlock(); const ComputedStyle* container_style = container->Style(); NGConstraintSpace constraint_space = - NGConstraintSpace::CreateFromLayoutObject(*this, - false /* is_layout_root */); + NGConstraintSpace::CreateFromLayoutObject(*this); // As this is part of the Legacy->NG bridge, the container_builder is used // for indicating the resolved size of the OOF-positioned containing-block @@ -97,7 +156,7 @@ void LayoutNGMixin<Base>::UpdateOutOfFlowBlockLayout() { container_node.CreatesNewFormattingContext()); NGFragmentGeometry fragment_geometry; - fragment_geometry.border = ComputeBorders(constraint_space, container_node); + fragment_geometry.border = ComputeBorders(constraint_space, *container_style); fragment_geometry.scrollbar = ComputeScrollbars(constraint_space, container_node); fragment_geometry.padding = @@ -141,8 +200,9 @@ void LayoutNGMixin<Base>::UpdateOutOfFlowBlockLayout() { NGBlockNode(this), static_position, ToLayoutInlineOrNull(css_container)); base::Optional<LogicalSize> initial_containing_block_fixed_size; - if (container->IsLayoutView() && !Base::GetDocument().Printing()) { - if (LocalFrameView* frame_view = ToLayoutView(container)->GetFrameView()) { + auto* layout_view = DynamicTo<LayoutView>(container); + if (layout_view && !Base::GetDocument().Printing()) { + if (LocalFrameView* frame_view = layout_view->GetFrameView()) { IntSize size = frame_view->LayoutViewport()->ExcludeScrollbars(frame_view->Size()); PhysicalSize physical_size(size); @@ -190,6 +250,29 @@ void LayoutNGMixin<Base>::UpdateOutOfFlowBlockLayout() { Base::SetIsLegacyInitiatedOutOfFlowLayout(true); } +template <typename Base> +scoped_refptr<const NGLayoutResult> +LayoutNGMixin<Base>::UpdateInFlowBlockLayout() { + const auto* previous_result = Base::GetCachedLayoutResult(); + bool is_layout_root = !Base::View()->GetLayoutState()->Next(); + + // If we are a layout root, use the previous space if available. This will + // include any stretched sizes if applicable. + NGConstraintSpace constraint_space = + is_layout_root && previous_result + ? previous_result->GetConstraintSpaceForCaching() + : NGConstraintSpace::CreateFromLayoutObject(*this); + + scoped_refptr<const NGLayoutResult> result = + NGBlockNode(this).Layout(constraint_space); + + for (const auto& descendant : + result->PhysicalFragment().OutOfFlowPositionedDescendants()) + descendant.node.UseLegacyOutOfFlowPositioning(); + + return result; +} + template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutBlock>; template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutBlockFlow>; template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutProgress>; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h index e75f321437e..a6194724268 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h @@ -23,16 +23,25 @@ class LayoutNGMixin : public Base { explicit LayoutNGMixin(Element* element); ~LayoutNGMixin() override; + void Paint(const PaintInfo&) const override; + + bool NodeAtPoint(HitTestResult&, + const HitTestLocation&, + const PhysicalOffset& accumulated_offset, + HitTestAction) override; + bool IsLayoutNGObject() const final { return true; } + const NGPhysicalBoxFragment* CurrentFragment() const final; + protected: bool IsOfType(LayoutObject::LayoutObjectType) const override; - void ComputeIntrinsicLogicalWidths( - LayoutUnit& min_logical_width, - LayoutUnit& max_logical_width) const override; + MinMaxSizes ComputeIntrinsicLogicalWidths() const override; + NGConstraintSpace ConstraintSpaceForMinMaxSizes() const; void UpdateOutOfFlowBlockLayout(); + scoped_refptr<const NGLayoutResult> UpdateInFlowBlockLayout(); }; extern template class CORE_EXTERN_TEMPLATE_EXPORT LayoutNGMixin<LayoutBlock>; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h index 7ad4c0c18d7..86d6850fdd5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h @@ -25,8 +25,6 @@ class CORE_EXPORT LayoutNGProgress bool IsOfType(LayoutObjectType type) const override; }; -DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGProgress, IsLayoutNGProgress()); - } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_PROGRESS_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc index 5d5418d2916..2a611c31d3a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.cc @@ -57,23 +57,9 @@ void LayoutNGTableCaption::UpdateBlockLayout(bool relayout_children) { DCHECK(!IsOutOfFlowPositioned()) << "Out of flow captions are blockified."; - NGConstraintSpace constraint_space = - NGConstraintSpace::CreateFromLayoutObject( - *this, !View()->GetLayoutState()->Next() /* is_layout_root */); - - scoped_refptr<const NGLayoutResult> result = - NGBlockNode(this).Layout(constraint_space); - - CalculateAndSetMargins(constraint_space, result->PhysicalFragment()); - - // Tell legacy layout there were abspos descendents we couldn't place. We know - // we have to pass up to legacy here because this method is legacy's entry - // point to LayoutNG. If our parent were LayoutNG, it wouldn't have called - // UpdateBlockLayout, it would have packaged this LayoutObject into - // NGBlockNode and called Layout on that. - for (const auto& descendant : - result->PhysicalFragment().OutOfFlowPositionedDescendants()) - descendant.node.UseLegacyOutOfFlowPositioning(); + scoped_refptr<const NGLayoutResult> result = UpdateInFlowBlockLayout(); + CalculateAndSetMargins(result->GetConstraintSpaceForCaching(), + result->PhysicalFragment()); // The parent table sometimes changes the caption's position after laying it // out. So there's no point in setting the fragment's offset here; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc index da93f774221..87eec6dff43 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.cc @@ -22,17 +22,7 @@ void LayoutNGTableCell::UpdateBlockLayout(bool relayout_children) { LayoutAnalyzer::BlockScope analyzer(*this); SetOverrideLogicalWidth(LogicalWidth()); - - NGConstraintSpace constraint_space = - NGConstraintSpace::CreateFromLayoutObject( - *this, !View()->GetLayoutState()->Next() /* is_layout_root */); - - scoped_refptr<const NGLayoutResult> result = - NGBlockNode(this).Layout(constraint_space); - - for (const auto& descendant : - result->PhysicalFragment().OutOfFlowPositionedDescendants()) - descendant.node.UseLegacyOutOfFlowPositioning(); + UpdateInFlowBlockLayout(); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/README.md b/chromium/third_party/blink/renderer/core/layout/ng/list/README.md index 9dbe472eaac..b8380c16919 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/README.md +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/README.md @@ -41,7 +41,7 @@ When the content is inline level and therefore generates line boxes: generates a box tree of: - LayoutNGListItem - - LayoutNGListMarker + - LayoutNGOutsideListMarker - LayoutText (1.) - LayoutText (sample text) @@ -56,7 +56,7 @@ When the content is block level: ``` - LayoutNGListItem - - LayoutNGListMarker + - LayoutNGOutsideListMarker - LayoutText (1.) - LayoutNGBlockFlow (div) - LayoutText (sample text) @@ -74,7 +74,7 @@ When the content is mixed: ``` - LayoutNGListItem - - LayoutNGListMarker + - LayoutNGOutsideListMarker - LayoutText (1.) - LayoutNGBlockFlow (anonymous) - LayoutText (inline text) @@ -134,7 +134,8 @@ and still easy to implement across implementations. [marker positioning]: https://drafts.csswg.org/css-lists-3/#positioning [LayoutNGListItem]: layout_ng_list_item.h -[LayoutNGListMarker]: layout_ng_list_marker.h +[LayoutNGInsideListMarker]: layout_ng_inside_list_marker.h +[LayoutNGOutsideListMarker]: layout_ng_outside_list_marker.h [NGBlockLayoutAlgorithm]: ../ng_block_layout_algorithm.h [NGInlineItem]: ../inline/ng_inline_item.h [NGInlineLayoutAlgorithm]: ../inline/ng_inline_layout_algorithm.h diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.cc index 5cdeb8d9124..626ba03e22b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.cc @@ -5,20 +5,12 @@ #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h" #include "third_party/blink/renderer/core/layout/layout_text.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" namespace blink { LayoutNGInsideListMarker::LayoutNGInsideListMarker(Element* element) : LayoutInline(element) {} -LayoutNGInsideListMarker* LayoutNGInsideListMarker::CreateAnonymous( - Document* document) { - LayoutNGInsideListMarker* object = new LayoutNGInsideListMarker(nullptr); - object->SetDocumentForAnonymous(document); - return object; -} - bool LayoutNGInsideListMarker::IsOfType(LayoutObjectType type) const { return type == kLayoutObjectNGInsideListMarker || LayoutInline::IsOfType(type); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h index 0ce5f756adb..5702a72422c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h @@ -7,23 +7,24 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/layout_inline.h" +#include "third_party/blink/renderer/core/layout/ng/list/list_marker.h" namespace blink { -class Document; - // A LayoutObject subclass for inside-positioned list markers in LayoutNG. class CORE_EXPORT LayoutNGInsideListMarker final : public LayoutInline { public: explicit LayoutNGInsideListMarker(Element*); - static LayoutNGInsideListMarker* CreateAnonymous(Document*); const char* GetName() const override { return "LayoutNGInsideListMarker"; } + const ListMarker& Marker() const { return list_marker_; } + ListMarker& Marker() { return list_marker_; } + #if DCHECK_IS_ON() void AddChild(LayoutObject* new_child, LayoutObject* before_child) override { - // Anonymous list marker should have at most one child. - DCHECK(GetNode() || !FirstChild()); + // List markers with 'content: normal' should have at most one child. + DCHECK(!StyleRef().ContentBehavesAsNormal() || !FirstChild()); LayoutInline::AddChild(new_child, before_child); } #endif @@ -31,6 +32,8 @@ class CORE_EXPORT LayoutNGInsideListMarker final : public LayoutInline { private: bool IsOfType(LayoutObjectType) const override; PositionWithAffinity PositionForPoint(const PhysicalOffset&) const override; + + ListMarker list_marker_; }; DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGInsideListMarker, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc index 826c41c3da0..c3a6d3f0b9f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc @@ -4,21 +4,12 @@ #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" -#include "third_party/blink/renderer/core/layout/layout_image_resource_style_image.h" -#include "third_party/blink/renderer/core/layout/layout_inline.h" -#include "third_party/blink/renderer/core/layout/layout_list_marker.h" -#include "third_party/blink/renderer/core/layout/list_marker_text.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h" -#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/core/layout/ng/list/list_marker.h" namespace blink { LayoutNGListItem::LayoutNGListItem(Element* element) - : LayoutNGBlockFlow(element), - marker_type_(kStatic), - is_marker_text_updated_(false) { + : LayoutNGBlockFlow(element) { SetInline(false); SetConsumesSubtreeChangeNotification(); @@ -29,12 +20,6 @@ bool LayoutNGListItem::IsOfType(LayoutObjectType type) const { return type == kLayoutObjectNGListItem || LayoutNGBlockFlow::IsOfType(type); } -void LayoutNGListItem::WillBeDestroyed() { - DestroyMarker(); - - LayoutNGBlockFlow::WillBeDestroyed(); -} - void LayoutNGListItem::InsertedIntoTree() { LayoutNGBlockFlow::InsertedIntoTree(); @@ -51,200 +36,52 @@ void LayoutNGListItem::StyleDidChange(StyleDifference diff, const ComputedStyle* old_style) { LayoutNGBlockFlow::StyleDidChange(diff, old_style); - UpdateMarker(); + LayoutObject* marker = Marker(); + ListMarker* list_marker = ListMarker::Get(marker); + if (!list_marker) + return; + + list_marker->UpdateMarkerContentIfNeeded(*marker); if (old_style && (old_style->ListStyleType() != StyleRef().ListStyleType() || (StyleRef().ListStyleType() == EListStyleType::kString && old_style->ListStyleStringValue() != StyleRef().ListStyleStringValue()))) - ListStyleTypeChanged(); -} - -// If the value of ListStyleType changed, we need to the marker text has been -// updated. -void LayoutNGListItem::ListStyleTypeChanged() { - if (!is_marker_text_updated_) - return; - - is_marker_text_updated_ = false; - if (marker_) { - marker_->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( - layout_invalidation_reason::kListStyleTypeChange); - } + list_marker->ListStyleTypeChanged(*marker); } void LayoutNGListItem::OrdinalValueChanged() { - if (marker_type_ == kOrdinalValue && is_marker_text_updated_) { - is_marker_text_updated_ = false; - - // |marker_| can be a nullptr, for example, in the case of :after list item - // elements. - if (marker_) { - marker_->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( - layout_invalidation_reason::kListValueChange); - } - } + LayoutObject* marker = Marker(); + if (ListMarker* list_marker = ListMarker::Get(marker)) + list_marker->OrdinalValueChanged(*marker); } void LayoutNGListItem::SubtreeDidChange() { - if (!marker_) - return; - - if (ordinal_.NotInListChanged()) { - UpdateMarker(); - ordinal_.SetNotInListChanged(false); + LayoutObject* marker = Marker(); + ListMarker* list_marker = ListMarker::Get(marker); + if (!list_marker) return; - } - // Make sure outside marker is the direct child of ListItem. - if (!IsInside() && marker_->Parent() != this) { - marker_->Remove(); - AddChild(marker_, FirstChild()); + // Make sure an outside marker is a direct child of the list item (not nested + // inside an anonymous box), and that a marker originated by a ::before or + // ::after precedes the generated contents. + if ((marker->IsLayoutNGOutsideListMarker() && marker->Parent() != this) || + (IsPseudoElement() && marker != FirstChild())) { + marker->Remove(); + AddChild(marker, FirstChild()); } - UpdateMarkerContentIfNeeded(); + list_marker->UpdateMarkerContentIfNeeded(*marker); } void LayoutNGListItem::WillCollectInlines() { UpdateMarkerTextIfNeeded(); } -// Returns true if this is 'list-style-position: inside', or should be laid out -// as 'inside'. -bool LayoutNGListItem::IsInside() const { - return ordinal_.NotInList() || - StyleRef().ListStylePosition() == EListStylePosition::kInside; -} - -// Destroy the list marker objects if exists. -void LayoutNGListItem::DestroyMarker() { - if (marker_) { - marker_->Destroy(); - marker_ = nullptr; - } -} - -void LayoutNGListItem::UpdateMarkerText(LayoutText* text) { - DCHECK(text); - StringBuilder marker_text_builder; - marker_type_ = MarkerText(&marker_text_builder, kWithSuffix); - text->SetTextIfNeeded(marker_text_builder.ToString().ReleaseImpl()); - is_marker_text_updated_ = true; -} - -void LayoutNGListItem::UpdateMarkerText() { - DCHECK(marker_); - UpdateMarkerText(ToLayoutText(marker_->SlowFirstChild())); -} - -void LayoutNGListItem::UpdateMarker() { - const ComputedStyle& style = StyleRef(); - if (style.ListStyleType() == EListStyleType::kNone && !IsMarkerImage()) { - DestroyMarker(); - marker_type_ = kStatic; - is_marker_text_updated_ = true; - return; - } - - // Create a marker box if it does not exist yet. - Node* list_item = GetNode(); - const ComputedStyle* cached_marker_style = - list_item->IsPseudoElement() - ? nullptr - : ToElement(list_item)->CachedStyleForPseudoElement(kPseudoIdMarker); - if (cached_marker_style && cached_marker_style->GetContentData()) { - // Don't create an anonymous layout for the marker, it will be generated - // by the ::marker pseudo-element. - DestroyMarker(); - marker_type_ = kStatic; - is_marker_text_updated_ = true; - return; - } - scoped_refptr<ComputedStyle> marker_style; - if (cached_marker_style) { - marker_style = ComputedStyle::Clone(*cached_marker_style); - } else { - marker_style = ComputedStyle::Create(); - marker_style->InheritFrom(style); - marker_style->SetStyleType(kPseudoIdMarker); - marker_style->SetUnicodeBidi(UnicodeBidi::kIsolate); - marker_style->SetFontVariantNumericSpacing( - FontVariantNumeric::kTabularNums); - } - if (IsInside()) { - if (marker_ && !marker_->IsLayoutInline()) - DestroyMarker(); - if (!marker_) - marker_ = LayoutNGInsideListMarker::CreateAnonymous(&GetDocument()); - marker_style->SetDisplay(EDisplay::kInline); - auto margins = - LayoutListMarker::InlineMarginsForInside(style, IsMarkerImage()); - marker_style->SetMarginStart(Length::Fixed(margins.first)); - marker_style->SetMarginEnd(Length::Fixed(margins.second)); - } else { - if (marker_ && !marker_->IsLayoutBlockFlow()) - DestroyMarker(); - if (!marker_) - marker_ = LayoutNGListMarker::CreateAnonymous(&GetDocument()); - marker_style->SetDisplay(EDisplay::kInlineBlock); - // Do not break inside the marker, and honor the trailing spaces. - marker_style->SetWhiteSpace(EWhiteSpace::kPre); - // Compute margins for 'outside' during layout, because it requires the - // layout size of the marker. - // TODO(kojii): absolute position looks more reasonable, and maybe required - // in some cases, but this is currently blocked by crbug.com/734554 - // marker_style->SetPosition(EPosition::kAbsolute); - // marker_->SetPositionState(EPosition::kAbsolute); - } - marker_->SetStyle(std::move(marker_style)); - - UpdateMarkerContentIfNeeded(); - - LayoutObject* first_child = FirstChild(); - if (first_child != marker_) { - marker_->Remove(); - AddChild(marker_, FirstChild()); - } -} - -LayoutNGListItem* LayoutNGListItem::FromMarker(const LayoutObject& marker) { - DCHECK(marker.IsLayoutNGListMarkerIncludingInside()); - for (LayoutObject* parent = marker.Parent(); parent; - parent = parent->Parent()) { - if (parent->IsLayoutNGListItem()) { -#if DCHECK_IS_ON() - LayoutObject* parent_marker = ToLayoutNGListItem(parent)->Marker(); - if (parent_marker) { - DCHECK(!marker.GetNode()); - DCHECK_EQ(ToLayoutNGListItem(parent)->Marker(), &marker); - } else { - DCHECK(marker.GetNode()->IsMarkerPseudoElement()); - DCHECK_EQ(marker.GetNode()->parentElement()->GetLayoutBox(), parent); - } -#endif - return ToLayoutNGListItem(parent); - } - // These DCHECKs are not critical but to ensure we cover all cases we know. - DCHECK(parent->IsAnonymous()); - DCHECK(parent->IsLayoutBlockFlow() || parent->IsLayoutFlowThread()); - } - return nullptr; -} - -LayoutNGListItem* LayoutNGListItem::FromMarkerOrMarkerContent( - const LayoutObject& object) { - DCHECK(object.IsAnonymous()); - - if (object.IsLayoutNGListMarkerIncludingInside()) - return FromMarker(object); - - // Check if this is a marker content. - if (const LayoutObject* parent = object.Parent()) { - if (parent->IsLayoutNGListMarkerIncludingInside()) - return FromMarker(*parent); - } - - return nullptr; +void LayoutNGListItem::UpdateMarkerTextIfNeeded() { + LayoutObject* marker = Marker(); + if (ListMarker* list_marker = ListMarker::Get(marker)) + list_marker->UpdateMarkerTextIfNeeded(*marker); } int LayoutNGListItem::Value() const { @@ -252,190 +89,16 @@ int LayoutNGListItem::Value() const { return ordinal_.Value(*GetNode()); } -LayoutNGListItem::MarkerType LayoutNGListItem::MarkerText( - StringBuilder* text, - MarkerTextFormat format) const { - if (IsMarkerImage()) { - if (format == kWithSuffix) - text->Append(' '); - return kStatic; - } - - const ComputedStyle& style = StyleRef(); - switch (style.ListStyleType()) { - case EListStyleType::kNone: - return kStatic; - case EListStyleType::kString: { - text->Append(style.ListStyleStringValue()); - return kStatic; - } - case EListStyleType::kDisc: - case EListStyleType::kCircle: - case EListStyleType::kSquare: - // value is ignored for these types - text->Append(list_marker_text::GetText(style.ListStyleType(), 0)); - if (format == kWithSuffix) - text->Append(' '); - return kSymbolValue; - case EListStyleType::kArabicIndic: - case EListStyleType::kArmenian: - case EListStyleType::kBengali: - case EListStyleType::kCambodian: - case EListStyleType::kCjkIdeographic: - case EListStyleType::kCjkEarthlyBranch: - case EListStyleType::kCjkHeavenlyStem: - case EListStyleType::kDecimalLeadingZero: - case EListStyleType::kDecimal: - case EListStyleType::kDevanagari: - case EListStyleType::kEthiopicHalehame: - case EListStyleType::kEthiopicHalehameAm: - case EListStyleType::kEthiopicHalehameTiEr: - case EListStyleType::kEthiopicHalehameTiEt: - case EListStyleType::kGeorgian: - case EListStyleType::kGujarati: - case EListStyleType::kGurmukhi: - case EListStyleType::kHangul: - case EListStyleType::kHangulConsonant: - case EListStyleType::kHebrew: - case EListStyleType::kHiragana: - case EListStyleType::kHiraganaIroha: - case EListStyleType::kKannada: - case EListStyleType::kKatakana: - case EListStyleType::kKatakanaIroha: - case EListStyleType::kKhmer: - case EListStyleType::kKoreanHangulFormal: - case EListStyleType::kKoreanHanjaFormal: - case EListStyleType::kKoreanHanjaInformal: - case EListStyleType::kLao: - case EListStyleType::kLowerAlpha: - case EListStyleType::kLowerArmenian: - case EListStyleType::kLowerGreek: - case EListStyleType::kLowerLatin: - case EListStyleType::kLowerRoman: - case EListStyleType::kMalayalam: - case EListStyleType::kMongolian: - case EListStyleType::kMyanmar: - case EListStyleType::kOriya: - case EListStyleType::kPersian: - case EListStyleType::kSimpChineseFormal: - case EListStyleType::kSimpChineseInformal: - case EListStyleType::kTelugu: - case EListStyleType::kThai: - case EListStyleType::kTibetan: - case EListStyleType::kTradChineseFormal: - case EListStyleType::kTradChineseInformal: - case EListStyleType::kUpperAlpha: - case EListStyleType::kUpperArmenian: - case EListStyleType::kUpperLatin: - case EListStyleType::kUpperRoman: - case EListStyleType::kUrdu: { - int value = Value(); - text->Append(list_marker_text::GetText(style.ListStyleType(), value)); - if (format == kWithSuffix) { - text->Append(list_marker_text::Suffix(style.ListStyleType(), value)); - text->Append(' '); - } - return kOrdinalValue; - } - } - NOTREACHED(); - return kStatic; -} - -String LayoutNGListItem::MarkerTextWithSuffix() const { - StringBuilder text; - MarkerText(&text, kWithSuffix); - return text.ToString(); -} - -String LayoutNGListItem::MarkerTextWithoutSuffix() const { - StringBuilder text; - MarkerText(&text, kWithoutSuffix); - return text.ToString(); -} - -String LayoutNGListItem::TextAlternative(const LayoutObject& marker) { - // For accessibility, return the marker string in the logical order even in - // RTL, reflecting speech order. - if (LayoutNGListItem* list_item = FromMarker(marker)) - return list_item->MarkerTextWithSuffix(); - return g_empty_string; -} - -void LayoutNGListItem::UpdateMarkerContentIfNeeded() { - DCHECK(marker_); - - LayoutObject* child = marker_->SlowFirstChild(); - // There should be at most one child. - DCHECK(!child || !child->SlowFirstChild()); - if (IsMarkerImage()) { - StyleImage* list_style_image = StyleRef().ListStyleImage(); - if (child) { - // If the url of `list-style-image` changed, create a new LayoutImage. - if (!child->IsLayoutImage() || - ToLayoutImage(child)->ImageResource()->ImagePtr() != - list_style_image->Data()) { - child->Destroy(); - child = nullptr; - } - } - if (!child) { - LayoutNGListMarkerImage* image = - LayoutNGListMarkerImage::CreateAnonymous(&GetDocument()); - scoped_refptr<ComputedStyle> image_style = - ComputedStyle::CreateAnonymousStyleWithDisplay(marker_->StyleRef(), - EDisplay::kInline); - image->SetStyle(image_style); - image->SetImageResource( - MakeGarbageCollected<LayoutImageResourceStyleImage>( - list_style_image)); - image->SetIsGeneratedContent(); - marker_->AddChild(image); - } - } else { - // Create a LayoutText in it. - LayoutText* text = nullptr; - // |text_style| should be as same as style propagated in - // |LayoutObject::PropagateStyleToAnonymousChildren()| to avoid unexpected - // full layout due by style difference. See http://crbug.com/980399 - scoped_refptr<ComputedStyle> text_style = - ComputedStyle::CreateAnonymousStyleWithDisplay( - marker_->StyleRef(), marker_->StyleRef().Display()); - if (child) { - if (child->IsText()) { - text = ToLayoutText(child); - text->SetStyle(text_style); - } else { - child->Destroy(); - child = nullptr; - } - } - if (!child) { - text = LayoutText::CreateEmptyAnonymous(GetDocument(), text_style, - LegacyLayout::kAuto); - marker_->AddChild(text); - is_marker_text_updated_ = false; - } - } -} - -LayoutObject* LayoutNGListItem::SymbolMarkerLayoutText() const { - if (marker_type_ != kSymbolValue) - return nullptr; - DCHECK(marker_); - return marker_->SlowFirstChild(); -} - const LayoutObject* LayoutNGListItem::FindSymbolMarkerLayoutText( const LayoutObject* object) { if (!object) return nullptr; - if (object->IsLayoutNGListItem()) - return ToLayoutNGListItem(object)->SymbolMarkerLayoutText(); + if (const ListMarker* list_marker = ListMarker::Get(object)) + return list_marker->SymbolMarkerLayoutText(*object); - if (object->IsLayoutNGListMarker()) - return ToLayoutNGListMarker(object)->SymbolMarkerLayoutText(); + if (object->IsLayoutNGListItem()) + return FindSymbolMarkerLayoutText(ToLayoutNGListItem(object)->Marker()); if (object->IsAnonymousBlock()) return FindSymbolMarkerLayoutText(object->Parent()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h index d6fd7b52ee1..c236130315a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h @@ -19,62 +19,30 @@ class CORE_EXPORT LayoutNGListItem final : public LayoutNGBlockFlow { ListItemOrdinal& Ordinal() { return ordinal_; } int Value() const; - String MarkerTextWithSuffix() const; - String MarkerTextWithoutSuffix() const; - // Marker text with suffix, e.g. "1. ", for use in accessibility. - static String TextAlternative(const LayoutObject& marker); - - LayoutObject* Marker() const { return marker_; } - bool IsMarkerImage() const { - return StyleRef().ListStyleImage() && - !StyleRef().ListStyleImage()->ErrorOccurred(); + LayoutObject* Marker() const { + Element* list_item = To<Element>(GetNode()); + return list_item->PseudoElementLayoutObject(kPseudoIdMarker); } - void UpdateMarkerTextIfNeeded() { - if (marker_ && !is_marker_text_updated_ && !IsMarkerImage()) - UpdateMarkerText(); - } - void UpdateMarkerContentIfNeeded(); + void UpdateMarkerTextIfNeeded(); void OrdinalValueChanged(); void WillCollectInlines() override; - LayoutObject* SymbolMarkerLayoutText() const; static const LayoutObject* FindSymbolMarkerLayoutText(const LayoutObject*); - // Find the LayoutNGListItem from a marker. - static LayoutNGListItem* FromMarker(const LayoutObject& marker); - static LayoutNGListItem* FromMarkerOrMarkerContent(const LayoutObject&); - const char* GetName() const override { return "LayoutNGListItem"; } private: bool IsOfType(LayoutObjectType) const override; - void WillBeDestroyed() override; void InsertedIntoTree() override; void WillBeRemovedFromTree() override; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; void SubtreeDidChange() final; - bool IsInside() const; - - enum MarkerTextFormat { kWithSuffix, kWithoutSuffix }; - enum MarkerType { kStatic, kOrdinalValue, kSymbolValue }; - MarkerType MarkerText(StringBuilder*, MarkerTextFormat) const; - void UpdateMarkerText(); - void UpdateMarkerText(LayoutText*); - void UpdateMarker(); - void DestroyMarker(); - - void ListStyleTypeChanged(); - ListItemOrdinal ordinal_; - LayoutObject* marker_ = nullptr; - - unsigned marker_type_ : 2; // MarkerType - unsigned is_marker_text_updated_ : 1; }; DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGListItem, IsLayoutNGListItem()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc deleted file mode 100644 index a477149ad5d..00000000000 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" - -#include "third_party/blink/renderer/core/layout/layout_text.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" - -namespace blink { - -LayoutNGListMarker::LayoutNGListMarker(Element* element) - : LayoutNGBlockFlowMixin<LayoutBlockFlow>(element) {} - -LayoutNGListMarker* LayoutNGListMarker::CreateAnonymous(Document* document) { - LayoutNGListMarker* object = new LayoutNGListMarker(nullptr); - object->SetDocumentForAnonymous(document); - return object; -} - -bool LayoutNGListMarker::IsOfType(LayoutObjectType type) const { - return type == kLayoutObjectNGListMarker || - LayoutNGMixin<LayoutBlockFlow>::IsOfType(type); -} - -void LayoutNGListMarker::WillCollectInlines() { - if (LayoutNGListItem* list_item = LayoutNGListItem::FromMarker(*this)) - list_item->UpdateMarkerTextIfNeeded(); -} - -bool LayoutNGListMarker::IsContentImage() const { - if (LayoutNGListItem* list_item = LayoutNGListItem::FromMarker(*this)) - return list_item->IsMarkerImage(); - return false; -} - -LayoutObject* LayoutNGListMarker::SymbolMarkerLayoutText() const { - if (LayoutNGListItem* list_item = LayoutNGListItem::FromMarker(*this)) - return list_item->SymbolMarkerLayoutText(); - return nullptr; -} - -bool LayoutNGListMarker::NeedsOccupyWholeLine() const { - if (!GetDocument().InQuirksMode()) - return false; - - LayoutObject* next_sibling = NextSibling(); - if (next_sibling && next_sibling->GetNode() && - (IsA<HTMLUListElement>(*next_sibling->GetNode()) || - IsA<HTMLOListElement>(*next_sibling->GetNode()))) - return true; - - return false; -} - -PositionWithAffinity LayoutNGListMarker::PositionForPoint( - const PhysicalOffset&) const { - return CreatePositionWithAffinity(0); -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.cc index 65a6f555531..0ff22412114 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.cc @@ -3,7 +3,9 @@ // found in the LICENSE file. #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h" + #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" #include "third_party/blink/renderer/core/svg/graphics/svg_image.h" namespace blink { @@ -22,21 +24,6 @@ bool LayoutNGListMarkerImage::IsOfType(LayoutObjectType type) const { return type == kLayoutObjectNGListMarkerImage || LayoutImage::IsOfType(type); } -Node* LayoutNGListMarkerImage::NodeForHitTest() const { - // In LayoutNG tree, image list marker is structured like this: - // <li> (LayoutListItem) - // <anonymous block> (LayoutNGListMarker or LayoutNGInsideListMarker) - // <anonymous img> (LayoutNGListMarkerImage) - // Hit testing should return the list-item node. - DCHECK(!GetNode()); - for (const LayoutObject* parent = Parent(); parent; - parent = parent->Parent()) { - if (Node* node = parent->GetNode()) - return node; - } - return nullptr; -} - // Because ImageResource() is always LayoutImageResourceStyleImage. So we could // use StyleImage::ImageSize to determine the concrete object size with // default object size(ascent/2 x ascent/2). diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h index 6ebae9a91d7..86bdb5ec592 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h @@ -19,8 +19,6 @@ class CORE_EXPORT LayoutNGListMarkerImage final : public LayoutImage { bool IsLayoutNGObject() const override { return true; } - Node* NodeForHitTest() const final; - private: bool IsOfType(LayoutObjectType) const override; @@ -28,9 +26,6 @@ class CORE_EXPORT LayoutNGListMarkerImage final : public LayoutImage { void ComputeIntrinsicSizingInfo(IntrinsicSizingInfo&) const final; }; -DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGListMarkerImage, - IsLayoutNGListMarkerImage()); - } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_LIST_MARKER_IMAGE_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.cc new file mode 100644 index 00000000000..4507904c341 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.cc @@ -0,0 +1,41 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" + +#include "third_party/blink/renderer/core/layout/layout_text.h" + +namespace blink { + +LayoutNGOutsideListMarker::LayoutNGOutsideListMarker(Element* element) + : LayoutNGBlockFlowMixin<LayoutBlockFlow>(element) {} + +bool LayoutNGOutsideListMarker::IsOfType(LayoutObjectType type) const { + return type == kLayoutObjectNGOutsideListMarker || + LayoutNGMixin<LayoutBlockFlow>::IsOfType(type); +} + +void LayoutNGOutsideListMarker::WillCollectInlines() { + list_marker_.UpdateMarkerTextIfNeeded(*this); +} + +bool LayoutNGOutsideListMarker::NeedsOccupyWholeLine() const { + if (!GetDocument().InQuirksMode()) + return false; + + LayoutObject* next_sibling = NextSibling(); + if (next_sibling && next_sibling->GetNode() && + (IsA<HTMLUListElement>(*next_sibling->GetNode()) || + IsA<HTMLOListElement>(*next_sibling->GetNode()))) + return true; + + return false; +} + +PositionWithAffinity LayoutNGOutsideListMarker::PositionForPoint( + const PhysicalOffset&) const { + return CreatePositionWithAffinity(0); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h index d317122a3a8..3b30f46349b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h @@ -2,41 +2,41 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_LIST_MARKER_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_LIST_MARKER_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_OUTSIDE_LIST_MARKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_OUTSIDE_LIST_MARKER_H_ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h" +#include "third_party/blink/renderer/core/layout/ng/list/list_marker.h" namespace blink { -class Document; - // A LayoutObject subclass for outside-positioned list markers in LayoutNG. -class CORE_EXPORT LayoutNGListMarker final +class CORE_EXPORT LayoutNGOutsideListMarker final : public LayoutNGBlockFlowMixin<LayoutBlockFlow> { public: - explicit LayoutNGListMarker(Element*); - static LayoutNGListMarker* CreateAnonymous(Document*); + explicit LayoutNGOutsideListMarker(Element*); void WillCollectInlines() override; - bool IsContentImage() const; - - LayoutObject* SymbolMarkerLayoutText() const; - - const char* GetName() const override { return "LayoutNGListMarker"; } + const char* GetName() const override { return "LayoutNGOutsideListMarker"; } bool NeedsOccupyWholeLine() const; + const ListMarker& Marker() const { return list_marker_; } + ListMarker& Marker() { return list_marker_; } + private: bool IsOfType(LayoutObjectType) const override; PositionWithAffinity PositionForPoint(const PhysicalOffset&) const override; + + ListMarker list_marker_; }; -DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGListMarker, IsLayoutNGListMarker()); +DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGOutsideListMarker, + IsLayoutNGOutsideListMarker()); } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_LIST_MARKER_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LAYOUT_NG_OUTSIDE_LIST_MARKER_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.cc new file mode 100644 index 00000000000..b2533d42d7a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.cc @@ -0,0 +1,256 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/list/list_marker.h" + +#include "third_party/blink/renderer/core/layout/layout_image_resource_style_image.h" +#include "third_party/blink/renderer/core/layout/list_marker_text.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_inside_list_marker.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" + +namespace blink { + +ListMarker::ListMarker() : marker_text_type_(kNotText) {} + +const ListMarker* ListMarker::Get(const LayoutObject* object) { + if (!object) + return nullptr; + if (object->IsLayoutNGOutsideListMarker()) + return &ToLayoutNGOutsideListMarker(object)->Marker(); + if (object->IsLayoutNGInsideListMarker()) + return &ToLayoutNGInsideListMarker(object)->Marker(); + return nullptr; +} + +ListMarker* ListMarker::Get(LayoutObject* object) { + return const_cast<ListMarker*>( + ListMarker::Get(static_cast<const LayoutObject*>(object))); +} + +// If the value of ListStyleType changed, we need to the marker text has been +// updated. +void ListMarker::ListStyleTypeChanged(LayoutObject& marker) { + if (marker_text_type_ == kNotText || marker_text_type_ == kUnresolved) + return; + + marker_text_type_ = kUnresolved; + marker.SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( + layout_invalidation_reason::kListStyleTypeChange); +} + +void ListMarker::OrdinalValueChanged(LayoutObject& marker) { + if (marker_text_type_ == kOrdinalValue) { + marker_text_type_ = kUnresolved; + marker.SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( + layout_invalidation_reason::kListValueChange); + } +} + +void ListMarker::UpdateMarkerText(LayoutObject& marker, LayoutText* text) { + DCHECK(text); + DCHECK_EQ(marker_text_type_, kUnresolved); + StringBuilder marker_text_builder; + marker_text_type_ = MarkerText(marker, &marker_text_builder, kWithSuffix); + text->SetTextIfNeeded(marker_text_builder.ToString().ReleaseImpl()); + DCHECK_NE(marker_text_type_, kNotText); + DCHECK_NE(marker_text_type_, kUnresolved); +} + +void ListMarker::UpdateMarkerText(LayoutObject& marker) { + UpdateMarkerText(marker, ToLayoutText(marker.SlowFirstChild())); +} + +LayoutNGListItem* ListMarker::ListItem(const LayoutObject& marker) { + return ToLayoutNGListItem(marker.GetNode()->parentNode()->GetLayoutObject()); +} + +ListMarker::MarkerTextType ListMarker::MarkerText( + const LayoutObject& marker, + StringBuilder* text, + MarkerTextFormat format) const { + if (IsMarkerImage(marker)) { + if (format == kWithSuffix) + text->Append(' '); + return kNotText; + } + + LayoutNGListItem* list_item = ListItem(marker); + const ComputedStyle& style = list_item->StyleRef(); + switch (style.ListStyleType()) { + case EListStyleType::kNone: + return kNotText; + case EListStyleType::kString: { + text->Append(style.ListStyleStringValue()); + return kStatic; + } + case EListStyleType::kDisc: + case EListStyleType::kCircle: + case EListStyleType::kSquare: + // value is ignored for these types + text->Append(list_marker_text::GetText(style.ListStyleType(), 0)); + if (format == kWithSuffix) + text->Append(' '); + return kSymbolValue; + case EListStyleType::kArabicIndic: + case EListStyleType::kArmenian: + case EListStyleType::kBengali: + case EListStyleType::kCambodian: + case EListStyleType::kCjkIdeographic: + case EListStyleType::kCjkEarthlyBranch: + case EListStyleType::kCjkHeavenlyStem: + case EListStyleType::kDecimalLeadingZero: + case EListStyleType::kDecimal: + case EListStyleType::kDevanagari: + case EListStyleType::kEthiopicHalehame: + case EListStyleType::kEthiopicHalehameAm: + case EListStyleType::kEthiopicHalehameTiEr: + case EListStyleType::kEthiopicHalehameTiEt: + case EListStyleType::kGeorgian: + case EListStyleType::kGujarati: + case EListStyleType::kGurmukhi: + case EListStyleType::kHangul: + case EListStyleType::kHangulConsonant: + case EListStyleType::kHebrew: + case EListStyleType::kHiragana: + case EListStyleType::kHiraganaIroha: + case EListStyleType::kKannada: + case EListStyleType::kKatakana: + case EListStyleType::kKatakanaIroha: + case EListStyleType::kKhmer: + case EListStyleType::kKoreanHangulFormal: + case EListStyleType::kKoreanHanjaFormal: + case EListStyleType::kKoreanHanjaInformal: + case EListStyleType::kLao: + case EListStyleType::kLowerAlpha: + case EListStyleType::kLowerArmenian: + case EListStyleType::kLowerGreek: + case EListStyleType::kLowerLatin: + case EListStyleType::kLowerRoman: + case EListStyleType::kMalayalam: + case EListStyleType::kMongolian: + case EListStyleType::kMyanmar: + case EListStyleType::kOriya: + case EListStyleType::kPersian: + case EListStyleType::kSimpChineseFormal: + case EListStyleType::kSimpChineseInformal: + case EListStyleType::kTelugu: + case EListStyleType::kThai: + case EListStyleType::kTibetan: + case EListStyleType::kTradChineseFormal: + case EListStyleType::kTradChineseInformal: + case EListStyleType::kUpperAlpha: + case EListStyleType::kUpperArmenian: + case EListStyleType::kUpperLatin: + case EListStyleType::kUpperRoman: + case EListStyleType::kUrdu: { + int value = list_item->Value(); + text->Append(list_marker_text::GetText(style.ListStyleType(), value)); + if (format == kWithSuffix) { + text->Append(list_marker_text::Suffix(style.ListStyleType(), value)); + text->Append(' '); + } + return kOrdinalValue; + } + } + NOTREACHED(); + return kStatic; +} + +String ListMarker::MarkerTextWithSuffix(const LayoutObject& marker) const { + StringBuilder text; + MarkerText(marker, &text, kWithSuffix); + return text.ToString(); +} + +String ListMarker::MarkerTextWithoutSuffix(const LayoutObject& marker) const { + StringBuilder text; + MarkerText(marker, &text, kWithoutSuffix); + return text.ToString(); +} + +String ListMarker::TextAlternative(const LayoutObject& marker) const { + // For accessibility, return the marker string in the logical order even in + // RTL, reflecting speech order. + return MarkerTextWithSuffix(marker); +} + +void ListMarker::UpdateMarkerContentIfNeeded(LayoutObject& marker) { + LayoutNGListItem* list_item = ListItem(marker); + + if (!marker.StyleRef().ContentBehavesAsNormal()) { + marker_text_type_ = kNotText; + return; + } + + // There should be at most one child. + LayoutObject* child = marker.SlowFirstChild(); + DCHECK(!child || !child->NextSibling()); + + if (IsMarkerImage(marker)) { + StyleImage* list_style_image = list_item->StyleRef().ListStyleImage(); + if (child) { + // If the url of `list-style-image` changed, create a new LayoutImage. + if (!child->IsLayoutImage() || + ToLayoutImage(child)->ImageResource()->ImagePtr() != + list_style_image->Data()) { + child->Destroy(); + child = nullptr; + } + } + if (!child) { + LayoutNGListMarkerImage* image = + LayoutNGListMarkerImage::CreateAnonymous(&marker.GetDocument()); + scoped_refptr<ComputedStyle> image_style = + ComputedStyle::CreateAnonymousStyleWithDisplay(marker.StyleRef(), + EDisplay::kInline); + image->SetStyle(image_style); + image->SetImageResource( + MakeGarbageCollected<LayoutImageResourceStyleImage>( + list_style_image)); + image->SetIsGeneratedContent(); + marker.AddChild(image); + } + marker_text_type_ = kNotText; + return; + } + + if (list_item->StyleRef().ListStyleType() == EListStyleType::kNone) { + marker_text_type_ = kNotText; + return; + } + + // Create a LayoutText in it. + LayoutText* text = nullptr; + // |text_style| should be as same as style propagated in + // |LayoutObject::PropagateStyleToAnonymousChildren()| to avoid unexpected + // full layout due by style difference. See http://crbug.com/980399 + scoped_refptr<ComputedStyle> text_style = + ComputedStyle::CreateAnonymousStyleWithDisplay( + marker.StyleRef(), marker.StyleRef().Display()); + if (child) { + if (child->IsText()) { + text = ToLayoutText(child); + text->SetStyle(text_style); + } else { + child->Destroy(); + child = nullptr; + } + } + if (!child) { + text = LayoutText::CreateEmptyAnonymous(marker.GetDocument(), text_style, + LegacyLayout::kAuto); + marker.AddChild(text); + marker_text_type_ = kUnresolved; + } +} + +LayoutObject* ListMarker::SymbolMarkerLayoutText( + const LayoutObject& marker) const { + if (marker_text_type_ != kSymbolValue) + return nullptr; + return marker.SlowFirstChild(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.h new file mode 100644 index 00000000000..0ecf1844689 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/list_marker.h @@ -0,0 +1,71 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LIST_MARKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LIST_MARKER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" + +namespace blink { + +// This class holds code shared among LayoutNG classes for list markers. +class CORE_EXPORT ListMarker { + friend class LayoutNGListItem; + + public: + explicit ListMarker(); + + static const ListMarker* Get(const LayoutObject*); + static ListMarker* Get(LayoutObject*); + + static LayoutNGListItem* ListItem(const LayoutObject&); + + String MarkerTextWithSuffix(const LayoutObject&) const; + String MarkerTextWithoutSuffix(const LayoutObject&) const; + + // Marker text with suffix, e.g. "1. ", for use in accessibility. + String TextAlternative(const LayoutObject&) const; + + static bool IsMarkerImage(const LayoutObject& marker) { + return ListItem(marker)->StyleRef().GeneratesMarkerImage(); + } + + void UpdateMarkerTextIfNeeded(LayoutObject& marker) { + if (marker_text_type_ == kUnresolved) + UpdateMarkerText(marker); + } + void UpdateMarkerContentIfNeeded(LayoutObject&); + + void OrdinalValueChanged(LayoutObject&); + + LayoutObject* SymbolMarkerLayoutText(const LayoutObject&) const; + + private: + enum MarkerTextFormat { kWithSuffix, kWithoutSuffix }; + enum MarkerTextType { + kNotText, // The marker doesn't have a LayoutText, either because it has + // not been created yet or because 'list-style-type' is 'none', + // 'list-style-image' is not 'none', or 'content' is not + // 'normal'. + kUnresolved, // The marker has a LayoutText that needs to be updated. + kOrdinalValue, // The marker text depends on the ordinal. + kStatic, // The marker text doesn't depend on the ordinal. + kSymbolValue, // Like kStatic, but the marker is painted as a symbol. + }; + MarkerTextType MarkerText(const LayoutObject&, + StringBuilder*, + MarkerTextFormat) const; + void UpdateMarkerText(LayoutObject&); + void UpdateMarkerText(LayoutObject&, LayoutText*); + + void ListStyleTypeChanged(LayoutObject&); + + unsigned marker_text_type_ : 3; // MarkerTextType +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LIST_LIST_MARKER_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc index 710824388d4..e0e08726a1f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.cc @@ -7,7 +7,7 @@ #include "third_party/blink/renderer/core/layout/layout_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" @@ -15,16 +15,18 @@ namespace blink { -NGUnpositionedListMarker::NGUnpositionedListMarker(LayoutNGListMarker* marker) +NGUnpositionedListMarker::NGUnpositionedListMarker( + LayoutNGOutsideListMarker* marker) : marker_layout_object_(marker) {} NGUnpositionedListMarker::NGUnpositionedListMarker(const NGBlockNode& node) - : NGUnpositionedListMarker(ToLayoutNGListMarker(node.GetLayoutBox())) {} + : NGUnpositionedListMarker( + ToLayoutNGOutsideListMarker(node.GetLayoutBox())) {} // Returns true if this is an image marker. bool NGUnpositionedListMarker::IsImage() const { DCHECK(marker_layout_object_); - return marker_layout_object_->IsContentImage(); + return marker_layout_object_->Marker().IsMarkerImage(*marker_layout_object_); } // Compute the inline offset of the marker, relative to the list item. @@ -45,24 +47,21 @@ scoped_refptr<const NGLayoutResult> NGUnpositionedListMarker::Layout( FontBaseline baseline_type) const { DCHECK(marker_layout_object_); NGBlockNode marker_node(marker_layout_object_); + + // We need the first-line baseline from the list-marker, instead of the + // typical atomic-inline baseline. scoped_refptr<const NGLayoutResult> marker_layout_result = - marker_node.LayoutAtomicInline(parent_space, parent_style, baseline_type, - parent_space.UseFirstLineStyle()); + marker_node.LayoutAtomicInline(parent_space, parent_style, + parent_space.UseFirstLineStyle(), + NGBaselineAlgorithmType::kFirstLine); DCHECK(marker_layout_result); return marker_layout_result; } -bool NGUnpositionedListMarker::CanAddToBox( +base::Optional<LayoutUnit> NGUnpositionedListMarker::ContentAlignmentBaseline( const NGConstraintSpace& space, FontBaseline baseline_type, - const NGPhysicalFragment& content, - NGLineHeightMetrics* content_metrics) const { - DCHECK(content_metrics); - - // Baselines from two different writing-mode cannot be aligned. - if (UNLIKELY(space.GetWritingMode() != content.Style().GetWritingMode())) - return false; - + const NGPhysicalFragment& content) const { // Compute the baseline of the child content. if (content.IsLineBox()) { const auto& line_box = To<NGPhysicalLineBoxFragment>(content); @@ -71,22 +70,17 @@ bool NGUnpositionedListMarker::CanAddToBox( // with the next non-empty line box produced. (This can occur with floats // producing empty line-boxes). if (line_box.IsEmptyLineBox() && !line_box.BreakToken()->IsFinished()) - return false; + return base::nullopt; - *content_metrics = line_box.Metrics(); - } else { - NGBoxFragment content_fragment(space.GetWritingMode(), space.Direction(), - To<NGPhysicalBoxFragment>(content)); - *content_metrics = content_fragment.BaselineMetricsWithoutSynthesize( - {NGBaselineAlgorithmType::kFirstLine, baseline_type}); - - // If this child content does not have any line boxes, the list marker - // should be aligned to the first line box of next child. - // https://github.com/w3c/csswg-drafts/issues/2417 - if (content_metrics->IsEmpty()) - return false; + return line_box.Metrics().ascent; } - return true; + + // If this child content does not have any line boxes, the list marker + // should be aligned to the first line box of next child. + // https://github.com/w3c/csswg-drafts/issues/2417 + return NGBoxFragment(space.GetWritingMode(), space.Direction(), + To<NGPhysicalBoxFragment>(content)) + .FirstBaseline(); } void NGUnpositionedListMarker::AddToBox( @@ -94,12 +88,10 @@ void NGUnpositionedListMarker::AddToBox( FontBaseline baseline_type, const NGPhysicalFragment& content, const NGBoxStrut& border_scrollbar_padding, - const NGLineHeightMetrics& content_metrics, const NGLayoutResult& marker_layout_result, + LayoutUnit content_baseline, LogicalOffset* content_offset, NGBoxFragmentBuilder* container_builder) const { - DCHECK(!content_metrics.IsEmpty()); - const NGPhysicalBoxFragment& marker_physical_fragment = To<NGPhysicalBoxFragment>(marker_layout_result.PhysicalFragment()); @@ -111,8 +103,8 @@ void NGUnpositionedListMarker::AddToBox( // Adjust the block offset to align baselines of the marker and the content. NGLineHeightMetrics marker_metrics = marker_fragment.BaselineMetrics( - {NGBaselineAlgorithmType::kAtomicInline, baseline_type}, space); - LayoutUnit baseline_adjust = content_metrics.ascent - marker_metrics.ascent; + /* margins */ NGLineBoxStrut(), baseline_type); + LayoutUnit baseline_adjust = content_baseline - marker_metrics.ascent; if (baseline_adjust >= 0) { marker_offset.block_offset += baseline_adjust; } else { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h index 8305cac2cf6..5c5e6e3091f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h @@ -14,7 +14,7 @@ namespace blink { class ComputedStyle; -class LayoutNGListMarker; +class LayoutNGOutsideListMarker; class LayoutUnit; class NGBlockNode; class NGConstraintSpace; @@ -23,7 +23,6 @@ class NGLayoutResult; class NGPhysicalFragment; struct LogicalOffset; -struct NGLineHeightMetrics; // Represents an unpositioned list marker. // @@ -37,8 +36,8 @@ struct NGLineHeightMetrics; // // In order to adjust with the other content of LI, marker will be handled // after other children. -// First, try to find the adjusted content_metrics for the marker. See -// |CanAddToBox()| for details. +// First, try to find the alignment-baseline for the marker. See +// |ContentAlignmentBaseline()| for details. // If found, layout marker, compute the content adjusted offset and float // intuded offset. See |AddToBox()| for details. // If not, layout marker and deal with it in |AddToBoxWithoutLineBoxes()|. @@ -52,25 +51,27 @@ class CORE_EXPORT NGUnpositionedListMarker final { public: NGUnpositionedListMarker() : marker_layout_object_(nullptr) {} - explicit NGUnpositionedListMarker(LayoutNGListMarker*); + explicit NGUnpositionedListMarker(LayoutNGOutsideListMarker*); explicit NGUnpositionedListMarker(const NGBlockNode&); explicit operator bool() const { return marker_layout_object_; } - // Returns true if the list marker can be added to box. False indicates - // that the child content does not have a baseline to align to, and that - // caller should try next child, or "WithoutLineBoxes" version. - bool CanAddToBox(const NGConstraintSpace&, - FontBaseline, - const NGPhysicalFragment& content, - NGLineHeightMetrics* content_metrics) const; + // Returns the baseline that the list-marker should place itself along. + // + // |base::nullopt| indicates that the child |content| does not have a baseline + // to align to, and that caller should try next child, or use the + // |AddToBoxWithoutLineBoxes()| method. + base::Optional<LayoutUnit> ContentAlignmentBaseline( + const NGConstraintSpace&, + FontBaseline, + const NGPhysicalFragment& content) const; // Add a fragment for an outside list marker. void AddToBox(const NGConstraintSpace&, FontBaseline, const NGPhysicalFragment& content, const NGBoxStrut&, - const NGLineHeightMetrics& content_metrics, const NGLayoutResult& marker_layout_result, + LayoutUnit content_baseline, LogicalOffset* content_offset, NGBoxFragmentBuilder*) const; @@ -105,7 +106,7 @@ class CORE_EXPORT NGUnpositionedListMarker final { const NGBoxStrut&, LayoutUnit) const; - LayoutNGListMarker* marker_layout_object_; + LayoutNGOutsideListMarker* marker_layout_object_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.cc new file mode 100644 index 00000000000..161826f1cff --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.cc @@ -0,0 +1,46 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h" + +#include "third_party/blink/renderer/core/layout/layout_analyzer.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" + +namespace blink { + +LayoutNGMathMLBlock::LayoutNGMathMLBlock(MathMLElement* element) + : LayoutNGMixin<LayoutBlock>(element) { + DCHECK(element); +} + +void LayoutNGMathMLBlock::UpdateBlockLayout(bool relayout_children) { + LayoutAnalyzer::BlockScope analyzer(*this); + + if (IsOutOfFlowPositioned()) { + UpdateOutOfFlowBlockLayout(); + return; + } + + UpdateInFlowBlockLayout(); +} + +bool LayoutNGMathMLBlock::IsOfType(LayoutObjectType type) const { + return type == kLayoutObjectMathML || + (type == kLayoutObjectMathMLRoot && GetNode() && + GetNode()->HasTagName(mathml_names::kMathTag)) || + LayoutNGMixin<LayoutBlock>::IsOfType(type); +} + +bool LayoutNGMathMLBlock::IsChildAllowed(LayoutObject* child, + const ComputedStyle&) const { + return child->GetNode() && child->GetNode()->IsMathMLElement(); +} + +bool LayoutNGMathMLBlock::CanHaveChildren() const { + if (GetNode() && GetNode()->HasTagName(mathml_names::kMspaceTag)) + return false; + return LayoutNGMixin<LayoutBlock>::CanHaveChildren(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h new file mode 100644 index 00000000000..bc9dc975643 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/layout_ng_mathml_block.h @@ -0,0 +1,29 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_LAYOUT_NG_MATHML_BLOCK_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_LAYOUT_NG_MATHML_BLOCK_H_ + +#include "third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h" +#include "third_party/blink/renderer/core/mathml/mathml_element.h" + +namespace blink { + +class LayoutNGMathMLBlock : public LayoutNGMixin<LayoutBlock> { + public: + explicit LayoutNGMathMLBlock(MathMLElement*); + + const char* GetName() const override { return "LayoutNGMathMLBlock"; } + + private: + void UpdateBlockLayout(bool relayout_children) final; + + bool IsOfType(LayoutObjectType) const final; + bool IsChildAllowed(LayoutObject*, const ComputedStyle&) const final; + bool CanHaveChildren() const final; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_LAYOUT_NG_MATHML_BLOCK_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc new file mode 100644 index 00000000000..9deb2e3c49f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc @@ -0,0 +1,323 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h" + +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h" + +namespace blink { +namespace { + +// Describes the amount to shift the numerator/denominator of the fraction when +// a fraction bar is present. Data is populated from the OpenType MATH table. +// If the OpenType MATH table is not present fallback values are used. +// https://mathml-refresh.github.io/mathml-core/#fraction-with-nonzero-line-thickness +struct FractionParameters { + LayoutUnit numerator_gap_min; + LayoutUnit denominator_gap_min; + LayoutUnit numerator_min_shift_up; + LayoutUnit denominator_min_shift_down; +}; + +FractionParameters GetFractionParameters(const ComputedStyle& style) { + FractionParameters parameters; + + bool has_display_style = HasDisplayStyle(style); + + // We try and read constants to draw the fraction from the OpenType MATH and + // use fallback values otherwise. + // The MATH table specification suggests default rule thickness or (in + // displaystyle) 3 times default rule thickness for the gaps. + parameters.numerator_gap_min = LayoutUnit( + MathConstant( + style, + has_display_style + ? OpenTypeMathSupport::MathConstants:: + kFractionNumDisplayStyleGapMin + : OpenTypeMathSupport::MathConstants::kFractionNumeratorGapMin) + .value_or((has_display_style ? 3 : 1) * + RuleThicknessFallback(style))); + parameters.denominator_gap_min = LayoutUnit( + MathConstant( + style, + has_display_style + ? OpenTypeMathSupport::MathConstants:: + kFractionDenomDisplayStyleGapMin + : OpenTypeMathSupport::MathConstants::kFractionDenominatorGapMin) + .value_or(parameters.numerator_gap_min)); + + // TODO(crbug.com/1058369): The MATH table specification does not suggest + // any values for shifts, so we leave them at zero for now. + parameters.numerator_min_shift_up = LayoutUnit( + MathConstant( + style, + has_display_style + ? OpenTypeMathSupport::MathConstants:: + kFractionNumeratorDisplayStyleShiftUp + : OpenTypeMathSupport::MathConstants::kFractionNumeratorShiftUp) + .value_or(0)); + parameters.denominator_min_shift_down = LayoutUnit( + MathConstant(style, has_display_style + ? OpenTypeMathSupport::MathConstants:: + kFractionDenominatorDisplayStyleShiftDown + : OpenTypeMathSupport::MathConstants:: + kFractionDenominatorShiftDown) + .value_or(0)); + + return parameters; +} + +// Describes the amount to shift the numerator/denominator of the fraction when +// a fraction bar is not present. Data is populated from the OpenType MATH +// table. If the OpenType MATH table is not present fallback values are used. +// https://mathml-refresh.github.io/mathml-core/#fraction-with-zero-line-thickness +struct FractionStackParameters { + LayoutUnit gap_min; + LayoutUnit top_shift_up; + LayoutUnit bottom_shift_down; +}; + +FractionStackParameters GetFractionStackParameters(const ComputedStyle& style) { + FractionStackParameters parameters; + + bool has_display_style = HasDisplayStyle(style); + + // We try and read constants to draw the stack from the OpenType MATH and use + // fallback values otherwise. + // We use the fallback values suggested in the MATH table specification. + parameters.gap_min = LayoutUnit( + MathConstant( + style, + has_display_style + ? OpenTypeMathSupport::MathConstants::kStackDisplayStyleGapMin + : OpenTypeMathSupport::MathConstants::kStackGapMin) + .value_or((has_display_style ? 7 : 3) * + RuleThicknessFallback(style))); + // The MATH table specification does not suggest any values for shifts, so + // we leave them at zero. + parameters.top_shift_up = LayoutUnit( + MathConstant( + style, + has_display_style + ? OpenTypeMathSupport::MathConstants::kStackTopDisplayStyleShiftUp + : OpenTypeMathSupport::MathConstants::kStackTopShiftUp) + .value_or(0)); + parameters.bottom_shift_down = LayoutUnit( + MathConstant( + style, + has_display_style + ? OpenTypeMathSupport::MathConstants:: + kStackBottomDisplayStyleShiftDown + : OpenTypeMathSupport::MathConstants::kStackBottomShiftDown) + .value_or(0)); + + return parameters; +} + +} // namespace + +NGMathFractionLayoutAlgorithm::NGMathFractionLayoutAlgorithm( + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_scrollbar_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding + + params.fragment_geometry.scrollbar) { + DCHECK(params.space.IsNewFormattingContext()); + container_builder_.SetIsNewFormattingContext( + params.space.IsNewFormattingContext()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); + container_builder_.SetIsMathMLFraction(); +} + +void NGMathFractionLayoutAlgorithm::GatherChildren(NGBlockNode* numerator, + NGBlockNode* denominator) { + for (NGLayoutInputNode child = Node().FirstChild(); child; + child = child.NextSibling()) { + NGBlockNode block_child = To<NGBlockNode>(child); + if (child.IsOutOfFlowPositioned()) { + container_builder_.AddOutOfFlowChildCandidate( + block_child, {border_scrollbar_padding_.inline_start, + border_scrollbar_padding_.block_start}); + continue; + } + if (!*numerator) { + *numerator = block_child; + continue; + } + if (!*denominator) { + *denominator = block_child; + continue; + } + + NOTREACHED(); + } + + DCHECK(*numerator); + DCHECK(*denominator); +} + +scoped_refptr<const NGLayoutResult> NGMathFractionLayoutAlgorithm::Layout() { + DCHECK(!BreakToken()); + + NGBlockNode numerator = nullptr; + NGBlockNode denominator = nullptr; + GatherChildren(&numerator, &denominator); + + const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); + auto child_available_size = + ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); + auto numerator_space = CreateConstraintSpaceForMathChild( + Node(), child_available_size, ConstraintSpace(), numerator); + scoped_refptr<const NGLayoutResult> numerator_layout_result = + numerator.Layout(numerator_space); + auto numerator_margins = + ComputeMarginsFor(numerator_space, numerator.Style(), ConstraintSpace()); + auto denominator_space = CreateConstraintSpaceForMathChild( + Node(), child_available_size, ConstraintSpace(), denominator); + scoped_refptr<const NGLayoutResult> denominator_layout_result = + denominator.Layout(denominator_space); + auto denominator_margins = ComputeMarginsFor( + denominator_space, denominator.Style(), ConstraintSpace()); + + NGBoxFragment numerator_fragment( + ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction(), + To<NGPhysicalBoxFragment>(numerator_layout_result->PhysicalFragment())); + NGBoxFragment denominator_fragment( + ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction(), + To<NGPhysicalBoxFragment>(denominator_layout_result->PhysicalFragment())); + + LayoutUnit content_inline_size = std::max( + numerator_fragment.InlineSize() + numerator_margins.InlineSum(), + denominator_fragment.InlineSize() + denominator_margins.InlineSum()); + + LayoutUnit numerator_ascent = + numerator_margins.block_start + + numerator_fragment.Baseline().value_or(numerator_fragment.BlockSize()); + LayoutUnit numerator_descent = numerator_fragment.BlockSize() + + numerator_margins.BlockSum() - + numerator_ascent; + LayoutUnit denominator_ascent = denominator_margins.block_start + + denominator_fragment.Baseline().value_or( + denominator_fragment.BlockSize()); + LayoutUnit denominator_descent = denominator_fragment.BlockSize() + + denominator_margins.BlockSum() - + denominator_ascent; + + LayoutUnit numerator_shift, denominator_shift; + LayoutUnit thickness = FractionLineThickness(Style()); + if (thickness) { + LayoutUnit axis_height = MathAxisHeight(Style()); + FractionParameters parameters = GetFractionParameters(Style()); + numerator_shift = + std::max(parameters.numerator_min_shift_up, + axis_height + thickness / 2 + parameters.numerator_gap_min + + numerator_descent); + denominator_shift = + std::max(parameters.denominator_min_shift_down, + thickness / 2 + parameters.denominator_gap_min + + denominator_ascent - axis_height); + } else { + FractionStackParameters parameters = GetFractionStackParameters(Style()); + numerator_shift = parameters.top_shift_up; + denominator_shift = parameters.bottom_shift_down; + LayoutUnit gap = denominator_shift - denominator_ascent + numerator_shift - + numerator_descent; + if (gap < parameters.gap_min) { + LayoutUnit diff = parameters.gap_min - gap; + LayoutUnit delta = diff / 2; + numerator_shift += delta; + denominator_shift += diff - delta; + } + } + + LayoutUnit fraction_ascent = + std::max(numerator_shift + numerator_ascent, + -denominator_shift + denominator_ascent); + LayoutUnit fraction_descent = + std::max(-numerator_shift + numerator_descent, + denominator_shift + denominator_descent); + fraction_ascent += border_scrollbar_padding_.block_start; + fraction_descent += border_scrollbar_padding_.block_end; + LayoutUnit total_block_size = fraction_ascent + fraction_descent; + + container_builder_.SetBaseline(fraction_ascent); + + LogicalOffset numerator_offset; + LogicalOffset denominator_offset; + numerator_offset.inline_offset = + border_scrollbar_padding_.inline_start + numerator_margins.inline_start + + (content_inline_size - + (numerator_fragment.InlineSize() + numerator_margins.InlineSum())) / + 2; + denominator_offset.inline_offset = + border_scrollbar_padding_.inline_start + + denominator_margins.inline_start + + (content_inline_size - + (denominator_fragment.InlineSize() + denominator_margins.InlineSum())) / + 2; + + numerator_offset.block_offset = numerator_margins.block_start + + fraction_ascent - numerator_shift - + numerator_ascent; + denominator_offset.block_offset = denominator_margins.block_start + + fraction_ascent + denominator_shift - + denominator_ascent; + + container_builder_.AddChild(numerator_layout_result->PhysicalFragment(), + numerator_offset); + container_builder_.AddChild(denominator_layout_result->PhysicalFragment(), + denominator_offset); + + numerator.StoreMargins(ConstraintSpace(), numerator_margins); + denominator.StoreMargins(ConstraintSpace(), denominator_margins); + + LayoutUnit block_size = ComputeBlockSizeForFragment( + ConstraintSpace(), Style(), border_scrollbar_padding_, total_block_size); + + container_builder_.SetIntrinsicBlockSize(total_block_size); + container_builder_.SetBlockSize(block_size); + + NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), container_builder_.Borders(), + &container_builder_) + .Run(); + + return container_builder_.ToBoxFragment(); +} + +base::Optional<MinMaxSizes> NGMathFractionLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + base::Optional<MinMaxSizes> sizes = + CalculateMinMaxSizesIgnoringChildren(Node(), border_scrollbar_padding_); + if (sizes) + return sizes; + + sizes.emplace(); + LayoutUnit child_percentage_resolution_block_size = + CalculateChildPercentageBlockSizeForMinMax( + ConstraintSpace(), Node(), border_scrollbar_padding_, + input.percentage_resolution_block_size); + + MinMaxSizesInput child_input(child_percentage_resolution_block_size); + + for (NGLayoutInputNode child = Node().FirstChild(); child; + child = child.NextSibling()) { + if (child.IsOutOfFlowPositioned()) + continue; + auto child_sizes = + ComputeMinAndMaxContentContribution(Style(), child, child_input); + NGBoxStrut margins = ComputeMinMaxMargins(Style(), child); + child_sizes += margins.InlineSum(); + sizes->Encompass(child_sizes); + } + + *sizes += border_scrollbar_padding_.InlineSum(); + return sizes; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h new file mode 100644 index 00000000000..d645439465f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_FRACTION_LAYOUT_ALGORITHM_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_FRACTION_LAYOUT_ALGORITHM_H_ + +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h" + +namespace blink { + +class CORE_EXPORT NGMathFractionLayoutAlgorithm + : public NGLayoutAlgorithm<NGBlockNode, + NGBoxFragmentBuilder, + NGBlockBreakToken> { + public: + explicit NGMathFractionLayoutAlgorithm(const NGLayoutAlgorithmParams& params); + + private: + scoped_refptr<const NGLayoutResult> Layout() final; + + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const final; + + void GatherChildren(NGBlockNode* numerator, NGBlockNode* denominator); + const NGBoxStrut border_scrollbar_padding_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_FRACTION_LAYOUT_ALGORITHM_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.cc new file mode 100644 index 00000000000..44f6ed8d617 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.cc @@ -0,0 +1,97 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h" + +#include "third_party/blink/renderer/core/layout/layout_box.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" +#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" +#include "third_party/blink/renderer/core/mathml/mathml_fraction_element.h" + +namespace blink { + +NGConstraintSpace CreateConstraintSpaceForMathChild( + const NGBlockNode& parent_node, + const LogicalSize& child_available_size, + const NGConstraintSpace& parent_constraint_space, + const NGLayoutInputNode& child) { + const ComputedStyle& parent_style = parent_node.Style(); + const ComputedStyle& child_style = child.Style(); + DCHECK(child.CreatesNewFormattingContext()); + NGConstraintSpaceBuilder space_builder(parent_constraint_space, + child_style.GetWritingMode(), + true /* is_new_fc */); + SetOrthogonalFallbackInlineSizeIfNeeded(parent_style, child, &space_builder); + + space_builder.SetAvailableSize(child_available_size); + space_builder.SetPercentageResolutionSize(child_available_size); + space_builder.SetReplacedPercentageResolutionSize(child_available_size); + + space_builder.SetIsShrinkToFit(child_style.LogicalWidth().IsAuto()); + + // TODO(rbuis): add target stretch sizes. + + space_builder.SetTextDirection(child_style.Direction()); + + // TODO(rbuis): add ink baselines? + space_builder.SetNeedsBaseline(true); + return space_builder.ToConstraintSpace(); +} + +NGLayoutInputNode FirstChildInFlow(const NGBlockNode& node) { + NGLayoutInputNode child = node.FirstChild(); + while (child && child.IsOutOfFlowPositioned()) + child = child.NextSibling(); + return child; +} + +NGLayoutInputNode NextSiblingInFlow(const NGBlockNode& node) { + NGLayoutInputNode sibling = node.NextSibling(); + while (sibling && sibling.IsOutOfFlowPositioned()) + sibling = sibling.NextSibling(); + return sibling; +} + +inline bool InFlowChildCountIs(const NGBlockNode& node, unsigned count) { + DCHECK(count == 2 || count == 3); + auto child = To<NGBlockNode>(FirstChildInFlow(node)); + while (count && child) { + child = To<NGBlockNode>(NextSiblingInFlow(child)); + count--; + } + return !count && !child; +} + +bool IsValidMathMLFraction(const NGBlockNode& node) { + return InFlowChildCountIs(node, 2); +} + +namespace { + +inline LayoutUnit DefaultFractionLineThickness(const ComputedStyle& style) { + return LayoutUnit( + MathConstant(style, + OpenTypeMathSupport::MathConstants::kFractionRuleThickness) + .value_or(RuleThicknessFallback(style))); +} + +} // namespace + +LayoutUnit MathAxisHeight(const ComputedStyle& style) { + return LayoutUnit( + MathConstant(style, OpenTypeMathSupport::MathConstants::kAxisHeight) + .value_or(style.GetFont().PrimaryFont()->GetFontMetrics().XHeight() / + 2)); +} + +LayoutUnit FractionLineThickness(const ComputedStyle& style) { + return std::max<LayoutUnit>( + ValueForLength(style.GetMathFractionBarThickness(), + DefaultFractionLineThickness(style)), + LayoutUnit()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h new file mode 100644 index 00000000000..366e1a81171 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h @@ -0,0 +1,54 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_LAYOUT_UTILS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_LAYOUT_UTILS_H_ + +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h" + +namespace blink { + +struct LogicalSize; +class NGBlockNode; +class NGConstraintSpace; +class NGLayoutInputNode; + +// Creates a new constraint space for the current child. +NGConstraintSpace CreateConstraintSpaceForMathChild( + const NGBlockNode& parent_node, + const LogicalSize& child_available_size, + const NGConstraintSpace& parent_constraint_space, + const NGLayoutInputNode&); + +NGLayoutInputNode FirstChildInFlow(const NGBlockNode&); +NGLayoutInputNode NextSiblingInFlow(const NGBlockNode&); + +bool IsValidMathMLFraction(const NGBlockNode&); + +inline float RuleThicknessFallback(const ComputedStyle& style) { + // This function returns a value for the default rule thickness (TeX's + // \xi_8) to be used as a fallback when we lack a MATH table. + return 0.05f * style.FontSize(); +} + +LayoutUnit MathAxisHeight(const ComputedStyle& style); + +inline base::Optional<float> MathConstant( + const ComputedStyle& style, + OpenTypeMathSupport::MathConstants constant) { + return OpenTypeMathSupport::MathConstant( + style.GetFont().PrimaryFont()->PlatformData().GetHarfBuzzFace(), + constant); +} + +LayoutUnit FractionLineThickness(const ComputedStyle& style); + +inline bool HasDisplayStyle(const ComputedStyle& style) { + return style.MathStyle() == EMathStyle::kDisplay; +} + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_LAYOUT_UTILS_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.cc new file mode 100644 index 00000000000..6fe8785a1cc --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.cc @@ -0,0 +1,180 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h" + +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h" +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/core/mathml/mathml_element.h" + +namespace blink { +namespace { + +inline LayoutUnit InlineOffsetForDisplayMathCentering( + bool is_display_math, + LayoutUnit available_inline_size, + LayoutUnit max_row_inline_size) { + if (is_display_math) + return (available_inline_size - max_row_inline_size) / 2; + return LayoutUnit(); +} + +} // namespace + +NGMathRowLayoutAlgorithm::NGMathRowLayoutAlgorithm( + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding), + border_scrollbar_padding_(border_padding_ + + params.fragment_geometry.scrollbar) { + DCHECK(params.space.IsNewFormattingContext()); + DCHECK(!ConstraintSpace().HasBlockFragmentation()); + container_builder_.SetIsNewFormattingContext( + params.space.IsNewFormattingContext()); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); +} + +void NGMathRowLayoutAlgorithm::LayoutRowItems( + NGContainerFragmentBuilder::ChildrenVector* children, + LayoutUnit* max_row_block_baseline, + LogicalSize* row_total_size) { + LayoutUnit inline_offset, max_row_ascent, max_row_descent; + for (NGLayoutInputNode child = Node().FirstChild(); child; + child = child.NextSibling()) { + if (child.IsOutOfFlowPositioned()) { + // TODO(rbuis): OOF should be "where child would have been if not + // absolutely positioned". + // Issue: https://github.com/mathml-refresh/mathml/issues/16 + container_builder_.AddOutOfFlowChildCandidate( + To<NGBlockNode>(child), {border_scrollbar_padding_.inline_start, + border_scrollbar_padding_.block_start}); + continue; + } + const ComputedStyle& child_style = child.Style(); + NGConstraintSpace child_space = CreateConstraintSpaceForMathChild( + Node(), child_available_size_, ConstraintSpace(), child); + scoped_refptr<const NGLayoutResult> result = + To<NGBlockNode>(child).Layout(child_space, nullptr /* break token */); + const NGPhysicalContainerFragment& physical_fragment = + result->PhysicalFragment(); + NGBoxFragment fragment(ConstraintSpace().GetWritingMode(), + ConstraintSpace().Direction(), + To<NGPhysicalBoxFragment>(physical_fragment)); + + NGBoxStrut margins = + ComputeMarginsFor(child_space, child_style, ConstraintSpace()); + inline_offset += margins.inline_start; + + LayoutUnit ascent = margins.block_start + + fragment.Baseline().value_or(fragment.BlockSize()); + *max_row_block_baseline = std::max(*max_row_block_baseline, ascent); + + // TODO(rbuis): Operators can add lspace and rspace. + + children->emplace_back( + LogicalOffset{inline_offset, margins.block_start - ascent}, + &physical_fragment); + + inline_offset += fragment.InlineSize() + margins.inline_end; + + max_row_ascent = std::max(max_row_ascent, ascent + margins.block_start); + max_row_descent = std::max( + max_row_descent, fragment.BlockSize() + margins.block_end - ascent); + row_total_size->inline_size = + std::max(row_total_size->inline_size, inline_offset); + } + row_total_size->block_size = max_row_ascent + max_row_descent; +} + +scoped_refptr<const NGLayoutResult> NGMathRowLayoutAlgorithm::Layout() { + DCHECK(!BreakToken()); + + bool is_display_math = + Node().IsMathRoot() && Style().Display() == EDisplay::kMath; + + LogicalSize max_row_size; + LayoutUnit max_row_block_baseline; + + const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); + child_available_size_ = + ShrinkAvailableSize(border_box_size, border_scrollbar_padding_); + + NGContainerFragmentBuilder::ChildrenVector children; + LayoutRowItems(&children, &max_row_block_baseline, &max_row_size); + + // Add children taking into account centering, baseline and + // border/scrollbar/padding. + LayoutUnit center_offset = InlineOffsetForDisplayMathCentering( + is_display_math, container_builder_.InlineSize(), + max_row_size.inline_size); + LogicalOffset adjust_offset( + border_scrollbar_padding_.inline_start + center_offset, + border_scrollbar_padding_.block_start + max_row_block_baseline); + for (auto& child : children) { + child.offset += adjust_offset; + container_builder_.AddChild( + To<NGPhysicalContainerFragment>(*child.fragment), child.offset); + } + + container_builder_.SetBaseline(border_scrollbar_padding_.block_start + + max_row_block_baseline); + + auto block_size = ComputeBlockSizeForFragment( + ConstraintSpace(), Style(), border_padding_, + max_row_size.block_size + border_scrollbar_padding_.BlockSum()); + container_builder_.SetBlockSize(block_size); + + NGOutOfFlowLayoutPart( + Node(), ConstraintSpace(), + container_builder_.Borders() + container_builder_.Scrollbar(), + &container_builder_) + .Run(); + + return container_builder_.ToBoxFragment(); +} + +base::Optional<MinMaxSizes> NGMathRowLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + base::Optional<MinMaxSizes> sizes = + CalculateMinMaxSizesIgnoringChildren(Node(), border_scrollbar_padding_); + if (sizes) + return sizes; + + sizes.emplace(); + LayoutUnit child_percentage_resolution_block_size = + CalculateChildPercentageBlockSizeForMinMax( + ConstraintSpace(), Node(), border_padding_, + input.percentage_resolution_block_size); + + MinMaxSizesInput child_input(child_percentage_resolution_block_size); + + for (NGLayoutInputNode child = Node().FirstChild(); child; + child = child.NextSibling()) { + if (child.IsOutOfFlowPositioned()) + continue; + MinMaxSizes child_min_max_sizes = + ComputeMinAndMaxContentContribution(Style(), child, child_input); + NGBoxStrut child_margins = ComputeMinMaxMargins(Style(), child); + child_min_max_sizes += child_margins.InlineSum(); + sizes->max_size += child_min_max_sizes.max_size; + sizes->min_size += child_min_max_sizes.min_size; + + // TODO(rbuis): Operators can add lspace and rspace. + } + sizes->max_size = std::max(sizes->max_size, sizes->min_size); + + // Due to negative margins, it is possible that we calculated a negative + // intrinsic width. Make sure that we never return a negative width. + sizes->Encompass(LayoutUnit()); + *sizes += border_scrollbar_padding_.InlineSum(); + return sizes; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h new file mode 100644 index 00000000000..72b8dd10b40 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h @@ -0,0 +1,42 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_ROW_LAYOUT_ALGORITHM_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_ROW_LAYOUT_ALGORITHM_H_ + +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" + +namespace blink { + +class LayoutUnit; + +class CORE_EXPORT NGMathRowLayoutAlgorithm + : public NGLayoutAlgorithm<NGBlockNode, + NGBoxFragmentBuilder, + NGBlockBreakToken> { + public: + NGMathRowLayoutAlgorithm(const NGLayoutAlgorithmParams& params); + + protected: + void LayoutRowItems(NGContainerFragmentBuilder::ChildrenVector*, + LayoutUnit* max_row_block_baseline, + LogicalSize* row_total_size); + + private: + scoped_refptr<const NGLayoutResult> Layout() final; + + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const final; + + LogicalSize child_available_size_; + const NGBoxStrut border_padding_; + const NGBoxStrut border_scrollbar_padding_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_ROW_LAYOUT_ALGORITHM_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.cc new file mode 100644 index 00000000000..4abfa765870 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.cc @@ -0,0 +1,42 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h" + +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" + +namespace blink { + +NGMathSpaceLayoutAlgorithm::NGMathSpaceLayoutAlgorithm( + const NGLayoutAlgorithmParams& params) + : NGLayoutAlgorithm(params), + border_padding_(params.fragment_geometry.border + + params.fragment_geometry.padding) { + DCHECK(params.fragment_geometry.scrollbar.IsEmpty()); + container_builder_.SetIsNewFormattingContext(true); + container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); +} + +scoped_refptr<const NGLayoutResult> NGMathSpaceLayoutAlgorithm::Layout() { + DCHECK(!BreakToken()); + + LayoutUnit block_size = ComputeBlockSizeForFragment( + ConstraintSpace(), Style(), border_padding_, border_padding_.BlockSum()); + + container_builder_.SetIntrinsicBlockSize(border_padding_.BlockSum()); + container_builder_.SetBlockSize(block_size); + + container_builder_.SetBaseline( + border_padding_.block_start + + ValueForLength(Style().GetMathBaseline(), LayoutUnit())); + return container_builder_.ToBoxFragment(); +} + +base::Optional<MinMaxSizes> NGMathSpaceLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + return CalculateMinMaxSizesIgnoringChildren(Node(), border_padding_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h new file mode 100644 index 00000000000..7b493ef0b17 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_SPACE_LAYOUT_ALGORITHM_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_SPACE_LAYOUT_ALGORITHM_H_ + +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" + +namespace blink { + +class CORE_EXPORT NGMathSpaceLayoutAlgorithm + : public NGLayoutAlgorithm<NGBlockNode, + NGBoxFragmentBuilder, + NGBlockBreakToken> { + public: + explicit NGMathSpaceLayoutAlgorithm(const NGLayoutAlgorithmParams& params); + + private: + scoped_refptr<const NGLayoutResult> Layout() final; + + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const final; + + const NGBoxStrut border_padding_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_SPACE_LAYOUT_ALGORITHM_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc index d6c66b0b708..fd2180c4c50 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc @@ -31,7 +31,7 @@ bool IsLogicalWidthTreatedAsAuto(const ComputedStyle& style) { return IsTable(style) || style.LogicalWidth().IsAuto(); } -bool IsLogicalHeightTreatAsAuto(const ComputedStyle& style) { +bool IsLogicalHeightTreatedAsAuto(const ComputedStyle& style) { return IsTable(style) || style.LogicalHeight().IsAuto(); } @@ -111,11 +111,11 @@ inline LayoutUnit StaticPositionEndInset(StaticPositionEdge edge, } LayoutUnit ComputeShrinkToFitSize( - const base::Optional<MinMaxSize>& child_minmax, + const base::Optional<MinMaxSizes>& min_max_sizes, LayoutUnit computed_available_size, LayoutUnit margin_start, LayoutUnit margin_end) { - return child_minmax->ShrinkToFit( + return min_max_sizes->ShrinkToFit( (computed_available_size - margin_start - margin_end) .ClampNegativeToZero()); } @@ -123,10 +123,10 @@ LayoutUnit ComputeShrinkToFitSize( // Implement the absolute size resolution algorithm. // https://www.w3.org/TR/css-position-3/#abs-non-replaced-width // https://www.w3.org/TR/css-position-3/#abs-non-replaced-height -// |child_minmax| can have no value if an element is replaced, and has no +// |min_max_sizes| can have no value if an element is replaced, and has no // intrinsic width or height, but has an aspect ratio. void ComputeAbsoluteSize(const LayoutUnit border_padding_size, - const base::Optional<MinMaxSize>& child_minmax, + const base::Optional<MinMaxSizes>& min_max_sizes, const LayoutUnit margin_percentage_resolution_size, const LayoutUnit available_size, const Length& margin_start_length, @@ -199,7 +199,7 @@ void ComputeAbsoluteSize(const LayoutUnit border_padding_size, computed_available_size = static_position_offset; break; } - size = ComputeShrinkToFitSize(child_minmax, computed_available_size, + size = ComputeShrinkToFitSize(min_max_sizes, computed_available_size, *margin_start, *margin_end); LayoutUnit margin_size = *size + *margin_start + *margin_end; if (is_start_dominant) { @@ -259,7 +259,7 @@ void ComputeAbsoluteSize(const LayoutUnit border_padding_size, // Rule 1: left/width are unknown. DCHECK(inset_end.has_value()); LayoutUnit computed_available_size = available_size - *inset_end; - size = ComputeShrinkToFitSize(child_minmax, computed_available_size, + size = ComputeShrinkToFitSize(min_max_sizes, computed_available_size, *margin_start, *margin_end); } else if (!inset_start && !inset_end) { // Rule 2. @@ -276,7 +276,7 @@ void ComputeAbsoluteSize(const LayoutUnit border_padding_size, } else if (!size && !inset_end) { // Rule 3. LayoutUnit computed_available_size = available_size - *inset_start; - size = ComputeShrinkToFitSize(child_minmax, computed_available_size, + size = ComputeShrinkToFitSize(min_max_sizes, computed_available_size, *margin_start, *margin_end); } @@ -300,7 +300,7 @@ void ComputeAbsoluteSize(const LayoutUnit border_padding_size, // is safe to recursively call ourselves here because on the second call it // is guaranteed to be within |min_size| and |max_size|. ComputeAbsoluteSize( - border_padding_size, child_minmax, margin_percentage_resolution_size, + border_padding_size, min_max_sizes, margin_percentage_resolution_size, available_size, margin_start_length, margin_end_length, inset_start_length, inset_end_length, min_size, max_size, static_position_offset, static_position_edge, is_start_dominant, @@ -334,7 +334,7 @@ bool AbsoluteNeedsChildBlockSize(const ComputedStyle& style) { return is_logical_height_intrinsic || style.LogicalMinHeight().IsIntrinsic() || style.LogicalMaxHeight().IsIntrinsic() || - (IsLogicalHeightTreatAsAuto(style) && + (IsLogicalHeightTreatedAsAuto(style) && (style.LogicalTop().IsAuto() || style.LogicalBottom().IsAuto())); } @@ -382,30 +382,31 @@ base::Optional<LayoutUnit> ComputeAbsoluteDialogYPosition( return top; } -NGLogicalOutOfFlowPosition ComputePartialAbsoluteWithChildInlineSize( +void ComputeOutOfFlowInlineDimensions( const NGConstraintSpace& space, const ComputedStyle& style, const NGBoxStrut& border_padding, const NGLogicalStaticPosition& static_position, - const base::Optional<MinMaxSize>& child_minmax, + const base::Optional<MinMaxSizes>& min_max_sizes, const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, - const TextDirection container_direction) { - NGLogicalOutOfFlowPosition position; + const TextDirection container_direction, + NGLogicalOutOfFlowDimensions* dimensions) { + DCHECK(dimensions); base::Optional<LayoutUnit> inline_size; if (!IsLogicalWidthTreatedAsAuto(style)) { inline_size = ResolveMainInlineLength(space, style, border_padding, - child_minmax, style.LogicalWidth()); + min_max_sizes, style.LogicalWidth()); } else if (replaced_size.has_value()) { inline_size = replaced_size->inline_size; } LayoutUnit min_inline_size = ResolveMinInlineLength( - space, style, border_padding, child_minmax, style.LogicalMinWidth(), + space, style, border_padding, min_max_sizes, style.LogicalMinWidth(), LengthResolvePhase::kLayout); LayoutUnit max_inline_size = ResolveMaxInlineLength( - space, style, border_padding, child_minmax, style.LogicalMaxWidth(), + space, style, border_padding, min_max_sizes, style.LogicalMaxWidth(), LengthResolvePhase::kLayout); // Tables use the inline-size as a minimum. @@ -413,7 +414,7 @@ NGLogicalOutOfFlowPosition ComputePartialAbsoluteWithChildInlineSize( min_inline_size = std::max(min_inline_size, ResolveMainInlineLength(space, style, border_padding, - child_minmax, style.LogicalWidth())); + min_max_sizes, style.LogicalWidth())); } bool is_start_dominant; @@ -428,20 +429,19 @@ NGLogicalOutOfFlowPosition ComputePartialAbsoluteWithChildInlineSize( } ComputeAbsoluteSize( - border_padding.InlineSum(), child_minmax, + border_padding.InlineSum(), min_max_sizes, space.PercentageResolutionInlineSizeForParentWritingMode(), space.AvailableSize().inline_size, style.MarginStart(), style.MarginEnd(), style.LogicalInlineStart(), style.LogicalInlineEnd(), min_inline_size, max_inline_size, static_position.offset.inline_offset, GetStaticPositionEdge(static_position.inline_edge), is_start_dominant, - false /* is_block_direction */, inline_size, &position.size.inline_size, - &position.inset.inline_start, &position.inset.inline_end, - &position.margins.inline_start, &position.margins.inline_end); - - return position; + false /* is_block_direction */, inline_size, + &dimensions->size.inline_size, &dimensions->inset.inline_start, + &dimensions->inset.inline_end, &dimensions->margins.inline_start, + &dimensions->margins.inline_end); } -void ComputeFullAbsoluteWithChildBlockSize( +void ComputeOutOfFlowBlockDimensions( const NGConstraintSpace& space, const ComputedStyle& style, const NGBoxStrut& border_padding, @@ -450,20 +450,20 @@ void ComputeFullAbsoluteWithChildBlockSize( const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, const TextDirection container_direction, - NGLogicalOutOfFlowPosition* position) { + NGLogicalOutOfFlowDimensions* dimensions) { // After partial size has been computed, child block size is either unknown, // or fully computed, there is no minmax. To express this, a 'fixed' minmax // is created where min and max are the same. - base::Optional<MinMaxSize> child_minmax; + base::Optional<MinMaxSizes> min_max_sizes; if (child_block_size.has_value()) { - child_minmax = MinMaxSize{*child_block_size, *child_block_size}; + min_max_sizes = MinMaxSizes{*child_block_size, *child_block_size}; } LayoutUnit child_block_size_or_indefinite = child_block_size.value_or(kIndefiniteSize); base::Optional<LayoutUnit> block_size; - if (!IsLogicalHeightTreatAsAuto(style)) { + if (!IsLogicalHeightTreatedAsAuto(style)) { block_size = ResolveMainBlockLength( space, style, border_padding, style.LogicalHeight(), child_block_size_or_indefinite, LengthResolvePhase::kLayout); @@ -473,10 +473,10 @@ void ComputeFullAbsoluteWithChildBlockSize( LayoutUnit min_block_size = ResolveMinBlockLength( space, style, border_padding, style.LogicalMinHeight(), - child_block_size_or_indefinite, LengthResolvePhase::kLayout); + LengthResolvePhase::kLayout); LayoutUnit max_block_size = ResolveMaxBlockLength( space, style, border_padding, style.LogicalMaxHeight(), - child_block_size_or_indefinite, LengthResolvePhase::kLayout); + LengthResolvePhase::kLayout); bool is_start_dominant; if (style.GetWritingMode() == WritingMode::kHorizontalTb) { @@ -490,15 +490,15 @@ void ComputeFullAbsoluteWithChildBlockSize( } ComputeAbsoluteSize( - border_padding.BlockSum(), child_minmax, + border_padding.BlockSum(), min_max_sizes, space.PercentageResolutionInlineSizeForParentWritingMode(), space.AvailableSize().block_size, style.MarginBefore(), style.MarginAfter(), style.LogicalTop(), style.LogicalBottom(), min_block_size, max_block_size, static_position.offset.block_offset, GetStaticPositionEdge(static_position.block_edge), is_start_dominant, - true /* is_block_direction */, block_size, &position->size.block_size, - &position->inset.block_start, &position->inset.block_end, - &position->margins.block_start, &position->margins.block_end); + true /* is_block_direction */, block_size, &dimensions->size.block_size, + &dimensions->inset.block_start, &dimensions->inset.block_end, + &dimensions->margins.block_start, &dimensions->margins.block_end); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h index bcfa41fc6a7..5c2f3694cb8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h @@ -9,7 +9,7 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/geometry/physical_size.h" -#include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" namespace blink { @@ -19,7 +19,7 @@ class LayoutObject; class NGConstraintSpace; struct NGLogicalStaticPosition; -struct CORE_EXPORT NGLogicalOutOfFlowPosition { +struct CORE_EXPORT NGLogicalOutOfFlowDimensions { NGBoxStrut inset; LogicalSize size; NGBoxStrut margins; @@ -36,43 +36,43 @@ CORE_EXPORT base::Optional<LayoutUnit> ComputeAbsoluteDialogYPosition( // The following routines implement the absolute size resolution algorithm. // https://www.w3.org/TR/css-position-3/#abs-non-replaced-width // -// The size is computed as |NGLogicalOutOfFlowPosition|. +// The size is computed as |NGLogicalOutOfFlowDimensions|. // It needs to be computed in 4 stages: // 1. If |AbsoluteNeedsChildInlineSize| is true, compute estimated inline_size -// using |NGBlockNode::MinMaxSize|. -// 2. Compute part of the |NGLogicalOutOfFlowPosition| which depends on the -// child inline-size with |ComputePartialAbsoluteWithChildInlineSize|. +// using |NGBlockNode::ComputeMinMaxSize|. +// 2. Compute part of the |NGLogicalOutOfFlowDimensions| which depends on the +// child inline-size with |ComputeOutOfFlowInlineDimensions|. // 3. If |AbsoluteNeedsChildBlockSize| is true, compute estimated block_size by // performing layout with the inline_size calculated from (2). -// 4. Compute the full |NGLogicalOutOfFlowPosition| with -// |ComputeFullAbsoluteWithChildBlockSize|. +// 4. Compute the full |NGLogicalOutOfFlowDimensions| with +// |ComputeOutOfFlowBlockDimensions|. -// Returns true if |ComputePartialAbsoluteWithChildInlineSize| will need an -// estimated inline-size. +// Returns true if |ComputeOutOfFlowInlineDimensions| will need an estimated +// inline-size. CORE_EXPORT bool AbsoluteNeedsChildInlineSize(const ComputedStyle&); -// Returns true if |ComputeFullAbsoluteWithChildBlockSize| will need an -// estimated block-size. +// Returns true if |ComputeOutOfFlowBlockDimensions| will need an estimated +// block-size. CORE_EXPORT bool AbsoluteNeedsChildBlockSize(const ComputedStyle&); // Computes part of the absolute position which depends on the child's // inline-size. // |replaced_size| should be set if and only if element is replaced element. // Returns the partially filled position. -CORE_EXPORT NGLogicalOutOfFlowPosition -ComputePartialAbsoluteWithChildInlineSize( +CORE_EXPORT void ComputeOutOfFlowInlineDimensions( const NGConstraintSpace&, const ComputedStyle&, const NGBoxStrut& border_padding, const NGLogicalStaticPosition&, - const base::Optional<MinMaxSize>& child_minmax, + const base::Optional<MinMaxSizes>& child_minmax, const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, - const TextDirection container_direction); + const TextDirection container_direction, + NGLogicalOutOfFlowDimensions* dimensions); // Computes the rest of the absolute position which depends on child's // block-size. -CORE_EXPORT void ComputeFullAbsoluteWithChildBlockSize( +CORE_EXPORT void ComputeOutOfFlowBlockDimensions( const NGConstraintSpace&, const ComputedStyle&, const NGBoxStrut& border_padding, @@ -81,7 +81,7 @@ CORE_EXPORT void ComputeFullAbsoluteWithChildBlockSize( const base::Optional<LogicalSize>& replaced_size, const WritingMode container_writing_mode, const TextDirection container_direction, - NGLogicalOutOfFlowPosition* position); + NGLogicalOutOfFlowDimensions* dimensions); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc index 86af90137d8..dac7b8f79c4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc @@ -118,10 +118,10 @@ TEST_F(NGAbsoluteUtilsTest, Horizontal) { LayoutUnit width = container_size_.inline_size - left - margin_left - right - margin_right; - base::Optional<MinMaxSize> estimated_inline; + base::Optional<MinMaxSizes> estimated_inline; base::Optional<LayoutUnit> estimated_block; - MinMaxSize minmax_60{LayoutUnit(60) + horizontal_border_padding, - LayoutUnit(60) + horizontal_border_padding}; + MinMaxSizes min_max_60{LayoutUnit(60) + horizontal_border_padding, + LayoutUnit(60) + horizontal_border_padding}; style_->SetBorderLeftWidth(border_left.ToInt()); style_->SetBorderRightWidth(border_right.ToInt()); @@ -154,151 +154,154 @@ TEST_F(NGAbsoluteUtilsTest, Horizontal) { // Tests. // - NGLogicalOutOfFlowPosition p; + NGLogicalOutOfFlowDimensions dimensions; // All auto => width is estimated_inline, left is 0. SetHorizontalStyle(NGAuto, NGAuto, NGAuto, NGAuto, NGAuto); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), true); - estimated_inline = minmax_60; - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(minmax_60.min_size, p.size.inline_size); - EXPECT_EQ(LayoutUnit(0), p.inset.inline_start); + estimated_inline = min_max_60; + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min_max_60.min_size, dimensions.size.inline_size); + EXPECT_EQ(LayoutUnit(0), dimensions.inset.inline_start); // All auto => width is estimated_inline, static_position is right SetHorizontalStyle(NGAuto, NGAuto, NGAuto, NGAuto, NGAuto); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), true); - estimated_inline = minmax_60; - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position_inline_end, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(minmax_60.min_size, p.size.inline_size); - EXPECT_EQ(container_size_.inline_size, p.inset.inline_end); + estimated_inline = min_max_60; + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position_inline_end, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min_max_60.min_size, dimensions.size.inline_size); + EXPECT_EQ(container_size_.inline_size, dimensions.inset.inline_end); // All auto + RTL. - p = ComputePartialAbsoluteWithChildInlineSize( - rtl_space_, *style_, rtl_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(minmax_60.min_size, p.size.inline_size); - EXPECT_EQ(container_size_.inline_size - minmax_60.min_size, - p.inset.inline_end); + ComputeOutOfFlowInlineDimensions(rtl_space_, *style_, rtl_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min_max_60.min_size, dimensions.size.inline_size); + EXPECT_EQ(container_size_.inline_size - min_max_60.min_size, + dimensions.inset.inline_end); // left, right, and left are known, compute margins. SetHorizontalStyle(left, NGAuto, width, NGAuto, right); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - LayoutUnit margin_space = - (container_size_.inline_size - left - right - p.size.inline_size) / 2; - EXPECT_EQ(left + margin_space, p.inset.inline_start); - EXPECT_EQ(right + margin_space, p.inset.inline_end); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + LayoutUnit margin_space = (container_size_.inline_size - left - right - + dimensions.size.inline_size) / + 2; + EXPECT_EQ(left + margin_space, dimensions.inset.inline_start); + EXPECT_EQ(right + margin_space, dimensions.inset.inline_end); // left, right, and left are known, compute margins, writing mode vertical_lr. SetHorizontalStyle(left, NGAuto, width, NGAuto, right, WritingMode::kVerticalLr); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); estimated_inline.reset(); - ComputeFullAbsoluteWithChildBlockSize( - vlr_space_, *style_, vlr_border_padding, static_position, estimated_block, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(left + margin_space, p.inset.block_start); - EXPECT_EQ(right + margin_space, p.inset.block_end); + ComputeOutOfFlowBlockDimensions(vlr_space_, *style_, vlr_border_padding, + static_position, estimated_block, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(left + margin_space, dimensions.inset.block_start); + EXPECT_EQ(right + margin_space, dimensions.inset.block_end); // left, right, and left are known, compute margins, writing mode vertical_rl. SetHorizontalStyle(left, NGAuto, width, NGAuto, right, WritingMode::kVerticalRl); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); estimated_inline.reset(); - ComputeFullAbsoluteWithChildBlockSize( - vrl_space_, *style_, vrl_border_padding, static_position, estimated_block, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(left + margin_space, p.inset.block_end); - EXPECT_EQ(right + margin_space, p.inset.block_start); + ComputeOutOfFlowBlockDimensions(vrl_space_, *style_, vrl_border_padding, + static_position, estimated_block, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(left + margin_space, dimensions.inset.block_end); + EXPECT_EQ(right + margin_space, dimensions.inset.block_start); // left, right, and width are known, not enough space for margins LTR. SetHorizontalStyle(left, NGAuto, LayoutUnit(200), NGAuto, right); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(left, p.inset.inline_start); - EXPECT_EQ(-left, p.inset.inline_end); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(left, dimensions.inset.inline_start); + EXPECT_EQ(-left, dimensions.inset.inline_end); // left, right, and left are known, not enough space for margins RTL. SetHorizontalStyle(left, NGAuto, LayoutUnit(200), NGAuto, right, WritingMode::kHorizontalTb); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - rtl_space_, *style_, rtl_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kRtl); - EXPECT_EQ(-right, p.inset.inline_start); - EXPECT_EQ(right, p.inset.inline_end); + ComputeOutOfFlowInlineDimensions(rtl_space_, *style_, rtl_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kRtl, &dimensions); + EXPECT_EQ(-right, dimensions.inset.inline_start); + EXPECT_EQ(right, dimensions.inset.inline_end); // Rule 1 left and width are auto. SetHorizontalStyle(NGAuto, margin_left, NGAuto, margin_right, right); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), true); - estimated_inline = minmax_60; - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(minmax_60.min_size, p.size.inline_size); + estimated_inline = min_max_60; + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min_max_60.min_size, dimensions.size.inline_size); // Rule 2 left and right are auto LTR. SetHorizontalStyle(NGAuto, margin_left, width, margin_right, NGAuto); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(margin_left, p.inset.inline_start); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(margin_left, dimensions.inset.inline_start); EXPECT_EQ(container_size_.inline_size - margin_left - width, - p.inset.inline_end); + dimensions.inset.inline_end); // Rule 2 left and right are auto RTL. SetHorizontalStyle(NGAuto, margin_left, width, margin_right, NGAuto); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - rtl_space_, *style_, rtl_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(margin_left, p.inset.inline_start); + ComputeOutOfFlowInlineDimensions(rtl_space_, *style_, rtl_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(margin_left, dimensions.inset.inline_start); EXPECT_EQ(container_size_.inline_size - margin_left - width, - p.inset.inline_end); + dimensions.inset.inline_end); // Rule 3 width and right are auto. SetHorizontalStyle(left, margin_left, NGAuto, margin_right, NGAuto); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), true); - estimated_inline = minmax_60; - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); + estimated_inline = min_max_60; + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); EXPECT_EQ( - container_size_.inline_size - minmax_60.min_size - left - margin_left, - p.inset.inline_end); - EXPECT_EQ(minmax_60.min_size, p.size.inline_size); + container_size_.inline_size - min_max_60.min_size - left - margin_left, + dimensions.inset.inline_end); + EXPECT_EQ(min_max_60.min_size, dimensions.size.inline_size); // Rule 4: left is auto. SetHorizontalStyle(NGAuto, margin_left, width, margin_right, right); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(left + margin_left, p.inset.inline_start); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(left + margin_left, dimensions.inset.inline_start); // Rule 4: left is auto, EBoxSizing::kContentBox style_->SetBoxSizing(EBoxSizing::kContentBox); @@ -307,32 +310,32 @@ TEST_F(NGAbsoluteUtilsTest, Horizontal) { margin_right, right); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(left + margin_left, p.inset.inline_start); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(left + margin_left, dimensions.inset.inline_start); style_->SetBoxSizing(EBoxSizing::kBorderBox); // Rule 5: right is auto. SetHorizontalStyle(left, margin_left, width, margin_right, NGAuto); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(right + margin_right, p.inset.inline_end); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(right + margin_right, dimensions.inset.inline_end); // Rule 6: width is auto. SetHorizontalStyle(left, margin_left, NGAuto, margin_right, right); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); estimated_inline.reset(); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(width, p.size.inline_size); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(width, dimensions.size.inline_size); } TEST_F(NGAbsoluteUtilsTest, Vertical) { @@ -365,7 +368,7 @@ TEST_F(NGAbsoluteUtilsTest, Vertical) { style_->SetBorderRightWidth(0); base::Optional<LayoutUnit> auto_height; - MinMaxSize minmax_60{LayoutUnit(60), LayoutUnit(60)}; + MinMaxSizes min_max_60{LayoutUnit(60), LayoutUnit(60)}; NGBoxStrut ltr_border_padding = ComputeBordersForTest(*style_) + ComputePadding(ltr_space_, *style_); @@ -387,133 +390,145 @@ TEST_F(NGAbsoluteUtilsTest, Vertical) { // Tests // - NGLogicalOutOfFlowPosition p; + NGLogicalOutOfFlowDimensions dimensions; // All auto, compute margins. SetVerticalStyle(NGAuto, NGAuto, NGAuto, NGAuto, NGAuto); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), true); auto_height = LayoutUnit(60); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(*auto_height, p.size.block_size); - EXPECT_EQ(LayoutUnit(0), p.inset.block_start); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(*auto_height, dimensions.size.block_size); + EXPECT_EQ(LayoutUnit(0), dimensions.inset.block_start); // All auto, static position bottom - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position_block_end, - auto_height, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr, &p); - EXPECT_EQ(container_size_.block_size, p.inset.block_end); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position_block_end, auto_height, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(container_size_.block_size, dimensions.inset.block_end); // If top, bottom, and height are known, compute margins. SetVerticalStyle(top, NGAuto, height, NGAuto, bottom); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); auto_height.reset(); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); LayoutUnit margin_space = (container_size_.block_size - top - height - bottom) / 2; - EXPECT_EQ(top + margin_space, p.inset.block_start); - EXPECT_EQ(bottom + margin_space, p.inset.block_end); + EXPECT_EQ(top + margin_space, dimensions.inset.block_start); + EXPECT_EQ(bottom + margin_space, dimensions.inset.block_end); // If top, bottom, and height are known, writing mode vertical_lr. SetVerticalStyle(top, NGAuto, height, NGAuto, bottom, WritingMode::kVerticalLr); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); - p = ComputePartialAbsoluteWithChildInlineSize( - vlr_space_, *style_, vlr_border_padding, static_position, minmax_60, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr); - EXPECT_EQ(top + margin_space, p.inset.inline_start); - EXPECT_EQ(bottom + margin_space, p.inset.inline_end); + ComputeOutOfFlowInlineDimensions(vlr_space_, *style_, vlr_border_padding, + static_position, min_max_60, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(top + margin_space, dimensions.inset.inline_start); + EXPECT_EQ(bottom + margin_space, dimensions.inset.inline_end); // If top, bottom, and height are known, writing mode vertical_rl. SetVerticalStyle(top, NGAuto, height, NGAuto, bottom, WritingMode::kVerticalRl); EXPECT_EQ(AbsoluteNeedsChildInlineSize(*style_), false); - p = ComputePartialAbsoluteWithChildInlineSize( - vrl_space_, *style_, vrl_border_padding, static_position, minmax_60, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr); - EXPECT_EQ(top + margin_space, p.inset.inline_start); - EXPECT_EQ(bottom + margin_space, p.inset.inline_end); + ComputeOutOfFlowInlineDimensions(vrl_space_, *style_, vrl_border_padding, + static_position, min_max_60, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(top + margin_space, dimensions.inset.inline_start); + EXPECT_EQ(bottom + margin_space, dimensions.inset.inline_end); // If top, bottom, and height are known, negative auto margins. LayoutUnit negative_margin_space = (container_size_.block_size - top - LayoutUnit(300) - bottom) / 2; SetVerticalStyle(top, NGAuto, LayoutUnit(300), NGAuto, bottom); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(top + negative_margin_space, p.inset.block_start); - EXPECT_EQ(bottom + negative_margin_space, p.inset.block_end); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(top + negative_margin_space, dimensions.inset.block_start); + EXPECT_EQ(bottom + negative_margin_space, dimensions.inset.block_end); // Rule 1: top and height are unknown. SetVerticalStyle(NGAuto, margin_top, NGAuto, margin_bottom, bottom); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), true); auto_height = LayoutUnit(60); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(*auto_height, p.size.block_size); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(*auto_height, dimensions.size.block_size); // Rule 2: top and bottom are unknown. SetVerticalStyle(NGAuto, margin_top, height, margin_bottom, NGAuto); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); auto_height.reset(); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(margin_top, p.inset.block_start); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(margin_top, dimensions.inset.block_start); EXPECT_EQ(container_size_.block_size - margin_top - height, - p.inset.block_end); + dimensions.inset.block_end); // Rule 3: height and bottom are unknown, auto_height < // horizontal_border_padding. SetVerticalStyle(top, margin_top, NGAuto, margin_bottom, NGAuto); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), true); auto_height = LayoutUnit(20); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(horizontal_border_padding, p.size.block_size); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(horizontal_border_padding, dimensions.size.block_size); // Rule 3: height and bottom are unknown. SetVerticalStyle(top, margin_top, NGAuto, margin_bottom, NGAuto); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), true); auto_height = LayoutUnit(70); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(*auto_height, p.size.block_size); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(*auto_height, dimensions.size.block_size); // Rule 4: top is unknown. SetVerticalStyle(NGAuto, margin_top, height, margin_bottom, bottom); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); auto_height.reset(); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(top + margin_top, p.inset.block_start); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(top + margin_top, dimensions.inset.block_start); // Rule 5: bottom is unknown. SetVerticalStyle(top, margin_top, height, margin_bottom, NGAuto); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); auto_height.reset(); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(bottom + margin_bottom, p.inset.block_end); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(bottom + margin_bottom, dimensions.inset.block_end); // Rule 6: height is unknown. SetVerticalStyle(top, margin_top, NGAuto, margin_bottom, bottom); EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), false); auto_height.reset(); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(height, p.size.block_size); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(height, dimensions.size.block_size); } TEST_F(NGAbsoluteUtilsTest, CenterStaticPosition) { @@ -529,28 +544,31 @@ TEST_F(NGAbsoluteUtilsTest, CenterStaticPosition) { EXPECT_EQ(AbsoluteNeedsChildBlockSize(*style_), true); NGBoxStrut border_padding; - NGLogicalOutOfFlowPosition p = ComputePartialAbsoluteWithChildInlineSize( + NGLogicalOutOfFlowDimensions dimensions; + + ComputeOutOfFlowInlineDimensions( ltr_space_, *style_, border_padding, static_position, - MinMaxSize{LayoutUnit(), LayoutUnit(1000)}, base::nullopt, - WritingMode::kHorizontalTb, TextDirection::kLtr); - EXPECT_EQ(LayoutUnit(100), p.size.inline_size); - EXPECT_EQ(LayoutUnit(100), p.inset.inline_start); - EXPECT_EQ(LayoutUnit(), p.inset.inline_end); + MinMaxSizes{LayoutUnit(), LayoutUnit(1000)}, base::nullopt, + WritingMode::kHorizontalTb, TextDirection::kLtr, &dimensions); + EXPECT_EQ(LayoutUnit(100), dimensions.size.inline_size); + EXPECT_EQ(LayoutUnit(100), dimensions.inset.inline_start); + EXPECT_EQ(LayoutUnit(), dimensions.inset.inline_end); - p = ComputePartialAbsoluteWithChildInlineSize( + ComputeOutOfFlowInlineDimensions( ltr_space_, *style_, border_padding, static_position, - MinMaxSize{LayoutUnit(), LayoutUnit(1000)}, base::nullopt, - WritingMode::kHorizontalTb, TextDirection::kRtl); - EXPECT_EQ(LayoutUnit(100), p.size.inline_size); - EXPECT_EQ(LayoutUnit(100), p.inset.inline_start); - EXPECT_EQ(LayoutUnit(), p.inset.inline_end); - - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, border_padding, static_position, LayoutUnit(150), - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(LayoutUnit(150), p.size.block_size); - EXPECT_EQ(LayoutUnit(125), p.inset.block_start); - EXPECT_EQ(LayoutUnit(25), p.inset.block_end); + MinMaxSizes{LayoutUnit(), LayoutUnit(1000)}, base::nullopt, + WritingMode::kHorizontalTb, TextDirection::kRtl, &dimensions); + EXPECT_EQ(LayoutUnit(100), dimensions.size.inline_size); + EXPECT_EQ(LayoutUnit(100), dimensions.inset.inline_start); + EXPECT_EQ(LayoutUnit(), dimensions.inset.inline_end); + + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, border_padding, + static_position, LayoutUnit(150), + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(LayoutUnit(150), dimensions.size.block_size); + EXPECT_EQ(LayoutUnit(125), dimensions.inset.block_start); + EXPECT_EQ(LayoutUnit(25), dimensions.inset.block_end); } TEST_F(NGAbsoluteUtilsTest, MinMax) { @@ -569,34 +587,34 @@ TEST_F(NGAbsoluteUtilsTest, MinMax) { {LayoutUnit(), LayoutUnit()}, NGLogicalStaticPosition::kInlineStart, NGLogicalStaticPosition::kBlockStart}; - MinMaxSize estimated_inline{LayoutUnit(20), LayoutUnit(20)}; - NGLogicalOutOfFlowPosition p; + MinMaxSizes estimated_inline{LayoutUnit(20), LayoutUnit(20)}; + NGLogicalOutOfFlowDimensions dimensions; // WIDTH TESTS // width < min gets set to min. SetHorizontalStyle(NGAuto, NGAuto, LayoutUnit(5), NGAuto, NGAuto); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(min, p.size.inline_size); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min, dimensions.size.inline_size); // width > max gets set to max. SetHorizontalStyle(NGAuto, NGAuto, LayoutUnit(200), NGAuto, NGAuto); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(max, p.size.inline_size); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(max, dimensions.size.inline_size); - // Unspecified width becomes minmax, gets clamped to min. + // Unspecified width becomes min_max, gets clamped to min. SetHorizontalStyle(NGAuto, NGAuto, NGAuto, NGAuto, NGAuto); - p = ComputePartialAbsoluteWithChildInlineSize( - ltr_space_, *style_, ltr_border_padding, static_position, - estimated_inline, base::nullopt, WritingMode::kHorizontalTb, - TextDirection::kLtr); - EXPECT_EQ(min, p.size.inline_size); + ComputeOutOfFlowInlineDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, estimated_inline, + base::nullopt, WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min, dimensions.size.inline_size); // HEIGHT TESTS @@ -604,25 +622,28 @@ TEST_F(NGAbsoluteUtilsTest, MinMax) { // height < min gets set to min. SetVerticalStyle(NGAuto, NGAuto, LayoutUnit(5), NGAuto, NGAuto); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(min, p.size.block_size); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min, dimensions.size.block_size); // height > max gets set to max. SetVerticalStyle(NGAuto, NGAuto, LayoutUnit(200), NGAuto, NGAuto); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(max, p.size.block_size); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(max, dimensions.size.block_size); // // Unspecified height becomes estimated, gets clamped to min. SetVerticalStyle(NGAuto, NGAuto, NGAuto, NGAuto, NGAuto); auto_height = LayoutUnit(20); - ComputeFullAbsoluteWithChildBlockSize( - ltr_space_, *style_, ltr_border_padding, static_position, auto_height, - base::nullopt, WritingMode::kHorizontalTb, TextDirection::kLtr, &p); - EXPECT_EQ(min, p.size.block_size); + ComputeOutOfFlowBlockDimensions(ltr_space_, *style_, ltr_border_padding, + static_position, auto_height, base::nullopt, + WritingMode::kHorizontalTb, + TextDirection::kLtr, &dimensions); + EXPECT_EQ(min, dimensions.size.block_size); } } // namespace diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc index 7f919d2599f..1ddab49c2b9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.cc @@ -8,6 +8,7 @@ #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" @@ -49,8 +50,8 @@ std::pair<scoped_refptr<const NGPhysicalBoxFragment>, NGConstraintSpace> NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithmForElement(Element* element) { auto* block_flow = To<LayoutBlockFlow>(element->GetLayoutObject()); NGBlockNode node(block_flow); - NGConstraintSpace space = NGConstraintSpace::CreateFromLayoutObject( - *block_flow, false /* is_layout_root */); + NGConstraintSpace space = + NGConstraintSpace::CreateFromLayoutObject(*block_flow); NGFragmentGeometry fragment_geometry = CalculateInitialFragmentGeometry(space, node); @@ -61,6 +62,22 @@ NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithmForElement(Element* element) { } scoped_refptr<const NGPhysicalBoxFragment> +NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + NGBlockNode node, + const NGConstraintSpace& space, + const NGBreakToken* break_token) { + NGFragmentGeometry fragment_geometry = + CalculateInitialFragmentGeometry(space, node); + + scoped_refptr<const NGLayoutResult> result = + NGFieldsetLayoutAlgorithm( + {node, fragment_geometry, space, To<NGBlockBreakToken>(break_token)}) + .Layout(); + + return To<NGPhysicalBoxFragment>(&result->PhysicalFragment()); +} + +scoped_refptr<const NGPhysicalBoxFragment> NGBaseLayoutAlgorithmTest::GetBoxFragmentByElementId(const char* id) { LayoutObject* layout_object = GetLayoutObjectByElementId(id); CHECK(layout_object && layout_object->IsLayoutNGMixin()); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h index 702637ee1bb..9f9fe176151 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h @@ -41,6 +41,11 @@ class NGBaseLayoutAlgorithmTest std::pair<scoped_refptr<const NGPhysicalBoxFragment>, NGConstraintSpace> RunBlockLayoutAlgorithmForElement(Element* element); + scoped_refptr<const NGPhysicalBoxFragment> RunFieldsetLayoutAlgorithm( + NGBlockNode node, + const NGConstraintSpace& space, + const NGBreakToken* break_token = nullptr); + scoped_refptr<const NGPhysicalBoxFragment> GetBoxFragmentByElementId( const char*); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc index 6e9781b9bdf..eea8efbb3da 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.cc @@ -12,7 +12,7 @@ namespace blink { namespace { struct SameSizeAsNGBlockBreakToken : NGBreakToken { - unsigned numbers[2]; + unsigned numbers[3]; }; static_assert(sizeof(NGBlockBreakToken) == sizeof(SameSizeAsNGBlockBreakToken), @@ -21,13 +21,16 @@ static_assert(sizeof(NGBlockBreakToken) == sizeof(SameSizeAsNGBlockBreakToken), } // namespace NGBlockBreakToken::NGBlockBreakToken( + PassKey key, NGLayoutInputNode node, LayoutUnit consumed_block_size, + unsigned sequence_number, const NGBreakTokenVector& child_break_tokens, NGBreakAppeal break_appeal, bool has_seen_all_children) : NGBreakToken(kBlockBreakToken, kUnfinished, node), consumed_block_size_(consumed_block_size), + sequence_number_(sequence_number), num_children_(child_break_tokens.size()) { break_appeal_ = break_appeal; has_seen_all_children_ = has_seen_all_children; @@ -37,7 +40,7 @@ NGBlockBreakToken::NGBlockBreakToken( } } -NGBlockBreakToken::NGBlockBreakToken(NGLayoutInputNode node) +NGBlockBreakToken::NGBlockBreakToken(PassKey key, NGLayoutInputNode node) : NGBreakToken(kBlockBreakToken, kUnfinished, node), num_children_(0) {} const NGInlineBreakToken* NGBlockBreakToken::InlineBreakTokenFor( diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h index fc5ab4ba485..ee2bcc92fba 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_break_token.h @@ -27,6 +27,7 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { static scoped_refptr<NGBlockBreakToken> Create( NGLayoutInputNode node, LayoutUnit consumed_block_size, + unsigned sequence_number, const NGBreakTokenVector& child_break_tokens, NGBreakAppeal break_appeal, bool has_seen_all_children) { @@ -37,7 +38,8 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { sizeof(NGBlockBreakToken) + child_break_tokens.size() * sizeof(NGBreakToken*), ::WTF::GetStringWithTypeName<NGBlockBreakToken>()); - new (data) NGBlockBreakToken(node, consumed_block_size, child_break_tokens, + new (data) NGBlockBreakToken(PassKey(), node, consumed_block_size, + sequence_number, child_break_tokens, break_appeal, has_seen_all_children); return base::AdoptRef(static_cast<NGBlockBreakToken*>(data)); } @@ -48,7 +50,7 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { static scoped_refptr<NGBlockBreakToken> CreateBreakBefore( NGLayoutInputNode node, bool is_forced_break) { - auto* token = new NGBlockBreakToken(node); + auto* token = new NGBlockBreakToken(PassKey(), node); token->is_break_before_ = true; token->is_forced_break_ = is_forced_break; return base::AdoptRef(token); @@ -68,6 +70,17 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { // the fragmentainer is shorter than 50px, for instance). LayoutUnit ConsumedBlockSize() const { return consumed_block_size_; } + // A unique identifier for a fragment that generates a break token. This is + // unique within the generating layout input node. The break token of the + // first fragment gets 0, then second 1, and so on. Note that we don't "count" + // break tokens that aren't associated with a fragment (this happens when we + // want a fragmentainer break before laying out the node). What the sequence + // number is for such a break token is undefined. + unsigned SequenceNumber() const { + DCHECK(!IsBreakBefore()); + return sequence_number_; + } + // Return true if this is a break token that was produced without any // "preceding" fragment. This happens when we determine that the first // fragment for a node needs to be created in a later fragmentainer than the @@ -102,18 +115,23 @@ class CORE_EXPORT NGBlockBreakToken final : public NGBreakToken { String ToString() const override; #endif - private: + using PassKey = util::PassKey<NGBlockBreakToken>; + // Must only be called from Create(), because it assumes that enough space // has been allocated in the flexible array to store the children. - NGBlockBreakToken(NGLayoutInputNode node, + NGBlockBreakToken(PassKey, + NGLayoutInputNode node, LayoutUnit consumed_block_size, + unsigned sequence_number, const NGBreakTokenVector& child_break_tokens, NGBreakAppeal break_appeal, bool has_seen_all_children); - explicit NGBlockBreakToken(NGLayoutInputNode node); + explicit NGBlockBreakToken(PassKey, NGLayoutInputNode node); + private: LayoutUnit consumed_block_size_; + unsigned sequence_number_ = 0; wtf_size_t num_children_; // This must be the last member, because it is a flexible array. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc index 917eb7864f5..01b46282762 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator_test.cc @@ -58,19 +58,19 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { NGBreakTokenVector empty_tokens_list; scoped_refptr<NGBreakToken> child_token1 = NGBlockBreakToken::Create( - node1, LayoutUnit(), empty_tokens_list, kBreakAppealPerfect, + node1, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, /* has_seen_all_children */ false); scoped_refptr<NGBreakToken> child_token2 = NGBlockBreakToken::Create( - node2, LayoutUnit(), empty_tokens_list, kBreakAppealPerfect, + node2, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, /* has_seen_all_children */ false); scoped_refptr<NGBreakToken> child_token3 = NGBlockBreakToken::Create( - node3, LayoutUnit(), empty_tokens_list, kBreakAppealPerfect, + node3, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, /* has_seen_all_children */ false); NGBreakTokenVector child_break_tokens; child_break_tokens.push_back(child_token1); scoped_refptr<NGBlockBreakToken> parent_token = NGBlockBreakToken::Create( - container, LayoutUnit(), child_break_tokens, kBreakAppealPerfect, + container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, /* has_seen_all_children */ false); NGBlockChildIterator iterator(node1, parent_token.get()); @@ -86,7 +86,7 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { child_break_tokens.push_back(child_token1); child_break_tokens.push_back(child_token2); parent_token = NGBlockBreakToken::Create( - container, LayoutUnit(), child_break_tokens, kBreakAppealPerfect, + container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, /* has_seen_all_children */ false); iterator = NGBlockChildIterator(node1, parent_token.get()); @@ -103,7 +103,7 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { child_break_tokens.push_back(child_token2); child_break_tokens.push_back(child_token3); parent_token = NGBlockBreakToken::Create( - container, LayoutUnit(), child_break_tokens, kBreakAppealPerfect, + container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, /* has_seen_all_children */ false); iterator = NGBlockChildIterator(node1, parent_token.get()); @@ -119,7 +119,7 @@ TEST_F(NGBlockChildIteratorTest, BreakTokens) { child_break_tokens.push_back(child_token1); child_break_tokens.push_back(child_token3); parent_token = NGBlockBreakToken::Create( - container, LayoutUnit(), child_break_tokens, kBreakAppealPerfect, + container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, /* has_seen_all_children */ false); iterator = NGBlockChildIterator(node1, parent_token.get()); @@ -145,13 +145,13 @@ TEST_F(NGBlockChildIteratorTest, SeenAllChildren) { NGBreakTokenVector empty_tokens_list; scoped_refptr<NGBreakToken> child_token1 = NGBlockBreakToken::Create( - node1, LayoutUnit(), empty_tokens_list, kBreakAppealPerfect, + node1, LayoutUnit(), 0, empty_tokens_list, kBreakAppealPerfect, /* has_seen_all_children */ false); NGBreakTokenVector child_break_tokens; child_break_tokens.push_back(child_token1); scoped_refptr<NGBlockBreakToken> parent_token = NGBlockBreakToken::Create( - container, LayoutUnit(), child_break_tokens, kBreakAppealPerfect, + container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, /* has_seen_all_children */ true); // We have a break token for #child1, but have seen all children. This happens @@ -166,7 +166,7 @@ TEST_F(NGBlockChildIteratorTest, SeenAllChildren) { child_break_tokens.clear(); parent_token = NGBlockBreakToken::Create( - container, LayoutUnit(), child_break_tokens, kBreakAppealPerfect, + container, LayoutUnit(), 0, child_break_tokens, kBreakAppealPerfect, /* has_seen_all_children */ true); // We have no break tokens, but have seen all children. This happens e.g. when diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc index ec88e7d665b..9d0e5660942 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc @@ -16,6 +16,7 @@ #include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" @@ -30,7 +31,6 @@ #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h" -#include "third_party/blink/renderer/core/layout/text_autosizer.h" #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -53,7 +53,8 @@ inline scoped_refptr<const NGLayoutResult> LayoutBlockChild( // child. DCHECK(early_break_in_child); } - return node->Layout(space, break_token, early_break_in_child); + return node->Layout(space, To<NGBlockBreakToken>(break_token), + early_break_in_child); } inline scoped_refptr<const NGLayoutResult> LayoutInflow( @@ -180,6 +181,8 @@ NGBlockLayoutAlgorithm::NGBlockLayoutAlgorithm( params.fragment_geometry.scrollbar), is_resuming_(IsResumingLayout(params.break_token)), exclusion_space_(params.space.ExclusionSpace()), + lines_until_clamp_(params.space.LinesUntilClamp()), + force_truncate_at_line_clamp_(params.space.ForceTruncateAtLineClamp()), early_break_(params.early_break) { AdjustForFragmentation(BreakToken(), &border_scrollbar_padding_); container_builder_.SetIsNewFormattingContext( @@ -195,10 +198,10 @@ void NGBlockLayoutAlgorithm::SetBoxType(NGPhysicalFragment::NGBoxType type) { container_builder_.SetBoxType(type); } -base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( - const MinMaxSizeInput& input) const { - base::Optional<MinMaxSize> sizes = CalculateMinMaxSizesIgnoringChildren( - node_, border_scrollbar_padding_, input.size_type); +base::Optional<MinMaxSizes> NGBlockLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + base::Optional<MinMaxSizes> sizes = + CalculateMinMaxSizesIgnoringChildren(node_, border_scrollbar_padding_); if (sizes) return sizes; @@ -242,13 +245,13 @@ base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( float_right_inline_size = LayoutUnit(); } - MinMaxSizeInput child_input(child_percentage_resolution_block_size); + MinMaxSizesInput child_input(child_percentage_resolution_block_size); if (child.IsInline() || child.IsAnonymousBlock()) { child_input.float_left_inline_size = float_left_inline_size; child_input.float_right_inline_size = float_right_inline_size; } - MinMaxSize child_sizes; + MinMaxSizes child_sizes; if (child.IsInline()) { // From |NGBlockLayoutAlgorithm| perspective, we can handle |NGInlineNode| // almost the same as |NGBlockNode|, because an |NGInlineNode| includes @@ -257,7 +260,7 @@ base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( // |NextSibling| returns the next block sibling, or nullptr, skipping all // following inline siblings and descendants. child_sizes = - child.ComputeMinMaxSize(Style().GetWritingMode(), child_input); + child.ComputeMinMaxSizes(Style().GetWritingMode(), child_input); } else { child_sizes = ComputeMinAndMaxContentContribution(Style(), child, child_input); @@ -331,8 +334,7 @@ base::Optional<MinMaxSize> NGBlockLayoutAlgorithm::ComputeMinMaxSize( DCHECK_GE(sizes->min_size, LayoutUnit()); DCHECK_LE(sizes->min_size, sizes->max_size) << Node().ToString(); - if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) - *sizes += border_scrollbar_padding_.InlineSum(); + *sizes += border_scrollbar_padding_.InlineSum(); return sizes; } @@ -364,7 +366,7 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout() { // Inline children require an inline child layout context to be // passed between siblings. We want to stack-allocate that one, but // only on demand, as it's quite big. - if (Node().ChildrenInline()) + if (Node().IsInlineFormattingContextRoot()) result = LayoutWithInlineChildLayoutContext(); else result = Layout(nullptr); @@ -374,6 +376,11 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout() { DCHECK(!early_break_); DCHECK(result->GetEarlyBreak()); return RelayoutAndBreakEarlier(*result->GetEarlyBreak()); + } else if (UNLIKELY(result->Status() == + NGLayoutResult:: + kNeedsRelayoutWithNoForcedTruncateAtLineClamp)) { + DCHECK(force_truncate_at_line_clamp_); + return RelayoutNoForcedTruncateForLineClamp(); } return result; } @@ -417,6 +424,19 @@ NGBlockLayoutAlgorithm::RelayoutAndBreakEarlier( return algorithm_with_break.Layout(); } +NOINLINE scoped_refptr<const NGLayoutResult> +NGBlockLayoutAlgorithm::RelayoutNoForcedTruncateForLineClamp() { + NGLayoutAlgorithmParams params(Node(), + container_builder_.InitialFragmentGeometry(), + ConstraintSpace(), BreakToken(), nullptr); + NGBlockLayoutAlgorithm algorithm_with_forced_truncate(params); + algorithm_with_forced_truncate.force_truncate_at_line_clamp_ = false; + NGBoxFragmentBuilder& new_builder = + algorithm_with_forced_truncate.container_builder_; + new_builder.SetBoxType(container_builder_.BoxType()); + return algorithm_with_forced_truncate.Layout(); +} + inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( NGInlineChildLayoutContext* inline_child_layout_context) { const LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); @@ -436,6 +456,10 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( ConstraintSpace(), Style(), container_builder_.Scrollbar()); } + DCHECK_EQ(!!inline_child_layout_context, + Node().IsInlineFormattingContextRoot()); + container_builder_.SetIsInlineFormattingContext(inline_child_layout_context); + if (ConstraintSpace().HasBlockFragmentation()) { container_builder_.SetHasBlockFragmentation(); // The whereabouts of our container's so far best breakpoint is none of our @@ -463,6 +487,10 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( container_builder_.SetAdjoiningObjectTypes(adjoining_object_types); } + if (Style().IsDeprecatedWebkitBoxWithVerticalLineClamp() && + RuntimeEnabledFeatures::BlockFlowHandlesWebkitLineClampEnabled()) + lines_until_clamp_ = Style().LineClamp(); + LayoutUnit content_edge = border_scrollbar_padding_.block_start; NGPreviousInflowPosition previous_inflow_position = { @@ -480,8 +508,7 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( // // In all those cases we can and must resolve the BFC block offset now. if (border_scrollbar_padding_.block_start || is_resuming_ || - ConstraintSpace().IsNewFormattingContext() || - Style().MarginBeforeCollapse() != EMarginCollapse::kCollapse) { + ConstraintSpace().IsNewFormattingContext()) { bool discard_subsequent_margins = previous_inflow_position.margin_strut.discard_margins && !border_scrollbar_padding_.block_start; @@ -537,12 +564,6 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( if (node_.IsQuirkyContainer()) previous_inflow_position.margin_strut.is_quirky_container_start = true; - // Before we descend into children (but after we have determined our inline - // size), give the autosizer an opportunity to adjust the font size on the - // children. - TextAutosizer::NGLayoutScope text_autosizer_layout_scope( - Node(), border_box_size.inline_size); - // Try to reuse line box fragments from cached fragments if possible. // When possible, this adds fragments to |container_builder_| and update // |previous_inflow_position| and |BreakToken()|. @@ -650,6 +671,18 @@ inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout( } } + if (UNLIKELY(ConstraintSpace().IsNewFormattingContext() && + force_truncate_at_line_clamp_ && + intrinsic_block_size_when_clamped_ && lines_until_clamp_ == 0)) { + // Truncation of the last line was forced, but there are no lines after the + // truncated line. Rerun layout without forcing truncation. This is only + // done if line-clamp was specified on the element as the element containing + // the node may have subsequent lines. If there aren't, the containing + // element will relayout. + return container_builder_.Abort( + NGLayoutResult::kNeedsRelayoutWithNoForcedTruncateAtLineClamp); + } + if (child_iterator.IsAtEnd()) { // We've gone through all the children. This doesn't necessarily mean that // we're done fragmenting, as there may be parallel flows [1] (visible @@ -685,15 +718,22 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( intrinsic_block_size_, exclusion_space_.ClearanceOffset(EClear::kBoth)); } - // The end margin strut of an in-flow fragment contributes to the size of the - // current fragment if: - // - There is block-end border/scrollbar/padding. - // - There was a self-collapsing child affected by clearance. - // - We are a new formatting context. - // Additionally this fragment produces no end margin strut. - if (border_scrollbar_padding_.block_end || - previous_inflow_position->self_collapsing_child_had_clearance || - ConstraintSpace().IsNewFormattingContext()) { + // If line clamping occurred, the intrinsic block-size comes from the + // intrinsic block-size at the time of the clamp. + if (intrinsic_block_size_when_clamped_) { + DCHECK(container_builder_.BfcBlockOffset()); + intrinsic_block_size_ = *intrinsic_block_size_when_clamped_; + end_margin_strut = NGMarginStrut(); + } else if (border_scrollbar_padding_.block_end || + previous_inflow_position->self_collapsing_child_had_clearance || + ConstraintSpace().IsNewFormattingContext()) { + // The end margin strut of an in-flow fragment contributes to the size of + // the current fragment if: + // - There is block-end border/scrollbar/padding. + // - There was a self-collapsing child affected by clearance. + // - We are a new formatting context. + // Additionally this fragment produces no end margin strut. + // // If we are a quirky container, we ignore any quirky margins and // just consider normal margins to extend our size. Other UAs // perform this calculation differently, e.g. by just ignoring the @@ -741,7 +781,7 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( } // Save the unconstrained intrinsic size on the builder before clamping it. - container_builder_.SetUnconstrainedIntrinsicBlockSize(intrinsic_block_size_); + container_builder_.SetOverflowBlockSize(intrinsic_block_size_); intrinsic_block_size_ = ClampIntrinsicBlockSize( ConstraintSpace(), Node(), border_scrollbar_padding_, @@ -827,11 +867,14 @@ scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout( container_builder_.CheckNoBlockFragmentation(); #endif - PropagateBaselinesFromChildren(); + // Adjust the position of the final baseline if needed. + FinalizeBaseline(); // An exclusion space is confined to nodes within the same formatting context. - if (!ConstraintSpace().IsNewFormattingContext()) + if (!ConstraintSpace().IsNewFormattingContext()) { container_builder_.SetExclusionSpace(std::move(exclusion_space_)); + container_builder_.SetLinesUntilClamp(lines_until_clamp_); + } if (ConstraintSpace().UseFirstLineStyle()) container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine); @@ -1016,6 +1059,10 @@ void NGBlockLayoutAlgorithm::HandleFloat( PositionFloat(&unpositioned_float, &exclusion_space_); const NGLayoutResult& layout_result = *positioned_float.layout_result; + + // TODO(mstensho): Handle abortions caused by block fragmentation. + DCHECK_EQ(layout_result.Status(), NGLayoutResult::kSuccess); + const auto& physical_fragment = layout_result.PhysicalFragment(); if (const NGBreakToken* token = physical_fragment.BreakToken()) { DCHECK(ConstraintSpace().HasBlockFragmentation()); @@ -1230,13 +1277,6 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::HandleNewFormattingContext( /* abort_if_cleared */ false, &child_bfc_offset); } - NGFragment fragment(ConstraintSpace().GetWritingMode(), - layout_result->PhysicalFragment()); - - LogicalOffset logical_offset = LogicalFromBfcOffsets( - child_bfc_offset, ContainerBfcOffset(), fragment.InlineSize(), - container_builder_.Size().inline_size, ConstraintSpace().Direction()); - if (ConstraintSpace().HasBlockFragmentation()) { bool has_container_separation = has_processed_first_child_ || child_margin_got_separated || @@ -1244,20 +1284,32 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::HandleNewFormattingContext( layout_result->IsPushedByFloats(); NGBreakStatus break_status = BreakBeforeChildIfNeeded( child, *layout_result, previous_inflow_position, - logical_offset.block_offset, has_container_separation); + child_bfc_offset.block_offset, has_container_separation); if (break_status == NGBreakStatus::kBrokeBefore) return NGLayoutResult::kSuccess; if (break_status == NGBreakStatus::kNeedsEarlierBreak) return NGLayoutResult::kNeedsEarlierBreak; + + // If the child aborted layout, we cannot continue. + DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess); + EBreakBetween break_after = JoinFragmentainerBreakValues( layout_result->FinalBreakAfter(), child.Style().BreakAfter()); container_builder_.SetPreviousBreakAfter(break_after); } + const auto& physical_fragment = layout_result->PhysicalFragment(); + NGFragment fragment(ConstraintSpace().GetWritingMode(), physical_fragment); + + LogicalOffset logical_offset = LogicalFromBfcOffsets( + child_bfc_offset, ContainerBfcOffset(), fragment.InlineSize(), + container_builder_.Size().inline_size, ConstraintSpace().Direction()); + if (!PositionOrPropagateListMarker(*layout_result, &logical_offset, previous_inflow_position)) return NGLayoutResult::kBfcBlockOffsetResolved; + PropagateBaselineFromChild(physical_fragment, logical_offset.block_offset); container_builder_.AddResult(*layout_result, logical_offset); // The margins we store will be used by e.g. getComputedStyle(). @@ -1306,7 +1358,7 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( // fit where it was laid out, and is pushed downwards, we'll lay out over // again, since a new BFC block offset could result in a new fragment size, // e.g. when inline size is auto, or if we're block-fragmented. - for (const auto opportunity : opportunities) { + for (const auto& opportunity : opportunities) { if (abort_if_cleared && origin_offset.block_offset < opportunity.rect.BlockStartOffset()) { // Abort if we got pushed downwards. We need to adjust @@ -1378,6 +1430,12 @@ NGBlockLayoutAlgorithm::LayoutNewFormattingContext( // should be returned. DCHECK(layout_result->ExclusionSpace().IsEmpty()); + if (layout_result->Status() != NGLayoutResult::kSuccess) { + DCHECK_EQ(layout_result->Status(), + NGLayoutResult::kOutOfFragmentainerSpace); + return layout_result; + } + NGFragment fragment(writing_mode, layout_result->PhysicalFragment()); // Check if the fragment will fit in this layout opportunity, if not proceed @@ -1730,7 +1788,8 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( LogicalOffset logical_offset = CalculateLogicalOffset( fragment, layout_result->BfcLineOffset(), child_bfc_block_offset); - if (ConstraintSpace().HasBlockFragmentation()) { + if (ConstraintSpace().HasBlockFragmentation() && + container_builder_.BfcBlockOffset() && child_bfc_block_offset) { // Floats only cause container separation for the outermost block child that // gets pushed down (the container and the child may have adjoining // block-start margins). @@ -1739,7 +1798,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( !container_builder_.IsPushedByFloats()); NGBreakStatus break_status = BreakBeforeChildIfNeeded( child, *layout_result, previous_inflow_position, - logical_offset.block_offset, has_container_separation); + *child_bfc_block_offset, has_container_separation); if (break_status == NGBreakStatus::kBrokeBefore) return NGLayoutResult::kSuccess; if (break_status == NGBreakStatus::kNeedsEarlierBreak) @@ -1753,6 +1812,7 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( previous_inflow_position)) return NGLayoutResult::kBfcBlockOffsetResolved; + PropagateBaselineFromChild(physical_fragment, logical_offset.block_offset); container_builder_.AddResult(*layout_result, logical_offset); if (auto* block_child = DynamicTo<NGBlockNode>(child)) { @@ -1795,6 +1855,24 @@ NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow( } } + // Update |lines_until_clamp_| from the LayoutResult. + if (lines_until_clamp_) { + if (const auto* line_box = + DynamicTo<NGPhysicalLineBoxFragment>(physical_fragment)) { + if (!line_box->IsEmptyLineBox()) + lines_until_clamp_ = *lines_until_clamp_ - 1; + } else { + lines_until_clamp_ = layout_result->LinesUntilClamp(); + } + if (lines_until_clamp_ <= 0 && + !intrinsic_block_size_when_clamped_.has_value()) { + // If line-clamping occurred save the intrinsic block-size, as this + // becomes the final intrinsic block-size. + intrinsic_block_size_when_clamped_ = + previous_inflow_position->logical_block_offset + + border_scrollbar_padding_.block_end; + } + } return NGLayoutResult::kSuccess; } @@ -1832,32 +1910,9 @@ NGInflowChildData NGBlockLayoutAlgorithm::ComputeChildData( LayoutUnit logical_block_offset = previous_inflow_position.logical_block_offset; - EMarginCollapse margin_before_collapse = child.Style().MarginBeforeCollapse(); - if (margin_before_collapse != EMarginCollapse::kCollapse) { - // Stop margin collapsing on the block-start side of the child. - StopMarginCollapsing(child.Style().MarginBeforeCollapse(), - margins.block_start, &logical_block_offset, - &margin_strut); - - if (margin_before_collapse == EMarginCollapse::kSeparate) { - UseCounter::Count(Node().GetDocument(), - WebFeature::kWebkitMarginBeforeCollapseSeparate); - if (margin_strut != previous_inflow_position.margin_strut || - logical_block_offset != - previous_inflow_position.logical_block_offset) { - UseCounter::Count( - Node().GetDocument(), - WebFeature::kWebkitMarginBeforeCollapseSeparateMaybeDoesSomething); - } - } else if (margin_before_collapse == EMarginCollapse::kDiscard) { - UseCounter::Count(Node().GetDocument(), - WebFeature::kWebkitMarginBeforeCollapseDiscard); - } - } else { - margin_strut.Append(margins.block_start, - child.Style().HasMarginBeforeQuirk()); - SetSubtreeModifiedMarginStrutIfNeeded(&child.Style().MarginBefore()); - } + margin_strut.Append(margins.block_start, + child.Style().HasMarginBeforeQuirk()); + SetSubtreeModifiedMarginStrutIfNeeded(&child.Style().MarginBefore()); NGBfcOffset child_bfc_offset = { ConstraintSpace().BfcOffset().line_offset + @@ -1945,36 +2000,14 @@ NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition( NGMarginStrut margin_strut = layout_result.EndMarginStrut(); - EMarginCollapse margin_after_collapse = child.Style().MarginAfterCollapse(); - if (margin_after_collapse != EMarginCollapse::kCollapse) { - LayoutUnit logical_block_offset_copy = logical_block_offset; - // Stop margin collapsing on the block-end side of the child. - StopMarginCollapsing(margin_after_collapse, child_data.margins.block_end, - &logical_block_offset, &margin_strut); - - if (margin_after_collapse == EMarginCollapse::kSeparate) { - UseCounter::Count(Node().GetDocument(), - WebFeature::kWebkitMarginAfterCollapseSeparate); - if (margin_strut != layout_result.EndMarginStrut() || - logical_block_offset != logical_block_offset_copy) { - UseCounter::Count( - Node().GetDocument(), - WebFeature::kWebkitMarginAfterCollapseSeparateMaybeDoesSomething); - } - } else if (margin_after_collapse == EMarginCollapse::kDiscard) { - UseCounter::Count(Node().GetDocument(), - WebFeature::kWebkitMarginAfterCollapseDiscard); - } - } else { - // Self collapsing child's end margin can "inherit" quirkiness from its - // start margin. E.g. - // <ol style="margin-bottom: 20px"></ol> - bool is_quirky = - (is_self_collapsing && child.Style().HasMarginBeforeQuirk()) || - child.Style().HasMarginAfterQuirk(); - margin_strut.Append(child_data.margins.block_end, is_quirky); - SetSubtreeModifiedMarginStrutIfNeeded(&child.Style().MarginAfter()); - } + // Self collapsing child's end margin can "inherit" quirkiness from its start + // margin. E.g. + // <ol style="margin-bottom: 20px"></ol> + bool is_quirky = + (is_self_collapsing && child.Style().HasMarginBeforeQuirk()) || + child.Style().HasMarginAfterQuirk(); + margin_strut.Append(child_data.margins.block_end, is_quirky); + SetSubtreeModifiedMarginStrutIfNeeded(&child.Style().MarginAfter()); // This flag is subtle, but in order to determine our size correctly we need // to check if our last child is self-collapsing, and it was affected by @@ -2044,7 +2077,7 @@ void NGBlockLayoutAlgorithm::SetFragmentainerOutOfSpace( } bool NGBlockLayoutAlgorithm::FinalizeForFragmentation() { - if (Node().ChildrenInline() && !early_break_) { + if (Node().IsInlineFormattingContextRoot() && !early_break_) { if (container_builder_.DidBreak() || first_overflowing_line_) { if (first_overflowing_line_ && first_overflowing_line_ < container_builder_.LineCount()) { @@ -2112,8 +2145,8 @@ bool NGBlockLayoutAlgorithm::FinalizeForFragmentation() { } } - FinishFragmentation(ConstraintSpace(), block_size, intrinsic_block_size_, - consumed_block_size, space_left, &container_builder_); + FinishFragmentation(ConstraintSpace(), BreakToken(), block_size, + intrinsic_block_size_, space_left, &container_builder_); return true; } @@ -2122,14 +2155,13 @@ NGBreakStatus NGBlockLayoutAlgorithm::BreakBeforeChildIfNeeded( NGLayoutInputNode child, const NGLayoutResult& layout_result, NGPreviousInflowPosition* previous_inflow_position, - LayoutUnit block_offset, + LayoutUnit bfc_block_offset, bool has_container_separation) { DCHECK(ConstraintSpace().HasBlockFragmentation()); // If the BFC offset is unknown, there's nowhere to break, since there's no // non-empty child content yet (as that would have resolved the BFC offset). - if (!container_builder_.BfcBlockOffset()) - return NGBreakStatus::kContinue; + DCHECK(container_builder_.BfcBlockOffset()); // If we already know where to insert the break, we already know that it's not // going to be here, since that's something we check before entering layout of @@ -2138,8 +2170,7 @@ NGBreakStatus NGBlockLayoutAlgorithm::BreakBeforeChildIfNeeded( return NGBreakStatus::kContinue; LayoutUnit fragmentainer_block_offset = - ConstraintSpace().FragmentainerOffsetAtBfc() + - *container_builder_.BfcBlockOffset() + block_offset; + ConstraintSpace().FragmentainerOffsetAtBfc() + bfc_block_offset; if (has_container_separation) { EBreakBetween break_between = @@ -2315,7 +2346,7 @@ NGBoxStrut NGBlockLayoutAlgorithm::CalculateMargins( NGConstraintSpace space = builder.ToConstraintSpace(); NGBoxStrut child_border_padding = - ComputeBorders(space, child) + ComputePadding(space, child.Style()); + ComputeBorders(space, child_style) + ComputePadding(space, child_style); LayoutUnit child_inline_size = ComputeInlineSizeForFragment(space, child, child_border_padding); @@ -2327,29 +2358,6 @@ NGBoxStrut NGBlockLayoutAlgorithm::CalculateMargins( return margins; } -// Stop margin collapsing on one side of a block when -// -webkit-margin-{after,before}-collapse is something other than 'collapse' -// (the initial value) -void NGBlockLayoutAlgorithm::StopMarginCollapsing( - EMarginCollapse collapse_value, - LayoutUnit this_margin, - LayoutUnit* logical_block_offset, - NGMarginStrut* margin_strut) { - DCHECK_NE(collapse_value, EMarginCollapse::kCollapse); - if (collapse_value == EMarginCollapse::kSeparate) { - // Separate margins between previously adjoining margins and this margin, - // AND between this margin and adjoining margins to come. - *logical_block_offset += margin_strut->Sum() + this_margin; - *margin_strut = NGMarginStrut(); - return; - } - DCHECK_EQ(collapse_value, EMarginCollapse::kDiscard); - // Discard previously adjoining margins, this margin AND all adjoining margins - // to come, so that the sum becomes 0. - margin_strut->discard_margins = true; - SetSubtreeModifiedMarginStrutIfNeeded(); -} - NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( const NGLayoutInputNode child, const NGInflowChildData& child_data, @@ -2369,6 +2377,9 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( if (!IsParallelWritingMode(ConstraintSpace().GetWritingMode(), child_writing_mode)) builder.SetIsShrinkToFit(child_style.LogicalWidth().IsAuto()); + if (child_style.LogicalWidth().IsAuto() && + child.GetLayoutBox()->AutoWidthShouldFitContent()) + builder.SetIsShrinkToFit(true); builder.SetAvailableSize(child_available_size); builder.SetPercentageResolutionSize(child_percentage_size_); @@ -2387,9 +2398,6 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( builder.SetTableCellChildLayoutMode(mode); } - if (NGBaseline::ShouldPropagateBaselines(child)) - builder.AddBaselineRequests(ConstraintSpace().BaselineRequests()); - bool has_bfc_block_offset = container_builder_.BfcBlockOffset().has_value(); // Propagate the |NGConstraintSpace::ForcedBfcBlockOffset| down to our @@ -2447,11 +2455,10 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( clearance_offset = std::max(clearance_offset, child_clearance_offset); builder.SetTextDirection(child_style.Direction()); - // PositionListMarker() requires a first line baseline. - if (container_builder_.UnpositionedListMarker()) { - builder.AddBaselineRequest( - {NGBaselineAlgorithmType::kFirstLine, style.GetFontBaseline()}); - } + // |PositionListMarker()| requires a baseline. + builder.SetNeedsBaseline(ConstraintSpace().NeedsBaseline() || + container_builder_.UnpositionedListMarker()); + builder.SetBaselineAlgorithmType(ConstraintSpace().BaselineAlgorithmType()); } else { builder.SetTextDirection(style.Direction()); } @@ -2465,6 +2472,8 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( builder.SetAdjoiningObjectTypes( container_builder_.AdjoiningObjectTypes()); } + builder.SetLinesUntilClamp(lines_until_clamp_); + builder.SetForceTruncateAtLineClamp(force_truncate_at_line_clamp_); } else if (child_data.is_resuming_after_break) { // If the child is being resumed after a break, margins inside the child may // be adjoining with the fragmentainer boundary, regardless of whether the @@ -2479,109 +2488,78 @@ NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild( // fragmentation line. if (is_new_fc) fragmentainer_offset_delta = *child_bfc_block_offset; - SetupFragmentation(ConstraintSpace(), fragmentainer_offset_delta, &builder, - is_new_fc); + SetupFragmentation(ConstraintSpace(), child, fragmentainer_offset_delta, + &builder, is_new_fc); builder.SetEarlyBreakAppeal(container_builder_.BreakAppeal()); } return builder.ToConstraintSpace(); } -LayoutUnit NGBlockLayoutAlgorithm::ComputeLineBoxBaselineOffset( - const NGBaselineRequest& request, - const NGPhysicalLineBoxFragment& line_box, - LayoutUnit line_box_block_offset) const { - NGLineHeightMetrics metrics = - line_box.BaselineMetrics(request.BaselineType()); - DCHECK(!metrics.IsEmpty()); - - // NGLineHeightMetrics is line-relative, which matches to the flow-relative - // unless this box is in flipped-lines writing-mode. - if (!Style().IsFlippedLinesWritingMode()) - return metrics.ascent + line_box_block_offset; - - if (Node().IsInlineLevel()) { - // If this box is inline-level, since we're in NGBlockLayoutAlgorithm, this - // is an inline-block. - DCHECK(Node().IsAtomicInlineLevel()); - // This box will be flipped when the containing line is flipped. Compute the - // baseline offset from the block-end (right in vertical-lr) content edge. - line_box_block_offset = container_builder_.Size().block_size - - (line_box_block_offset + line_box.Size().width); - return metrics.ascent + line_box_block_offset; - } - - // Otherwise, the baseline is offset by the descent from the block-start - // content edge. - return metrics.descent + line_box_block_offset; -} +void NGBlockLayoutAlgorithm::PropagateBaselineFromChild( + const NGPhysicalContainerFragment& child, + LayoutUnit block_offset) { + // Check if we've already found an appropriate baseline. + if (container_builder_.Baseline() && + ConstraintSpace().BaselineAlgorithmType() == + NGBaselineAlgorithmType::kFirstLine) + return; -// Add a baseline from a child box fragment. -// @return false if the specified child is not a box or is OOF. -bool NGBlockLayoutAlgorithm::AddBaseline(const NGBaselineRequest& request, - const NGPhysicalFragment& child, - LayoutUnit child_offset) { if (child.IsLineBox()) { const auto& line_box = To<NGPhysicalLineBoxFragment>(child); - // Skip over a line-box which is empty. These don't have any baselines which - // should be added. + // Skip over a line-box which is empty. These don't have any baselines + // which should be added. if (line_box.IsEmptyLineBox()) - return false; + return; - LayoutUnit offset = - ComputeLineBoxBaselineOffset(request, line_box, child_offset); - container_builder_.AddBaseline(request, offset); - return true; + NGLineHeightMetrics metrics = line_box.BaselineMetrics(); + DCHECK(!metrics.IsEmpty()); + LayoutUnit baseline = + block_offset + (Style().IsFlippedLinesWritingMode() ? metrics.descent + : metrics.ascent); + + if (!container_builder_.Baseline()) + container_builder_.SetBaseline(baseline); + + // Set the last baseline only if required. + if (ConstraintSpace().BaselineAlgorithmType() != + NGBaselineAlgorithmType::kFirstLine) + container_builder_.SetLastBaseline(baseline); + + return; } - if (child.IsFloatingOrOutOfFlowPositioned()) - return false; + NGBoxFragment fragment(ConstraintSpace().GetWritingMode(), + ConstraintSpace().Direction(), + To<NGPhysicalBoxFragment>(child)); - if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(child)) { - if (base::Optional<LayoutUnit> baseline = box->Baseline(request)) { - container_builder_.AddBaseline(request, *baseline + child_offset); - return true; - } + if (!container_builder_.Baseline()) { + if (auto baseline = fragment.FirstBaseline()) + container_builder_.SetBaseline(block_offset + *baseline); } - return false; + // Set the last baseline only if required. + if (ConstraintSpace().BaselineAlgorithmType() != + NGBaselineAlgorithmType::kFirstLine) { + if (auto last_baseline = fragment.Baseline()) + container_builder_.SetLastBaseline(block_offset + *last_baseline); + } } -// Propagate computed baselines from children. -// Skip children that do not produce baselines (e.g., empty blocks.) -void NGBlockLayoutAlgorithm::PropagateBaselinesFromChildren() { - const NGBaselineRequestList requests = ConstraintSpace().BaselineRequests(); - if (requests.IsEmpty()) +void NGBlockLayoutAlgorithm::FinalizeBaseline() { + if (ConstraintSpace().BaselineAlgorithmType() != + NGBaselineAlgorithmType::kInlineBlock) return; - for (const auto& request : requests) { - switch (request.AlgorithmType()) { - case NGBaselineAlgorithmType::kAtomicInline: { - if (Node().UseLogicalBottomMarginEdgeForInlineBlockBaseline()) { - LayoutUnit block_end = container_builder_.BlockSize(); - NGBoxStrut margins = - ComputeMarginsForSelf(ConstraintSpace(), Style()); - container_builder_.AddBaseline(request, - block_end + margins.block_end); - break; - } + if (!Node().UseLogicalBottomMarginEdgeForInlineBlockBaseline()) + return; - const auto& children = container_builder_.Children(); - for (auto it = children.rbegin(); it != children.rend(); ++it) { - if (AddBaseline(request, *it->fragment, it->offset.block_offset)) - break; - } - break; - } - case NGBaselineAlgorithmType::kFirstLine: - for (const auto& child : container_builder_.Children()) { - if (AddBaseline(request, *child.fragment, child.offset.block_offset)) - break; - } - break; - } - } + // When overflow is present (within an atomic-inline baseline context) we + // should always use the block-end margin edge as the baseline. + NGBoxStrut margins = ComputeMarginsForSelf(ConstraintSpace(), Style()); + container_builder_.SetLastBaseline(container_builder_.BlockSize() + + margins.block_end); } bool NGBlockLayoutAlgorithm::ResolveBfcBlockOffset( @@ -2687,12 +2665,11 @@ bool NGBlockLayoutAlgorithm::PositionOrPropagateListMarker( container_builder_.SetUnpositionedListMarker(NGUnpositionedListMarker()); } - NGLineHeightMetrics content_metrics; const NGConstraintSpace& space = ConstraintSpace(); const NGPhysicalFragment& content = layout_result.PhysicalFragment(); FontBaseline baseline_type = Style().GetFontBaseline(); - if (list_marker.CanAddToBox(space, baseline_type, content, - &content_metrics)) { + if (auto content_baseline = + list_marker.ContentAlignmentBaseline(space, baseline_type, content)) { // TODO: We are reusing the ConstraintSpace for LI here. It works well for // now because authors cannot style list-markers currently. If we want to // support `::marker` pseudo, we need to create ConstraintSpace for marker @@ -2713,8 +2690,8 @@ bool NGBlockLayoutAlgorithm::PositionOrPropagateListMarker( } list_marker.AddToBox(space, baseline_type, content, - border_scrollbar_padding_, content_metrics, - *marker_layout_result, content_offset, + border_scrollbar_padding_, *marker_layout_result, + *content_baseline, content_offset, &container_builder_); return true; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h index 8274b1dc201..644bb031ead 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h @@ -6,6 +6,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_BLOCK_LAYOUT_ALGORITHM_H_ #include "base/memory/scoped_refptr.h" +#include "base/optional.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h" @@ -17,6 +18,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h" +#include "third_party/blink/renderer/platform/geometry/layout_unit.h" namespace blink { @@ -24,7 +26,6 @@ enum class NGBreakStatus; class NGConstraintSpace; class NGEarlyBreak; class NGFragment; -class NGPhysicalLineBoxFragment; // This struct is used for communicating to a child the position of the previous // inflow child. This will be used to calculate the position of the next child. @@ -58,8 +59,8 @@ class CORE_EXPORT NGBlockLayoutAlgorithm void SetBoxType(NGPhysicalFragment::NGBoxType type); - base::Optional<MinMaxSize> ComputeMinMaxSize( - const MinMaxSizeInput&) const override; + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const override; scoped_refptr<const NGLayoutResult> Layout() override; private: @@ -75,10 +76,14 @@ class CORE_EXPORT NGBlockLayoutAlgorithm NOINLINE scoped_refptr<const NGLayoutResult> RelayoutAndBreakEarlier( const NGEarlyBreak&); + NOINLINE scoped_refptr<const NGLayoutResult> + RelayoutNoForcedTruncateForLineClamp(); + inline scoped_refptr<const NGLayoutResult> Layout( NGInlineChildLayoutContext* inline_child_layout_context); - scoped_refptr<const NGLayoutResult> FinishLayout(NGPreviousInflowPosition*); + scoped_refptr<const NGLayoutResult> FinishLayout( + NGPreviousInflowPosition* previous_inflow_position); // Return the BFC block offset of this block. LayoutUnit BfcBlockOffset() const { @@ -102,11 +107,6 @@ class CORE_EXPORT NGBlockLayoutAlgorithm bool is_new_fc, bool* margins_fully_resolved); - void StopMarginCollapsing(EMarginCollapse collapse_value, - LayoutUnit this_margin, - LayoutUnit* logical_block_offset, - NGMarginStrut* margin_strut); - // Creates a new constraint space for the current child. NGConstraintSpace CreateConstraintSpaceForChild( const NGLayoutInputNode child, @@ -238,25 +238,19 @@ class CORE_EXPORT NGBlockLayoutAlgorithm NGBreakStatus BreakBeforeChildIfNeeded(NGLayoutInputNode child, const NGLayoutResult&, NGPreviousInflowPosition*, - LayoutUnit block_offset, + LayoutUnit bfc_block_offset, bool has_container_separation); // Look for a better breakpoint (than we already have) between lines (i.e. a // class B breakpoint), and store it. void UpdateEarlyBreakBetweenLines(); - void PropagateBaselinesFromChildren(); - bool AddBaseline(const NGBaselineRequest&, - const NGPhysicalFragment&, - LayoutUnit child_offset); + // Propagates the baseline from the given |child| if needed. + void PropagateBaselineFromChild(const NGPhysicalContainerFragment& child, + LayoutUnit block_offset); - // Compute the baseline offset of a line box from the content box. - // Line boxes are in line-relative coordinates. This function returns the - // offset in flow-relative coordinates. - LayoutUnit ComputeLineBoxBaselineOffset( - const NGBaselineRequest&, - const NGPhysicalLineBoxFragment&, - LayoutUnit line_box_block_offset) const; + // Performs any final baseline adjustments needed. + void FinalizeBaseline(); // If still unresolved, resolve the fragment's BFC block offset. // @@ -396,6 +390,18 @@ class CORE_EXPORT NGBlockLayoutAlgorithm NGExclusionSpace exclusion_space_; + // If set, this is the number of lines until a clamp. A value of 1 indicates + // the current line should be clamped. This may go negative. + base::Optional<int> lines_until_clamp_; + + // If true, truncation is forced at the clamped line regardless of whether + // there is more text. + bool force_truncate_at_line_clamp_ = true; + + // If set, one of the lines was clamped and this is the intrinsic size at the + // time of the clamp. + base::Optional<LayoutUnit> intrinsic_block_size_when_clamped_; + // When set, this will specify where to break before or inside. const NGEarlyBreak* early_break_ = nullptr; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc index 1cc8a99f1ed..f07b9dcfcf3 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc @@ -33,7 +33,7 @@ class NGBlockLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { NGBaseLayoutAlgorithmTest::SetUp(); } - MinMaxSize RunComputeMinAndMax(NGBlockNode node) { + MinMaxSizes RunComputeMinMaxSizes(NGBlockNode node) { // The constraint space is not used for min/max computation, but we need // it to create the algorithm. NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( @@ -43,9 +43,9 @@ class NGBlockLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { CalculateInitialMinMaxFragmentGeometry(space, node); NGBlockLayoutAlgorithm algorithm({node, fragment_geometry, space}); - MinMaxSizeInput input( + MinMaxSizesInput input( /* percentage_resolution_block_size */ (LayoutUnit())); - auto min_max = algorithm.ComputeMinMaxSize(input); + auto min_max = algorithm.ComputeMinMaxSizes(input); EXPECT_TRUE(min_max.has_value()); return *min_max; } @@ -97,10 +97,11 @@ TEST_F(NGBlockLayoutAlgorithmTest, FixedSize) { } TEST_F(NGBlockLayoutAlgorithmTest, Caching) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - + // The inner element exists so that "simplified" layout logic isn't invoked. SetBodyInnerHTML(R"HTML( - <div id="box" style="width:30px; height:40%;"></div> + <div id="box" style="width:30px; height:40%;"> + <div style="height: 100%;"></div> + </div> )HTML"); NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( @@ -132,7 +133,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, Caching) { EXPECT_NE(result.get(), nullptr); // Test a different constraint space that will actually result in a different - // size. + // sized fragment. space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, LogicalSize(LayoutUnit(200), LayoutUnit(200))); @@ -146,8 +147,6 @@ TEST_F(NGBlockLayoutAlgorithmTest, Caching) { } TEST_F(NGBlockLayoutAlgorithmTest, MinInlineSizeCaching) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <div id="box" style="min-width:30%; width: 10px; height:40px;"></div> )HTML"); @@ -190,8 +189,6 @@ TEST_F(NGBlockLayoutAlgorithmTest, MinInlineSizeCaching) { } TEST_F(NGBlockLayoutAlgorithmTest, PercentageBlockSizeQuirkDescendantsCaching) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Quirks mode triggers the interesting parent-child %-resolution behaviour. GetDocument().SetCompatibilityMode(Document::kQuirksMode); @@ -237,10 +234,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, PercentageBlockSizeQuirkDescendantsCaching) { builder.SetAvailableSize(size); builder.SetPercentageResolutionSize(size); builder.SetTextDirection(TextDirection::kLtr); - builder.AddBaselineRequest({NGBaselineAlgorithmType::kAtomicInline, - FontBaseline::kAlphabeticBaseline}); - builder.AddBaselineRequest({NGBaselineAlgorithmType::kFirstLine, - FontBaseline::kAlphabeticBaseline}); + builder.SetNeedsBaseline(true); return builder.ToConstraintSpace(); }; @@ -298,99 +292,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, PercentageBlockSizeQuirkDescendantsCaching) { EXPECT_EQ(run_test("box9"), nullptr); } -TEST_F(NGBlockLayoutAlgorithmTest, ShrinkToFitCaching) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - - SetBodyInnerHTML(R"HTML( - <div id="container" style="display: flow-root; width: 300px; height: 100px;"> - <div id="box1" style="float: left;"> - <div style="display: inline-block; width: 150px;"></div> - <div style="display: inline-block; width: 50px;"></div> - </div> - <div id="box2" style="float: left;"> - <div style="display: inline-block; width: 350px;"></div> - <div style="display: inline-block; width: 250px;"></div> - </div> - <div id="box3" style="float: left; min-width: 80%;"> - <div style="display: inline-block; width: 150px;"></div> - <div style="display: inline-block; width: 250px;"></div> - </div> - <div id="box4" style="float: left; margin-left: 75px;"> - <div style="display: inline-block; width: 150px;"></div> - <div style="display: inline-block; width: 50px;"></div> - </div> - </div> - )HTML"); - - NGConstraintSpace space100 = ConstructBlockLayoutTestConstraintSpace( - WritingMode::kHorizontalTb, TextDirection::kLtr, - LogicalSize(LayoutUnit(100), LayoutUnit(100)), - /* shrink_to_fit */ true, /* is_new_formatting_context */ true); - NGConstraintSpace space200 = ConstructBlockLayoutTestConstraintSpace( - WritingMode::kHorizontalTb, TextDirection::kLtr, - LogicalSize(LayoutUnit(200), LayoutUnit(100)), - /* shrink_to_fit */ true, /* is_new_formatting_context */ true); - NGConstraintSpace space250 = ConstructBlockLayoutTestConstraintSpace( - WritingMode::kHorizontalTb, TextDirection::kLtr, - LogicalSize(LayoutUnit(250), LayoutUnit(100)), - /* shrink_to_fit */ true, /* is_new_formatting_context */ true); - NGConstraintSpace space300 = ConstructBlockLayoutTestConstraintSpace( - WritingMode::kHorizontalTb, TextDirection::kLtr, - LogicalSize(LayoutUnit(300), LayoutUnit(100)), - /* shrink_to_fit */ true, /* is_new_formatting_context */ true); - NGConstraintSpace space400 = ConstructBlockLayoutTestConstraintSpace( - WritingMode::kHorizontalTb, TextDirection::kLtr, - LogicalSize(LayoutUnit(400), LayoutUnit(100)), - /* shrink_to_fit */ true, /* is_new_formatting_context */ true); - scoped_refptr<const NGLayoutResult> result; - - auto* box1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box1")); - auto* box2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box2")); - auto* box3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box3")); - auto* box4 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box4")); - - // Ensure we cached the result for box1 in the first layout pass. - result = RunCachedLayoutResult(space300, NGBlockNode(box1)); - EXPECT_NE(result.get(), nullptr); - - // box1 was sized to its max-content size in the first layout pass, passing - // an available size larger than the fragment should hit the cache. - result = RunCachedLayoutResult(space400, NGBlockNode(box1)); - EXPECT_NE(result.get(), nullptr); - - // Passing an available size smaller than the fragment should miss the cache - // as the fragment may shrink. - result = RunCachedLayoutResult(space100, NGBlockNode(box1)); - EXPECT_EQ(result.get(), nullptr); - - // Ensure we cached the result for box2 in the first layout pass. - result = RunCachedLayoutResult(space300, NGBlockNode(box2)); - EXPECT_NE(result.get(), nullptr); - - // box2 was sized to its min-content size in the first layout pass, passing - // an available size smaller than the fragment should hit the cache. - result = RunCachedLayoutResult(space200, NGBlockNode(box2)); - EXPECT_NE(result.get(), nullptr); - - // Passing an available size larger than the fragment should miss the cache - // as the fragment may shrink. - result = RunCachedLayoutResult(space400, NGBlockNode(box2)); - EXPECT_EQ(result.get(), nullptr); - - // box3 was sized to its min-content size in the first layout pass, however - // it should miss the cache as it has a %-min-size. - result = RunCachedLayoutResult(space200, NGBlockNode(box3)); - EXPECT_EQ(result.get(), nullptr); - - // box4 was sized to its max-content size in the first layout pass (the same - // as box1) however it should miss the cache due to its margin. - result = RunCachedLayoutResult(space250, NGBlockNode(box4)); - EXPECT_EQ(result.get(), nullptr); -} - TEST_F(NGBlockLayoutAlgorithmTest, LineOffsetCaching) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <div id="container" style="display: flow-root; width: 300px; height: 100px;"> <div id="box1" style="width: 100px; margin: 0 auto 0 auto;"></div> @@ -404,10 +306,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, LineOffsetCaching) { builder.SetAvailableSize(size); builder.SetPercentageResolutionSize(size); builder.SetTextDirection(TextDirection::kLtr); - builder.AddBaselineRequest({NGBaselineAlgorithmType::kAtomicInline, - FontBaseline::kAlphabeticBaseline}); - builder.AddBaselineRequest({NGBaselineAlgorithmType::kFirstLine, - FontBaseline::kAlphabeticBaseline}); + builder.SetNeedsBaseline(true); builder.SetBfcOffset(bfc_offset); return builder.ToConstraintSpace(); }; @@ -1645,7 +1544,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContent) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); - MinMaxSize sizes = RunComputeMinAndMax(container); + MinMaxSizes sizes = RunComputeMinMaxSizes(container); EXPECT_EQ(kSecondChildWidth, sizes.min_size); EXPECT_EQ(kSecondChildWidth, sizes.max_size); } @@ -1666,7 +1565,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContentFloats) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); - MinMaxSize sizes = RunComputeMinAndMax(container); + MinMaxSizes sizes = RunComputeMinMaxSizes(container); EXPECT_EQ(LayoutUnit(40), sizes.min_size); EXPECT_EQ(LayoutUnit(90), sizes.max_size); } @@ -1687,7 +1586,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContentFloatsClearance) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); - MinMaxSize sizes = RunComputeMinAndMax(container); + MinMaxSizes sizes = RunComputeMinMaxSizes(container); EXPECT_EQ(LayoutUnit(40), sizes.min_size); EXPECT_EQ(LayoutUnit(50), sizes.max_size); } @@ -1708,7 +1607,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContentNewFormattingContext) { NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); - MinMaxSize sizes = RunComputeMinAndMax(container); + MinMaxSizes sizes = RunComputeMinMaxSizes(container); EXPECT_EQ(LayoutUnit(100), sizes.min_size); EXPECT_EQ(LayoutUnit(100), sizes.max_size); } @@ -1730,7 +1629,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); - MinMaxSize sizes = RunComputeMinAndMax(container); + MinMaxSizes sizes = RunComputeMinMaxSizes(container); EXPECT_EQ(LayoutUnit(30), sizes.min_size); EXPECT_EQ(LayoutUnit(70), sizes.max_size); } @@ -1748,7 +1647,7 @@ TEST_F(NGBlockLayoutAlgorithmTest, NGBlockNode container(ToLayoutBox(GetLayoutObjectByElementId("container"))); - MinMaxSize sizes = RunComputeMinAndMax(container); + MinMaxSizes sizes = RunComputeMinMaxSizes(container); EXPECT_EQ(LayoutUnit(), sizes.min_size); EXPECT_EQ(LayoutUnit(), sizes.max_size); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc index 301d9e9215b..48871b0c4f8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.cc @@ -7,8 +7,11 @@ #include <memory> #include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/html_marquee_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" #include "third_party/blink/renderer/core/layout/box_layout_extra_input.h" +#include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/layout_fieldset.h" #include "third_party/blink/renderer/core/layout/layout_inline.h" @@ -17,7 +20,8 @@ #include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h" #include "third_party/blink/renderer/core/layout/layout_table.h" #include "third_party/blink/renderer/core/layout/layout_table_cell.h" -#include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/layout_video.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h" #include "third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" @@ -25,6 +29,10 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h" #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h" +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h" +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h" +#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" @@ -43,6 +51,10 @@ #include "third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h" +#include "third_party/blink/renderer/core/layout/text_autosizer.h" +#include "third_party/blink/renderer/core/mathml/mathml_element.h" +#include "third_party/blink/renderer/core/mathml/mathml_fraction_element.h" +#include "third_party/blink/renderer/core/mathml/mathml_space_element.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/text/writing_mode.h" @@ -74,6 +86,24 @@ NOINLINE void CreateAlgorithmAndRun(const NGLayoutAlgorithmParams& params, } template <typename Callback> +NOINLINE void DetermineMathMLAlgorithmAndRun( + const LayoutBox& box, + const NGLayoutAlgorithmParams& params, + const Callback& callback) { + DCHECK(box.IsMathML()); + // Currently math layout algorithms can only apply to MathML elements. + auto* element = box.GetNode(); + DCHECK(element); + if (IsA<MathMLSpaceElement>(element)) + CreateAlgorithmAndRun<NGMathSpaceLayoutAlgorithm>(params, callback); + else if (IsA<MathMLFractionElement>(element) && + IsValidMathMLFraction(params.node)) + CreateAlgorithmAndRun<NGMathFractionLayoutAlgorithm>(params, callback); + else + CreateAlgorithmAndRun<NGMathRowLayoutAlgorithm>(params, callback); +} + +template <typename Callback> NOINLINE void DetermineAlgorithmAndRun(const NGLayoutAlgorithmParams& params, const Callback& callback) { const ComputedStyle& style = params.node.Style(); @@ -82,6 +112,8 @@ NOINLINE void DetermineAlgorithmAndRun(const NGLayoutAlgorithmParams& params, CreateAlgorithmAndRun<NGFlexLayoutAlgorithm>(params, callback); } else if (box.IsLayoutNGCustom()) { CreateAlgorithmAndRun<NGCustomLayoutAlgorithm>(params, callback); + } else if (box.IsMathML()) { + DetermineMathMLAlgorithmAndRun(box, params, callback); } else if (box.IsLayoutNGFieldset()) { CreateAlgorithmAndRun<NGFieldsetLayoutAlgorithm>(params, callback); // If there's a legacy layout box, we can only do block fragmentation if @@ -108,15 +140,15 @@ inline scoped_refptr<const NGLayoutResult> LayoutWithAlgorithm( return result; } -inline base::Optional<MinMaxSize> ComputeMinMaxSizeWithAlgorithm( +inline base::Optional<MinMaxSizes> ComputeMinMaxSizesWithAlgorithm( const NGLayoutAlgorithmParams& params, - const MinMaxSizeInput& input) { - base::Optional<MinMaxSize> minmax; + const MinMaxSizesInput& input) { + base::Optional<MinMaxSizes> min_max_sizes; DetermineAlgorithmAndRun( - params, [&minmax, &input](NGLayoutAlgorithmOperations* algorithm) { - minmax = algorithm->ComputeMinMaxSize(input); + params, [&min_max_sizes, &input](NGLayoutAlgorithmOperations* algorithm) { + min_max_sizes = algorithm->ComputeMinMaxSizes(input); }); - return minmax; + return min_max_sizes; } void UpdateLegacyMultiColumnFlowThread( @@ -131,7 +163,7 @@ void UpdateLegacyMultiColumnFlowThread( // Stitch the columns together. NGBoxStrut border_scrollbar_padding = - ComputeBorders(constraint_space, node) + + ComputeBorders(constraint_space, node.Style()) + ComputeScrollbars(constraint_space, node) + ComputePadding(constraint_space, node.Style()); NGFragment logical_multicol_fragment(writing_mode, fragment); @@ -171,7 +203,8 @@ void UpdateLegacyMultiColumnFlowThread( if (!has_processed_first_column_in_flow_thread) { // The offset of the flow thread should be the same as that of the first // first column. - flow_thread->SetLocation(child.Offset().ToLayoutPoint()); + flow_thread->SetLocationAndUpdateOverflowControlsIfNeeded( + child.Offset().ToLayoutPoint()); flow_thread->SetLogicalWidth(logical_column_fragment.InlineSize()); has_processed_first_column_in_flow_thread = true; } @@ -267,11 +300,36 @@ void SetupBoxLayoutExtraInput(const NGConstraintSpace& space, input->override_block_size = space.AvailableSize().block_size; } +bool CanUseCachedIntrinsicInlineSizes(const MinMaxSizesInput& input, + const LayoutBox& box) { + // Obviously can't use the cache if our intrinsic logical widths are dirty. + if (box.IntrinsicLogicalWidthsDirty()) + return false; + + // We don't store the float inline sizes for comparison, always skip the + // cache in this case. + if (input.float_left_inline_size || input.float_right_inline_size) + return false; + + // Check if we have any percentage inline padding. + const auto& style = box.StyleRef(); + if (style.MayHavePadding() && (style.PaddingStart().IsPercentOrCalc() || + style.PaddingEnd().IsPercentOrCalc())) + return false; + + // Check if the %-block-size matches. + if (input.percentage_resolution_block_size != + box.IntrinsicLogicalWidthsPercentageResolutionBlockSize()) + return false; + + return true; +} + } // namespace scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( const NGConstraintSpace& constraint_space, - const NGBreakToken* break_token, + const NGBlockBreakToken* break_token, const NGEarlyBreak* early_break) { // Use the old layout code and synthesize a fragment. if (!CanUseNewLayout()) @@ -296,8 +354,8 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( scoped_refptr<const NGLayoutResult> layout_result = box_->CachedLayoutResult(constraint_space, break_token, early_break, &fragment_geometry, &cache_status); - if (layout_result) { - DCHECK_EQ(cache_status, NGLayoutCacheStatus::kHit); + if (cache_status == NGLayoutCacheStatus::kHit) { + DCHECK(layout_result); // We may have to update the margins on box_; we reuse the layout result // even if a percentage margin may have changed. @@ -325,11 +383,13 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( CalculateInitialFragmentGeometry(constraint_space, *this); } + TextAutosizer::NGLayoutScope text_autosizer_layout_scope( + box_, fragment_geometry->border_box_size.inline_size); + PrepareForLayout(); NGLayoutAlgorithmParams params(*this, *fragment_geometry, constraint_space, - To<NGBlockBreakToken>(break_token), - early_break); + break_token, early_break); // Try to perform "simplified" layout. // TODO(crbug.com/992953): Add a simplified layout pass for custom layout. @@ -339,26 +399,33 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( !(block_flow->ChildrenInline() && RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) && !block_flow->IsLayoutNGCustom()) { + DCHECK(layout_result); +#if DCHECK_IS_ON() + scoped_refptr<const NGLayoutResult> previous_result = layout_result; +#endif + // A child may have changed size while performing "simplified" layout (it // may have gained or removed scrollbars, changing its size). In these // cases "simplified" layout will return a null layout-result, indicating // we need to perform a full layout. - layout_result = RunSimplifiedLayout(params); + layout_result = RunSimplifiedLayout(params, *layout_result); #if DCHECK_IS_ON() if (layout_result) { layout_result->CheckSameForSimplifiedLayout( - *box_->GetCachedLayoutResult(), /* check_same_block_size */ false); + *previous_result, /* check_same_block_size */ false); } #endif + } else { + layout_result = nullptr; } // Fragment geometry scrollbars are potentially size constrained, and cannot // be used for comparison with their after layout size. NGBoxStrut before_layout_scrollbars = ComputeScrollbars(constraint_space, *this); - bool before_layout_preferred_logical_widths_dirty = - box_->PreferredLogicalWidthsDirty(); + bool before_layout_intrinsic_logical_widths_dirty = + box_->IntrinsicLogicalWidthsDirty(); if (!layout_result) layout_result = LayoutWithAlgorithm(params); @@ -373,10 +440,15 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::Layout( // This mirrors legacy code in PaintLayerScrollableArea::UpdateAfterLayout. if ((before_layout_scrollbars != ComputeScrollbars(constraint_space, *this)) || - (!before_layout_preferred_logical_widths_dirty && - box_->PreferredLogicalWidthsDirty())) { + (!before_layout_intrinsic_logical_widths_dirty && + box_->IntrinsicLogicalWidthsDirty())) { PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbars; + // We need to clear any previous results when scrollbars change. For + // example - we may have stored a "measure" layout result which will be + // incorrect if we try and reuse it. + box_->ClearLayoutResults(); + #if DCHECK_IS_ON() // Ensure turning on/off scrollbars only once at most, when we call // |LayoutWithAlgorithm| recursively. @@ -506,18 +578,32 @@ void NGBlockNode::PrepareForLayout() { void NGBlockNode::FinishLayout( LayoutBlockFlow* block_flow, const NGConstraintSpace& constraint_space, - const NGBreakToken* break_token, + const NGBlockBreakToken* break_token, scoped_refptr<const NGLayoutResult> layout_result) { // If we abort layout and don't clear the cached layout-result, we can end // up in a state where the layout-object tree doesn't match fragment tree // referenced by this layout-result. if (layout_result->Status() != NGLayoutResult::kSuccess) { - box_->ClearCachedLayoutResult(); + box_->ClearLayoutResults(); return; } - if (!constraint_space.HasBlockFragmentation()) - box_->SetCachedLayoutResult(*layout_result, break_token); + // Add all layout results (and fragments) generated from a node to a list in + // the layout object. Some extra care is required to correctly overwrite + // intermediate layout results: The sequence number of an incoming break token + // corresponds with the fragment index in the layout object (off by 1, + // though). When writing back a layout result, we remove any fragments in the + // layout box at higher indices than that of the one we're writing back. + const auto& physical_fragment = + To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()); + wtf_size_t fragment_index = 0; + if (break_token && !break_token->IsBreakBefore()) + fragment_index = break_token->SequenceNumber() + 1; + + if (layout_result->IsSingleUse()) + box_->AddLayoutResult(layout_result, fragment_index); + else + box_->SetCachedLayoutResult(layout_result); if (block_flow) { auto* child = GetLayoutObjectForFirstChildNode(block_flow); @@ -543,40 +629,28 @@ void NGBlockNode::FinishLayout( } if (has_inline_children) { - const auto& physical_fragment = - To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()); if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { CopyFragmentDataToLayoutBoxForInlineChildren( physical_fragment, physical_fragment.Size().width, Style().IsFlippedBlocksWritingMode()); - block_flow->SetPaintFragment(To<NGBlockBreakToken>(break_token), - &physical_fragment); + block_flow->SetPaintFragment(break_token, &physical_fragment); } else { CopyFragmentDataToLayoutBoxForInlineChildren(physical_fragment); - - // Floats are in the fragment tree, not in the item list, and the - // painter relies on |LayoutBox.Location()|. - if (physical_fragment.HasFloatingDescendantsForPaint()) { - CopyFragmentDataToLayoutBoxForInlineChildren( - physical_fragment, physical_fragment.Size().width, - Style().IsFlippedBlocksWritingMode()); - } } } else { // We still need to clear paint fragments in case it had inline children, // and thus had NGPaintFragment. block_flow->ClearNGInlineNodeData(); - block_flow->SetPaintFragment(To<NGBlockBreakToken>(break_token), nullptr); + block_flow->SetPaintFragment(break_token, nullptr); } } - CopyFragmentDataToLayoutBox(constraint_space, *layout_result, - To<NGBlockBreakToken>(break_token)); + CopyFragmentDataToLayoutBox(constraint_space, *layout_result, break_token); } -MinMaxSize NGBlockNode::ComputeMinMaxSize( +MinMaxSizes NGBlockNode::ComputeMinMaxSizes( WritingMode container_writing_mode, - const MinMaxSizeInput& input, + const MinMaxSizesInput& input, const NGConstraintSpace* constraint_space) { // TODO(layoutng) Can UpdateMarkerTextIfNeeded call be moved // somewhere else? List items need up-to-date markers before layout. @@ -586,13 +660,17 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( bool is_orthogonal_flow_root = !IsParallelWritingMode(container_writing_mode, Style().GetWritingMode()); - MinMaxSize sizes; + if (CanUseCachedIntrinsicInlineSizes(input, *box_)) + return box_->IntrinsicLogicalWidths(); + + box_->SetIntrinsicLogicalWidthsDirty(); + + MinMaxSizes sizes; // If we're orthogonal, we have to run layout to compute the sizes. However, // if we're outside of layout, we can't do that. This can happen on Mac. if ((!CanUseNewLayout() && !is_orthogonal_flow_root) || - (is_orthogonal_flow_root && !box_->GetFrameView()->IsInPerformLayout())) { - return ComputeMinMaxSizeFromLegacy(input); - } + (is_orthogonal_flow_root && !box_->GetFrameView()->IsInPerformLayout())) + return ComputeMinMaxSizesFromLegacy(input); NGConstraintSpace zero_constraint_space = CreateConstraintSpaceBuilderForMinMax(*this).ToConstraintSpace(); @@ -614,18 +692,12 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( TextDirection::kLtr, // irrelevant here To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment())); sizes.min_size = sizes.max_size = fragment.Size().inline_size; - if (input.size_type == NGMinMaxSizeType::kContentBoxSize) { - sizes -= fragment.Borders().InlineSum() + fragment.Padding().InlineSum() + - box_->ScrollbarLogicalWidth(); - DCHECK_GE(sizes.min_size, LayoutUnit()); - DCHECK_GE(sizes.max_size, LayoutUnit()); - } return sizes; } NGFragmentGeometry fragment_geometry = CalculateInitialMinMaxFragmentGeometry(*constraint_space, *this); - base::Optional<MinMaxSize> maybe_sizes = ComputeMinMaxSizeWithAlgorithm( + base::Optional<MinMaxSizes> maybe_sizes = ComputeMinMaxSizesWithAlgorithm( NGLayoutAlgorithmParams(*this, fragment_geometry, *constraint_space), input); @@ -633,13 +705,21 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( auto* html_marquee_element = DynamicTo<HTMLMarqueeElement>(box_->GetNode()); if (UNLIKELY(html_marquee_element && html_marquee_element->IsHorizontal())) maybe_sizes->min_size = LayoutUnit(); + else if (UNLIKELY(IsA<HTMLSelectElement>(box_->GetNode()) || + (IsA<HTMLInputElement>(box_->GetNode()) && + To<HTMLInputElement>(box_->GetNode())->type() == + input_type_names::kFile)) && + Style().LogicalWidth().IsPercentOrCalc()) + maybe_sizes->min_size = LayoutUnit(); + box_->SetIntrinsicLogicalWidthsFromNG( + *maybe_sizes, input.percentage_resolution_block_size); return *maybe_sizes; } if (!box_->GetFrameView()->IsInPerformLayout()) { // We can't synthesize these using Layout() if we're not in PerformLayout. // This situation can happen on mac. Fall back to legacy instead. - return ComputeMinMaxSizeFromLegacy(input); + return ComputeMinMaxSizesFromLegacy(input); } // Have to synthesize this value. @@ -662,18 +742,11 @@ MinMaxSize NGBlockNode::ComputeMinMaxSize( TextDirection::kLtr, // irrelevant here To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment())); sizes.max_size = max_fragment.Size().inline_size; - - if (input.size_type == NGMinMaxSizeType::kContentBoxSize) { - sizes -= max_fragment.Borders().InlineSum() + - max_fragment.Padding().InlineSum() + box_->ScrollbarLogicalWidth(); - DCHECK_GE(sizes.min_size, LayoutUnit()); - DCHECK_GE(sizes.max_size, LayoutUnit()); - } return sizes; } -MinMaxSize NGBlockNode::ComputeMinMaxSizeFromLegacy( - const MinMaxSizeInput& input) const { +MinMaxSizes NGBlockNode::ComputeMinMaxSizesFromLegacy( + const MinMaxSizesInput& input) const { bool needs_size_reset = false; if (!box_->HasOverrideContainingBlockContentLogicalHeight()) { box_->SetOverrideContainingBlockContentLogicalHeight( @@ -681,16 +754,13 @@ MinMaxSize NGBlockNode::ComputeMinMaxSizeFromLegacy( needs_size_reset = true; } - MinMaxSize sizes; - // ComputeIntrinsicLogicalWidths returns content-box + scrollbar. - box_->ComputeIntrinsicLogicalWidths(sizes.min_size, sizes.max_size); - if (input.size_type == NGMinMaxSizeType::kContentBoxSize) { - sizes -= LayoutUnit(box_->ScrollbarLogicalWidth()); - DCHECK_GE(sizes.min_size, LayoutUnit()); - DCHECK_GE(sizes.max_size, LayoutUnit()); - } else { - sizes += box_->BorderAndPaddingLogicalWidth(); - } + // Tables don't calculate their min/max content contribution the same way as + // other layout nodes. This is because width/min-width/etc have a different + // meaning for tables. + // + // Due to this the min/max content contribution is their min/max content size. + MinMaxSizes sizes = box_->IsTable() ? box_->PreferredLogicalWidths() + : box_->IntrinsicLogicalWidths(); if (needs_size_reset) box_->ClearOverrideContainingBlockContentSize(); @@ -857,10 +927,9 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( LayoutBlock* block = DynamicTo<LayoutBlock>(box_); bool needs_full_invalidation = false; if (LIKELY(block && is_last_fragment)) { - LayoutUnit intrinsic_block_size = - layout_result.UnconstrainedIntrinsicBlockSize(); + LayoutUnit overflow_block_size = layout_result.OverflowBlockSize(); if (UNLIKELY(previous_break_token)) - intrinsic_block_size += previous_break_token->ConsumedBlockSize(); + overflow_block_size += previous_break_token->ConsumedBlockSize(); #if DCHECK_IS_ON() block->CheckPositionedObjectsNeedLayout(); @@ -881,10 +950,9 @@ void NGBlockNode::CopyFragmentDataToLayoutBox( // |ComputeOverflow()| below calls |AddVisualOverflowFromChildren()|, which // computes visual overflow from |RootInlineBox| if |ChildrenInline()| - // TODO(rego): This causes that ChildNeedsLayoutOverflowRecalc flags are not - // cleared after layout (see https://crbug.com/941180). - block->SetNeedsOverflowRecalc(); - block->ComputeLayoutOverflow(intrinsic_block_size - borders.block_end - + block->SetNeedsOverflowRecalc( + LayoutObject::OverflowRecalcType::kOnlyVisualOverflowRecalc); + block->ComputeLayoutOverflow(overflow_block_size - borders.block_end - scrollbars.block_end); } @@ -915,7 +983,7 @@ void NGBlockNode::PlaceChildrenInLayoutBox( for (const auto& child_fragment : physical_fragment.Children()) { // Skip any line-boxes we have as children, this is handled within // NGInlineNode at the moment. - if (!child_fragment->IsBox() && !child_fragment->IsRenderedLegend()) + if (!child_fragment->IsBox()) continue; const auto& box_fragment = *To<NGPhysicalBoxFragment>(child_fragment.get()); @@ -940,7 +1008,7 @@ void NGBlockNode::PlaceChildrenInLayoutBox( DCHECK(IsA<HTMLFieldSetElement>(content_wrapper->Parent()->GetNode())); LayoutPoint location = rendered_legend->Location(); location -= content_wrapper->Location(); - rendered_legend->SetLocation(location); + rendered_legend->SetLocationAndUpdateOverflowControlsIfNeeded(location); } } @@ -996,7 +1064,8 @@ void NGBlockNode::CopyChildFragmentPosition( offset.left += consumed; } - layout_box->SetLocation(offset.ToLayoutPoint()); + layout_box->SetLocationAndUpdateOverflowControlsIfNeeded( + offset.ToLayoutPoint()); } // For inline children, NG painters handles fragments directly, but there are @@ -1007,6 +1076,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( LayoutUnit initial_container_width, bool initial_container_is_flipped, PhysicalOffset offset) { + DCHECK(!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); for (const auto& child : container.Children()) { if (child->IsContainer()) { PhysicalOffset child_offset = offset + child.Offset(); @@ -1022,7 +1092,8 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( child->Size().width - maybe_flipped_offset.left; } - layout_box.SetLocation(maybe_flipped_offset.ToLayoutPoint()); + layout_box.SetLocationAndUpdateOverflowControlsIfNeeded( + maybe_flipped_offset.ToLayoutPoint()); } // Legacy compatibility. This flag is used in paint layer for @@ -1038,7 +1109,7 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( // LayoutBlockFlow. If |child| establishes a new block formatting context, // it also creates another inline formatting context. Do not copy to its // descendants in this case. - if (!child->IsBlockFormattingContextRoot()) { + if (!child->IsFormattingContextRoot()) { CopyFragmentDataToLayoutBoxForInlineChildren( To<NGPhysicalContainerFragment>(*child), initial_container_width, initial_container_is_flipped, child_offset); @@ -1055,20 +1126,22 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( return; bool initial_container_is_flipped = Style().IsFlippedBlocksWritingMode(); for (NGInlineCursor cursor(*items); cursor; cursor.MoveToNext()) { - if (const NGPhysicalBoxFragment* child = cursor.CurrentBoxFragment()) { + if (const NGPhysicalBoxFragment* child = cursor.Current().BoxFragment()) { // Replaced elements and inline blocks need Location() set relative to // their block container. LayoutObject* layout_object = child->GetMutableLayoutObject(); if (!layout_object) continue; if (LayoutBox* layout_box = ToLayoutBoxOrNull(layout_object)) { - PhysicalOffset maybe_flipped_offset = cursor.CurrentOffset(); + PhysicalOffset maybe_flipped_offset = + cursor.Current().OffsetInContainerBlock(); if (initial_container_is_flipped) { maybe_flipped_offset.left = container.Size().width - child->Size().width - maybe_flipped_offset.left; } - layout_box->SetLocation(maybe_flipped_offset.ToLayoutPoint()); + layout_box->SetLocationAndUpdateOverflowControlsIfNeeded( + maybe_flipped_offset.ToLayoutPoint()); continue; } @@ -1085,9 +1158,9 @@ void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren( } } -bool NGBlockNode::ChildrenInline() const { +bool NGBlockNode::IsInlineFormattingContextRoot() const { if (const auto* block = DynamicTo<LayoutBlockFlow>(box_)) - return AreNGBlockFlowChildrenInline(block); + return AreNGBlockFlowChildrenInline(block) && FirstChild().IsInline(); return false; } @@ -1101,10 +1174,28 @@ bool NGBlockNode::IsAtomicInlineLevel() const { return GetLayoutBox()->IsAtomicInlineLevel() && GetLayoutBox()->IsInline(); } -bool NGBlockNode::MayHaveAspectRatio() const { +bool NGBlockNode::HasAspectRatio() const { LayoutBox* layout_object = GetLayoutBox(); - return layout_object->IsImage() || layout_object->IsVideo() || - layout_object->IsCanvas(); + if (!layout_object->IsImage() && !IsA<LayoutVideo>(layout_object) && + !layout_object->IsCanvas()) + return false; + + // Retrieving this and throwing it away is wasteful. We could make this method + // return Optional<LogicalSize> that returns the aspect_ratio if there is one. + return !GetAspectRatio().IsEmpty(); +} + +LogicalSize NGBlockNode::GetAspectRatio() const { + base::Optional<LayoutUnit> computed_inline_size; + base::Optional<LayoutUnit> computed_block_size; + GetOverrideIntrinsicSize(&computed_inline_size, &computed_block_size); + if (computed_inline_size && computed_block_size) + return LogicalSize(*computed_inline_size, *computed_block_size); + + IntrinsicSizingInfo legacy_sizing_info; + ToLayoutReplaced(box_)->ComputeIntrinsicSizingInfo(legacy_sizing_info); + return LogicalSize(LayoutUnit(legacy_sizing_info.aspect_ratio.Width()), + LayoutUnit(legacy_sizing_info.aspect_ratio.Height())); } bool NGBlockNode::UseLogicalBottomMarginEdgeForInlineBlockBaseline() const { @@ -1121,21 +1212,17 @@ bool NGBlockNode::IsCustomLayoutLoaded() const { scoped_refptr<const NGLayoutResult> NGBlockNode::LayoutAtomicInline( const NGConstraintSpace& parent_constraint_space, const ComputedStyle& parent_style, - FontBaseline baseline_type, - bool use_first_line_style) { + bool use_first_line_style, + NGBaselineAlgorithmType baseline_algorithm_type) { NGConstraintSpaceBuilder builder( parent_constraint_space, Style().GetWritingMode(), /* is_new_fc */ true); SetOrthogonalFallbackInlineSizeIfNeeded(parent_style, *this, &builder); + builder.SetIsPaintedAtomically(true); builder.SetUseFirstLineStyle(use_first_line_style); - // Request to compute baseline during the layout, except when we know the box - // would synthesize box-baseline. - LayoutBox* layout_box = GetLayoutBox(); - if (NGBaseline::ShouldPropagateBaselines(layout_box)) { - builder.AddBaselineRequest( - {NGBaselineAlgorithmType::kAtomicInline, baseline_type}); - } + builder.SetNeedsBaseline(true); + builder.SetBaselineAlgorithmType(baseline_algorithm_type); builder.SetIsShrinkToFit(Style().LogicalWidth().IsAuto()); builder.SetAvailableSize(parent_constraint_space.AvailableSize()); @@ -1148,7 +1235,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::LayoutAtomicInline( scoped_refptr<const NGLayoutResult> result = Layout(constraint_space); // TODO(kojii): Investigate why ClearNeedsLayout() isn't called automatically // when it's being laid out. - layout_box->ClearNeedsLayout(); + GetLayoutBox()->ClearNeedsLayout(); return result; } @@ -1208,7 +1295,13 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout( constraint_space.IsNewFormattingContext()); builder.SetInitialFragmentGeometry(fragment_geometry); builder.SetIsLegacyLayoutRoot(); - builder.SetIntrinsicBlockSize(box_->IntrinsicContentLogicalHeight()); + if (box_->ShouldComputeSizeAsReplaced()) { + builder.SetIntrinsicBlockSize(box_->LogicalHeight()); + } else { + builder.SetIntrinsicBlockSize(box_->IntrinsicContentLogicalHeight() + + box_->BorderAndPaddingLogicalHeight() + + box_->ScrollbarLogicalHeight()); + } // If we're block-fragmented, we can only handle monolithic content, since // the two block fragmentation machineries (NG and legacy) cannot cooperate. @@ -1227,7 +1320,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout( CopyBaselinesFromLegacyLayout(constraint_space, &builder); layout_result = builder.ToBoxFragment(); - box_->SetCachedLayoutResult(*layout_result, /* break_token */ nullptr); + box_->SetCachedLayoutResult(layout_result); // If |SetCachedLayoutResult| did not update cached |LayoutResult|, // |NeedsLayout()| flag should not be cleared. @@ -1254,7 +1347,7 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout( *layout_result, constraint_space, layout_result->EndMarginStrut(), layout_result->BfcLineOffset(), layout_result->BfcBlockOffset(), LayoutUnit() /* block_offset_delta */)); - box_->SetCachedLayoutResult(*layout_result, /* break_token */ nullptr); + box_->SetCachedLayoutResult(layout_result); } } @@ -1265,42 +1358,40 @@ scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout( } scoped_refptr<const NGLayoutResult> NGBlockNode::RunSimplifiedLayout( - const NGLayoutAlgorithmParams& params) const { - return NGSimplifiedLayoutAlgorithm(params, *box_->GetCachedLayoutResult()) - .Layout(); + const NGLayoutAlgorithmParams& params, + const NGLayoutResult& result) const { + return NGSimplifiedLayoutAlgorithm(params, result).Layout(); } void NGBlockNode::CopyBaselinesFromLegacyLayout( const NGConstraintSpace& constraint_space, NGBoxFragmentBuilder* builder) { - const NGBaselineRequestList requests = constraint_space.BaselineRequests(); - if (requests.IsEmpty()) + // As the calls to query baselines from legacy layout are potentially + // expensive we only ask for them if needed. + // TODO(layout-dev): Once we have flexbox, and editing switched over to + // LayoutNG we should be able to safely remove this flag without a + // performance penalty. + if (!constraint_space.NeedsBaseline()) return; - if (UNLIKELY(constraint_space.GetWritingMode() != Style().GetWritingMode())) - return; - - for (const auto& request : requests) { - switch (request.AlgorithmType()) { - case NGBaselineAlgorithmType::kAtomicInline: { - LayoutUnit position = - AtomicInlineBaselineFromLegacyLayout(request, constraint_space); - if (position != -1) - builder->AddBaseline(request, position); - break; - } - case NGBaselineAlgorithmType::kFirstLine: { - LayoutUnit position = box_->FirstLineBoxBaseline(); - if (position != -1) - builder->AddBaseline(request, position); - break; - } + switch (constraint_space.BaselineAlgorithmType()) { + case NGBaselineAlgorithmType::kFirstLine: { + LayoutUnit position = box_->FirstLineBoxBaseline(); + if (position != -1) + builder->SetBaseline(position); + break; + } + case NGBaselineAlgorithmType::kInlineBlock: { + LayoutUnit position = + AtomicInlineBaselineFromLegacyLayout(constraint_space); + if (position != -1) + builder->SetBaseline(position); + break; } } } LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout( - const NGBaselineRequest& request, const NGConstraintSpace& constraint_space) { LineDirectionMode line_direction = box_->IsHorizontalWritingMode() ? LineDirectionMode::kHorizontalLine @@ -1310,7 +1401,7 @@ LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout( // classes override it assuming inline layout calls |BaselinePosition()|. if (box_->IsInline()) { LayoutUnit position = LayoutUnit(box_->BaselinePosition( - request.BaselineType(), constraint_space.UseFirstLineStyle(), + box_->Style()->GetFontBaseline(), constraint_space.UseFirstLineStyle(), line_direction, kPositionOnContainingLine)); // BaselinePosition() uses margin edge for atomic inlines. Subtract @@ -1318,6 +1409,9 @@ LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout( if (box_->IsAtomicInlineLevel()) position -= box_->MarginOver(); + if (IsFlippedLinesWritingMode(constraint_space.GetWritingMode())) + return box_->Size().Width() - position; + return position; } @@ -1363,4 +1457,8 @@ void NGBlockNode::StoreMargins(const NGConstraintSpace& constraint_space, box_->SetMargin(physical_margins); } +void NGBlockNode::StoreMargins(const NGPhysicalBoxStrut& physical_margins) { + box_->SetMargin(physical_margins); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h index 91b6986774a..7ccb575505f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node.h @@ -14,16 +14,14 @@ namespace blink { class LayoutBox; -class NGBaselineRequest; class NGBlockBreakToken; class NGBoxFragmentBuilder; -class NGBreakToken; class NGConstraintSpace; class NGEarlyBreak; class NGLayoutResult; class NGPhysicalBoxFragment; class NGPhysicalContainerFragment; -struct MinMaxSize; +struct MinMaxSizes; struct NGBoxStrut; struct NGLayoutAlgorithmParams; @@ -37,7 +35,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { scoped_refptr<const NGLayoutResult> Layout( const NGConstraintSpace& constraint_space, - const NGBreakToken* break_token = nullptr, + const NGBlockBreakToken* break_token = nullptr, const NGEarlyBreak* = nullptr); // This method is just for use within the |NGSimplifiedLayoutAlgorithm|. @@ -66,7 +64,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // Computes the value of min-content and max-content for this node's border // box. - // If the underlying layout algorithm's ComputeMinMaxSize returns + // If the underlying layout algorithm's ComputeMinMaxSizes returns // no value, this function will synthesize these sizes using Layout with // special constraint spaces -- infinite available size for max content, zero // available size for min content, and percentage resolution size zero for @@ -82,21 +80,29 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // The constraint space is also used to perform layout when this block's // writing mode is orthogonal to its parent's, in which case the constraint // space is not optional. - MinMaxSize ComputeMinMaxSize(WritingMode container_writing_mode, - const MinMaxSizeInput&, - const NGConstraintSpace* = nullptr); + MinMaxSizes ComputeMinMaxSizes(WritingMode container_writing_mode, + const MinMaxSizesInput&, + const NGConstraintSpace* = nullptr); - MinMaxSize ComputeMinMaxSizeFromLegacy(const MinMaxSizeInput&) const; + MinMaxSizes ComputeMinMaxSizesFromLegacy(const MinMaxSizesInput&) const; NGLayoutInputNode FirstChild() const; NGBlockNode GetRenderedLegend() const; NGBlockNode GetFieldsetContent() const; - bool ChildrenInline() const; + // Return true if this block node establishes an inline formatting context. + // This will only be the case if there is actual inline content. Empty nodes + // or nodes consisting purely of block-level, floats, and/or out-of-flow + // positioned children will return false. + bool IsInlineFormattingContextRoot() const; + bool IsInlineLevel() const; bool IsAtomicInlineLevel() const; - bool MayHaveAspectRatio() const; + bool HasAspectRatio() const; + + // Returns the aspect ratio of a replaced element. + LogicalSize GetAspectRatio() const; // Returns true if this node should fill the viewport. // This occurs when we are in quirks-mode and we are *not* OOF-positioned, @@ -127,8 +133,9 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { scoped_refptr<const NGLayoutResult> LayoutAtomicInline( const NGConstraintSpace& parent_constraint_space, const ComputedStyle& parent_style, - FontBaseline, - bool use_first_line_style); + bool use_first_line_style, + NGBaselineAlgorithmType baseline_algorithm_type = + NGBaselineAlgorithmType::kInlineBlock); // Called if this is an out-of-flow block which needs to be // positioned with legacy layout. @@ -136,6 +143,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { // Write back resolved margins to legacy. void StoreMargins(const NGConstraintSpace&, const NGBoxStrut& margins); + void StoreMargins(const NGPhysicalBoxStrut& margins); static bool CanUseNewLayout(const LayoutBox&); bool CanUseNewLayout() const; @@ -150,13 +158,14 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { scoped_refptr<const NGLayoutResult> RunLegacyLayout(const NGConstraintSpace&); scoped_refptr<const NGLayoutResult> RunSimplifiedLayout( - const NGLayoutAlgorithmParams&) const; + const NGLayoutAlgorithmParams&, + const NGLayoutResult&) const; // If this node is a LayoutNGMixin, the caller must pass the layout object for // this node cast to a LayoutBlockFlow as the first argument. void FinishLayout(LayoutBlockFlow*, const NGConstraintSpace&, - const NGBreakToken*, + const NGBlockBreakToken*, scoped_refptr<const NGLayoutResult>); // After we run the layout algorithm, this function copies back the geometry @@ -183,8 +192,7 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { void CopyBaselinesFromLegacyLayout(const NGConstraintSpace&, NGBoxFragmentBuilder*); - LayoutUnit AtomicInlineBaselineFromLegacyLayout(const NGBaselineRequest&, - const NGConstraintSpace&); + LayoutUnit AtomicInlineBaselineFromLegacyLayout(const NGConstraintSpace&); void UpdateShapeOutsideInfoIfNeeded( const NGLayoutResult&, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc index 8190b808e58..f42f0ef6ff5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_block_node_test.cc @@ -4,7 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" -#include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" namespace blink { @@ -174,9 +174,9 @@ TEST_F(NGBlockNodeForTest, MinAndMaxContent) { const int kWidth = 30; NGBlockNode box(ToLayoutBox(GetLayoutObjectByElementId("box"))); - MinMaxSize sizes = box.ComputeMinMaxSize( + MinMaxSizes sizes = box.ComputeMinMaxSizes( WritingMode::kHorizontalTb, - MinMaxSizeInput(/* percentage_resolution_block_size */ LayoutUnit())); + MinMaxSizesInput(/* percentage_resolution_block_size */ LayoutUnit())); EXPECT_EQ(LayoutUnit(kWidth), sizes.min_size); EXPECT_EQ(LayoutUnit(kWidth), sizes.max_size); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.cc index 20aef602f4e..f0b62aff415 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.cc @@ -12,75 +12,46 @@ namespace blink { -NGLineHeightMetrics NGBoxFragment::BaselineMetricsWithoutSynthesize( - const NGBaselineRequest& request) const { +NGLineHeightMetrics NGBoxFragment::BaselineMetrics( + const NGLineBoxStrut& margins, + FontBaseline baseline_type) const { + DCHECK(physical_fragment_.IsAtomicInline() || + physical_fragment_.IsListMarker()); + const ComputedStyle& style = physical_fragment_.Style(); + // For "leaf" theme objects, let the theme decide what the baseline position // is. The theme baseline wins over the propagated baselines. - const auto& physical_fragment = To<NGPhysicalBoxFragment>(physical_fragment_); - DCHECK(physical_fragment_.GetLayoutObject()); - const LayoutBox& layout_box = - ToLayoutBox(*physical_fragment_.GetLayoutObject()); - const ComputedStyle& style = physical_fragment.Style(); if (style.HasEffectiveAppearance() && !LayoutTheme::GetTheme().IsControlContainer( style.EffectiveAppearance())) { return NGLineHeightMetrics( - BlockSize() + layout_box.MarginOver() + + BlockSize() + margins.line_over + LayoutTheme::GetTheme().BaselinePositionAdjustment(style), - layout_box.MarginUnder()); + margins.line_under); } - // Check if we have a propagated baseline. - if (base::Optional<LayoutUnit> baseline = - physical_fragment.Baseline(request)) { - LayoutUnit ascent = *baseline; - LayoutUnit descent = BlockSize() - ascent; + base::Optional<LayoutUnit> baseline = Baseline(); + if (baseline) { + NGLineHeightMetrics metrics = + IsFlippedLinesWritingMode(GetWritingMode()) + ? NGLineHeightMetrics(BlockSize() - *baseline, *baseline) + : NGLineHeightMetrics(*baseline, BlockSize() - *baseline); - // For replaced elements, inline-block elements, and inline-table - // elements, the height is the height of their margin box. + // For replaced elements, inline-block elements, and inline-table elements, + // the height is the height of their margin-box. // https://drafts.csswg.org/css2/visudet.html#line-height - if (layout_box.IsAtomicInlineLevel()) { - ascent += layout_box.MarginOver(); - descent += layout_box.MarginUnder(); - } + metrics.ascent += margins.line_over; + metrics.descent += margins.line_under; - return NGLineHeightMetrics(ascent, descent); - } - - return NGLineHeightMetrics(); -} - -NGLineHeightMetrics NGBoxFragment::BaselineMetrics( - const NGBaselineRequest& request, - const NGConstraintSpace& constraint_space) const { - // Try to compute the baseline if the writing-modes are the same. - if (constraint_space.GetWritingMode() == GetWritingMode()) { - NGLineHeightMetrics metrics = BaselineMetricsWithoutSynthesize(request); - if (!metrics.IsEmpty()) - return metrics; + return metrics; } // The baseline type was not found. This is either this box should synthesize // box-baseline without propagating from children, or caller forgot to add // baseline requests to constraint space when it called Layout(). - LayoutUnit block_size = BlockSize(); - - // If atomic inline, use the margin box. See above. - const auto& physical_fragment = To<NGPhysicalBoxFragment>(physical_fragment_); - DCHECK(physical_fragment_.GetLayoutObject()); - const LayoutBox& layout_box = - ToLayoutBox(*physical_fragment_.GetLayoutObject()); - if (layout_box.IsAtomicInlineLevel()) { - bool is_parallel_writing_mode = - IsParallelWritingMode(constraint_space.GetWritingMode(), - physical_fragment.Style().GetWritingMode()); - if (is_parallel_writing_mode) - block_size += layout_box.MarginLogicalHeight(); - else - block_size += layout_box.MarginLogicalWidth(); - } + LayoutUnit block_size = BlockSize() + margins.BlockSum(); - if (request.BaselineType() == kAlphabeticBaseline) + if (baseline_type == kAlphabeticBaseline) return NGLineHeightMetrics(block_size, LayoutUnit()); return NGLineHeightMetrics(block_size - block_size / 2, block_size / 2); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h index dbfd474e7c8..f31585b148d 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment.h @@ -14,7 +14,6 @@ namespace blink { -class NGBaselineRequest; struct NGLineHeightMetrics; class CORE_EXPORT NGBoxFragment final : public NGFragment { @@ -24,18 +23,34 @@ class CORE_EXPORT NGBoxFragment final : public NGFragment { const NGPhysicalBoxFragment& physical_fragment) : NGFragment(writing_mode, physical_fragment), direction_(direction) {} + base::Optional<LayoutUnit> FirstBaseline() const { + if (GetWritingMode() != physical_fragment_.Style().GetWritingMode()) + return base::nullopt; + + return To<NGPhysicalBoxFragment>(physical_fragment_).Baseline(); + } + + // Returns the baseline for this fragment wrt. the parent writing mode. Will + // return a null baseline if: + // - The fragment has no baseline. + // - The writing modes differ. + base::Optional<LayoutUnit> Baseline() const { + if (GetWritingMode() != physical_fragment_.Style().GetWritingMode()) + return base::nullopt; + + if (auto last_baseline = + To<NGPhysicalBoxFragment>(physical_fragment_).LastBaseline()) + return last_baseline; + + return To<NGPhysicalBoxFragment>(physical_fragment_).Baseline(); + } + // Compute baseline metrics (ascent/descent) for this box. // - // Baseline requests must be added to constraint space when this fragment was - // laid out. - // - // The "WithoutSynthesize" version returns an empty metrics if this box does - // not have any baselines, while the other version synthesize the baseline - // from the box. - NGLineHeightMetrics BaselineMetricsWithoutSynthesize( - const NGBaselineRequest&) const; - NGLineHeightMetrics BaselineMetrics(const NGBaselineRequest&, - const NGConstraintSpace&) const; + // This will synthesize baseline metrics if no baseline is available. See + // |Baseline()| for when this may occur. + NGLineHeightMetrics BaselineMetrics(const NGLineBoxStrut& margins, + FontBaseline) const; NGBoxStrut Borders() const { const NGPhysicalBoxFragment& physical_box_fragment = diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc index 990d5a59d6d..96c7f46d336 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc @@ -77,6 +77,70 @@ void GatherInlineContainerFragmentsFromLinebox( } } +void GatherInlineContainerFragmentsFromItems( + const Vector<std::unique_ptr<NGFragmentItem>>& items, + const PhysicalOffset& box_offset, + NGBoxFragmentBuilder::InlineContainingBlockMap* inline_containing_block_map, + HashMap<const LayoutObject*, LineBoxPair>* containing_linebox_map) { + const NGPhysicalLineBoxFragment* linebox = nullptr; + for (const auto& item : items) { + // Track the current linebox. + if (const NGPhysicalLineBoxFragment* current_linebox = + item->LineBoxFragment()) { + linebox = current_linebox; + continue; + } + + // We only care about inlines which have generated a box fragment. + const NGPhysicalBoxFragment* box = item->BoxFragment(); + if (!box) + continue; + + // The key for the inline is the continuation root if it exists. + const LayoutObject* key = box->GetLayoutObject(); + if (key->IsLayoutInline() && key->GetNode()) + key = key->ContinuationRoot(); + + // See if we need the containing block information for this inline. + auto it = inline_containing_block_map->find(key); + if (it == inline_containing_block_map->end()) + continue; + + base::Optional<NGBoxFragmentBuilder::InlineContainingBlockGeometry>& + containing_block_geometry = it->value; + LineBoxPair& containing_lineboxes = + containing_linebox_map->insert(key, LineBoxPair{nullptr, nullptr}) + .stored_value->value; + DCHECK(containing_block_geometry.has_value() || + !containing_lineboxes.first); + + PhysicalRect fragment_rect = item->RectInContainerBlock(); + fragment_rect.offset += box_offset; + if (containing_lineboxes.first == linebox) { + // Unite the start rect with the fragment's rect. + containing_block_geometry->start_fragment_union_rect.Unite(fragment_rect); + } else if (!containing_lineboxes.first) { + DCHECK(!containing_lineboxes.second); + // This is the first linebox we've encountered, initialize the containing + // block geometry. + containing_lineboxes.first = linebox; + containing_lineboxes.second = linebox; + containing_block_geometry = + NGBoxFragmentBuilder::InlineContainingBlockGeometry{fragment_rect, + fragment_rect}; + } + + if (containing_lineboxes.second == linebox) { + // Unite the end rect with the fragment's rect. + containing_block_geometry->end_fragment_union_rect.Unite(fragment_rect); + } else if (!linebox->IsEmptyLineBox()) { + // We've found a new "end" linebox, update the containing block geometry. + containing_lineboxes.second = linebox; + containing_block_geometry->end_fragment_union_rect = fragment_rect; + } + } +} + } // namespace void NGBoxFragmentBuilder::AddBreakBeforeChild( @@ -121,8 +185,6 @@ void NGBoxFragmentBuilder::AddResult(const NGLayoutResult& child_layout_result, items_builder_->AddLine(*line, offset); // TODO(kojii): We probably don't need to AddChild this line, but there // maybe OOF objects. Investigate how to handle them. - } else { - DCHECK(fragment.IsFloating()); } } AddChild(fragment, offset, inline_container); @@ -156,6 +218,8 @@ NGPhysicalFragment::NGBoxType NGBoxFragmentBuilder::BoxType() const { return NGPhysicalFragment::NGBoxType::kFloating; if (layout_object_->IsOutOfFlowPositioned()) return NGPhysicalFragment::NGBoxType::kOutOfFlowPositioned; + if (layout_object_->IsRenderedLegend()) + return NGPhysicalFragment::NGBoxType::kRenderedLegend; if (layout_object_->IsInline()) { // Check |IsAtomicInlineLevel()| after |IsInline()| because |LayoutReplaced| // sets |IsAtomicInlineLevel()| even when it's block-level. crbug.com/567964 @@ -171,15 +235,6 @@ NGPhysicalFragment::NGBoxType NGBoxFragmentBuilder::BoxType() const { return NGPhysicalFragment::NGBoxType::kNormalBox; } -void NGBoxFragmentBuilder::AddBaseline(NGBaselineRequest request, - LayoutUnit offset) { -#if DCHECK_IS_ON() - for (const auto& baseline : baselines_) - DCHECK(baseline.request != request); -#endif - baselines_.emplace_back(request, offset); -} - EBreakBetween NGBoxFragmentBuilder::JoinedBreakBetweenValue( EBreakBetween break_before) const { return JoinFragmentainerBreakValues(previous_break_after_, break_before); @@ -226,8 +281,8 @@ scoped_refptr<const NGLayoutResult> NGBoxFragmentBuilder::ToBoxFragment( } if (did_break_) { break_token_ = NGBlockBreakToken::Create( - node_, consumed_block_size_, child_break_tokens_, break_appeal_, - has_seen_all_children_); + node_, consumed_block_size_, sequence_number_, child_break_tokens_, + break_appeal_, has_seen_all_children_); } } @@ -240,18 +295,54 @@ scoped_refptr<const NGLayoutResult> NGBoxFragmentBuilder::ToBoxFragment( NGPhysicalBoxFragment::Create(this, block_or_line_writing_mode); fragment->CheckType(); - return base::AdoptRef(new NGLayoutResult(std::move(fragment), this)); + return base::AdoptRef( + new NGLayoutResult(NGLayoutResult::NGBoxFragmentBuilderPassKey(), + std::move(fragment), this)); } scoped_refptr<const NGLayoutResult> NGBoxFragmentBuilder::Abort( NGLayoutResult::EStatus status) { - return base::AdoptRef(new NGLayoutResult(status, this)); + return base::AdoptRef(new NGLayoutResult( + NGLayoutResult::NGBoxFragmentBuilderPassKey(), status, this)); +} + +LogicalOffset NGBoxFragmentBuilder::GetChildOffset( + const LayoutObject* object) const { + DCHECK(object); + + if (const NGFragmentItemsBuilder* items_builder = items_builder_) { + if (auto offset = items_builder->LogicalOffsetFor(*object)) + return *offset; + NOTREACHED(); + return LogicalOffset(); + } + + for (const auto& child : children_) { + if (child.fragment->GetLayoutObject() == object) + return child.offset; + + // TODO(layout-dev): ikilpatrick thinks we may need to traverse + // further than the initial line-box children for a nested inline + // container. We could not come up with a testcase, it would be + // something with split inlines, and nested oof/fixed descendants maybe. + if (child.fragment->IsLineBox()) { + const auto& line_box_fragment = + To<NGPhysicalLineBoxFragment>(*child.fragment); + for (const auto& line_box_child : line_box_fragment.Children()) { + if (line_box_child->GetLayoutObject() == object) { + return child.offset + line_box_child.Offset().ConvertToLogical( + GetWritingMode(), Direction(), + line_box_fragment.Size(), + line_box_child->Size()); + } + } + } + } + NOTREACHED(); + return LogicalOffset(); } -// Computes the geometry required for any inline containing blocks. -// |inline_containing_block_map| is a map whose keys specify which inline -// containing block geometry is required. -void NGBoxFragmentBuilder::ComputeInlineContainerFragments( +void NGBoxFragmentBuilder::ComputeInlineContainerGeometryFromFragmentTree( InlineContainingBlockMap* inline_containing_block_map) { if (inline_containing_block_map->IsEmpty()) return; @@ -307,6 +398,59 @@ void NGBoxFragmentBuilder::ComputeInlineContainerFragments( } } +void NGBoxFragmentBuilder::ComputeInlineContainerGeometry( + InlineContainingBlockMap* inline_containing_block_map) { + if (inline_containing_block_map->IsEmpty()) + return; + + // This function requires that we have the final size of the fragment set + // upon the builder. + DCHECK_GE(InlineSize(), LayoutUnit()); + DCHECK_GE(BlockSize(), LayoutUnit()); + +#if DCHECK_IS_ON() + // Make sure all entries are a continuation root. + for (const auto& entry : *inline_containing_block_map) + DCHECK_EQ(entry.key, entry.key->ContinuationRoot()); +#endif + + HashMap<const LayoutObject*, LineBoxPair> containing_linebox_map; + + if (items_builder_) { + // To access the items correctly we need to convert them to the physical + // coordinate space. + GatherInlineContainerFragmentsFromItems( + items_builder_->Items(GetWritingMode(), Direction(), + ToPhysicalSize(Size(), GetWritingMode())), + PhysicalOffset(), inline_containing_block_map, &containing_linebox_map); + return; + } + + // If we have children which are anonymous block, we might contain split + // inlines, this can occur in the following example: + // <div> + // Some text <span style="position: relative;">text + // <div>block</div> + // text </span> text. + // </div> + for (const auto& child : children_) { + if (!child.fragment->IsAnonymousBlock()) + continue; + + const auto& child_fragment = To<NGPhysicalBoxFragment>(*child.fragment); + const auto* items = child_fragment.Items(); + if (!items) + continue; + + const PhysicalOffset child_offset = child.offset.ConvertToPhysical( + GetWritingMode(), Direction(), ToPhysicalSize(Size(), GetWritingMode()), + child_fragment.Size()); + GatherInlineContainerFragmentsFromItems(items->Items(), child_offset, + inline_containing_block_map, + &containing_linebox_map); + } +} + #if DCHECK_IS_ON() void NGBoxFragmentBuilder::CheckNoBlockFragmentation() const { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h index 0544b7aa751..288530aeceb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h @@ -9,7 +9,6 @@ #include "third_party/blink/renderer/core/layout/ng/geometry/ng_border_edges.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h" @@ -38,6 +37,7 @@ class CORE_EXPORT NGBoxFragmentBuilder final writing_mode, direction), box_type_(NGPhysicalFragment::NGBoxType::kNormalBox), + is_inline_formatting_context_(node.IsInline()), did_break_(false) {} // Build a fragment for LayoutObject without NGLayoutInputNode. LayoutInline @@ -52,6 +52,7 @@ class CORE_EXPORT NGBoxFragmentBuilder final writing_mode, direction), box_type_(NGPhysicalFragment::NGBoxType::kNormalBox), + is_inline_formatting_context_(true), did_break_(false) { layout_object_ = layout_object; } @@ -68,9 +69,8 @@ class CORE_EXPORT NGBoxFragmentBuilder final return *initial_fragment_geometry_; } - void SetUnconstrainedIntrinsicBlockSize( - LayoutUnit unconstrained_intrinsic_block_size) { - unconstrained_intrinsic_block_size_ = unconstrained_intrinsic_block_size; + void SetOverflowBlockSize(LayoutUnit overflow_block_size) { + overflow_block_size_ = overflow_block_size; } void SetIntrinsicBlockSize(LayoutUnit intrinsic_block_size) { intrinsic_block_size_ = intrinsic_block_size; @@ -121,6 +121,10 @@ class CORE_EXPORT NGBoxFragmentBuilder final // building now. void SetConsumedBlockSize(LayoutUnit size) { consumed_block_size_ = size; } + void SetSequenceNumber(unsigned sequence_number) { + sequence_number_ = sequence_number; + } + // Specify that we broke. // // This will result in a fragment which has an unfinished break token. @@ -197,6 +201,10 @@ class CORE_EXPORT NGBoxFragmentBuilder final void SetColumnSpanner(NGBlockNode spanner) { column_spanner_ = spanner; } bool FoundColumnSpanner() const { return !!column_spanner_; } + void SetLinesUntilClamp(const base::Optional<int>& value) { + lines_until_clamp_ = value; + } + void SetEarlyBreak(scoped_refptr<const NGEarlyBreak> breakpoint, NGBreakAppeal appeal) { early_break_ = breakpoint; @@ -240,6 +248,12 @@ class CORE_EXPORT NGBoxFragmentBuilder final void SetIsFieldsetContainer() { is_fieldset_container_ = true; } void SetIsLegacyLayoutRoot() { is_legacy_layout_root_ = true; } + void SetIsInlineFormattingContext(bool is_inline_formatting_context) { + is_inline_formatting_context_ = is_inline_formatting_context; + } + + void SetIsMathMLFraction() { is_math_fraction_ = true; } + bool DidBreak() const { return did_break_; } void SetBorderEdges(NGBorderEdges border_edges) { @@ -254,15 +268,16 @@ class CORE_EXPORT NGBoxFragmentBuilder final custom_layout_data_ = std::move(custom_layout_data); } - // Layout algorithms should call this function for each baseline request in - // the constraint space. - // - // If a request should use a synthesized baseline from the box rectangle, - // algorithms can omit the call. - // - // This function should be called at most once for a given algorithm/baseline - // type pair. - void AddBaseline(NGBaselineRequest, LayoutUnit); + // Sets the alignment baseline for this fragment. + void SetBaseline(LayoutUnit baseline) { baseline_ = baseline; } + base::Optional<LayoutUnit> Baseline() const { return baseline_; } + + // Sets the last baseline for this fragment. + void SetLastBaseline(LayoutUnit baseline) { + DCHECK_EQ(space_->BaselineAlgorithmType(), + NGBaselineAlgorithmType::kInlineBlock); + last_baseline_ = baseline; + } // The |NGFragmentItemsBuilder| for the inline formatting context of this box. NGFragmentItemsBuilder* ItemsBuilder() { return items_builder_; } @@ -270,8 +285,12 @@ class CORE_EXPORT NGBoxFragmentBuilder final items_builder_ = builder; } - // Inline containing block geometry is defined by two rectangles defined - // by fragments generated by LayoutInline. + // Returns offset for given child. DCHECK if child not found. + // Warning: Do not call unless necessary. + LogicalOffset GetChildOffset(const LayoutObject* child) const; + + // Inline containing block geometry is defined by two rectangles, generated + // by fragments of the LayoutInline. struct InlineContainingBlockGeometry { DISALLOW_NEW(); // Union of fragments generated on the first line. @@ -283,7 +302,13 @@ class CORE_EXPORT NGBoxFragmentBuilder final using InlineContainingBlockMap = HashMap<const LayoutObject*, base::Optional<InlineContainingBlockGeometry>>; - void ComputeInlineContainerFragments( + + // Computes the geometry required for any inline containing blocks. + // |inline_containing_block_map| is a map whose keys specify which inline + // containing block geometry is required. + void ComputeInlineContainerGeometryFromFragmentTree( + InlineContainingBlockMap* inline_containing_block_map); + void ComputeInlineContainerGeometry( InlineContainingBlockMap* inline_containing_block_map); #if DCHECK_IS_ON() @@ -304,7 +329,7 @@ class CORE_EXPORT NGBoxFragmentBuilder final scoped_refptr<const NGLayoutResult> ToBoxFragment(WritingMode); const NGFragmentGeometry* initial_fragment_geometry_ = nullptr; - LayoutUnit unconstrained_intrinsic_block_size_ = kIndefiniteSize; + LayoutUnit overflow_block_size_ = kIndefiniteSize; LayoutUnit intrinsic_block_size_; NGFragmentItemsBuilder* items_builder_ = nullptr; @@ -314,12 +339,15 @@ class CORE_EXPORT NGBoxFragmentBuilder final NGPhysicalFragment::NGBoxType box_type_; bool is_fieldset_container_ = false; bool is_initial_block_size_indefinite_ = false; + bool is_inline_formatting_context_; bool did_break_; bool has_forced_break_ = false; bool is_new_fc_ = false; bool subtree_modified_margin_strut_ = false; bool has_seen_all_children_ = false; + bool is_math_fraction_ = false; LayoutUnit consumed_block_size_; + unsigned sequence_number_ = 0; LayoutUnit minimal_space_shortage_ = LayoutUnit::Max(); LayoutUnit tallest_unbreakable_block_size_ = LayoutUnit::Min(); @@ -331,11 +359,12 @@ class CORE_EXPORT NGBoxFragmentBuilder final // The break-after value of the previous in-flow sibling. EBreakBetween previous_break_after_ = EBreakBetween::kAuto; - NGBaselineList baselines_; - + base::Optional<LayoutUnit> baseline_; + base::Optional<LayoutUnit> last_baseline_; NGBorderEdges border_edges_; scoped_refptr<SerializedScriptValue> custom_layout_data_; + base::Optional<int> lines_until_clamp_; friend class NGPhysicalBoxFragment; friend class NGLayoutResult; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc index e61e4d42998..f1034c33a1c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc @@ -8,7 +8,6 @@ #include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" @@ -138,10 +137,16 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { intrinsic_block_size_ = border_scrollbar_padding_.block_start; - if (!LayoutChildren()) { + NGBreakStatus break_status = LayoutChildren(); + if (break_status == NGBreakStatus::kNeedsEarlierBreak) { // We need to discard this layout and do it again. We found an earlier break // point that's more appealing than the one we ran out of space at. return RelayoutAndBreakEarlier(); + } else if (break_status == NGBreakStatus::kBrokeBefore) { + // If we want to break before, make sure that we're actually at the start. + DCHECK(!BreakToken()); + + return container_builder_.Abort(NGLayoutResult::kOutOfFragmentainerSpace); } // Figure out how much space we've already been able to process in previous @@ -166,10 +171,9 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { if (is_constrained_by_outer_fragmentation_context_) { // In addition to establishing one, we're nested inside another // fragmentation context. - FinishFragmentation(ConstraintSpace(), block_size, intrinsic_block_size_, - previously_consumed_block_size, - FragmentainerSpaceAtBfcStart(ConstraintSpace()), - &container_builder_); + FinishFragmentation( + ConstraintSpace(), BreakToken(), block_size, intrinsic_block_size_, + FragmentainerSpaceAtBfcStart(ConstraintSpace()), &container_builder_); } else { container_builder_.SetBlockSize(block_size); container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); @@ -184,19 +188,17 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() { return container_builder_.ToBoxFragment(); } -base::Optional<MinMaxSize> NGColumnLayoutAlgorithm::ComputeMinMaxSize( - const MinMaxSizeInput& input) const { +base::Optional<MinMaxSizes> NGColumnLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { // First calculate the min/max sizes of columns. NGConstraintSpace space = CreateConstraintSpaceForMinMax(); NGFragmentGeometry fragment_geometry = CalculateInitialMinMaxFragmentGeometry(space, Node()); NGBlockLayoutAlgorithm algorithm({Node(), fragment_geometry, space}); - MinMaxSizeInput child_input(input); - child_input.size_type = NGMinMaxSizeType::kContentBoxSize; - base::Optional<MinMaxSize> min_max_sizes = - algorithm.ComputeMinMaxSize(child_input); + base::Optional<MinMaxSizes> min_max_sizes = + algorithm.ComputeMinMaxSizes(input); DCHECK(min_max_sizes.has_value()); - MinMaxSize sizes = *min_max_sizes; + MinMaxSizes sizes = *min_max_sizes; // If column-width is non-auto, pick the larger of that and intrinsic column // width. @@ -217,14 +219,11 @@ base::Optional<MinMaxSize> NGColumnLayoutAlgorithm::ComputeMinMaxSize( // TODO(mstensho): Need to include spanners. - if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) { - sizes += border_scrollbar_padding_.InlineSum(); - } - + sizes += border_scrollbar_padding_.InlineSum(); return sizes; } -bool NGColumnLayoutAlgorithm::LayoutChildren() { +NGBreakStatus NGColumnLayoutAlgorithm::LayoutChildren() { NGMarginStrut margin_strut; // First extract incoming child break tokens. @@ -281,7 +280,7 @@ bool NGColumnLayoutAlgorithm::LayoutChildren() { PushSpannerBreakTokens(std::move(spanner_break_token), std::move(next_column_token), &container_builder_); - return true; + return NGBreakStatus::kContinue; } } else { // Breaking before the first element in the fragmentainer isn't allowed, @@ -291,7 +290,7 @@ bool NGColumnLayoutAlgorithm::LayoutChildren() { } if (BreakToken() && BreakToken()->HasSeenAllChildren() && !next_column_token) - return true; + return NGBreakStatus::kContinue; // Entering the child main loop. Here we'll alternate between laying out // column content and column spanners, until we're either done, or until @@ -301,6 +300,26 @@ bool NGColumnLayoutAlgorithm::LayoutChildren() { do { scoped_refptr<const NGLayoutResult> result = LayoutRow(next_column_token.get(), &margin_strut); + + if (!result) { + // Not enough outer fragmentainer space to produce any columns at all. + container_builder_.SetDidBreak(); + if (intrinsic_block_size_) { + // We have preceding initial border/padding, or a column spanner + // (possibly preceded by other spanners or even column content). So we + // need to break inside the multicol container. Stop walking the + // children, but "continue" layout, so that we produce a fragment. Note + // that we normally don't want to break right after initial + // border/padding, but will do so as a last resort. It's up to our + // containing block to decide what's best. + FinishAfterBreakBeforeRow(std::move(next_column_token)); + return NGBreakStatus::kContinue; + } + // Otherwise we have nothing here, and need to break before the multicol + // container. No fragment will be produced. + return NGBreakStatus::kBrokeBefore; + } + next_column_token = To<NGBlockBreakToken>(result->PhysicalFragment().BreakToken()); @@ -320,7 +339,7 @@ bool NGColumnLayoutAlgorithm::LayoutChildren() { container_builder_.AddBreakBeforeChild( spanner_node, kBreakAppealPerfect, /* is_forced_break */ false); FinishAfterBreakBeforeSpanner(std::move(next_column_token)); - return true; + return NGBreakStatus::kContinue; } } @@ -328,18 +347,18 @@ bool NGColumnLayoutAlgorithm::LayoutChildren() { NGBreakStatus break_status = LayoutSpanner( spanner_node, nullptr, &margin_strut, &spanner_break_token); if (break_status == NGBreakStatus::kNeedsEarlierBreak) { - return false; + return break_status; } else if (break_status == NGBreakStatus::kBrokeBefore) { DCHECK(ConstraintSpace().HasBlockFragmentation()); FinishAfterBreakBeforeSpanner(std::move(next_column_token)); - return true; + return NGBreakStatus::kContinue; } else if (spanner_break_token) { DCHECK_EQ(break_status, NGBreakStatus::kContinue); // We broke inside the spanner. This may happen if we're nested inside // another fragmentation context. PushSpannerBreakTokens(std::move(spanner_break_token), std::move(next_column_token), &container_builder_); - return true; + return NGBreakStatus::kContinue; } } while (next_column_token); @@ -362,7 +381,7 @@ bool NGColumnLayoutAlgorithm::LayoutChildren() { intrinsic_block_size_ += margin_strut.Sum(); } - return true; + return NGBreakStatus::kContinue; } scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow( @@ -401,13 +420,23 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow( LayoutUnit column_block_offset = intrinsic_block_size_ + margin_strut->Sum(); bool needs_more_fragments_in_outer = false; + bool zero_outer_space_left = false; if (is_constrained_by_outer_fragmentation_context_) { LayoutUnit available_outer_space = FragmentainerSpaceAtBfcStart(ConstraintSpace()) - column_block_offset; - // TODO(mstensho): This should never be negative, or even zero. Turn into a - // DCHECK when the underlying problem is fixed. - available_outer_space = available_outer_space.ClampNegativeToZero(); + if (available_outer_space <= LayoutUnit()) { + if (available_outer_space < LayoutUnit()) { + // We're past the end of the outer fragmentainer (typically due to a + // margin). Nothing will fit here, not even zero-size content. + return nullptr; + } + + // We are out of space, but we're exactly at the end of the outer + // fragmentainer. If none of our contents take up space, we're going to + // fit, otherwise not. Lay out and find out. + zero_outer_space_left = true; + } // Check if we can fit everything (that's remaining), block-wise, within the // current outer fragmentainer. If we can't, we need to adjust the block @@ -498,6 +527,11 @@ scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow( if (ConstraintSpace().HasBlockFragmentation() && column_break_token && actual_column_count >= used_column_count_ && needs_more_fragments_in_outer) { + // We cannot keep any of this if we have zero space left. Then we need + // to resume in the next outer fragmentainer. + if (zero_outer_space_left) + return nullptr; + container_builder_.SetDidBreak(); container_builder_.SetBreakAppeal(kBreakAppealPerfect); break; @@ -626,7 +660,8 @@ NGBreakStatus NGColumnLayoutAlgorithm::LayoutSpanner( margin_strut->Append(margins.block_start, /* is_quirky */ false); LayoutUnit block_offset = intrinsic_block_size_ + margin_strut->Sum(); - auto spanner_space = CreateConstraintSpaceForSpanner(block_offset); + auto spanner_space = + CreateConstraintSpaceForSpanner(spanner_node, block_offset); const NGEarlyBreak* early_break_in_child = nullptr; if (early_break_ && early_break_->Type() == NGEarlyBreak::kBlock && @@ -764,6 +799,10 @@ LayoutUnit NGColumnLayoutAlgorithm::CalculateBalancedColumnBlockSize( NGBlockLayoutAlgorithm balancing_algorithm( {Node(), fragment_geometry, space, break_token.get()}); scoped_refptr<const NGLayoutResult> result = balancing_algorithm.Layout(); + + // This algorithm should never abort. + DCHECK_EQ(result->Status(), NGLayoutResult::kSuccess); + const NGPhysicalBoxFragment& fragment = To<NGPhysicalBoxFragment>(result->PhysicalFragment()); LayoutUnit column_block_size = CalculateColumnContentBlockSize( @@ -841,7 +880,7 @@ LayoutUnit NGColumnLayoutAlgorithm::ConstrainColumnBlockSize( const ComputedStyle& style = Style(); LayoutUnit max = ResolveMaxBlockLength( - ConstraintSpace(), style, border_padding_, style.LogicalMaxHeight(), size, + ConstraintSpace(), style, border_padding_, style.LogicalMaxHeight(), LengthResolvePhase::kLayout); LayoutUnit extent = ResolveMainBlockLength( ConstraintSpace(), style, border_padding_, style.LogicalHeight(), size, @@ -856,6 +895,20 @@ LayoutUnit NGColumnLayoutAlgorithm::ConstrainColumnBlockSize( return size - extra; } +void NGColumnLayoutAlgorithm::FinishAfterBreakBeforeRow( + scoped_refptr<const NGBlockBreakToken> next_column_token) { + // We broke before a row for columns. We're done here. Take up the remaining + // space in the outer fragmentation context. + intrinsic_block_size_ = FragmentainerSpaceAtBfcStart(ConstraintSpace()); + + // If we were about to resume column layout after a spanner, add a break token + // for this, so that we resume there in the next outer fragmentainer. If + // there's no such break token, it means that we're at the start of the + // multicol container. + if (next_column_token) + container_builder_.AddBreakToken(std::move(next_column_token)); +} + void NGColumnLayoutAlgorithm::FinishAfterBreakBeforeSpanner( scoped_refptr<const NGBlockBreakToken> next_column_token) { // We broke before the spanner. We're done here. Take up the remaining space @@ -898,9 +951,6 @@ NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForColumns( space_builder.SetAvailableSize(column_size); space_builder.SetPercentageResolutionSize(column_size); - if (NGBaseline::ShouldPropagateBaselines(Node())) - space_builder.AddBaselineRequests(ConstraintSpace().BaselineRequests()); - // To ensure progression, we need something larger than 0 here. The spec // actually says that fragmentainers have to accept at least 1px of content. // See https://www.w3.org/TR/css-break-3/#breaking-rules @@ -939,6 +989,7 @@ NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForBalancing( } NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForSpanner( + const NGBlockNode& spanner, LayoutUnit block_offset) const { NGConstraintSpaceBuilder space_builder( ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true); @@ -946,7 +997,7 @@ NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForSpanner( space_builder.SetPercentageResolutionSize(content_box_size_); if (ConstraintSpace().HasBlockFragmentation()) { - SetupFragmentation(ConstraintSpace(), block_offset, &space_builder, + SetupFragmentation(ConstraintSpace(), spanner, block_offset, &space_builder, /* is_new_fc */ true); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h index b9108019680..c516f1bafaa 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h @@ -26,17 +26,21 @@ class CORE_EXPORT NGColumnLayoutAlgorithm scoped_refptr<const NGLayoutResult> Layout() override; - base::Optional<MinMaxSize> ComputeMinMaxSize( - const MinMaxSizeInput&) const override; + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const override; private: - // Lay out as many children as we can. If false is returned, it means that we - // ran out of space at an unappealing location, and need to relayout and break - // earlier (because we have a better breakpoint there). - bool LayoutChildren(); + // Lay out as many children as we can. If |kNeedsEarlierBreak| is returned, it + // means that we ran out of space at an unappealing location, and need to + // relayout and break earlier (because we have a better breakpoint there). If + // |kBrokeBefore| is returned, it means that we need to break before the + // multicol container, and retry in the next fragmentainer. + NGBreakStatus LayoutChildren(); // Lay out one row of columns. The layout result returned is for the last - // column that was laid out. The rows themselves don't create fragments. + // column that was laid out. The rows themselves don't create fragments. If + // we're in a nested fragmentation context and completely out of outer + // fragmentainer space, nullptr will be returned. scoped_refptr<const NGLayoutResult> LayoutRow( const NGBlockBreakToken* next_column_token, NGMarginStrut*); @@ -66,6 +70,10 @@ class CORE_EXPORT NGColumnLayoutAlgorithm return intrinsic_block_size_ - border_scrollbar_padding_.block_start; } + // Finalize layout after breaking before column contents. + void FinishAfterBreakBeforeRow( + scoped_refptr<const NGBlockBreakToken> next_column_token); + // Finalize layout after breaking before a spanner. void FinishAfterBreakBeforeSpanner( scoped_refptr<const NGBlockBreakToken> next_column_token); @@ -83,6 +91,7 @@ class CORE_EXPORT NGColumnLayoutAlgorithm NGConstraintSpace CreateConstraintSpaceForBalancing( const LogicalSize& column_size) const; NGConstraintSpace CreateConstraintSpaceForSpanner( + const NGBlockNode& spanner, LayoutUnit block_offset) const; NGConstraintSpace CreateConstraintSpaceForMinMax() const; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc index 276c9823f0b..b2d3a0825ec 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc @@ -787,7 +787,6 @@ TEST_F(NGColumnLayoutAlgorithmTest, FloatWithLastResortBreak) { offset:110,0 size:100x100 offset:0,0 size:88x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1434,14 +1433,10 @@ TEST_F(NGColumnLayoutAlgorithmTest, LinesInMulticolExtraSpace) { offset:0,0 size:320x50 offset:0,0 size:100x50 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x50 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1476,14 +1471,10 @@ TEST_F(NGColumnLayoutAlgorithmTest, LinesInMulticolExactFit) { offset:0,0 size:320x40 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1521,15 +1512,11 @@ TEST_F(NGColumnLayoutAlgorithmTest, LinesInMulticolChildExtraSpace) { offset:0,0 size:100x50 offset:0,0 size:77x50 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x50 offset:0,0 size:77x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1567,15 +1554,11 @@ TEST_F(NGColumnLayoutAlgorithmTest, LinesInMulticolChildExactFit) { offset:0,0 size:100x40 offset:0,0 size:77x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x40 offset:0,0 size:77x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1615,13 +1598,10 @@ TEST_F(NGColumnLayoutAlgorithmTest, LinesInMulticolChildNoSpaceForFirst) { offset:110,0 size:100x50 offset:0,0 size:77x50 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x50 offset:0,0 size:77x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1662,13 +1642,10 @@ TEST_F(NGColumnLayoutAlgorithmTest, offset:110,0 size:100x50 offset:0,0 size:77x50 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x50 offset:0,0 size:77x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1713,7 +1690,6 @@ TEST_F(NGColumnLayoutAlgorithmTest, LineAtColumnBoundaryInFirstBlock) { offset:110,0 size:100x50 offset:0,0 size:66x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1752,20 +1728,15 @@ TEST_F(NGColumnLayoutAlgorithmTest, DISABLED_LinesAndFloatsMulticol) { offset:0,0 size:320x70 offset:0,0 size:100x70 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:10x50 offset:10,20 size:0x20 - offset:0,9 size:0x1 offset:10,40 size:11x30 offset:21,40 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x70 offset:0,0 size:10x70 offset:10,0 size:11x70 offset:21,0 size:0x20 - offset:0,9 size:0x1 offset:21,20 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x70 offset:0,0 size:11x20 )DUMP"; @@ -1805,18 +1776,13 @@ TEST_F(NGColumnLayoutAlgorithmTest, DISABLED_FloatBelowLastLineInColumn) { offset:0,0 size:320x70 offset:0,0 size:100x70 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:11x10 offset:110,0 size:100x70 offset:0,0 size:11x70 offset:11,0 size:0x20 - offset:0,9 size:0x1 offset:11,20 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x70 offset:0,0 size:11x40 )DUMP"; @@ -1858,11 +1824,8 @@ TEST_F(NGColumnLayoutAlgorithmTest, Orphans) { offset:110,0 size:100x90 offset:0,0 size:77x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1899,18 +1862,12 @@ TEST_F(NGColumnLayoutAlgorithmTest, OrphansUnsatisfiable) { offset:0,0 size:320x90 offset:0,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -1948,20 +1905,13 @@ TEST_F(NGColumnLayoutAlgorithmTest, Widows) { offset:0,0 size:320x110 offset:0,0 size:100x110 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x110 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2006,37 +1956,23 @@ TEST_F(NGColumnLayoutAlgorithmTest, WidowsUnsatisfiable) { offset:0,0 size:320x90 offset:0,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:330,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:440,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2071,14 +2007,10 @@ TEST_F(NGColumnLayoutAlgorithmTest, OrphansAndUnsatisfiableWidows) { offset:0,0 size:320x70 offset:0,0 size:100x70 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x70 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2113,14 +2045,10 @@ TEST_F(NGColumnLayoutAlgorithmTest, UnsatisfiableOrphansAndWidows) { offset:0,0 size:320x70 offset:0,0 size:100x70 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x70 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2160,17 +2088,12 @@ TEST_F(NGColumnLayoutAlgorithmTest, WidowsAndAbspos) { offset:0,0 size:100x70 offset:0,0 size:100x70 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x70 offset:0,0 size:100x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:33x33 )DUMP"; EXPECT_EQ(expectation, dump); @@ -2214,15 +2137,11 @@ TEST_F(NGColumnLayoutAlgorithmTest, BreakBetweenLinesNotBefore) { offset:0,0 size:44x60 offset:0,60 size:55x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:55x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2262,11 +2181,9 @@ TEST_F(NGColumnLayoutAlgorithmTest, BreakBetweenLinesNotBefore2) { offset:0,0 size:44x80 offset:0,80 size:55x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:55x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2306,11 +2223,9 @@ TEST_F(NGColumnLayoutAlgorithmTest, BreakBetweenLinesNotBefore3) { offset:0,0 size:44x80 offset:0,80 size:55x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:55x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2351,10 +2266,8 @@ TEST_F(NGColumnLayoutAlgorithmTest, DISABLED_FloatInBlockMovedByOrphans) { offset:110,0 size:100x70 offset:0,0 size:77x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:10x10 offset:10,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2392,17 +2305,12 @@ TEST_F(NGColumnLayoutAlgorithmTest, DISABLED_FloatMovedWithWidows) { offset:0,0 size:320x90 offset:0,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x90 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:10x10 offset:10,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -2809,32 +2717,32 @@ TEST_F(NGColumnLayoutAlgorithmTest, MinMax) { NGFragmentGeometry fragment_geometry = CalculateInitialFragmentGeometry(space, node); NGColumnLayoutAlgorithm algorithm({node, fragment_geometry, space}); - base::Optional<MinMaxSize> size; - MinMaxSizeInput zero_input( + base::Optional<MinMaxSizes> sizes; + MinMaxSizesInput zero_input( /* percentage_resolution_block_size */ (LayoutUnit())); // Both column-count and column-width set. style->SetColumnCount(3); style->SetColumnWidth(80); - size = algorithm.ComputeMinMaxSize(zero_input); - ASSERT_TRUE(size.has_value()); - EXPECT_EQ(LayoutUnit(260), size->min_size); - EXPECT_EQ(LayoutUnit(320), size->max_size); + sizes = algorithm.ComputeMinMaxSizes(zero_input); + ASSERT_TRUE(sizes.has_value()); + EXPECT_EQ(LayoutUnit(260), sizes->min_size); + EXPECT_EQ(LayoutUnit(320), sizes->max_size); // Only column-count set. style->SetHasAutoColumnWidth(); - size = algorithm.ComputeMinMaxSize(zero_input); - ASSERT_TRUE(size.has_value()); - EXPECT_EQ(LayoutUnit(170), size->min_size); - EXPECT_EQ(LayoutUnit(320), size->max_size); + sizes = algorithm.ComputeMinMaxSizes(zero_input); + ASSERT_TRUE(sizes.has_value()); + EXPECT_EQ(LayoutUnit(170), sizes->min_size); + EXPECT_EQ(LayoutUnit(320), sizes->max_size); // Only column-width set. style->SetColumnWidth(80); style->SetHasAutoColumnCount(); - size = algorithm.ComputeMinMaxSize(zero_input); - ASSERT_TRUE(size.has_value()); - EXPECT_EQ(LayoutUnit(80), size->min_size); - EXPECT_EQ(LayoutUnit(100), size->max_size); + sizes = algorithm.ComputeMinMaxSizes(zero_input); + ASSERT_TRUE(sizes.has_value()); + EXPECT_EQ(LayoutUnit(80), sizes->min_size); + EXPECT_EQ(LayoutUnit(100), sizes->max_size); } TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancing) { @@ -3196,7 +3104,6 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingSingleLine) { offset:0,0 size:320x20 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3228,7 +3135,6 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingSingleLineInNested) { offset:0,0 size:100x20 offset:0,0 size:45x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3262,7 +3168,6 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingSingleLineInNestedSpanner) { offset:0,0 size:100x20 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3327,17 +3232,12 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingLines) { offset:0,0 size:320x40 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3375,21 +3275,15 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingLinesOrphans) { offset:0,0 size:100x60 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x60 offset:0,0 size:100x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x60 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3427,21 +3321,15 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingLinesForcedBreak) { offset:0,0 size:100x60 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x60 offset:0,0 size:100x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x60 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3479,34 +3367,22 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingLinesForcedBreak2) { offset:0,0 size:100x100 offset:0,0 size:100x100 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:0,80 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x100 offset:0,0 size:99x0 offset:0,0 size:100x100 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:0,80 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3548,36 +3424,24 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingLinesForcedBreak3) { offset:0,0 size:66x100 offset:0,0 size:66x100 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:0,80 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:66x100 offset:0,0 size:66x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x100 offset:0,0 size:66x100 offset:0,0 size:99x0 offset:0,0 size:66x100 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:0,60 size:0x20 - offset:0,9 size:0x1 offset:0,80 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3615,21 +3479,15 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingLinesAvoidBreakInside) { offset:0,0 size:100x60 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x60 offset:0,0 size:100x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x60 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3667,19 +3525,14 @@ TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingLinesAvoidBreakInside2) { offset:0,0 size:100x60 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x60 offset:0,0 size:100x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 offset:220,0 size:100x60 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -3978,7 +3831,6 @@ TEST_F(NGColumnLayoutAlgorithmTest, ClassCBreakPointBeforeLine) { offset:110,0 size:100x100 offset:0,0 size:55x20 offset:0,0 size:33x20 - offset:0,0 size:33x11 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -4313,6 +4165,168 @@ TEST_F(NGColumnLayoutAlgorithmTest, NestedUnbalancedInnerAutoHeight) { EXPECT_EQ(expectation, dump); } +TEST_F(NGColumnLayoutAlgorithmTest, NestedAtOuterBoundary) { + SetBodyInnerHTML(R"HTML( + <style> + .outer { columns:3; height:100px; width:320px; } + .inner { columns:2; height:50px; } + .outer, .inner { column-gap:10px; column-fill:auto; } + </style> + <div id="container"> + <div class="outer"> + <div style="width:11px; height:100px;"></div> + <div class="inner"> + <div style="width:22px; height:70px;"></div> + </div> + </div> + </div> + )HTML"); + + String dump = DumpFragmentTree(GetElementById("container")); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:320x100 + offset:0,0 size:100x100 + offset:0,0 size:11x100 + offset:110,0 size:100x100 + offset:0,0 size:100x50 + offset:0,0 size:45x50 + offset:0,0 size:22x50 + offset:55,0 size:45x50 + offset:0,0 size:22x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGColumnLayoutAlgorithmTest, NestedZeroHeightAtOuterBoundary) { + SetBodyInnerHTML(R"HTML( + <style> + .outer { columns:3; height:100px; width:320px; } + .inner { columns:2; } + .outer, .inner { column-gap:10px; column-fill:auto; } + </style> + <div id="container"> + <div class="outer"> + <div style="width:11px; height:100px;"></div> + <div class="inner"> + <div style="width:22px;"></div> + </div> + </div> + </div> + )HTML"); + + String dump = DumpFragmentTree(GetElementById("container")); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:320x100 + offset:0,0 size:100x100 + offset:0,0 size:11x100 + offset:0,100 size:100x0 + offset:0,0 size:45x1 + offset:0,0 size:22x0 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGColumnLayoutAlgorithmTest, NestedWithMarginAtOuterBoundary) { + SetBodyInnerHTML(R"HTML( + <style> + .outer { columns:3; height:100px; width:320px; } + .inner { columns:2; height:50px; margin-top:20px; } + .outer, .inner { column-gap:10px; column-fill:auto; } + </style> + <div id="container"> + <div class="outer"> + <div style="width:11px; height:90px;"></div> + <div class="inner"> + <div style="width:22px; height:70px;"></div> + </div> + </div> + </div> + )HTML"); + + String dump = DumpFragmentTree(GetElementById("container")); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:320x100 + offset:0,0 size:100x100 + offset:0,0 size:11x90 + offset:110,0 size:100x100 + offset:0,0 size:100x50 + offset:0,0 size:45x50 + offset:0,0 size:22x50 + offset:55,0 size:45x50 + offset:0,0 size:22x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGColumnLayoutAlgorithmTest, NestedWithTallBorder) { + SetBodyInnerHTML(R"HTML( + <style> + .outer { columns:3; height:100px; width:320px; } + .inner { columns:2; height:50px; border-top:100px solid; } + .outer, .inner { column-gap:10px; column-fill:auto; } + </style> + <div id="container"> + <div class="outer"> + <div class="inner"> + <div style="width:22px; height:70px;"></div> + </div> + </div> + </div> + )HTML"); + + String dump = DumpFragmentTree(GetElementById("container")); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:320x100 + offset:0,0 size:100x100 + offset:0,0 size:100x100 + offset:110,0 size:100x100 + offset:0,0 size:100x50 + offset:0,0 size:45x50 + offset:0,0 size:22x50 + offset:55,0 size:45x50 + offset:0,0 size:22x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGColumnLayoutAlgorithmTest, NestedWithTallSpanner) { + SetBodyInnerHTML(R"HTML( + <style> + .outer { columns:3; height:100px; width:320px; column-fill:auto; } + .inner { columns:2; } + .outer, .inner { column-gap:10px; } + </style> + <div id="container"> + <div class="outer"> + <div class="inner"> + <div style="column-span:all; width:22px; height:100px;"></div> + <div style="width:22px; height:70px;"></div> + </div> + </div> + </div> + )HTML"); + + String dump = DumpFragmentTree(GetElementById("container")); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:320x100 + offset:0,0 size:100x100 + offset:0,0 size:100x100 + offset:0,0 size:22x100 + offset:110,0 size:100x100 + offset:0,0 size:100x35 + offset:0,0 size:45x35 + offset:0,0 size:22x35 + offset:55,0 size:45x35 + offset:0,0 size:22x35 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + TEST_F(NGColumnLayoutAlgorithmTest, AbsposFitsInOneColumn) { SetBodyInnerHTML(R"HTML( <div id="container"> @@ -5523,14 +5537,11 @@ TEST_F(NGColumnLayoutAlgorithmTest, AvoidSoftBreakBetweenSpanners3) { offset:0,0 size:100x100 offset:0,0 size:11x100 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:100x80 offset:0,0 size:11x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:55x60 )DUMP"; EXPECT_EQ(expectation, dump); @@ -6084,15 +6095,11 @@ TEST_F(NGColumnLayoutAlgorithmTest, AvoidBreakBetweenHonorOrphansWidows) { offset:0,0 size:100x100 offset:0,0 size:100x100 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:100x30 )DUMP"; EXPECT_EQ(expectation, dump); @@ -6135,9 +6142,7 @@ TEST_F(NGColumnLayoutAlgorithmTest, AvoidBreakBetweenHonorOrphansWidows2) { offset:110,0 size:100x100 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:100x30 )DUMP"; EXPECT_EQ(expectation, dump); @@ -6186,22 +6191,15 @@ TEST_F(NGColumnLayoutAlgorithmTest, AvoidBreakBetweenHonorOrphansWidows3) { offset:0,0 size:100x100 offset:0,0 size:100x100 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:100x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:100x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:0x20 - offset:0,9 size:0x1 )DUMP"; EXPECT_EQ(expectation, dump); } @@ -6243,11 +6241,9 @@ TEST_F(NGColumnLayoutAlgorithmTest, AvoidBreakBetweenIgnoreOrphansWidows) { offset:0,0 size:100x40 offset:0,40 size:100x60 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:110,0 size:100x100 offset:0,0 size:100x20 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:100x30 )DUMP"; EXPECT_EQ(expectation, dump); @@ -6296,9 +6292,7 @@ TEST_F(NGColumnLayoutAlgorithmTest, AvoidBreakBetweenLinesInsideBreakAvoid) { offset:110,0 size:100x100 offset:0,0 size:35x40 offset:0,0 size:0x20 - offset:0,9 size:0x1 offset:0,20 size:0x20 - offset:0,9 size:0x1 offset:0,40 size:36x30 )DUMP"; EXPECT_EQ(expectation, dump); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc index c219e2f14f3..e916c433314 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc @@ -35,8 +35,7 @@ static_assert(sizeof(NGConstraintSpace) == sizeof(SameSizeAsNGConstraintSpace), } // namespace NGConstraintSpace NGConstraintSpace::CreateFromLayoutObject( - const LayoutBlock& block, - bool is_layout_root) { + const LayoutBlock& block) { // We should only ever create a constraint space from legacy layout if the // object is a new formatting context. DCHECK(block.CreatesNewFormattingContext()); @@ -77,28 +76,14 @@ NGConstraintSpace NGConstraintSpace::CreateFromLayoutObject( /* is_new_fc */ true, !parallel_containing_block); - auto* previous_result = block.GetCachedLayoutResult(); - if (is_layout_root && previous_result) { - // Due to layout-roots (starting layout at an arbirary node, instead of the - // |LayoutView|), we can end up with a situation where we'll miss our cache - // due to baseline-requests not matching. - // - // For the case where we start at a layout-root, the baselines don't - // particularly matter, so we just request exactly the same as the previous - // layout. - builder.AddBaselineRequests( - previous_result->GetConstraintSpaceForCaching().BaselineRequests()); - } else if (!block.IsWritingModeRoot() || block.IsGridItem()) { - // Add all types because we don't know which baselines will be requested. - FontBaseline baseline_type = style.GetFontBaseline(); - bool synthesize_inline_block_baseline = - block.UseLogicalBottomMarginEdgeForInlineBlockBaseline(); - if (!synthesize_inline_block_baseline) { - builder.AddBaselineRequest( - {NGBaselineAlgorithmType::kAtomicInline, baseline_type}); - } - builder.AddBaselineRequest( - {NGBaselineAlgorithmType::kFirstLine, baseline_type}); + if (!block.IsWritingModeRoot() || block.IsGridItem()) { + // We don't know if the parent layout will require our baseline, so always + // request it. + builder.SetNeedsBaseline(true); + builder.SetBaselineAlgorithmType(block.IsInline() && + block.IsAtomicInlineLevel() + ? NGBaselineAlgorithmType::kInlineBlock + : NGBaselineAlgorithmType::kFirstLine); } if (block.IsTableCell()) { @@ -123,6 +108,10 @@ NGConstraintSpace NGConstraintSpace::CreateFromLayoutObject( table_style.BorderCollapse() == EBorderCollapse::kSeparate); } + if (block.IsAtomicInlineLevel() || block.IsFlexItem() || block.IsGridItem() || + block.IsFloating()) + builder.SetIsPaintedAtomically(true); + builder.SetAvailableSize(available_size); builder.SetPercentageResolutionSize(percentage_size); builder.SetIsFixedInlineSize(fixed_inline); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h index 91ec55842b2..e409701f6ce 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h @@ -13,7 +13,6 @@ #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/ng_break_appeal.h" #include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h" #include "third_party/blink/renderer/platform/text/text_direction.h" @@ -68,6 +67,28 @@ enum NGPercentageStorage { kRareDataPercentage }; +// Some layout algorithms (flow, tables) calculate their alignment baseline +// differently if they are within an atomic-inline context. +// +// Other more modern layout algorithms (flex, grid) however ignore this flag +// and always calculate the alignment baseline in the same way (returning the +// "first-line"). +enum class NGBaselineAlgorithmType { + // Compute the baseline of the first line box. + kFirstLine, + // Compute the baseline(s) for when we are within an inline-block context. If + // the child is block-flow it will produce both the first, and last baselines. + kInlineBlock +}; + +// Some layout algorithms have multiple layout passes. Between passes they +// typically have different results which we need to cache separately for +// performance reasons. +// +// This enum gives the caching logic a hint into which cache "slot" it should +// store a result in. +enum class NGCacheSlot { kLayout, kMeasure }; + // The NGConstraintSpace represents a set of constraints and available space // which a layout algorithm may produce a NGFragment within. class CORE_EXPORT NGConstraintSpace final { @@ -134,8 +155,7 @@ class CORE_EXPORT NGConstraintSpace final { // Creates NGConstraintSpace representing LayoutObject's containing block. // This should live on NGBlockNode or another layout bridge and probably take // a root NGConstraintSpace. - static NGConstraintSpace CreateFromLayoutObject(const LayoutBlock&, - bool is_layout_root); + static NGConstraintSpace CreateFromLayoutObject(const LayoutBlock&); const NGExclusionSpace& ExclusionSpace() const { return exclusion_space_; } @@ -239,6 +259,36 @@ class CORE_EXPORT NGConstraintSpace final { return LayoutUnit(); } + // Inline/block target stretch size constraints. + // See: + // https://mathml-refresh.github.io/mathml-core/#dfn-inline-stretch-size-constraint + LayoutUnit TargetStretchInlineSize() const { + return HasRareData() ? rare_data_->TargetStretchInlineSize() + : kIndefiniteSize; + } + + bool HasTargetStretchInlineSize() const { + return TargetStretchInlineSize() != kIndefiniteSize; + } + + LayoutUnit TargetStretchAscentSize() const { + return HasRareData() ? rare_data_->TargetStretchAscentSize() + : kIndefiniteSize; + } + + bool HasTargetStretchAscentSize() const { + return TargetStretchAscentSize() != kIndefiniteSize; + } + + LayoutUnit TargetStretchDescentSize() const { + return HasRareData() ? rare_data_->TargetStretchDescentSize() + : kIndefiniteSize; + } + + bool HasTargetStretchDescentSize() const { + return TargetStretchDescentSize() != kIndefiniteSize; + } + // Return the borders which should be used for a table-cell. NGBoxStrut TableCellBorders() const { return HasRareData() ? rare_data_->TableCellBorders() : NGBoxStrut(); @@ -322,6 +372,24 @@ class CORE_EXPORT NGConstraintSpace final { return bitfields_.ancestor_has_clearance_past_adjoining_floats; } + // Returns if the parent layout needs the baseline from this layout. + // + // This bit is only used for skipping querying baseline information from + // legacy layout. + bool NeedsBaseline() const { return bitfields_.needs_baseline; } + + // How the baseline for the fragment should be calculated, see documentation + // for |NGBaselineAlgorithmType|. + NGBaselineAlgorithmType BaselineAlgorithmType() const { + return static_cast<NGBaselineAlgorithmType>( + bitfields_.baseline_algorithm_type); + } + + // Which cache slot the output layout result should be stored in. + NGCacheSlot CacheSlot() const { + return static_cast<NGCacheSlot>(bitfields_.cache_slot); + } + // Some layout modes “stretch” their children to a fixed size (e.g. flex, // grid). These flags represented whether a layout needs to produce a // fragment that satisfies a fixed constraint in the inline and block @@ -342,6 +410,8 @@ class CORE_EXPORT NGConstraintSpace final { // (ie. fit-content). This is used for inline-block, floats, etc. bool IsShrinkToFit() const { return bitfields_.is_shrink_to_fit; } + bool IsPaintedAtomically() const { return bitfields_.is_painted_atomically; } + // If specified a layout should produce a Fragment which fragments at the // blockSize if possible. NGFragmentationType BlockFragmentationType() const { @@ -387,7 +457,7 @@ class CORE_EXPORT NGConstraintSpace final { // Return true if the block size of the table-cell should be considered // restricted (e.g. height of the cell or its table is non-auto). bool IsRestrictedBlockSizeTableCell() const { - return bitfields_.is_restricted_block_size_table_cell; + return HasRareData() && rare_data_->is_restricted_block_size_table_cell; } NGMarginStrut MarginStrut() const { @@ -488,8 +558,12 @@ class CORE_EXPORT NGConstraintSpace final { return HasRareData() ? rare_data_->ClearanceOffset() : LayoutUnit::Min(); } - const NGBaselineRequestList BaselineRequests() const { - return NGBaselineRequestList(bitfields_.baseline_requests); + bool ForceTruncateAtLineClamp() const { + return HasRareData() ? rare_data_->ForceTruncateAtLineClamp() : true; + } + + base::Optional<int> LinesUntilClamp() const { + return HasRareData() ? rare_data_->LinesUntilClamp() : base::nullopt; } // Return true if the two constraint spaces are similar enough that it *may* @@ -567,9 +641,6 @@ class CORE_EXPORT NGConstraintSpace final { private: friend class NGConstraintSpaceBuilder; - explicit NGConstraintSpace(WritingMode writing_mode) - : bfc_offset_(), bitfields_(writing_mode) {} - // This struct defines all of the inputs to layout which we consider rare. // Primarily this is: // - Percentage resolution sizes which differ from the available size or @@ -577,6 +648,7 @@ class CORE_EXPORT NGConstraintSpace final { // - The margin strut. // - Anything to do with floats (the exclusion space, clearance offset, etc). // - Anything to do with fragmentation. + // - Anything to do with stretching of math operators. // // This information is kept in a separate in this heap-allocated struct to // reduce memory usage. Over time this may have to change based on usage data. @@ -584,9 +656,20 @@ class CORE_EXPORT NGConstraintSpace final { USING_FAST_MALLOC(RareData); public: + // |RareData| unions different types of data which are mutually exclusive. + // They fall into the following categories: + enum DataUnionType { + kNone, + kBlockData, // An inflow block which doesn't establish a new FC. + kTableCellData, // A table-cell (display: table-cell). + kCustomData, // A custom layout (display: layout(foo)). + kStretchData // The target inline/block stretch sizes for MathML. + }; + explicit RareData(const NGBfcOffset bfc_offset) : bfc_offset(bfc_offset), data_union_type(static_cast<unsigned>(kNone)), + is_restricted_block_size_table_cell(false), hide_table_cell_if_empty(false), block_direction_fragmentation_type( static_cast<unsigned>(kFragmentNone)), @@ -601,6 +684,8 @@ class CORE_EXPORT NGConstraintSpace final { fragmentainer_block_size(other.fragmentainer_block_size), fragmentainer_offset_at_bfc(other.fragmentainer_offset_at_bfc), data_union_type(other.data_union_type), + is_restricted_block_size_table_cell( + other.is_restricted_block_size_table_cell), hide_table_cell_if_empty(other.hide_table_cell_if_empty), block_direction_fragmentation_type( other.block_direction_fragmentation_type), @@ -619,6 +704,9 @@ class CORE_EXPORT NGConstraintSpace final { case kCustomData: new (&custom_data_) CustomData(other.custom_data_); break; + case kStretchData: + new (&stretch_data_) StretchData(other.stretch_data_); + break; default: NOTREACHED(); } @@ -636,38 +724,20 @@ class CORE_EXPORT NGConstraintSpace final { case kCustomData: custom_data_.~CustomData(); break; + case kStretchData: + stretch_data_.~StretchData(); + break; default: NOTREACHED(); } } - // |RareData| unions different types of data which are mutually exclusive. - // They fall into the following categories: - enum DataUnionType { - kNone, - kBlockData, // An inflow block which doesn't establish a new FC. - kTableCellData, // A table-cell (display: table-cell). - kCustomData // A custom layout (display: layout(foo)). - }; - - LogicalSize percentage_resolution_size; - LayoutUnit replaced_percentage_resolution_block_size; - NGBfcOffset bfc_offset; - - LayoutUnit fragmentainer_block_size = kIndefiniteSize; - LayoutUnit fragmentainer_offset_at_bfc; - - unsigned data_union_type : 2; - unsigned hide_table_cell_if_empty : 1; - unsigned block_direction_fragmentation_type : 2; - unsigned is_inside_balanced_columns : 1; - unsigned is_in_column_bfc : 1; - unsigned early_break_appeal : 2; // NGBreakAppeal - bool MaySkipLayout(const RareData& other) const { if (fragmentainer_block_size != other.fragmentainer_block_size || fragmentainer_offset_at_bfc != other.fragmentainer_offset_at_bfc || data_union_type != other.data_union_type || + is_restricted_block_size_table_cell != + other.is_restricted_block_size_table_cell || hide_table_cell_if_empty != other.hide_table_cell_if_empty || block_direction_fragmentation_type != other.block_direction_fragmentation_type || @@ -680,19 +750,23 @@ class CORE_EXPORT NGConstraintSpace final { return true; if (data_union_type == kBlockData) - return true; + return block_data_.MaySkipLayout(other.block_data_); if (data_union_type == kTableCellData) return table_cell_data_.MaySkipLayout(other.table_cell_data_); - DCHECK_EQ(data_union_type, kCustomData); - return custom_data_.MaySkipLayout(other.custom_data_); + if (data_union_type == kCustomData) + return custom_data_.MaySkipLayout(other.custom_data_); + + DCHECK_EQ(data_union_type, kStretchData); + return stretch_data_.MaySkipLayout(other.stretch_data_); } // Must be kept in sync with members checked within |MaySkipLayout|. bool IsInitialForMaySkipLayout() const { if (fragmentainer_block_size != kIndefiniteSize || - fragmentainer_offset_at_bfc || hide_table_cell_if_empty || + fragmentainer_offset_at_bfc || is_restricted_block_size_table_cell || + hide_table_cell_if_empty || block_direction_fragmentation_type != kFragmentNone || is_inside_balanced_columns || is_in_column_bfc || early_break_appeal != kBreakAppealLastResort) @@ -702,13 +776,16 @@ class CORE_EXPORT NGConstraintSpace final { return true; if (data_union_type == kBlockData) - return true; + return block_data_.IsInitialForMaySkipLayout(); if (data_union_type == kTableCellData) return table_cell_data_.IsInitialForMaySkipLayout(); - DCHECK_EQ(data_union_type, kCustomData); - return custom_data_.IsInitialForMaySkipLayout(); + if (data_union_type == kCustomData) + return custom_data_.IsInitialForMaySkipLayout(); + + DCHECK_EQ(data_union_type, kStretchData); + return stretch_data_.IsInitialForMaySkipLayout(); } NGMarginStrut MarginStrut() const { @@ -749,6 +826,25 @@ class CORE_EXPORT NGConstraintSpace final { EnsureBlockData()->clearance_offset = clearance_offset; } + base::Optional<int> LinesUntilClamp() const { + return data_union_type == kBlockData ? block_data_.lines_until_clamp + : base::nullopt; + } + + void SetLinesUntilClamp(int value) { + EnsureBlockData()->lines_until_clamp = value; + } + + int ForceTruncateAtLineClamp() const { + return data_union_type == kBlockData + ? block_data_.force_truncate_at_line_clamp + : true; + } + + void SetForceTruncateAtLineClamp(bool value) { + EnsureBlockData()->force_truncate_at_line_clamp = value; + } + NGBoxStrut TableCellBorders() const { return data_union_type == kTableCellData ? table_cell_data_.table_cell_borders @@ -786,28 +882,78 @@ class CORE_EXPORT NGConstraintSpace final { EnsureCustomData()->data = std::move(custom_layout_data); } + LayoutUnit TargetStretchInlineSize() const { + return data_union_type == kStretchData + ? stretch_data_.target_stretch_inline_size + : kIndefiniteSize; + } + + void SetTargetStretchInlineSize(LayoutUnit target_stretch_inline_size) { + EnsureStretchData()->target_stretch_inline_size = + target_stretch_inline_size; + } + + LayoutUnit TargetStretchAscentSize() const { + return data_union_type == kStretchData + ? stretch_data_.target_stretch_ascent_size + : kIndefiniteSize; + } + + void SetTargetStretchAscentSize(LayoutUnit target_stretch_ascent_size) { + EnsureStretchData()->target_stretch_ascent_size = + target_stretch_ascent_size; + } + + LayoutUnit TargetStretchDescentSize() const { + return data_union_type == kStretchData + ? stretch_data_.target_stretch_descent_size + : kIndefiniteSize; + } + + void SetTargetStretchDescentSize(LayoutUnit target_stretch_descent_size) { + EnsureStretchData()->target_stretch_descent_size = + target_stretch_descent_size; + } + + LogicalSize percentage_resolution_size; + LayoutUnit replaced_percentage_resolution_block_size; + NGBfcOffset bfc_offset; + + LayoutUnit fragmentainer_block_size = kIndefiniteSize; + LayoutUnit fragmentainer_offset_at_bfc; + + unsigned data_union_type : 3; + + unsigned is_restricted_block_size_table_cell : 1; + unsigned hide_table_cell_if_empty : 1; + + unsigned block_direction_fragmentation_type : 2; + unsigned is_inside_balanced_columns : 1; + unsigned is_in_column_bfc : 1; + unsigned early_break_appeal : 2; // NGBreakAppeal private: struct BlockData { + bool MaySkipLayout(const BlockData& other) const { + return lines_until_clamp == other.lines_until_clamp && + force_truncate_at_line_clamp == + other.force_truncate_at_line_clamp; + } + + bool IsInitialForMaySkipLayout() const { + return !lines_until_clamp.has_value() && force_truncate_at_line_clamp; + } + NGMarginStrut margin_strut; base::Optional<LayoutUnit> optimistic_bfc_block_offset; base::Optional<LayoutUnit> forced_bfc_block_offset; LayoutUnit clearance_offset = LayoutUnit::Min(); + base::Optional<int> lines_until_clamp; + // If true and |lines_until_clamp| == 1, then the line should be truncated + // regardless of whether there is more text that follows on the line. + bool force_truncate_at_line_clamp = true; }; - BlockData* EnsureBlockData() { - DCHECK(data_union_type == kNone || data_union_type == kBlockData); - if (data_union_type != kBlockData) { - data_union_type = kBlockData; - new (&block_data_) BlockData(); - } - return &block_data_; - } - struct TableCellData { - NGBoxStrut table_cell_borders; - LayoutUnit table_cell_intrinsic_padding_block_start; - LayoutUnit table_cell_intrinsic_padding_block_end; - bool MaySkipLayout(const TableCellData& other) const { return table_cell_borders == other.table_cell_borders && table_cell_intrinsic_padding_block_start == @@ -821,16 +967,11 @@ class CORE_EXPORT NGConstraintSpace final { table_cell_intrinsic_padding_block_start == LayoutUnit() && table_cell_intrinsic_padding_block_end == LayoutUnit(); } - }; - TableCellData* EnsureTableCellData() { - DCHECK(data_union_type == kNone || data_union_type == kTableCellData); - if (data_union_type != kTableCellData) { - data_union_type = kTableCellData; - new (&table_cell_data_) TableCellData(); - } - return &table_cell_data_; - } + NGBoxStrut table_cell_borders; + LayoutUnit table_cell_intrinsic_padding_block_start; + LayoutUnit table_cell_intrinsic_padding_block_end; + }; struct CustomData { scoped_refptr<SerializedScriptValue> data; @@ -842,6 +983,42 @@ class CORE_EXPORT NGConstraintSpace final { bool IsInitialForMaySkipLayout() const { return !data; } }; + struct StretchData { + bool MaySkipLayout(const StretchData& other) const { + return target_stretch_inline_size == other.target_stretch_inline_size && + target_stretch_ascent_size == other.target_stretch_ascent_size && + target_stretch_descent_size == other.target_stretch_descent_size; + } + + bool IsInitialForMaySkipLayout() const { + return target_stretch_inline_size == kIndefiniteSize && + target_stretch_ascent_size == kIndefiniteSize && + target_stretch_descent_size == kIndefiniteSize; + } + + LayoutUnit target_stretch_inline_size = kIndefiniteSize; + LayoutUnit target_stretch_ascent_size = kIndefiniteSize; + LayoutUnit target_stretch_descent_size = kIndefiniteSize; + }; + + BlockData* EnsureBlockData() { + DCHECK(data_union_type == kNone || data_union_type == kBlockData); + if (data_union_type != kBlockData) { + data_union_type = kBlockData; + new (&block_data_) BlockData(); + } + return &block_data_; + } + + TableCellData* EnsureTableCellData() { + DCHECK(data_union_type == kNone || data_union_type == kTableCellData); + if (data_union_type != kTableCellData) { + data_union_type = kTableCellData; + new (&table_cell_data_) TableCellData(); + } + return &table_cell_data_; + } + CustomData* EnsureCustomData() { DCHECK(data_union_type == kNone || data_union_type == kCustomData); if (data_union_type != kCustomData) { @@ -851,10 +1028,20 @@ class CORE_EXPORT NGConstraintSpace final { return &custom_data_; } + StretchData* EnsureStretchData() { + DCHECK(data_union_type == kNone || data_union_type == kStretchData); + if (data_union_type != kStretchData) { + data_union_type = kStretchData; + new (&stretch_data_) StretchData(); + } + return &stretch_data_; + } + union { BlockData block_data_; TableCellData table_cell_data_; CustomData custom_data_; + StretchData stretch_data_; }; }; @@ -876,13 +1063,17 @@ class CORE_EXPORT NGConstraintSpace final { is_anonymous(false), is_new_formatting_context(false), is_orthogonal_writing_mode_root(false), - is_fixed_block_size_indefinite(false), - is_restricted_block_size_table_cell(false), + is_painted_atomically(false), use_first_line_style(false), ancestor_has_clearance_past_adjoining_floats(false), + needs_baseline(false), + baseline_algorithm_type( + static_cast<unsigned>(NGBaselineAlgorithmType::kFirstLine)), + cache_slot(static_cast<unsigned>(NGCacheSlot::kLayout)), is_shrink_to_fit(false), is_fixed_inline_size(false), is_fixed_block_size(false), + is_fixed_block_size_indefinite(false), table_cell_child_layout_mode(static_cast<unsigned>( NGTableCellChildLayoutMode::kNotTableCellChild)), percentage_inline_storage(kSameAsAvailable), @@ -898,20 +1089,20 @@ class CORE_EXPORT NGConstraintSpace final { is_new_formatting_context == other.is_new_formatting_context && is_orthogonal_writing_mode_root == other.is_orthogonal_writing_mode_root && - is_fixed_block_size_indefinite == - other.is_fixed_block_size_indefinite && - is_restricted_block_size_table_cell == - other.is_restricted_block_size_table_cell && + is_painted_atomically == other.is_painted_atomically && use_first_line_style == other.use_first_line_style && ancestor_has_clearance_past_adjoining_floats == other.ancestor_has_clearance_past_adjoining_floats && - baseline_requests == other.baseline_requests; + needs_baseline == other.needs_baseline && + baseline_algorithm_type == other.baseline_algorithm_type; } bool AreSizeConstraintsEqual(const Bitfields& other) const { return is_shrink_to_fit == other.is_shrink_to_fit && is_fixed_inline_size == other.is_fixed_inline_size && is_fixed_block_size == other.is_fixed_block_size && + is_fixed_block_size_indefinite == + other.is_fixed_block_size_indefinite && table_cell_child_layout_mode == other.table_cell_child_layout_mode; } @@ -925,17 +1116,20 @@ class CORE_EXPORT NGConstraintSpace final { unsigned is_new_formatting_context : 1; unsigned is_orthogonal_writing_mode_root : 1; - unsigned is_fixed_block_size_indefinite : 1; - unsigned is_restricted_block_size_table_cell : 1; + unsigned is_painted_atomically : 1; unsigned use_first_line_style : 1; unsigned ancestor_has_clearance_past_adjoining_floats : 1; - unsigned baseline_requests : NGBaselineRequestList::kSerializedBits; + unsigned needs_baseline : 1; + unsigned baseline_algorithm_type : 1; + + unsigned cache_slot : 1; // Size constraints. unsigned is_shrink_to_fit : 1; unsigned is_fixed_inline_size : 1; unsigned is_fixed_block_size : 1; + unsigned is_fixed_block_size_indefinite : 1; unsigned table_cell_child_layout_mode : 2; // NGTableCellChildLayoutMode unsigned percentage_inline_storage : 2; // NGPercentageStorage @@ -943,6 +1137,9 @@ class CORE_EXPORT NGConstraintSpace final { unsigned replaced_percentage_block_storage : 2; // NGPercentageStorage }; + explicit NGConstraintSpace(WritingMode writing_mode) + : bfc_offset_(), bitfields_(writing_mode) {} + inline bool HasRareData() const { return bitfields_.has_rare_data; } RareData* EnsureRareData() { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h index 9a1709ec95f..68cc44fc392 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h @@ -145,6 +145,10 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { void SetIsShrinkToFit(bool b) { space_.bitfields_.is_shrink_to_fit = b; } + void SetIsPaintedAtomically(bool b) { + space_.bitfields_.is_painted_atomically = b; + } + void SetFragmentationType(NGFragmentationType fragmentation_type) { #if DCHECK_IS_ON() DCHECK(!is_block_direction_fragmentation_type_set_); @@ -172,13 +176,16 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { void SetIsRestrictedBlockSizeTableCell(bool b) { DCHECK(space_.bitfields_.is_table_cell); - space_.bitfields_.is_restricted_block_size_table_cell = b; + if (!b && !space_.rare_data_) + return; + space_.EnsureRareData()->is_restricted_block_size_table_cell = b; } void SetHideTableCellIfEmpty(bool b) { DCHECK(space_.bitfields_.is_table_cell); - if (b) - space_.EnsureRareData()->hide_table_cell_if_empty = b; + if (!b && !space_.rare_data_) + return; + space_.EnsureRareData()->hide_table_cell_if_empty = b; } void SetIsAnonymous(bool b) { space_.bitfields_.is_anonymous = b; } @@ -187,10 +194,6 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { space_.bitfields_.use_first_line_style = b; } - void SetAncestorHasClearancePastAdjoiningFloats() { - space_.bitfields_.ancestor_has_clearance_past_adjoining_floats = true; - } - void SetAdjoiningObjectTypes(NGAdjoiningObjectTypes adjoining_object_types) { if (!is_new_fc_) { space_.bitfields_.adjoining_object_types = @@ -198,6 +201,20 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { } } + void SetAncestorHasClearancePastAdjoiningFloats() { + space_.bitfields_.ancestor_has_clearance_past_adjoining_floats = true; + } + + void SetNeedsBaseline(bool b) { space_.bitfields_.needs_baseline = b; } + + void SetBaselineAlgorithmType(NGBaselineAlgorithmType type) { + space_.bitfields_.baseline_algorithm_type = static_cast<unsigned>(type); + } + + void SetCacheSlot(NGCacheSlot slot) { + space_.bitfields_.cache_slot = static_cast<unsigned>(slot); + } + void SetMarginStrut(const NGMarginStrut& margin_strut) { #if DCHECK_IS_ON() DCHECK(!is_margin_strut_set_); @@ -301,12 +318,41 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { } } - void AddBaselineRequests(const NGBaselineRequestList requests) { - DCHECK(baseline_requests_.IsEmpty()); - baseline_requests_.AppendVector(requests); + void SetForceTruncateAtLineClamp(bool value) { +#if DCHECK_IS_ON() + DCHECK(!is_force_truncate_at_line_clamp_set_); + is_force_truncate_at_line_clamp_set_ = true; +#endif + if (!value) + space_.EnsureRareData()->SetForceTruncateAtLineClamp(value); } - void AddBaselineRequest(const NGBaselineRequest request) { - baseline_requests_.push_back(request); + + void SetLinesUntilClamp(const base::Optional<int>& clamp) { +#if DCHECK_IS_ON() + DCHECK(!is_lines_until_clamp_set_); + is_lines_until_clamp_set_ = true; +#endif + DCHECK(!is_new_fc_); + if (clamp) + space_.EnsureRareData()->SetLinesUntilClamp(*clamp); + } + + void SetTargetStretchInlineSize(LayoutUnit target_stretch_inline_size) { + DCHECK_GE(target_stretch_inline_size, LayoutUnit()); + space_.EnsureRareData()->SetTargetStretchInlineSize( + target_stretch_inline_size); + } + + void SetTargetStretchAscentSize(LayoutUnit target_stretch_ascent_size) { + DCHECK_GE(target_stretch_ascent_size, LayoutUnit()); + space_.EnsureRareData()->SetTargetStretchAscentSize( + target_stretch_ascent_size); + } + + void SetTargetStretchDescentSize(LayoutUnit target_stretch_descent_size) { + DCHECK_GE(target_stretch_descent_size, LayoutUnit()); + space_.EnsureRareData()->SetTargetStretchDescentSize( + target_stretch_descent_size); } // Creates a new constraint space. @@ -326,7 +372,6 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { "simultaneously. Inferred means the constraints are in parent " "writing mode, forced means they are in child writing mode."; - space_.bitfields_.baseline_requests = baseline_requests_.Serialize(); return std::move(space_); } @@ -355,11 +400,11 @@ class CORE_EXPORT NGConstraintSpaceBuilder final { bool is_table_cell_borders_set_ = false; bool is_table_cell_intrinsic_padding_set_ = false; bool is_custom_layout_data_set_ = false; + bool is_lines_until_clamp_set_ = false; + bool is_force_truncate_at_line_clamp_set_ = false; bool to_constraint_space_called_ = false; #endif - - NGBaselineRequestList baseline_requests_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc index 6e6115501e8..4c26a00648f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc @@ -101,7 +101,7 @@ void NGContainerFragmentBuilder::PropagateChildData( // have a child positioned above our block-start edge. if ((child_offset.block_offset < LayoutUnit() && !child.IsOutOfFlowPositioned()) || - (!child.IsBlockFormattingContextRoot() && !child.IsLineBox() && + (!child.IsFormattingContextRoot() && !child.IsLineBox() && child.MayHaveDescendantAboveBlockStart())) may_have_descendant_above_block_start_ = true; @@ -120,7 +120,7 @@ void NGContainerFragmentBuilder::PropagateChildData( // If a fragment doesn't have any adjoining object descendants, and is // self-collapsing, it can be "shifted" anywhere. if (!has_adjoining_object_descendants_) { - if (!child.IsBlockFormattingContextRoot() && + if (!child.IsFormattingContextRoot() && child.HasAdjoiningObjectDescendants()) has_adjoining_object_descendants_ = true; } @@ -132,7 +132,6 @@ void NGContainerFragmentBuilder::PropagateChildData( if (const NGBreakToken* child_break_token = child.BreakToken()) { switch (child.Type()) { case NGPhysicalFragment::kFragmentBox: - case NGPhysicalFragment::kFragmentRenderedLegend: child_break_tokens_.push_back(child_break_token); break; case NGPhysicalFragment::kFragmentLineBox: @@ -163,33 +162,6 @@ void NGContainerFragmentBuilder::AddChildInternal( children_.emplace_back(child_offset, std::move(child)); } -LogicalOffset NGContainerFragmentBuilder::GetChildOffset( - const LayoutObject* object) const { - for (const auto& child : children_) { - if (child.fragment->GetLayoutObject() == object) - return child.offset; - - // TODO(layout-dev): ikilpatrick thinks we may need to traverse - // further than the initial line-box children for a nested inline - // container. We could not come up with a testcase, it would be - // something with split inlines, and nested oof/fixed descendants maybe. - if (child.fragment->IsLineBox()) { - const auto& line_box_fragment = - To<NGPhysicalLineBoxFragment>(*child.fragment); - for (const auto& line_box_child : line_box_fragment.Children()) { - if (line_box_child->GetLayoutObject() == object) { - return child.offset + line_box_child.Offset().ConvertToLogical( - GetWritingMode(), Direction(), - line_box_fragment.Size(), - line_box_child->Size()); - } - } - } - } - NOTREACHED(); - return LogicalOffset(); -} - void NGContainerFragmentBuilder::AddOutOfFlowChildCandidate( NGBlockNode child, const LogicalOffset& child_offset, @@ -197,8 +169,17 @@ void NGContainerFragmentBuilder::AddOutOfFlowChildCandidate( NGLogicalStaticPosition::BlockEdge block_edge) { DCHECK(child); + // If an OOF-positioned candidate has a static-position which uses a + // non-block-start edge, we need to adjust its static-position when the final + // block-size is known. + bool needs_block_offset_adjustment = + block_edge != NGLogicalStaticPosition::BlockEdge::kBlockStart; + has_oof_candidate_that_needs_block_offset_adjustment_ |= + needs_block_offset_adjustment; + oof_positioned_candidates_.emplace_back( - child, NGLogicalStaticPosition{child_offset, inline_edge, block_edge}); + child, NGLogicalStaticPosition{child_offset, inline_edge, block_edge}, + /* inline_container */ nullptr, needs_block_offset_adjustment); } void NGContainerFragmentBuilder::AddOutOfFlowInlineChildCandidate( @@ -226,6 +207,27 @@ void NGContainerFragmentBuilder::SwapOutOfFlowPositionedCandidates( Vector<NGLogicalOutOfFlowPositionedNode>* candidates) { DCHECK(candidates->IsEmpty()); std::swap(oof_positioned_candidates_, *candidates); + + if (!has_oof_candidate_that_needs_block_offset_adjustment_) + return; + + using BlockEdge = NGLogicalStaticPosition::BlockEdge; + + // We might have an OOF-positioned candidate whose static-position depends on + // the final block-size of this fragment. + DCHECK_NE(BlockSize(), kIndefiniteSize); + for (auto& candidate : *candidates) { + if (!candidate.needs_block_offset_adjustment) + continue; + + if (candidate.static_position.block_edge == BlockEdge::kBlockCenter) + candidate.static_position.offset.block_offset += BlockSize() / 2; + else if (candidate.static_position.block_edge == BlockEdge::kBlockEnd) + candidate.static_position.offset.block_offset += BlockSize(); + candidate.needs_block_offset_adjustment = false; + } + + has_oof_candidate_that_needs_block_offset_adjustment_ = false; } void NGContainerFragmentBuilder:: diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h index 0ca512fee7f..a76a849df0b 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h @@ -88,10 +88,6 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { const ChildrenVector& Children() const { return children_; } - // Returns offset for given child. DCHECK if child not found. - // Warning: Do not call unless necessary. - LogicalOffset GetChildOffset(const LayoutObject* child) const; - // Builder has non-trivial OOF-positioned methods. // They are intended to be used by a layout algorithm like this: // @@ -183,8 +179,9 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { #endif protected: - friend class NGPhysicalContainerFragment; + friend class NGInlineLayoutStateStack; friend class NGLayoutResult; + friend class NGPhysicalContainerFragment; NGContainerFragmentBuilder(NGLayoutInputNode node, scoped_refptr<const ComputedStyle> style, @@ -240,6 +237,8 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder { bool has_block_fragmentation_ = false; bool is_fragmentation_context_root_ = false; bool may_have_descendant_above_block_start_ = false; + + bool has_oof_candidate_that_needs_block_offset_adjustment_ = false; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc index 79bf0f09f45..02c3b80ed79 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc @@ -10,6 +10,7 @@ #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" @@ -21,16 +22,30 @@ namespace blink { NGFieldsetLayoutAlgorithm::NGFieldsetLayoutAlgorithm( const NGLayoutAlgorithmParams& params) : NGLayoutAlgorithm(params), + writing_mode_(ConstraintSpace().GetWritingMode()), border_padding_(params.fragment_geometry.border + - params.fragment_geometry.padding) { + params.fragment_geometry.padding), + consumed_block_size_(BreakToken() ? BreakToken()->ConsumedBlockSize() + : LayoutUnit()) { container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); + + borders_ = container_builder_.Borders(); + padding_ = container_builder_.Padding(); + border_box_size_ = container_builder_.InitialBorderBoxSize(); + block_start_padding_edge_ = borders_.block_start; + + // Leading border and padding should only apply to the first fragment. We + // don't adjust the value of border_padding_ itself so that it can be used + // when calculating the block size of the last fragment. + adjusted_border_padding_ = border_padding_; + AdjustForFragmentation(BreakToken(), &adjusted_border_padding_); } scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { - // TODO(mstensho): Support block fragmentation. - DCHECK(!BreakToken()); + // TODO(almaher): Make sure the border start is handled correctly during + // fragmentation. // Layout of a fieldset container consists of two parts: Create a child // fragment for the rendered legend (if any), and create a child fragment for @@ -42,87 +57,32 @@ scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { // with the actual fieldset contents. Since scrollbars are handled by the // anonymous child box, and since padding is inside the scrollport, padding // also needs to be handled by the anonymous child. - NGBoxStrut borders = container_builder_.Borders(); - NGBoxStrut padding = container_builder_.Padding(); - LogicalSize border_box_size = container_builder_.InitialBorderBoxSize(); - const auto writing_mode = ConstraintSpace().GetWritingMode(); - LayoutUnit block_start_padding_edge = - container_builder_.Borders().block_start; - - // TODO(vmpstr): Skip child (including legend) layout for fieldset elements. - if (NGBlockNode legend = Node().GetRenderedLegend()) { - // Lay out the legend. While the fieldset container normally ignores its - // padding, the legend is laid out within what would have been the content - // box had the fieldset been a regular block with no weirdness. - LogicalSize content_box_size = - ShrinkAvailableSize(border_box_size, border_padding_); - auto legend_space = - CreateConstraintSpaceForLegend(legend, content_box_size); - auto result = legend.Layout(legend_space, BreakToken()); - const auto& physical_fragment = result->PhysicalFragment(); - NGBoxStrut legend_margins = - ComputeMarginsFor(legend_space, legend.Style(), ConstraintSpace()); - // If the margin box of the legend is at least as tall as the fieldset - // block-start border width, it will start at the block-start border edge of - // the fieldset. As a paint effect, the block-start border will be pushed so - // that the center of the border will be flush with the center of the - // border-box of the legend. - // TODO(mstensho): inline alignment - LogicalOffset legend_offset = LogicalOffset( - border_padding_.inline_start + legend_margins.inline_start, - legend_margins.block_start); - LayoutUnit legend_margin_box_block_size = - NGFragment(writing_mode, physical_fragment).BlockSize() + - legend_margins.BlockSum(); - LayoutUnit space_left = borders.block_start - legend_margin_box_block_size; - if (space_left > LayoutUnit()) { - // If the border is the larger one, though, it will stay put at the - // border-box block-start edge of the fieldset. Then it's the legend that - // needs to be pushed. We'll center the margin box in this case, to make - // sure that both margins remain within the area occupied by the border - // also after adjustment. - legend_offset.block_offset += space_left / 2; - } else { - // If the legend is larger than the width of the fieldset block-start - // border, the actual padding edge of the fieldset will be moved - // accordingly. This will be the block-start offset for the fieldset - // contents anonymous box. - block_start_padding_edge = legend_margin_box_block_size; - } - - container_builder_.AddChild(physical_fragment, legend_offset); - } - NGBoxStrut borders_with_legend = borders; - borders_with_legend.block_start = block_start_padding_edge; - LayoutUnit intrinsic_block_size = borders_with_legend.BlockSum(); + if (ConstraintSpace().HasBlockFragmentation()) { + container_builder_.SetHasBlockFragmentation(); + // The whereabouts of our container's so far best breakpoint is none of our + // business, but we store its appeal, so that we don't look for breakpoints + // with lower appeal than that. + container_builder_.SetBreakAppeal(ConstraintSpace().EarlyBreakAppeal()); - // Proceed with normal fieldset children (excluding the rendered legend). They - // all live inside an anonymous child box of the fieldset container. - if (auto fieldset_content = Node().GetFieldsetContent()) { - LogicalSize adjusted_padding_box_size = - ShrinkAvailableSize(border_box_size, borders_with_legend); - auto child_space = - CreateConstraintSpaceForFieldsetContent(adjusted_padding_box_size); - auto result = fieldset_content.Layout(child_space, BreakToken()); - const auto& physical_fragment = result->PhysicalFragment(); - container_builder_.AddChild(physical_fragment, - borders_with_legend.StartOffset()); + if (ConstraintSpace().IsInitialColumnBalancingPass()) + container_builder_.SetIsInitialColumnBalancingPass(); + } - intrinsic_block_size += - NGFragment(writing_mode, physical_fragment).BlockSize(); - } else { - // There was no anonymous child to provide the padding, so we have to add it - // ourselves. - intrinsic_block_size += padding.BlockSum(); + NGBreakStatus break_status = LayoutChildren(); + if (break_status == NGBreakStatus::kNeedsEarlierBreak) { + // We need to abort the layout. No fragment will be generated. + return container_builder_.Abort(NGLayoutResult::kNeedsEarlierBreak); } - intrinsic_block_size = ClampIntrinsicBlockSize( - ConstraintSpace(), Node(), border_padding_, intrinsic_block_size); + intrinsic_block_size_ = + ClampIntrinsicBlockSize(ConstraintSpace(), Node(), + adjusted_border_padding_, intrinsic_block_size_); // Recompute the block-axis size now that we know our content size. - border_box_size.block_size = ComputeBlockSizeForFragment( - ConstraintSpace(), Style(), border_padding_, intrinsic_block_size); + border_box_size_.block_size = + ComputeBlockSizeForFragment(ConstraintSpace(), Style(), border_padding_, + intrinsic_block_size_ + consumed_block_size_); // The above computation utility knows nothing about fieldset weirdness. The // legend may eat from the available content box block size. Make room for @@ -131,34 +91,291 @@ scoped_refptr<const NGLayoutResult> NGFieldsetLayoutAlgorithm::Layout() { // contents, with the conjecture being that legend is part of the contents. // Thus, only do this adjustment if we do not contain size. if (!Node().ShouldApplySizeContainment()) { - LayoutUnit minimum_border_box_block_size = - borders_with_legend.BlockSum() + padding.BlockSum(); - border_box_size.block_size = - std::max(border_box_size.block_size, minimum_border_box_block_size); + // Similar to how we add the consumed block size to the intrinsic + // block size when calculating border_box_size_.block_size, we also need to + // do so when the fieldset is adjusted to encompass the legend. + border_box_size_.block_size = + std::max(border_box_size_.block_size, + minimum_border_box_block_size_ + consumed_block_size_); } + // TODO(almaher): end border and padding may overflow the parent + // fragmentainer, and we should avoid that. + LayoutUnit block_size = border_box_size_.block_size - consumed_block_size_; + container_builder_.SetIsFieldsetContainer(); - container_builder_.SetIntrinsicBlockSize(intrinsic_block_size); - container_builder_.SetBlockSize(border_box_size.block_size); + if (ConstraintSpace().HasKnownFragmentainerBlockSize()) { + FinishFragmentation( + ConstraintSpace(), BreakToken(), block_size, intrinsic_block_size_, + FragmentainerSpaceAtBfcStart(ConstraintSpace()), &container_builder_); + } else { + container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_); + container_builder_.SetBlockSize(block_size); + } - NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders_with_legend, + NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders_with_legend_, &container_builder_) .Run(); return container_builder_.ToBoxFragment(); } -base::Optional<MinMaxSize> NGFieldsetLayoutAlgorithm::ComputeMinMaxSize( - const MinMaxSizeInput& input) const { - MinMaxSize sizes; +NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutChildren() { + scoped_refptr<const NGBlockBreakToken> legend_break_token; + scoped_refptr<const NGBlockBreakToken> content_break_token; + bool has_seen_all_children = false; + if (const auto* token = BreakToken()) { + const auto child_tokens = token->ChildBreakTokens(); + if (wtf_size_t break_token_count = child_tokens.size()) { + DCHECK_LE(break_token_count, 2u); + for (wtf_size_t break_token_idx = 0; break_token_idx < break_token_count; + break_token_idx++) { + scoped_refptr<const NGBlockBreakToken> child_token = + To<NGBlockBreakToken>(child_tokens[break_token_idx]); + if (child_token && child_token->InputNode().IsRenderedLegend()) { + DCHECK_EQ(break_token_idx, 0u); + legend_break_token = child_token; + } else { + content_break_token = child_token; + } + } + } + if (token->HasSeenAllChildren()) { + container_builder_.SetHasSeenAllChildren(); + has_seen_all_children = true; + } + } + + NGBlockNode legend = Node().GetRenderedLegend(); + bool legend_needs_layout = + legend && (legend_break_token || !IsResumingLayout(BreakToken())); - bool apply_size_containment = node_.ShouldApplySizeContainment(); - // TODO(crbug.com/1011842): Need to consider content-size here. - if (apply_size_containment) { - if (input.size_type == NGMinMaxSizeType::kContentBoxSize) - return sizes; + if (legend_needs_layout) { + NGBreakStatus break_status = LayoutLegend(legend, legend_break_token); + if (break_status != NGBreakStatus::kContinue) + return break_status; + } + + borders_with_legend_ = borders_; + borders_with_legend_.block_start = block_start_padding_edge_; + + // The legend may eat from the available content box block size. If the + // border_box_size_ is expanded to encompass the legend, then update the + // border_box_size_ here, as well, to ensure the fieldset content gets the + // correct size. + if (!Node().ShouldApplySizeContainment() && legend_needs_layout) { + minimum_border_box_block_size_ = + borders_with_legend_.BlockSum() + padding_.BlockSum(); + if (border_box_size_.block_size != kIndefiniteSize) { + border_box_size_.block_size = + std::max(border_box_size_.block_size, minimum_border_box_block_size_); + } + } + + LogicalSize adjusted_padding_box_size = + ShrinkAvailableSize(border_box_size_, borders_with_legend_); + + // If the legend has been laid out in previous fragments, + // adjusted_padding_box_size will need to be adjusted further to account for + // block size taken up by the legend. + if (legend && adjusted_padding_box_size.block_size != kIndefiniteSize) { + LayoutUnit content_consumed_block_size = + content_break_token ? content_break_token->ConsumedBlockSize() + : LayoutUnit(); + LayoutUnit legend_block_size = + consumed_block_size_ - content_consumed_block_size; + adjusted_padding_box_size.block_size = + std::max(padding_.BlockSum(), + adjusted_padding_box_size.block_size - legend_block_size); + } + + if ((IsResumingLayout(content_break_token.get())) || + (!block_start_padding_edge_adjusted_ && IsResumingLayout(BreakToken()))) { + borders_with_legend_.block_start = LayoutUnit(); + } + intrinsic_block_size_ = borders_with_legend_.BlockSum(); + + // Proceed with normal fieldset children (excluding the rendered legend). They + // all live inside an anonymous child box of the fieldset container. + auto fieldset_content = Node().GetFieldsetContent(); + if (fieldset_content && (content_break_token || !has_seen_all_children)) { + LayoutUnit fragmentainer_block_offset; + if (ConstraintSpace().HasBlockFragmentation()) { + fragmentainer_block_offset = + ConstraintSpace().FragmentainerOffsetAtBfc() + intrinsic_block_size_; + if (legend_broke_ && + IsFragmentainerOutOfSpace(fragmentainer_block_offset)) + return NGBreakStatus::kContinue; + } + NGBreakStatus break_status = LayoutFieldsetContent( + fieldset_content, content_break_token, adjusted_padding_box_size, + fragmentainer_block_offset, !!legend); + if (break_status == NGBreakStatus::kNeedsEarlierBreak) + return break_status; + } + + if (!fieldset_content) { + container_builder_.SetHasSeenAllChildren(); + // There was no anonymous child to provide the padding, so we have to add it + // ourselves. + intrinsic_block_size_ += padding_.BlockSum(); } + return NGBreakStatus::kContinue; +} + +NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutLegend( + NGBlockNode& legend, + scoped_refptr<const NGBlockBreakToken> legend_break_token) { + // Lay out the legend. While the fieldset container normally ignores its + // padding, the legend is laid out within what would have been the content + // box had the fieldset been a regular block with no weirdness. + LogicalSize content_box_size = + ShrinkAvailableSize(border_box_size_, adjusted_border_padding_); + LogicalSize percentage_size = + CalculateChildPercentageSize(ConstraintSpace(), Node(), content_box_size); + NGBoxStrut legend_margins = ComputeMarginsFor( + legend.Style(), percentage_size.inline_size, + ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction()); + + if (legend_break_token) + legend_margins.block_start = LayoutUnit(); + + LogicalOffset legend_offset; + scoped_refptr<const NGLayoutResult> result; + scoped_refptr<const NGLayoutResult> previous_result; + LayoutUnit block_offset = legend_margins.block_start; + do { + auto legend_space = CreateConstraintSpaceForLegend( + legend, content_box_size, percentage_size, block_offset); + result = legend.Layout(legend_space, legend_break_token.get()); + + // TODO(layout-dev): Handle abortions caused by block fragmentation. + DCHECK_EQ(result->Status(), NGLayoutResult::kSuccess); + + if (ConstraintSpace().HasBlockFragmentation()) { + NGBreakStatus break_status = BreakBeforeChildIfNeeded( + ConstraintSpace(), legend, *result.get(), + ConstraintSpace().FragmentainerOffsetAtBfc() + block_offset, + /*has_container_separation*/ false, &container_builder_); + if (break_status != NGBreakStatus::kContinue) + return break_status; + EBreakBetween break_after = JoinFragmentainerBreakValues( + result->FinalBreakAfter(), legend.Style().BreakAfter()); + container_builder_.SetPreviousBreakAfter(break_after); + } + + const auto& physical_fragment = result->PhysicalFragment(); + legend_broke_ = physical_fragment.BreakToken(); + + // We have already adjusted the legend block offset, no need to adjust + // again. + if (block_offset != legend_margins.block_start) { + // If adjusting the block_offset caused the legend to break, revert back + // to the previous result. + if (legend_broke_) { + result = std::move(previous_result); + block_offset = legend_margins.block_start; + } + break; + } + + LayoutUnit legend_margin_box_block_size = + NGFragment(writing_mode_, physical_fragment).BlockSize() + + legend_margins.BlockSum(); + LayoutUnit space_left = borders_.block_start - legend_margin_box_block_size; + if (space_left > LayoutUnit()) { + // Don't adjust the block_offset if the legend broke. + if (legend_break_token || legend_broke_) + break; + + // If the border is the larger one, though, it will stay put at the + // border-box block-start edge of the fieldset. Then it's the legend + // that needs to be pushed. We'll center the margin box in this case, to + // make sure that both margins remain within the area occupied by the + // border also after adjustment. + block_offset += space_left / 2; + if (ConstraintSpace().HasBlockFragmentation()) { + // Save the previous result in case adjusting the block_offset causes + // the legend to break. + previous_result = std::move(result); + continue; + } + } else { + // If the legend is larger than the width of the fieldset block-start + // border, the actual padding edge of the fieldset will be moved + // accordingly. This will be the block-start offset for the fieldset + // contents anonymous box. + block_start_padding_edge_ = legend_margin_box_block_size; + block_start_padding_edge_adjusted_ = true; + } + break; + } while (true); + + // If the margin box of the legend is at least as tall as the fieldset + // block-start border width, it will start at the block-start border edge + // of the fieldset. As a paint effect, the block-start border will be + // pushed so that the center of the border will be flush with the center + // of the border-box of the legend. + // TODO(mstensho): inline alignment + legend_offset = LogicalOffset( + adjusted_border_padding_.inline_start + legend_margins.inline_start, + block_offset); + + container_builder_.AddResult(*result, legend_offset); + return NGBreakStatus::kContinue; +} + +NGBreakStatus NGFieldsetLayoutAlgorithm::LayoutFieldsetContent( + NGBlockNode& fieldset_content, + scoped_refptr<const NGBlockBreakToken> content_break_token, + LogicalSize adjusted_padding_box_size, + LayoutUnit fragmentainer_block_offset, + bool has_legend) { + auto child_space = CreateConstraintSpaceForFieldsetContent( + fieldset_content, adjusted_padding_box_size, + borders_with_legend_.block_start); + auto result = fieldset_content.Layout(child_space, content_break_token.get()); + + // TODO(layout-dev): Handle abortions caused by block fragmentation. + DCHECK_EQ(result->Status(), NGLayoutResult::kSuccess); + + NGBreakStatus break_status = NGBreakStatus::kContinue; + if (ConstraintSpace().HasBlockFragmentation()) { + // TODO(almaher): The legend should be treated as out-of-flow. + break_status = BreakBeforeChildIfNeeded( + ConstraintSpace(), fieldset_content, *result.get(), + fragmentainer_block_offset, + /*has_container_separation*/ has_legend, &container_builder_); + EBreakBetween break_after = JoinFragmentainerBreakValues( + result->FinalBreakAfter(), fieldset_content.Style().BreakAfter()); + container_builder_.SetPreviousBreakAfter(break_after); + } + + if (break_status == NGBreakStatus::kContinue) { + container_builder_.AddResult(*result, borders_with_legend_.StartOffset()); + intrinsic_block_size_ += + NGFragment(writing_mode_, result->PhysicalFragment()).BlockSize(); + container_builder_.SetHasSeenAllChildren(); + } + + return break_status; +} + +bool NGFieldsetLayoutAlgorithm::IsFragmentainerOutOfSpace( + LayoutUnit block_offset) const { + if (!ConstraintSpace().HasKnownFragmentainerBlockSize()) + return false; + return block_offset >= FragmentainerSpaceAtBfcStart(ConstraintSpace()); +} + +base::Optional<MinMaxSizes> NGFieldsetLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + MinMaxSizes sizes; + + // TODO(crbug.com/1011842): Need to consider content-size here. + bool apply_size_containment = Node().ShouldApplySizeContainment(); + // Size containment does not consider the legend for sizing. if (!apply_size_containment) { if (NGBlockNode legend = Node().GetRenderedLegend()) { @@ -170,42 +387,51 @@ base::Optional<MinMaxSize> NGFieldsetLayoutAlgorithm::ComputeMinMaxSize( // The fieldset content includes the fieldset padding (and any scrollbars), // while the legend is a regular child and doesn't. We may have a fieldset // without any content or legend, so add the padding here, on the outside. - sizes += ComputePadding(ConstraintSpace(), node_.Style()).InlineSum(); + sizes += ComputePadding(ConstraintSpace(), Style()).InlineSum(); // Size containment does not consider the content for sizing. if (!apply_size_containment) { if (NGBlockNode content = Node().GetFieldsetContent()) { - MinMaxSize content_minmax = + MinMaxSizes content_min_max_sizes = ComputeMinAndMaxContentContribution(Style(), content, input); - content_minmax += ComputeMinMaxMargins(Style(), content).InlineSum(); - sizes.Encompass(content_minmax); + content_min_max_sizes += + ComputeMinMaxMargins(Style(), content).InlineSum(); + sizes.Encompass(content_min_max_sizes); } } - sizes += ComputeBorders(ConstraintSpace(), node_).InlineSum(); + sizes += ComputeBorders(ConstraintSpace(), Style()).InlineSum(); return sizes; } const NGConstraintSpace NGFieldsetLayoutAlgorithm::CreateConstraintSpaceForLegend( NGBlockNode legend, - LogicalSize available_size) { + LogicalSize available_size, + LogicalSize percentage_size, + LayoutUnit block_offset) { NGConstraintSpaceBuilder builder( ConstraintSpace(), legend.Style().GetWritingMode(), /* is_new_fc */ true); SetOrthogonalFallbackInlineSizeIfNeeded(Style(), legend, &builder); builder.SetAvailableSize(available_size); - LogicalSize percentage_size = - CalculateChildPercentageSize(ConstraintSpace(), Node(), available_size); builder.SetPercentageResolutionSize(percentage_size); builder.SetIsShrinkToFit(legend.Style().LogicalWidth().IsAuto()); builder.SetTextDirection(legend.Style().Direction()); + + if (ConstraintSpace().HasBlockFragmentation()) { + SetupFragmentation(ConstraintSpace(), legend, block_offset, &builder, + /* is_new_fc */ true); + builder.SetEarlyBreakAppeal(container_builder_.BreakAppeal()); + } return builder.ToConstraintSpace(); } const NGConstraintSpace NGFieldsetLayoutAlgorithm::CreateConstraintSpaceForFieldsetContent( - LogicalSize padding_box_size) { + NGBlockNode fieldset_content, + LogicalSize padding_box_size, + LayoutUnit block_offset) { NGConstraintSpaceBuilder builder(ConstraintSpace(), ConstraintSpace().GetWritingMode(), /* is_new_fc */ true); @@ -213,6 +439,12 @@ NGFieldsetLayoutAlgorithm::CreateConstraintSpaceForFieldsetContent( builder.SetPercentageResolutionSize( ConstraintSpace().PercentageResolutionSize()); builder.SetIsFixedBlockSize(padding_box_size.block_size != kIndefiniteSize); + + if (ConstraintSpace().HasBlockFragmentation()) { + SetupFragmentation(ConstraintSpace(), fieldset_content, block_offset, + &builder, /* is_new_fc */ true); + builder.SetEarlyBreakAppeal(container_builder_.BreakAppeal()); + } return builder.ToConstraintSpace(); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h index df5a8d3538b..c375081737a 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h @@ -12,6 +12,7 @@ namespace blink { +enum class NGBreakStatus; class NGBlockBreakToken; class NGConstraintSpace; @@ -24,16 +25,63 @@ class CORE_EXPORT NGFieldsetLayoutAlgorithm scoped_refptr<const NGLayoutResult> Layout() override; - base::Optional<MinMaxSize> ComputeMinMaxSize( - const MinMaxSizeInput&) const override; + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const override; + + private: + NGBreakStatus LayoutChildren(); + NGBreakStatus LayoutLegend( + NGBlockNode& legend, + scoped_refptr<const NGBlockBreakToken> legend_break_token); + NGBreakStatus LayoutFieldsetContent( + NGBlockNode& fieldset_content, + scoped_refptr<const NGBlockBreakToken> content_break_token, + LogicalSize adjusted_padding_box_size, + LayoutUnit fragmentainer_block_offset, + bool has_legend); const NGConstraintSpace CreateConstraintSpaceForLegend( NGBlockNode legend, - LogicalSize available_size); + LogicalSize available_size, + LogicalSize percentage_size, + LayoutUnit block_offset); const NGConstraintSpace CreateConstraintSpaceForFieldsetContent( - LogicalSize padding_box_size); + NGBlockNode fieldset_content, + LogicalSize padding_box_size, + LayoutUnit block_offset); + + bool IsFragmentainerOutOfSpace(LayoutUnit block_offset) const; + + const WritingMode writing_mode_; const NGBoxStrut border_padding_; + NGBoxStrut borders_; + NGBoxStrut padding_; + + // The border and padding after adjusting to ensure that the leading border + // and padding are only applied to the first fragment. + NGBoxStrut adjusted_border_padding_; + + // The result of borders_ after positioning the fieldset's legend element. + NGBoxStrut borders_with_legend_; + + LayoutUnit block_start_padding_edge_; + LayoutUnit intrinsic_block_size_; + const LayoutUnit consumed_block_size_; + LogicalSize border_box_size_; + + // The legend may eat from the available content box block size. This + // represents the minimum block size needed by the border box to encompass + // the legend. + LayoutUnit minimum_border_box_block_size_; + + // If true, this indicates the block_start_padding_edge_ had changed from its + // initial value during the current layout pass. + bool block_start_padding_edge_adjusted_ = false; + + // If true, this indicates that the legend broke during the current layout + // pass. + bool legend_broke_ = false; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc index ab1718db0e4..36789003311 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc @@ -35,25 +35,26 @@ class NGFieldsetLayoutAlgorithmTest return NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); } - MinMaxSize RunComputeMinAndMax(NGBlockNode node) { + MinMaxSizes RunComputeMinMaxSizes(NGBlockNode node) { NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( WritingMode::kHorizontalTb, TextDirection::kLtr, - LogicalSize(LayoutUnit(), LayoutUnit())); + LogicalSize(LayoutUnit(), LayoutUnit()), false, + node.CreatesNewFormattingContext()); NGFragmentGeometry fragment_geometry = CalculateInitialMinMaxFragmentGeometry(space, node); NGFieldsetLayoutAlgorithm algorithm({node, fragment_geometry, space}); - MinMaxSizeInput input( + MinMaxSizesInput input( /* percentage_resolution_block_size */ (LayoutUnit())); - auto min_max = algorithm.ComputeMinMaxSize(input); + auto min_max = algorithm.ComputeMinMaxSizes(input); EXPECT_TRUE(min_max.has_value()); return *min_max; } - MinMaxSize RunComputeMinAndMax(const char* element_id) { + MinMaxSizes RunComputeMinMaxSizes(const char* element_id) { Element* element = GetDocument().getElementById(element_id); NGBlockNode node(ToLayoutBox(element->GetLayoutObject())); - return RunComputeMinAndMax(node); + return RunComputeMinMaxSizes(node); } String DumpFragmentTree(const NGPhysicalBoxFragment* fragment) { @@ -349,7 +350,7 @@ TEST_F(NGFieldsetLayoutAlgorithmTest, ZeroHeight) { offset:unplaced size:1000x53 offset:0,0 size:126x53 offset:13,0 size:30x30 - offset:3,30 size:120x0 + offset:3,30 size:120x20 offset:10,10 size:100x200 )DUMP"; EXPECT_EQ(expectation, dump); @@ -444,6 +445,46 @@ TEST_F(NGFieldsetLayoutAlgorithmTest, LegendPercentHeightQuirks) { EXPECT_EQ(expectation, dump); } +// This test makes sure that the fieldset content handles fieldset padding +// when the fieldset is expanded to encompass the legend. +TEST_F(NGFieldsetLayoutAlgorithmTest, FieldsetPaddingWithLegend) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:10px; width: 150px; height: 100px; + } + #legend { + padding:0px; margin:0; width: 50px; height: 120px; + } + #child { + width: 100px; height: 40px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + <div id="child"></div> + </fieldset> + )HTML"); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext()); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:170x140 + offset:10,0 size:50x120 + offset:0,120 size:170x20 + offset:10,10 size:100x40 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + TEST_F(NGFieldsetLayoutAlgorithmTest, MinMax) { SetBodyInnerHTML(R"HTML( <style> @@ -483,31 +524,1455 @@ TEST_F(NGFieldsetLayoutAlgorithmTest, MinMax) { </div> )HTML"); - MinMaxSize size; + MinMaxSizes sizes; + + sizes = RunComputeMinMaxSizes("fieldset1"); + EXPECT_EQ(sizes.min_size, LayoutUnit(26)); + EXPECT_EQ(sizes.max_size, LayoutUnit(26)); + + sizes = RunComputeMinMaxSizes("fieldset2"); + EXPECT_EQ(sizes.min_size, LayoutUnit(102)); + EXPECT_EQ(sizes.max_size, LayoutUnit(102)); + + sizes = RunComputeMinMaxSizes("fieldset3"); + EXPECT_EQ(sizes.min_size, LayoutUnit(102)); + EXPECT_EQ(sizes.max_size, LayoutUnit(126)); + + sizes = RunComputeMinMaxSizes("fieldset4"); + EXPECT_EQ(sizes.min_size, LayoutUnit(152)); + EXPECT_EQ(sizes.max_size, LayoutUnit(202)); + + sizes = RunComputeMinMaxSizes("fieldset5"); + EXPECT_EQ(sizes.min_size, LayoutUnit(152)); + EXPECT_EQ(sizes.max_size, LayoutUnit(176)); + + sizes = RunComputeMinMaxSizes("fieldset6"); + EXPECT_EQ(sizes.min_size, LayoutUnit(76)); + EXPECT_EQ(sizes.max_size, LayoutUnit(126)); +} + +// Tests that a fieldset won't fragment if it doesn't reach the fragmentation +// line. +TEST_F(NGFieldsetLayoutAlgorithmTest, NoFragmentation) { + SetBodyInnerHTML(R"HTML( + <style> + fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; height: 100px; + } + </style> + <fieldset id="fieldset"></fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + // We should only have one 176x126 fragment with no fragmentation. + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(176, 126), fragment->Size()); + ASSERT_FALSE(fragment->BreakToken()); +} + +// Tests that a fieldset will fragment if it reaches the fragmentation line. +TEST_F(NGFieldsetLayoutAlgorithmTest, SimpleFragmentation) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; height: 500px; + } + </style> + <fieldset id="fieldset"></fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + EXPECT_EQ(PhysicalSize(176, 200), fragment->Size()); + ASSERT_TRUE(fragment->BreakToken()); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(176, 200), fragment->Size()); + ASSERT_TRUE(fragment->BreakToken()); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + EXPECT_EQ(PhysicalSize(176, 126), fragment->Size()); + ASSERT_FALSE(fragment->BreakToken()); +} + +// Tests that a fieldset with no content or padding will fragment if it reaches +// the fragmentation line. +TEST_F(NGFieldsetLayoutAlgorithmTest, FragmentationNoPadding) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { margin:0; border:10px solid; padding:0px; width:100px; } + </style> + <fieldset id="fieldset"></fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(10); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:120x10 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:120x10 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with auto height will fragment when its content reaches +// the fragmentation line. +TEST_F(NGFieldsetLayoutAlgorithmTest, FieldsetContentFragmentationAutoHeight) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; + } + #child { + margin:0; width: 50px; height: 500px; + } + </style> + <fieldset id="fieldset"> + <div id="child"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:3,3 size:170x197 + offset:10,10 size:50x187 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:3,0 size:170x200 + offset:10,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x126 + offset:3,0 size:170x123 + offset:10,0 size:50x113 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with a set height will fragment when its content +// reaches the fragmentation line. +TEST_F(NGFieldsetLayoutAlgorithmTest, FieldsetContentFragmentation) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; height: 100px; + } + #child { + margin:0; width: 50px; height: 500px; + } + </style> + <fieldset id="fieldset"> + <div id="child"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x126 + offset:3,3 size:170x120 + offset:10,10 size:50x187 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x0 + offset:3,0 size:170x0 + offset:10,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x0 + offset:3,0 size:170x0 + offset:10,0 size:50x113 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with auto height will fragment when its legend reaches +// the fragmentation line. +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendFragmentationAutoHeight) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; + } + #legend { + padding:0px; margin:0; width: 50px; height: 500px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x123 + offset:13,0 size:50x100 + offset:3,100 size:170x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with a set height will fragment when its legend +// reaches the fragmentation line. The used height should also be extended to +// encompass the legend. +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendFragmentation) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; height: 100px; + } + #legend { + padding:0px; margin:0; width: 50px; height: 500px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x123 + offset:13,0 size:50x100 + offset:3,100 size:170x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with auto height will fragment when its legend/content +// reaches the fragmentation line. +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendAndContentFragmentationAutoHeight) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; + } + #legend { + padding:0px; margin:0; width: 50px; height: 500px; + } + #child { + margin:0; width: 100px; height: 200px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + <div id="child"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x100 + offset:3,100 size:170x100 + offset:10,10 size:100x90 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x123 + offset:3,0 size:170x120 + offset:10,0 size:100x110 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with a set height will fragment when its legend/content +// reaches the fragmentation line. +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendAndContentFragmentation) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:3px solid; margin:0; padding:10px; width: 150px; height: 100px; + } + #legend { + padding:0px; margin:0; width: 50px; height: 500px; + } + #child { + margin:0; width: 100px; height: 200px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + <div id="child"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(200); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x200 + offset:13,0 size:50x200 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x123 + offset:13,0 size:50x100 + offset:3,100 size:170x20 + offset:10,10 size:100x90 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:176x0 + offset:3,0 size:170x0 + offset:10,0 size:100x110 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests fragmentation when a legend's child content overflows. +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendFragmentationWithOverflow) { + SetBodyInnerHTML(R"HTML( + <style> + fieldset, legend { margin:0; border:none; padding:0; } + </style> + <fieldset id="fieldset"> + <legend style="height:30px;"> + <div style="width:55px; height:150px;"></div> + </legend> + <div style="width:44px; height:150px;"></div> + </div> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:55x30 + offset:0,0 size:55x100 + offset:0,30 size:1000x70 + offset:0,0 size:44x70 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x80 + offset:0,0 size:55x0 + offset:0,0 size:55x50 + offset:0,0 size:1000x80 + offset:0,0 size:44x80 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that fragmentation works as expected when the fieldset content has a +// negative margin block start. +TEST_F(NGFieldsetLayoutAlgorithmTest, + LegendAndContentFragmentationNegativeMargin) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 150px; height: 100px; + } + #legend { + padding:0px; margin:0; width: 50px; height: 100px; + } + #child { + margin-top: -20px; width: 100px; height: 40px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + <div id="child"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:150x100 + offset:0,0 size:50x100 + offset:0,100 size:150x0 + offset:0,-20 size:100x20 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:150x0 + offset:0,0 size:150x0 + offset:0,0 size:100x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, OverflowedLegend) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; height: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend" style="width:75%; height:60px;"> + <div id="grandchild1" style="width:50px; height:120px;"></div> + <div id="grandchild2" style="width:40px; height:20px;"></div> + </legend> + <div id="child" style="width:85%; height:10px;"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x100 + offset:0,0 size:75x60 + offset:0,0 size:50x100 + offset:0,60 size:100x40 + offset:0,0 size:85x10 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x0 + offset:0,0 size:75x0 + offset:0,0 size:50x20 + offset:0,20 size:40x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, OverflowedFieldsetContent) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; height: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend" style="width:75%; height:10px;"> + <div style="width:50px; height:220px;"></div> + </legend> + <div style="width:85%; height:10px;"></div> + <div id="child" style="width:65%; height:10px;"> + <div style="width:51px; height:220px;"></div> + </div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x100 + offset:0,0 size:75x10 + offset:0,0 size:50x100 + offset:0,10 size:100x90 + offset:0,0 size:85x10 + offset:0,10 size:65x10 + offset:0,0 size:51x80 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x0 + offset:0,0 size:75x0 + offset:0,0 size:50x100 + offset:0,0 size:100x0 + offset:0,0 size:65x0 + offset:0,0 size:51x100 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x0 + offset:0,0 size:75x0 + offset:0,0 size:50x20 + offset:0,0 size:100x0 + offset:0,0 size:65x0 + offset:0,0 size:51x40 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, BreakInsideAvoid) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; height: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend" style="width:10px; height:50px;"></legend> + <div style="break-inside:avoid; width:20px; height:70px;"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x100 + offset:0,0 size:10x50 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x0 + offset:0,0 size:100x0 + offset:0,0 size:20x70 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, BreakInsideAvoidTallBlock) { + // The block that has break-inside:avoid is too tall to fit in one + // fragmentainer. So a break is unavoidable. Let's check that: + // 1. The block is still shifted to the start of the next fragmentainer + // 2. We give up shifting it any further (would cause infinite an loop) + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; height: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend" style="width:10px; height:50px;"></legend> + <div style="break-inside:avoid; width:20px; height:170px;"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x100 + offset:0,0 size:10x50 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x0 + offset:0,0 size:100x0 + offset:0,0 size:20x100 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x0 + offset:0,0 size:100x0 + offset:0,0 size:20x70 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendBreakInsideAvoid) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; height: 50px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <div id="container"> + <div style="width:20px; height:50px;"></div> + <fieldset id="fieldset"> + <legend id="legend" style="break-inside:avoid; width:10px; height:60px;"> + </legend> + </fieldset> + </div> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:20x50 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x60 + offset:0,0 size:100x60 + offset:0,0 size:10x60 + offset:0,60 size:100x0 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, BreakBeforeAvoid) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <div id="container"> + <div style="width:20px; height:50px;"></div> + <fieldset id="fieldset"> + <legend id="legend" style="width:10px; height:25px;"></legend> + <div style="width:30px; height:25px;"></div> + <div style="break-before:avoid; width:15px; height:25px;"></div> + </fieldset> + </div> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x75 + offset:0,0 size:20x50 + offset:0,50 size:100x25 + offset:0,0 size:10x25 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x50 + offset:0,0 size:100x50 + offset:0,0 size:100x50 + offset:0,0 size:30x25 + offset:0,25 size:15x25 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendBreakBeforeAvoid) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:10px solid; margin:0; padding:0px; width: 100px; + } + #legend { + padding:0px; margin:10px; width:10px; height:25px; + } + </style> + <div id="container"> + <div style="width:20px; height:90px;"></div> + <fieldset id="fieldset"> + <legend id="legend" style="break-before:avoid;"></legend> + </fieldset> + </div> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:20x90 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x55 + offset:0,0 size:120x55 + offset:20,10 size:10x25 + offset:10,45 size:100x0 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, BreakAfterAvoid) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <div id="container"> + <div style="width:20px; height:50px;"></div> + <fieldset id="fieldset"> + <legend id="legend" style="width:10px; height:25px;"></legend> + <div style="break-after:avoid; width:30px; height:25px;"></div> + <div style="width:15px; height:25px;"></div> + </fieldset> + </div> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x75 + offset:0,0 size:20x50 + offset:0,50 size:100x25 + offset:0,0 size:10x25 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x50 + offset:0,0 size:100x50 + offset:0,0 size:100x50 + offset:0,0 size:30x25 + offset:0,25 size:15x25 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, LegendBreakAfterAvoid) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:0px solid; margin:0; padding:0px; width: 100px; + } + #legend { + padding:0px; margin:0px; width:10px; height:50px; + } + </style> + <div id="container"> + <div style="width:20px; height:50px;"></div> + <fieldset id="fieldset"> + <legend id="legend" style="break-after:avoid;"></legend> + <div style="width:15px; height:25px;"></div> + </fieldset> + </div> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("container"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x100 + offset:0,0 size:20x50 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:1000x75 + offset:0,0 size:100x75 + offset:0,0 size:10x50 + offset:0,50 size:100x25 + offset:0,0 size:15x25 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, MarginTopPastEndOfFragmentainer) { + // A block whose border box would start past the end of the current + // fragmentainer should start exactly at the start of the next fragmentainer, + // discarding what's left of the margin. + // https://www.w3.org/TR/css-break-3/#break-margins + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; height: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend" style="margin-top:60px; width:10px; height:20px;"></legend> + <div style="width:20px; height:20px;"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(50); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x50 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x50 + offset:0,0 size:10x20 + offset:0,20 size:100x30 + offset:0,0 size:20x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +TEST_F(NGFieldsetLayoutAlgorithmTest, MarginBottomPastEndOfFragmentainer) { + // A block whose border box would start past the end of the current + // fragmentainer should start exactly at the start of the next fragmentainer, + // discarding what's left of the margin. + // https://www.w3.org/TR/css-break-3/#break-margins + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { + border:none; margin:0; padding:0px; width: 100px; height: 100px; + } + #legend { + padding:0px; margin:0px; + } + </style> + <fieldset id="fieldset"> + <legend id="legend" style="margin-bottom:20px; height:90px;"></legend> + <div style="width:20px; height:20px;"></div> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(100); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x100 + offset:0,0 size:0x90 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_FALSE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:100x0 + offset:0,0 size:100x0 + offset:0,0 size:20x20 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with a large border and a small legend fragment +// correctly. In this case, the legend block offset is not adjusted because the +// legend is broken across multiple fragments. +TEST_F(NGFieldsetLayoutAlgorithmTest, SmallLegendLargeBorderFragmentation) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { margin:0; border:60px solid; padding:0px; width:100px; + height:10px; } + #legend { padding:0; width:10px; height:50px; } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(40); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 + offset:60,0 size:10x40 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 + offset:60,0 size:10x10 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + // TODO(almaher): There should be no break token here. In this case the bottom + // border never reduces in size, causing fragmentation to continue infinitely. + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x10 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with a large border and a small legend fragment +// correctly. In this case, the legend block offset is adjusted because the +// legend fits inside the first fragment. +TEST_F(NGFieldsetLayoutAlgorithmTest, SmallerLegendLargeBorderFragmentation) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { margin:0; border:60px solid; padding:0px; width:100px; + height:10px; } + #legend { padding:0; width:10px; height:5px; } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(40); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 + offset:60,27.5 size:10x5 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 +)DUMP"; + EXPECT_EQ(expectation, dump); + + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + // TODO(almaher): There should be no break token here. In this case the bottom + // border never reduces in size, causing fragmentation to continue infinitely. + ASSERT_TRUE(fragment->BreakToken()); + + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x10 +)DUMP"; + EXPECT_EQ(expectation, dump); +} + +// Tests that a fieldset with a large border and a small legend fragment +// correctly. In this case, the legend block offset is not adjusted because the +// legend breaks after attempting to adjust the offset. +TEST_F(NGFieldsetLayoutAlgorithmTest, SmallerLegendLargeBorderWithBreak) { + SetBodyInnerHTML(R"HTML( + <style> + #fieldset { margin:0; border:60px solid; padding:0px; width:100px; + height:10px; } + #legend { padding:0; width:10px; height:5px; margin-top:16px; } + </style> + <fieldset id="fieldset"> + <legend id="legend"></legend> + </fieldset> + )HTML"); + + LayoutUnit kFragmentainerSpaceAvailable(40); + + NGBlockNode node(ToLayoutBox(GetLayoutObjectByElementId("fieldset"))); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize), false, + node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); + + scoped_refptr<const NGPhysicalBoxFragment> fragment = + NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(node, space); + ASSERT_TRUE(fragment->BreakToken()); + + String dump = DumpFragmentTree(fragment.get()); + String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 + offset:60,16 size:10x5 +)DUMP"; + EXPECT_EQ(expectation, dump); - size = RunComputeMinAndMax("fieldset1"); - EXPECT_EQ(size.min_size, LayoutUnit(26)); - EXPECT_EQ(size.max_size, LayoutUnit(26)); + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); - size = RunComputeMinAndMax("fieldset2"); - EXPECT_EQ(size.min_size, LayoutUnit(102)); - EXPECT_EQ(size.max_size, LayoutUnit(102)); + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 +)DUMP"; + EXPECT_EQ(expectation, dump); - size = RunComputeMinAndMax("fieldset3"); - EXPECT_EQ(size.min_size, LayoutUnit(102)); - EXPECT_EQ(size.max_size, LayoutUnit(126)); + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + ASSERT_TRUE(fragment->BreakToken()); - size = RunComputeMinAndMax("fieldset4"); - EXPECT_EQ(size.min_size, LayoutUnit(152)); - EXPECT_EQ(size.max_size, LayoutUnit(202)); + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x40 +)DUMP"; + EXPECT_EQ(expectation, dump); - size = RunComputeMinAndMax("fieldset5"); - EXPECT_EQ(size.min_size, LayoutUnit(152)); - EXPECT_EQ(size.max_size, LayoutUnit(176)); + fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm( + node, space, fragment->BreakToken()); + // TODO(almaher): There should be no break token here. In this case the bottom + // border never reduces in size, causing fragmentation to continue infinitely. + ASSERT_TRUE(fragment->BreakToken()); - size = RunComputeMinAndMax("fieldset6"); - EXPECT_EQ(size.min_size, LayoutUnit(76)); - EXPECT_EQ(size.max_size, LayoutUnit(126)); + dump = DumpFragmentTree(fragment.get()); + expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. + offset:unplaced size:220x10 +)DUMP"; + EXPECT_EQ(expectation, dump); } } // anonymous namespace diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.cc new file mode 100644 index 00000000000..350d605af11 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.cc @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.h" + +namespace blink { + +NGFlexChildIterator::NGFlexChildIterator(const NGBlockNode node) { + bool is_deprecated_webkit_box = node.Style().IsDeprecatedWebkitBox(); + int initial_order = is_deprecated_webkit_box + ? ComputedStyleInitialValues::InitialBoxOrdinalGroup() + : ComputedStyleInitialValues::InitialOrder(); + bool needs_sort = false; + + // Collect all our children, and order them by either their + // -webkit-box-ordinal-group/order property. + for (NGLayoutInputNode child = node.FirstChild(); child; + child = child.NextSibling()) { + int order = is_deprecated_webkit_box ? child.Style().BoxOrdinalGroup() + : child.Style().Order(); + needs_sort |= order != initial_order; + children_.emplace_back(To<NGBlockNode>(child), order); + } + + // We only need to sort this vector if we encountered a non-initial + // -webkit-box-ordinal-group/order property. + if (needs_sort) { + std::stable_sort(children_.begin(), children_.end(), + [](const ChildWithOrder& c1, const ChildWithOrder& c2) { + return c1.order < c2.order; + }); + } + + iterator_ = children_.begin(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.h new file mode 100644 index 00000000000..609483fc790 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.h @@ -0,0 +1,53 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FLEX_CHILD_ITERATOR_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FLEX_CHILD_ITERATOR_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +// A utility class for flex layout which given the current node will iterate +// through its children. +// +// TODO(layout-dev): Once flex layout supports NG-fragmentation this will need +// to be updated to accept a break-token. +// +// This class does not handle modifications to its arguments after it has been +// constructed. +class CORE_EXPORT NGFlexChildIterator { + STACK_ALLOCATED(); + + public: + NGFlexChildIterator(const NGBlockNode node); + + // Returns the next block node which should be laid out. + NGBlockNode NextChild() { + if (iterator_ == children_.end()) + return nullptr; + + return (*iterator_++).child; + } + + struct ChildWithOrder { + DISALLOW_NEW(); + ChildWithOrder(NGBlockNode child, int order) : child(child), order(order) {} + NGBlockNode child; + int order; + }; + + private: + Vector<ChildWithOrder, 4> children_; + Vector<ChildWithOrder, 4>::const_iterator iterator_; +}; + +} // namespace blink + +WTF_ALLOW_MOVE_AND_INIT_WITH_MEM_FUNCTIONS( + blink::NGFlexChildIterator::ChildWithOrder) + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FLEX_CHILD_ITERATOR_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc index 9bbdcdbedba..21c01f8a5be 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc @@ -9,12 +9,16 @@ #include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/layout_flexible_box.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" +#include "third_party/blink/renderer/core/layout/ng/ng_flex_child_iterator.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" +#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/platform/wtf/vector.h" namespace blink { @@ -27,10 +31,18 @@ NGFlexLayoutAlgorithm::NGFlexLayoutAlgorithm( border_scrollbar_padding_(border_padding_ + params.fragment_geometry.scrollbar), is_column_(Style().ResolvedIsColumnFlexDirection()), - is_horizontal_flow_(FlexLayoutAlgorithm::IsHorizontalFlow(Style())) { + is_horizontal_flow_(FlexLayoutAlgorithm::IsHorizontalFlow(Style())), + is_cross_size_definite_(IsContainerCrossSizeDefinite()) { container_builder_.SetIsNewFormattingContext( params.space.IsNewFormattingContext()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); + + border_box_size_ = container_builder_.InitialBorderBoxSize(); + content_box_size_ = + ShrinkAvailableSize(border_box_size_, border_scrollbar_padding_); + child_percentage_size_ = CalculateChildPercentageSize( + ConstraintSpace(), Node(), content_box_size_); + algorithm_.emplace(&Style(), MainAxisContentExtent(LayoutUnit::Max())); } bool NGFlexLayoutAlgorithm::MainAxisIsInlineAxis( @@ -40,26 +52,101 @@ bool NGFlexLayoutAlgorithm::MainAxisIsInlineAxis( } LayoutUnit NGFlexLayoutAlgorithm::MainAxisContentExtent( - LayoutUnit sum_hypothetical_main_size) { + LayoutUnit sum_hypothetical_main_size) const { if (Style().ResolvedIsColumnFlexDirection()) { return ComputeBlockSizeForFragment( ConstraintSpace(), Style(), border_padding_, - sum_hypothetical_main_size + (border_padding_).BlockSum()) - + sum_hypothetical_main_size + + border_scrollbar_padding_.BlockSum()) - border_scrollbar_padding_.BlockSum(); } return content_box_size_.inline_size; } +namespace { + +enum AxisEdge { kStart, kCenter, kEnd }; + +// Maps the resolved justify-content value to a static-position edge. +AxisEdge MainAxisStaticPositionEdge(const ComputedStyle& style, + bool is_column) { + const StyleContentAlignmentData justify = + FlexLayoutAlgorithm::ResolvedJustifyContent(style); + const ContentPosition content_position = justify.GetPosition(); + bool is_reverse_flex = is_column + ? style.ResolvedIsColumnReverseFlexDirection() + : style.ResolvedIsRowReverseFlexDirection(); + + if (content_position == ContentPosition::kFlexEnd) + return is_reverse_flex ? AxisEdge::kStart : AxisEdge::kEnd; + + if (content_position == ContentPosition::kCenter || + justify.Distribution() == ContentDistributionType::kSpaceAround || + justify.Distribution() == ContentDistributionType::kSpaceEvenly) + return AxisEdge::kCenter; + + return is_reverse_flex ? AxisEdge::kEnd : AxisEdge::kStart; +} + +// Maps the resolved alignment value to a static-position edge. +AxisEdge CrossAxisStaticPositionEdge(const ComputedStyle& style, + const ComputedStyle& child_style) { + ItemPosition alignment = + FlexLayoutAlgorithm::AlignmentForChild(style, child_style); + bool is_wrap_reverse = style.FlexWrap() == EFlexWrap::kWrapReverse; + + if (alignment == ItemPosition::kFlexEnd) + return is_wrap_reverse ? AxisEdge::kStart : AxisEdge::kEnd; + + if (alignment == ItemPosition::kCenter) + return AxisEdge::kCenter; + + return is_wrap_reverse ? AxisEdge::kEnd : AxisEdge::kStart; +} + +} // namespace + void NGFlexLayoutAlgorithm::HandleOutOfFlowPositioned(NGBlockNode child) { - // TODO(dgrogan): There's stuff from - // https://www.w3.org/TR/css-flexbox-1/#abspos-items that isn't done here. - // Specifically, neither rtl nor alignment is handled here, at least. - // Look at LayoutFlexibleBox::PrepareChildForPositionedLayout and - // SetStaticPositionForPositionedLayout to see how to statically position - // this. - container_builder_.AddOutOfFlowChildCandidate( - child, {border_scrollbar_padding_.inline_start, - border_scrollbar_padding_.block_start}); + AxisEdge main_axis_edge = MainAxisStaticPositionEdge(Style(), is_column_); + AxisEdge cross_axis_edge = + CrossAxisStaticPositionEdge(Style(), child.Style()); + + AxisEdge inline_axis_edge = is_column_ ? cross_axis_edge : main_axis_edge; + AxisEdge block_axis_edge = is_column_ ? main_axis_edge : cross_axis_edge; + + using InlineEdge = NGLogicalStaticPosition::InlineEdge; + using BlockEdge = NGLogicalStaticPosition::BlockEdge; + + InlineEdge inline_edge; + BlockEdge block_edge; + LogicalOffset offset(border_scrollbar_padding_.inline_start, + border_scrollbar_padding_.block_start); + + // Determine the static-position based off the axis-edge. + if (inline_axis_edge == AxisEdge::kStart) { + inline_edge = InlineEdge::kInlineStart; + } else if (inline_axis_edge == AxisEdge::kCenter) { + inline_edge = InlineEdge::kInlineCenter; + offset.inline_offset += content_box_size_.inline_size / 2; + } else { + inline_edge = InlineEdge::kInlineEnd; + offset.inline_offset += content_box_size_.inline_size; + } + + // We may not know the final block-size of the fragment yet. This will be + // adjusted within the |NGContainerFragmentBuilder| once set. + if (block_axis_edge == AxisEdge::kStart) { + block_edge = BlockEdge::kBlockStart; + } else if (block_axis_edge == AxisEdge::kCenter) { + block_edge = BlockEdge::kBlockCenter; + offset.block_offset -= border_scrollbar_padding_.BlockSum() / 2; + } else { + block_edge = BlockEdge::kBlockEnd; + offset.block_offset -= border_scrollbar_padding_.BlockSum(); + } + + container_builder_.AddOutOfFlowChildCandidate(child, offset, inline_edge, + block_edge); } bool NGFlexLayoutAlgorithm::IsColumnContainerMainSizeDefinite() const { @@ -110,13 +197,27 @@ bool NGFlexLayoutAlgorithm::DoesItemStretch(const NGBlockNode& child) const { ItemPosition::kStretch; } +bool NGFlexLayoutAlgorithm::IsItemFlexBasisDefinite( + const NGBlockNode& child) const { + const Length& flex_basis = child.Style().FlexBasis(); + DCHECK(!flex_basis.IsAuto()) + << "This is never called with flex_basis.IsAuto, but it'd be trivial to " + "support."; + if (!is_column_) + return true; + return !BlockLengthUnresolvable(BuildSpaceForFlexBasis(child), flex_basis, + LengthResolvePhase::kLayout); +} + // This behavior is under discussion: the item's pre-flexing main size // definiteness may no longer imply post-flexing definiteness. // TODO(dgrogan): Have https://crbug.com/1003506 and // https://github.com/w3c/csswg-drafts/issues/4305 been resolved yet? bool NGFlexLayoutAlgorithm::IsItemMainSizeDefinite( const NGBlockNode& child) const { - DCHECK(is_column_); + DCHECK(is_column_) + << "This method doesn't work with row flexboxes because we assume " + "main size is block size when we call BlockLengthUnresolvable."; // Inline sizes are always definite. // TODO(dgrogan): The relevant tests, the last two cases in // css/css-flexbox/percentage-heights-003.html passed even without this, so it @@ -126,9 +227,8 @@ bool NGFlexLayoutAlgorithm::IsItemMainSizeDefinite( // We need a constraint space for the child to determine resolvability and the // space for flex-basis is sufficient, even though it has some unnecessary // stuff (ShrinkToFit and fixed cross sizes). - NGConstraintSpace child_space = - BuildConstraintSpaceForDeterminingFlexBasis(child); - return !BlockLengthUnresolvable(child_space, child.Style().LogicalHeight(), + return !BlockLengthUnresolvable(BuildSpaceForFlexBasis(child), + child.Style().LogicalHeight(), LengthResolvePhase::kLayout); } @@ -143,9 +243,8 @@ bool NGFlexLayoutAlgorithm::IsItemCrossAxisLengthDefinite( if (!MainAxisIsInlineAxis(child)) return true; // If we get here, cross axis is block axis. - return !BlockLengthUnresolvable( - BuildConstraintSpaceForDeterminingFlexBasis(child), length, - LengthResolvePhase::kLayout); + return !BlockLengthUnresolvable(BuildSpaceForFlexBasis(child), length, + LengthResolvePhase::kLayout); } bool NGFlexLayoutAlgorithm::DoesItemCrossSizeComputeToAuto( @@ -197,139 +296,157 @@ bool NGFlexLayoutAlgorithm::ShouldItemShrinkToFit( bool NGFlexLayoutAlgorithm::WillChildCrossSizeBeContainerCrossSize( const NGBlockNode& child) const { - return !algorithm_->IsMultiline() && IsContainerCrossSizeDefinite() && + return !algorithm_->IsMultiline() && is_cross_size_definite_ && DoesItemStretch(child); } -NGConstraintSpace -NGFlexLayoutAlgorithm::BuildConstraintSpaceForDeterminingFlexBasis( - const NGBlockNode& flex_item) const { +double NGFlexLayoutAlgorithm::GetMainOverCrossAspectRatio( + const NGBlockNode& child) const { + DCHECK(child.HasAspectRatio()); + LogicalSize aspect_ratio = child.GetAspectRatio(); + + DCHECK_GT(aspect_ratio.inline_size, 0); + DCHECK_GT(aspect_ratio.block_size, 0); + + double ratio = + aspect_ratio.inline_size.ToDouble() / aspect_ratio.block_size.ToDouble(); + // Multiplying by ratio will take something in the item's block axis and + // convert it to the inline axis. We want to convert from cross size to main + // size. If block axis and cross axis are the same, then we already have what + // we need. Otherwise we need to use the reciprocal. + if (!MainAxisIsInlineAxis(child)) + ratio = 1 / ratio; + return ratio; +} + +namespace { + +LayoutUnit CalculateFixedCrossSize(LayoutUnit available_size, + const MinMaxSizes& cross_axis_min_max, + LayoutUnit margin_sum) { + return cross_axis_min_max.ClampSizeToMinAndMax(available_size - margin_sum); +} + +} // namespace + +NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForIntrinsicBlockSize( + const NGBlockNode& flex_item, + const NGPhysicalBoxStrut& physical_margins, + const MinMaxSizes& cross_axis_min_max) const { const ComputedStyle& child_style = flex_item.Style(); NGConstraintSpaceBuilder space_builder(ConstraintSpace(), child_style.GetWritingMode(), /* is_new_fc */ true); SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item, &space_builder); + space_builder.SetCacheSlot(NGCacheSlot::kMeasure); + space_builder.SetIsPaintedAtomically(true); - if (ShouldItemShrinkToFit(flex_item)) + NGBoxStrut margins = physical_margins.ConvertToLogical( + ConstraintSpace().GetWritingMode(), Style().Direction()); + LogicalSize child_available_size = content_box_size_; + if (ShouldItemShrinkToFit(flex_item)) { space_builder.SetIsShrinkToFit(true); - if (WillChildCrossSizeBeContainerCrossSize(flex_item)) { + } else if (WillChildCrossSizeBeContainerCrossSize(flex_item)) { if (is_column_) { space_builder.SetIsFixedInlineSize(true); + child_available_size.inline_size = + CalculateFixedCrossSize(child_available_size.inline_size, + cross_axis_min_max, margins.InlineSum()); } else { space_builder.SetIsFixedBlockSize(true); DCHECK_NE(content_box_size_.block_size, kIndefiniteSize); + child_available_size.block_size = + CalculateFixedCrossSize(child_available_size.block_size, + cross_axis_min_max, margins.BlockSum()); } } + space_builder.SetNeedsBaseline( + ConstraintSpace().NeedsBaseline() || + FlexLayoutAlgorithm::AlignmentForChild(Style(), child_style) == + ItemPosition::kBaseline); + + // For determining the intrinsic block-size we make %-block-sizes resolve + // against an indefinite size. + LogicalSize child_percentage_size = child_percentage_size_; + if (is_column_) + child_percentage_size.block_size = kIndefiniteSize; + + space_builder.SetAvailableSize(child_available_size); + space_builder.SetPercentageResolutionSize(child_percentage_size); + // TODO(dgrogan): The SetReplacedPercentageResolutionSize calls in this file + // may be untested. Write a test or determine why they're unnecessary. + space_builder.SetReplacedPercentageResolutionSize(child_percentage_size); + space_builder.SetTextDirection(child_style.Direction()); + return space_builder.ToConstraintSpace(); +} + +NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForFlexBasis( + const NGBlockNode& flex_item) const { + NGConstraintSpaceBuilder space_builder(ConstraintSpace(), + flex_item.Style().GetWritingMode(), + /* is_new_fc */ true); + SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item, &space_builder); + + // This space is only used for resolving lengths, not for layout. We only + // need the available and percentage sizes. space_builder.SetAvailableSize(content_box_size_); space_builder.SetPercentageResolutionSize(child_percentage_size_); - space_builder.SetTextDirection(child_style.Direction()); + space_builder.SetReplacedPercentageResolutionSize(child_percentage_size_); return space_builder.ToConstraintSpace(); } void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { - for (NGLayoutInputNode generic_child = Node().FirstChild(); generic_child; - generic_child = generic_child.NextSibling()) { - auto child = To<NGBlockNode>(generic_child); + NGFlexChildIterator iterator(Node()); + for (NGBlockNode child = iterator.NextChild(); child; + child = iterator.NextChild()) { if (child.IsOutOfFlowPositioned()) { HandleOutOfFlowPositioned(child); continue; } const ComputedStyle& child_style = child.Style(); - NGConstraintSpace child_space = - BuildConstraintSpaceForDeterminingFlexBasis(child); + NGConstraintSpace flex_basis_space = BuildSpaceForFlexBasis(child); + + NGPhysicalBoxStrut physical_child_margins = + ComputePhysicalMargins(flex_basis_space, child_style); NGBoxStrut border_padding_in_child_writing_mode = - ComputeBorders(child_space, child) + - ComputePadding(child_space, child_style); - NGBoxStrut border_scrollbar_padding_in_child_writing_mode = - border_padding_in_child_writing_mode + - ComputeScrollbars(child_space, child); + ComputeBorders(flex_basis_space, child_style) + + ComputePadding(flex_basis_space, child_style); NGPhysicalBoxStrut physical_border_padding( border_padding_in_child_writing_mode.ConvertToPhysical( child_style.GetWritingMode(), child_style.Direction())); - NGPhysicalBoxStrut physical_border_scrollbar_padding( - border_scrollbar_padding_in_child_writing_mode.ConvertToPhysical( - child_style.GetWritingMode(), child_style.Direction())); LayoutUnit main_axis_border_padding = is_horizontal_flow_ ? physical_border_padding.HorizontalSum() : physical_border_padding.VerticalSum(); - LayoutUnit main_axis_border_scrollbar_padding = - is_horizontal_flow_ ? physical_border_scrollbar_padding.HorizontalSum() - : physical_border_scrollbar_padding.VerticalSum(); - - // We want the child's min/max size in its writing mode, not ours. We'll - // only ever use it if the child's inline axis is our main axis. - MinMaxSizeInput input( - /* percentage_resolution_block_size */ content_box_size_.block_size); - MinMaxSize intrinsic_sizes_border_box = child.ComputeMinMaxSize( - child_style.GetWritingMode(), input, &child_space); - // TODO(dgrogan): Don't layout every time, just when you need to. - // Use ChildHasIntrinsicMainAxisSize as a guide. - scoped_refptr<const NGLayoutResult> layout_result = - child.Layout(child_space, nullptr /*break token*/); - NGFragment fragment_in_child_writing_mode( - child_style.GetWritingMode(), layout_result->PhysicalFragment()); - - LayoutUnit flex_base_border_box; - const Length& specified_length_in_main_axis = - is_horizontal_flow_ ? child_style.Width() : child_style.Height(); - const Length& flex_basis = child_style.FlexBasis(); - // TODO(dgrogan): Generalize IsAuto: See the <'width'> section of - // https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis - // and https://drafts.csswg.org/css-flexbox/#flex-basis-property, which says - // that if a flex-basis value would resolve to auto (but not literally auto) - // we should interpret it as flex-basis:content. - if (flex_basis.IsAuto() && specified_length_in_main_axis.IsAuto()) { - if (MainAxisIsInlineAxis(child)) - flex_base_border_box = intrinsic_sizes_border_box.max_size; - else - flex_base_border_box = fragment_in_child_writing_mode.BlockSize(); - } else { - // TODO(dgrogan): Check for definiteness. - // This block covers case A in - // https://drafts.csswg.org/css-flexbox/#algo-main-item. - const Length& length_to_resolve = - flex_basis.IsAuto() ? specified_length_in_main_axis : flex_basis; - DCHECK(!length_to_resolve.IsAuto()); - - if (MainAxisIsInlineAxis(child)) { - flex_base_border_box = ResolveMainInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, - intrinsic_sizes_border_box, length_to_resolve); - } else { - // Flex container's main axis is in child's block direction. Child's - // flex basis is in child's block direction. - flex_base_border_box = ResolveMainBlockLength( - child_space, child_style, border_padding_in_child_writing_mode, - length_to_resolve, fragment_in_child_writing_mode.BlockSize(), - LengthResolvePhase::kLayout); + LayoutUnit cross_axis_border_padding = + is_horizontal_flow_ ? physical_border_padding.VerticalSum() + : physical_border_padding.HorizontalSum(); + + base::Optional<MinMaxSizes> min_max_size; + auto MinMaxSizesFunc = [&]() -> MinMaxSizes { + if (!min_max_size) { + if (child.Style().OverflowBlockDirection() == EOverflow::kAuto) { + // Ensure this child has been laid out so its auto scrollbars are + // included in its intrinsic sizes. + child.Layout(flex_basis_space); + } + // We want the child's min/max size in its writing mode, not ours. + // We'll only ever use it if the child's inline axis is our main axis. + min_max_size = child.ComputeMinMaxSizes( + child_style.GetWritingMode(), + MinMaxSizesInput(content_box_size_.block_size), &flex_basis_space); } - } + return *min_max_size; + }; - // Spec calls this "flex base size" - // https://www.w3.org/TR/css-flexbox-1/#algo-main-item - // Blink's FlexibleBoxAlgorithm expects it to be content + scrollbar widths, - // but no padding or border. - LayoutUnit flex_base_content_size = - flex_base_border_box - main_axis_border_padding; - - NGPhysicalBoxStrut physical_child_margins = - ComputePhysicalMargins(child_space, child_style); - // Set margin because FlexibleBoxAlgorithm reads it from legacy. - child.GetLayoutBox()->SetMargin(physical_child_margins); - - LayoutUnit main_axis_margin = is_horizontal_flow_ - ? physical_child_margins.HorizontalSum() - : physical_child_margins.VerticalSum(); - - MinMaxSize min_max_sizes_in_main_axis_direction{LayoutUnit(), - LayoutUnit::Max()}; - MinMaxSize min_max_sizes_in_cross_axis_direction{LayoutUnit(), + MinMaxSizes min_max_sizes_in_main_axis_direction{main_axis_border_padding, LayoutUnit::Max()}; + MinMaxSizes min_max_sizes_in_cross_axis_direction{LayoutUnit(), + LayoutUnit::Max()}; const Length& max_property_in_main_axis = is_horizontal_flow_ ? child.Style().MaxWidth() : child.Style().MaxHeight(); @@ -341,112 +458,282 @@ void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() { : child.Style().MinWidth(); if (MainAxisIsInlineAxis(child)) { min_max_sizes_in_main_axis_direction.max_size = ResolveMaxInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, - intrinsic_sizes_border_box, max_property_in_main_axis, + flex_basis_space, child_style, border_padding_in_child_writing_mode, + MinMaxSizesFunc, max_property_in_main_axis, LengthResolvePhase::kLayout); - min_max_sizes_in_cross_axis_direction.max_size = - ResolveMaxBlockLength(child_space, child_style, - border_scrollbar_padding_in_child_writing_mode, - max_property_in_cross_axis, - fragment_in_child_writing_mode.BlockSize(), - LengthResolvePhase::kLayout); - min_max_sizes_in_cross_axis_direction.min_size = - ResolveMinBlockLength(child_space, child_style, - border_scrollbar_padding_in_child_writing_mode, - min_property_in_cross_axis, - fragment_in_child_writing_mode.BlockSize(), - LengthResolvePhase::kLayout); + min_max_sizes_in_cross_axis_direction.max_size = ResolveMaxBlockLength( + flex_basis_space, child_style, border_padding_in_child_writing_mode, + max_property_in_cross_axis, LengthResolvePhase::kLayout); + min_max_sizes_in_cross_axis_direction.min_size = ResolveMinBlockLength( + flex_basis_space, child_style, border_padding_in_child_writing_mode, + min_property_in_cross_axis, LengthResolvePhase::kLayout); } else { min_max_sizes_in_main_axis_direction.max_size = ResolveMaxBlockLength( - child_space, child_style, border_padding_in_child_writing_mode, - max_property_in_main_axis, fragment_in_child_writing_mode.BlockSize(), - LengthResolvePhase::kLayout); + flex_basis_space, child_style, border_padding_in_child_writing_mode, + max_property_in_main_axis, LengthResolvePhase::kLayout); min_max_sizes_in_cross_axis_direction.max_size = ResolveMaxInlineLength( - child_space, child_style, - border_scrollbar_padding_in_child_writing_mode, - intrinsic_sizes_border_box, max_property_in_cross_axis, + flex_basis_space, child_style, border_padding_in_child_writing_mode, + MinMaxSizesFunc, max_property_in_cross_axis, LengthResolvePhase::kLayout); min_max_sizes_in_cross_axis_direction.min_size = ResolveMinInlineLength( - child_space, child_style, - border_scrollbar_padding_in_child_writing_mode, - intrinsic_sizes_border_box, min_property_in_cross_axis, + flex_basis_space, child_style, border_padding_in_child_writing_mode, + MinMaxSizesFunc, min_property_in_cross_axis, LengthResolvePhase::kLayout); } + base::Optional<LayoutUnit> intrinsic_block_size; + auto IntrinsicBlockSizeFunc = [&]() -> LayoutUnit { + if (!intrinsic_block_size) { + NGConstraintSpace child_space = BuildSpaceForIntrinsicBlockSize( + child, physical_child_margins, + min_max_sizes_in_cross_axis_direction); + scoped_refptr<const NGLayoutResult> layout_result = + child.Layout(child_space, /* break_token */ nullptr); + intrinsic_block_size = layout_result->IntrinsicBlockSize(); + } + return *intrinsic_block_size; + }; + + // The logic that calculates flex_base_border_box assumes that the used + // value of the flex-basis property is either definite or 'content'. + LayoutUnit flex_base_border_box; + const Length& specified_length_in_main_axis = + is_horizontal_flow_ ? child_style.Width() : child_style.Height(); + const Length& flex_basis = child_style.FlexBasis(); + Length length_to_resolve = Length::Auto(); + if (flex_basis.IsAuto()) { + if (!is_column_ || IsItemMainSizeDefinite(child)) + length_to_resolve = specified_length_in_main_axis; + } else if (IsItemFlexBasisDefinite(child)) { + length_to_resolve = flex_basis; + } + + if (length_to_resolve.IsAuto()) { + // This block means that the used flex-basis is 'content'. In here we + // implement parts B,C,D,E of 9.2.3 + // https://drafts.csswg.org/css-flexbox/#algo-main-item + const Length& cross_axis_length = + is_horizontal_flow_ ? child.Style().Height() : child.Style().Width(); + if (child.HasAspectRatio() && + (IsItemCrossAxisLengthDefinite(child, cross_axis_length))) { + // This is Part B of 9.2.3 + // https://drafts.csswg.org/css-flexbox/#algo-main-item It requires that + // the item has a definite cross size. + // + // But for determining the flex-basis of aspect ratio items, both legacy + // and FF both ignore part of the flex spec that has a more lenient + // definition of definite. + // https://drafts.csswg.org/css-flexbox/#definite says "If a single-line + // flex container has a definite cross size, the outer cross size of any + // stretched flex items is the flex container's inner cross size + // (clamped to the flex item's min and max cross size) and is considered + // definite". But when this happens, neither legacy nor firefox use the + // container's cross size to calculate the item's main size, they just + // fall to block E. E.g. Legacy and FF show a 16x100 green square + // instead of a 100x100 green square for + // https://jsfiddle.net/dgrogan/djh5wu0x/1/. I think it should be + // 100x100. + LayoutUnit cross_size; + if (MainAxisIsInlineAxis(child)) { + cross_size = ResolveMainBlockLength( + flex_basis_space, child_style, + border_padding_in_child_writing_mode, cross_axis_length, + kIndefiniteSize, LengthResolvePhase::kLayout); + } else { + cross_size = + ResolveMainInlineLength(flex_basis_space, child_style, + border_padding_in_child_writing_mode, + MinMaxSizesFunc, cross_axis_length); + } + cross_size = min_max_sizes_in_cross_axis_direction.ClampSizeToMinAndMax( + cross_size); + flex_base_border_box = + LayoutUnit(cross_size * GetMainOverCrossAspectRatio(child)); + } else if (MainAxisIsInlineAxis(child)) { + // We're now in parts C, D, and E for what are usually (horizontal-tb + // containers AND children) row flex containers. I _think_ the C and D + // cases are correctly handled by this code, which was originally + // written for case E. + if (child.HasAspectRatio()) { + // Legacy uses child.PreferredLogicalWidths() for this case, which + // is not exactly correct. + // TODO(dgrogan): Replace with a variant of ComputeReplacedSize that + // ignores min-width, width, max-width. + flex_base_border_box = + child.GetLayoutBox()->PreferredLogicalWidths().max_size; + } else { + flex_base_border_box = MinMaxSizesFunc().max_size; + } + } else { + // Parts C, D, and E for what are usually column flex containers. + // + // This is the post-layout height for aspect-ratio items, which matches + // legacy but isn't always correct. + // TODO(dgrogan): Replace with a variant of ComputeReplacedSize that + // ignores min-height, height, max-height. + flex_base_border_box = IntrinsicBlockSizeFunc(); + } + } else { + // Part A of 9.2.3 https://drafts.csswg.org/css-flexbox/#algo-main-item + if (MainAxisIsInlineAxis(child)) { + flex_base_border_box = ResolveMainInlineLength( + flex_basis_space, child_style, border_padding_in_child_writing_mode, + MinMaxSizesFunc, length_to_resolve); + } else { + // Flex container's main axis is in child's block direction. Child's + // flex basis is in child's block direction. + flex_base_border_box = ResolveMainBlockLength( + flex_basis_space, child_style, border_padding_in_child_writing_mode, + length_to_resolve, IntrinsicBlockSizeFunc, + LengthResolvePhase::kLayout); + } + } + + // Spec calls this "flex base size" + // https://www.w3.org/TR/css-flexbox-1/#algo-main-item + // Blink's FlexibleBoxAlgorithm expects it to be content + scrollbar widths, + // but no padding or border. + // The ClampNegativeToZero is needed for the last canvas element in + // flexbox-flex-basis-content-001a.html. It's possibly only needed because + // we don't properly account for borders+padding when multiplying by the + // aspect ratio. + LayoutUnit flex_base_content_size = + (flex_base_border_box - main_axis_border_padding).ClampNegativeToZero(); + const Length& min = is_horizontal_flow_ ? child.Style().MinWidth() : child.Style().MinHeight(); + // TODO(dgrogan): min.IsIntrinsic should enter this block when it's in the + // item's block direction. if (min.IsAuto()) { if (algorithm_->ShouldApplyMinSizeAutoForChild(*child.GetLayoutBox())) { - // TODO(dgrogan): Do the aspect ratio parts of - // https://www.w3.org/TR/css-flexbox-1/#min-size-auto - - LayoutUnit content_size_suggestion = - MainAxisIsInlineAxis(child) ? intrinsic_sizes_border_box.min_size - : layout_result->IntrinsicBlockSize(); - content_size_suggestion = - std::min(content_size_suggestion, - min_max_sizes_in_main_axis_direction.max_size); - - if (child.MayHaveAspectRatio()) { - // TODO(dgrogan): We're including borders/padding in both - // content_size_suggestion and min_max_sizes_in_cross_axis_direction. - // Maybe we need to multiply the content size by the aspect ratio and - // then apply the border/padding from the other axis inside the - // Adjust* function. Test legacy/firefox. Start with - // https://jsfiddle.net/dgrogan/9uyg3aro/ - content_size_suggestion = - AdjustChildSizeForAspectRatioCrossAxisMinAndMax( - child, content_size_suggestion, - min_max_sizes_in_cross_axis_direction.min_size, - min_max_sizes_in_cross_axis_direction.max_size); - } + // TODO(dgrogan): This should probably apply to column flexboxes also, + // but that's not what legacy does. + if (child.IsTable() && !is_column_) { + MinMaxSizes table_preferred_widths = + ComputeMinAndMaxContentContribution( + Style(), child, + MinMaxSizesInput(child_percentage_size_.block_size)); + min_max_sizes_in_main_axis_direction.min_size = + table_preferred_widths.min_size; + } else { + LayoutUnit content_size_suggestion; + if (MainAxisIsInlineAxis(child)) { + content_size_suggestion = MinMaxSizesFunc().min_size; + } else { + LayoutUnit intrinsic_block_size; + if (child.HasAspectRatio()) { + base::Optional<LayoutUnit> computed_inline_size; + base::Optional<LayoutUnit> computed_block_size; + child.IntrinsicSize(&computed_inline_size, &computed_block_size); + + // The 150 is for elements that have an aspect ratio but no size, + // which SVG can have (maybe others?). + intrinsic_block_size = + computed_block_size.value_or(LayoutUnit(150)); + } else { + intrinsic_block_size = IntrinsicBlockSizeFunc(); + } + content_size_suggestion = intrinsic_block_size; + } - LayoutUnit specified_size_suggestion(LayoutUnit::Max()); - // If the item’s computed main size property is definite, then the - // specified size suggestion is that size. - if (MainAxisIsInlineAxis(child)) { - if (!specified_length_in_main_axis.IsAuto()) { - // TODO(dgrogan): Optimization opportunity: we may have already - // resolved specified_length_in_main_axis in the flex basis - // calculation. Reuse that if possible. - specified_size_suggestion = ResolveMainInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, - intrinsic_sizes_border_box, specified_length_in_main_axis); + + if (child.HasAspectRatio()) { + // TODO(dgrogan): We're including borders/padding in both + // content_size_suggestion and + // min_max_sizes_in_cross_axis_direction. Maybe we need to multiply + // the content size by the aspect ratio and then apply the + // border/padding from the other axis inside the Adjust* function. + // Test legacy/firefox. Start with + // https://jsfiddle.net/dgrogan/9uyg3aro/ + content_size_suggestion = + AdjustChildSizeForAspectRatioCrossAxisMinAndMax( + child, content_size_suggestion, + min_max_sizes_in_cross_axis_direction.min_size, + min_max_sizes_in_cross_axis_direction.max_size); + } + + LayoutUnit specified_size_suggestion = LayoutUnit::Max(); + // If the item’s computed main size property is definite, then the + // specified size suggestion is that size. + if (MainAxisIsInlineAxis(child)) { + if (!specified_length_in_main_axis.IsAuto()) { + // TODO(dgrogan): Optimization opportunity: we may have already + // resolved specified_length_in_main_axis in the flex basis + // calculation. Reuse that if possible. + specified_size_suggestion = ResolveMainInlineLength( + flex_basis_space, child_style, + border_padding_in_child_writing_mode, MinMaxSizesFunc, + specified_length_in_main_axis); + } + } else if (!BlockLengthUnresolvable(flex_basis_space, + specified_length_in_main_axis, + LengthResolvePhase::kLayout)) { + specified_size_suggestion = ResolveMainBlockLength( + flex_basis_space, child_style, + border_padding_in_child_writing_mode, + specified_length_in_main_axis, IntrinsicBlockSizeFunc, + LengthResolvePhase::kLayout); + DCHECK_NE(specified_size_suggestion, kIndefiniteSize); } - } else if (!BlockLengthUnresolvable(child_space, - specified_length_in_main_axis, - LengthResolvePhase::kLayout)) { - specified_size_suggestion = ResolveMainBlockLength( - child_space, child_style, border_padding_in_child_writing_mode, - specified_length_in_main_axis, - layout_result->IntrinsicBlockSize(), LengthResolvePhase::kLayout); - DCHECK_NE(specified_size_suggestion, kIndefiniteSize); - } - // Spec says to clamp specified_size_suggestion by max size but because - // content_size_suggestion already is, and we take the min of those - // two, we don't need to clamp specified_size_suggestion. - // https://github.com/w3c/csswg-drafts/issues/3669 - min_max_sizes_in_main_axis_direction.min_size = - std::min(specified_size_suggestion, content_size_suggestion); + LayoutUnit transferred_size_suggestion = LayoutUnit::Max(); + if (specified_size_suggestion == LayoutUnit::Max() && + child.HasAspectRatio()) { + const Length& cross_axis_length = is_horizontal_flow_ + ? child_style.Height() + : child_style.Width(); + if (IsItemCrossAxisLengthDefinite(child, cross_axis_length)) { + LayoutUnit cross_axis_size; + if (MainAxisIsInlineAxis(child)) { + cross_axis_size = ResolveMainBlockLength( + flex_basis_space, child_style, + border_padding_in_child_writing_mode, cross_axis_length, + kIndefiniteSize, LengthResolvePhase::kLayout); + DCHECK_NE(cross_axis_size, kIndefiniteSize); + } else { + cross_axis_size = ResolveMainInlineLength( + flex_basis_space, child_style, + border_padding_in_child_writing_mode, MinMaxSizesFunc, + cross_axis_length); + } + double ratio = GetMainOverCrossAspectRatio(child); + transferred_size_suggestion = LayoutUnit( + ratio * + min_max_sizes_in_cross_axis_direction.ClampSizeToMinAndMax( + cross_axis_size)); + } + } + + DCHECK(specified_size_suggestion == LayoutUnit::Max() || + transferred_size_suggestion == LayoutUnit::Max()); + + min_max_sizes_in_main_axis_direction.min_size = + std::min({specified_size_suggestion, content_size_suggestion, + transferred_size_suggestion, + min_max_sizes_in_main_axis_direction.max_size}); + } } } else if (MainAxisIsInlineAxis(child)) { min_max_sizes_in_main_axis_direction.min_size = ResolveMinInlineLength( - child_space, child_style, border_padding_in_child_writing_mode, - intrinsic_sizes_border_box, min, LengthResolvePhase::kLayout); + flex_basis_space, child_style, border_padding_in_child_writing_mode, + MinMaxSizesFunc, min, LengthResolvePhase::kLayout); } else { min_max_sizes_in_main_axis_direction.min_size = ResolveMinBlockLength( - child_space, child_style, border_padding_in_child_writing_mode, min, - fragment_in_child_writing_mode.BlockSize(), - LengthResolvePhase::kLayout); + flex_basis_space, child_style, border_padding_in_child_writing_mode, + min, LengthResolvePhase::kLayout); } - // TODO(dgrogan): Should this include scrollbar? - min_max_sizes_in_main_axis_direction -= main_axis_border_scrollbar_padding; + min_max_sizes_in_main_axis_direction -= main_axis_border_padding; + DCHECK_GE(min_max_sizes_in_main_axis_direction.min_size, 0); + DCHECK_GE(min_max_sizes_in_main_axis_direction.max_size, 0); + + // TODO(dgrogan): Should min_max_sizes_in_cross_axis_direction include + // cross_axis_border_padding? algorithm_ - ->emplace_back(child.GetLayoutBox(), flex_base_content_size, + ->emplace_back(nullptr, child.Style(), flex_base_content_size, min_max_sizes_in_main_axis_direction, min_max_sizes_in_cross_axis_direction, - main_axis_border_padding, main_axis_margin) + main_axis_border_padding, cross_axis_border_padding, + physical_child_margins) .ng_input_node = child; } } @@ -457,33 +744,16 @@ NGFlexLayoutAlgorithm::AdjustChildSizeForAspectRatioCrossAxisMinAndMax( LayoutUnit content_suggestion, LayoutUnit cross_min, LayoutUnit cross_max) { - DCHECK(child.MayHaveAspectRatio()); + DCHECK(child.HasAspectRatio()); + + double ratio = GetMainOverCrossAspectRatio(child); // Clamp content_suggestion by any definite min and max cross size properties // converted through the aspect ratio. - - base::Optional<LayoutUnit> computed_inline_size; - base::Optional<LayoutUnit> computed_block_size; - LogicalSize aspect_ratio; - - child.IntrinsicSize(&computed_inline_size, &computed_block_size, - &aspect_ratio); - - // TODO(dgrogan): Should we quit here if only the denominator is 0? - if (aspect_ratio.inline_size == 0 || aspect_ratio.block_size == 0) - return content_suggestion; - - double ratio = aspect_ratio.inline_size / aspect_ratio.block_size; - - // Multiplying by ratio will take something in the item's block axis and - // convert it to the inline axis. We want to convert from cross size to main - // size. If block axis and cross axis are the same, then we already have what - // we need. Otherwise we need to use the reciprocal. - if (!MainAxisIsInlineAxis(child)) - ratio = 1 / ratio; - const Length& cross_max_length = is_horizontal_flow_ ? child.Style().MaxHeight() : child.Style().MaxWidth(); + // TODO(dgrogan): No tests fail if we unconditionally apply max_main_length. + // Either add a test that needs it or remove it. if (IsItemCrossAxisLengthDefinite(child, cross_max_length)) { LayoutUnit max_main_length = LayoutUnit(cross_max * ratio); content_suggestion = std::min(max_main_length, content_suggestion); @@ -492,6 +762,8 @@ NGFlexLayoutAlgorithm::AdjustChildSizeForAspectRatioCrossAxisMinAndMax( const Length& cross_min_length = is_horizontal_flow_ ? child.Style().MinHeight() : child.Style().MinWidth(); + // TODO(dgrogan): Same as above with min_main_length here -- it may be + // unneeded or untested. if (IsItemCrossAxisLengthDefinite(child, cross_min_length)) { LayoutUnit min_main_length = LayoutUnit(cross_min * ratio); content_suggestion = std::max(min_main_length, content_suggestion); @@ -500,15 +772,7 @@ NGFlexLayoutAlgorithm::AdjustChildSizeForAspectRatioCrossAxisMinAndMax( } scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { - border_box_size_ = container_builder_.InitialBorderBoxSize(); - content_box_size_ = - ShrinkAvailableSize(border_box_size_, border_scrollbar_padding_); - child_percentage_size_ = CalculateChildPercentageSize( - ConstraintSpace(), Node(), content_box_size_); - - const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max()); - algorithm_.emplace(&Style(), line_break_length); - + PaintLayerScrollableArea::DelayScrollOffsetClampScope delay_clamp_scope; ConstructAndAppendFlexItems(); LayoutUnit main_axis_start_offset; @@ -542,22 +806,29 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { for (wtf_size_t i = 0; i < line->line_items.size(); ++i) { FlexItem& flex_item = line->line_items[i]; - WritingMode child_writing_mode = - flex_item.ng_input_node.Style().GetWritingMode(); + const ComputedStyle& child_style = flex_item.ng_input_node.Style(); NGConstraintSpaceBuilder space_builder(ConstraintSpace(), - child_writing_mode, + child_style.GetWritingMode(), /* is_new_fc */ true); SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item.ng_input_node, &space_builder); - space_builder.SetTextDirection( - flex_item.ng_input_node.Style().Direction()); + space_builder.SetTextDirection(child_style.Direction()); + space_builder.SetIsPaintedAtomically(true); LogicalSize available_size; + NGBoxStrut margins = flex_item.physical_margins.ConvertToLogical( + ConstraintSpace().GetWritingMode(), Style().Direction()); if (is_column_) { available_size.inline_size = content_box_size_.inline_size; available_size.block_size = flex_item.flexed_content_size + flex_item.main_axis_border_padding; space_builder.SetIsFixedBlockSize(true); + if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node)) { + space_builder.SetIsFixedInlineSize(true); + available_size.inline_size = CalculateFixedCrossSize( + available_size.inline_size, flex_item.min_max_cross_sizes.value(), + margins.InlineSum()); + } // https://drafts.csswg.org/css-flexbox/#definite-sizes // If the flex container has a definite main size, a flex item's // post-flexing main size is treated as definite, even though it can @@ -571,16 +842,22 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { flex_item.flexed_content_size + flex_item.main_axis_border_padding; available_size.block_size = content_box_size_.block_size; space_builder.SetIsFixedInlineSize(true); - } - if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node)) { - if (is_column_) - space_builder.SetIsFixedInlineSize(true); - else + if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node)) { space_builder.SetIsFixedBlockSize(true); + available_size.block_size = CalculateFixedCrossSize( + available_size.block_size, flex_item.min_max_cross_sizes.value(), + margins.BlockSum()); + } } + space_builder.SetNeedsBaseline( + ConstraintSpace().NeedsBaseline() || + FlexLayoutAlgorithm::AlignmentForChild(Style(), child_style) == + ItemPosition::kBaseline); + space_builder.SetAvailableSize(available_size); space_builder.SetPercentageResolutionSize(child_percentage_size_); + space_builder.SetReplacedPercentageResolutionSize(child_percentage_size_); // https://drafts.csswg.org/css-flexbox/#algo-cross-item // Determine the hypothetical cross size of each item by performing layout @@ -592,6 +869,10 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { NGConstraintSpace child_space = space_builder.ToConstraintSpace(); flex_item.layout_result = flex_item.ng_input_node.Layout(child_space, nullptr /*break token*/); + + // TODO(layout-dev): Handle abortions caused by block fragmentation. + DCHECK_EQ(flex_item.layout_result->Status(), NGLayoutResult::kSuccess); + flex_item.cross_axis_size = is_horizontal_flow_ ? flex_item.layout_result->PhysicalFragment().Size().height @@ -627,12 +908,13 @@ scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() { } void NGFlexLayoutAlgorithm::ApplyStretchAlignmentToChild(FlexItem& flex_item) { - WritingMode child_writing_mode = - flex_item.ng_input_node.Style().GetWritingMode(); - NGConstraintSpaceBuilder space_builder(ConstraintSpace(), child_writing_mode, + const ComputedStyle& child_style = flex_item.ng_input_node.Style(); + NGConstraintSpaceBuilder space_builder(ConstraintSpace(), + child_style.GetWritingMode(), /* is_new_fc */ true); SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item.ng_input_node, &space_builder); + space_builder.SetIsPaintedAtomically(true); LogicalSize available_size( flex_item.flexed_content_size + flex_item.main_axis_border_padding, @@ -644,9 +926,16 @@ void NGFlexLayoutAlgorithm::ApplyStretchAlignmentToChild(FlexItem& flex_item) { space_builder.SetIsFixedBlockSizeIndefinite(true); } } - space_builder.SetTextDirection(flex_item.ng_input_node.Style().Direction()); + + space_builder.SetNeedsBaseline( + ConstraintSpace().NeedsBaseline() || + FlexLayoutAlgorithm::AlignmentForChild(Style(), child_style) == + ItemPosition::kBaseline); + + space_builder.SetTextDirection(child_style.Direction()); space_builder.SetAvailableSize(available_size); space_builder.SetPercentageResolutionSize(child_percentage_size_); + space_builder.SetReplacedPercentageResolutionSize(child_percentage_size_); space_builder.SetIsFixedInlineSize(true); space_builder.SetIsFixedBlockSize(true); NGConstraintSpace child_space = space_builder.ToConstraintSpace(); @@ -687,6 +976,9 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { border_scrollbar_padding_.block_start); } + base::Optional<LayoutUnit> fallback_baseline; + + LayoutUnit overflow_block_size; for (FlexLine& line_context : line_contexts) { for (wtf_size_t child_number = 0; child_number < line_context.line_items.size(); ++child_number) { @@ -695,6 +987,9 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { if (DoesItemStretch(flex_item.ng_input_node)) ApplyStretchAlignmentToChild(flex_item); + const auto& physical_fragment = To<NGPhysicalBoxFragment>( + flex_item.layout_result->PhysicalFragment()); + // flex_item.desired_location stores the main axis offset in X and the // cross axis offset in Y. But AddChild wants offset from parent // rectangle, so we have to transpose for columns. AddChild takes care of @@ -702,16 +997,70 @@ void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() { LayoutPoint location = is_column_ ? flex_item.desired_location.TransposedPoint() : flex_item.desired_location; - container_builder_.AddChild(flex_item.layout_result->PhysicalFragment(), + + NGBoxFragment fragment(ConstraintSpace().GetWritingMode(), + ConstraintSpace().Direction(), physical_fragment); + // Only propagate baselines from children on the first flex-line. + if (&line_context == line_contexts.begin()) { + PropagateBaselineFromChild(flex_item, fragment, location.Y(), + &fallback_baseline); + } + + container_builder_.AddChild(physical_fragment, {location.X(), location.Y()}); + + flex_item.ng_input_node.StoreMargins(flex_item.physical_margins); + + LayoutUnit margin_block_end = + flex_item.physical_margins + .ConvertToLogical(ConstraintSpace().GetWritingMode(), + ConstraintSpace().Direction()) + .block_end; + overflow_block_size = + std::max(overflow_block_size, + location.Y() + fragment.BlockSize() + margin_block_end); } } + + container_builder_.SetOverflowBlockSize(overflow_block_size + + border_scrollbar_padding_.block_end); + + // Set the baseline to the fallback, if we didn't find any children with + // baseline alignment. + if (!container_builder_.Baseline() && fallback_baseline) + container_builder_.SetBaseline(*fallback_baseline); +} + +void NGFlexLayoutAlgorithm::PropagateBaselineFromChild( + const FlexItem& flex_item, + const NGBoxFragment& fragment, + LayoutUnit block_offset, + base::Optional<LayoutUnit>* fallback_baseline) { + // Check if we've already found an appropriate baseline. + if (container_builder_.Baseline()) + return; + + LayoutUnit baseline_offset = + block_offset + fragment.Baseline().value_or(fragment.BlockSize()); + + // We prefer a baseline from a child with baseline alignment, and no + // auto-margins in the cross axis (even if we have to synthesize the + // baseline). + if (FlexLayoutAlgorithm::AlignmentForChild(Style(), flex_item.style) == + ItemPosition::kBaseline && + !flex_item.HasAutoMarginsInCrossAxis()) { + container_builder_.SetBaseline(baseline_offset); + return; + } + + // Set the fallback baseline if it doesn't have a value yet. + *fallback_baseline = fallback_baseline->value_or(baseline_offset); } -base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize( - const MinMaxSizeInput& input) const { - base::Optional<MinMaxSize> sizes = CalculateMinMaxSizesIgnoringChildren( - Node(), border_scrollbar_padding_, input.size_type); +base::Optional<MinMaxSizes> NGFlexLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { + base::Optional<MinMaxSizes> sizes = + CalculateMinMaxSizesIgnoringChildren(Node(), border_scrollbar_padding_); if (sizes) return sizes; @@ -721,21 +1070,15 @@ base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize( ConstraintSpace(), Node(), border_padding_, input.percentage_resolution_block_size); - // Use default MinMaxSizeInput: - // - Children of flexbox ignore any specified float properties, so children - // never have to take floated siblings into account, and external floats - // don't make it through the new formatting context that flexbox - // establishes. - // - We want the child's border box MinMaxSize, which is the default. - MinMaxSizeInput child_input(child_percentage_resolution_block_size); - - for (NGLayoutInputNode generic_child = Node().FirstChild(); generic_child; - generic_child = generic_child.NextSibling()) { - auto child = To<NGBlockNode>(generic_child); + MinMaxSizesInput child_input(child_percentage_resolution_block_size); + + NGFlexChildIterator iterator(Node()); + for (NGBlockNode child = iterator.NextChild(); child; + child = iterator.NextChild()) { if (child.IsOutOfFlowPositioned()) continue; - MinMaxSize child_min_max_sizes = + MinMaxSizes child_min_max_sizes = ComputeMinAndMaxContentContribution(Style(), child, child_input); NGBoxStrut child_margins = ComputeMinMaxMargins(Style(), child); child_min_max_sizes += child_margins.InlineSum(); @@ -744,7 +1087,7 @@ base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize( sizes->max_size = std::max(sizes->max_size, child_min_max_sizes.max_size); } else { sizes->max_size += child_min_max_sizes.max_size; - if (IsMultiline()) { + if (algorithm_->IsMultiline()) { sizes->min_size = std::max(sizes->min_size, child_min_max_sizes.min_size); } else { @@ -757,15 +1100,8 @@ base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize( // Due to negative margins, it is possible that we calculated a negative // intrinsic width. Make sure that we never return a negative width. sizes->Encompass(LayoutUnit()); - - if (input.size_type == NGMinMaxSizeType::kBorderBoxSize) - *sizes += border_scrollbar_padding_.InlineSum(); - + *sizes += border_scrollbar_padding_.InlineSum(); return sizes; } -bool NGFlexLayoutAlgorithm::IsMultiline() const { - return Style().FlexWrap() != EFlexWrap::kNowrap; -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h index 16f6aaa02e1..066277e3075 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h @@ -14,6 +14,7 @@ namespace blink { class NGBlockNode; class NGBlockBreakToken; +class NGBoxFragment; class CORE_EXPORT NGFlexLayoutAlgorithm : public NGLayoutAlgorithm<NGBlockNode, @@ -24,15 +25,17 @@ class CORE_EXPORT NGFlexLayoutAlgorithm scoped_refptr<const NGLayoutResult> Layout() override; - base::Optional<MinMaxSize> ComputeMinMaxSize( - const MinMaxSizeInput&) const override; + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const override; private: bool DoesItemCrossSizeComputeToAuto(const NGBlockNode& child) const; + bool IsItemFlexBasisDefinite(const NGBlockNode& child) const; bool IsItemMainSizeDefinite(const NGBlockNode& child) const; bool IsItemCrossAxisLengthDefinite(const NGBlockNode& child, const Length& length) const; bool ShouldItemShrinkToFit(const NGBlockNode& child) const; + double GetMainOverCrossAspectRatio(const NGBlockNode& child) const; bool DoesItemStretch(const NGBlockNode& child) const; // This implements the first of the additional scenarios where a flex item // has definite sizes when it would not if it weren't a flex item. @@ -47,8 +50,11 @@ class CORE_EXPORT NGFlexLayoutAlgorithm bool IsColumnContainerMainSizeDefinite() const; bool IsContainerCrossSizeDefinite() const; - NGConstraintSpace BuildConstraintSpaceForDeterminingFlexBasis( - const NGBlockNode& flex_item) const; + NGConstraintSpace BuildSpaceForFlexBasis(const NGBlockNode& flex_item) const; + NGConstraintSpace BuildSpaceForIntrinsicBlockSize( + const NGBlockNode& flex_item, + const NGPhysicalBoxStrut& physical_margins, + const MinMaxSizes& cross_axis) const; void ConstructAndAppendFlexItems(); void ApplyStretchAlignmentToChild(FlexItem& flex_item); void GiveLinesAndItemsFinalPositionAndSize(); @@ -57,20 +63,22 @@ class CORE_EXPORT NGFlexLayoutAlgorithm // This is same method as FlexItem but we need that logic before FlexItem is // constructed. bool MainAxisIsInlineAxis(const NGBlockNode& child) const; - LayoutUnit MainAxisContentExtent(LayoutUnit sum_hypothetical_main_size); + LayoutUnit MainAxisContentExtent(LayoutUnit sum_hypothetical_main_size) const; void HandleOutOfFlowPositioned(NGBlockNode child); - // TODO(dgrogan): This is redundant with FlexLayoutAlgorithm.IsMultiline() but - // it's needed before the algorithm is instantiated. Figure out how to - // not reimplement. - bool IsMultiline() const; + + // Propagates the baseline from the given flex-item if needed. + void PropagateBaselineFromChild( + const FlexItem&, + const NGBoxFragment&, + LayoutUnit block_offset, + base::Optional<LayoutUnit>* fallback_baseline); const NGBoxStrut border_padding_; const NGBoxStrut border_scrollbar_padding_; const bool is_column_; const bool is_horizontal_flow_; - // These are populated at the top of Layout(), so aren't available in - // ComputeMinMaxSize() or anything it calls. + const bool is_cross_size_definite_; LogicalSize border_box_size_; LogicalSize content_box_size_; LogicalSize child_percentage_size_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc index dccc520c12e..3404db2e94f 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc @@ -6,7 +6,7 @@ #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/layout/layout_box.h" -#include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" @@ -66,13 +66,14 @@ NGConstraintSpace CreateConstraintSpaceForFloat( /* is_new_fc */ true); SetOrthogonalFallbackInlineSizeIfNeeded(unpositioned_float.parent_style, unpositioned_float.node, &builder); + builder.SetIsPaintedAtomically(true); if (origin_block_offset) { DCHECK(parent_space.HasBlockFragmentation()); DCHECK_EQ(style.GetWritingMode(), parent_space.GetWritingMode()); - SetupFragmentation(parent_space, *origin_block_offset, &builder, - /* is_new_fc */ true); + SetupFragmentation(parent_space, unpositioned_float.node, + *origin_block_offset, &builder, /* is_new_fc */ true); } else { builder.SetFragmentationType(NGFragmentationType::kFragmentNone); } @@ -118,7 +119,7 @@ std::unique_ptr<NGExclusionShapeData> CreateExclusionShapeData( case CSSBoxType::kContent: const NGConstraintSpace space = CreateConstraintSpaceForFloat(unpositioned_float); - NGBoxStrut strut = ComputeBorders(space, unpositioned_float.node); + NGBoxStrut strut = ComputeBorders(space, style); if (style.ShapeOutside()->CssBox() == CSSBoxType::kContent) strut += ComputePadding(space, style); shape_insets = diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h index a1ceb920f02..f0bd55d5bc0 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h @@ -47,6 +47,9 @@ class CORE_EXPORT NGFragmentBuilder { void SetIsHiddenForPaint(bool value) { is_hidden_for_paint_ = value; } + // Specify whether this will be the first fragment generated for the node. + void SetIsFirstForNode(bool is_first) { is_first_for_node_ = is_first; } + const LayoutObject* GetLayoutObject() const { return layout_object_; } protected: @@ -80,6 +83,7 @@ class CORE_EXPORT NGFragmentBuilder { LayoutObject* layout_object_ = nullptr; scoped_refptr<NGBreakToken> break_token_; bool is_hidden_for_paint_ = false; + bool is_first_for_node_ = true; friend class NGPhysicalFragment; }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.cc new file mode 100644 index 00000000000..167e14136f6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.cc @@ -0,0 +1,212 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h" + +#include "third_party/blink/renderer/core/layout/layout_box.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" + +namespace blink { + +NGFragmentChildIterator::NGFragmentChildIterator( + const NGPhysicalBoxFragment& parent, + const NGBlockBreakToken* parent_break_token) + : parent_fragment_(&parent), + parent_break_token_(parent_break_token), + is_fragmentation_context_root_(parent.IsFragmentationContextRoot()) { + DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); + current_.link_.fragment = nullptr; + if (parent_break_token) + child_break_tokens_ = parent_break_token->ChildBreakTokens(); + if (parent.HasItems()) { + current_.cursor_.emplace(*parent.Items()); + current_.block_break_token_ = parent_break_token; + UpdateSelfFromCursor(); + } else { + UpdateSelfFromFragment(); + } +} + +NGFragmentChildIterator::NGFragmentChildIterator( + const NGInlineCursor& parent, + const NGBlockBreakToken* parent_break_token, + base::span<const NGBreakToken* const> child_break_tokens) + : parent_break_token_(parent_break_token), + child_break_tokens_(child_break_tokens) { + current_.block_break_token_ = parent_break_token; + current_.link_.fragment = nullptr; + current_.cursor_ = parent.CursorForDescendants(); + UpdateSelfFromCursor(); +} + +NGFragmentChildIterator NGFragmentChildIterator::Descend() const { + if (current_.cursor_) { + const NGFragmentItem* item = current_.cursor_->CurrentItem(); + // Descend using the cursor if the current item doesn't establish a new + // formatting context. + if (!item->IsFormattingContextRoot()) { + return NGFragmentChildIterator( + *current_.cursor_, + current_.BlockBreakToken() ? parent_break_token_ : nullptr, + child_break_tokens_.subspan(child_break_token_idx_)); + } + } + DCHECK(current_.BoxFragment()); + return NGFragmentChildIterator(*current_.BoxFragment(), + current_.BlockBreakToken()); +} + +bool NGFragmentChildIterator::AdvanceChildFragment() { + DCHECK(parent_fragment_); + const auto children = parent_fragment_->Children(); + const NGPhysicalBoxFragment* previous_fragment = + To<NGPhysicalBoxFragment>(current_.link_.fragment); + DCHECK(previous_fragment); + if (child_fragment_idx_ < children.size()) + child_fragment_idx_++; + // There may be line box fragments among the children, and we're not + // interested in them (lines will already have been handled by the inline + // cursor). + SkipToBoxFragment(); + if (child_fragment_idx_ >= children.size()) + return false; + if (child_break_token_idx_ < child_break_tokens_.size()) + child_break_token_idx_++; + UpdateSelfFromFragment(previous_fragment); + return true; +} + +void NGFragmentChildIterator::UpdateSelfFromFragment( + const NGPhysicalBoxFragment* previous_fragment) { + DCHECK(parent_fragment_); + const auto children = parent_fragment_->Children(); + if (child_fragment_idx_ >= children.size()) + return; + current_.link_ = children[child_fragment_idx_]; + DCHECK(current_.link_.fragment); + SkipToBlockBreakToken(); + if (child_break_token_idx_ < child_break_tokens_.size()) { + current_.block_break_token_ = + To<NGBlockBreakToken>(child_break_tokens_[child_break_token_idx_]); + DCHECK(!current_.link_.fragment->GetLayoutObject() || + current_.block_break_token_->InputNode().GetLayoutBox() == + current_.link_.fragment->GetLayoutObject()); + current_.break_token_for_fragmentainer_only_ = false; + } else if (is_fragmentation_context_root_ && previous_fragment) { + if (previous_fragment->IsColumnBox()) { + // The outgoing break token from one fragmentainer is the incoming break + // token to the next one. This is also true when there are column spanners + // between two columns (fragmentainers); the outgoing break token from the + // former column will be ignored by any intervening spanners, and then fed + // into the first column that comes after them, as an incoming break + // token. + current_.block_break_token_ = + To<NGBlockBreakToken>(previous_fragment->BreakToken()); + current_.break_token_for_fragmentainer_only_ = true; + } else { + // This is a column spanner. We'll leave |current_block_break_token_| + // alone here, as it will be used as in incoming break token when we get + // to the next column. + DCHECK( + NGBlockNode(ToLayoutBox(previous_fragment->GetMutableLayoutObject())) + .IsColumnSpanAll()); + + // If the previous fragment is a column spanner, it's not expected to have + // a break token; if a spanner runs out of space, no columns (or spanners) + // would fit after it. + DCHECK(!previous_fragment->BreakToken()); + } + } else { + current_.block_break_token_ = nullptr; + } +} + +bool NGFragmentChildIterator::AdvanceWithCursor() { + DCHECK(current_.cursor_); + const NGFragmentItem* item = current_.cursor_->CurrentItem(); + if (item->HasChildren()) { + // If we're advancing past a non-atomic inline, we also need to advance past + // any break tokens for fragments in there. + for (wtf_size_t remaining = item->DescendantsCount(); remaining; + remaining--) { + if (item->IsFloating()) { + SkipToBlockBreakToken(); + if (child_break_token_idx_ < child_break_tokens_.size()) { + DCHECK_EQ(child_break_tokens_[child_break_token_idx_] + ->InputNode() + .GetLayoutBox(), + item->GetLayoutObject()); + child_break_token_idx_++; + } + } + current_.cursor_->MoveToNext(); + item = current_.cursor_->CurrentItem(); + } + } else { + current_.cursor_->MoveToNext(); + } + UpdateSelfFromCursor(); + if (current_.cursor_->CurrentItem()) + return true; + // If there are more items, proceed and see if we have box fragment + // children. There may be out-of-flow positioned child fragments. + if (!parent_fragment_) + return false; + current_.cursor_.reset(); + SkipToBoxFragment(); + UpdateSelfFromFragment(); + return !IsAtEnd(); +} + +void NGFragmentChildIterator::UpdateSelfFromCursor() { + DCHECK(current_.cursor_); + // For inline items we just use the incoming break token to the containing + // block. + current_.block_break_token_ = parent_break_token_; + const NGFragmentItem* item = current_.cursor_->CurrentItem(); + if (!item) { + current_.link_.fragment = nullptr; + return; + } + current_.link_ = {item->BoxFragment(), item->OffsetInContainerBlock()}; + if (!current_.link_.fragment || !current_.link_.fragment->IsFloating()) { + DCHECK(!current_.link_.fragment || + current_.link_.fragment->GetLayoutObject()->IsInline()); + return; + } + if (!parent_break_token_) + return; + // Floats may fragment, in which case there's a designated break token for + // them. + SkipToBlockBreakToken(); + if (child_break_token_idx_ >= child_break_tokens_.size()) { + current_.block_break_token_ = nullptr; + return; + } + current_.block_break_token_ = + To<NGBlockBreakToken>(child_break_tokens_[child_break_token_idx_]); + DCHECK(!current_.link_.fragment->GetLayoutObject() || + current_.block_break_token_->InputNode().GetLayoutBox() == + current_.link_.fragment->GetLayoutObject()); +} + +void NGFragmentChildIterator::SkipToBoxFragment() { + for (const auto children = parent_fragment_->Children(); + child_fragment_idx_ < children.size(); child_fragment_idx_++) { + if (children[child_fragment_idx_].fragment->IsBox()) + break; + } +} + +void NGFragmentChildIterator::SkipToBlockBreakToken() { + // There may be inline break tokens here. Ignore them. + while (child_break_token_idx_ < child_break_tokens_.size() && + !child_break_tokens_[child_break_token_idx_]->IsBlockType()) + child_break_token_idx_++; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h new file mode 100644 index 00000000000..c4927f0acad --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h @@ -0,0 +1,141 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENT_CHILD_ITERATOR_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENT_CHILD_ITERATOR_H_ + +#include "base/containers/span.h" +#include "base/optional.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" +#include "third_party/blink/renderer/core/layout/ng/ng_link.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" + +namespace blink { + +class LayoutObject; +class NGBlockBreakToken; + +// Iterator for children of a box fragment. Supports fragment items and break +// tokens. To advance to the next sibling, call |Advance()|. To descend into +// children of the current child, call |Descend()|. +// +// Using this class requires LayoutNGFragmentItem to be enabled. While fragment +// items are in a flat list representing the contents of an inline formatting +// context, the iterator will to a certain extent restore the object hierarchy, +// so that we can calculate the global offset of children of a relatively +// positioned inline correctly. +class CORE_EXPORT NGFragmentChildIterator { + STACK_ALLOCATED(); + + public: + explicit NGFragmentChildIterator( + const NGPhysicalBoxFragment& parent, + const NGBlockBreakToken* parent_break_token = nullptr); + + // Create a child iterator for the current child. + NGFragmentChildIterator Descend() const; + + // Move to the next sibling. Return false if there's no next sibling. Once + // false is returned, this object is in an unusable state, with the exception + // that calling IsAtEnd() is allowed. + bool Advance() { + if (current_.cursor_) + return AdvanceWithCursor(); + return AdvanceChildFragment(); + } + + bool IsAtEnd() { + if (current_.cursor_) + return !*current_.cursor_; + DCHECK(parent_fragment_); + const auto children = parent_fragment_->Children(); + return child_fragment_idx_ >= children.size(); + } + + class Current { + friend class NGFragmentChildIterator; + + public: + // Return the current NGLink. Note that its offset is relative to the inline + // formatting context root, if the fragment / item participates in one. + const NGLink& Link() const { return link_; } + + const NGPhysicalBoxFragment* BoxFragment() const { + return To<NGPhysicalBoxFragment>(link_.fragment); + } + const NGFragmentItem* FragmentItem() const { + if (!cursor_) + return nullptr; + return cursor_->CurrentItem(); + } + + // Get the incoming break token for the current child, i.e. the context at + // which layout of this child's node was resumed. Note that for text and + // non-atomic inlines this will be the incoming block break token to the + // inline formatting context root. For monolithic content, no break token + // will be returned (since such content isn't considered to participate in a + // fragmentation context). + const NGBlockBreakToken* BlockBreakToken() const { + if (LIKELY(!block_break_token_)) + return nullptr; + if (link_.fragment) { + // Don't pass the break token into monolithic content. + if (link_.fragment->IsMonolithic()) + return nullptr; + // If the break token we've found is from a fragmentainer, it's only to + // be used by a subsequent fragmentainer. Other fragment types (such as + // column spanners) need to ignore it. + if (break_token_for_fragmentainer_only_ && + !link_.fragment->IsColumnBox()) + return nullptr; + } + return block_break_token_; + } + + const LayoutObject* GetLayoutObject() const { + if (const NGFragmentItem* item = FragmentItem()) + return item->GetLayoutObject(); + return BoxFragment()->GetLayoutObject(); + } + + private: + NGLink link_; + base::Optional<NGInlineCursor> cursor_; + const NGBlockBreakToken* block_break_token_ = nullptr; + bool break_token_for_fragmentainer_only_ = false; + }; + + const Current& GetCurrent() const { return current_; } + const Current& operator*() const { return current_; } + const Current* operator->() const { return ¤t_; } + + private: + NGFragmentChildIterator( + const NGInlineCursor& parent, + const NGBlockBreakToken* parent_break_token, + base::span<const NGBreakToken* const> child_break_tokens); + + bool AdvanceChildFragment(); + void UpdateSelfFromFragment( + const NGPhysicalBoxFragment* previous_fragment = nullptr); + + bool AdvanceWithCursor(); + void UpdateSelfFromCursor(); + void SkipToBoxFragment(); + void SkipToBlockBreakToken(); + + const NGPhysicalBoxFragment* parent_fragment_ = nullptr; + const NGBlockBreakToken* parent_break_token_ = nullptr; + Current current_; + base::span<const NGBreakToken* const> child_break_tokens_; + wtf_size_t child_fragment_idx_ = 0; + wtf_size_t child_break_token_idx_ = 0; + bool is_fragmentation_context_root_ = false; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENT_CHILD_ITERATOR_H_ diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator_test.cc new file mode 100644 index 00000000000..f0304c056d1 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator_test.cc @@ -0,0 +1,808 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h" +#include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" + +namespace blink { +namespace { + +class NGFragmentChildIteratorTest + : public NGBaseLayoutAlgorithmTest, + private ScopedLayoutNGBlockFragmentationForTest, + private ScopedLayoutNGFragmentItemForTest { + protected: + NGFragmentChildIteratorTest() + : ScopedLayoutNGBlockFragmentationForTest(true), + ScopedLayoutNGFragmentItemForTest(true) {} + + scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( + Element* element) { + NGBlockNode container(ToLayoutBox(element->GetLayoutObject())); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); + return NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); + } +}; + +TEST_F(NGFragmentChildIteratorTest, Basic) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div id="child1"> + <div id="grandchild"></div> + </div> + <div id="child2"></div> + </div> + )HTML"); + + const LayoutObject* child1 = GetLayoutObjectByElementId("child1"); + const LayoutObject* child2 = GetLayoutObjectByElementId("child2"); + const LayoutObject* grandchild = GetLayoutObjectByElementId("grandchild"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator1(*container.get()); + EXPECT_FALSE(iterator1.IsAtEnd()); + + const NGPhysicalBoxFragment* fragment = iterator1->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child1); + EXPECT_FALSE(iterator1.IsAtEnd()); + + NGFragmentChildIterator iterator2 = iterator1.Descend(); + EXPECT_FALSE(iterator2.IsAtEnd()); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), grandchild); + EXPECT_FALSE(iterator2.IsAtEnd()); + EXPECT_FALSE(iterator2.Advance()); + EXPECT_TRUE(iterator2.IsAtEnd()); + + EXPECT_TRUE(iterator1.Advance()); + fragment = iterator1->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child2); + EXPECT_FALSE(iterator1.IsAtEnd()); + + // #child2 has no children. + EXPECT_TRUE(iterator1.Descend().IsAtEnd()); + + // No more children left. + EXPECT_FALSE(iterator1.Advance()); + EXPECT_TRUE(iterator1.IsAtEnd()); +} + +TEST_F(NGFragmentChildIteratorTest, BasicInline) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + xxx + <span id="span1" style="border:solid;"> + <div id="float1" style="float:left;"></div> + xxx + </span> + xxx + </div> + )HTML"); + + const LayoutObject* span1 = GetLayoutObjectByElementId("span1"); + const LayoutObject* float1 = GetLayoutObjectByElementId("float1"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator1(*container.get()); + + EXPECT_FALSE(iterator1->BoxFragment()); + const NGFragmentItem* fragment_item = iterator1->FragmentItem(); + ASSERT_TRUE(fragment_item); + EXPECT_EQ(fragment_item->Type(), NGFragmentItem::kLine); + + // Descend into the line box. + NGFragmentChildIterator iterator2 = iterator1.Descend(); + fragment_item = iterator2->FragmentItem(); + ASSERT_TRUE(fragment_item); + EXPECT_TRUE(fragment_item->IsText()); + + EXPECT_TRUE(iterator2.Advance()); + const NGPhysicalBoxFragment* fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), span1); + + // Descend into children of #span1. + NGFragmentChildIterator iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), float1); + + EXPECT_TRUE(iterator3.Advance()); + fragment_item = iterator3->FragmentItem(); + ASSERT_TRUE(fragment_item); + EXPECT_TRUE(fragment_item->IsText()); + EXPECT_FALSE(iterator3.Advance()); + + // Continue with siblings of #span1. + EXPECT_TRUE(iterator2.Advance()); + fragment_item = iterator2->FragmentItem(); + ASSERT_TRUE(fragment_item); + EXPECT_TRUE(fragment_item->IsText()); + + EXPECT_FALSE(iterator2.Advance()); + EXPECT_FALSE(iterator1.Advance()); +} + +TEST_F(NGFragmentChildIteratorTest, InlineBlock) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + xxx + <span id="inlineblock"> + <div id="float1" style="float:left;"></div> + </span> + xxx + </div> + )HTML"); + + const LayoutObject* inlineblock = GetLayoutObjectByElementId("inlineblock"); + const LayoutObject* float1 = GetLayoutObjectByElementId("float1"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator1(*container.get()); + + EXPECT_FALSE(iterator1->BoxFragment()); + const NGFragmentItem* fragment_item = iterator1->FragmentItem(); + ASSERT_TRUE(fragment_item); + EXPECT_EQ(fragment_item->Type(), NGFragmentItem::kLine); + + // Descend into the line box. + NGFragmentChildIterator iterator2 = iterator1.Descend(); + fragment_item = iterator2->FragmentItem(); + ASSERT_TRUE(fragment_item); + EXPECT_TRUE(fragment_item->IsText()); + + EXPECT_TRUE(iterator2.Advance()); + const NGPhysicalBoxFragment* fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), inlineblock); + + // Descend into children of #inlineblock. + NGFragmentChildIterator iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), float1); + EXPECT_FALSE(iterator3.Advance()); + + // Continue with siblings of #inlineblock. + EXPECT_TRUE(iterator2.Advance()); + fragment_item = iterator2->FragmentItem(); + ASSERT_TRUE(fragment_item); + EXPECT_TRUE(fragment_item->IsText()); + + EXPECT_FALSE(iterator2.Advance()); + EXPECT_FALSE(iterator1.Advance()); +} + +TEST_F(NGFragmentChildIteratorTest, FloatsInInline) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <span id="span1" style="border:solid;"> + <div id="float1" style="float:left;"> + <div id="child"></div> + </div> + </span> + </div> + )HTML"); + + const LayoutObject* span1 = GetLayoutObjectByElementId("span1"); + const LayoutObject* float1 = GetLayoutObjectByElementId("float1"); + const LayoutObject* child = GetLayoutObjectByElementId("child"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator1(*container.get()); + + const NGPhysicalBoxFragment* fragment = iterator1->BoxFragment(); + EXPECT_FALSE(fragment); + const NGFragmentItem* item = iterator1->FragmentItem(); + ASSERT_TRUE(item); + EXPECT_EQ(item->Type(), NGFragmentItem::kLine); + + // Descend into the line box. + NGFragmentChildIterator iterator2 = iterator1.Descend(); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), span1); + + // Descend into children of #span1. + NGFragmentChildIterator iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), float1); + + // Descend into children of #float1. + NGFragmentChildIterator iterator4 = iterator3.Descend(); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child); + + EXPECT_FALSE(iterator4.Advance()); + EXPECT_FALSE(iterator3.Advance()); + EXPECT_FALSE(iterator2.Advance()); + EXPECT_FALSE(iterator1.Advance()); +} + +TEST_F(NGFragmentChildIteratorTest, AbsposAndLine) { + SetBodyInnerHTML(R"HTML( + <div id="container" style="position:relative;"> + <div id="abspos" style="position:absolute;"></div> + xxx + </div> + )HTML"); + + const LayoutObject* abspos = GetLayoutObjectByElementId("abspos"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator1(*container.get()); + + const NGPhysicalBoxFragment* fragment = iterator1->BoxFragment(); + EXPECT_FALSE(fragment); + const NGFragmentItem* item = iterator1->FragmentItem(); + ASSERT_TRUE(item); + EXPECT_EQ(item->Type(), NGFragmentItem::kLine); + + // Descend into the line box. + NGFragmentChildIterator iterator2 = iterator1.Descend(); + + fragment = iterator2->BoxFragment(); + EXPECT_FALSE(fragment); + item = iterator2->FragmentItem(); + ASSERT_TRUE(item); + EXPECT_TRUE(item->IsText()); + EXPECT_FALSE(iterator2.Advance()); + + // The abspos is a sibling of the line box. + EXPECT_TRUE(iterator1.Advance()); + fragment = iterator1->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), abspos); + EXPECT_FALSE(iterator1.Advance()); +} + +TEST_F(NGFragmentChildIteratorTest, BasicMulticol) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div id="mc" style="columns:3; padding:2px; column-fill:auto; column-gap:10px; width:320px; height:100px;"> + <div id="child" style="margin-top:30px; margin-left:4px; height:200px;"></div> + </div> + </div> + )HTML"); + + const LayoutObject* mc = GetLayoutObjectByElementId("mc"); + const LayoutObject* child = GetLayoutObjectByElementId("child"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator(*container.get()); + + const NGPhysicalBoxFragment* fragment = iterator->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), mc); + + // First column. + NGFragmentChildIterator child_iterator = iterator.Descend(); + fragment = child_iterator->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_EQ(child_iterator->Link().offset.top, LayoutUnit(2)); + EXPECT_EQ(child_iterator->Link().offset.left, LayoutUnit(2)); + EXPECT_EQ(fragment->Size().height, LayoutUnit(100)); + EXPECT_FALSE(fragment->GetLayoutObject()); + EXPECT_FALSE(child_iterator->BlockBreakToken()); + + NGFragmentChildIterator grandchild_iterator = child_iterator.Descend(); + fragment = grandchild_iterator->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(grandchild_iterator->Link().offset.top, LayoutUnit(30)); + EXPECT_EQ(grandchild_iterator->Link().offset.left, LayoutUnit(4)); + EXPECT_EQ(fragment->Size().height, LayoutUnit(70)); + EXPECT_EQ(fragment->GetLayoutObject(), child); + EXPECT_FALSE(grandchild_iterator.Advance()); + EXPECT_FALSE(grandchild_iterator->BlockBreakToken()); + + // Second column. + ASSERT_TRUE(child_iterator.Advance()); + fragment = child_iterator->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_EQ(child_iterator->Link().offset.top, LayoutUnit(2)); + EXPECT_EQ(child_iterator->Link().offset.left, LayoutUnit(112)); + EXPECT_EQ(fragment->Size().height, LayoutUnit(100)); + EXPECT_FALSE(fragment->GetLayoutObject()); + const auto* break_token = child_iterator->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(100)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc); + + grandchild_iterator = child_iterator.Descend(); + fragment = grandchild_iterator->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(grandchild_iterator->Link().offset.top, LayoutUnit(0)); + EXPECT_EQ(grandchild_iterator->Link().offset.left, LayoutUnit(4)); + EXPECT_EQ(fragment->Size().height, LayoutUnit(100)); + EXPECT_EQ(fragment->GetLayoutObject(), child); + EXPECT_FALSE(grandchild_iterator.Advance()); + break_token = grandchild_iterator->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(70)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child); + + // Third column. + ASSERT_TRUE(child_iterator.Advance()); + fragment = child_iterator->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_EQ(child_iterator->Link().offset.top, LayoutUnit(2)); + EXPECT_EQ(child_iterator->Link().offset.left, LayoutUnit(222)); + EXPECT_EQ(fragment->Size().height, LayoutUnit(100)); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = child_iterator->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(200)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc); + + grandchild_iterator = child_iterator.Descend(); + fragment = grandchild_iterator->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(grandchild_iterator->Link().offset.top, LayoutUnit(0)); + EXPECT_EQ(grandchild_iterator->Link().offset.left, LayoutUnit(4)); + EXPECT_EQ(fragment->Size().height, LayoutUnit(30)); + EXPECT_EQ(fragment->GetLayoutObject(), child); + EXPECT_FALSE(grandchild_iterator.Advance()); + break_token = grandchild_iterator->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(170)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child); + + EXPECT_FALSE(child_iterator.Advance()); + EXPECT_FALSE(iterator.Advance()); +} + +TEST_F(NGFragmentChildIteratorTest, ColumnSpanner) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div id="mc" style="columns:2;"> + <div id="child"> + <div id="grandchild1" style="height:150px;"></div> + <div id="spanner" style="column-span:all; height:11px;"></div> + <div id="grandchild2" style="height:66px;"></div> + </div> + </div> + </div> + )HTML"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator1(*container.get()); + + const LayoutObject* mc = GetLayoutObjectByElementId("mc"); + const LayoutObject* child = GetLayoutObjectByElementId("child"); + const LayoutObject* spanner = GetLayoutObjectByElementId("spanner"); + const LayoutObject* grandchild1 = GetLayoutObjectByElementId("grandchild1"); + const LayoutObject* grandchild2 = GetLayoutObjectByElementId("grandchild2"); + + const NGPhysicalBoxFragment* fragment = iterator1->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), mc); + + // First column before spanner. + NGFragmentChildIterator iterator2 = iterator1.Descend(); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_EQ(fragment->Size().height, LayoutUnit(75)); + EXPECT_FALSE(fragment->GetLayoutObject()); + EXPECT_FALSE(iterator2->BlockBreakToken()); + + // First fragment for #child. + NGFragmentChildIterator iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(75)); + EXPECT_EQ(fragment->GetLayoutObject(), child); + EXPECT_FALSE(iterator3->BlockBreakToken()); + + // First fragment for #grandchild1. + NGFragmentChildIterator iterator4 = iterator3.Descend(); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(75)); + EXPECT_EQ(fragment->GetLayoutObject(), grandchild1); + EXPECT_FALSE(iterator4->BlockBreakToken()); + EXPECT_FALSE(iterator4.Advance()); + EXPECT_FALSE(iterator3.Advance()); + + // Second column before spanner. + EXPECT_TRUE(iterator2.Advance()); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_EQ(fragment->Size().height, LayoutUnit(75)); + EXPECT_FALSE(fragment->GetLayoutObject()); + const auto* break_token = iterator2->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(75)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc); + + // Second fragment for #child. + iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(75)); + EXPECT_EQ(fragment->GetLayoutObject(), child); + break_token = iterator3->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(75)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child); + + // Second fragment for #grandchild1. + iterator4 = iterator3.Descend(); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(75)); + EXPECT_EQ(fragment->GetLayoutObject(), grandchild1); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(75)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), grandchild1); + EXPECT_FALSE(iterator4.Advance()); + EXPECT_FALSE(iterator3.Advance()); + + // The spanner. + EXPECT_TRUE(iterator2.Advance()); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(11)); + EXPECT_EQ(fragment->GetLayoutObject(), spanner); + EXPECT_FALSE(iterator2->BlockBreakToken()); + + // First column after spanner. + EXPECT_TRUE(iterator2.Advance()); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_EQ(fragment->Size().height, LayoutUnit(33)); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = iterator2->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(150)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc); + + // Third fragment for #child. + iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(33)); + EXPECT_EQ(fragment->GetLayoutObject(), child); + break_token = iterator3->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(150)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child); + + // First fragment for #grandchild2. + iterator4 = iterator3.Descend(); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(33)); + EXPECT_EQ(fragment->GetLayoutObject(), grandchild2); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_TRUE(break_token->IsBreakBefore()); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), grandchild2); + EXPECT_FALSE(iterator4.Advance()); + EXPECT_FALSE(iterator3.Advance()); + + // Second column after spanner. + EXPECT_TRUE(iterator2.Advance()); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_EQ(fragment->Size().height, LayoutUnit(33)); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = iterator2->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(183)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc); + + // Fourth fragment for #child. + iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(33)); + EXPECT_EQ(fragment->GetLayoutObject(), child); + break_token = iterator3->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(183)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child); + + // Second fragment for #grandchild2. + iterator4 = iterator3.Descend(); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->Size().height, LayoutUnit(33)); + EXPECT_EQ(fragment->GetLayoutObject(), grandchild2); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(33)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), grandchild2); + EXPECT_FALSE(iterator4.Advance()); + EXPECT_FALSE(iterator3.Advance()); + + EXPECT_FALSE(iterator2.Advance()); + EXPECT_FALSE(iterator1.Advance()); +} + +TEST_F(NGFragmentChildIteratorTest, NestedWithColumnSpanner) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div id="mc1" style="columns:2; column-fill:auto; height:100px;"> + <div id="mc2" style="columns:2;"> + <div id="child1" style="height:150px;"></div> + <div id="spanner1" style="column-span:all;"> + <div id="spanner1child" style="height:55px;"></div> + </div> + <div id="child2" style="height:50px;"></div> + <div id="spanner2" style="column-span:all;"> + <div id="spanner2child" style="height:20px;"></div> + </div> + <div id="child3" style="height:20px;"></div> + </div> + </div> + </div> + )HTML"); + + scoped_refptr<const NGPhysicalBoxFragment> container = + RunBlockLayoutAlgorithm(GetElementById("container")); + NGFragmentChildIterator iterator1(*container.get()); + + const LayoutObject* mc1 = GetLayoutObjectByElementId("mc1"); + const LayoutObject* mc2 = GetLayoutObjectByElementId("mc2"); + const LayoutObject* child1 = GetLayoutObjectByElementId("child1"); + const LayoutObject* child2 = GetLayoutObjectByElementId("child2"); + const LayoutObject* child3 = GetLayoutObjectByElementId("child3"); + const LayoutObject* spanner1 = GetLayoutObjectByElementId("spanner1"); + const LayoutObject* spanner2 = GetLayoutObjectByElementId("spanner2"); + const LayoutObject* spanner1child = + GetLayoutObjectByElementId("spanner1child"); + const LayoutObject* spanner2child = + GetLayoutObjectByElementId("spanner2child"); + + const NGPhysicalBoxFragment* fragment = iterator1->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), mc1); + + // First outer column. + NGFragmentChildIterator iterator2 = iterator1.Descend(); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + EXPECT_FALSE(iterator2->BlockBreakToken()); + + // First fragment for #mc2. + NGFragmentChildIterator iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), mc2); + EXPECT_FALSE(iterator3->BlockBreakToken()); + + // First inner column in first outer column. + NGFragmentChildIterator iterator4 = iterator3.Descend(); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + EXPECT_FALSE(iterator4->BlockBreakToken()); + + // First fragment for #child1. + NGFragmentChildIterator iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child1); + EXPECT_FALSE(iterator5->BlockBreakToken()); + EXPECT_FALSE(iterator5.Advance()); + + // Second inner column in first outer column. + EXPECT_TRUE(iterator4.Advance()); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + const auto* break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(75)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc2); + + // Second fragment for #child1. + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child1); + break_token = iterator5->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(75)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child1); + + // First fragment for #spanner1 (it's split into the first and second outer + // columns). + EXPECT_TRUE(iterator4.Advance()); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), spanner1); + EXPECT_FALSE(iterator4->BlockBreakToken()); + + // First fragment for #spanner1child + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), spanner1child); + EXPECT_FALSE(iterator5->BlockBreakToken()); + EXPECT_FALSE(iterator5.Advance()); + EXPECT_FALSE(iterator4.Advance()); + EXPECT_FALSE(iterator3.Advance()); + + // Second outer column + EXPECT_TRUE(iterator2.Advance()); + fragment = iterator2->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = iterator2->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(100)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc1); + + // Second fragment for #mc2. + iterator3 = iterator2.Descend(); + fragment = iterator3->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), mc2); + break_token = iterator3->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(100)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc2); + + // Second fragment for #spanner1 (it's split into the first and second outer + // columns). + iterator4 = iterator3.Descend(); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), spanner1); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(25)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), spanner1); + + // Second fragment for #spanner1child. + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), spanner1child); + break_token = iterator5->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(25)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), spanner1child); + EXPECT_FALSE(iterator5.Advance()); + + // First inner column after first spanner in second outer column. + EXPECT_TRUE(iterator4.Advance()); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(150)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc2); + + // First fragment for #child2. + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child2); + break_token = iterator5->BlockBreakToken(); + EXPECT_TRUE(break_token); + EXPECT_TRUE(break_token->IsBreakBefore()); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child2); + EXPECT_FALSE(iterator5.Advance()); + + // Second inner column after first spanner in second outer column. + EXPECT_TRUE(iterator4.Advance()); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(175)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc2); + + // Second fragment for #child2. + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child2); + break_token = iterator5->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(25)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child2); + EXPECT_FALSE(iterator5.Advance()); + + // The only fragment for #spanner2 + EXPECT_TRUE(iterator4.Advance()); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), spanner2); + EXPECT_FALSE(iterator4->BlockBreakToken()); + + // First fragment for #spanner2child + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), spanner2child); + EXPECT_FALSE(iterator5->BlockBreakToken()); + EXPECT_FALSE(iterator5.Advance()); + + // First inner column after second spanner in second outer column. + EXPECT_TRUE(iterator4.Advance()); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(200)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc2); + + // First fragment for #child3. + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child3); + break_token = iterator5->BlockBreakToken(); + EXPECT_TRUE(break_token); + EXPECT_TRUE(break_token->IsBreakBefore()); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child3); + EXPECT_FALSE(iterator5.Advance()); + + // Second inner column after second spanner in second outer column. + EXPECT_TRUE(iterator4.Advance()); + fragment = iterator4->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_TRUE(fragment->IsColumnBox()); + EXPECT_FALSE(fragment->GetLayoutObject()); + break_token = iterator4->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(210)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), mc2); + + // Second fragment for #child3. + iterator5 = iterator4.Descend(); + fragment = iterator5->BoxFragment(); + ASSERT_TRUE(fragment); + EXPECT_EQ(fragment->GetLayoutObject(), child3); + break_token = iterator5->BlockBreakToken(); + ASSERT_TRUE(break_token); + EXPECT_EQ(break_token->ConsumedBlockSize(), LayoutUnit(10)); + EXPECT_EQ(break_token->InputNode().GetLayoutBox(), child3); + EXPECT_FALSE(iterator5.Advance()); + EXPECT_FALSE(iterator4.Advance()); + EXPECT_FALSE(iterator3.Advance()); + EXPECT_FALSE(iterator2.Advance()); + EXPECT_FALSE(iterator1.Advance()); +} + +} // anonymous namespace +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_test.cc new file mode 100644 index 00000000000..a0ddc40690f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_test.cc @@ -0,0 +1,197 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.h" +#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" +#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" + +namespace blink { +namespace { + +class NGFragmentationTest : public NGBaseLayoutAlgorithmTest, + private ScopedLayoutNGBlockFragmentationForTest { + protected: + NGFragmentationTest() : ScopedLayoutNGBlockFragmentationForTest(true) {} + + scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm( + Element* element) { + NGBlockNode container(ToLayoutBox(element->GetLayoutObject())); + NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( + WritingMode::kHorizontalTb, TextDirection::kLtr, + LogicalSize(LayoutUnit(1000), kIndefiniteSize)); + return NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space); + } +}; + +TEST_F(NGFragmentationTest, MultipleFragments) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div style="columns:3; width:620px; column-fill:auto; height:100px; column-gap:10px;"> + <div id="outer1" style="height:150px;"> + <div id="inner1" style="height:250px;"></div> + <div id="inner2" style="height:10px;"></div> + </div> + <div id="outer2" style="height:90px;"></div> + </div> + </div> + )HTML"); + + RunBlockLayoutAlgorithm(GetElementById("container")); + const LayoutBox* outer1 = ToLayoutBox(GetLayoutObjectByElementId("outer1")); + const LayoutBox* outer2 = ToLayoutBox(GetLayoutObjectByElementId("outer2")); + const LayoutBox* inner1 = ToLayoutBox(GetLayoutObjectByElementId("inner1")); + const LayoutBox* inner2 = ToLayoutBox(GetLayoutObjectByElementId("inner2")); + + EXPECT_EQ(outer1->PhysicalFragmentCount(), 3u); + EXPECT_EQ(outer2->PhysicalFragmentCount(), 2u); + EXPECT_EQ(inner1->PhysicalFragmentCount(), 3u); + EXPECT_EQ(inner2->PhysicalFragmentCount(), 1u); + + // While the #outer1 box itself only needs two fragments, we need to create a + // third fragment to hold the overflowing children in the third column. + EXPECT_EQ(outer1->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 100)); + EXPECT_EQ(outer1->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 50)); + EXPECT_EQ(outer1->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 0)); + + // #inner1 overflows its parent and uses three columns. + EXPECT_EQ(inner1->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 100)); + EXPECT_EQ(inner1->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 100)); + EXPECT_EQ(inner1->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 50)); + + // #inner2 is tiny, and only needs some space in one column (the third one). + EXPECT_EQ(inner2->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 10)); + + // #outer2 starts in the second column and ends in the third. + EXPECT_EQ(outer2->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 50)); + EXPECT_EQ(outer2->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40)); +} + +TEST_F(NGFragmentationTest, MultipleFragmentsAndColumnSpanner) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div id="multicol" style="columns:3; width:620px; column-gap:10px; orphans:1; widows:1; line-height:20px;"> + <div id="outer"> + <div id="inner1"><br><br><br><br></div> + <div id="spanner1" style="column-span:all;"></div> + <div id="inner2"><br><br><br><br><br></div> + <div id="spanner2" style="column-span:all;"></div> + <div id="inner3"><br><br><br><br><br><br><br></div> + </div> + </div> + </div> + )HTML"); + + RunBlockLayoutAlgorithm(GetElementById("container")); + const LayoutBox* multicol = + ToLayoutBox(GetLayoutObjectByElementId("multicol")); + const LayoutBox* outer = ToLayoutBox(GetLayoutObjectByElementId("outer")); + const LayoutBox* inner1 = ToLayoutBox(GetLayoutObjectByElementId("inner1")); + const LayoutBox* inner2 = ToLayoutBox(GetLayoutObjectByElementId("inner2")); + const LayoutBox* inner3 = ToLayoutBox(GetLayoutObjectByElementId("inner3")); + const LayoutBox* spanner1 = + ToLayoutBox(GetLayoutObjectByElementId("spanner1")); + const LayoutBox* spanner2 = + ToLayoutBox(GetLayoutObjectByElementId("spanner2")); + + EXPECT_EQ(multicol->PhysicalFragmentCount(), 1u); + + // #outer will create 8 fragments: 2 for the 2 columns before the first + // spanner, 3 for the 3 columns between the two spanners, and 3 for the 3 + // columns after the last spanner. + EXPECT_EQ(outer->PhysicalFragmentCount(), 8u); + + // #inner1 has 4 lines split into 2 columns. + EXPECT_EQ(inner1->PhysicalFragmentCount(), 2u); + + // #inner2 has 5 lines split into 3 columns. + EXPECT_EQ(inner2->PhysicalFragmentCount(), 3u); + + // #inner3 has 8 lines split into 3 columns. + EXPECT_EQ(inner3->PhysicalFragmentCount(), 3u); + + EXPECT_EQ(spanner1->PhysicalFragmentCount(), 1u); + EXPECT_EQ(spanner2->PhysicalFragmentCount(), 1u); + + EXPECT_EQ(multicol->GetPhysicalFragment(0)->Size(), PhysicalSize(620, 140)); + EXPECT_EQ(outer->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(outer->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(outer->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(outer->GetPhysicalFragment(3)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(outer->GetPhysicalFragment(4)->Size(), PhysicalSize(200, 20)); + EXPECT_EQ(outer->GetPhysicalFragment(5)->Size(), PhysicalSize(200, 60)); + EXPECT_EQ(outer->GetPhysicalFragment(6)->Size(), PhysicalSize(200, 60)); + EXPECT_EQ(outer->GetPhysicalFragment(7)->Size(), PhysicalSize(200, 20)); + EXPECT_EQ(inner1->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(inner1->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(inner2->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(inner2->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40)); + EXPECT_EQ(inner2->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 20)); + EXPECT_EQ(inner3->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 60)); + EXPECT_EQ(inner3->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 60)); + EXPECT_EQ(inner3->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 20)); + EXPECT_EQ(spanner1->GetPhysicalFragment(0)->Size(), PhysicalSize(620, 0)); + EXPECT_EQ(spanner2->GetPhysicalFragment(0)->Size(), PhysicalSize(620, 0)); +} + +TEST_F(NGFragmentationTest, MultipleFragmentsNestedMulticol) { + SetBodyInnerHTML(R"HTML( + <div id="container"> + <div id="outer_multicol" style="columns:3; column-fill:auto; height:100px; width:620px; column-gap:10px;"> + <div id="inner_multicol" style="columns:2;"> + <div id="child1" style="width:11px; height:350px;"></div> + <div id="child2" style="width:22px; height:350px;"></div> + </div> + </div> + </div> + )HTML"); + + RunBlockLayoutAlgorithm(GetElementById("container")); + const LayoutBox* outer_multicol = + ToLayoutBox(GetLayoutObjectByElementId("outer_multicol")); + const LayoutBox* inner_multicol = + ToLayoutBox(GetLayoutObjectByElementId("inner_multicol")); + const LayoutBox* child1 = ToLayoutBox(GetLayoutObjectByElementId("child1")); + const LayoutBox* child2 = ToLayoutBox(GetLayoutObjectByElementId("child2")); + + EXPECT_EQ(outer_multicol->PhysicalFragmentCount(), 1u); + + // The content is too tall (350px + 350px, column height 100px, 2*3 columns = + // 600px) and will use one more column than we have specified. + EXPECT_EQ(inner_multicol->PhysicalFragmentCount(), 4u); + + // 350px tall content with a column height of 100px will require 4 fragments. + EXPECT_EQ(child1->PhysicalFragmentCount(), 4u); + EXPECT_EQ(child2->PhysicalFragmentCount(), 4u); + + EXPECT_EQ(outer_multicol->GetPhysicalFragment(0)->Size(), + PhysicalSize(620, 100)); + + EXPECT_EQ(inner_multicol->GetPhysicalFragment(0)->Size(), + PhysicalSize(200, 100)); + EXPECT_EQ(inner_multicol->GetPhysicalFragment(1)->Size(), + PhysicalSize(200, 100)); + EXPECT_EQ(inner_multicol->GetPhysicalFragment(2)->Size(), + PhysicalSize(200, 100)); + EXPECT_EQ(inner_multicol->GetPhysicalFragment(3)->Size(), + PhysicalSize(200, 100)); + + // #child1 starts at the beginning of a column, so the last fragment will be + // shorter than the rest. + EXPECT_EQ(child1->GetPhysicalFragment(0)->Size(), PhysicalSize(11, 100)); + EXPECT_EQ(child1->GetPhysicalFragment(1)->Size(), PhysicalSize(11, 100)); + EXPECT_EQ(child1->GetPhysicalFragment(2)->Size(), PhysicalSize(11, 100)); + EXPECT_EQ(child1->GetPhysicalFragment(3)->Size(), PhysicalSize(11, 50)); + + // #child2 starts in the middle of a column, so the first fragment will be + // shorter than the rest. + EXPECT_EQ(child2->GetPhysicalFragment(0)->Size(), PhysicalSize(22, 50)); + EXPECT_EQ(child2->GetPhysicalFragment(1)->Size(), PhysicalSize(22, 100)); + EXPECT_EQ(child2->GetPhysicalFragment(2)->Size(), PhysicalSize(22, 100)); + EXPECT_EQ(child2->GetPhysicalFragment(3)->Size(), PhysicalSize(22, 100)); +} + +} // anonymous namespace +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc index 72a107d1f7b..327212ca3a8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc @@ -164,11 +164,20 @@ NGBreakAppeal CalculateBreakAppealInside(const NGConstraintSpace& space, } void SetupFragmentation(const NGConstraintSpace& parent_space, + const NGLayoutInputNode& child, LayoutUnit fragmentainer_offset_delta, NGConstraintSpaceBuilder* builder, bool is_new_fc) { DCHECK(parent_space.HasBlockFragmentation()); + // If the child is truly unbreakable, it won't participate in block + // fragmentation. If it's too tall to fit, it will either overflow the + // fragmentainer or get brutally sliced into pieces (without looking for + // allowed breakpoints, since there are none, by definition), depending on + // fragmentation type (multicol vs. printing). + if (child.IsMonolithic()) + return; + builder->SetFragmentainerBlockSize(parent_space.FragmentainerBlockSize()); builder->SetFragmentainerOffsetAtBfc(parent_space.FragmentainerOffsetAtBfc() + fragmentainer_offset_delta); @@ -179,11 +188,20 @@ void SetupFragmentation(const NGConstraintSpace& parent_space, } void FinishFragmentation(const NGConstraintSpace& space, + const NGBlockBreakToken* previous_break_token, LayoutUnit block_size, LayoutUnit intrinsic_block_size, - LayoutUnit previously_consumed_block_size, LayoutUnit space_left, NGBoxFragmentBuilder* builder) { + LayoutUnit previously_consumed_block_size; + unsigned sequence_number = 0; + if (previous_break_token && !previous_break_token->IsBreakBefore()) { + previously_consumed_block_size = previous_break_token->ConsumedBlockSize(); + sequence_number = previous_break_token->SequenceNumber() + 1; + builder->SetIsFirstForNode(false); + } + builder->SetSequenceNumber(sequence_number); + if (builder->DidBreak()) { // One of our children broke. Even if we fit within the remaining space, we // need to prepare a break token. @@ -271,11 +289,13 @@ void BreakBeforeChild(const NGConstraintSpace& space, bool is_forced_break, NGBoxFragmentBuilder* builder) { #if DCHECK_IS_ON() - // In order to successfully break before a node, this has to be its first - // fragment. - const auto& physical_fragment = layout_result.PhysicalFragment(); - DCHECK(!physical_fragment.IsBox() || - To<NGPhysicalBoxFragment>(physical_fragment).IsFirstForNode()); + if (layout_result.Status() == NGLayoutResult::kSuccess) { + // In order to successfully break before a node, this has to be its first + // fragment. + const auto& physical_fragment = layout_result.PhysicalFragment(); + DCHECK(!physical_fragment.IsBox() || + To<NGPhysicalBoxFragment>(physical_fragment).IsFirstForNode()); + } #endif // Report space shortage. Note that we're not doing this for line boxes here @@ -310,7 +330,10 @@ void PropagateSpaceShortage(const NGConstraintSpace& space, LayoutUnit space_shortage; if (layout_result.MinimalSpaceShortage() == LayoutUnit::Max()) { // Calculate space shortage: Figure out how much more space would have been - // sufficient to make the child fit right here in the current fragment. + // sufficient to make the child fragment fit right here in the current + // fragmentainer. If layout aborted, though, we can't propagate anything. + if (layout_result.Status() != NGLayoutResult::kSuccess) + return; NGFragment fragment(space.GetWritingMode(), layout_result.PhysicalFragment()); space_shortage = fragmentainer_block_offset + fragment.BlockSize() - @@ -335,6 +358,13 @@ bool MovePastBreakpoint(const NGConstraintSpace& space, LayoutUnit fragmentainer_block_offset, NGBreakAppeal appeal_before, NGBoxFragmentBuilder* builder) { + if (layout_result.Status() != NGLayoutResult::kSuccess) { + // Layout aborted - no fragment was produced. There's nothing to move + // past. We need to break before. + DCHECK_EQ(layout_result.Status(), NGLayoutResult::kOutOfFragmentainerSpace); + return false; + } + const auto& physical_fragment = layout_result.PhysicalFragment(); NGFragment fragment(space.GetWritingMode(), physical_fragment); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h index 3f0f844f3a1..48ca63e28a5 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h @@ -6,6 +6,8 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENTATION_UTILS_H_ #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" @@ -45,6 +47,14 @@ inline bool IsResumingLayout(const NGBlockBreakToken* token) { return token && !token->IsBreakBefore(); } +// Return true if the fragment to be generated for the specified item is going +// to be the first fragment for the node. +inline bool IsFirstForNode(const NGInlineItem& item, + const NGInlineBreakToken* token) { + return item.IsFirstForNode() && + (!token || item.StartOffset() >= token->TextOffset()); +} + // Calculate the final "break-between" value at a class A or C breakpoint. This // is the combination of all break-before and break-after values that met at the // breakpoint. @@ -93,15 +103,16 @@ inline void AdjustForFragmentation(const NGBlockBreakToken* break_token, // formatting context starts in a previous fragmentainer; the offset from the // current fragmentainer block-start. void SetupFragmentation(const NGConstraintSpace& parent_space, + const NGLayoutInputNode& child, LayoutUnit fragmentainer_offset_delta, NGConstraintSpaceBuilder*, bool is_new_fc); // Write fragmentation information to the fragment builder after layout. void FinishFragmentation(const NGConstraintSpace&, + const NGBlockBreakToken* previous_break_token, LayoutUnit block_size, LayoutUnit intrinsic_block_size, - LayoutUnit previously_consumed_block_size, LayoutUnit space_left, NGBoxFragmentBuilder*); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h index 2ef8605d97e..b04949398e8 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h @@ -7,7 +7,7 @@ #include "base/optional.h" #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" @@ -17,7 +17,7 @@ namespace blink { class ComputedStyle; class NGEarlyBreak; class NGLayoutResult; -struct MinMaxSizeInput; +struct MinMaxSizesInput; // Operations provided by a layout algorithm. class NGLayoutAlgorithmOperations { @@ -33,8 +33,8 @@ class NGLayoutAlgorithmOperations { // account. If the return value is empty, the caller is expected to synthesize // this value from the overflow rect returned from Layout called with an // available width of 0 and LayoutUnit::max(), respectively. - virtual base::Optional<MinMaxSize> ComputeMinMaxSize( - const MinMaxSizeInput&) const { + virtual base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const { return base::nullopt; } }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc index f46d17d48d1..28d5bcce744 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc @@ -8,9 +8,8 @@ #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h" #include "third_party/blink/renderer/core/layout/layout_replaced.h" #include "third_party/blink/renderer/core/layout/layout_view.h" -#include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" @@ -61,39 +60,23 @@ void AppendNodeToString(NGLayoutInputNode node, } // namespace -MinMaxSize NGLayoutInputNode::ComputeMinMaxSize( +MinMaxSizes NGLayoutInputNode::ComputeMinMaxSizes( WritingMode writing_mode, - const MinMaxSizeInput& input, + const MinMaxSizesInput& input, const NGConstraintSpace* space) { if (auto* inline_node = DynamicTo<NGInlineNode>(this)) - return inline_node->ComputeMinMaxSize(writing_mode, input, space); - return To<NGBlockNode>(*this).ComputeMinMaxSize(writing_mode, input, space); + return inline_node->ComputeMinMaxSizes(writing_mode, input, space); + return To<NGBlockNode>(*this).ComputeMinMaxSizes(writing_mode, input, space); } void NGLayoutInputNode::IntrinsicSize( base::Optional<LayoutUnit>* computed_inline_size, - base::Optional<LayoutUnit>* computed_block_size, - LogicalSize* aspect_ratio) const { + base::Optional<LayoutUnit>* computed_block_size) const { DCHECK(IsReplaced()); - LayoutUnit override_inline_size = OverrideIntrinsicContentInlineSize(); - if (override_inline_size != kIndefiniteSize) - *computed_inline_size = override_inline_size; - - LayoutUnit override_block_size = OverrideIntrinsicContentBlockSize(); - if (override_block_size != kIndefiniteSize) - *computed_block_size = override_block_size; - - if (ShouldApplySizeContainment()) { - if (!*computed_inline_size) - *computed_inline_size = LayoutUnit(); - if (!*computed_block_size) - *computed_block_size = LayoutUnit(); - } - if (*computed_inline_size && *computed_block_size) { - *aspect_ratio = LogicalSize(**computed_inline_size, **computed_block_size); + GetOverrideIntrinsicSize(computed_inline_size, computed_block_size); + if (*computed_inline_size && *computed_block_size) return; - } IntrinsicSizingInfo legacy_sizing_info; @@ -102,9 +85,6 @@ void NGLayoutInputNode::IntrinsicSize( *computed_inline_size = LayoutUnit(legacy_sizing_info.size.Width()); if (!*computed_block_size && legacy_sizing_info.has_height) *computed_block_size = LayoutUnit(legacy_sizing_info.size.Height()); - *aspect_ratio = - LogicalSize(LayoutUnit(legacy_sizing_info.aspect_ratio.Width()), - LayoutUnit(legacy_sizing_info.aspect_ratio.Height())); } NGLayoutInputNode NGLayoutInputNode::NextSibling() { @@ -134,8 +114,39 @@ void NGLayoutInputNode::ShowNodeTree() const { StringBuilder string_builder; string_builder.Append(".:: LayoutNG Node Tree ::.\n"); AppendNodeToString(*this, &string_builder); - fprintf(stderr, "%s\n", string_builder.ToString().Utf8().c_str()); + DLOG(INFO) << "\n" << string_builder.ToString().Utf8(); } #endif +void NGLayoutInputNode::GetOverrideIntrinsicSize( + base::Optional<LayoutUnit>* computed_inline_size, + base::Optional<LayoutUnit>* computed_block_size) const { + DCHECK(IsReplaced()); + + LayoutUnit override_inline_size = OverrideIntrinsicContentInlineSize(); + if (override_inline_size != kIndefiniteSize) { + *computed_inline_size = override_inline_size; + } else { + LayoutUnit default_inline_size = DefaultIntrinsicContentInlineSize(); + if (default_inline_size != kIndefiniteSize) + *computed_inline_size = default_inline_size; + } + + LayoutUnit override_block_size = OverrideIntrinsicContentBlockSize(); + if (override_block_size != kIndefiniteSize) { + *computed_block_size = override_block_size; + } else { + LayoutUnit default_block_size = DefaultIntrinsicContentBlockSize(); + if (default_block_size != kIndefiniteSize) + *computed_block_size = default_block_size; + } + + if (ShouldApplySizeContainment()) { + if (!*computed_inline_size) + *computed_inline_size = LayoutUnit(); + if (!*computed_block_size) + *computed_block_size = LayoutUnit(); + } +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h index c77e16c4310..45acfcb0f7c 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h @@ -11,7 +11,7 @@ #include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/ng/layout_box_utils.h" -#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_outside_list_marker.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h" #include "third_party/blink/renderer/platform/text/writing_mode.h" @@ -24,16 +24,13 @@ class LayoutObject; class LayoutBox; class NGConstraintSpace; class NGPaintFragment; -struct MinMaxSize; -struct LogicalSize; +struct MinMaxSizes; struct PhysicalSize; -enum class NGMinMaxSizeType { kContentBoxSize, kBorderBoxSize }; - // Input to the min/max inline size calculation algorithm for child nodes. Child // nodes within the same formatting context need to know which floats are beside // them. -struct MinMaxSizeInput { +struct MinMaxSizesInput { // The min-max size calculation (un-intuitively) requires a percentage // resolution size! // This occurs when a replaced element has an intrinsic size. E.g. @@ -44,14 +41,11 @@ struct MinMaxSizeInput { // // As we don't perform any tree walking, we need to pass the percentage // resolution block-size for min/max down the min/max size calculation. - explicit MinMaxSizeInput(LayoutUnit percentage_resolution_block_size) + explicit MinMaxSizesInput(LayoutUnit percentage_resolution_block_size) : percentage_resolution_block_size(percentage_resolution_block_size) {} LayoutUnit float_left_inline_size; LayoutUnit float_right_inline_size; LayoutUnit percentage_resolution_block_size; - - // Whether to return the size as a content-box size or border-box size. - NGMinMaxSizeType size_type = NGMinMaxSizeType::kBorderBoxSize; }; // Represents the input to a layout algorithm for a given node. The layout @@ -108,11 +102,11 @@ class CORE_EXPORT NGLayoutInputNode { } bool IsListItem() const { return IsBlock() && box_->IsLayoutNGListItem(); } bool IsListMarker() const { - return IsBlock() && box_->IsLayoutNGListMarker(); + return IsBlock() && box_->IsLayoutNGOutsideListMarker(); } bool ListMarkerOccupiesWholeLine() const { DCHECK(IsListMarker()); - return ToLayoutNGListMarker(box_)->NeedsOccupyWholeLine(); + return ToLayoutNGOutsideListMarker(box_)->NeedsOccupyWholeLine(); } bool IsFieldsetContainer() const { return IsBlock() && box_->IsLayoutNGFieldset(); @@ -123,6 +117,9 @@ class CORE_EXPORT NGLayoutInputNode { bool IsRenderedLegend() const { return IsBlock() && box_->IsRenderedLegend(); } + bool IsTable() const { return IsBlock() && box_->IsTable(); } + + bool IsMathRoot() const { return box_->IsMathMLRoot(); } bool IsAnonymousBlock() const { return box_->IsAnonymousBlock(); } @@ -159,16 +156,16 @@ class CORE_EXPORT NGLayoutInputNode { } // Returns border box. - MinMaxSize ComputeMinMaxSize(WritingMode, - const MinMaxSizeInput&, - const NGConstraintSpace* = nullptr); + MinMaxSizes ComputeMinMaxSizes(WritingMode, + const MinMaxSizesInput&, + const NGConstraintSpace* = nullptr); // Returns intrinsic sizing information for replaced elements. // ComputeReplacedSize can use it to compute actual replaced size. // Corresponds to Legacy's LayoutReplaced::IntrinsicSizingInfo. + // Use NGBlockNode::GetAspectRatio to get the aspect ratio. void IntrinsicSize(base::Optional<LayoutUnit>* computed_inline_size, - base::Optional<LayoutUnit>* computed_block_size, - LogicalSize* aspect_ratio) const; + base::Optional<LayoutUnit>* computed_block_size) const; // Returns the next sibling. NGLayoutInputNode NextSibling(); @@ -201,6 +198,13 @@ class CORE_EXPORT NGLayoutInputNode { return kIndefiniteSize; } + LayoutUnit DefaultIntrinsicContentInlineSize() const { + return box_->DefaultIntrinsicContentInlineSize(); + } + LayoutUnit DefaultIntrinsicContentBlockSize() const { + return box_->DefaultIntrinsicContentBlockSize(); + } + // Display locking functionality. const DisplayLockContext& GetDisplayLockContext() const { DCHECK(box_->GetDisplayLockContext()); @@ -240,6 +244,10 @@ class CORE_EXPORT NGLayoutInputNode { NGLayoutInputNode(LayoutBox* box, NGLayoutInputNodeType type) : box_(box), type_(type) {} + void GetOverrideIntrinsicSize( + base::Optional<LayoutUnit>* computed_inline_size, + base::Optional<LayoutUnit>* computed_block_size) const; + LayoutBox* box_; unsigned type_ : 1; // NGLayoutInputNodeType diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc index fac77834d22..6dc8f994eee 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc @@ -39,6 +39,7 @@ static_assert(sizeof(NGLayoutResult) == sizeof(SameSizeAsNGLayoutResult), } // namespace NGLayoutResult::NGLayoutResult( + NGBoxFragmentBuilderPassKey passkey, scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, NGBoxFragmentBuilder* builder) : NGLayoutResult(std::move(physical_fragment), @@ -48,6 +49,10 @@ NGLayoutResult::NGLayoutResult( bitfields_.subtree_modified_margin_strut = builder->subtree_modified_margin_strut_; intrinsic_block_size_ = builder->intrinsic_block_size_; + // We don't support fragment caching when block-fragmenting, so mark the + // result as non-reusable. + if (builder->has_block_fragmentation_) + EnsureRareData()->is_single_use = true; if (builder->minimal_space_shortage_ != LayoutUnit::Max()) { #if DCHECK_IS_ON() DCHECK(!HasRareData() || !rare_data_->has_tallest_unbreakable_block_size); @@ -62,10 +67,9 @@ NGLayoutResult::NGLayoutResult( rare_data->has_tallest_unbreakable_block_size = true; #endif } - if (builder->unconstrained_intrinsic_block_size_ != kIndefiniteSize && - builder->unconstrained_intrinsic_block_size_ != intrinsic_block_size_) { - EnsureRareData()->unconstrained_intrinsic_block_size_ = - builder->unconstrained_intrinsic_block_size_; + if (builder->overflow_block_size_ != kIndefiniteSize && + builder->overflow_block_size_ != intrinsic_block_size_) { + EnsureRareData()->overflow_block_size_ = builder->overflow_block_size_; } if (builder->custom_layout_data_) { EnsureRareData()->custom_layout_data = @@ -73,6 +77,8 @@ NGLayoutResult::NGLayoutResult( } if (builder->column_spanner_) EnsureRareData()->column_spanner = builder->column_spanner_; + if (builder->lines_until_clamp_) + EnsureRareData()->lines_until_clamp = *builder->lines_until_clamp_; bitfields_.initial_break_before = static_cast<unsigned>(builder->initial_break_before_); bitfields_.final_break_after = @@ -81,15 +87,20 @@ NGLayoutResult::NGLayoutResult( } NGLayoutResult::NGLayoutResult( + NGLineBoxFragmentBuilderPassKey passkey, scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, NGLineBoxFragmentBuilder* builder) : NGLayoutResult(std::move(physical_fragment), static_cast<NGContainerFragmentBuilder*>(builder)) {} -NGLayoutResult::NGLayoutResult(EStatus status, NGBoxFragmentBuilder* builder) +NGLayoutResult::NGLayoutResult(NGBoxFragmentBuilderPassKey key, + EStatus status, + NGBoxFragmentBuilder* builder) : NGLayoutResult(/* physical_fragment */ nullptr, static_cast<NGContainerFragmentBuilder*>(builder)) { bitfields_.status = status; + if (builder->lines_until_clamp_) + EnsureRareData()->lines_until_clamp = *builder->lines_until_clamp_; DCHECK_NE(status, kSuccess) << "Use the other constructor for successful layout"; } @@ -152,7 +163,7 @@ NGLayoutResult::NGLayoutResult( #if DCHECK_IS_ON() if (bitfields_.is_self_collapsing && physical_fragment_) { // A new formatting-context shouldn't be self-collapsing. - DCHECK(!physical_fragment_->IsBlockFormattingContextRoot()); + DCHECK(!physical_fragment_->IsFormattingContextRoot()); // Self-collapsing children must have a block-size of zero. NGFragment fragment(physical_fragment_->Style().GetWritingMode(), diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h index 77ff5c5b9b9..3faf3aa43aa 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result.h @@ -41,6 +41,8 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { kSuccess = 0, kBfcBlockOffsetResolved = 1, kNeedsEarlierBreak = 2, + kOutOfFragmentainerSpace = 3, + kNeedsRelayoutWithNoForcedTruncateAtLineClamp = 4, // When adding new values, make sure the bit size of |Bitfields::status| is // large enough to store. }; @@ -62,6 +64,10 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { return *physical_fragment_; } + int LinesUntilClamp() const { + return HasRareData() ? rare_data_->lines_until_clamp : 0; + } + LogicalOffset OutOfFlowPositionedOffset() const { DCHECK(bitfields_.has_oof_positioned_offset); return HasRareData() ? rare_data_->oof_positioned_offset @@ -140,16 +146,13 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { } const LayoutUnit IntrinsicBlockSize() const { - DCHECK(physical_fragment_->Type() == NGPhysicalFragment::kFragmentBox || - physical_fragment_->Type() == - NGPhysicalFragment::kFragmentRenderedLegend); + DCHECK(physical_fragment_->IsBox()); return intrinsic_block_size_; } - LayoutUnit UnconstrainedIntrinsicBlockSize() const { - return HasRareData() && rare_data_->unconstrained_intrinsic_block_size_ != - kIndefiniteSize - ? rare_data_->unconstrained_intrinsic_block_size_ + LayoutUnit OverflowBlockSize() const { + return HasRareData() && rare_data_->overflow_block_size_ != kIndefiniteSize + ? rare_data_->overflow_block_size_ : intrinsic_block_size_; } @@ -173,6 +176,14 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { return rare_data_->tallest_unbreakable_block_size; } + // Return whether this result is single-use only (true), or if it is allowed + // to be involved in cache hits in future layout passes (false). + // For example, this happens when a block is fragmented, since we don't yet + // support caching of block-fragmented results. + bool IsSingleUse() const { + return HasRareData() && rare_data_->is_single_use; + } + SerializedScriptValue* CustomLayoutData() const { return HasRareData() ? rare_data_->custom_layout_data.get() : nullptr; } @@ -285,21 +296,24 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { bool check_same_block_size = true) const; #endif - private: - friend class NGBoxFragmentBuilder; - friend class NGLineBoxFragmentBuilder; - friend class MutableForOutOfFlow; - + using NGBoxFragmentBuilderPassKey = util::PassKey<NGBoxFragmentBuilder>; + // This constructor is for a non-success status. + NGLayoutResult(NGBoxFragmentBuilderPassKey, EStatus, NGBoxFragmentBuilder*); // This constructor requires a non-null fragment and sets a success status. NGLayoutResult( + NGBoxFragmentBuilderPassKey, scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, NGBoxFragmentBuilder*); + using NGLineBoxFragmentBuilderPassKey = + util::PassKey<NGLineBoxFragmentBuilder>; // This constructor requires a non-null fragment and sets a success status. NGLayoutResult( + NGLineBoxFragmentBuilderPassKey, scoped_refptr<const NGPhysicalContainerFragment> physical_fragment, NGLineBoxFragmentBuilder*); - // This constructor is for a non-success status. - NGLayoutResult(EStatus, NGBoxFragmentBuilder*); + + private: + friend class MutableForOutOfFlow; // We don't need the copy constructor, move constructor, copy // assigmnment-operator, or move assignment-operator today. @@ -358,10 +372,12 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { }; NGExclusionSpace exclusion_space; scoped_refptr<SerializedScriptValue> custom_layout_data; - LayoutUnit unconstrained_intrinsic_block_size_ = kIndefiniteSize; + LayoutUnit overflow_block_size_ = kIndefiniteSize; #if DCHECK_IS_ON() bool has_tallest_unbreakable_block_size = false; #endif + bool is_single_use = false; + int lines_until_clamp = 0; }; bool HasRareData() const { return bitfields_.has_rare_data; } @@ -421,7 +437,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { unsigned initial_break_before : 4; // EBreakBetween unsigned final_break_after : 4; // EBreakBetween - unsigned status : 2; // EStatus + unsigned status : 3; // EStatus }; // The constraint space which generated this layout result, may not be valid diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc index 107e1cc3dc8..24df10ed942 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc @@ -19,8 +19,6 @@ namespace { class NGLayoutResultCachingTest : public NGLayoutTest {}; TEST_F(NGLayoutResultCachingTest, HitDifferentExclusionSpace) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Same BFC offset, different exclusion space. SetBodyInnerHTML(R"HTML( <style> @@ -58,8 +56,6 @@ TEST_F(NGLayoutResultCachingTest, HitDifferentExclusionSpace) { } TEST_F(NGLayoutResultCachingTest, HitDifferentBFCOffset) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space. SetBodyInnerHTML(R"HTML( <style> @@ -123,8 +119,6 @@ TEST_F(NGLayoutResultCachingTest, HitDifferentBFCOffset) { } TEST_F(NGLayoutResultCachingTest, HitDifferentBFCOffsetSameMarginStrut) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same margin-strut. SetBodyInnerHTML(R"HTML( <style> @@ -155,8 +149,6 @@ TEST_F(NGLayoutResultCachingTest, HitDifferentBFCOffsetSameMarginStrut) { } TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart1) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Same BFC offset, different exclusion space, descendant above // block start. SetBodyInnerHTML(R"HTML( @@ -195,8 +187,6 @@ TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart1) { } TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart2) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space, descendant above // block start. SetBodyInnerHTML(R"HTML( @@ -235,8 +225,6 @@ TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart2) { } TEST_F(NGLayoutResultCachingTest, HitOOFDescendantAboveBlockStart) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space, OOF-descendant above // block start. SetBodyInnerHTML(R"HTML( @@ -275,8 +263,6 @@ TEST_F(NGLayoutResultCachingTest, HitOOFDescendantAboveBlockStart) { } TEST_F(NGLayoutResultCachingTest, HitLineBoxDescendantAboveBlockStart) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space, line-box descendant above // block start. SetBodyInnerHTML(R"HTML( @@ -320,8 +306,6 @@ TEST_F(NGLayoutResultCachingTest, HitLineBoxDescendantAboveBlockStart) { } TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding1) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Same BFC offset, different exclusion space, float initially // intruding. SetBodyInnerHTML(R"HTML( @@ -358,8 +342,6 @@ TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding1) { } TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding2) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space, float initially // intruding. SetBodyInnerHTML(R"HTML( @@ -396,8 +378,6 @@ TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding2) { } TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude1) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Same BFC offset, different exclusion space, float will intrude. SetBodyInnerHTML(R"HTML( <style> @@ -433,8 +413,6 @@ TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude1) { } TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude2) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space, float will intrude. SetBodyInnerHTML(R"HTML( <style> @@ -470,8 +448,6 @@ TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude2) { } TEST_F(NGLayoutResultCachingTest, HitPushedByFloats1) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Same BFC offset, different exclusion space, pushed by floats. SetBodyInnerHTML(R"HTML( <style> @@ -507,8 +483,6 @@ TEST_F(NGLayoutResultCachingTest, HitPushedByFloats1) { } TEST_F(NGLayoutResultCachingTest, HitPushedByFloats2) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space, pushed by floats. SetBodyInnerHTML(R"HTML( <style> @@ -544,8 +518,6 @@ TEST_F(NGLayoutResultCachingTest, HitPushedByFloats2) { } TEST_F(NGLayoutResultCachingTest, MissPushedByFloats1) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Same BFC offset, different exclusion space, pushed by floats. // Miss due to shrinking offset. SetBodyInnerHTML(R"HTML( @@ -582,8 +554,6 @@ TEST_F(NGLayoutResultCachingTest, MissPushedByFloats1) { } TEST_F(NGLayoutResultCachingTest, MissPushedByFloats2) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Different BFC offset, same exclusion space, pushed by floats. // Miss due to shrinking offset. SetBodyInnerHTML(R"HTML( @@ -620,8 +590,6 @@ TEST_F(NGLayoutResultCachingTest, MissPushedByFloats2) { } TEST_F(NGLayoutResultCachingTest, HitDifferentRareData) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Same absolute fixed constraints. SetBodyInnerHTML(R"HTML( <style> @@ -651,8 +619,6 @@ TEST_F(NGLayoutResultCachingTest, HitDifferentRareData) { } TEST_F(NGLayoutResultCachingTest, HitPercentageMinWidth) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // min-width calculates to different values, but doesn't change size. SetBodyInnerHTML(R"HTML( <style> @@ -682,8 +648,6 @@ TEST_F(NGLayoutResultCachingTest, HitPercentageMinWidth) { } TEST_F(NGLayoutResultCachingTest, HitFixedMinWidth) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // min-width is always larger than the available size. SetBodyInnerHTML(R"HTML( <style> @@ -712,9 +676,151 @@ TEST_F(NGLayoutResultCachingTest, HitFixedMinWidth) { EXPECT_NE(result.get(), nullptr); } -TEST_F(NGLayoutResultCachingTest, HitShrinkToFitSameIntrinsicSizes) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); +TEST_F(NGLayoutResultCachingTest, HitShrinkToFit) { + SetBodyInnerHTML(R"HTML( + <div style="display: flow-root; width: 300px; height: 100px;"> + <div id="test1" style="float: left;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 50px;"></div> + </div> + <div id="test2" style="float: left;"> + <div style="display: inline-block; width: 350px;"></div> + <div style="display: inline-block; width: 250px;"></div> + </div> + </div> + <div style="display: flow-root; width: 400px; height: 100px;"> + <div id="src1" style="float: left;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 50px;"></div> + </div> + </div> + <div style="display: flow-root; width: 200px; height: 100px;"> + <div id="src2" style="float: left;"> + <div style="display: inline-block; width: 350px;"></div> + <div style="display: inline-block; width: 250px;"></div> + </div> + </div> + )HTML"); + + auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1")); + auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2")); + auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1")); + auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + NGConstraintSpace space = + src1->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test1->CachedLayoutResult( + space, nullptr, nullptr, &fragment_geometry, &cache_status); + // test1 was sized to its max-content size, passing an available size larger + // than the fragment should hit the cache. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); + + fragment_geometry.reset(); + space = src2->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + result = test2->CachedLayoutResult(space, nullptr, nullptr, + &fragment_geometry, &cache_status); + // test2 was sized to its min-content size in, passing an available size + // smaller than the fragment should hit the cache. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, MissShrinkToFit) { + SetBodyInnerHTML(R"HTML( + <div style="display: flow-root; width: 300px; height: 100px;"> + <div id="test1" style="float: left;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 50px;"></div> + </div> + <div id="test2" style="float: left;"> + <div style="display: inline-block; width: 350px;"></div> + <div style="display: inline-block; width: 250px;"></div> + </div> + <div id="test3" style="float: left; min-width: 80%;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 250px;"></div> + </div> + <div id="test4" style="float: left; margin-left: 75px;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 50px;"></div> + </div> + </div> + <div style="display: flow-root; width: 100px; height: 100px;"> + <div id="src1" style="float: left;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 50px;"></div> + </div> + </div> + <div style="display: flow-root; width: 400px; height: 100px;"> + <div id="src2" style="float: left;"> + <div style="display: inline-block; width: 350px;"></div> + <div style="display: inline-block; width: 250px;"></div> + </div> + <div id="src3" style="float: left; min-width: 80%;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 250px;"></div> + </div> + </div> + <div style="display: flow-root; width: 250px; height: 100px;"> + <div id="src4" style="float: left; margin-left: 75px;"> + <div style="display: inline-block; width: 150px;"></div> + <div style="display: inline-block; width: 50px;"></div> + </div> + </div> + )HTML"); + + auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1")); + auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2")); + auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3")); + auto* test4 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test4")); + auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1")); + auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2")); + auto* src3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src3")); + auto* src4 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src4")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + NGConstraintSpace space = + src1->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test1->CachedLayoutResult( + space, nullptr, nullptr, &fragment_geometry, &cache_status); + // test1 was sized to its max-content size, passing an available size smaller + // than the fragment should miss the cache. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); + EXPECT_EQ(result.get(), nullptr); + + fragment_geometry.reset(); + space = src2->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + result = test2->CachedLayoutResult(space, nullptr, nullptr, + &fragment_geometry, &cache_status); + // test2 was sized to its min-content size, passing an available size + // larger than the fragment should miss the cache. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); + EXPECT_EQ(result.get(), nullptr); + + fragment_geometry.reset(); + space = src3->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + result = test3->CachedLayoutResult(space, nullptr, nullptr, + &fragment_geometry, &cache_status); + // test3 was sized to its min-content size, however it should miss the cache + // as it has a %-min-size. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); + EXPECT_EQ(result.get(), nullptr); + + fragment_geometry.reset(); + space = src4->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + result = test4->CachedLayoutResult(space, nullptr, nullptr, + &fragment_geometry, &cache_status); + // test4 was sized to its max-content size, however it should miss the cache + // due to its margin. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); + EXPECT_EQ(result.get(), nullptr); +} +TEST_F(NGLayoutResultCachingTest, HitShrinkToFitSameIntrinsicSizes) { // We have a shrink-to-fit node, with the min, and max intrinsic sizes being // equal (the available size doesn't affect the final size). SetBodyInnerHTML(R"HTML( @@ -750,8 +856,6 @@ TEST_F(NGLayoutResultCachingTest, HitShrinkToFitSameIntrinsicSizes) { } TEST_F(NGLayoutResultCachingTest, HitShrinkToFitDifferentParent) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // The parent "bfc" node changes from shrink-to-fit, to a fixed width. But // these calculate as the same available space to the "test" element. SetBodyInnerHTML(R"HTML( @@ -786,8 +890,6 @@ TEST_F(NGLayoutResultCachingTest, HitShrinkToFitDifferentParent) { } TEST_F(NGLayoutResultCachingTest, MissQuirksModePercentageBasedChild) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Quirks-mode %-block-size child. GetDocument().SetCompatibilityMode(Document::kQuirksMode); SetBodyInnerHTML(R"HTML( @@ -822,8 +924,6 @@ TEST_F(NGLayoutResultCachingTest, MissQuirksModePercentageBasedChild) { } TEST_F(NGLayoutResultCachingTest, HitQuirksModePercentageBasedParentAndChild) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Quirks-mode %-block-size parent *and* child. Here we mark the parent as // depending on %-block-size changes, however itself doesn't change in // height. @@ -863,8 +963,6 @@ TEST_F(NGLayoutResultCachingTest, HitQuirksModePercentageBasedParentAndChild) { } TEST_F(NGLayoutResultCachingTest, HitStandardsModePercentageBasedChild) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - // Standards-mode %-block-size child. SetBodyInnerHTML(R"HTML( <style> @@ -898,8 +996,6 @@ TEST_F(NGLayoutResultCachingTest, HitStandardsModePercentageBasedChild) { } TEST_F(NGLayoutResultCachingTest, ChangeTableCellBlockSizeConstrainedness) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <style> .table { display: table; width: 300px; } @@ -964,12 +1060,9 @@ TEST_F(NGLayoutResultCachingTest, ChangeTableCellBlockSizeConstrainedness) { // height or not. We're only going to need simplified layout, though, since no // children will be affected by its height change. EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsSimplifiedLayout); - EXPECT_EQ(result.get(), nullptr); } TEST_F(NGLayoutResultCachingTest, OptimisticFloatPlacementNoRelayout) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <style> .root { display: flow-root; width: 300px; } @@ -994,8 +1087,6 @@ TEST_F(NGLayoutResultCachingTest, OptimisticFloatPlacementNoRelayout) { } TEST_F(NGLayoutResultCachingTest, SelfCollapsingShifting) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <style> .bfc { display: flow-root; width: 300px; height: 300px; } @@ -1080,8 +1171,6 @@ TEST_F(NGLayoutResultCachingTest, SelfCollapsingShifting) { } TEST_F(NGLayoutResultCachingTest, ClearancePastAdjoiningFloatsMovement) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <style> .bfc { display: flow-root; width: 300px; height: 300px; } @@ -1146,8 +1235,6 @@ TEST_F(NGLayoutResultCachingTest, ClearancePastAdjoiningFloatsMovement) { } TEST_F(NGLayoutResultCachingTest, MarginStrutMovementSelfCollapsing) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <style> .bfc { display: flow-root; width: 300px; height: 300px; } @@ -1217,8 +1304,6 @@ TEST_F(NGLayoutResultCachingTest, MarginStrutMovementSelfCollapsing) { } TEST_F(NGLayoutResultCachingTest, MarginStrutMovementInFlow) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <style> .bfc { display: flow-root; width: 300px; height: 300px; } @@ -1315,8 +1400,6 @@ TEST_F(NGLayoutResultCachingTest, MarginStrutMovementInFlow) { } TEST_F(NGLayoutResultCachingTest, MarginStrutMovementPercentage) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - SetBodyInnerHTML(R"HTML( <style> .bfc { display: flow-root; width: 300px; height: 300px; } @@ -1354,53 +1437,55 @@ TEST_F(NGLayoutResultCachingTest, MarginStrutMovementPercentage) { EXPECT_EQ(result.get(), nullptr); } -TEST_F(NGLayoutResultCachingTest, MarginStrutMovementDiscard) { - ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true); - +TEST_F(NGLayoutResultCachingTest, HitIsFixedBlockSizeIndefinite) { SetBodyInnerHTML(R"HTML( - <style> - .bfc { display: flow-root; width: 300px; height: 300px; } - </style> - <div class="bfc"> - <div style="margin-top: 10px;"> - <div id="test1"> - <div style="-webkit-margin-top-collapse: discard;">text</div> - </div> + <div style="display: flex; width: 100px; height: 100px;"> + <div id="test1" style="flex-grow: 1; min-height: 100px;"> + <div style="height: 50px;">text</div> </div> </div> - <div class="bfc"> - <div style="margin-top: 5px;"> - <div id="src1"> - <div style="-webkit-margin-top-collapse: discard;">text</div> - </div> + <div style="display: flex; width: 100px; height: 100px; align-items: stretch;"> + <div id="src1" style="flex-grow: 1; min-height: 100px;"> + <div style="height: 50px;">text</div> </div> </div> - <div class="bfc"> - <div style="margin-top: 10px;"> - <div id="test2"> - <div> - <div style="-webkit-margin-bottom-collapse: discard;"></div> - </div> - <div>text</div> - </div> + )HTML"); + + auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1")); + auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + + NGConstraintSpace space = + src1->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test1->CachedLayoutResult( + space, nullptr, nullptr, &fragment_geometry, &cache_status); + + // Even though the "align-items: stretch" will make the final fixed + // block-size indefinite, we don't have any %-block-size children, so we can + // hit the cache. + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); +} + +TEST_F(NGLayoutResultCachingTest, MissIsFixedBlockSizeIndefinite) { + SetBodyInnerHTML(R"HTML( + <!DOCTYPE html> + <div style="display: flex; width: 100px; height: 100px; align-items: start;"> + <div id="src1" style="flex-grow: 1; min-height: 100px;"> + <div style="height: 50%;">text</div> </div> </div> - <div class="bfc"> - <div style="margin-top: 5px;"> - <div id="src2"> - <div> - <div style="-webkit-margin-bottom-collapse: discard;"></div> - </div> - <div>text</div> - </div> + <div style="display: flex; width: 100px; height: 100px; align-items: stretch;"> + <div id="test1" style="flex-grow: 1; min-height: 100px;"> + <div style="height: 50%;">text</div> </div> </div> )HTML"); auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1")); - auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2")); auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1")); - auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2")); NGLayoutCacheStatus cache_status; base::Optional<NGFragmentGeometry> fragment_geometry; @@ -1410,18 +1495,62 @@ TEST_F(NGLayoutResultCachingTest, MarginStrutMovementDiscard) { scoped_refptr<const NGLayoutResult> result = test1->CachedLayoutResult( space, nullptr, nullptr, &fragment_geometry, &cache_status); - // Case 1: We can't re-use this fragment as the sub-tree discards margins. + // The "align-items: stretch" will make the final fixed block-size + // indefinite, and we have a %-block-size child, so we need to miss the + // cache. EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); EXPECT_EQ(result.get(), nullptr); +} - fragment_geometry.reset(); +TEST_F(NGLayoutResultCachingTest, HitFlexBoxMeasureAndLayout) { + ScopedLayoutNGFlexBoxForTest layout_ng_flex_box(true); + + SetBodyInnerHTML(R"HTML( + <!DOCTYPE html> + <div style="display: flex; flex-direction: column; width: 100px; height: 100px;"> + <div id="src1" style="flex-grow: 0;"> + <div style="height: 50px;"></div> + </div> + </div> + <div style="display: flex; flex-direction: column; width: 100px; height: 100px;"> + <div id="src2" style="flex-grow: 1;"> + <div style="height: 50px;"></div> + </div> + </div> + <div style="display: flex; flex-direction: column; width: 100px; height: 100px;"> + <div id="test1" style="flex-grow: 2;"> + <div style="height: 50px;"></div> + </div> + </div> + )HTML"); + + auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1")); + auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1")); + auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2")); + + NGLayoutCacheStatus cache_status; + base::Optional<NGFragmentGeometry> fragment_geometry; + + // "src1" only had one "measure" pass performed, and should hit the "measure" + // cache-slot for "test1". + NGConstraintSpace space = + src1->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); + scoped_refptr<const NGLayoutResult> result = test1->CachedLayoutResult( + space, nullptr, nullptr, &fragment_geometry, &cache_status); + + EXPECT_EQ(space.CacheSlot(), NGCacheSlot::kMeasure); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); + + // "src2" had both a "measure" and "layout" pass performed, and should hit + // the "layout" cache-slot for "test1". space = src2->GetCachedLayoutResult()->GetConstraintSpaceForCaching(); - result = test2->CachedLayoutResult(space, nullptr, nullptr, + result = test1->CachedLayoutResult(space, nullptr, nullptr, &fragment_geometry, &cache_status); - // Case 2: Also check a self-collapsing block with a block-end discard. - EXPECT_EQ(cache_status, NGLayoutCacheStatus::kNeedsLayout); - EXPECT_EQ(result.get(), nullptr); + EXPECT_EQ(space.CacheSlot(), NGCacheSlot::kLayout); + EXPECT_EQ(cache_status, NGLayoutCacheStatus::kHit); + EXPECT_NE(result.get(), nullptr); } } // namespace diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc index 229af34617d..3802930ac23 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc @@ -84,6 +84,8 @@ bool SizeMayChange(const NGBlockNode& node, DCHECK_EQ(new_space.IsFixedInlineSize(), old_space.IsFixedInlineSize()); DCHECK_EQ(new_space.IsFixedBlockSize(), old_space.IsFixedBlockSize()); + DCHECK_EQ(new_space.IsFixedBlockSizeIndefinite(), + old_space.IsFixedBlockSizeIndefinite()); DCHECK_EQ(new_space.IsShrinkToFit(), old_space.IsShrinkToFit()); DCHECK_EQ(new_space.TableCellChildLayoutMode(), old_space.TableCellChildLayoutMode()); @@ -180,17 +182,48 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry( LayoutUnit block_size = fragment_geometry.border_box_size.block_size; bool is_initial_block_size_indefinite = block_size == kIndefiniteSize; if (is_initial_block_size_indefinite) { - // The intrinsic size of column flex-boxes can depend on the - // %-resolution-block-size. This occurs when a flex-box has "max-height: - // 100%" or similar on itself. - // - // Due to this we can't use cached |NGLayoutResult::IntrinsicBlockSize| - // value, as the following |block_size| calculation would be incorrect. - if (node.IsFlexibleBox() && style.ResolvedIsColumnFlexDirection() && - layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { - if (new_space.PercentageResolutionBlockSize() != - old_space.PercentageResolutionBlockSize()) + if (node.IsFlexibleBox()) { + // Flex-boxes can have their children calculate their size based in their + // parent's final block-size. E.g. + // <div style="display: flex;"> + // <div style="display: flex;"> + // <!-- Child will stretch to the parent's fixed block-size --> + // <div></div> + // </div> + // </div> + // <div style="display: flex;"> + // <div style="display: flex; flex-direction: column;"> + // <!-- Child will grow to the parent's fixed block-size --> + // <div style="flex: 1;"></div> + // </div> + // </div> + // + // If the previous |layout_result| was produced by a space which had a + // fixed block-size we can't use |NGLayoutResult::IntrinsicBlockSize()|, + // and need to layout. + // + // TODO(ikilpatrick): Similar to %-block-size descendants we could store + // a bit on the |NGLayoutResult| which indicates if it had a child which + // sized itself based on the parent's block-size. + // We should consider this optimization if we are missing this cache + // often within this branch (and could have re-used the result). + // TODO(ikilaptrick): This may occur for other layout modes, e.g. + // grid/custom-layout/etc. + if (old_space.IsFixedBlockSize()) return NGLayoutCacheStatus::kNeedsLayout; + + // The intrinsic size of column flex-boxes can depend on the + // %-resolution-block-size. This occurs when a flex-box has "max-height: + // 100%" or similar on itself. + // + // Due to this we can't use cached |NGLayoutResult::IntrinsicBlockSize| + // value, as the following |block_size| calculation would be incorrect. + if (style.ResolvedIsColumnFlexDirection() && + layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) { + if (new_space.PercentageResolutionBlockSize() != + old_space.PercentageResolutionBlockSize()) + return NGLayoutCacheStatus::kNeedsLayout; + } } block_size = ComputeBlockSizeForFragment( @@ -209,7 +242,7 @@ NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry( // If a block (within a formatting-context) changes to/from an empty-block, // margins may collapse through this node, requiring full layout. We // approximate this check by checking if the block-size is/was zero. - if (!physical_fragment.IsBlockFormattingContextRoot() && + if (!physical_fragment.IsFormattingContextRoot() && !block_size != !fragment.BlockSize()) return NGLayoutCacheStatus::kNeedsLayout; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc index 53a8071828b..e07c07e0d08 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc @@ -90,7 +90,7 @@ bool BlockLengthUnresolvable( LengthResolvePhase phase, const LayoutUnit* opt_percentage_resolution_block_size_for_min_max) { if (length.IsAuto() || length.IsMinContent() || length.IsMaxContent() || - length.IsFitContent() || length.IsMaxSizeNone()) + length.IsFitContent() || length.IsNone()) return true; if (length.IsPercentOrCalc()) { if (phase == LengthResolvePhase::kIntrinsic) @@ -115,7 +115,7 @@ LayoutUnit ResolveInlineLengthInternal( const NGConstraintSpace& constraint_space, const ComputedStyle& style, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>& min_and_max, + const base::Optional<MinMaxSizes>& min_max_sizes, const Length& length) { DCHECK_GE(constraint_space.AvailableSize().inline_size, LayoutUnit()); DCHECK_GE(constraint_space.PercentageResolutionInlineSize(), LayoutUnit()); @@ -146,20 +146,20 @@ LayoutUnit ResolveInlineLengthInternal( case Length::kMinContent: case Length::kMaxContent: case Length::kFitContent: { - DCHECK(min_and_max.has_value()); + DCHECK(min_max_sizes.has_value()); LayoutUnit available_size = constraint_space.AvailableSize().inline_size; LayoutUnit value; if (length.IsMinContent()) { - value = min_and_max->min_size; + value = min_max_sizes->min_size; } else if (length.IsMaxContent() || available_size == LayoutUnit::Max()) { // If the available space is infinite, fit-content resolves to // max-content. See css-sizing section 2.1. - value = min_and_max->max_size; + value = min_max_sizes->max_size; } else { NGBoxStrut margins = ComputeMarginsForSelf(constraint_space, style); LayoutUnit fill_available = std::max(LayoutUnit(), available_size - margins.InlineSum()); - value = min_and_max->ShrinkToFit(fill_available); + value = min_max_sizes->ShrinkToFit(fill_available); } return value; } @@ -168,7 +168,7 @@ LayoutUnit ResolveInlineLengthInternal( case Length::kExtendToZoom: NOTREACHED() << "These should only be used for viewport definitions"; FALLTHROUGH; - case Length::kMaxSizeNone: + case Length::kNone: default: NOTREACHED(); return border_padding.InlineSum(); @@ -235,18 +235,18 @@ LayoutUnit ResolveBlockLengthInternal( case Length::kExtendToZoom: NOTREACHED() << "These should only be used for viewport definitions"; FALLTHROUGH; - case Length::kMaxSizeNone: + case Length::kNone: default: NOTREACHED(); return border_padding.BlockSum(); } } -MinMaxSize ComputeMinAndMaxContentContribution( +MinMaxSizes ComputeMinAndMaxContentContribution( WritingMode parent_writing_mode, const ComputedStyle& style, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>& min_and_max) { + const base::Optional<MinMaxSizes>& min_max_sizes) { WritingMode child_writing_mode = style.GetWritingMode(); // Synthesize a zero-sized constraint space for resolving sizes against. @@ -256,20 +256,20 @@ MinMaxSize ComputeMinAndMaxContentContribution( .ToConstraintSpace(); LayoutUnit content_size = - min_and_max ? min_and_max->max_size : kIndefiniteSize; + min_max_sizes ? min_max_sizes->max_size : kIndefiniteSize; - MinMaxSize computed_sizes; + MinMaxSizes computed_sizes; const Length& inline_size = parent_writing_mode == WritingMode::kHorizontalTb ? style.Width() : style.Height(); if (inline_size.IsAuto() || inline_size.IsPercentOrCalc() || inline_size.IsFillAvailable() || inline_size.IsFitContent()) { - CHECK(min_and_max.has_value()); - computed_sizes = *min_and_max; + CHECK(min_max_sizes.has_value()); + computed_sizes = *min_max_sizes; } else { if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { computed_sizes = ResolveMainInlineLength(space, style, border_padding, - min_and_max, inline_size); + min_max_sizes, inline_size); } else { computed_sizes = ResolveMainBlockLength(space, style, border_padding, inline_size, @@ -282,11 +282,11 @@ MinMaxSize ComputeMinAndMaxContentContribution( : style.MaxHeight(); LayoutUnit max; if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { - max = ResolveMaxInlineLength(space, style, border_padding, min_and_max, + max = ResolveMaxInlineLength(space, style, border_padding, min_max_sizes, max_length, LengthResolvePhase::kIntrinsic); } else { max = ResolveMaxBlockLength(space, style, border_padding, max_length, - content_size, LengthResolvePhase::kIntrinsic); + LengthResolvePhase::kIntrinsic); } computed_sizes.Constrain(max); @@ -295,59 +295,51 @@ MinMaxSize ComputeMinAndMaxContentContribution( : style.MinHeight(); LayoutUnit min; if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { - min = ResolveMinInlineLength(space, style, border_padding, min_and_max, + min = ResolveMinInlineLength(space, style, border_padding, min_max_sizes, min_length, LengthResolvePhase::kIntrinsic); } else { min = ResolveMinBlockLength(space, style, border_padding, min_length, - content_size, LengthResolvePhase::kIntrinsic); + LengthResolvePhase::kIntrinsic); } computed_sizes.Encompass(min); return computed_sizes; } -MinMaxSize ComputeMinAndMaxContentContribution( +MinMaxSizes ComputeMinAndMaxContentContribution( const ComputedStyle& parent_style, NGLayoutInputNode child, - const MinMaxSizeInput& input) { + const MinMaxSizesInput& input) { const ComputedStyle& child_style = child.Style(); WritingMode parent_writing_mode = parent_style.GetWritingMode(); WritingMode child_writing_mode = child_style.GetWritingMode(); - LayoutBox* box = child.GetLayoutBox(); - - if (box->NeedsPreferredWidthsRecalculation()) { - // Some objects (when there's an intrinsic ratio) have their min/max inline - // size affected by the block size of their container. We don't really know - // whether the containing block of this child did change or is going to - // change size. However, this is our only opportunity to make sure that it - // gets its min/max widths calculated. - box->SetPreferredLogicalWidthsDirty(); - } if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { - if (!box->PreferredLogicalWidthsDirty()) { - return {box->MinPreferredLogicalWidth(), box->MaxPreferredLogicalWidth()}; - } // Tables are special; even if a width is specified, they may end up being // sized different. So we just always let the table code handle this. + if (child.IsTable()) + return child.ComputeMinMaxSizes(parent_writing_mode, input, nullptr); + // Replaced elements may size themselves using aspect ratios and block // sizes, so we pass that on as well. - if (box->IsTable() || box->IsTablePart() || box->IsLayoutReplaced()) { + if (child.IsReplaced()) { + LayoutBox* box = child.GetLayoutBox(); bool needs_size_reset = false; if (!box->HasOverrideContainingBlockContentLogicalHeight()) { box->SetOverrideContainingBlockContentLogicalHeight( input.percentage_resolution_block_size); needs_size_reset = true; } - MinMaxSize result{box->MinPreferredLogicalWidth(), - box->MaxPreferredLogicalWidth()}; + + MinMaxSizes result = box->PreferredLogicalWidths(); + if (needs_size_reset) box->ClearOverrideContainingBlockContentSize(); return result; } } - base::Optional<MinMaxSize> minmax; + base::Optional<MinMaxSizes> min_max_sizes; if (NeedMinMaxSizeForContentContribution(parent_writing_mode, child_style)) { // We need to set up a constraint space with correct fallback available // inline size in case of orthogonal children. @@ -358,8 +350,8 @@ MinMaxSize ComputeMinAndMaxContentContribution( CreateIndefiniteConstraintSpaceForChild(parent_style, child); child_constraint_space = &indefinite_constraint_space; } - minmax = child.ComputeMinMaxSize(parent_writing_mode, input, - child_constraint_space); + min_max_sizes = child.ComputeMinMaxSizes(parent_writing_mode, input, + child_constraint_space); } // Synthesize a zero-sized constraint space for determining the borders, and // padding. @@ -368,50 +360,17 @@ MinMaxSize ComputeMinAndMaxContentContribution( /* is_new_fc */ false) .ToConstraintSpace(); NGBoxStrut border_padding = - ComputeBorders(space, child) + ComputePadding(space, child_style); - - MinMaxSize sizes = ComputeMinAndMaxContentContribution( - parent_writing_mode, child_style, border_padding, minmax); - if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) - box->SetPreferredLogicalWidthsFromNG(sizes); - return sizes; -} - -MinMaxSize ComputeMinAndMaxContentSizeForOutOfFlow( - const NGConstraintSpace& constraint_space, - NGLayoutInputNode node, - const NGBoxStrut& border_padding, - const MinMaxSizeInput& input) { - LayoutBox* box = node.GetLayoutBox(); - // If we've already populated the legacy preferred logical widths cache for - // this node at the bottom of this function, use those cached results here. - // Or, if we're working on a table, use the legacy preferred widths code - // instead of ComputeMinAndMaxContentContribution below because - // ComputeMinAndMaxContentContribution assumes that if an element has a - // specified size, that's its final size, which tables don't follow. - if ((!box->PreferredLogicalWidthsDirty() && - !box->NeedsPreferredWidthsRecalculation()) || - box->IsTable()) { - return MinMaxSize{box->MinPreferredLogicalWidth(), - box->MaxPreferredLogicalWidth()}; - } + ComputeBorders(space, child_style) + ComputePadding(space, child_style); - // Compute the intrinsic sizes without regard to the specified sizes. - MinMaxSize result = node.ComputeMinMaxSize(node.Style().GetWritingMode(), - input, &constraint_space); - // Apply the specified min, main, max sizes. - MinMaxSize contribution = ComputeMinAndMaxContentContribution( - node.Style().GetWritingMode(), node.Style(), border_padding, result); - // Cache these computed values. - box->SetPreferredLogicalWidthsFromNG(contribution); - return result; + return ComputeMinAndMaxContentContribution(parent_writing_mode, child_style, + border_padding, min_max_sizes); } LayoutUnit ComputeInlineSizeForFragment( const NGConstraintSpace& space, NGLayoutInputNode node, const NGBoxStrut& border_padding, - const MinMaxSize* override_minmax_for_test) { + const MinMaxSizes* override_min_max_sizes_for_test) { if (space.IsFixedInlineSize() || space.IsAnonymous()) return space.AvailableSize().inline_size; @@ -420,57 +379,24 @@ LayoutUnit ComputeInlineSizeForFragment( if (logical_width.IsAuto() && space.IsShrinkToFit()) logical_width = Length::FitContent(); - LayoutBox* box = node.GetLayoutBox(); - // If we have usable cached min/max intrinsic sizes, use those if we can. They - // will normally also be constrained to {min,max}-inline-size, but not if - // percentages are involved. In such cases we'll have to calculate and apply - // the constraints on our own. We also need to discard the cached values if - // the box has certain properties (e.g. percentage padding) that cause the - // cached values to be affected by extrinsic sizing. - if (!box->PreferredLogicalWidthsDirty() && !override_minmax_for_test && - !style.LogicalMinWidth().IsPercentOrCalc() && - !style.LogicalMaxWidth().IsPercentOrCalc() && - !box->NeedsPreferredWidthsRecalculation()) { - if (logical_width.IsFitContent()) { - // This is not as easy as {min, max}.ShrinkToFit() because we also need - // to subtract inline margins from the available size. The code in - // ResolveMainInlineLength knows how to handle that, just call that. - - MinMaxSize min_and_max = {box->MinPreferredLogicalWidth(), - box->MaxPreferredLogicalWidth()}; - return ResolveMainInlineLength(space, style, border_padding, min_and_max, - logical_width); - } - if (logical_width.IsMinContent()) - return box->MinPreferredLogicalWidth(); - if (logical_width.IsMaxContent()) - return box->MaxPreferredLogicalWidth(); - } + auto MinMaxSizesFunc = [&]() -> MinMaxSizes { + if (override_min_max_sizes_for_test) + return *override_min_max_sizes_for_test; - base::Optional<MinMaxSize> min_and_max; - if (NeedMinMaxSize(space, style)) { - if (override_minmax_for_test) { - min_and_max = *override_minmax_for_test; - } else { - min_and_max = node.ComputeMinMaxSize( - space.GetWritingMode(), - MinMaxSizeInput(space.PercentageResolutionBlockSize()), &space); - // Cache these computed values - MinMaxSize contribution = ComputeMinAndMaxContentContribution( - style.GetWritingMode(), style, border_padding, min_and_max); - box->SetPreferredLogicalWidthsFromNG(contribution); - } - } + return node.ComputeMinMaxSizes( + space.GetWritingMode(), + MinMaxSizesInput(space.PercentageResolutionBlockSize()), &space); + }; LayoutUnit extent = ResolveMainInlineLength(space, style, border_padding, - min_and_max, logical_width); - - LayoutUnit max = ResolveMaxInlineLength(space, style, border_padding, - min_and_max, style.LogicalMaxWidth(), - LengthResolvePhase::kLayout); - LayoutUnit min = ResolveMinInlineLength(space, style, border_padding, - min_and_max, style.LogicalMinWidth(), - LengthResolvePhase::kLayout); + MinMaxSizesFunc, logical_width); + + LayoutUnit max = ResolveMaxInlineLength( + space, style, border_padding, MinMaxSizesFunc, style.LogicalMaxWidth(), + LengthResolvePhase::kLayout); + LayoutUnit min = ResolveMinInlineLength( + space, style, border_padding, MinMaxSizesFunc, style.LogicalMinWidth(), + LengthResolvePhase::kLayout); return ConstrainByMinMax(extent, min, max); } @@ -486,7 +412,7 @@ LayoutUnit ComputeBlockSizeForFragmentInternal( nullptr) { LayoutUnit min = ResolveMinBlockLength( constraint_space, style, border_padding, style.LogicalMinHeight(), - content_size, LengthResolvePhase::kLayout, + LengthResolvePhase::kLayout, opt_percentage_resolution_block_size_for_min_max); const Length& logical_height = style.LogicalHeight(); // Scrollable percentage-sized children of table cells, in the table @@ -518,7 +444,7 @@ LayoutUnit ComputeBlockSizeForFragmentInternal( LayoutUnit max = ResolveMaxBlockLength( constraint_space, style, border_padding, style.LogicalMaxHeight(), - content_size, LengthResolvePhase::kLayout, + LengthResolvePhase::kLayout, opt_percentage_resolution_block_size_for_min_max); return ConstrainByMinMax(extent, min, max); @@ -546,9 +472,9 @@ LayoutUnit ComputeBlockSizeForFragment( } // Computes size for a replaced element. -void ComputeReplacedSize(const NGLayoutInputNode& node, +void ComputeReplacedSize(const NGBlockNode& node, const NGConstraintSpace& space, - const base::Optional<MinMaxSize>& child_minmax, + const base::Optional<MinMaxSizes>& child_min_max_sizes, base::Optional<LogicalSize>* out_replaced_size, base::Optional<LogicalSize>* out_aspect_ratio) { DCHECK(node.IsReplaced()); @@ -558,26 +484,26 @@ void ComputeReplacedSize(const NGLayoutInputNode& node, const ComputedStyle& style = node.Style(); NGBoxStrut border_padding = - ComputeBorders(space, node) + ComputePadding(space, style); + ComputeBorders(space, style) + ComputePadding(space, style); LayoutUnit inline_min = ResolveMinInlineLength( - space, style, border_padding, child_minmax, style.LogicalMinWidth(), - LengthResolvePhase::kLayout); + space, style, border_padding, child_min_max_sizes, + style.LogicalMinWidth(), LengthResolvePhase::kLayout); LayoutUnit inline_max = ResolveMaxInlineLength( - space, style, border_padding, child_minmax, style.LogicalMaxWidth(), - LengthResolvePhase::kLayout); - LayoutUnit block_min = ResolveMinBlockLength( - space, style, border_padding, style.LogicalMinHeight(), - border_padding.BlockSum(), LengthResolvePhase::kLayout); - LayoutUnit block_max = ResolveMaxBlockLength( - space, style, border_padding, style.LogicalMaxHeight(), LayoutUnit::Max(), - LengthResolvePhase::kLayout); + space, style, border_padding, child_min_max_sizes, + style.LogicalMaxWidth(), LengthResolvePhase::kLayout); + LayoutUnit block_min = ResolveMinBlockLength(space, style, border_padding, + style.LogicalMinHeight(), + LengthResolvePhase::kLayout); + LayoutUnit block_max = ResolveMaxBlockLength(space, style, border_padding, + style.LogicalMaxHeight(), + LengthResolvePhase::kLayout); const Length& inline_length = style.LogicalWidth(); const Length& block_length = style.LogicalHeight(); base::Optional<LayoutUnit> replaced_inline; if (!inline_length.IsAuto()) { - replaced_inline = ResolveMainInlineLength(space, style, border_padding, - child_minmax, inline_length); + replaced_inline = ResolveMainInlineLength( + space, style, border_padding, child_min_max_sizes, inline_length); replaced_inline = ConstrainByMinMax(*replaced_inline, inline_min, inline_max); } @@ -595,9 +521,10 @@ void ComputeReplacedSize(const NGLayoutInputNode& node, base::Optional<LayoutUnit> intrinsic_inline; base::Optional<LayoutUnit> intrinsic_block; - LogicalSize aspect_ratio; + node.IntrinsicSize(&intrinsic_inline, &intrinsic_block); + + LogicalSize aspect_ratio = node.GetAspectRatio(); - node.IntrinsicSize(&intrinsic_inline, &intrinsic_block, &aspect_ratio); // Computing intrinsic size is complicated by the fact that // intrinsic_inline, intrinsic_block, and aspect_ratio can all // be empty independent of each other. @@ -850,7 +777,7 @@ NGBoxStrut ComputeBordersInternal(const ComputedStyle& style) { } // namespace NGBoxStrut ComputeBorders(const NGConstraintSpace& constraint_space, - const NGLayoutInputNode node) { + const ComputedStyle& style) { // If we are producing an anonymous fragment (e.g. a column), it has no // borders, padding or scrollbars. Using the ones from the container can only // cause trouble. @@ -862,7 +789,7 @@ NGBoxStrut ComputeBorders(const NGConstraintSpace& constraint_space, if (constraint_space.IsTableCell()) return constraint_space.TableCellBorders(); - return ComputeBordersInternal(node.Style()); + return ComputeBordersInternal(style); } NGBoxStrut ComputeBordersForInline(const ComputedStyle& style) { @@ -1058,7 +985,7 @@ NGFragmentGeometry CalculateInitialFragmentGeometry( const NGBlockNode& node) { const ComputedStyle& style = node.Style(); - NGBoxStrut border = ComputeBorders(constraint_space, node); + NGBoxStrut border = ComputeBorders(constraint_space, style); NGBoxStrut padding = ComputePadding(constraint_space, style); NGBoxStrut scrollbar = ComputeScrollbars(constraint_space, node); NGBoxStrut border_padding = border + padding; @@ -1096,8 +1023,9 @@ NGFragmentGeometry CalculateInitialFragmentGeometry( NGFragmentGeometry CalculateInitialMinMaxFragmentGeometry( const NGConstraintSpace& constraint_space, const NGBlockNode& node) { - NGBoxStrut border = ComputeBorders(constraint_space, node); - NGBoxStrut padding = ComputePadding(constraint_space, node.Style()); + const ComputedStyle& style = node.Style(); + NGBoxStrut border = ComputeBorders(constraint_space, style); + NGBoxStrut padding = ComputePadding(constraint_space, style); NGBoxStrut scrollbar = ComputeScrollbars(constraint_space, node); return {/* border_box_size */ LogicalSize(), border, scrollbar, padding}; @@ -1210,7 +1138,10 @@ LayoutUnit CalculateChildPercentageBlockSizeForMinMax( const NGBoxStrut& border_padding, LayoutUnit parent_percentage_block_size) { // Anonymous block or spaces should pass the percent size straight through. - if (space.IsAnonymous() || node.IsAnonymousBlock()) + // If this node is OOF-positioned, our size was pre-calculated and we should + // pass this through to our children. + if (space.IsAnonymous() || node.IsAnonymousBlock() || + node.IsOutOfFlowPositioned()) return parent_percentage_block_size; LayoutUnit block_size = ComputeBlockSizeForFragmentInternal( @@ -1225,8 +1156,7 @@ LayoutUnit CalculateChildPercentageBlockSizeForMinMax( // For OOF-positioned nodes, use the parent (containing-block) size. if (child_percentage_block_size == kIndefiniteSize && - (node.UseParentPercentageResolutionBlockSizeForChildren() || - node.IsOutOfFlowPositioned())) + node.UseParentPercentageResolutionBlockSizeForChildren()) child_percentage_block_size = parent_percentage_block_size; return child_percentage_block_size; @@ -1255,8 +1185,13 @@ LayoutUnit ClampIntrinsicBlockSize( // If the intrinsic size was overridden, then use that. LayoutUnit intrinsic_size_override = node.OverrideIntrinsicContentBlockSize(); - if (intrinsic_size_override != kIndefiniteSize) + if (intrinsic_size_override != kIndefiniteSize) { return intrinsic_size_override + border_scrollbar_padding.BlockSum(); + } else { + LayoutUnit default_intrinsic_size = node.DefaultIntrinsicContentBlockSize(); + if (default_intrinsic_size != kIndefiniteSize) + return default_intrinsic_size + border_scrollbar_padding.BlockSum(); + } // If we have size containment, we ignore child contributions to intrinsic // sizing. @@ -1265,13 +1200,11 @@ LayoutUnit ClampIntrinsicBlockSize( return current_intrinsic_block_size; } -base::Optional<MinMaxSize> CalculateMinMaxSizesIgnoringChildren( +base::Optional<MinMaxSizes> CalculateMinMaxSizesIgnoringChildren( const NGBlockNode& node, - const NGBoxStrut& border_scrollbar_padding, - NGMinMaxSizeType type) { - MinMaxSize sizes; - if (type == NGMinMaxSizeType::kBorderBoxSize) - sizes += border_scrollbar_padding.InlineSum(); + const NGBoxStrut& border_scrollbar_padding) { + MinMaxSizes sizes; + sizes += border_scrollbar_padding.InlineSum(); // If intrinsic size was overridden, then use that. const LayoutUnit intrinsic_size_override = @@ -1279,6 +1212,12 @@ base::Optional<MinMaxSize> CalculateMinMaxSizesIgnoringChildren( if (intrinsic_size_override != kIndefiniteSize) { sizes += intrinsic_size_override; return sizes; + } else { + LayoutUnit default_inline_size = node.DefaultIntrinsicContentInlineSize(); + if (default_inline_size != kIndefiniteSize) { + sizes += default_inline_size; + return sizes; + } } // Size contained elements don't consider children for intrinsic sizing. diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h index 525a28b6cec..2a618216acc 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils.h @@ -9,7 +9,7 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/geometry/logical_size.h" #include "third_party/blink/renderer/core/layout/geometry/physical_size.h" -#include "third_party/blink/renderer/core/layout/min_max_size.h" +#include "third_party/blink/renderer/core/layout/min_max_sizes.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" @@ -22,7 +22,7 @@ namespace blink { class ComputedStyle; class Length; -struct MinMaxSizeInput; +struct MinMaxSizesInput; class NGConstraintSpace; class NGBlockNode; class NGLayoutInputNode; @@ -41,15 +41,6 @@ inline bool NeedMinMaxSize(const ComputedStyle& style) { style.LogicalMaxWidth().IsIntrinsic(); } -// Whether the caller needs to compute min-content and max-content sizes to -// pass them to ResolveMainInlineLength / ComputeInlineSizeForFragment. -// If this function returns false, it is safe to pass an empty -// MinMaxSize struct to those functions. -inline bool NeedMinMaxSize(const NGConstraintSpace& constraint_space, - const ComputedStyle& style) { - return constraint_space.IsShrinkToFit() || NeedMinMaxSize(style); -} - // Like NeedMinMaxSize, but for use when calling // ComputeMinAndMaxContentContribution. // Because content contributions are commonly needed by a block's parent, @@ -75,17 +66,17 @@ CORE_EXPORT bool BlockLengthUnresolvable( // available-size. // - |ComputedStyle| the style of the node. // - |border_padding| the resolved border, and padding of the node. -// - |MinMaxSize| is only used when the length is intrinsic (fit-content). +// - |MinMaxSizes| is only used when the length is intrinsic (fit-content). // - |Length| is the length to resolve. CORE_EXPORT LayoutUnit ResolveInlineLengthInternal(const NGConstraintSpace&, const ComputedStyle&, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>&, + const base::Optional<MinMaxSizes>&, const Length&); // Same as ResolveInlineLengthInternal, except here |content_size| roughly plays -// the part of |MinMaxSize|. +// the part of |MinMaxSizes|. CORE_EXPORT LayoutUnit ResolveBlockLengthInternal( const NGConstraintSpace&, const ComputedStyle&, @@ -97,44 +88,100 @@ CORE_EXPORT LayoutUnit ResolveBlockLengthInternal( nullptr); // Used for resolving min inline lengths, (|ComputedStyle::MinLogicalWidth|). +template <typename MinMaxSizesFunc> inline LayoutUnit ResolveMinInlineLength( const NGConstraintSpace& constraint_space, const ComputedStyle& style, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>& min_and_max, + const MinMaxSizesFunc& min_max_sizes_func, + const Length& length, + LengthResolvePhase phase) { + if (LIKELY(length.IsAuto() || InlineLengthUnresolvable(length, phase))) + return border_padding.InlineSum(); + + base::Optional<MinMaxSizes> min_max_sizes; + if (length.IsIntrinsic()) + min_max_sizes = min_max_sizes_func(); + + return ResolveInlineLengthInternal(constraint_space, style, border_padding, + min_max_sizes, length); +} + +template <> +inline LayoutUnit ResolveMinInlineLength<base::Optional<MinMaxSizes>>( + const NGConstraintSpace& constraint_space, + const ComputedStyle& style, + const NGBoxStrut& border_padding, + const base::Optional<MinMaxSizes>& min_max_sizes, const Length& length, LengthResolvePhase phase) { if (LIKELY(length.IsAuto() || InlineLengthUnresolvable(length, phase))) return border_padding.InlineSum(); return ResolveInlineLengthInternal(constraint_space, style, border_padding, - min_and_max, length); + min_max_sizes, length); } // Used for resolving max inline lengths, (|ComputedStyle::MaxLogicalWidth|). +template <typename MinMaxSizesFunc> inline LayoutUnit ResolveMaxInlineLength( const NGConstraintSpace& constraint_space, const ComputedStyle& style, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>& min_and_max, + const MinMaxSizesFunc& min_max_sizes_func, const Length& length, LengthResolvePhase phase) { - if (LIKELY(length.IsMaxSizeNone() || InlineLengthUnresolvable(length, phase))) + if (LIKELY(length.IsNone() || InlineLengthUnresolvable(length, phase))) return LayoutUnit::Max(); + base::Optional<MinMaxSizes> min_max_sizes; + if (length.IsIntrinsic()) + min_max_sizes = min_max_sizes_func(); + return ResolveInlineLengthInternal(constraint_space, style, border_padding, - min_and_max, length); + min_max_sizes, length); +} + +template <> +inline LayoutUnit ResolveMaxInlineLength<base::Optional<MinMaxSizes>>( + const NGConstraintSpace& constraint_space, + const ComputedStyle& style, + const NGBoxStrut& border_padding, + const base::Optional<MinMaxSizes>& min_max_sizes, + const Length& length, + LengthResolvePhase phase) { + if (LIKELY(length.IsNone() || InlineLengthUnresolvable(length, phase))) + return LayoutUnit::Max(); + + return ResolveInlineLengthInternal(constraint_space, style, border_padding, + min_max_sizes, length); } // Used for resolving main inline lengths, (|ComputedStyle::LogicalWidth|). +template <typename MinMaxSizesFunc> inline LayoutUnit ResolveMainInlineLength( const NGConstraintSpace& constraint_space, const ComputedStyle& style, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>& min_and_max, + const MinMaxSizesFunc& min_max_sizes_func, + const Length& length) { + base::Optional<MinMaxSizes> min_max_sizes; + if (length.IsIntrinsic()) + min_max_sizes = min_max_sizes_func(); + + return ResolveInlineLengthInternal(constraint_space, style, border_padding, + min_max_sizes, length); +} + +template <> +inline LayoutUnit ResolveMainInlineLength<base::Optional<MinMaxSizes>>( + const NGConstraintSpace& constraint_space, + const ComputedStyle& style, + const NGBoxStrut& border_padding, + const base::Optional<MinMaxSizes>& min_max_sizes, const Length& length) { return ResolveInlineLengthInternal(constraint_space, style, border_padding, - min_and_max, length); + min_max_sizes, length); } // Used for resolving min block lengths, (|ComputedStyle::MinLogicalHeight|). @@ -143,7 +190,6 @@ inline LayoutUnit ResolveMinBlockLength( const ComputedStyle& style, const NGBoxStrut& border_padding, const Length& length, - LayoutUnit content_size, LengthResolvePhase phase, const LayoutUnit* opt_percentage_resolution_block_size_for_min_max = nullptr) { @@ -153,7 +199,7 @@ inline LayoutUnit ResolveMinBlockLength( return border_padding.BlockSum(); return ResolveBlockLengthInternal( - constraint_space, style, border_padding, length, content_size, phase, + constraint_space, style, border_padding, length, kIndefiniteSize, phase, opt_percentage_resolution_block_size_for_min_max); } @@ -163,7 +209,6 @@ inline LayoutUnit ResolveMaxBlockLength( const ComputedStyle& style, const NGBoxStrut& border_padding, const Length& length, - LayoutUnit content_size, LengthResolvePhase phase, const LayoutUnit* opt_percentage_resolution_block_size_for_min_max = nullptr) { @@ -173,7 +218,7 @@ inline LayoutUnit ResolveMaxBlockLength( return LayoutUnit::Max(); return ResolveBlockLengthInternal( - constraint_space, style, border_padding, length, content_size, phase, + constraint_space, style, border_padding, length, kIndefiniteSize, phase, opt_percentage_resolution_block_size_for_min_max); } @@ -198,6 +243,31 @@ inline LayoutUnit ResolveMainBlockLength( opt_percentage_resolution_block_size_for_min_max); } +template <typename IntrinsicBlockSizeFunc> +inline LayoutUnit ResolveMainBlockLength( + const NGConstraintSpace& constraint_space, + const ComputedStyle& style, + const NGBoxStrut& border_padding, + const Length& length, + const IntrinsicBlockSizeFunc& intrinsic_block_size_func, + LengthResolvePhase phase, + const LayoutUnit* opt_percentage_resolution_block_size_for_min_max = + nullptr) { + if (UNLIKELY((length.IsPercentOrCalc() || length.IsFillAvailable()) && + BlockLengthUnresolvable( + constraint_space, length, phase, + opt_percentage_resolution_block_size_for_min_max))) + return intrinsic_block_size_func(); + + LayoutUnit intrinsic_block_size = kIndefiniteSize; + if (length.IsIntrinsicOrAuto()) + intrinsic_block_size = intrinsic_block_size_func(); + + return ResolveBlockLengthInternal( + constraint_space, style, border_padding, length, intrinsic_block_size, + phase, opt_percentage_resolution_block_size_for_min_max); +} + // For the given style and min/max content sizes, computes the min and max // content contribution (https://drafts.csswg.org/css-sizing/#contributions). // This is similar to ComputeInlineSizeForFragment except that it does not @@ -208,11 +278,11 @@ inline LayoutUnit ResolveMainBlockLength( // Because content contributions are commonly needed by a block's parent, // we also take a writing mode here so we can compute this in the parent's // coordinate system. -CORE_EXPORT MinMaxSize +CORE_EXPORT MinMaxSizes ComputeMinAndMaxContentContribution(WritingMode writing_mode, const ComputedStyle&, const NGBoxStrut& border_padding, - const base::Optional<MinMaxSize>&); + const base::Optional<MinMaxSizes>&); // A version of ComputeMinAndMaxContentContribution that does not require you // to compute the min/max content size of the child. Instead, this function @@ -222,31 +292,22 @@ ComputeMinAndMaxContentContribution(WritingMode writing_mode, // parent, we'll still return the inline min/max contribution in the writing // mode of the parent (i.e. typically something based on the preferred *block* // size of the child). -MinMaxSize ComputeMinAndMaxContentContribution( +MinMaxSizes ComputeMinAndMaxContentContribution( const ComputedStyle& parent_style, NGLayoutInputNode child, - const MinMaxSizeInput&); - -// Computes the min/max-content size for an out-of-flow positioned node and -// returns it, using the cache where possible. ALways computes it in the writing -// mode of the node itself. -MinMaxSize ComputeMinAndMaxContentSizeForOutOfFlow( - const NGConstraintSpace&, - NGLayoutInputNode, - const NGBoxStrut& border_padding, - const MinMaxSizeInput&); + const MinMaxSizesInput&); // Returns inline size of the node's border box by resolving the computed value // in style.logicalWidth (Length) to a layout unit, adding border and padding, // then constraining the result by the resolved min logical width and max // logical width from the ComputedStyle object. Calls Node::ComputeMinMaxSize // if needed. -// |override_minmax_for_test| is provided *solely* for use by unit tests. +// |override_min_max_sizes_for_test| is provided *solely* for use by unit tests. CORE_EXPORT LayoutUnit ComputeInlineSizeForFragment( const NGConstraintSpace&, NGLayoutInputNode, const NGBoxStrut& border_padding, - const MinMaxSize* override_minmax_for_test = nullptr); + const MinMaxSizes* override_min_max_sizes_for_test = nullptr); // Same as ComputeInlineSizeForFragment, but uses height instead of width. CORE_EXPORT LayoutUnit @@ -265,9 +326,9 @@ ComputeBlockSizeForFragment(const NGConstraintSpace&, // - neither out_aspect_ratio, nor out_replaced_size // SVG elements can return any of the three options above. CORE_EXPORT void ComputeReplacedSize( - const NGLayoutInputNode&, + const NGBlockNode&, const NGConstraintSpace&, - const base::Optional<MinMaxSize>&, + const base::Optional<MinMaxSizes>&, base::Optional<LogicalSize>* out_replaced_size, base::Optional<LogicalSize>* out_aspect_ratio); @@ -364,7 +425,7 @@ CORE_EXPORT NGBoxStrut ComputeMinMaxMargins(const ComputedStyle& parent_style, NGLayoutInputNode child); CORE_EXPORT NGBoxStrut ComputeBorders(const NGConstraintSpace&, - const NGLayoutInputNode); + const ComputedStyle&); CORE_EXPORT NGBoxStrut ComputeBordersForInline(const ComputedStyle& style); @@ -480,11 +541,9 @@ LayoutUnit ClampIntrinsicBlockSize( // without considering children. If so, it returns the calculated size. // Otherwise, it returns base::nullopt and the caller has to compute the size // itself. -base::Optional<MinMaxSize> CalculateMinMaxSizesIgnoringChildren( +base::Optional<MinMaxSizes> CalculateMinMaxSizesIgnoringChildren( const NGBlockNode&, - const NGBoxStrut& border_scrollbar_padding, - NGMinMaxSizeType); - + const NGBoxStrut& border_scrollbar_padding); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc index 6b63229023f..f374f55a0cb 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_length_utils_test.cc @@ -41,7 +41,7 @@ class NGLengthUtilsTest : public testing::Test { LayoutUnit ResolveMainInlineLength( const Length& length, - const base::Optional<MinMaxSize>& sizes = base::nullopt) { + const base::Optional<MinMaxSizes>& sizes = base::nullopt) { NGConstraintSpace constraint_space = ConstructConstraintSpace(200, 300); NGBoxStrut border_padding = ComputeBordersForTest(*style_) + ComputePadding(constraint_space, *style_); @@ -53,7 +53,7 @@ class NGLengthUtilsTest : public testing::Test { LayoutUnit ResolveMinInlineLength( const Length& length, LengthResolvePhase phase = LengthResolvePhase::kLayout, - const base::Optional<MinMaxSize>& sizes = base::nullopt) { + const base::Optional<MinMaxSizes>& sizes = base::nullopt) { NGConstraintSpace constraint_space = ConstructConstraintSpace(200, 300); NGBoxStrut border_padding = ComputeBordersForTest(*style_) + ComputePadding(constraint_space, *style_); @@ -65,7 +65,7 @@ class NGLengthUtilsTest : public testing::Test { LayoutUnit ResolveMaxInlineLength( const Length& length, LengthResolvePhase phase = LengthResolvePhase::kLayout, - const base::Optional<MinMaxSize>& sizes = base::nullopt) { + const base::Optional<MinMaxSizes>& sizes = base::nullopt) { NGConstraintSpace constraint_space = ConstructConstraintSpace(200, 300); NGBoxStrut border_padding = ComputeBordersForTest(*style_) + ComputePadding(constraint_space, *style_); @@ -97,10 +97,10 @@ class NGLengthUtilsTestWithNode : public NGLayoutTest { LayoutUnit ComputeInlineSizeForFragment( NGConstraintSpace constraint_space = ConstructConstraintSpace(200, 300), - const MinMaxSize& sizes = MinMaxSize()) { + const MinMaxSizes& sizes = MinMaxSizes()) { LayoutBox* body = ToLayoutBox(GetDocument().body()->GetLayoutObject()); body->SetStyle(style_); - body->SetPreferredLogicalWidthsDirty(); + body->SetIntrinsicLogicalWidthsDirty(); NGBlockNode node(body); NGBoxStrut border_padding = ComputeBordersForTest(*style_) + @@ -114,7 +114,7 @@ class NGLengthUtilsTestWithNode : public NGLayoutTest { LayoutUnit content_size = LayoutUnit()) { LayoutBox* body = ToLayoutBox(GetDocument().body()->GetLayoutObject()); body->SetStyle(style_); - body->SetPreferredLogicalWidthsDirty(); + body->SetIntrinsicLogicalWidthsDirty(); NGBoxStrut border_padding = ComputeBordersForTest(*style_) + ComputePadding(constraint_space, *style_); @@ -139,7 +139,7 @@ TEST_F(NGLengthUtilsTest, testResolveInlineLength) { EXPECT_EQ(LayoutUnit::Max(), ResolveMaxInlineLength(Length::FillAvailable(), LengthResolvePhase::kIntrinsic)); - MinMaxSize sizes; + MinMaxSizes sizes; sizes.min_size = LayoutUnit(30); sizes.max_size = LayoutUnit(40); EXPECT_EQ(LayoutUnit(30), @@ -168,13 +168,13 @@ TEST_F(NGLengthUtilsTest, testResolveBlockLength) { } TEST_F(NGLengthUtilsTest, testComputeContentContribution) { - MinMaxSize sizes; + MinMaxSizes sizes; sizes.min_size = LayoutUnit(30); sizes.max_size = LayoutUnit(40); NGBoxStrut border_padding; - MinMaxSize expected = sizes; + MinMaxSizes expected = sizes; style_->SetLogicalWidth(Length::Percent(30)); EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( @@ -185,7 +185,7 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding, sizes)); - expected = MinMaxSize{LayoutUnit(150), LayoutUnit(150)}; + expected = MinMaxSizes{LayoutUnit(150), LayoutUnit(150)}; style_->SetLogicalWidth(Length::Fixed(150)); EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( @@ -197,7 +197,7 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding, sizes)); - expected = MinMaxSize{LayoutUnit(430), LayoutUnit(440)}; + expected = MinMaxSizes{LayoutUnit(430), LayoutUnit(440)}; style_->SetPaddingLeft(Length::Fixed(400)); auto sizes_padding400 = sizes; sizes_padding400 += LayoutUnit(400); @@ -207,7 +207,7 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { style_->GetWritingMode(), *style_, border_padding400, sizes_padding400)); - expected = MinMaxSize{LayoutUnit(30), LayoutUnit(40)}; + expected = MinMaxSizes{LayoutUnit(30), LayoutUnit(40)}; style_->SetPaddingLeft(Length::Fixed(0)); style_->SetLogicalWidth(Length(CalculationValue::Create( PixelsAndPercent(100, -10), kValueRangeNonNegative))); @@ -215,21 +215,21 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding, sizes)); - expected = MinMaxSize{LayoutUnit(30), LayoutUnit(35)}; + expected = MinMaxSizes{LayoutUnit(30), LayoutUnit(35)}; style_->SetLogicalWidth(Length::Auto()); style_->SetMaxWidth(Length::Fixed(35)); EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding, sizes)); - expected = MinMaxSize{LayoutUnit(80), LayoutUnit(80)}; + expected = MinMaxSizes{LayoutUnit(80), LayoutUnit(80)}; style_->SetLogicalWidth(Length::Fixed(50)); style_->SetMinWidth(Length::Fixed(80)); EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding, sizes)); - expected = MinMaxSize{LayoutUnit(150), LayoutUnit(150)}; + expected = MinMaxSizes{LayoutUnit(150), LayoutUnit(150)}; style_ = ComputedStyle::Create(); style_->SetLogicalWidth(Length::Fixed(100)); style_->SetPaddingLeft(Length::Fixed(50)); @@ -241,7 +241,7 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { style_->GetWritingMode(), *style_, border_padding50, sizes_padding50)); - expected = MinMaxSize{LayoutUnit(100), LayoutUnit(100)}; + expected = MinMaxSizes{LayoutUnit(100), LayoutUnit(100)}; style_->SetBoxSizing(EBoxSizing::kBorderBox); EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding50, @@ -249,7 +249,7 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { // Content size should never be below zero, even with box-sizing: border-box // and a large padding... - expected = MinMaxSize{LayoutUnit(400), LayoutUnit(400)}; + expected = MinMaxSizes{LayoutUnit(400), LayoutUnit(400)}; style_->SetPaddingLeft(Length::Fixed(400)); EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding400, @@ -264,11 +264,11 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { style_->SetMaxWidth(Length::MaxContent()); // Due to padding and box-sizing, width computes to 400px and max-width to // 440px, so the result is 400. - expected = MinMaxSize{LayoutUnit(400), LayoutUnit(400)}; + expected = MinMaxSizes{LayoutUnit(400), LayoutUnit(400)}; EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( style_->GetWritingMode(), *style_, border_padding400, sizes_padding400)); - expected = MinMaxSize{LayoutUnit(40), LayoutUnit(40)}; + expected = MinMaxSizes{LayoutUnit(40), LayoutUnit(40)}; style_->SetPaddingLeft(Length::Fixed(0)); EXPECT_EQ(expected, ComputeMinAndMaxContentContribution( @@ -276,7 +276,7 @@ TEST_F(NGLengthUtilsTest, testComputeContentContribution) { } TEST_F(NGLengthUtilsTestWithNode, testComputeInlineSizeForFragment) { - MinMaxSize sizes; + MinMaxSizes sizes; sizes.min_size = LayoutUnit(30); sizes.max_size = LayoutUnit(40); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc index df903e64148..0df01829351 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc @@ -237,10 +237,8 @@ bool NGOutOfFlowLayoutPart::SweepLegacyCandidates( // size first. // We perform a pre-layout to correctly determine the static position. // Copied from LayoutBlock::LayoutPositionedObject + // TODO(layout-dev): Remove this once LayoutFlexibleBox is removed. LayoutBox* layout_box = ToLayoutBox(legacy_object); - // TODO(dgrogan): The NG flexbox implementation doesn't have an - // analogous method yet, so abspos children of NG flexboxes that have a - // legacy containing block will not be positioned correctly. if (layout_box->Parent()->IsFlexibleBox()) { LayoutFlexibleBox* parent = ToLayoutFlexibleBox(layout_box->Parent()); if (parent->SetStaticPositionForPositionedLayout(*layout_box)) { @@ -293,22 +291,22 @@ void NGOutOfFlowLayoutPart::ComputeInlineContainingBlocks( inline_geometry); } } - // Fetch start/end fragment info. - container_builder_->ComputeInlineContainerFragments( - &inline_container_fragments); + + // Fetch the inline start/end fragment geometry. + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + container_builder_->ComputeInlineContainerGeometry( + &inline_container_fragments); + } else { + container_builder_->ComputeInlineContainerGeometryFromFragmentTree( + &inline_container_fragments); + } + LogicalSize container_builder_size = container_builder_->Size(); PhysicalSize container_builder_physical_size = ToPhysicalSize(container_builder_size, writing_mode_); - // Translate start/end fragments into ContainingBlockInfo. + // Transform the start/end fragments into a ContainingBlockInfo. for (auto& block_info : inline_container_fragments) { - // Variables needed to describe ContainingBlockInfo - const ComputedStyle* inline_cb_style = block_info.key->Style(); - LogicalSize inline_cb_size; - LogicalOffset container_offset; - DCHECK(block_info.value.has_value()); - DCHECK(inline_cb_style); - NGBoxStrut inline_cb_borders = ComputeBordersForInline(*inline_cb_style); // The calculation below determines the size of the inline containing block // rect. @@ -362,7 +360,11 @@ void NGOutOfFlowLayoutPart::ComputeInlineContainingBlocks( // // Note in cases [2a, 2b] we don't allow a "negative" containing block size, // we clamp negative sizes to zero. + const ComputedStyle* inline_cb_style = block_info.key->Style(); + DCHECK(inline_cb_style); + TextDirection container_direction = default_containing_block_.direction; + NGBoxStrut inline_cb_borders = ComputeBordersForInline(*inline_cb_style); bool is_same_direction = container_direction == inline_cb_style->Direction(); @@ -403,13 +405,14 @@ void NGOutOfFlowLayoutPart::ComputeInlineContainingBlocks( // Step 3 - determine the logical rectangle. // Determine the logical size of the containing block. - inline_cb_size = {end_offset.inline_offset - start_offset.inline_offset, - end_offset.block_offset - start_offset.block_offset}; + LogicalSize inline_cb_size = { + end_offset.inline_offset - start_offset.inline_offset, + end_offset.block_offset - start_offset.block_offset}; DCHECK_GE(inline_cb_size.inline_size, LayoutUnit()); DCHECK_GE(inline_cb_size.block_size, LayoutUnit()); // Set the container padding-box offset. - container_offset = start_offset; + LogicalOffset container_offset = start_offset; containing_blocks_map_.insert( block_info.key, @@ -525,12 +528,12 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutCandidate( // Since out-of-flow positioning sets up a constraint space with fixed // inline-size, the regular layout code (|NGBlockNode::Layout()|) cannot // re-layout if it discovers that a scrollbar was added or removed. Handle - // that situation here. The assumption is that if preferred logical widths - // are dirty after layout, AND its inline-size depends on preferred + // that situation here. The assumption is that if intrinsic logical widths + // are dirty after layout, AND its inline-size depends on the intrinsic // logical widths, it means that scrollbars appeared or disappeared. We // have the same logic in legacy layout in // |LayoutBlockFlow::UpdateBlockLayout()|. - if (node.GetLayoutBox()->PreferredLogicalWidthsDirty() && + if (node.GetLayoutBox()->IntrinsicLogicalWidthsDirty() && AbsoluteNeedsChildInlineSize(candidate_style)) { // Freeze the scrollbars for this layout pass. We don't want them to // change *again*. @@ -561,12 +564,12 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( LogicalSize container_content_size_in_candidate_writing_mode = container_physical_content_size.ConvertToLogical(candidate_writing_mode); NGBoxStrut border_padding = - ComputeBorders(candidate_constraint_space, node) + + ComputeBorders(candidate_constraint_space, candidate_style) + ComputePadding(candidate_constraint_space, candidate_style); // The |block_estimate| is wrt. the candidate's writing mode. base::Optional<LayoutUnit> block_estimate; - base::Optional<MinMaxSize> min_max_size; + base::Optional<MinMaxSizes> min_max_sizes; scoped_refptr<const NGLayoutResult> layout_result = nullptr; // In order to calculate the offsets, we may need to know the size. @@ -577,45 +580,61 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( // words, in that case, we may have to lay out, calculate the offset, and // then lay out again at the correct block-offset. + NGLogicalOutOfFlowDimensions node_dimensions; + bool has_computed_block_dimensions = false; bool is_replaced = node.IsReplaced(); bool should_be_considered_as_replaced = node.ShouldBeConsideredAsReplaced(); + bool absolute_needs_child_block_size = + AbsoluteNeedsChildBlockSize(candidate_style); if (AbsoluteNeedsChildInlineSize(candidate_style) || NeedMinMaxSize(candidate_style) || should_be_considered_as_replaced) { - // This is a new formatting context, so whatever happened on the outside - // doesn't concern us. - MinMaxSizeInput input(container_content_size.block_size); - min_max_size = ComputeMinAndMaxContentSizeForOutOfFlow( - candidate_constraint_space, node, border_padding, input); + MinMaxSizesInput input(kIndefiniteSize); + if (is_replaced) { + input.percentage_resolution_block_size = + container_content_size_in_candidate_writing_mode.block_size; + } else if (!absolute_needs_child_block_size) { + // If we can determine our block-size ahead of time (it doesn't depend on + // our content), we use this for our %-block-size. + ComputeOutOfFlowBlockDimensions( + candidate_constraint_space, candidate_style, border_padding, + candidate_static_position, base::nullopt, base::nullopt, + writing_mode_, container_direction, &node_dimensions); + has_computed_block_dimensions = true; + input.percentage_resolution_block_size = node_dimensions.size.block_size; + } + + min_max_sizes = node.ComputeMinMaxSizes(candidate_writing_mode, input, + &candidate_constraint_space); } base::Optional<LogicalSize> replaced_size; base::Optional<LogicalSize> replaced_aspect_ratio; bool is_replaced_with_only_aspect_ratio = false; if (is_replaced) { - ComputeReplacedSize(node, candidate_constraint_space, min_max_size, + ComputeReplacedSize(node, candidate_constraint_space, min_max_sizes, &replaced_size, &replaced_aspect_ratio); is_replaced_with_only_aspect_ratio = !replaced_size && replaced_aspect_ratio && !replaced_aspect_ratio->IsEmpty(); // If we only have aspect ratio, and no replaced size, intrinsic size - // defaults to 300x150. min_max_size gets computed from the intrinsic size. - // We reset the min_max_size because spec says that OOF-positioned size + // defaults to 300x150. min_max_sizes gets computed from the intrinsic size. + // We reset the min_max_sizes because spec says that OOF-positioned size // should not be constrained by intrinsic size in this case. // https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width if (is_replaced_with_only_aspect_ratio) - min_max_size = MinMaxSize{LayoutUnit(), LayoutUnit::NearlyMax()}; + min_max_sizes = MinMaxSizes{LayoutUnit(), LayoutUnit::NearlyMax()}; } else if (should_be_considered_as_replaced) { replaced_size = - LogicalSize{min_max_size->ShrinkToFit( + LogicalSize{min_max_sizes->ShrinkToFit( candidate_constraint_space.AvailableSize().inline_size), kIndefiniteSize}; } - NGLogicalOutOfFlowPosition node_position = - ComputePartialAbsoluteWithChildInlineSize( - candidate_constraint_space, candidate_style, border_padding, - candidate_static_position, min_max_size, replaced_size, writing_mode_, - container_direction); + + ComputeOutOfFlowInlineDimensions(candidate_constraint_space, candidate_style, + border_padding, candidate_static_position, + min_max_sizes, replaced_size, writing_mode_, + container_direction, &node_dimensions); // |should_be_considered_as_replaced| sets the inline-size. // It does not set the block-size. This is a compatibility quirk. @@ -627,16 +646,20 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( // https://www.w3.org/TR/css-sizing-3/#intrinsic-sizes if (is_replaced_with_only_aspect_ratio) { replaced_size = LogicalSize( - node_position.size.inline_size, + node_dimensions.size.inline_size, (replaced_aspect_ratio->block_size * - ((node_position.size.inline_size - border_padding.InlineSum()) / + ((node_dimensions.size.inline_size - border_padding.InlineSum()) / replaced_aspect_ratio->inline_size)) + border_padding.BlockSum()); } - if (AbsoluteNeedsChildBlockSize(candidate_style)) { + if (absolute_needs_child_block_size) { + DCHECK(!has_computed_block_dimensions); layout_result = GenerateFragment(node, container_content_size_in_candidate_writing_mode, - block_estimate, node_position); + block_estimate, node_dimensions); + + // TODO(layout-dev): Handle abortions caused by block fragmentation. + DCHECK(layout_result->Status() != NGLayoutResult::kOutOfFragmentainerSpace); NGFragment fragment(candidate_writing_mode, layout_result->PhysicalFragment()); @@ -644,15 +667,18 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( block_estimate = fragment.BlockSize(); } - // Calculate the offsets. - - ComputeFullAbsoluteWithChildBlockSize( - candidate_constraint_space, candidate_style, border_padding, - candidate_static_position, block_estimate, replaced_size, writing_mode_, - container_direction, &node_position); + // We may have already pre-computed our block-dimensions when determining our + // |min_max_sizes|, only run if needed. + if (!has_computed_block_dimensions) { + ComputeOutOfFlowBlockDimensions( + candidate_constraint_space, candidate_style, border_padding, + candidate_static_position, block_estimate, replaced_size, writing_mode_, + container_direction, &node_dimensions); + } + // Calculate the offsets. NGBoxStrut inset = - node_position.inset + node_dimensions.inset .ConvertToPhysical(candidate_writing_mode, candidate_direction) .ConvertToLogical(writing_mode_, default_direction); @@ -719,12 +745,15 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( // Skip this step if we produced a fragment when estimating the block-size. if (!layout_result) { - block_estimate = node_position.size.block_size; + block_estimate = node_dimensions.size.block_size; layout_result = GenerateFragment(node, container_content_size_in_candidate_writing_mode, - block_estimate, node_position); + block_estimate, node_dimensions); } + // TODO(layout-dev): Handle abortions caused by block fragmentation. + DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess); + // TODO(mstensho): Move the rest of this method back into LayoutCandidate(). if (node.GetLayoutBox()->IsLayoutNGObject()) { @@ -736,7 +765,7 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( if (!container_builder_->GetLayoutObject() ->Style() ->IsDisplayFlexibleOrGridBox()) { - node.GetLayoutBox()->SetMargin(node_position.margins.ConvertToPhysical( + node.GetLayoutBox()->SetMargin(node_dimensions.margins.ConvertToPhysical( candidate_writing_mode, candidate_direction)); } @@ -785,12 +814,12 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::GenerateFragment( NGBlockNode node, const LogicalSize& container_content_size_in_candidate_writing_mode, const base::Optional<LayoutUnit>& block_estimate, - const NGLogicalOutOfFlowPosition& node_position) { + const NGLogicalOutOfFlowDimensions& node_dimensions) { // As the |block_estimate| is always in the node's writing mode, we build the // constraint space in the node's writing mode. WritingMode writing_mode = node.Style().GetWritingMode(); - LayoutUnit inline_size = node_position.size.inline_size; + LayoutUnit inline_size = node_dimensions.size.inline_size; LayoutUnit block_size = block_estimate.value_or( container_content_size_in_candidate_writing_mode.block_size); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h index e8d9ad81dbd..777309f62ef 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h @@ -122,7 +122,7 @@ class CORE_EXPORT NGOutOfFlowLayoutPart { NGBlockNode node, const LogicalSize& container_content_size_in_child_writing_mode, const base::Optional<LayoutUnit>& block_estimate, - const NGLogicalOutOfFlowPosition& node_position); + const NGLogicalOutOfFlowDimensions& node_dimensions); const NGConstraintSpace& container_space_; NGBoxFragmentBuilder* container_builder_; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h index 1037e23bca2..3f9a5127140 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h @@ -54,14 +54,17 @@ struct NGLogicalOutOfFlowPositionedNode { NGLogicalStaticPosition static_position; // Continuation root of the optional inline container. const LayoutInline* inline_container; + bool needs_block_offset_adjustment; NGLogicalOutOfFlowPositionedNode( NGBlockNode node, NGLogicalStaticPosition static_position, - const LayoutInline* inline_container = nullptr) + const LayoutInline* inline_container = nullptr, + bool needs_block_offset_adjustment = false) : node(node), static_position(static_position), - inline_container(inline_container) { + inline_container(inline_container), + needs_block_offset_adjustment(needs_block_offset_adjustment) { DCHECK(!inline_container || inline_container == inline_container->ContinuationRoot()); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc index dece4940984..805794074b6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_outline_utils.cc @@ -46,7 +46,7 @@ bool NGOutlineUtils::ShouldPaintOutline( NGInlineCursor cursor; cursor.MoveTo(*layout_object); DCHECK(cursor); - return cursor.CurrentBoxFragment() == &physical_fragment; + return cursor.Current().BoxFragment() == &physical_fragment; } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc index 21ab11cf7b5..5328304e619 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc @@ -5,7 +5,6 @@ #include "third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h" #include <algorithm> -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h" #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" @@ -81,13 +80,13 @@ scoped_refptr<const NGLayoutResult> NGPageLayoutAlgorithm::Layout() { return container_builder_.ToBoxFragment(); } -base::Optional<MinMaxSize> NGPageLayoutAlgorithm::ComputeMinMaxSize( - const MinMaxSizeInput& input) const { +base::Optional<MinMaxSizes> NGPageLayoutAlgorithm::ComputeMinMaxSizes( + const MinMaxSizesInput& input) const { NGFragmentGeometry fragment_geometry = CalculateInitialMinMaxFragmentGeometry(ConstraintSpace(), Node()); NGBlockLayoutAlgorithm algorithm( {Node(), fragment_geometry, ConstraintSpace()}); - return algorithm.ComputeMinMaxSize(input); + return algorithm.ComputeMinMaxSizes(input); } NGConstraintSpace NGPageLayoutAlgorithm::CreateConstraintSpaceForPages( @@ -97,9 +96,6 @@ NGConstraintSpace NGPageLayoutAlgorithm::CreateConstraintSpaceForPages( space_builder.SetAvailableSize(page_size); space_builder.SetPercentageResolutionSize(page_size); - if (NGBaseline::ShouldPropagateBaselines(Node())) - space_builder.AddBaselineRequests(ConstraintSpace().BaselineRequests()); - // TODO(mstensho): Handle auto block size. space_builder.SetFragmentationType(kFragmentPage); space_builder.SetFragmentainerBlockSize(page_size.block_size); diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h index f05b8a8d717..8797b3ba1ac 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h @@ -25,8 +25,8 @@ class CORE_EXPORT NGPageLayoutAlgorithm scoped_refptr<const NGLayoutResult> Layout() override; - base::Optional<MinMaxSize> ComputeMinMaxSize( - const MinMaxSizeInput&) const override; + base::Optional<MinMaxSizes> ComputeMinMaxSizes( + const MinMaxSizesInput&) const override; private: NGConstraintSpace CreateConstraintSpaceForPages( diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc index d550eea7cd0..2092c9bbe84 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc @@ -10,6 +10,7 @@ #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/ng/inline/ng_fragment_item.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" @@ -22,7 +23,8 @@ namespace blink { namespace { struct SameSizeAsNGPhysicalBoxFragment : NGPhysicalContainerFragment { - NGBaselineList baselines; + LayoutUnit baseline; + LayoutUnit last_baseline; NGLink children[]; }; @@ -63,25 +65,23 @@ scoped_refptr<const NGPhysicalBoxFragment> NGPhysicalBoxFragment::Create( // we pass the buffer as a constructor argument. void* data = ::WTF::Partitions::FastMalloc( byte_size, ::WTF::GetStringWithTypeName<NGPhysicalBoxFragment>()); - new (data) NGPhysicalBoxFragment(builder, borders, padding, + new (data) NGPhysicalBoxFragment(PassKey(), builder, borders, padding, block_or_line_writing_mode); return base::AdoptRef(static_cast<NGPhysicalBoxFragment*>(data)); } NGPhysicalBoxFragment::NGPhysicalBoxFragment( + PassKey key, NGBoxFragmentBuilder* builder, const NGPhysicalBoxStrut& borders, const NGPhysicalBoxStrut& padding, WritingMode block_or_line_writing_mode) - : NGPhysicalContainerFragment( - builder, - block_or_line_writing_mode, - children_, - (builder->node_ && builder->node_.IsRenderedLegend()) - ? kFragmentRenderedLegend - : kFragmentBox, - builder->BoxType()), - baselines_(builder->baselines_) { + : NGPhysicalContainerFragment(builder, + block_or_line_writing_mode, + children_, + kFragmentBox, + builder->BoxType()) { + DCHECK(layout_object_); DCHECK(layout_object_->IsBoxModelObject()); if (NGFragmentItemsBuilder* items_builder = builder->ItemsBuilder()) { has_fragment_items_ = true; @@ -98,15 +98,34 @@ NGPhysicalBoxFragment::NGPhysicalBoxFragment( has_padding_ = !padding.IsZero(); if (has_padding_) *const_cast<NGPhysicalBoxStrut*>(ComputePaddingAddress()) = padding; - // consumed_block_size_ is only updated if we're in block - // fragmentation. Otherwise it will always be 0. - is_first_for_node_ = - builder->consumed_block_size_ <= builder->size_.block_size; + is_first_for_node_ = builder->is_first_for_node_; is_fieldset_container_ = builder->is_fieldset_container_; is_legacy_layout_root_ = builder->is_legacy_layout_root_; + is_painted_atomically_ = + builder->space_ && builder->space_->IsPaintedAtomically(); border_edge_ = builder->border_edges_.ToPhysical(builder->GetWritingMode()); - children_inline_ = - builder->layout_object_ && builder->layout_object_->ChildrenInline(); + is_inline_formatting_context_ = builder->is_inline_formatting_context_; + is_generated_text_or_math_fraction_ = builder->is_math_fraction_; + + bool has_layout_containment = layout_object_->ShouldApplyLayoutContainment(); + if (builder->baseline_.has_value() && !has_layout_containment) { + has_baseline_ = true; + baseline_ = *builder->baseline_; + } else { + has_baseline_ = false; + baseline_ = LayoutUnit::Min(); + } + if (builder->last_baseline_.has_value() && !has_layout_containment) { + has_last_baseline_ = true; + last_baseline_ = *builder->last_baseline_; + } else { + has_last_baseline_ = false; + last_baseline_ = LayoutUnit::Min(); + } + +#if DCHECK_IS_ON() + CheckIntegrity(); +#endif } scoped_refptr<const NGLayoutResult> @@ -153,7 +172,7 @@ PhysicalRect NGPhysicalBoxFragment::ScrollableOverflow() const { TextDirection container_direction = Style().Direction(); for (const auto& child_fragment : Children()) { PhysicalRect child_overflow = - child_fragment->ScrollableOverflowForPropagation(layout_object); + child_fragment->ScrollableOverflowForPropagation(*this); if (child_fragment->Style() != Style()) { PhysicalOffset relative_offset = ComputeRelativeOffset( child_fragment->Style(), container_writing_mode, @@ -170,6 +189,157 @@ PhysicalRect NGPhysicalBoxFragment::ScrollableOverflow() const { return PhysicalRect({}, Size()); } +PhysicalRect NGPhysicalBoxFragment::ScrollableOverflowFromChildren() const { + const NGFragmentItems* items = Items(); + if (Children().empty() && !items) + return PhysicalRect(); + + // Internal struct to share logic between child fragments and child items. + // - Inline children's overflow expands by padding end/after. + // - Float / OOF overflow is added as is. + // - Children not reachable by scroll overflow do not contribute to it. + struct ComputeOverflowContext { + ComputeOverflowContext(const NGPhysicalBoxFragment& container) + : container(container), + style(container.Style()), + writing_mode(style.GetWritingMode()), + direction(style.Direction()), + border_inline_start(LayoutUnit(style.BorderStartWidth())), + border_block_start(LayoutUnit(style.BorderBeforeWidth())) { + DCHECK_EQ(&style, container.GetLayoutObject()->Style( + container.UsesFirstLineStyle())); + + // End and under padding are added to scroll overflow of inline children. + // https://github.com/w3c/csswg-drafts/issues/129 + DCHECK_EQ(container.HasOverflowClip(), + container.GetLayoutObject()->HasOverflowClip()); + if (container.HasOverflowClip()) { + const LayoutBox* layout_object = + ToLayoutBox(container.GetLayoutObject()); + padding_strut = NGBoxStrut(LayoutUnit(), layout_object->PaddingEnd(), + LayoutUnit(), layout_object->PaddingAfter()) + .ConvertToPhysical(writing_mode, direction); + } + } + + // Rectangles not reachable by scroll should not be added to overflow. + bool IsRectReachableByScroll(const PhysicalRect& rect) { + LogicalOffset rect_logical_end = + rect.offset.ConvertToLogical(writing_mode, direction, + container.Size(), rect.size) + + rect.size.ConvertToLogical(writing_mode); + return rect_logical_end.inline_offset > border_inline_start && + rect_logical_end.block_offset > border_block_start; + } + + void AddChild(const PhysicalRect& child_scrollable_overflow) { + // Do not add overflow if fragment is not reachable by scrolling. + if (IsRectReachableByScroll(child_scrollable_overflow)) + children_overflow.Unite(child_scrollable_overflow); + } + + void AddFloatingOrOutOfFlowPositionedChild( + const NGPhysicalFragment& child, + const PhysicalOffset& child_offset) { + DCHECK(child.IsFloatingOrOutOfFlowPositioned()); + PhysicalRect child_scrollable_overflow = + child.ScrollableOverflowForPropagation(container); + child_scrollable_overflow.offset += ComputeRelativeOffset( + child.Style(), writing_mode, direction, container.Size()); + child_scrollable_overflow.offset += child_offset; + AddChild(child_scrollable_overflow); + } + + void AddLineBoxChild(const NGPhysicalLineBoxFragment& child, + const PhysicalOffset& child_offset) { + if (padding_strut) + AddLineBoxRect({child_offset, child.Size()}); + PhysicalRect child_scrollable_overflow = + child.ScrollableOverflow(container, style); + child_scrollable_overflow.offset += child_offset; + AddChild(child_scrollable_overflow); + } + + void AddLineBoxChild(const NGFragmentItem& child, + const NGInlineCursor& cursor) { + DCHECK_EQ(&child, cursor.CurrentItem()); + DCHECK_EQ(child.Type(), NGFragmentItem::kLine); + if (padding_strut) + AddLineBoxRect(child.RectInContainerBlock()); + const NGPhysicalLineBoxFragment* line_box = child.LineBoxFragment(); + DCHECK(line_box); + PhysicalRect child_scrollable_overflow = + line_box->ScrollableOverflowForLine(container, style, child, cursor); + AddChild(child_scrollable_overflow); + } + + void AddLineBoxRect(const PhysicalRect& linebox_rect) { + DCHECK(padding_strut); + if (lineboxes_enclosing_rect) + lineboxes_enclosing_rect->Unite(linebox_rect); + else + lineboxes_enclosing_rect = linebox_rect; + } + + void AddPaddingToLineBoxChildren() { + if (lineboxes_enclosing_rect) { + DCHECK(padding_strut); + lineboxes_enclosing_rect->Expand(*padding_strut); + AddChild(*lineboxes_enclosing_rect); + } + } + + const NGPhysicalBoxFragment& container; + const ComputedStyle& style; + const WritingMode writing_mode; + const TextDirection direction; + const LayoutUnit border_inline_start; + const LayoutUnit border_block_start; + base::Optional<NGPhysicalBoxStrut> padding_strut; + base::Optional<PhysicalRect> lineboxes_enclosing_rect; + PhysicalRect children_overflow; + } context(*this); + + // Traverse child items. + if (items) { + for (NGInlineCursor cursor(*items); cursor; cursor.MoveToNextSibling()) { + const NGFragmentItem* item = cursor.CurrentItem(); + if (item->Type() == NGFragmentItem::kLine) { + context.AddLineBoxChild(*item, cursor); + continue; + } + + if (const NGPhysicalBoxFragment* child_box = item->BoxFragment()) { + if (child_box->IsFloatingOrOutOfFlowPositioned()) { + context.AddFloatingOrOutOfFlowPositionedChild( + *child_box, item->OffsetInContainerBlock()); + } + } + } + } + + // Traverse child fragments. + const bool children_inline = IsInlineFormattingContext(); + // Only add overflow for fragments NG has not reflected into Legacy. + // These fragments are: + // - inline fragments, + // - out of flow fragments whose css container is inline box. + // TODO(layout-dev) Transforms also need to be applied to compute overflow + // correctly. NG is not yet transform-aware. crbug.com/855965 + for (const auto& child : Children()) { + if (child->IsFloatingOrOutOfFlowPositioned()) { + context.AddFloatingOrOutOfFlowPositionedChild(*child, child.Offset()); + } else if (children_inline && child->IsLineBox()) { + context.AddLineBoxChild(To<NGPhysicalLineBoxFragment>(*child), + child.Offset()); + } + } + + context.AddPaddingToLineBoxChildren(); + + return context.children_overflow; +} + LayoutSize NGPhysicalBoxFragment::PixelSnappedScrolledContentOffset() const { DCHECK(GetLayoutObject() && GetLayoutObject()->IsBox()); const LayoutBox* box = ToLayoutBox(GetLayoutObject()); @@ -318,9 +488,10 @@ void NGPhysicalBoxFragment::CheckSameForSimplifiedLayout( DCHECK_EQ(depends_on_percentage_block_size_, other.depends_on_percentage_block_size_); - DCHECK_EQ(children_inline_, other.children_inline_); + DCHECK_EQ(is_inline_formatting_context_, other.is_inline_formatting_context_); DCHECK_EQ(is_fieldset_container_, other.is_fieldset_container_); DCHECK_EQ(is_legacy_layout_root_, other.is_legacy_layout_root_); + DCHECK_EQ(is_painted_atomically_, other.is_painted_atomically_); DCHECK_EQ(border_edge_, other.border_edge_); // The oof_positioned_descendants_ vector can change during "simplified" @@ -329,10 +500,63 @@ void NGPhysicalBoxFragment::CheckSameForSimplifiedLayout( // Legacy layout can (incorrectly) shift baseline position(s) during // "simplified" layout. - DCHECK(IsLegacyLayoutRoot() || baselines_ == other.baselines_); + DCHECK(IsLegacyLayoutRoot() || Baseline() == other.Baseline()); + DCHECK(IsLegacyLayoutRoot() || LastBaseline() == other.LastBaseline()); DCHECK(Borders() == other.Borders()); DCHECK(Padding() == other.Padding()); } + +// Check our flags represent the actual children correctly. +void NGPhysicalBoxFragment::CheckIntegrity() const { + bool has_inflow_blocks = false; + bool has_inlines = false; + bool has_line_boxes = false; + bool has_floats = false; + bool has_list_markers = false; + + for (const NGLink& child : Children()) { + if (child->IsFloating()) + has_floats = true; + else if (child->IsOutOfFlowPositioned()) + ; // OOF can be in the fragment tree regardless of |HasItems|. + else if (child->IsLineBox()) + has_line_boxes = true; + else if (child->IsListMarker()) + has_list_markers = true; + else if (child->IsInline()) + has_inlines = true; + else + has_inflow_blocks = true; + } + + // If we have line boxes, |IsInlineFormattingContext()| is true, but the + // reverse is not always true. + if (has_line_boxes || has_inlines) + DCHECK(IsInlineFormattingContext()); + + // If display-locked, we may not have any children. + DCHECK(layout_object_); + if (layout_object_ && layout_object_->PaintBlockedByDisplayLock( + DisplayLockLifecycleTarget::kChildren)) + return; + + if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) { + if (RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) { + if (has_line_boxes) + DCHECK(HasItems()); + } else { + DCHECK_EQ(HasItems(), has_line_boxes); + } + + if (has_line_boxes) { + DCHECK(!has_inlines); + DCHECK(!has_inflow_blocks); + // Following objects should be in the items, not in the tree. + DCHECK(!has_floats); + DCHECK(!has_list_markers); + } + } +} #endif } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h index 5fdc31c023e..21527728c01 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h @@ -7,7 +7,6 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h" #include "third_party/blink/renderer/platform/graphics/scroll_types.h" @@ -25,6 +24,13 @@ class CORE_EXPORT NGPhysicalBoxFragment final NGBoxFragmentBuilder* builder, WritingMode block_or_line_writing_mode); + using PassKey = util::PassKey<NGPhysicalBoxFragment>; + NGPhysicalBoxFragment(PassKey, + NGBoxFragmentBuilder* builder, + const NGPhysicalBoxStrut& borders, + const NGPhysicalBoxStrut& padding, + WritingMode block_or_line_writing_mode); + scoped_refptr<const NGLayoutResult> CloneAsHiddenForPaint() const; ~NGPhysicalBoxFragment() { @@ -40,8 +46,16 @@ class CORE_EXPORT NGPhysicalBoxFragment final return has_fragment_items_ ? ComputeItemsAddress() : nullptr; } - base::Optional<LayoutUnit> Baseline(const NGBaselineRequest& request) const { - return baselines_.Offset(request); + base::Optional<LayoutUnit> Baseline() const { + if (has_baseline_) + return baseline_; + return base::nullopt; + } + + base::Optional<LayoutUnit> LastBaseline() const { + if (has_last_baseline_) + return last_baseline_; + return base::nullopt; } const NGPhysicalBoxStrut Borders() const { @@ -63,9 +77,40 @@ class CORE_EXPORT NGPhysicalBoxFragment final } bool HasSelfPaintingLayer() const; - bool ChildrenInline() const { return children_inline_; } + + // Return true if this is either a container that establishes an inline + // formatting context, or if it's non-atomic inline content participating in + // one. Empty blocks don't establish an inline formatting context. + // + // The return value from this method is undefined and irrelevant if the object + // establishes a different type of formatting context than block/inline, such + // as table or flexbox. + // + // Example: + // <div> <!-- false --> + // <div> <!-- true --> + // <div style="float:left;"></div> <!-- false --> + // <div style="float:left;"> <!-- true --> + // xxx <!-- true --> + // </div> + // <div style="float:left;"> <!-- false --> + // <div style="float:left;"></div> <!-- false --> + // </div> + // <span> <!-- true --> + // xxx <!-- true --> + // <span style="display:inline-block;"> <!-- false --> + // <div></div> <!-- false --> + // </span> + // <span style="display:inline-block;"> <!-- true --> + // xxx <!-- true --> + // </span> + // <span style="display:inline-flex;"> <!-- N/A --> + bool IsInlineFormattingContext() const { + return is_inline_formatting_context_; + } PhysicalRect ScrollableOverflow() const; + PhysicalRect ScrollableOverflowFromChildren() const; // TODO(layout-dev): These three methods delegate to legacy layout for now, // update them to use LayoutNG based overflow information from the fragment @@ -79,6 +124,14 @@ class CORE_EXPORT NGPhysicalBoxFragment final // Compute visual overflow of this box in the local coordinate. PhysicalRect ComputeSelfInkOverflow() const; + // Contents ink overflow includes anything that would bleed out of the box and + // would be clipped by the overflow clip ('overflow' != visible). This + // corresponds to children that overflows their parent. + PhysicalRect ContentsInkOverflow() const { + // TODO(layout-dev): Implement box fragment overflow. + return LocalRect(); + } + // Fragment offset is this fragment's offset from parent. // Needed to compensate for LayoutInline Legacy code offsets. void AddSelfOutlineRects(const PhysicalOffset& additional_offset, @@ -91,20 +144,12 @@ class CORE_EXPORT NGPhysicalBoxFragment final unsigned BorderEdges() const { return border_edge_; } NGPixelSnappedPhysicalBoxStrut BorderWidths() const; - // Return true if this is the first fragment generated from a node. - bool IsFirstForNode() const { return is_first_for_node_; } - #if DCHECK_IS_ON() void CheckSameForSimplifiedLayout(const NGPhysicalBoxFragment&, bool check_same_block_size) const; #endif private: - NGPhysicalBoxFragment(NGBoxFragmentBuilder* builder, - const NGPhysicalBoxStrut& borders, - const NGPhysicalBoxStrut& padding, - WritingMode block_or_line_writing_mode); - const NGFragmentItems* ComputeItemsAddress() const { DCHECK(has_fragment_items_ || has_borders_ || has_padding_); const NGLink* children_end = children_ + Children().size(); @@ -125,7 +170,12 @@ class CORE_EXPORT NGPhysicalBoxFragment final return has_borders_ ? address + 1 : address; } - NGBaselineList baselines_; +#if DCHECK_IS_ON() + void CheckIntegrity() const; +#endif + + LayoutUnit baseline_; + LayoutUnit last_baseline_; NGLink children_[]; // borders and padding come from after |children_| if they are not zero. }; @@ -133,8 +183,7 @@ class CORE_EXPORT NGPhysicalBoxFragment final template <> struct DowncastTraits<NGPhysicalBoxFragment> { static bool AllowFrom(const NGPhysicalFragment& fragment) { - return fragment.Type() == NGPhysicalFragment::kFragmentBox || - fragment.Type() == NGPhysicalFragment::kFragmentRenderedLegend; + return fragment.Type() == NGPhysicalFragment::kFragmentBox; } }; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc index 327d158404a..78373f21bb6 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment_test.cc @@ -122,7 +122,7 @@ TEST_F(NGPhysicalBoxFragmentTest, DISABLED_NormalLegacyLayoutRoot) { EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kNormalBox, fragment->BoxType()); EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); - EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); + EXPECT_TRUE(fragment->IsFormattingContextRoot()); } // TODO(editing-dev): Once LayoutNG supports editing, we should change this @@ -136,7 +136,7 @@ TEST_F(NGPhysicalBoxFragmentTest, DISABLED_FloatLegacyLayoutRoot) { EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kFloating, fragment->BoxType()); EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); - EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); + EXPECT_TRUE(fragment->IsFormattingContextRoot()); } // TODO(editing-dev): Once LayoutNG supports editing, we should change this @@ -152,7 +152,7 @@ TEST_F(NGPhysicalBoxFragmentTest, DISABLED_InlineBlockLegacyLayoutRoot) { EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kAtomicInline, fragment->BoxType()); EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); - EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); + EXPECT_TRUE(fragment->IsFormattingContextRoot()); } // TODO(editing-dev): Once LayoutNG supports editing, we should change this @@ -170,7 +170,7 @@ TEST_F(NGPhysicalBoxFragmentTest, EXPECT_TRUE(fragment->IsBox()); EXPECT_EQ(NGPhysicalFragment::kOutOfFlowPositioned, fragment->BoxType()); EXPECT_TRUE(fragment->IsLegacyLayoutRoot()); - EXPECT_TRUE(fragment->IsBlockFormattingContextRoot()); + EXPECT_TRUE(fragment->IsFormattingContextRoot()); } TEST_F(NGPhysicalBoxFragmentTest, ReplacedBlock) { diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc index 6e7ac12f47b..82f57612ce4 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc @@ -6,10 +6,12 @@ #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/layout_inline.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h" #include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h" #include "third_party/blink/renderer/platform/geometry/layout_rect.h" namespace blink { @@ -88,6 +90,40 @@ void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren( const PhysicalOffset& additional_offset, NGOutlineType outline_type, const LayoutBoxModelObject* containing_block) const { + if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(this)) { + if (const NGFragmentItems* items = box->Items()) { + for (NGInlineCursor cursor(*items); cursor; cursor.MoveToNext()) { + DCHECK(cursor.Current().Item()); + const NGFragmentItem& item = *cursor.Current().Item(); + if (item.Type() == NGFragmentItem::kLine) { + AddOutlineRectsForDescendant( + {item.LineBoxFragment(), item.OffsetInContainerBlock()}, + outline_rects, additional_offset, outline_type, containing_block); + continue; + } + if (item.Type() == NGFragmentItem::kBox) { + if (const NGPhysicalBoxFragment* child_box = item.BoxFragment()) { + DCHECK(!child_box->IsOutOfFlowPositioned()); + AddOutlineRectsForDescendant( + {child_box, item.OffsetInContainerBlock()}, outline_rects, + additional_offset, outline_type, containing_block); + } + continue; + } + DCHECK(item.IsText()); + } + // Don't add |Children()|. If |this| has |NGFragmentItems|, children are + // either line box, which we already handled in items, or OOF, which we + // should ignore. + DCHECK(std::all_of(PostLayoutChildren().begin(), + PostLayoutChildren().end(), [](const NGLink& child) { + return child->IsLineBox() || + child->IsOutOfFlowPositioned(); + })); + return; + } + } + for (const auto& child : PostLayoutChildren()) { // Outlines of out-of-flow positioned descendants are handled in // NGPhysicalBoxFragment::AddSelfOutlineRects(). @@ -111,6 +147,84 @@ void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren( } } +void NGPhysicalContainerFragment::AddScrollableOverflowForInlineChild( + const NGPhysicalBoxFragment& container, + const ComputedStyle& container_style, + const NGFragmentItem& line, + bool has_hanging, + const NGInlineCursor& cursor, + PhysicalRect* overflow) const { + DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()); + DCHECK(IsLineBox() || IsInlineBox()); + DCHECK(cursor.Current().Item() && + (cursor.Current().Item()->BoxFragment() == this || + cursor.Current().Item()->LineBoxFragment() == this)); + const WritingMode container_writing_mode = container_style.GetWritingMode(); + const TextDirection container_direction = container_style.Direction(); + for (NGInlineCursor descendants = cursor.CursorForDescendants(); + descendants;) { + const NGFragmentItem* item = descendants.CurrentItem(); + DCHECK(item); + if (item->IsText()) { + PhysicalRect child_scroll_overflow = item->RectInContainerBlock(); + if (UNLIKELY(has_hanging)) { + AdjustScrollableOverflowForHanging(line.RectInContainerBlock(), + container_writing_mode, + &child_scroll_overflow); + } + overflow->Unite(child_scroll_overflow); + descendants.MoveToNextSkippingChildren(); + continue; + } + + if (const NGPhysicalBoxFragment* child_box = item->BoxFragment()) { + PhysicalRect child_scroll_overflow = item->RectInContainerBlock(); + if (child_box->IsInlineBox()) { + child_box->AddScrollableOverflowForInlineChild( + container, container_style, line, has_hanging, descendants, + &child_scroll_overflow); + child_box->AdjustScrollableOverflowForPropagation( + container, &child_scroll_overflow); + } else { + child_scroll_overflow = + child_box->ScrollableOverflowForPropagation(container); + child_scroll_overflow.offset += item->OffsetInContainerBlock(); + } + child_scroll_overflow.offset += + ComputeRelativeOffset(child_box->Style(), container_writing_mode, + container_direction, container.Size()); + overflow->Unite(child_scroll_overflow); + descendants.MoveToNextSkippingChildren(); + continue; + } + + // Add all children of a culled inline box; i.e., an inline box without + // margin/border/padding etc. + DCHECK_EQ(item->Type(), NGFragmentItem::kBox); + descendants.MoveToNext(); + } +} + +// Chop the hanging part from scrollable overflow. Children overflow in inline +// direction should hang, which should not cause scroll. +// TODO(kojii): Should move to text fragment to make this more accurate. +void NGPhysicalContainerFragment::AdjustScrollableOverflowForHanging( + const PhysicalRect& rect, + const WritingMode container_writing_mode, + PhysicalRect* overflow) { + if (IsHorizontalWritingMode(container_writing_mode)) { + if (overflow->offset.left < rect.offset.left) + overflow->offset.left = rect.offset.left; + if (overflow->Right() > rect.Right()) + overflow->ShiftRightEdgeTo(rect.Right()); + } else { + if (overflow->offset.top < rect.offset.top) + overflow->offset.top = rect.offset.top; + if (overflow->Bottom() > rect.Bottom()) + overflow->ShiftBottomEdgeTo(rect.Bottom()); + } +} + // additional_offset must be offset from the containing_block because // LocalToAncestorRect returns rects wrt containing_block. void NGPhysicalContainerFragment::AddOutlineRectsForDescendant( @@ -212,16 +326,23 @@ bool NGPhysicalContainerFragment::DependsOnPercentageBlockSize( // element if it has a percentage block-size however, but this will return // the correct result from below. + // There are two conditions where we need to know about an (arbitrary) + // descendant which depends on a %-block-size. + // - In quirks mode, the arbitrary descendant may depend the percentage + // resolution block-size given (to this node), and need to relayout if + // this size changes. + // - A flex-item may have its "definiteness" change, (e.g. if itself is a + // flex item which is being stretched). This definiteness change will + // affect any %-block-size children. + // + // NOTE(ikilpatrick): For the flex-item case this is potentially too general. + // We only need to know about if this flex-item has a %-block-size child if + // the "definiteness" changes, not if the percentage resolution size changes. if ((builder.has_descendant_that_depends_on_percentage_block_size_ || builder.is_legacy_layout_root_) && - node.UseParentPercentageResolutionBlockSizeForChildren()) { - // Quirks mode has different %-block-size behaviour, than standards mode. - // An arbitrary descendant may depend on the percentage resolution - // block-size given. - // If this is also an anonymous block we need to mark ourselves dependent - // if we have a dependent child. + (node.UseParentPercentageResolutionBlockSizeForChildren() || + node.IsFlexItem())) return true; - } const ComputedStyle& style = builder.Style(); if (style.LogicalHeight().IsPercentOrCalc() || diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h index 4cf2b93116b..b3c7624f494 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h @@ -17,6 +17,7 @@ namespace blink { class NGContainerFragmentBuilder; +class NGFragmentItem; struct NGPhysicalOutOfFlowPositionedNode; enum class NGOutlineType; @@ -149,6 +150,19 @@ class CORE_EXPORT NGPhysicalContainerFragment : public NGPhysicalFragment { NGFragmentType, unsigned sub_type); + void AddScrollableOverflowForInlineChild( + const NGPhysicalBoxFragment& container, + const ComputedStyle& container_style, + const NGFragmentItem& line, + bool has_hanging, + const NGInlineCursor& cursor, + PhysicalRect* overflow) const; + + static void AdjustScrollableOverflowForHanging( + const PhysicalRect& rect, + const WritingMode container_writing_mode, + PhysicalRect* overflow); + void AddOutlineRectsForNormalChildren( Vector<PhysicalRect>* outline_rects, const PhysicalOffset& additional_offset, diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc index 02c51c05535..d5f70584bc9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc @@ -80,6 +80,9 @@ String StringForBoxType(const NGPhysicalFragment& fragment) { case NGPhysicalFragment::NGBoxType::kBlockFlowRoot: result.Append("block-flow-root"); break; + case NGPhysicalFragment::NGBoxType::kRenderedLegend: + result.Append("rendered-legend"); + break; } if (fragment.IsLegacyLayoutRoot()) { if (result.length()) @@ -91,18 +94,13 @@ String StringForBoxType(const NGPhysicalFragment& fragment) { result.Append(" "); result.Append("block-flow"); } - if (fragment.IsRenderedLegend()) { - if (result.length()) - result.Append(" "); - result.Append("rendered-legend"); - } if (fragment.IsFieldsetContainer()) { if (result.length()) result.Append(" "); result.Append("fieldset-container"); } if (fragment.IsBox() && - static_cast<const NGPhysicalBoxFragment&>(fragment).ChildrenInline()) { + To<NGPhysicalBoxFragment>(fragment).IsInlineFormattingContext()) { if (result.length()) result.Append(" "); result.Append("children-inline"); @@ -124,10 +122,7 @@ void AppendFragmentToString(const NGPhysicalFragment* fragment, bool has_content = false; if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(fragment)) { if (flags & NGPhysicalFragment::DumpType) { - if (fragment->IsRenderedLegend()) - builder->Append("RenderedLegend"); - else - builder->Append("Box"); + builder->Append("Box"); String box_type = StringForBoxType(*fragment); has_content = true; if (!box_type.IsEmpty()) { @@ -224,9 +219,12 @@ NGPhysicalFragment::NGPhysicalFragment(NGFragmentBuilder* builder, sub_type_(sub_type), style_variant_((unsigned)builder->style_variant_), is_hidden_for_paint_(builder->is_hidden_for_paint_), + is_first_for_node_(true), has_floating_descendants_for_paint_(false), is_fieldset_container_(false), - is_legacy_layout_root_(false) { + is_legacy_layout_root_(false), + is_painted_atomically_(false), + has_baseline_(false) { DCHECK(builder->layout_object_); } @@ -243,7 +241,9 @@ NGPhysicalFragment::NGPhysicalFragment(LayoutObject* layout_object, is_hidden_for_paint_(false), has_floating_descendants_for_paint_(false), is_fieldset_container_(false), - is_legacy_layout_root_(false) { + is_legacy_layout_root_(false), + is_painted_atomically_(false), + has_baseline_(false) { DCHECK(layout_object); } @@ -254,7 +254,6 @@ NGPhysicalFragment::~NGPhysicalFragment() = default; void NGPhysicalFragment::Destroy() const { switch (Type()) { case kFragmentBox: - case kFragmentRenderedLegend: delete static_cast<const NGPhysicalBoxFragment*>(this); break; case kFragmentText: @@ -305,6 +304,19 @@ bool NGPhysicalFragment::IsPlacedByLayoutNG() const { return container->IsLayoutNGMixin(); } +const FragmentData* NGPhysicalFragment::GetFragmentData() const { + DCHECK(CanTraverse()); + const LayoutObject* layout_object = GetLayoutObject(); + if (!layout_object) + return nullptr; + // TODO(mstensho): Actually return the correct FragmentData. For now this + // method only behaves if there's just one FragmentData associated with the + // LayoutObject. + const FragmentData& first_fragment_data = layout_object->FirstFragment(); + DCHECK(!first_fragment_data.NextFragment()); + return &first_fragment_data; +} + const NGPhysicalFragment* NGPhysicalFragment::PostLayout() const { if (IsBox() && !IsInlineBox()) { if (const auto* block = DynamicTo<LayoutBlockFlow>(GetLayoutObject())) { @@ -322,7 +334,6 @@ const NGPhysicalFragment* NGPhysicalFragment::PostLayout() const { void NGPhysicalFragment::CheckType() const { switch (Type()) { case kFragmentBox: - case kFragmentRenderedLegend: if (IsInlineBox()) { DCHECK(layout_object_->IsLayoutInline()); } else { @@ -337,10 +348,10 @@ void NGPhysicalFragment::CheckType() const { DCHECK(!IsFloating()); DCHECK(!IsOutOfFlowPositioned()); DCHECK(!IsAtomicInline()); - DCHECK(!IsBlockFormattingContextRoot()); + DCHECK(!IsFormattingContextRoot()); break; } - if (layout_object_->IsLayoutNGListMarker()) { + if (layout_object_->IsLayoutNGOutsideListMarker()) { // List marker is an atomic inline if it appears in a line box, or a // block box. DCHECK(!IsFloating()); @@ -392,7 +403,6 @@ void NGPhysicalFragment::CheckCanUpdateInkOverflow() const { PhysicalRect NGPhysicalFragment::ScrollableOverflow() const { switch (Type()) { case kFragmentBox: - case kFragmentRenderedLegend: return To<NGPhysicalBoxFragment>(*this).ScrollableOverflow(); case kFragmentText: return {{}, Size()}; @@ -406,18 +416,30 @@ PhysicalRect NGPhysicalFragment::ScrollableOverflow() const { } PhysicalRect NGPhysicalFragment::ScrollableOverflowForPropagation( - const LayoutObject* container) const { - DCHECK(container); + const NGPhysicalBoxFragment& container) const { PhysicalRect overflow = ScrollableOverflow(); - if (GetLayoutObject() && - GetLayoutObject()->ShouldUseTransformFromContainer(container)) { + AdjustScrollableOverflowForPropagation(container, &overflow); + return overflow; +} + +void NGPhysicalFragment::AdjustScrollableOverflowForPropagation( + const NGPhysicalBoxFragment& container, + PhysicalRect* overflow) const { + DCHECK(!IsLineBox()); + if (!IsCSSBox()) + return; + + const LayoutObject* layout_object = GetLayoutObject(); + DCHECK(layout_object); + const LayoutObject* container_layout_object = container.GetLayoutObject(); + DCHECK(container_layout_object); + if (layout_object->ShouldUseTransformFromContainer(container_layout_object)) { TransformationMatrix transform; - GetLayoutObject()->GetTransformFromContainer(container, PhysicalOffset(), - transform); - overflow = - PhysicalRect::EnclosingRect(transform.MapRect(FloatRect(overflow))); + layout_object->GetTransformFromContainer(container_layout_object, + PhysicalOffset(), transform); + *overflow = + PhysicalRect::EnclosingRect(transform.MapRect(FloatRect(*overflow))); } - return overflow; } const Vector<NGInlineItem>& NGPhysicalFragment::InlineItemsOfContainingBlock() @@ -430,6 +452,7 @@ const Vector<NGInlineItem>& NGPhysicalFragment::InlineItemsOfContainingBlock() // modification. Unify them. DCHECK(block_flow); NGBlockNode block_node = NGBlockNode(block_flow); + DCHECK(block_node.IsInlineFormattingContextRoot()); DCHECK(block_node.CanUseNewLayout()); NGLayoutInputNode node = block_node.FirstChild(); @@ -447,7 +470,6 @@ UBiDiLevel NGPhysicalFragment::BidiLevel() const { case kFragmentText: return To<NGPhysicalTextFragment>(*this).BidiLevel(); case kFragmentBox: - case kFragmentRenderedLegend: return To<NGPhysicalBoxFragment>(*this).BidiLevel(); case kFragmentLineBox: break; @@ -461,7 +483,6 @@ TextDirection NGPhysicalFragment::ResolvedDirection() const { case kFragmentText: return To<NGPhysicalTextFragment>(*this).ResolvedDirection(); case kFragmentBox: - case kFragmentRenderedLegend: DCHECK(IsInline() && IsAtomicInline()); // TODO(xiaochengh): Store direction in |base_direction_| flag. return DirectionFromLevel(BidiLevel()); @@ -494,7 +515,6 @@ String NGPhysicalFragment::ToString() const { Size().ToString().Ascii().c_str()); switch (Type()) { case kFragmentBox: - case kFragmentRenderedLegend: output.AppendFormat(", BoxType: '%s'", StringForBoxType(*this).Ascii().c_str()); break; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h index e3b64e26d96..2fcd7049c96 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h @@ -11,7 +11,7 @@ #include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" #include "third_party/blink/renderer/core/layout/geometry/physical_size.h" -#include "third_party/blink/renderer/core/layout/layout_object.h" +#include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/ng/ng_style_variant.h" #include "third_party/blink/renderer/platform/graphics/touch_action.h" #include "third_party/blink/renderer/platform/wtf/ref_counted.h" @@ -21,6 +21,7 @@ namespace blink { class ComputedStyle; +class FragmentData; class Node; class NGFragmentBuilder; class NGInlineItem; @@ -50,7 +51,6 @@ class CORE_EXPORT NGPhysicalFragment kFragmentBox = 0, kFragmentText = 1, kFragmentLineBox = 2, - kFragmentRenderedLegend = 3, // When adding new values, make sure the bit size of |type_| is large // enough to store. }; @@ -64,13 +64,14 @@ class CORE_EXPORT NGPhysicalFragment kFloating, kOutOfFlowPositioned, kBlockFlowRoot, + kRenderedLegend, // When adding new values, make sure the bit size of |sub_type_| is large // enough to store. - // Also, add after kMinimumBlockFormattingContextRoot if the box type is a - // block formatting context root, or before otherwise. See - // IsBlockFormattingContextRoot(). - kMinimumBlockFormattingContextRoot = kAtomicInline + // Also, add after kMinimumFormattingContextRoot if the box type is a + // formatting context root, or before otherwise. See + // IsFormattingContextRoot(). + kMinimumFormattingContextRoot = kAtomicInline }; ~NGPhysicalFragment(); @@ -78,19 +79,12 @@ class CORE_EXPORT NGPhysicalFragment NGFragmentType Type() const { return static_cast<NGFragmentType>(type_); } bool IsContainer() const { return Type() == NGFragmentType::kFragmentBox || - Type() == NGFragmentType::kFragmentLineBox || - Type() == NGFragmentType::kFragmentRenderedLegend; + Type() == NGFragmentType::kFragmentLineBox; } bool IsBox() const { return Type() == NGFragmentType::kFragmentBox; } bool IsText() const { return Type() == NGFragmentType::kFragmentText; } bool IsLineBox() const { return Type() == NGFragmentType::kFragmentLineBox; } - // Return true if this is the legend child of a fieldset that gets special - // treatment (i.e. placed over the block-start border). - bool IsRenderedLegend() const { - return Type() == NGFragmentType::kFragmentRenderedLegend; - } - // Returns the box type of this fragment. NGBoxType BoxType() const { DCHECK(IsBox()); @@ -122,6 +116,14 @@ class CORE_EXPORT NGPhysicalFragment bool IsFloatingOrOutOfFlowPositioned() const { return IsFloating() || IsOutOfFlowPositioned(); } + // Return true if this is the legend child of a fieldset that gets special + // treatment (i.e. placed over the block-start border). + bool IsRenderedLegend() const { + return IsBox() && BoxType() == NGBoxType::kRenderedLegend; + } + bool IsMathMLFraction() const { + return IsBox() && is_generated_text_or_math_fraction_; + } // Return true if this fragment corresponds directly to an entry in the CSS // box tree [1]. Note that anonymous blocks also exist in the CSS box @@ -140,7 +142,7 @@ class CORE_EXPORT NGPhysicalFragment return IsCSSBox() && layout_object_->IsAnonymousBlock(); } bool IsListMarker() const { - return IsCSSBox() && layout_object_->IsLayoutNGListMarker(); + return IsCSSBox() && layout_object_->IsLayoutNGOutsideListMarker(); } // Return true if this fragment is a container established by a fieldset @@ -152,9 +154,11 @@ class CORE_EXPORT NGPhysicalFragment // Returns whether the fragment is legacy layout root. bool IsLegacyLayoutRoot() const { return is_legacy_layout_root_; } - bool IsBlockFormattingContextRoot() const { - return (IsBox() && - BoxType() >= NGBoxType::kMinimumBlockFormattingContextRoot) || + // Returns whether the fragment should be atomically painted. + bool IsPaintedAtomically() const { return is_painted_atomically_; } + + bool IsFormattingContextRoot() const { + return (IsBox() && BoxType() >= NGBoxType::kMinimumFormattingContextRoot) || IsLegacyLayoutRoot(); } @@ -164,6 +168,9 @@ class CORE_EXPORT NGPhysicalFragment // |LayoutNGBlockFlow::UpdateBlockLayout()| and crbug.com/788590 bool IsPlacedByLayoutNG() const; + // Return true if this is the first fragment generated from a node. + bool IsFirstForNode() const { return is_first_for_node_; } + // The accessors in this class shouldn't be used by layout code directly, // instead should be accessed by the NGFragmentBase classes. These accessors // exist for paint, hit-testing, etc. @@ -206,6 +213,17 @@ class CORE_EXPORT NGPhysicalFragment // from GetNode() when this fragment is content of a pseudo node. Node* NodeForHitTest() const { return layout_object_->NodeForHitTest(); } + bool IsInSelfHitTestingPhase(HitTestAction action) const { + if (const auto* box = ToLayoutBoxOrNull(GetLayoutObject())) + return box->IsInSelfHitTestingPhase(action); + if (IsInlineBox()) + return action == kHitTestForeground; + // Assuming this is some sort of container, e.g. a fragmentainer (they don't + // have a LayoutObject associated). + return action == kHitTestBlockBackground || + action == kHitTestChildBlockBackground; + } + // Whether there is a PaintLayer associated with the fragment. bool HasLayer() const { return IsCSSBox() && layout_object_->HasLayer(); } @@ -225,10 +243,31 @@ class CORE_EXPORT NGPhysicalFragment return IsCSSBox() && layout_object_->ShouldClipOverflow(); } + bool IsFragmentationContextRoot() const { + return !IsColumnBox() && IsBlockFlow() && Style().SpecifiesColumns(); + } + + // Return whether we can traverse this fragment and its children directly, for + // painting, hit-testing and other layout read operations. If false is + // returned, we need to traverse the layout object tree instead. + bool CanTraverse() const { + return layout_object_->CanTraversePhysicalFragments(); + } + // This fragment is hidden for paint purpose, but exists for querying layout // information. Used for `text-overflow: ellipsis`. bool IsHiddenForPaint() const { return is_hidden_for_paint_; } + // Return true if this fragment is monolithic, as far as block fragmentation + // is concerned. + bool IsMonolithic() const { + const LayoutObject* layout_object = GetLayoutObject(); + if (!layout_object || !IsBox() || !layout_object->IsBox()) + return false; + return ToLayoutBox(layout_object)->GetPaginationBreakability() == + LayoutBox::kForbidBreaks; + } + // GetLayoutObject should only be used when necessary for compatibility // with LegacyLayout. // @@ -244,6 +283,8 @@ class CORE_EXPORT NGPhysicalFragment return IsCSSBox() ? layout_object_ : nullptr; } + const FragmentData* GetFragmentData() const; + // |NGPhysicalFragment| may live longer than the corresponding |LayoutObject|. // Though |NGPhysicalFragment| is immutable, |layout_object_| is cleared to // |nullptr| when it was destroyed to avoid reading destroyed objects. @@ -260,13 +301,21 @@ class CORE_EXPORT NGPhysicalFragment // check if there were newer generations. const NGPhysicalFragment* PostLayout() const; + PhysicalRect InkOverflow() const { + // TODO(layout-dev): Implement box fragment overflow. + return LocalRect(); + } + // Scrollable overflow. including contents, in the local coordinate. PhysicalRect ScrollableOverflow() const; // ScrollableOverflow(), with transforms applied wrt container if needed. // This does not include any offsets from the parent (including relpos). PhysicalRect ScrollableOverflowForPropagation( - const LayoutObject* container) const; + const NGPhysicalBoxFragment& container) const; + void AdjustScrollableOverflowForPropagation( + const NGPhysicalBoxFragment& container, + PhysicalRect* overflow) const; // The allowed touch action is the union of the effective touch action // (from style) and blocking touch event handlers. @@ -336,6 +385,7 @@ class CORE_EXPORT NGPhysicalFragment const unsigned sub_type_ : 3; // NGBoxType, NGTextType, or NGLineBoxType const unsigned style_variant_ : 2; // NGStyleVariant const unsigned is_hidden_for_paint_ : 1; + unsigned is_first_for_node_ : 1; // The following bitfields are only to be used by NGPhysicalContainerFragment // (it's defined here to save memory, since that class has no bitfields). @@ -348,28 +398,37 @@ class CORE_EXPORT NGPhysicalFragment // The following bitfields are only to be used by NGPhysicalLineBoxFragment // (it's defined here to save memory, since that class has no bitfields). unsigned has_propagated_descendants_ : 1; - unsigned base_direction_ : 1; // TextDirection + // base (line box) or resolve (text) direction + unsigned base_or_resolved_direction_ : 1; // TextDirection unsigned has_hanging_ : 1; // The following bitfields are only to be used by NGPhysicalBoxFragment // (it's defined here to save memory, since that class has no bitfields). - unsigned children_inline_ : 1; + unsigned is_inline_formatting_context_ : 1; unsigned has_fragment_items_ : 1; unsigned border_edge_ : 4; // NGBorderEdges::Physical unsigned has_borders_ : 1; unsigned has_padding_ : 1; - unsigned is_first_for_node_ : 1; // The following are only used by NGPhysicalBoxFragment but are initialized // for all types to allow methods using them to be inlined. unsigned is_fieldset_container_ : 1; unsigned is_legacy_layout_root_ : 1; + unsigned is_painted_atomically_ : 1; + unsigned has_baseline_ : 1; + unsigned has_last_baseline_ : 1; + + // The following bitfield is shared between NGPhysicalTextFragment and + // NGPhysicalBoxFragment. + unsigned is_generated_text_or_math_fraction_ : 1; // The following bitfields are only to be used by NGPhysicalTextFragment // (it's defined here to save memory, since that class has no bitfields). - unsigned is_generated_text_ : 1; mutable unsigned ink_overflow_computed_ : 1; + // Note: We've used 32-bit bit field. If you need more bits, please think to + // share bit fields. + private: friend struct NGPhysicalFragmentTraits; void Destroy() const; diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc index 448c3e00d89..a2bec293a78 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc @@ -36,9 +36,11 @@ NGSimplifiedLayoutAlgorithm::NGSimplifiedLayoutAlgorithm( const NGPhysicalBoxFragment& physical_fragment = To<NGPhysicalBoxFragment>(result.PhysicalFragment()); + container_builder_.SetIsInlineFormattingContext( + Node().IsInlineFormattingContextRoot()); container_builder_.SetStyleVariant(physical_fragment.StyleVariant()); container_builder_.SetIsNewFormattingContext( - physical_fragment.IsBlockFormattingContextRoot()); + physical_fragment.IsFormattingContextRoot()); container_builder_.SetInitialFragmentGeometry(params.fragment_geometry); NGExclusionSpace exclusion_space = result.ExclusionSpace(); @@ -65,11 +67,10 @@ NGSimplifiedLayoutAlgorithm::NGSimplifiedLayoutAlgorithm( container_builder_.SetIsPushedByFloats(); container_builder_.SetAdjoiningObjectTypes(result.AdjoiningObjectTypes()); - for (const auto& request : ConstraintSpace().BaselineRequests()) { - base::Optional<LayoutUnit> baseline = physical_fragment.Baseline(request); - if (baseline) - container_builder_.AddBaseline(request, *baseline); - } + if (physical_fragment.Baseline()) + container_builder_.SetBaseline(*physical_fragment.Baseline()); + if (physical_fragment.LastBaseline()) + container_builder_.SetLastBaseline(*physical_fragment.LastBaseline()); container_builder_.SetBlockSize(ComputeBlockSizeForFragment( ConstraintSpace(), Style(), @@ -171,7 +172,7 @@ scoped_refptr<const NGLayoutResult> NGSimplifiedLayoutAlgorithm::Layout() { // Only take exclusion spaces from children which don't establish their own // formatting context. - if (!fragment.IsBlockFormattingContextRoot()) + if (!fragment.IsFormattingContextRoot()) exclusion_space_ = result->ExclusionSpace(); ++it; } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc index 3ae73180868..11eec2a89c9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc @@ -38,6 +38,7 @@ NGConstraintSpace CreateIndefiniteConstraintSpaceForChild( builder.SetAvailableSize(indefinite_size); builder.SetPercentageResolutionSize(indefinite_size); builder.SetReplacedPercentageResolutionSize(indefinite_size); + builder.SetIsShrinkToFit(child.Style().LogicalWidth().IsAuto()); return builder.ToConstraintSpace(); } diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc index 13a027e0b88..b67b5756cc9 100644 --- a/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc +++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc @@ -4,7 +4,6 @@ #include "third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.h" -#include "third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -19,15 +18,8 @@ int NGTextDecorationOffset::ComputeUnderlineOffsetForUnder( const ComputedStyle& style = text_style_; FontBaseline baseline_type = style.GetFontBaseline(); - if (decorating_box_) { - NGBaselineRequest baseline_request = { - NGBaselineAlgorithmType::kAtomicInline, - FontBaseline::kIdeographicBaseline}; - - if (base::Optional<LayoutUnit> baseline = - decorating_box_->Baseline(baseline_request)) - offset = *baseline; - } + if (decorating_box_) + offset = decorating_box_->Baseline().value_or(offset); if (offset == LayoutUnit::Max()) { // TODO(layout-dev): How do we compute the baseline offset with a |