// Copyright 2017 The Chromium Authors
// 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_BLOCK_BREAK_TOKEN_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_BLOCK_BREAK_TOKEN_H_

#include "base/dcheck_is_on.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/block_node.h"
#include "third_party/blink/renderer/core/layout/break_token.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

namespace blink {

class BoxFragmentBuilder;
class LayoutBox;
struct BreakTokenAlgorithmData;

// Represents a break token for a block node.
class CORE_EXPORT BlockBreakToken final : public BreakToken {
 public:
  // Creates a break token for a node which did fragment, and can potentially
  // produce more fragments.
  //
  // The node is BlockNode, or any other LayoutInputNode that produces
  // anonymous box.
  static BlockBreakToken* Create(BoxFragmentBuilder*);

  // Creates a break token for a node that needs to produce its first fragment
  // in a subsequent fragmentainer. In this case we create a break token for a
  // node that hasn't yet produced any fragments.
  //
  // An out-of-flow positioned node may need to skip multiple fragmentainers
  // before creating a fragment, for instance due to a large block-start inset.
  // `oof_start_offset` is used for that.
  static BlockBreakToken* CreateBreakBefore(
      LayoutInputNode node,
      bool is_forced_break,
      LogicalOffset oof_start_offset = LogicalOffset()) {
    auto* token = MakeGarbageCollected<BlockBreakToken>(PassKey(), node);
    token->is_break_before_ = true;
    token->is_forced_break_ = is_forced_break;
    token->oof_start_offset_ = oof_start_offset;
    token->has_unpositioned_list_marker_ = node.IsListItem();
    return token;
  }

  // Create a "repeat" break token. This is created at each fragment (that
  // didn't otherwise break) generated by repeated content, unless it's the very
  // last fragment. This is needed in order to get the sequence numbers right.
  static BlockBreakToken* CreateRepeated(const BlockNode&,
                                         unsigned sequence_number);

  // Create a break token for a "regular" break in a repeated fragment.
  //
  // This is needed when repeated content has another fragmentation context
  // inside, and there are actual breaks inside that fragmentation context.
  //
  // Note: Although the break token created here corresponds with one inside the
  // first fragment, this break token is "crippled" in many ways. There'll never
  // be any child break tokens, for instance. The only information that's
  // carried over from the original break token is what need, such as consumed
  // block-size, sequence number, and whether we are at/past the block-end.
  // Break tokens created by this function aren't meant to be used in
  // layout. They are just here to keep pre-paint and paint happy (which rely on
  // sequence numbers and consumed block-size). Any other use of this break
  // token is undefined (and likely to fail DCHECKs).
  static BlockBreakToken* CreateForBreakInRepeatedFragment(
      const BlockNode&,
      unsigned sequence_number,
      LayoutUnit consumed_block_size,
      bool is_at_block_end);

  // Represents the amount of block-size consumed by previous fragments.
  //
  // E.g. if the node specifies a block-size of 200px, and the previous
  // fragments generated for this box consumed 150px in total (which is what
  // this method would return then), there's 50px left to consume. The next
  // fragment will become 50px tall, assuming no additional fragmentation (if
  // the fragmentainer is shorter than 50px, for instance).
  LayoutUnit ConsumedBlockSize() const { return consumed_block_size_; }

  // Returns the block node associated with this break token.
  BlockNode InputNode() const { return BlockNode(box_.Get()); }

  // 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(is_repeated_actual_break_ || !IsBreakBefore());
    return sequence_number_;
  }

  // The amount of monolithic fragmentainer overflow.
  //
  // Fragmentainer overflow occurs when there is monolithic content, and when
  // printing, we record it here, in order to steer clear of it on subsequent
  // pages.
  //
  // This value is only used (and set) when printing.
  LayoutUnit MonolithicOverflow() const {
    DCHECK(!is_repeated_actual_break_);
    return monolithic_overflow_;
  }

  const BreakTokenAlgorithmData* TokenData() const {
    DCHECK(!is_repeated_actual_break_);
    return data_.Get();
  }

  // Return the start block-offset for the next / first fragment to be generated
  // for an OOF node. This is used for two purposes:
  //
  // 1. When there's a block-start inset too large for the node to produce a
  // fragment in the current fragmentainer. This value will then be the
  // remaining block-start inset to use in the next fragmentainer. This may
  // repeat across multiple fragmentainers before all of the inset has been
  // "eaten" and we're ready to produce a fragment.
  //
  // 2. Repeated fixed-positioned nodes (printing). Then the offset will be the
  // same at every break token for the node.
  LayoutUnit OofBlockStartOffset() const {
    DCHECK(InputNode().IsOutOfFlowPositioned());
    return oof_start_offset_.block_offset;
  }

  // Return the start inline-offset for the next fragment to be generated for an
  // OOF node. When resuming layout of an OOF after a break, there's no easy way
  // of recomputing the inline offset, since it may be based on a static
  // position, which in turn may be based on something in the middle of a line
  // box, for instance.
  LayoutUnit OofInlineStartOffset() const {
    DCHECK(InputNode().IsOutOfFlowPositioned());
    return oof_start_offset_.inline_offset;
  }

