Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(299)

Unified Diff: third_party/WebKit/Source/core/editing/LayoutSelection.cpp

Issue 2962143002: [WIP] Find LayoutSelection start/end w/o VisibleSelection. (Closed)
Patch Set: update Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/WebKit/Source/core/editing/LayoutSelection.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/WebKit/Source/core/editing/LayoutSelection.cpp
diff --git a/third_party/WebKit/Source/core/editing/LayoutSelection.cpp b/third_party/WebKit/Source/core/editing/LayoutSelection.cpp
index 8d87696471ccc12588e00077d133d8478233925b..6b93bc89dfa9700d94f9f58e27bf821efef544ef 100644
--- a/third_party/WebKit/Source/core/editing/LayoutSelection.cpp
+++ b/third_party/WebKit/Source/core/editing/LayoutSelection.cpp
@@ -27,6 +27,8 @@
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleUnits.h"
#include "core/html/TextControlElement.h"
+#include "core/layout/LayoutBlock.h"
+#include "core/layout/LayoutObject.h"
#include "core/layout/LayoutView.h"
#include "core/paint/PaintLayer.h"
@@ -109,31 +111,488 @@ LayoutSelection::LayoutSelection(FrameSelection& frame_selection)
has_pending_selection_(false),
paint_range_(SelectionPaintRange()) {}
+static bool IsSelectionAtomicAndVisible(LayoutObject* layout_object) {
+ if (!layout_object)
+ return false;
+ // return true;/*
+ if (layout_object->CanBeSelectionLeaf())
+ return true;
+ if (layout_object->IsImage() || layout_object->IsLayoutEmbeddedContent())
+ return true;
+ Node* node = layout_object->GetNode();
+ if (!node || !node->IsHTMLElement())
+ return false;
+ if (isHTMLTableElement(node))
+ return true;
+ if (IsHTMLFormControlElement(ToHTMLElement(*node)) ||
+ isHTMLLegendElement(ToHTMLElement(*node)) ||
+ isHTMLImageElement(ToHTMLElement(*node)) ||
+ isHTMLMeterElement(ToHTMLElement(*node)) ||
+ isHTMLProgressElement(ToHTMLElement(*node)))
+ return true;
+ return false; //*/
+}
+
+static Vector<LayoutObject*> GetAncestors(LayoutObject* layout_object) {
+ Vector<LayoutObject*> ancestors;
+ for (LayoutObject* runner = layout_object; runner;) {
+ ancestors.push_back(runner);
+ LayoutObject* next = runner->Parent();
+ if (runner == next)
+ break;
+ runner = next;
+ }
+ return std::move(ancestors);
+}
+
+static Optional<int> compare(LayoutObject* a, LayoutObject* b) {
+ if (a == b)
+ return {0};
+ const Vector<LayoutObject*>& a_ancestors = GetAncestors(a);
+ const Vector<LayoutObject*>& b_ancestors = GetAncestors(b);
+ if (a_ancestors[a_ancestors.size() - 1] !=
+ b_ancestors[b_ancestors.size() - 1])
+ return {};
+ LayoutObject* recentAncestor = a_ancestors[a_ancestors.size() - 1];
+ size_t i = 0;
+ for (; i < std::min(a_ancestors.size(), b_ancestors.size()); ++i) {
+ if (a_ancestors[a_ancestors.size() - 1 - i] !=
+ b_ancestors[b_ancestors.size() - 1 - i])
+ break;
+ recentAncestor = a_ancestors[a_ancestors.size() - 1 - i];
+ }
+
+ if (i == a_ancestors.size())
+ return {-1}; // a is ancestor of b
+ if (i == b_ancestors.size())
+ return {1}; // b is ancestor of a
+ LayoutObject* const a_child_of_RA = a_ancestors[a_ancestors.size() - 1 - i];
+ LayoutObject* const b_child_of_RA = b_ancestors[b_ancestors.size() - 1 - i];
+ for (LayoutObject* runner = a_child_of_RA; runner;
+ runner = runner->NextSibling()) {
+ if (runner == b_child_of_RA)
+ return {-1};
+ }
+ return {1};
+}
+
+struct SelectionPosition {
+ STACK_ALLOCATED();
+
+ SelectionPosition() : SelectionPosition(nullptr, -1) {}
+ SelectionPosition(LayoutObject* layout_object, int offset)
+ : layout_object_(layout_object), offset_(offset) {}
+
+ SelectionPosition(const PositionInFlatTree& position) : SelectionPosition() {
+ if (position.IsNull())
+ return;
+ layout_object_ = position.AnchorNode()->GetLayoutObject();
+ offset_ = position.ComputeEditingOffset();
+ }
+
+ bool IsNull() const { return !layout_object_; }
+ PositionInFlatTree ToPositionInFlatTree() const {
+ if (IsNull())
+ return PositionInFlatTree();
+ return PositionInFlatTree(layout_object_->GetNode(), offset_);
+ }
+
+ LayoutObject* layout_object_;
+ int offset_;
+
+ bool operator==(const SelectionPosition& other) const {
+ return layout_object_ == other.layout_object_ && offset_ == other.offset_;
+ }
+ bool operator!=(const SelectionPosition& other) const {
+ return !operator==(other);
+ }
+};
+
+Node* ComputeNodeAfterPosition(const PositionInFlatTree& position) {
+ if (!position.AnchorNode())
+ return 0;
+
+ switch (position.AnchorType()) {
+ case PositionAnchorType::kBeforeChildren: {
+ if (Node* first_child =
+ FlatTreeTraversal::FirstChild(*position.AnchorNode()))
+ return first_child;
+ FlatTreeTraversal::NextSkippingChildren(*position.AnchorNode());
+ }
+ case PositionAnchorType::kAfterChildren:
+ return FlatTreeTraversal::NextSkippingChildren(*position.AnchorNode());
+ case PositionAnchorType::kOffsetInAnchor: {
+ if (position.AnchorNode()->IsCharacterDataNode())
+ return FlatTreeTraversal::Next(*position.AnchorNode());
+ if (Node* child_at = FlatTreeTraversal::ChildAt(
+ *position.AnchorNode(), position.OffsetInContainerNode()))
+ return child_at;
+ return FlatTreeTraversal::Next(*position.AnchorNode());
+ }
+ case PositionAnchorType::kBeforeAnchor:
+ return position.AnchorNode();
+ case PositionAnchorType::kAfterAnchor:
+ return FlatTreeTraversal::NextSkippingChildren(*position.AnchorNode());
+ }
+ NOTREACHED();
+ return 0;
+}
+
+static SelectionPosition FirstLayoutPosition(const PositionInFlatTree& start) {
+ if (start.AnchorNode()->IsTextNode() &&
+ start.AnchorNode()->GetLayoutObject()) {
+ return start;
+ }
+
+ LayoutObject* first_layout_object = nullptr;
+ for (Node* runner = ComputeNodeAfterPosition(start); runner;
+ runner = FlatTreeTraversal::Next(*runner)) {
+ if ((first_layout_object = runner->GetLayoutObject()))
+ break;
+ }
+ if (!first_layout_object)
+ return {};
+
+ for (LayoutObject* runner = first_layout_object; runner;
+ runner = runner->NextInPreOrder()) {
+ if (!IsSelectionAtomicAndVisible(runner))
+ continue;
+
+ return {runner, 0};
+ }
+ return {};
+}
+
+// Traverse FlatTree parent first backward.
+// It looks mirror of Next().
+static Node* Previous(const Node& node) {
+ if (FlatTreeTraversal::FirstChild(node))
+ return FlatTreeTraversal::LastWithin(node);
+ return FlatTreeTraversal::PreviousSkippingChildren(node);
+}
+
+// Traverse FlatTree parent first backward.
+// It looks mirror of Next().
+static LayoutObject* Previous(const LayoutObject& layout_object) {
+ if (LayoutObject* last_child = layout_object.SlowLastChild())
+ return last_child;
+ if (LayoutObject* previous_sibling = layout_object.PreviousSibling())
+ return previous_sibling;
+ for (LayoutObject* ancestor = layout_object.Parent(); ancestor;) {
+ LayoutObject* ancestor_prev_sib = ancestor->PreviousSibling();
+ if (ancestor_prev_sib)
+ return ancestor_prev_sib;
+ LayoutObject* parent = layout_object.Parent();
+ // LayoutTests/paint/invalidation/text-selection-rect-in-overflow-2.html
+ // makes infinite self-parent loop. Strange.
+ if (parent == ancestor)
+ return nullptr;
+ ancestor = parent;
+ }
+ return nullptr;
+}
+
+static Node* ComputeNodeBeforePosition(const PositionInFlatTree& position) {
+ if (!position.AnchorNode())
+ return nullptr;
+ switch (position.AnchorType()) {
+ case PositionAnchorType::kBeforeChildren:
+ return Previous(*position.AnchorNode());
+ case PositionAnchorType::kAfterChildren: {
+ if (Node* last_child =
+ FlatTreeTraversal::LastChild(*position.AnchorNode()))
+ return last_child;
+ return Previous(*position.AnchorNode());
+ }
+ case PositionAnchorType::kOffsetInAnchor: {
+ if (position.AnchorNode()->IsCharacterDataNode())
+ return Previous(*position.AnchorNode());
+ if (position.OffsetInContainerNode() == 0)
+ return Previous(*position.AnchorNode());
+ Node* child_before_offset = FlatTreeTraversal::ChildAt(
+ *position.AnchorNode(), position.OffsetInContainerNode() - 1);
+ return child_before_offset;
+ }
+ case PositionAnchorType::kBeforeAnchor:
+ return Previous(*position.AnchorNode());
+ case PositionAnchorType::kAfterAnchor:
+ return position.AnchorNode();
+ }
+ NOTREACHED();
+ return 0;
+}
+
+static SelectionPosition LastLayoutPosition(const PositionInFlatTree& end) {
+ if (end.AnchorNode()->IsTextNode() && end.AnchorNode()->GetLayoutObject()) {
+ return end;
+ }
+
+ LayoutObject* last_layout_object = nullptr;
+ for (Node* runner = ComputeNodeBeforePosition(end); runner;
+ runner = Previous(*runner)) {
+ if ((last_layout_object = runner->GetLayoutObject()))
+ break;
+ }
+ if (!last_layout_object)
+ return {};
+
+ for (LayoutObject* runner = last_layout_object; runner;
+ runner = Previous(*runner)) {
+ if (!IsSelectionAtomicAndVisible(runner))
+ continue;
+ if (Node* node = runner->GetNode()) {
+ if (node->IsTextNode()) {
+ return {runner, (int)ToText(runner->GetNode())->data().length()};
+ }
+ }
+
+ return {runner, 1};
+ }
+ return PositionInFlatTree();
+}
+
+static SelectionPosition ComputeStartRespectingGranularity(
+ const PositionInFlatTree passed_start,
+ TextGranularity granularity) {
+ DCHECK(passed_start.IsNotNull());
+
+ switch (granularity) {
+ case kCharacterGranularity:
+ // Don't do any expansion.
+ return FirstLayoutPosition(passed_start);
+ case kWordGranularity: {
+ // General case: Select the word the caret is positioned inside of.
+ // If the caret is on the word boundary, select the word according to
+ // |wordSide|.
+ // Edge case: If the caret is after the last word in a soft-wrapped line
+ // or the last word in the document, select that last word
+ // (LeftWordIfOnBoundary).
+ // Edge case: If the caret is after the last word in a paragraph, select
+ // from the the end of the last word to the line break (also
+ // RightWordIfOnBoundary);
+ const VisiblePositionInFlatTree& visible_start =
+ CreateVisiblePosition(passed_start);
+ if (IsEndOfEditableOrNonEditableContent(visible_start) ||
+ (IsEndOfLine(visible_start) && !IsStartOfLine(visible_start) &&
+ !IsEndOfParagraph(visible_start))) {
+ return StartOfWord(visible_start, kLeftWordIfOnBoundary)
+ .DeepEquivalent();
+ }
+ return StartOfWord(visible_start, kRightWordIfOnBoundary)
+ .DeepEquivalent();
+ }
+ case kLineGranularity:
+ return StartOfLine(CreateVisiblePosition(passed_start)).DeepEquivalent();
+ case kParagraphGranularity: {
+ const VisiblePositionInFlatTree pos = CreateVisiblePosition(passed_start);
+ if (IsStartOfLine(pos) && IsEndOfEditableOrNonEditableContent(pos))
+ return StartOfParagraph(PreviousPositionOf(pos)).DeepEquivalent();
+ return StartOfParagraph(pos).DeepEquivalent();
+ }
+ default:
+ break;
+ }
+
+ NOTREACHED();
+ return PositionInFlatTree();
+}
+
+static SelectionPosition ComputeEndRespectingGranularity(
+ const PositionInFlatTree& start,
+ const PositionInFlatTree& passed_end,
+ TextGranularity granularity) {
+ DCHECK(passed_end.IsNotNull());
+
+ switch (granularity) {
+ case kCharacterGranularity:
+ // Don't do any expansion.
+ return LastLayoutPosition(passed_end);
+ case kWordGranularity: {
+ // General case: Select the word the caret is positioned inside of.
+ // If the caret is on the word boundary, select the word according to
+ // |wordSide|.
+ // Edge case: If the caret is after the last word in a soft-wrapped line
+ // or the last word in the document, select that last word
+ // (|LeftWordIfOnBoundary|).
+ // Edge case: If the caret is after the last word in a paragraph, select
+ // from the the end of the last word to the line break (also
+ // |RightWordIfOnBoundary|);
+ const VisiblePositionInFlatTree& original_end =
+ CreateVisiblePosition(passed_end);
+ EWordSide side = kRightWordIfOnBoundary;
+ if (IsEndOfEditableOrNonEditableContent(original_end) ||
+ (IsEndOfLine(original_end) && !IsStartOfLine(original_end) &&
+ !IsEndOfParagraph(original_end)))
+ side = kLeftWordIfOnBoundary;
+
+ const VisiblePositionInFlatTree& word_end = EndOfWord(original_end, side);
+ if (!IsEndOfParagraph(original_end))
+ return word_end.DeepEquivalent();
+ if (IsEmptyTableCell(start.AnchorNode()))
+ return word_end.DeepEquivalent();
+
+ // Select the paragraph break (the space from the end of a paragraph
+ // to the start of the next one) to match TextEdit.
+ const VisiblePositionInFlatTree& end = NextPositionOf(word_end);
+ Element* const table = TableElementJustBefore(end);
+ if (!table) {
+ if (end.IsNull())
+ return word_end.DeepEquivalent();
+ return end.DeepEquivalent();
+ }
+
+ if (!IsEnclosingBlock(table))
+ return word_end.DeepEquivalent();
+
+ // The paragraph break after the last paragraph in the last cell
+ // of a block table ends at the start of the paragraph after the
+ // table.
+ const VisiblePositionInFlatTree next =
+ NextPositionOf(end, kCannotCrossEditingBoundary);
+ if (next.IsNull())
+ return word_end.DeepEquivalent();
+ return next.DeepEquivalent();
+ }
+ case kLineGranularity: {
+ const VisiblePositionInFlatTree& end =
+ EndOfLine(CreateVisiblePosition(passed_end));
+ if (!IsEndOfParagraph(end))
+ return end.DeepEquivalent();
+ // If the end of this line is at the end of a paragraph, include the
+ // space after the end of the line in the selection.
+ const VisiblePositionInFlatTree& next = NextPositionOf(end);
+ if (next.IsNull())
+ return end.DeepEquivalent();
+ return next.DeepEquivalent();
+ }
+ case kParagraphGranularity: {
+ const VisiblePositionInFlatTree& visible_paragraph_end =
+ EndOfParagraph(CreateVisiblePosition(passed_end));
+
+ // Include the "paragraph break" (the space from the end of this
+ // paragraph to the start of the next one) in the selection.
+ const VisiblePositionInFlatTree& end =
+ NextPositionOf(visible_paragraph_end);
+
+ Element* const table = TableElementJustBefore(end);
+ if (!table) {
+ if (end.IsNull())
+ return visible_paragraph_end.DeepEquivalent();
+ return end.DeepEquivalent();
+ }
+
+ if (!IsEnclosingBlock(table)) {
+ // There is no paragraph break after the last paragraph in the
+ // last cell of an inline table.
+ return visible_paragraph_end.DeepEquivalent();
+ }
+
+ // The paragraph break after the last paragraph in the last cell of
+ // a block table ends at the start of the paragraph after the table,
+ // not at the position just after the table.
+ const VisiblePositionInFlatTree& next =
+ NextPositionOf(end, kCannotCrossEditingBoundary);
+ if (next.IsNull())
+ return visible_paragraph_end.DeepEquivalent();
+ return next.DeepEquivalent();
+ }
+ default:
+ break;
+ }
+ NOTREACHED();
+ return PositionInFlatTree();
+}
+
static bool ShouldShowBlockCursor(const FrameSelection& frame_selection,
- const VisibleSelectionInFlatTree& selection) {
+ const SelectionPosition& start,
+ const SelectionPosition& end) {
if (!frame_selection.ShouldShowBlockCursor())
return false;
- if (selection.GetSelectionType() != SelectionType::kCaretSelection)
+ if (start != end)
return false;
- if (IsLogicalEndOfLine(selection.VisibleEnd()))
+ if (IsLogicalEndOfLine(CreateVisiblePosition(start.ToPositionInFlatTree())))
return false;
return true;
}
-static VisibleSelectionInFlatTree CalcSelection(
+#define MYDEBUG
+
+#ifdef MYDEBUG
+static VisibleSelectionInFlatTree createFromDOM(
+ const SelectionInDOMTree& selection_) {
+ SelectionInFlatTree::Builder builder;
+ const PositionInFlatTree& base = ToPositionInFlatTree(selection_.Base());
+ const PositionInFlatTree& extent = ToPositionInFlatTree(selection_.Extent());
+ if (base.IsNotNull() && extent.IsNotNull())
+ builder.SetBaseAndExtent(base, extent);
+ else if (base.IsNotNull())
+ builder.Collapse(base);
+ else if (extent.IsNotNull())
+ builder.Collapse(extent);
+ builder.SetAffinity(selection_.Affinity())
+ .SetHasTrailingWhitespace(selection_.HasTrailingWhitespace())
+ .SetGranularity(selection_.Granularity())
+ .SetIsDirectional(selection_.IsDirectional());
+ return CreateVisibleSelection(builder.Build());
+}
+#endif
+
+static std::pair<SelectionPosition, SelectionPosition> CalcSelection(
const FrameSelection& frame_selection) {
+ const SelectionInDOMTree& selection_in_dom =
+ frame_selection.GetSelectionInDOMTree();
+#ifdef MYDEBUG
const VisibleSelectionInFlatTree& original_selection =
- frame_selection.ComputeVisibleSelectionInFlatTree();
-
- if (!ShouldShowBlockCursor(frame_selection, original_selection))
- return original_selection;
-
- const PositionInFlatTree end_position = NextPositionOf(
- original_selection.Start(), PositionMoveType::kGraphemeCluster);
- return CreateVisibleSelection(
- SelectionInFlatTree::Builder()
- .SetBaseAndExtent(original_selection.Start(), end_position)
- .Build());
+ createFromDOM(selection_in_dom);
+ const SelectionPosition original_start = original_selection.Start();
+ const SelectionPosition original_end = original_selection.End();
+ DCHECK(original_start.IsNull() || original_end.IsNull() ||
+ original_selection.IsNone() || true);
+#endif
+
+ const PositionInFlatTree& base =
+ ToPositionInFlatTree(selection_in_dom.Base());
+ const PositionInFlatTree& extent =
+ ToPositionInFlatTree(selection_in_dom.Extent());
+ const bool is_base_first = base <= extent;
+ const PositionInFlatTree& start = is_base_first ? base : extent;
+ const PositionInFlatTree& end = is_base_first ? extent : base;
+ DCHECK_LE(start, end);
+ if (start.IsNull() || end.IsNull())
+ return {PositionInFlatTree(), PositionInFlatTree()};
+
+ SelectionPosition start_mod_granularity = start;
+ SelectionPosition end_mod_granularity = end;
+ if (selection_in_dom.Granularity() != kCharacterGranularity || start != end) {
+ start_mod_granularity = ComputeStartRespectingGranularity(
+ start, selection_in_dom.Granularity());
+ end_mod_granularity = ComputeEndRespectingGranularity(
+ start, end, selection_in_dom.Granularity());
+ }
+// #define USE_ORIGINAL
+#ifdef USE_ORIGINAL
+ start_mod_granularity = original_start;
+ end_mod_granularity = original_end;
+#else
+ if (start_mod_granularity.IsNull() || end_mod_granularity.IsNull())
+ return {PositionInFlatTree(), PositionInFlatTree()};
+ Optional<int> comp = compare(start_mod_granularity.layout_object_,
+ end_mod_granularity.layout_object_);
+ DCHECK(comp.has_value());
+ if (comp.value() > 0)
+ end_mod_granularity = start_mod_granularity;
+#endif
+ // DCHECK_LE(start_mod_granularity, end_mod_granularity);
+ /*const PositionInFlatTree& start_most_forward =
+ MostForwardCaretPosition(start_mod_granularity); const PositionInFlatTree&
+ end_most_backward = MostBackwardCaretPosition(end_mod_granularity);*/
+ if (!ShouldShowBlockCursor(frame_selection, start_mod_granularity,
+ end_mod_granularity))
+ return {start_mod_granularity, end_mod_granularity};
+
+ const PositionInFlatTree end_position =
+ NextPositionOf(start, PositionMoveType::kGraphemeCluster);
+ return {start, end_position};
}
// Objects each have a single selection rect to examine.
@@ -297,23 +756,22 @@ static SelectionPaintRange CalcSelectionPaintRange(
if (selection_in_dom.IsNone())
return SelectionPaintRange();
- const VisibleSelectionInFlatTree& selection = CalcSelection(frame_selection);
- if (!selection.IsRange() || frame_selection.IsHidden())
+ const std::pair<SelectionPosition, SelectionPosition>& selection =
+ CalcSelection(frame_selection);
+ const SelectionPosition& start_pos = selection.first;
+ const SelectionPosition& end_pos = selection.second;
+ if (start_pos == end_pos || frame_selection.IsHidden())
return SelectionPaintRange();
- DCHECK(!selection.IsNone());
- const PositionInFlatTree start_pos = selection.Start();
- const PositionInFlatTree end_pos = selection.End();
- DCHECK_LE(start_pos, end_pos);
- LayoutObject* start_layout_object = start_pos.AnchorNode()->GetLayoutObject();
- LayoutObject* end_layout_object = end_pos.AnchorNode()->GetLayoutObject();
+ // DCHECK_LE(start_pos, end_pos);
+ LayoutObject* start_layout_object = start_pos.layout_object_;
+ LayoutObject* end_layout_object = end_pos.layout_object_;
DCHECK(start_layout_object);
DCHECK(end_layout_object);
DCHECK(start_layout_object->View() == end_layout_object->View());
- return SelectionPaintRange(start_layout_object,
- start_pos.ComputeEditingOffset(),
- end_layout_object, end_pos.ComputeEditingOffset());
+ return SelectionPaintRange(start_layout_object, start_pos.offset_,
+ end_layout_object, end_pos.offset_);
}
void LayoutSelection::Commit() {
« no previous file with comments | « third_party/WebKit/Source/core/editing/LayoutSelection.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698