Index: third_party/WebKit/Source/core/editing/VisibleUnitsParagraph.cpp |
diff --git a/third_party/WebKit/Source/core/editing/VisibleUnitsParagraph.cpp b/third_party/WebKit/Source/core/editing/VisibleUnitsParagraph.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e4eb2451876bbc98aa22715446e3d084ae4025f2 |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/editing/VisibleUnitsParagraph.cpp |
@@ -0,0 +1,408 @@ |
+/* |
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 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. |
+ */ |
+ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "core/editing/VisibleUnits.h" |
+ |
+#include "core/editing/EditingUtilities.h" |
+#include "core/layout/LayoutText.h" |
+#include "core/layout/api/LayoutItem.h" |
+ |
+namespace blink { |
+ |
+namespace { |
+ |
+bool NodeIsUserSelectAll(const Node* node) { |
+ return node && node->GetLayoutObject() && |
+ node->GetLayoutObject()->Style()->UserSelect() == EUserSelect::kAll; |
+} |
+ |
+template <typename Strategy> |
+PositionTemplate<Strategy> StartOfParagraphAlgorithm( |
+ const PositionTemplate<Strategy>& position, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ Node* const start_node = position.AnchorNode(); |
+ |
+ if (!start_node) |
+ return PositionTemplate<Strategy>(); |
+ |
+ if (IsRenderedAsNonInlineTableImageOrHR(start_node)) |
+ return PositionTemplate<Strategy>::BeforeNode(start_node); |
+ |
+ Element* const start_block = EnclosingBlock( |
+ PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node), |
+ kCannotCrossEditingBoundary); |
+ ContainerNode* const highest_root = HighestEditableRoot(position); |
+ const bool start_node_is_editable = HasEditableStyle(*start_node); |
+ |
+ Node* candidate_node = start_node; |
+ PositionAnchorType candidate_type = position.AnchorType(); |
+ int candidate_offset = position.ComputeEditingOffset(); |
+ |
+ Node* previous_node_iterator = start_node; |
+ while (previous_node_iterator) { |
+ if (boundary_crossing_rule == kCannotCrossEditingBoundary && |
+ !NodeIsUserSelectAll(previous_node_iterator) && |
+ HasEditableStyle(*previous_node_iterator) != start_node_is_editable) |
+ break; |
+ if (boundary_crossing_rule == kCanSkipOverEditingBoundary) { |
+ while (previous_node_iterator && |
+ HasEditableStyle(*previous_node_iterator) != |
+ start_node_is_editable) { |
+ previous_node_iterator = |
+ Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
+ } |
+ if (!previous_node_iterator || |
+ !previous_node_iterator->IsDescendantOf(highest_root)) |
+ break; |
+ } |
+ |
+ const LayoutItem layout_item = |
+ LayoutItem(previous_node_iterator->GetLayoutObject()); |
+ if (layout_item.IsNull()) { |
+ previous_node_iterator = |
+ Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
+ continue; |
+ } |
+ const ComputedStyle& style = layout_item.StyleRef(); |
+ if (style.Visibility() != EVisibility::kVisible) { |
+ previous_node_iterator = |
+ Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
+ continue; |
+ } |
+ |
+ if (layout_item.IsBR() || IsEnclosingBlock(previous_node_iterator)) |
+ break; |
+ |
+ if (layout_item.IsText() && |
+ ToLayoutText(previous_node_iterator->GetLayoutObject()) |
+ ->ResolvedTextLength()) { |
+ SECURITY_DCHECK(previous_node_iterator->IsTextNode()); |
+ if (style.PreserveNewline()) { |
+ LayoutText* text = |
+ ToLayoutText(previous_node_iterator->GetLayoutObject()); |
+ int index = text->TextLength(); |
+ if (previous_node_iterator == start_node && candidate_offset < index) |
+ index = max(0, candidate_offset); |
+ while (--index >= 0) { |
+ if ((*text)[index] == '\n') { |
+ return PositionTemplate<Strategy>(ToText(previous_node_iterator), |
+ index + 1); |
+ } |
+ } |
+ } |
+ candidate_node = previous_node_iterator; |
+ candidate_type = PositionAnchorType::kOffsetInAnchor; |
+ candidate_offset = 0; |
+ previous_node_iterator = |
+ Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
+ } else if (EditingIgnoresContent(*previous_node_iterator) || |
+ IsDisplayInsideTable(previous_node_iterator)) { |
+ candidate_node = previous_node_iterator; |
+ candidate_type = PositionAnchorType::kBeforeAnchor; |
+ previous_node_iterator = previous_node_iterator->previousSibling() |
+ ? previous_node_iterator->previousSibling() |
+ : Strategy::PreviousPostOrder( |
+ *previous_node_iterator, start_block); |
+ } else { |
+ previous_node_iterator = |
+ Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
+ } |
+ } |
+ |
+ if (candidate_type == PositionAnchorType::kOffsetInAnchor) |
+ return PositionTemplate<Strategy>(candidate_node, candidate_offset); |
+ |
+ return PositionTemplate<Strategy>(candidate_node, candidate_type); |
+} |
+ |
+template <typename Strategy> |
+VisiblePositionTemplate<Strategy> StartOfParagraphAlgorithm( |
+ const VisiblePositionTemplate<Strategy>& visible_position, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ DCHECK(visible_position.IsValid()) << visible_position; |
+ return CreateVisiblePosition(StartOfParagraphAlgorithm( |
+ visible_position.DeepEquivalent(), boundary_crossing_rule)); |
+} |
+ |
+template <typename Strategy> |
+PositionTemplate<Strategy> EndOfParagraphAlgorithm( |
+ const PositionTemplate<Strategy>& position, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ Node* const start_node = position.AnchorNode(); |
+ |
+ if (!start_node) |
+ return PositionTemplate<Strategy>(); |
+ |
+ if (IsRenderedAsNonInlineTableImageOrHR(start_node)) |
+ return PositionTemplate<Strategy>::AfterNode(start_node); |
+ |
+ Element* const start_block = EnclosingBlock( |
+ PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node), |
+ kCannotCrossEditingBoundary); |
+ ContainerNode* const highest_root = HighestEditableRoot(position); |
+ const bool start_node_is_editable = HasEditableStyle(*start_node); |
+ |
+ Node* candidate_node = start_node; |
+ PositionAnchorType candidate_type = position.AnchorType(); |
+ int candidate_offset = position.ComputeEditingOffset(); |
+ |
+ Node* next_node_iterator = start_node; |
+ while (next_node_iterator) { |
+ if (boundary_crossing_rule == kCannotCrossEditingBoundary && |
+ !NodeIsUserSelectAll(next_node_iterator) && |
+ HasEditableStyle(*next_node_iterator) != start_node_is_editable) |
+ break; |
+ if (boundary_crossing_rule == kCanSkipOverEditingBoundary) { |
+ while (next_node_iterator && |
+ HasEditableStyle(*next_node_iterator) != start_node_is_editable) |
+ next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
+ if (!next_node_iterator || |
+ !next_node_iterator->IsDescendantOf(highest_root)) |
+ break; |
+ } |
+ |
+ LayoutObject* const layout_object = next_node_iterator->GetLayoutObject(); |
+ if (!layout_object) { |
+ next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
+ continue; |
+ } |
+ const ComputedStyle& style = layout_object->StyleRef(); |
+ if (style.Visibility() != EVisibility::kVisible) { |
+ next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
+ continue; |
+ } |
+ |
+ if (layout_object->IsBR() || IsEnclosingBlock(next_node_iterator)) |
+ break; |
+ |
+ // TODO(editing-dev): We avoid returning a position where the layoutObject |
+ // can't accept the caret. |
+ if (layout_object->IsText() && |
+ ToLayoutText(layout_object)->ResolvedTextLength()) { |
+ SECURITY_DCHECK(next_node_iterator->IsTextNode()); |
+ LayoutText* const text = ToLayoutText(layout_object); |
+ if (style.PreserveNewline()) { |
+ const int length = ToLayoutText(layout_object)->TextLength(); |
+ for (int i = (next_node_iterator == start_node ? candidate_offset : 0); |
+ i < length; ++i) { |
+ if ((*text)[i] == '\n') { |
+ return PositionTemplate<Strategy>(ToText(next_node_iterator), |
+ i + text->TextStartOffset()); |
+ } |
+ } |
+ } |
+ |
+ candidate_node = next_node_iterator; |
+ candidate_type = PositionAnchorType::kOffsetInAnchor; |
+ candidate_offset = |
+ layout_object->CaretMaxOffset() + text->TextStartOffset(); |
+ next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
+ } else if (EditingIgnoresContent(*next_node_iterator) || |
+ IsDisplayInsideTable(next_node_iterator)) { |
+ candidate_node = next_node_iterator; |
+ candidate_type = PositionAnchorType::kAfterAnchor; |
+ next_node_iterator = |
+ Strategy::NextSkippingChildren(*next_node_iterator, start_block); |
+ } else { |
+ next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
+ } |
+ } |
+ |
+ if (candidate_type == PositionAnchorType::kOffsetInAnchor) |
+ return PositionTemplate<Strategy>(candidate_node, candidate_offset); |
+ |
+ return PositionTemplate<Strategy>(candidate_node, candidate_type); |
+} |
+ |
+template <typename Strategy> |
+VisiblePositionTemplate<Strategy> EndOfParagraphAlgorithm( |
+ const VisiblePositionTemplate<Strategy>& visible_position, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ DCHECK(visible_position.IsValid()) << visible_position; |
+ return CreateVisiblePosition(EndOfParagraphAlgorithm( |
+ visible_position.DeepEquivalent(), boundary_crossing_rule)); |
+} |
+ |
+template <typename Strategy> |
+bool IsStartOfParagraphAlgorithm( |
+ const VisiblePositionTemplate<Strategy>& pos, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ DCHECK(pos.IsValid()) << pos; |
+ return pos.IsNotNull() && |
+ pos.DeepEquivalent() == |
+ StartOfParagraph(pos, boundary_crossing_rule).DeepEquivalent(); |
+} |
+ |
+template <typename Strategy> |
+bool IsEndOfParagraphAlgorithm( |
+ const VisiblePositionTemplate<Strategy>& pos, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ DCHECK(pos.IsValid()) << pos; |
+ return pos.IsNotNull() && |
+ pos.DeepEquivalent() == |
+ EndOfParagraph(pos, boundary_crossing_rule).DeepEquivalent(); |
+} |
+ |
+} // namespace |
+ |
+VisiblePosition StartOfParagraph( |
+ const VisiblePosition& c, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return StartOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule); |
+} |
+ |
+VisiblePositionInFlatTree StartOfParagraph( |
+ const VisiblePositionInFlatTree& c, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return StartOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
+ c, boundary_crossing_rule); |
+} |
+ |
+VisiblePosition EndOfParagraph( |
+ const VisiblePosition& c, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return EndOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule); |
+} |
+ |
+VisiblePositionInFlatTree EndOfParagraph( |
+ const VisiblePositionInFlatTree& c, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return EndOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
+ c, boundary_crossing_rule); |
+} |
+ |
+// TODO(editing-dev): isStartOfParagraph(startOfNextParagraph(pos)) is not |
+// always true |
+VisiblePosition StartOfNextParagraph(const VisiblePosition& visible_position) { |
+ DCHECK(visible_position.IsValid()) << visible_position; |
+ VisiblePosition paragraph_end( |
+ EndOfParagraph(visible_position, kCanSkipOverEditingBoundary)); |
+ VisiblePosition after_paragraph_end( |
+ NextPositionOf(paragraph_end, kCannotCrossEditingBoundary)); |
+ // The position after the last position in the last cell of a table |
+ // is not the start of the next paragraph. |
+ if (TableElementJustBefore(after_paragraph_end)) |
+ return NextPositionOf(after_paragraph_end, kCannotCrossEditingBoundary); |
+ return after_paragraph_end; |
+} |
+ |
+// TODO(editing-dev): isStartOfParagraph(startOfNextParagraph(pos)) is not |
+// always true |
+bool InSameParagraph(const VisiblePosition& a, |
+ const VisiblePosition& b, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ DCHECK(a.IsValid()) << a; |
+ DCHECK(b.IsValid()) << b; |
+ return a.IsNotNull() && |
+ StartOfParagraph(a, boundary_crossing_rule).DeepEquivalent() == |
+ StartOfParagraph(b, boundary_crossing_rule).DeepEquivalent(); |
+} |
+ |
+bool IsStartOfParagraph(const VisiblePosition& pos, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return IsStartOfParagraphAlgorithm<EditingStrategy>(pos, |
+ boundary_crossing_rule); |
+} |
+ |
+bool IsStartOfParagraph(const VisiblePositionInFlatTree& pos, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return IsStartOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
+ pos, boundary_crossing_rule); |
+} |
+ |
+bool IsEndOfParagraph(const VisiblePosition& pos, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return IsEndOfParagraphAlgorithm<EditingStrategy>(pos, |
+ boundary_crossing_rule); |
+} |
+ |
+bool IsEndOfParagraph(const VisiblePositionInFlatTree& pos, |
+ EditingBoundaryCrossingRule boundary_crossing_rule) { |
+ return IsEndOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
+ pos, boundary_crossing_rule); |
+} |
+ |
+// TODO(editing-dev): We should move |PreviousParagraphPosition()| to |
+// "SelectionModifier.cpp" |
+VisiblePosition PreviousParagraphPosition(const VisiblePosition& p, |
+ LayoutUnit x) { |
+ DCHECK(p.IsValid()) << p; |
+ VisiblePosition pos = p; |
+ do { |
+ VisiblePosition n = PreviousLinePosition(pos, x); |
+ if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent()) |
+ break; |
+ pos = n; |
+ } while (InSameParagraph(p, pos)); |
+ return pos; |
+} |
+ |
+// TODO(editing-dev): We should move |NextParagraphPosition()| to |
+// "SelectionModifier.cpp" |
+VisiblePosition NextParagraphPosition(const VisiblePosition& p, LayoutUnit x) { |
+ DCHECK(p.IsValid()) << p; |
+ VisiblePosition pos = p; |
+ do { |
+ VisiblePosition n = NextLinePosition(pos, x); |
+ if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent()) |
+ break; |
+ pos = n; |
+ } while (InSameParagraph(p, pos)); |
+ return pos; |
+} |
+ |
+EphemeralRange ExpandToParagraphBoundary(const EphemeralRange& range) { |
+ const VisiblePosition& start = CreateVisiblePosition(range.StartPosition()); |
+ DCHECK(start.IsNotNull()) << range.StartPosition(); |
+ const Position& paragraph_start = StartOfParagraph(start).DeepEquivalent(); |
+ DCHECK(paragraph_start.IsNotNull()) << range.StartPosition(); |
+ |
+ const VisiblePosition& end = CreateVisiblePosition(range.EndPosition()); |
+ DCHECK(end.IsNotNull()) << range.EndPosition(); |
+ const Position& paragraph_end = EndOfParagraph(end).DeepEquivalent(); |
+ DCHECK(paragraph_end.IsNotNull()) << range.EndPosition(); |
+ |
+ // TODO(xiaochengh): There are some cases (crbug.com/640112) where we get |
+ // |paragraphStart > paragraphEnd|, which is the reason we cannot directly |
+ // return |EphemeralRange(paragraphStart, paragraphEnd)|. This is not |
+ // desired, though. We should do more investigation to ensure that why |
+ // |paragraphStart <= paragraphEnd| is violated. |
+ const Position& result_start = |
+ paragraph_start.IsNotNull() && paragraph_start <= range.StartPosition() |
+ ? paragraph_start |
+ : range.StartPosition(); |
+ const Position& result_end = |
+ paragraph_end.IsNotNull() && paragraph_end >= range.EndPosition() |
+ ? paragraph_end |
+ : range.EndPosition(); |
+ return EphemeralRange(result_start, result_end); |
+} |
+ |
+} // namespace blink |