  // 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
  // one it was it was first encountered, due to block space shortage.
  bool IsBreakBefore() const { return is_break_before_; }

  bool IsForcedBreak() const { return is_forced_break_; }

  // Return true if the node didn't actually break, but is repeated in the next
  // fragmentainer in the fragmentation context in which the repeated content
  // root (table header / footer, or fixed-positioned element when printing)
  // lives.
  bool IsRepeated() const { return is_repeated_; }

  bool IsCausedByColumnSpanner() const {
    DCHECK(!is_repeated_actual_break_);
    return is_caused_by_column_spanner_;
  }

  // Return true if all children have been "seen". When we have reached this
  // point, and resume layout in a fragmentainer, we should only process child
  // break tokens, if any, and not attempt to start laying out nodes that don't
  // have one (since all children are either finished, or have a break token).
  bool HasSeenAllChildren() const {
    DCHECK(!is_repeated_actual_break_);
    return has_seen_all_children_;
  }

  // Return true if layout was past the block-end border edge of the node when
  // it fragmented. This typically means that something is overflowing the node,
  // and that establishes a parallel flow [1]. Subsequent content may be put
  // into the same fragmentainer as a fragment whose break token is in this
  // state, as long as it fits.
  //
  // [1] https://www.w3.org/TR/css-break-3/#parallel-flows
  //
  // <div style="columns:2; column-fill:auto; height:100px;">
  //   <div id="a" style="height:100px;">
  //     <div id="inner" style="height:200px;"></div>
  //   </div>
  //   <div id="b" style="margin-top:-30px; height:30px;"></div>
  // </div>
  //
  // #a and #b will be in the first column, while #inner will be in both the
  // first and second one. The important detail here is that we're at the end of
  // #a exactly at the bottom of the first column - even if #a broke inside
  // because of #child. This means that we have no space left as such, but we're
  // not ready to proceed to the next column. Anything that can fit at the
  // bottom of a column (either because it actually has 0 height, or e.g. a
  // negative top margin) will be put into that column, not the next.
  bool IsAtBlockEnd() const { return is_at_block_end_; }

  // True if earlier fragments could not position the list marker.
  bool HasUnpositionedListMarker() const {
    DCHECK(!is_repeated_actual_break_);
    return has_unpositioned_list_marker_;
  }

  // The break tokens for children of the layout node.
  //
  // Each child we have visited previously in the block-flow layout algorithm
  // has an associated break token. This may be either finished (we should skip
  // this child) or unfinished (we should try and produce the next fragment for
  // this child).
  //
  // A child which we haven't visited yet doesn't have a break token here.
  const base::span<const Member<const BreakToken>> ChildBreakTokens() const {
    DCHECK(!is_repeated_actual_break_);
    return ChildBreakTokensInternal();
  }

  // When merging out-of-flow children from a new placeholder fragmentainer into
  // an existing one, some new break token data may also have to be copied over.
  class MutableForOofFragmentation {
    STACK_ALLOCATED();

   public:
    explicit MutableForOofFragmentation(const BlockBreakToken& break_token)
        : break_token_(const_cast<BlockBreakToken&>(break_token)) {}

    // Merge the relevant parts (from the perspective of a fragmentainer that
    // has been updated with additional OOF children) of the specified break
    // token into this one.
    void Merge(const BlockBreakToken&);

    void SetInlineStartOffset(LayoutUnit offset) {
      break_token_.oof_start_offset_.inline_offset = offset;
    }
    void SetBlockStartOffset(LayoutUnit offset) {
      break_token_.oof_start_offset_.block_offset = offset;
    }

   private:
    BlockBreakToken& break_token_;
  };
  friend class MutableForOofFragmentation;
  MutableForOofFragmentation GetMutableForOofFragmentation() const {
    return MutableForOofFragmentation(*this);
  }

  String ToString(bool skip_node_info = false) const;

  using PassKey = base::PassKey<BlockBreakToken>;

  // Must only be called from Create(), because it assumes that enough space
  // has been allocated in the flexible array to store the children.
  BlockBreakToken(PassKey, BoxFragmentBuilder*);

  explicit BlockBreakToken(PassKey, LayoutInputNode node);

  void TraceAfterDispatch(Visitor*) const;

 private:
  const base::span<const Member<const BreakToken>> ChildBreakTokensInternal()
      const {
    // SAFETY: `const_num_children_` ensures buffer access never goes out of
    // range.
    return UNSAFE_BUFFERS(
        base::span(base::unchecked, child_break_tokens_, const_num_children_));
  }

  Member<LayoutBox> box_;
  Member<BreakTokenAlgorithmData> data_;

  LayoutUnit consumed_block_size_;
  LayoutUnit monolithic_overflow_;
  LogicalOffset oof_start_offset_;
  unsigned sequence_number_ = 0;

  const wtf_size_t const_num_children_;
  // This must be the last member, because it is a flexible array.
  Member<const BreakToken> child_break_tokens_[];
};

template <>
struct DowncastTraits<BlockBreakToken> {
  static bool AllowFrom(const BreakToken& token) { return token.IsBlockType(); }
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_BLOCK_BREAK_TOKEN_H_
