summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/editing/layout_selection.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/editing/layout_selection.cc')
-rw-r--r--chromium/third_party/blink/renderer/core/editing/layout_selection.cc891
1 files changed, 891 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/core/editing/layout_selection.cc b/chromium/third_party/blink/renderer/core/editing/layout_selection.cc
new file mode 100644
index 00000000000..56c1deb2b89
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/editing/layout_selection.cc
@@ -0,0 +1,891 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights
+ * reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "third_party/blink/renderer/core/editing/layout_selection.h"
+
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/editing/editing_utilities.h"
+#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/selection_template.h"
+#include "third_party/blink/renderer/core/editing/visible_position.h"
+#include "third_party/blink/renderer/core/editing/visible_units.h"
+#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
+#include "third_party/blink/renderer/core/layout/layout_text.h"
+#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
+#include "third_party/blink/renderer/core/layout/layout_view.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
+#include "third_party/blink/renderer/core/paint/paint_layer.h"
+
+namespace blink {
+
+SelectionPaintRange::SelectionPaintRange(LayoutObject* start_layout_object,
+ WTF::Optional<unsigned> start_offset,
+ LayoutObject* end_layout_object,
+ WTF::Optional<unsigned> end_offset)
+ : start_layout_object_(start_layout_object),
+ start_offset_(start_offset),
+ end_layout_object_(end_layout_object),
+ end_offset_(end_offset) {}
+
+bool SelectionPaintRange::operator==(const SelectionPaintRange& other) const {
+ return start_layout_object_ == other.start_layout_object_ &&
+ start_offset_ == other.start_offset_ &&
+ end_layout_object_ == other.end_layout_object_ &&
+ end_offset_ == other.end_offset_;
+}
+
+LayoutObject* SelectionPaintRange::StartLayoutObject() const {
+ DCHECK(!IsNull());
+ return start_layout_object_;
+}
+
+WTF::Optional<unsigned> SelectionPaintRange::StartOffset() const {
+ DCHECK(!IsNull());
+ return start_offset_;
+}
+
+LayoutObject* SelectionPaintRange::EndLayoutObject() const {
+ DCHECK(!IsNull());
+ return end_layout_object_;
+}
+
+WTF::Optional<unsigned> SelectionPaintRange::EndOffset() const {
+ DCHECK(!IsNull());
+ return end_offset_;
+}
+
+SelectionPaintRange::Iterator::Iterator(const SelectionPaintRange* range) {
+ if (!range || range->IsNull()) {
+ current_ = nullptr;
+ return;
+ }
+ current_ = range->StartLayoutObject();
+ stop_ = range->EndLayoutObject()->NextInPreOrder();
+}
+
+LayoutObject* SelectionPaintRange::Iterator::operator*() const {
+ DCHECK(current_);
+ return current_;
+}
+
+SelectionPaintRange::Iterator& SelectionPaintRange::Iterator::operator++() {
+ DCHECK(current_);
+ current_ = current_->NextInPreOrder();
+ if (current_ && current_ != stop_)
+ return *this;
+
+ current_ = nullptr;
+ return *this;
+}
+
+LayoutSelection::LayoutSelection(FrameSelection& frame_selection)
+ : frame_selection_(&frame_selection),
+ has_pending_selection_(false),
+ paint_range_(SelectionPaintRange()) {}
+
+enum class SelectionMode {
+ kNone,
+ kRange,
+ kBlockCursor,
+};
+
+static SelectionMode ComputeSelectionMode(
+ const FrameSelection& frame_selection) {
+ const SelectionInDOMTree& selection_in_dom =
+ frame_selection.GetSelectionInDOMTree();
+ if (selection_in_dom.IsRange())
+ return SelectionMode::kRange;
+ DCHECK(selection_in_dom.IsCaret());
+ if (!frame_selection.ShouldShowBlockCursor())
+ return SelectionMode::kNone;
+ if (IsLogicalEndOfLine(CreateVisiblePosition(selection_in_dom.Base())))
+ return SelectionMode::kNone;
+ return SelectionMode::kBlockCursor;
+}
+
+static EphemeralRangeInFlatTree CalcSelectionInFlatTree(
+ const FrameSelection& frame_selection) {
+ const SelectionInDOMTree& selection_in_dom =
+ frame_selection.GetSelectionInDOMTree();
+ switch (ComputeSelectionMode(frame_selection)) {
+ case SelectionMode::kNone:
+ return {};
+ case SelectionMode::kRange: {
+ const PositionInFlatTree& base =
+ ToPositionInFlatTree(selection_in_dom.Base());
+ const PositionInFlatTree& extent =
+ ToPositionInFlatTree(selection_in_dom.Extent());
+ if (base.IsNull() || extent.IsNull() || base == extent ||
+ !base.IsValidFor(frame_selection.GetDocument()) ||
+ !extent.IsValidFor(frame_selection.GetDocument()))
+ return {};
+ return base <= extent ? EphemeralRangeInFlatTree(base, extent)
+ : EphemeralRangeInFlatTree(extent, base);
+ }
+ case SelectionMode::kBlockCursor: {
+ const PositionInFlatTree& base =
+ CreateVisiblePosition(ToPositionInFlatTree(selection_in_dom.Base()))
+ .DeepEquivalent();
+ if (base.IsNull())
+ return {};
+ const PositionInFlatTree end_position =
+ NextPositionOf(base, PositionMoveType::kGraphemeCluster);
+ if (end_position.IsNull())
+ return {};
+ return base <= end_position
+ ? EphemeralRangeInFlatTree(base, end_position)
+ : EphemeralRangeInFlatTree(end_position, base);
+ }
+ }
+ NOTREACHED();
+ return {};
+}
+
+// LayoutObjects each has SelectionState of kStart, kEnd, kStartAndEnd, or
+// kInside.
+using SelectedLayoutObjects = HashSet<LayoutObject*>;
+// OldSelectedLayoutObjects is current selected LayoutObjects with
+// current SelectionState which is kStart, kEnd, kStartAndEnd or kInside.
+using OldSelectedLayoutObjects = HashMap<LayoutObject*, SelectionState>;
+
+#ifndef NDEBUG
+void PrintSelectedLayoutObjects(
+ const SelectedLayoutObjects& new_selected_objects) {
+ std::stringstream stream;
+ stream << std::endl;
+ for (LayoutObject* layout_object : new_selected_objects) {
+ PrintLayoutObjectForSelection(stream, layout_object);
+ stream << std::endl;
+ }
+ LOG(INFO) << stream.str();
+}
+
+void PrintOldSelectedLayoutObjects(
+ const OldSelectedLayoutObjects& old_selected_objects) {
+ std::stringstream stream;
+ stream << std::endl;
+ for (const auto& key_pair : old_selected_objects) {
+ LayoutObject* layout_object = key_pair.key;
+ SelectionState old_state = key_pair.value;
+ PrintLayoutObjectForSelection(stream, layout_object);
+ stream << " old: " << old_state << std::endl;
+ }
+ LOG(INFO) << stream.str();
+}
+
+void PrintSelectionPaintRange(const SelectionPaintRange& paint_range) {
+ std::stringstream stream;
+ stream << std::endl << "layout_objects:" << std::endl;
+ for (LayoutObject* layout_object : paint_range) {
+ PrintLayoutObjectForSelection(stream, layout_object);
+ stream << std::endl;
+ }
+ LOG(INFO) << stream.str();
+}
+
+void PrintSelectionStateInLayoutView(const FrameSelection& selection) {
+ std::stringstream stream;
+ stream << std::endl << "layout_objects:" << std::endl;
+ LayoutView* layout_view = selection.GetDocument().GetLayoutView();
+ for (LayoutObject* layout_object = layout_view; layout_object;
+ layout_object = layout_object->NextInPreOrder()) {
+ PrintLayoutObjectForSelection(stream, layout_object);
+ stream << std::endl;
+ }
+ LOG(INFO) << stream.str();
+}
+#endif
+
+// This class represents a selection range in layout tree and each LayoutObject
+// is SelectionState-marked.
+class NewPaintRangeAndSelectedLayoutObjects {
+ STACK_ALLOCATED();
+
+ public:
+ NewPaintRangeAndSelectedLayoutObjects() = default;
+ NewPaintRangeAndSelectedLayoutObjects(SelectionPaintRange paint_range,
+ SelectedLayoutObjects selected_objects)
+ : paint_range_(paint_range),
+ selected_objects_(std::move(selected_objects)) {}
+ NewPaintRangeAndSelectedLayoutObjects(
+ NewPaintRangeAndSelectedLayoutObjects&& other) {
+ paint_range_ = other.paint_range_;
+ selected_objects_ = std::move(other.selected_objects_);
+ }
+
+ SelectionPaintRange PaintRange() const { return paint_range_; }
+
+ const SelectedLayoutObjects& LayoutObjects() const {
+ return selected_objects_;
+ }
+
+ private:
+ SelectionPaintRange paint_range_;
+ SelectedLayoutObjects selected_objects_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NewPaintRangeAndSelectedLayoutObjects);
+};
+
+static void SetShouldInvalidateIfNeeded(LayoutObject* layout_object) {
+ if (layout_object->ShouldInvalidateSelection())
+ return;
+ layout_object->SetShouldInvalidateSelection();
+
+ // We should invalidate if ancestor of |layout_object| is LayoutSVGText
+ // because SVGRootInlineBoxPainter::Paint() paints selection for
+ // |layout_object| in/ LayoutSVGText and it is invoked when parent
+ // LayoutSVGText is invalidated.
+ // That is different from InlineTextBoxPainter::Paint() which paints
+ // LayoutText selection when LayoutText is invalidated.
+ if (!layout_object->IsSVG())
+ return;
+ for (LayoutObject* parent = layout_object->Parent(); parent;
+ parent = parent->Parent()) {
+ if (parent->IsSVGRoot())
+ return;
+ if (parent->IsSVGText()) {
+ if (!parent->ShouldInvalidateSelection())
+ parent->SetShouldInvalidateSelection();
+ return;
+ }
+ }
+}
+
+static void SetSelectionStateIfNeeded(LayoutObject* layout_object,
+ SelectionState state) {
+ DCHECK_NE(state, SelectionState::kContain) << layout_object;
+ DCHECK_NE(state, SelectionState::kNone) << layout_object;
+ if (layout_object->GetSelectionState() == state)
+ return;
+ layout_object->SetSelectionState(state);
+
+ // Set containing block SelectionState kContain for CSS ::selection style.
+ // See LayoutObject::InvalidatePaintForSelection().
+ for (LayoutObject* containing_block = layout_object->ContainingBlock();
+ containing_block;
+ containing_block = containing_block->ContainingBlock()) {
+ if (containing_block->GetSelectionState() == SelectionState::kContain)
+ return;
+ containing_block->LayoutObject::SetSelectionState(SelectionState::kContain);
+ }
+}
+
+// Set ShouldInvalidateSelection flag of LayoutObjects
+// comparing them in |new_range| and |old_range|.
+static void SetShouldInvalidateSelection(
+ const NewPaintRangeAndSelectedLayoutObjects& new_range,
+ const SelectionPaintRange& old_range,
+ const OldSelectedLayoutObjects& old_selected_objects) {
+ // We invalidate each LayoutObject in new SelectionPaintRange which
+ // has SelectionState of kStart, kEnd, kStartAndEnd, or kInside
+ // and is not in old SelectionPaintRange.
+ for (LayoutObject* layout_object : new_range.LayoutObjects()) {
+ if (old_selected_objects.Contains(layout_object))
+ continue;
+ const SelectionState new_state = layout_object->GetSelectionState();
+ DCHECK_NE(new_state, SelectionState::kContain) << layout_object;
+ DCHECK_NE(new_state, SelectionState::kNone) << layout_object;
+ SetShouldInvalidateIfNeeded(layout_object);
+ }
+ // For LayoutObject in old SelectionPaintRange, we invalidate LayoutObjects
+ // each of:
+ // 1. LayoutObject was painted and would not be painted.
+ // 2. LayoutObject was not painted and would be painted.
+ for (const auto& key_value : old_selected_objects) {
+ LayoutObject* const layout_object = key_value.key;
+ const SelectionState old_state = key_value.value;
+ const SelectionState new_state = layout_object->GetSelectionState();
+ if (new_state == old_state)
+ continue;
+ DCHECK(new_state != SelectionState::kNone ||
+ old_state != SelectionState::kNone)
+ << layout_object;
+ DCHECK_NE(new_state, SelectionState::kContain) << layout_object;
+ DCHECK_NE(old_state, SelectionState::kContain) << layout_object;
+ SetShouldInvalidateIfNeeded(layout_object);
+ }
+
+ // Invalidate Selection start/end is moving on a same node.
+ const SelectionPaintRange& new_paint_range = new_range.PaintRange();
+ if (new_paint_range.IsNull() || old_range.IsNull())
+ return;
+ if (new_paint_range.StartLayoutObject()->IsText() &&
+ new_paint_range.StartLayoutObject() == old_range.StartLayoutObject() &&
+ new_paint_range.StartOffset() != old_range.StartOffset())
+ SetShouldInvalidateIfNeeded(new_paint_range.StartLayoutObject());
+ if (new_paint_range.EndLayoutObject()->IsText() &&
+ new_paint_range.EndLayoutObject() == old_range.EndLayoutObject() &&
+ new_paint_range.EndOffset() != old_range.EndOffset())
+ SetShouldInvalidateIfNeeded(new_paint_range.EndLayoutObject());
+}
+
+WTF::Optional<unsigned> LayoutSelection::SelectionStart() const {
+ DCHECK(!HasPendingSelection());
+ if (paint_range_.IsNull())
+ return WTF::nullopt;
+ return paint_range_.StartOffset();
+}
+
+WTF::Optional<unsigned> LayoutSelection::SelectionEnd() const {
+ DCHECK(!HasPendingSelection());
+ if (paint_range_.IsNull())
+ return WTF::nullopt;
+ return paint_range_.EndOffset();
+}
+
+static OldSelectedLayoutObjects ResetOldSelectedLayoutObjects(
+ const SelectionPaintRange& old_range) {
+ OldSelectedLayoutObjects old_selected_objects;
+ HashSet<LayoutObject*> containing_block_set;
+ for (LayoutObject* layout_object : old_range) {
+ const SelectionState old_state = layout_object->GetSelectionState();
+ if (old_state == SelectionState::kNone)
+ continue;
+ if (old_state != SelectionState::kContain)
+ old_selected_objects.insert(layout_object, old_state);
+ layout_object->SetSelectionState(SelectionState::kNone);
+
+ // Reset containing block SelectionState for CSS ::selection style.
+ // See LayoutObject::InvalidatePaintForSelection().
+ for (LayoutObject* containing_block = layout_object->ContainingBlock();
+ containing_block;
+ containing_block = containing_block->ContainingBlock()) {
+ if (containing_block_set.Contains(containing_block))
+ break;
+ containing_block->SetSelectionState(SelectionState::kNone);
+ containing_block_set.insert(containing_block);
+ }
+ }
+ return old_selected_objects;
+}
+
+void LayoutSelection::ClearSelection() {
+ // For querying Layer::compositingState()
+ // This is correct, since destroying layout objects needs to cause eager paint
+ // invalidations.
+ DisableCompositingQueryAsserts disabler;
+
+ // Just return if the selection is already empty.
+ if (paint_range_.IsNull())
+ return;
+
+ const OldSelectedLayoutObjects& old_selected_objects =
+ ResetOldSelectedLayoutObjects(paint_range_);
+ for (LayoutObject* const layout_object : old_selected_objects.Keys())
+ SetShouldInvalidateIfNeeded(layout_object);
+
+ // Reset selection.
+ paint_range_ = SelectionPaintRange();
+}
+
+static WTF::Optional<unsigned> ComputeStartOffset(
+ const LayoutObject& layout_object,
+ const PositionInFlatTree& position) {
+ Node* const layout_node = layout_object.GetNode();
+ if (!layout_node || !layout_node->IsTextNode())
+ return WTF::nullopt;
+
+ if (layout_node == position.AnchorNode())
+ return position.OffsetInContainerNode();
+ return 0;
+}
+
+static WTF::Optional<unsigned> ComputeEndOffset(
+ const LayoutObject& layout_object,
+ const PositionInFlatTree& position) {
+ Node* const layout_node = layout_object.GetNode();
+ if (!layout_node || !layout_node->IsTextNode())
+ return WTF::nullopt;
+
+ if (layout_node == position.AnchorNode())
+ return position.OffsetInContainerNode();
+ return ToText(layout_node)->length();
+}
+
+static LayoutTextFragment* FirstLetterPartFor(LayoutObject* layout_object) {
+ if (!layout_object->IsText())
+ return nullptr;
+ if (!ToLayoutText(layout_object)->IsTextFragment())
+ return nullptr;
+ return ToLayoutTextFragment(const_cast<LayoutObject*>(
+ AssociatedLayoutObjectOf(*layout_object->GetNode(), 0)));
+}
+
+static void MarkSelected(SelectedLayoutObjects* selected_objects,
+ LayoutObject* layout_object,
+ SelectionState state) {
+ DCHECK(layout_object->CanBeSelectionLeaf());
+ SetSelectionStateIfNeeded(layout_object, state);
+ selected_objects->insert(layout_object);
+}
+
+static void MarkSelectedInside(SelectedLayoutObjects* selected_objects,
+ LayoutObject* layout_object) {
+ MarkSelected(selected_objects, layout_object, SelectionState::kInside);
+ LayoutTextFragment* const first_letter_part =
+ FirstLetterPartFor(layout_object);
+ if (!first_letter_part)
+ return;
+ MarkSelected(selected_objects, first_letter_part, SelectionState::kInside);
+}
+
+static NewPaintRangeAndSelectedLayoutObjects MarkStartAndEndInOneNode(
+ SelectedLayoutObjects selected_objects,
+ LayoutObject* layout_object,
+ WTF::Optional<unsigned> start_offset,
+ WTF::Optional<unsigned> end_offset) {
+ if (!layout_object->GetNode()->IsTextNode()) {
+ DCHECK(!start_offset.has_value());
+ DCHECK(!end_offset.has_value());
+ MarkSelected(&selected_objects, layout_object,
+ SelectionState::kStartAndEnd);
+ return {{layout_object, WTF::nullopt, layout_object, WTF::nullopt},
+ std::move(selected_objects)};
+ }
+
+ DCHECK(start_offset.has_value());
+ DCHECK(end_offset.has_value());
+ DCHECK_GE(end_offset.value(), start_offset.value());
+ if (start_offset.value() == end_offset.value())
+ return {};
+ LayoutTextFragment* const first_letter_part =
+ FirstLetterPartFor(layout_object);
+ if (!first_letter_part) {
+ MarkSelected(&selected_objects, layout_object,
+ SelectionState::kStartAndEnd);
+ return {{layout_object, start_offset, layout_object, end_offset},
+ std::move(selected_objects)};
+ }
+ const unsigned unsigned_start = start_offset.value();
+ const unsigned unsigned_end = end_offset.value();
+ LayoutTextFragment* const remaining_part =
+ ToLayoutTextFragment(layout_object);
+ if (unsigned_start >= remaining_part->Start()) {
+ // Case 1: The selection starts and ends in remaining part.
+ DCHECK_GT(unsigned_end, remaining_part->Start());
+ MarkSelected(&selected_objects, remaining_part,
+ SelectionState::kStartAndEnd);
+ return {{remaining_part, unsigned_start - remaining_part->Start(),
+ remaining_part, unsigned_end - remaining_part->Start()},
+ std::move(selected_objects)};
+ }
+ if (unsigned_end <= remaining_part->Start()) {
+ // Case 2: The selection starts and ends in first letter part.
+ MarkSelected(&selected_objects, first_letter_part,
+ SelectionState::kStartAndEnd);
+ return {{first_letter_part, start_offset, first_letter_part, end_offset},
+ std::move(selected_objects)};
+ }
+ // Case 3: The selection starts in first-letter part and ends in remaining
+ // part.
+ DCHECK_GT(unsigned_end, remaining_part->Start());
+ MarkSelected(&selected_objects, first_letter_part, SelectionState::kStart);
+ MarkSelected(&selected_objects, remaining_part, SelectionState::kEnd);
+ return {{first_letter_part, start_offset, remaining_part,
+ unsigned_end - remaining_part->Start()},
+ std::move(selected_objects)};
+}
+
+// LayoutObjectAndOffset represents start or end of SelectionPaintRange.
+struct LayoutObjectAndOffset {
+ STACK_ALLOCATED();
+ LayoutObject* layout_object;
+ WTF::Optional<unsigned> offset;
+
+ explicit LayoutObjectAndOffset(LayoutObject* passed_layout_object)
+ : layout_object(passed_layout_object), offset(WTF::nullopt) {
+ DCHECK(passed_layout_object);
+ DCHECK(!passed_layout_object->GetNode()->IsTextNode());
+ }
+ LayoutObjectAndOffset(LayoutText* layout_text, unsigned passed_offset)
+ : layout_object(layout_text), offset(passed_offset) {
+ DCHECK(layout_object);
+ }
+};
+
+LayoutObjectAndOffset MarkStart(SelectedLayoutObjects* selected_objects,
+ LayoutObject* start_layout_object,
+ WTF::Optional<unsigned> start_offset) {
+ if (!start_layout_object->GetNode()->IsTextNode()) {
+ DCHECK(!start_offset.has_value());
+ MarkSelected(selected_objects, start_layout_object, SelectionState::kStart);
+ return LayoutObjectAndOffset(start_layout_object);
+ }
+
+ DCHECK(start_offset.has_value());
+ const unsigned unsigned_offset = start_offset.value();
+ LayoutText* const start_layout_text = ToLayoutText(start_layout_object);
+ if (unsigned_offset >= start_layout_text->TextStartOffset()) {
+ // |start_offset| is within |start_layout_object| whether it has first
+ // letter part or not.
+ MarkSelected(selected_objects, start_layout_object, SelectionState::kStart);
+ return {start_layout_text,
+ unsigned_offset - start_layout_text->TextStartOffset()};
+ }
+
+ // |start_layout_object| has first letter part and |start_offset| is within
+ // the part.
+ LayoutTextFragment* const first_letter_part =
+ FirstLetterPartFor(start_layout_object);
+ DCHECK(first_letter_part);
+ MarkSelected(selected_objects, first_letter_part, SelectionState::kStart);
+ MarkSelected(selected_objects, start_layout_text, SelectionState::kInside);
+ return {first_letter_part, start_offset.value()};
+}
+
+LayoutObjectAndOffset MarkEnd(SelectedLayoutObjects* selected_objects,
+ LayoutObject* end_layout_object,
+ WTF::Optional<unsigned> end_offset) {
+ if (!end_layout_object->GetNode()->IsTextNode()) {
+ DCHECK(!end_offset.has_value());
+ MarkSelected(selected_objects, end_layout_object, SelectionState::kEnd);
+ return LayoutObjectAndOffset(end_layout_object);
+ }
+
+ DCHECK(end_offset.has_value());
+ const unsigned unsigned_offset = end_offset.value();
+ LayoutText* const end_layout_text = ToLayoutText(end_layout_object);
+ if (unsigned_offset >= end_layout_text->TextStartOffset()) {
+ // |end_offset| is within |end_layout_object| whether it has first
+ // letter part or not.
+ MarkSelected(selected_objects, end_layout_object, SelectionState::kEnd);
+ if (LayoutTextFragment* const first_letter_part =
+ FirstLetterPartFor(end_layout_object)) {
+ MarkSelected(selected_objects, first_letter_part,
+ SelectionState::kInside);
+ }
+ return {end_layout_text,
+ unsigned_offset - end_layout_text->TextStartOffset()};
+ }
+
+ // |end_layout_object| has first letter part and |end_offset| is within
+ // the part.
+ LayoutTextFragment* const first_letter_part =
+ FirstLetterPartFor(end_layout_object);
+ DCHECK(first_letter_part);
+ MarkSelected(selected_objects, first_letter_part, SelectionState::kEnd);
+ return {first_letter_part, end_offset.value()};
+}
+
+static NewPaintRangeAndSelectedLayoutObjects MarkStartAndEndInTwoNodes(
+ SelectedLayoutObjects selected_objects,
+ LayoutObject* start_layout_object,
+ WTF::Optional<unsigned> start_offset,
+ LayoutObject* end_layout_object,
+ WTF::Optional<unsigned> end_offset) {
+ const LayoutObjectAndOffset& start =
+ MarkStart(&selected_objects, start_layout_object, start_offset);
+ const LayoutObjectAndOffset& end =
+ MarkEnd(&selected_objects, end_layout_object, end_offset);
+ return {{start.layout_object, start.offset, end.layout_object, end.offset},
+ std::move(selected_objects)};
+}
+
+static WTF::Optional<unsigned> GetTextContentOffset(
+ LayoutObject* layout_object,
+ WTF::Optional<unsigned> node_offset) {
+ DCHECK(layout_object->EnclosingNGBlockFlow());
+ // |layout_object| is start or end of selection and offset is only valid
+ // if it is LayoutText.
+ if (!layout_object->IsText())
+ return WTF::nullopt;
+ // There are LayoutText that selection can't be inside it(BR, WBR,
+ // LayoutCounter).
+ if (!node_offset.has_value())
+ return WTF::nullopt;
+ const Position position_in_dom(*layout_object->GetNode(),
+ node_offset.value());
+ const NGOffsetMapping* const offset_mapping =
+ NGOffsetMapping::GetFor(position_in_dom);
+ DCHECK(offset_mapping);
+ const WTF::Optional<unsigned>& ng_offset =
+ offset_mapping->GetTextContentOffset(position_in_dom);
+ return ng_offset;
+}
+
+static NewPaintRangeAndSelectedLayoutObjects ComputeNewPaintRange(
+ const NewPaintRangeAndSelectedLayoutObjects& new_range,
+ LayoutObject* start_layout_object,
+ WTF::Optional<unsigned> start_node_offset,
+ LayoutObject* end_layout_object,
+ WTF::Optional<unsigned> end_node_offset) {
+ if (new_range.PaintRange().IsNull())
+ return {};
+ LayoutObject* const start = new_range.PaintRange().StartLayoutObject();
+ // If LayoutObject is not in NG, use legacy offset.
+ const WTF::Optional<unsigned> start_offset =
+ start->EnclosingNGBlockFlow()
+ ? GetTextContentOffset(start_layout_object, start_node_offset)
+ : new_range.PaintRange().StartOffset();
+
+ LayoutObject* const end = new_range.PaintRange().EndLayoutObject();
+ const WTF::Optional<unsigned> end_offset =
+ end->EnclosingNGBlockFlow()
+ ? GetTextContentOffset(end_layout_object, end_node_offset)
+ : new_range.PaintRange().EndOffset();
+
+ return {{start, start_offset, end, end_offset},
+ std::move(new_range.LayoutObjects())};
+}
+
+// ClampOffset modifies |offset| fixed in a range of |text_fragment| start/end
+// offsets.
+static unsigned ClampOffset(unsigned offset,
+ const NGPhysicalTextFragment& text_fragment) {
+ return std::min(std::max(offset, text_fragment.StartOffset()),
+ text_fragment.EndOffset());
+}
+
+std::pair<unsigned, unsigned> LayoutSelection::SelectionStartEndForNG(
+ const NGPhysicalTextFragment& text_fragment) const {
+ // FrameSelection holds selection offsets in layout block flow at
+ // LayoutSelection::Commit() if selection starts/ends within Text that
+ // each LayoutObject::SelectionState indicates.
+ // These offset can out of |text_fragment| because SelectionState is of each
+ // LayoutText and not |text_fragment|.
+ switch (text_fragment.GetLayoutObject()->GetSelectionState()) {
+ case SelectionState::kStart: {
+ DCHECK(SelectionStart().has_value());
+ unsigned start_in_block = SelectionStart().value_or(0);
+ return {ClampOffset(start_in_block, text_fragment),
+ text_fragment.EndOffset()};
+ }
+ case SelectionState::kEnd: {
+ DCHECK(SelectionEnd().has_value());
+ unsigned end_in_block =
+ SelectionEnd().value_or(text_fragment.EndOffset());
+ return {text_fragment.StartOffset(),
+ ClampOffset(end_in_block, text_fragment)};
+ }
+ case SelectionState::kStartAndEnd: {
+ DCHECK(SelectionStart().has_value());
+ DCHECK(SelectionEnd().has_value());
+ unsigned start_in_block = SelectionStart().value_or(0);
+ unsigned end_in_block =
+ SelectionEnd().value_or(text_fragment.EndOffset());
+ return {ClampOffset(start_in_block, text_fragment),
+ ClampOffset(end_in_block, text_fragment)};
+ }
+ case SelectionState::kInside:
+ return {text_fragment.StartOffset(), text_fragment.EndOffset()};
+ default:
+ // This block is not included in selection.
+ return {0, 0};
+ }
+}
+
+static NewPaintRangeAndSelectedLayoutObjects
+CalcSelectionRangeAndSetSelectionState(const FrameSelection& frame_selection) {
+ const SelectionInDOMTree& selection_in_dom =
+ frame_selection.GetSelectionInDOMTree();
+ if (selection_in_dom.IsNone())
+ return {};
+
+ const EphemeralRangeInFlatTree& selection =
+ CalcSelectionInFlatTree(frame_selection);
+ if (selection.IsCollapsed() || frame_selection.IsHidden())
+ return {};
+
+ // Find first/last visible LayoutObject while
+ // marking SelectionState and collecting invalidation candidate LayoutObjects.
+ LayoutObject* start_layout_object = nullptr;
+ LayoutObject* end_layout_object = nullptr;
+ SelectedLayoutObjects selected_objects;
+ for (const Node& node : selection.Nodes()) {
+ LayoutObject* const layout_object = node.GetLayoutObject();
+ if (!layout_object || !layout_object->CanBeSelectionLeaf())
+ continue;
+
+ if (!start_layout_object) {
+ DCHECK(!end_layout_object);
+ start_layout_object = end_layout_object = layout_object;
+ continue;
+ }
+
+ // In this loop, |end_layout_object| is pointing current last candidate
+ // LayoutObject and if it is not start and we find next, we mark the
+ // current one as kInside.
+ if (end_layout_object != start_layout_object)
+ MarkSelectedInside(&selected_objects, end_layout_object);
+ end_layout_object = layout_object;
+ }
+
+ // No valid LayOutObject found.
+ if (!start_layout_object) {
+ DCHECK(!end_layout_object);
+ return {};
+ }
+
+ // Compute offset. It has value iff start/end is text.
+ const WTF::Optional<unsigned> start_offset = ComputeStartOffset(
+ *start_layout_object, selection.StartPosition().ToOffsetInAnchor());
+ const WTF::Optional<unsigned> end_offset = ComputeEndOffset(
+ *end_layout_object, selection.EndPosition().ToOffsetInAnchor());
+
+ NewPaintRangeAndSelectedLayoutObjects new_range =
+ start_layout_object == end_layout_object
+ ? MarkStartAndEndInOneNode(std::move(selected_objects),
+ start_layout_object, start_offset,
+ end_offset)
+ : MarkStartAndEndInTwoNodes(std::move(selected_objects),
+ start_layout_object, start_offset,
+ end_layout_object, end_offset);
+
+ if (!RuntimeEnabledFeatures::LayoutNGEnabled())
+ return new_range;
+ return ComputeNewPaintRange(new_range, start_layout_object, start_offset,
+ end_layout_object, end_offset);
+}
+
+void LayoutSelection::SetHasPendingSelection() {
+ has_pending_selection_ = true;
+}
+
+void LayoutSelection::Commit() {
+ if (!HasPendingSelection())
+ return;
+ has_pending_selection_ = false;
+
+ DCHECK(!frame_selection_->GetDocument().NeedsLayoutTreeUpdate());
+ DCHECK_GE(frame_selection_->GetDocument().Lifecycle().GetState(),
+ DocumentLifecycle::kLayoutClean);
+ DocumentLifecycle::DisallowTransitionScope disallow_transition(
+ frame_selection_->GetDocument().Lifecycle());
+
+ const OldSelectedLayoutObjects& old_selected_objects =
+ ResetOldSelectedLayoutObjects(paint_range_);
+ const NewPaintRangeAndSelectedLayoutObjects& new_range =
+ CalcSelectionRangeAndSetSelectionState(*frame_selection_);
+ DCHECK(frame_selection_->GetDocument().GetLayoutView()->GetFrameView());
+ SetShouldInvalidateSelection(new_range, paint_range_, old_selected_objects);
+
+ paint_range_ = new_range.PaintRange();
+ if (paint_range_.IsNull())
+ return;
+ // TODO(yoichio): Remove this if state.
+ // This SelectionState reassignment is ad-hoc patch for
+ // prohibiting use-after-free(crbug.com/752715).
+ // LayoutText::setSelectionState(state) propergates |state| to ancestor
+ // LayoutObjects, which can accidentally change start/end LayoutObject state
+ // then LayoutObject::IsSelectionBorder() returns false although we should
+ // clear selection at LayoutObject::WillBeRemoved().
+ // We should make LayoutObject::setSelectionState() trivial and remove
+ // such propagation or at least do it in LayoutSelection.
+ if ((paint_range_.StartLayoutObject()->GetSelectionState() !=
+ SelectionState::kStart &&
+ paint_range_.StartLayoutObject()->GetSelectionState() !=
+ SelectionState::kStartAndEnd) ||
+ (paint_range_.EndLayoutObject()->GetSelectionState() !=
+ SelectionState::kEnd &&
+ paint_range_.EndLayoutObject()->GetSelectionState() !=
+ SelectionState::kStartAndEnd)) {
+ if (paint_range_.StartLayoutObject() == paint_range_.EndLayoutObject()) {
+ paint_range_.StartLayoutObject()->SetSelectionState(
+ SelectionState::kStartAndEnd);
+ } else {
+ paint_range_.StartLayoutObject()->SetSelectionState(
+ SelectionState::kStart);
+ paint_range_.EndLayoutObject()->SetSelectionState(SelectionState::kEnd);
+ }
+ }
+ // TODO(yoichio): If start == end, they should be kStartAndEnd.
+ // If not, start.SelectionState == kStart and vice versa.
+ DCHECK(paint_range_.StartLayoutObject()->GetSelectionState() ==
+ SelectionState::kStart ||
+ paint_range_.StartLayoutObject()->GetSelectionState() ==
+ SelectionState::kStartAndEnd);
+ DCHECK(paint_range_.EndLayoutObject()->GetSelectionState() ==
+ SelectionState::kEnd ||
+ paint_range_.EndLayoutObject()->GetSelectionState() ==
+ SelectionState::kStartAndEnd);
+}
+
+void LayoutSelection::OnDocumentShutdown() {
+ has_pending_selection_ = false;
+ paint_range_ = SelectionPaintRange();
+}
+
+static LayoutRect SelectionRectForLayoutObject(const LayoutObject* object) {
+ if (!object->IsRooted())
+ return LayoutRect();
+
+ if (!object->CanUpdateSelectionOnRootLineBoxes())
+ return LayoutRect();
+
+ return object->AbsoluteSelectionRect();
+}
+
+IntRect LayoutSelection::AbsoluteSelectionBounds() {
+ Commit();
+ if (paint_range_.IsNull())
+ return IntRect();
+
+ // Create a single bounding box rect that encloses the whole selection.
+ LayoutRect selected_rect;
+ for (LayoutObject* layout_object : paint_range_) {
+ const SelectionState state = layout_object->GetSelectionState();
+ if (state == SelectionState::kContain || state == SelectionState::kNone)
+ continue;
+ selected_rect.Unite(SelectionRectForLayoutObject(layout_object));
+ }
+
+ return PixelSnappedIntRect(selected_rect);
+}
+
+void LayoutSelection::InvalidatePaintForSelection() {
+ if (paint_range_.IsNull())
+ return;
+
+ for (LayoutObject* runner : paint_range_) {
+ if (runner->GetSelectionState() == SelectionState::kNone)
+ continue;
+
+ runner->SetShouldInvalidateSelection();
+ }
+}
+
+void LayoutSelection::Trace(blink::Visitor* visitor) {
+ visitor->Trace(frame_selection_);
+}
+
+void PrintLayoutObjectForSelection(std::ostream& ostream,
+ LayoutObject* layout_object) {
+ if (!layout_object) {
+ ostream << "<null>";
+ return;
+ }
+ ostream << (void*)layout_object << ' ' << layout_object->GetNode()
+ << ", state:" << layout_object->GetSelectionState()
+ << (layout_object->ShouldInvalidateSelection() ? ", ShouldInvalidate"
+ : ", NotInvalidate");
+}
+#ifndef NDEBUG
+void ShowLayoutObjectForSelection(LayoutObject* layout_object) {
+ std::stringstream stream;
+ PrintLayoutObjectForSelection(stream, layout_object);
+ LOG(INFO) << '\n' << stream.str();
+}
+#endif
+
+} // namespace blink