/*
 * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/core/editing/selection_editor.h"

#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node_with_index.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/editing_behavior.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/frame_caret.h"
#include "third_party/blink/renderer/core/editing/local_caret_rect.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/selection_adjuster.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/layout/layout_block.h"
#include "third_party/blink/renderer/core/layout/layout_invalidation_reason.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_object_inlines.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"

namespace blink {

SelectionEditor::SelectionEditor(LocalFrame& frame) : frame_(frame) {
  ClearVisibleSelection();
}

void SelectionEditor::AssertSelectionValid() const {
#if DCHECK_IS_ON()
  // Since We don't track dom tree version during attribute changes, we can't
  // use it for validity of |selection_|.
  const_cast<SelectionEditor*>(this)->selection_.dom_tree_version_ =
      GetDocument().DomTreeVersion();
#endif
  selection_.AssertValidFor(GetDocument());
}

void SelectionEditor::ClearVisibleSelection() {
  selection_ = SelectionInDomTree();
  cached_visible_selection_in_dom_tree_ = VisibleSelection();
  cached_visible_selection_in_flat_tree_ = VisibleSelectionInFlatTree();
  cached_visible_selection_in_dom_tree_is_dirty_ = true;
  cached_visible_selection_in_flat_tree_is_dirty_ = true;
}

void SelectionEditor::Dispose() {
  ClearDocumentCachedRange();
  ClearVisibleSelection();
}

Document& SelectionEditor::GetDocument() const {
  DCHECK(document_);
  return *document_;
}

VisibleSelection SelectionEditor::ComputeVisibleSelectionInDomTree() const {
  DCHECK_EQ(GetFrame()->GetDocument(), GetDocument());
  DCHECK_EQ(GetFrame(), GetDocument().GetFrame());
  UpdateCachedVisibleSelectionIfNeeded();
  if (cached_visible_selection_in_dom_tree_.IsNone())
    return cached_visible_selection_in_dom_tree_;
  DCHECK_EQ(cached_visible_selection_in_dom_tree_.Anchor().GetDocument(),
            GetDocument());
  return cached_visible_selection_in_dom_tree_;
}

VisibleSelectionInFlatTree SelectionEditor::ComputeVisibleSelectionInFlatTree()
    const {
  DCHECK_EQ(GetFrame()->GetDocument(), GetDocument());
  DCHECK_EQ(GetFrame(), GetDocument().GetFrame());
  UpdateCachedVisibleSelectionInFlatTreeIfNeeded();
  if (cached_visible_selection_in_flat_tree_.IsNone())
    return cached_visible_selection_in_flat_tree_;
  DCHECK_EQ(cached_visible_selection_in_flat_tree_.Anchor().GetDocument(),
            GetDocument());
  return cached_visible_selection_in_flat_tree_;
}

bool SelectionEditor::ComputeAbsoluteBounds(gfx::Rect& anchor,
                                            gfx::Rect& focus) const {
  DCHECK_EQ(GetFrame()->GetDocument(), GetDocument());
  DCHECK_EQ(GetFrame(), GetDocument().GetFrame());
  UpdateCachedAbsoluteBoundsIfNeeded();
  if (!has_selection_bounds_)
    return has_selection_bounds_;
  anchor = cached_anchor_bounds_;
  focus = cached_focus_bounds_;
  return has_selection_bounds_;
}

const SelectionInDomTree& SelectionEditor::GetSelectionInDomTree() const {
  AssertSelectionValid();
  return selection_;
}

void SelectionEditor::MarkCacheDirty() {
  if (!cached_visible_selection_in_dom_tree_is_dirty_) {
    cached_visible_selection_in_dom_tree_ = VisibleSelection();
    cached_visible_selection_in_dom_tree_is_dirty_ = true;
  }
  if (!cached_visible_selection_in_flat_tree_is_dirty_) {
    cached_visible_selection_in_flat_tree_ = VisibleSelectionInFlatTree();
    cached_visible_selection_in_flat_tree_is_dirty_ = true;
  }
  if (!cached_absolute_bounds_are_dirty_) {
    cached_absolute_bounds_are_dirty_ = true;
    has_selection_bounds_ = false;
    cached_anchor_bounds_ = gfx::Rect();
    cached_focus_bounds_ = gfx::Rect();
  }
}

void SelectionEditor::SetSelectionAndEndTyping(
    const SelectionInDomTree& new_selection) {
  new_selection.AssertValidFor(GetDocument());
  DCHECK_NE(selection_, new_selection);

  const SelectionInDomTree old_selection = selection_;

  ClearDocumentCachedRange();
  MarkCacheDirty();
  selection_ = new_selection;

  if (RuntimeEnabledFeatures::TextOverflowClipWithSelectionEnabled()) {
    Node* old_focus = old_selection.Focus().AnchorNode();
    Node* new_focus = new_selection.Focus().AnchorNode();
    LayoutObject* old_style_owner =
        old_focus && old_focus->GetLayoutObject()
            ? old_focus->GetLayoutObject()->ContainingBlockForTextOverflow()
            : nullptr;
    // Editable-only: non-editable content keeps its ellipsis
    // (crbug.com/526471153).
    LayoutObject* new_style_owner =
        new_focus && new_focus->GetLayoutObject() && IsEditable(*new_focus)
            ? new_focus->GetLayoutObject()->ContainingBlockForTextOverflow()
            : nullptr;

    if (old_style_owner != new_style_owner) {
      SetContainsSelectionFocusFlag(old_style_owner, false);
      SetContainsSelectionFocusFlag(new_style_owner, true);
    }
  }
}

void SelectionEditor::SetContainsSelectionFocusFlag(LayoutObject* style_owner,
                                                    bool value) {
  if (!style_owner || style_owner->StyleRef().TextOverflow().IsClip() ||
      style_owner->ContainsSelectionFocus() == value) {
    return;
  }

  style_owner->SetContainsSelectionFocus(value);

  // Children must be marked dirty so LayoutNG re-evaluates truncation (results
  // are cached per child). Defer via a task: this runs during
  // selection/input/focus handling, where dirtying layout synchronously would
  // trip hit-test DCHECKs (crbug.com/406853131).
  Node* style_node = style_owner->GetNode();
  if (!style_node) {
    return;
  }
  style_node->GetDocument()
      .GetTaskRunner(TaskType::kInternalDefault)
      ->PostTask(FROM_HERE,
                 BindOnce(&SelectionEditor::InvalidateTextOverflowLayoutForNode,
                          WrapWeakPersistent(style_node)));
}

void SelectionEditor::InvalidateTextOverflowLayoutForNode(
    Node* style_owner_node) {
  if (!style_owner_node) {
    return;
  }
  LayoutObject* style_owner = style_owner_node->GetLayoutObject();
  if (!style_owner) {
    return;
  }
  style_owner->SetNeedsLayout(layout_invalidation_reason::kStyleChange);
  for (LayoutObject* child = style_owner->SlowFirstChild(); child;
       child = child->NextSibling()) {
    if (child->IsLayoutBlock()) {
      child->SetNeedsLayout(layout_invalidation_reason::kStyleChange);
    }
  }
}

void SelectionEditor::DidChangeChildren(
    const ContainerNode::ChildrenChange& change) {
  if (RuntimeEnabledFeatures::UpdateSelectionOnNodeInsertionEnabled() &&
      (change.type == ContainerNode::ChildrenChangeType::kElementInserted ||
       change.type == ContainerNode::ChildrenChangeType::kNonElementInserted)) {
    DidInsertNode(*change.sibling_changed);
  }
  selection_.ResetDirectionCache();
  MarkCacheDirty();
  DidFinishDomMutation();
}

void SelectionEditor::DidFinishTextChange(const Position& new_anchor,
                                          const Position& new_focus) {
  if (new_anchor == selection_.anchor_ && new_focus == selection_.focus_) {
    DidFinishDomMutation();
    return;
  }
  selection_.anchor_ = new_anchor;
  selection_.focus_ = new_focus;
  selection_.ResetDirectionCache();

  // See: https://w3c.github.io/selection-api/#selectionchange-event
  TextControlElement* text_control =
      EnclosingTextControl(GetSelectionInDomTree().Anchor());
  if (text_control && !text_control->IsInShadowTree()) {
    text_control->ScheduleSelectionchangeEvent();
  } else {
    GetDocument().ScheduleSelectionchangeEvent();
  }

  MarkCacheDirty();
  DidFinishDomMutation();
}

void SelectionEditor::DidFinishDomMutation() {
  AssertSelectionValid();
}

static Position ComputePositionForNodeInsertion(const Position& position,
                                                const Node& node) {
  if (position.IsNull()) {
    return position;
  }

  if (position.IsOffsetInAnchor()) {
    Node* container_node = position.ComputeContainerNode();
    // Increase the offset value when new node is inserted before the current
    // position.
    if (container_node == node.parentNode() &&
        position.OffsetInContainerNode() > node.NodeIndex()) {
      return Position(container_node, position.OffsetInContainerNode() + 1);
    }
  }
  return position;
}

void SelectionEditor::DidInsertNode(const Node& node) {
  if (selection_.IsNone()) {
    return;
  }
  const Position old_anchor = selection_.anchor_;
  const Position old_focus = selection_.focus_;
  const Position& new_anchor =
      ComputePositionForNodeInsertion(old_anchor, node);
  const Position& new_focus = ComputePositionForNodeInsertion(old_focus, node);
  if (new_anchor == old_anchor && new_focus == old_focus) {
    return;
  }
  selection_ = SelectionInDomTree::Builder()
                   .SetBaseAndExtent(new_anchor, new_focus)
                   .Build();
}

void SelectionEditor::DidAttachDocument(Document* document) {
  DCHECK(document);
  DCHECK(!document_);
#if DCHECK_IS_ON()
  style_version_for_dom_tree_ = static_cast<uint64_t>(-1);
  style_version_for_flat_tree_ = static_cast<uint64_t>(-1);
#endif
  ClearVisibleSelection();
  document_ = document;
}

void SelectionEditor::ContextDestroyed() {
  Dispose();
#if DCHECK_IS_ON()
  style_version_for_dom_tree_ = static_cast<uint64_t>(-1);
  style_version_for_flat_tree_ = static_cast<uint64_t>(-1);
  style_version_for_absolute_bounds_ = static_cast<uint64_t>(-1);
#endif
  selection_ = SelectionInDomTree();
  cached_visible_selection_in_dom_tree_ = VisibleSelection();
  cached_visible_selection_in_flat_tree_ = VisibleSelectionInFlatTree();
  cached_visible_selection_in_dom_tree_is_dirty_ = true;
  cached_visible_selection_in_flat_tree_is_dirty_ = true;
  cached_absolute_bounds_are_dirty_ = true;
  has_selection_bounds_ = false;
  cached_anchor_bounds_ = gfx::Rect();
  cached_focus_bounds_ = gfx::Rect();
  document_ = nullptr;
}

static Position ComputePositionForChildrenRemoval(const Position& position,
                                                  ContainerNode& container) {
  Node* node = position.ComputeContainerNode();
#if DCHECK_IS_ON()
  DCHECK(node) << position;
#else
  // TODO(https://crbug.com/882592): Once we know the root cause, we should
  // get rid of following if-statement.
  if (!node)
    return position;
#endif
  if (!container.ContainsIncludingHostElements(*node))
    return position;
  if (auto* element = DynamicTo<Element>(container)) {
    if (auto* shadow_root = element->GetShadowRoot()) {
      // Removal of light children does not affect position in the
      // shadow tree.
      if (shadow_root->ContainsIncludingHostElements(*node))
        return position;
    }
  }
  return Position::FirstPositionInNode(container);
}

void SelectionEditor::NodeChildrenWillBeRemoved(ContainerNode& container) {
  if (selection_.IsNone())
    return;
  const Position old_anchor = selection_.anchor_;
  const Position old_focus = selection_.focus_;
  const Position& new_anchor =
      ComputePositionForChildrenRemoval(old_anchor, container);
  const Position& new_focus =
      ComputePositionForChildrenRemoval(old_focus, container);
  if (new_anchor == old_anchor && new_focus == old_focus) {
    return;
  }
  selection_ = SelectionInDomTree::Builder()
                   .SetBaseAndExtent(new_anchor, new_focus)
                   .Build();
  MarkCacheDirty();
}

void SelectionEditor::NodeWillBeRemoved(Node& node_to_be_removed) {
  if (selection_.IsNone())
    return;

  const Position old_anchor = selection_.anchor_;
  const Position old_focus = selection_.focus_;
  Position new_anchor = old_anchor;
  Position new_focus = old_focus;

  // In the case where an atomic move is in progress, `node_to_be_removed` is
  // not actually being removed from the DOM entirely, so we don't want to snap
  // either end (anchor or focus) of the selection range to the next logical
  // node (i.e., `ComputePositionForNodeRemoval()`). Instead we just need to run
  // the various steps that would ordinarily attend a true selection change, so
  // that in the case where selection changes direction, selection state is
  // updated properly.
  new_anchor = ComputePositionForNodeRemoval(old_anchor, node_to_be_removed);
  new_focus = ComputePositionForNodeRemoval(old_focus, node_to_be_removed);
  if (new_anchor == old_anchor && new_focus == old_focus) {
    return;
  }

  selection_ = SelectionInDomTree::Builder()
                   .SetBaseAndExtent(new_anchor, new_focus)
                   .Build();
  MarkCacheDirty();
}

static Position UpdatePositionAfterAdoptingTextReplacement(
    const Position& position,
    CharacterData* node,
    wtf_size_t offset,
    wtf_size_t old_length,
    wtf_size_t new_length) {
  if (position.AnchorNode() != node)
    return position;

  if (position.IsBeforeAnchor()) {
    return UpdatePositionAfterAdoptingTextReplacement(
        Position(node, 0), node, offset, old_length, new_length);
  }
  if (position.IsAfterAnchor()) {
    return UpdatePositionAfterAdoptingTextReplacement(
        Position(node, old_length), node, offset, old_length, new_length);
  }

  // See:
  // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation
  wtf_size_t position_offset = position.OffsetInContainerNode();
  // Replacing text can be viewed as a deletion followed by insertion.
  if (position_offset >= offset && position_offset <= offset + old_length)
    position_offset = offset;

  // Adjust the offset if the position is after the end of the deleted contents
  // (positionOffset > offset + oldLength) to avoid having a stale offset.
  if (position_offset > offset + old_length)
    position_offset = position_offset - old_length + new_length;

  // Due to case folding
  // (http://unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), LayoutText
  // length may be different from Text length.  A correct implementation would
  // translate the LayoutText offset to a Text offset; this is just a safety
  // precaution to avoid offset values that run off the end of the Text.
  if (position_offset > node->length())
    position_offset = node->length();

  return Position(node, position_offset);
}

void SelectionEditor::DidUpdateCharacterData(CharacterData* node,
                                             wtf_size_t offset,
                                             wtf_size_t old_length,
                                             wtf_size_t new_length) {
  // The fragment check is a performance optimization. See
  // http://trac.webkit.org/changeset/30062.
  if (selection_.IsNone() || !node || !node->isConnected()) {
    DidFinishDomMutation();
    return;
  }
  const Position& new_anchor = UpdatePositionAfterAdoptingTextReplacement(
      selection_.anchor_, node, offset, old_length, new_length);
  const Position& new_focus = UpdatePositionAfterAdoptingTextReplacement(
      selection_.focus_, node, offset, old_length, new_length);
  DidFinishTextChange(new_anchor, new_focus);
}

static Position UpdatePostionAfterAdoptingTextNodesMerged(
    const Position& position,
    const Text& merged_node,
    const NodeWithIndex& node_to_be_removed_with_index,
    wtf_size_t old_length) {
  Node* const anchor_node = position.AnchorNode();
  const Node& node_to_be_removed = node_to_be_removed_with_index.GetNode();
  switch (position.AnchorType()) {
    case PositionAnchorType::kAfterChildren:
      return position;
    case PositionAnchorType::kBeforeAnchor:
      if (anchor_node == node_to_be_removed)
        return Position(merged_node, merged_node.length());
      return position;
    case PositionAnchorType::kAfterAnchor:
      if (anchor_node == node_to_be_removed)
        return Position(merged_node, merged_node.length());
      if (anchor_node == merged_node)
        return Position(merged_node, old_length);
      return position;
    case PositionAnchorType::kOffsetInAnchor: {
      const int offset = position.OffsetInContainerNode();
      if (anchor_node == node_to_be_removed)
        return Position(merged_node, old_length + offset);
      if (anchor_node == node_to_be_removed.parentNode() &&
          offset == node_to_be_removed_with_index.Index()) {
        return Position(merged_node, old_length);
      }
      return position;
    }
  }
  NOTREACHED() << position;
}

void SelectionEditor::DidMergeTextNodes(
    const Text& merged_node,
    const NodeWithIndex& node_to_be_removed_with_index,
    wtf_size_t old_length) {
  if (selection_.IsNone()) {
    DidFinishDomMutation();
    return;
  }
  const Position& new_anchor = UpdatePostionAfterAdoptingTextNodesMerged(
      selection_.anchor_, merged_node, node_to_be_removed_with_index,
      old_length);
  const Position& new_focus = UpdatePostionAfterAdoptingTextNodesMerged(
      selection_.focus_, merged_node, node_to_be_removed_with_index,
      old_length);
  DidFinishTextChange(new_anchor, new_focus);
}

static Position UpdatePostionAfterAdoptingTextNodeSplit(
    const Position& position,
    const Text& old_node) {
  if (!position.AnchorNode() || position.AnchorNode() != &old_node ||
      !position.IsOffsetInAnchor())
    return position;
  // See:
  // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation
  wtf_size_t position_offset = position.OffsetInContainerNode();
  wtf_size_t old_length = old_node.length();
  if (position_offset <= old_length)
    return position;
  return Position(To<Text>(old_node.nextSibling()),
                  position_offset - old_length);
}

void SelectionEditor::DidSplitTextNode(const Text& old_node) {
  if (selection_.IsNone() || !old_node.isConnected()) {
    DidFinishDomMutation();
    return;
  }
  const Position& new_anchor =
      UpdatePostionAfterAdoptingTextNodeSplit(selection_.anchor_, old_node);
  const Position& new_focus =
      UpdatePostionAfterAdoptingTextNodeSplit(selection_.focus_, old_node);
  DidFinishTextChange(new_anchor, new_focus);
}

bool SelectionEditor::ShouldAlwaysUseDirectionalSelection() const {
  return GetFrame()
      ->GetEditor()
      .Behavior()
      .ShouldConsiderSelectionAsDirectional();
}

bool SelectionEditor::NeedsUpdateVisibleSelection() const {
#if DCHECK_IS_ON()
  // Verify that cache has been marked dirty on style changes
  DCHECK(cached_visible_selection_in_dom_tree_is_dirty_ ||
         style_version_for_dom_tree_ == GetDocument().StyleVersion());
#endif
  return cached_visible_selection_in_dom_tree_is_dirty_;
}

void SelectionEditor::UpdateCachedVisibleSelectionIfNeeded() const {
  // Note: Since we |FrameCaret::updateApperance()| is called from
  // |FrameView::performPostLayoutTasks()|, we check lifecycle against
  // |AfterPerformLayout| instead of |LayoutClean|.
  DCHECK_GE(GetDocument().Lifecycle().GetState(),
            DocumentLifecycle::kAfterPerformLayout);
  AssertSelectionValid();
  if (!NeedsUpdateVisibleSelection())
    return;
#if DCHECK_IS_ON()
  style_version_for_dom_tree_ = GetDocument().StyleVersion();
#endif
  cached_visible_selection_in_dom_tree_is_dirty_ = false;
  cached_visible_selection_in_dom_tree_ = CreateVisibleSelection(selection_);
  if (!cached_visible_selection_in_dom_tree_.IsNone())
    return;
#if DCHECK_IS_ON()
  style_version_for_flat_tree_ = GetDocument().StyleVersion();
#endif
  cached_visible_selection_in_flat_tree_is_dirty_ = false;
  cached_visible_selection_in_flat_tree_ = VisibleSelectionInFlatTree();
}

bool SelectionEditor::NeedsUpdateVisibleSelectionInFlatTree() const {
#if DCHECK_IS_ON()
  // Verify that cache has been marked dirty on style changes
  DCHECK(cached_visible_selection_in_flat_tree_is_dirty_ ||
         style_version_for_flat_tree_ == GetDocument().StyleVersion());
#endif
  return cached_visible_selection_in_flat_tree_is_dirty_;
}

void SelectionEditor::UpdateCachedVisibleSelectionInFlatTreeIfNeeded() const {
  // Note: Since we |FrameCaret::updateApperance()| is called from
  // |FrameView::performPostLayoutTasks()|, we check lifecycle against
  // |AfterPerformLayout| instead of |LayoutClean|.
  DCHECK_GE(GetDocument().Lifecycle().GetState(),
            DocumentLifecycle::kAfterPerformLayout);
  AssertSelectionValid();
  if (!NeedsUpdateVisibleSelectionInFlatTree())
    return;
#if DCHECK_IS_ON()
  style_version_for_flat_tree_ = GetDocument().StyleVersion();
#endif
  cached_visible_selection_in_flat_tree_is_dirty_ = false;
  cached_visible_selection_in_flat_tree_ =
      CreateVisibleSelection(ConvertToSelectionInFlatTree(selection_));
  if (!cached_visible_selection_in_flat_tree_.IsNone())
    return;
#if DCHECK_IS_ON()
  style_version_for_dom_tree_ = GetDocument().StyleVersion();
#endif
  cached_visible_selection_in_dom_tree_is_dirty_ = false;
  cached_visible_selection_in_dom_tree_ = VisibleSelection();
}

bool SelectionEditor::NeedsUpdateAbsoluteBounds() const {
#if DCHECK_IS_ON()
  // Verify that cache has been marked dirty on style changes
  DCHECK(cached_absolute_bounds_are_dirty_ ||
         style_version_for_absolute_bounds_ == GetDocument().StyleVersion());
#endif
  return cached_absolute_bounds_are_dirty_;
}

void SelectionEditor::UpdateCachedAbsoluteBoundsIfNeeded() const {
  // Note: Since we |FrameCaret::updateApperance()| is called from
  // |FrameView::performPostLayoutTasks()|, we check lifecycle against
  // |AfterPerformLayout| instead of |LayoutClean|.
  DCHECK_GE(GetDocument().Lifecycle().GetState(),
            DocumentLifecycle::kAfterPerformLayout);
  AssertSelectionValid();
  if (!NeedsUpdateAbsoluteBounds())
    return;

  DocumentLifecycle::DisallowTransitionScope disallow_transition(
      frame_->GetDocument()->Lifecycle());

#if DCHECK_IS_ON()
  style_version_for_absolute_bounds_ = GetDocument().StyleVersion();
#endif
  cached_absolute_bounds_are_dirty_ = false;

  const VisibleSelection selection = ComputeVisibleSelectionInDomTree();

  if (selection.IsCaret()) {
    DCHECK(selection.IsValidFor(*frame_->GetDocument()));
    const PositionWithAffinity caret(selection.Start(), selection.Affinity());
    cached_anchor_bounds_ = cached_focus_bounds_ = AbsoluteCaretBoundsOf(caret);
  } else {
    const EphemeralRange selected_range =
        selection.ToNormalizedEphemeralRange();
    if (selected_range.IsNull()) {
      has_selection_bounds_ = false;
      return;
    }
    cached_anchor_bounds_ =
        FirstRectForRange(EphemeralRange(selected_range.StartPosition()));
    cached_focus_bounds_ =
        FirstRectForRange(EphemeralRange(selected_range.EndPosition()));
  }

  if (!selection.IsAnchorFirst()) {
    std::swap(cached_anchor_bounds_, cached_focus_bounds_);
  }

  has_selection_bounds_ = true;
}

void SelectionEditor::CacheRangeOfDocument(Range* range) {
  cached_range_ = range;
}

Range* SelectionEditor::DocumentCachedRange() const {
  return cached_range_.Get();
}

void SelectionEditor::ClearDocumentCachedRange() {
  cached_range_ = nullptr;
}

void SelectionEditor::Trace(Visitor* visitor) const {
  visitor->Trace(frame_);
  visitor->Trace(document_);
  visitor->Trace(selection_);
  visitor->Trace(cached_visible_selection_in_dom_tree_);
  visitor->Trace(cached_visible_selection_in_flat_tree_);
  visitor->Trace(cached_range_);
}

}  // namespace blink
