blob: 5805a43450a4fa952fbbf4dbc2170c529c7a026f [file] [log] [blame]
// 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()) {
current_.link_.fragment = nullptr;
if (parent_break_token)
child_break_tokens_ = parent_break_token->ChildBreakTokens();
if (parent.HasItems()) {
current_.cursor_.emplace(parent);
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_]);
// TODO(mstensho): Clean up this. What we're trying to do here is to detect
// whether the incoming break token matches the current fragment or not.
// Figuring out if a fragment is generated from a given node is currently
// not possible without checking the LayoutObject associated.
const auto* layout_object = current_.link_.fragment->GetLayoutObject();
if (layout_object &&
layout_object !=
current_.block_break_token_->InputNode().GetLayoutBox()) {
DCHECK(current_.link_.fragment->IsColumnSpanAll());
current_.break_token_for_fragmentainer_only_ = true;
} else {
current_.break_token_for_fragmentainer_only_ = false;
}
} else if (is_fragmentation_context_root_ && previous_fragment) {
if (previous_fragment->IsFragmentainerBox()) {
// 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, or in the case of a fieldset, this could be a
// rendered legend. 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(previous_fragment->IsRenderedLegend() ||
previous_fragment->IsColumnSpanAll());
}
} else if (current_.link_.fragment->IsOutOfFlowPositioned() &&
!To<NGPhysicalBoxFragment>(current_.link_.fragment)
->IsFirstForNode()) {
// If an out-of-flow positioned element fragments beyond the last existing
// fragmentainer in a nested fragmentation context, instead of creating a
// new fragmentainer to hold it, we add it to the last existing
// fragmentainer at the correct inline offset. Therefore, in order to find
// the corrcet incoming break token in such cases, we must look for any
// previous fragments inside |children| that were created by the same node.
current_.block_break_token_ = nullptr;
const auto* layout_object = current_.link_.fragment->GetLayoutObject();
DCHECK(layout_object);
for (wtf_size_t index = child_fragment_idx_; index > 0; index--) {
const auto* child_fragment = children[index - 1].fragment;
if (layout_object == child_fragment->GetLayoutObject()) {
current_.block_break_token_ = To<NGBlockBreakToken>(
To<NGPhysicalBoxFragment>(child_fragment)->BreakToken());
current_.break_token_for_fragmentainer_only_ = false;
break;
}
}
} else {
current_.block_break_token_ = nullptr;
}
}
bool NGFragmentChildIterator::AdvanceWithCursor() {
DCHECK(current_.cursor_);
current_.cursor_->MoveToNextSkippingChildren();
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->OffsetInContainerFragment()};
}
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