blob: c446c8da348b0317d02f2816b33fd54c50f370a1 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 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_adjuster.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/position.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_selection.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
namespace blink {
namespace {
template <typename Strategy>
SelectionTemplate<Strategy> ComputeAdjustedSelection(
const SelectionTemplate<Strategy> selection,
const EphemeralRangeTemplate<Strategy>& range) {
if (range.StartPosition().CompareTo(range.EndPosition()) == 0) {
return typename SelectionTemplate<Strategy>::Builder()
.Collapse(selection.IsBaseFirst() ? range.StartPosition()
: range.EndPosition())
.Build();
}
if (selection.IsBaseFirst()) {
return typename SelectionTemplate<Strategy>::Builder()
.SetAsForwardSelection(range)
.Build();
}
return typename SelectionTemplate<Strategy>::Builder()
.SetAsBackwardSelection(range)
.Build();
}
bool IsEmptyTableCell(const Node* node) {
// Returns true IFF the passed in node is one of:
// .) a table cell with no children,
// .) a table cell with a single BR child, and which has no other child
// layoutObject, including :before and :after layoutObject
// .) the BR child of such a table cell
// Find rendered node
while (node && !node->GetLayoutObject())
node = node->parentNode();
if (!node)
return false;
// Make sure the rendered node is a table cell or <br>.
// If it's a <br>, then the parent node has to be a table cell.
const LayoutObject* layout_object = node->GetLayoutObject();
if (layout_object->IsBR()) {
layout_object = layout_object->Parent();
if (!layout_object)
return false;
}
if (!layout_object->IsTableCell())
return false;
// Check that the table cell contains no child layoutObjects except for
// perhaps a single <br>.
const LayoutObject* const child_layout_object =
layout_object->SlowFirstChild();
if (!child_layout_object)
return true;
if (!child_layout_object->IsBR())
return false;
return !child_layout_object->NextSibling();
}
} // anonymous namespace
class GranularityAdjuster final {
STATIC_ONLY(GranularityAdjuster);
public:
template <typename Strategy>
static PositionTemplate<Strategy> ComputeStartRespectingGranularityAlgorithm(
const PositionWithAffinityTemplate<Strategy>& passed_start,
TextGranularity granularity) {
DCHECK(passed_start.IsNotNull());
switch (granularity) {
case TextGranularity::kCharacter:
// Don't do any expansion.
return passed_start.GetPosition();
case TextGranularity::kWord: {
// 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
// (kPreviousWordIfOnBoundary).
// 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
// kNextWordIfOnBoundary);
const VisiblePositionTemplate<Strategy> visible_start =
CreateVisiblePosition(passed_start);
const PositionTemplate<Strategy> word_start = StartOfWordPosition(
passed_start.GetPosition(), ChooseWordSide(visible_start));
return CreateVisiblePosition(word_start).DeepEquivalent();
}
case TextGranularity::kSentence:
return StartOfSentencePosition(passed_start.GetPosition());
case TextGranularity::kLine:
return StartOfLine(CreateVisiblePosition(passed_start))
.DeepEquivalent();
case TextGranularity::kLineBoundary:
return StartOfLine(CreateVisiblePosition(passed_start))
.DeepEquivalent();
case TextGranularity::kParagraph: {
const VisiblePositionTemplate<Strategy> pos =
CreateVisiblePosition(passed_start);
if (IsStartOfLine(pos) && IsEndOfEditableOrNonEditableContent(pos))
return StartOfParagraph(PreviousPositionOf(pos)).DeepEquivalent();
return StartOfParagraph(pos).DeepEquivalent();
}
case TextGranularity::kDocumentBoundary:
return CreateVisiblePosition(
StartOfDocument(passed_start.GetPosition()))
.DeepEquivalent();
case TextGranularity::kParagraphBoundary:
return StartOfParagraph(CreateVisiblePosition(passed_start))
.DeepEquivalent();
case TextGranularity::kSentenceBoundary:
return StartOfSentencePosition(passed_start.GetPosition());
}
NOTREACHED();
return passed_start.GetPosition();
}
template <typename Strategy>
static PositionTemplate<Strategy> ComputeEndRespectingGranularityAlgorithm(
const PositionTemplate<Strategy>& start,
const PositionWithAffinityTemplate<Strategy>& passed_end,
TextGranularity granularity) {
DCHECK(passed_end.IsNotNull());
switch (granularity) {
case TextGranularity::kCharacter:
// Don't do any expansion.
return passed_end.GetPosition();
case TextGranularity::kWord: {
// 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
// (|kPreviousWordIfOnBoundary|).
// 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
// |kNextWordIfOnBoundary|);
const VisiblePositionTemplate<Strategy> original_end =
CreateVisiblePosition(passed_end);
bool is_end_of_paragraph = IsEndOfParagraph(original_end);
// Get last word of paragraph. If original_end is already known to be
// the last word, use that. If not the last word, find it with
// EndOfWordPosition
const VisiblePositionTemplate<Strategy> word_end =
is_end_of_paragraph
? original_end
: CreateVisiblePosition(EndOfWordPosition(
passed_end.GetPosition(), ChooseWordSide(original_end)));
if (!is_end_of_paragraph)
return word_end.DeepEquivalent();
if (IsEmptyTableCell(start.AnchorNode()))
return word_end.DeepEquivalent();
// If the end was in a table cell, we don't want the \t from between
// cells or \n after the row, so return last word
if (EnclosingTableCell(original_end.DeepEquivalent()))
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 VisiblePositionTemplate<Strategy> 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 VisiblePositionTemplate<Strategy> next =
NextPositionOf(end, kCannotCrossEditingBoundary);
if (next.IsNull())
return word_end.DeepEquivalent();
return next.DeepEquivalent();
}
case TextGranularity::kSentence:
return EndOfSentence(CreateVisiblePosition(passed_end))
.DeepEquivalent();
case TextGranularity::kLine: {
const VisiblePositionTemplate<Strategy> end =
CreateVisiblePosition(EndOfLine(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 VisiblePositionTemplate<Strategy> next = NextPositionOf(end);
if (next.IsNull())
return end.DeepEquivalent();
return next.DeepEquivalent();
}
case TextGranularity::kLineBoundary:
return EndOfLine(passed_end).GetPosition();
case TextGranularity::kParagraph: {
const VisiblePositionTemplate<Strategy> 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 VisiblePositionTemplate<Strategy> 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 VisiblePositionTemplate<Strategy> next =
NextPositionOf(end, kCannotCrossEditingBoundary);
if (next.IsNull())
return visible_paragraph_end.DeepEquivalent();
return next.DeepEquivalent();
}
case TextGranularity::kDocumentBoundary:
return EndOfDocument(CreateVisiblePosition(passed_end))
.DeepEquivalent();
case TextGranularity::kParagraphBoundary:
return EndOfParagraph(CreateVisiblePosition(passed_end))
.DeepEquivalent();
case TextGranularity::kSentenceBoundary:
return EndOfSentence(CreateVisiblePosition(passed_end))
.DeepEquivalent();
}
NOTREACHED();
return passed_end.GetPosition();
}
template <typename Strategy>
static SelectionTemplate<Strategy> AdjustSelection(
const SelectionTemplate<Strategy>& canonicalized_selection,
TextGranularity granularity) {
const TextAffinity affinity = canonicalized_selection.Affinity();
const PositionTemplate<Strategy> start =
canonicalized_selection.ComputeStartPosition();
const PositionTemplate<Strategy> new_start =
ComputeStartRespectingGranularityAlgorithm(
PositionWithAffinityTemplate<Strategy>(start, affinity),
granularity);
const PositionTemplate<Strategy> expanded_start =
new_start.IsNotNull() ? new_start : start;
const PositionTemplate<Strategy> end =
canonicalized_selection.ComputeEndPosition();
const PositionTemplate<Strategy> new_end =
ComputeEndRespectingGranularityAlgorithm(
expanded_start,
PositionWithAffinityTemplate<Strategy>(end, affinity), granularity);
const PositionTemplate<Strategy> expanded_end =
new_end.IsNotNull() ? new_end : end;
const EphemeralRangeTemplate<Strategy> expanded_range =
AdjustStartAndEnd(expanded_start, expanded_end);
return ComputeAdjustedSelection(canonicalized_selection, expanded_range);
}
private:
template <typename Strategy>
static WordSide ChooseWordSide(
const VisiblePositionTemplate<Strategy>& position) {
return IsEndOfEditableOrNonEditableContent(position) ||
(IsEndOfLine(position) && !IsStartOfLine(position) &&
!IsEndOfParagraph(position))
? kPreviousWordIfOnBoundary
: kNextWordIfOnBoundary;
}
// Because of expansion is done in flat tree, in case of |start| and |end| are
// distributed, |start| can be after |end|.
static EphemeralRange AdjustStartAndEnd(const Position& start,
const Position& end) {
if (start <= end)
return EphemeralRange(start, end);
return EphemeralRange(end, start);
}
static EphemeralRangeInFlatTree AdjustStartAndEnd(
const PositionInFlatTree& start,
const PositionInFlatTree& end) {
return EphemeralRangeInFlatTree(start, end);
}
};
PositionInFlatTree ComputeStartRespectingGranularity(
const PositionInFlatTreeWithAffinity& start,
TextGranularity granularity) {
return GranularityAdjuster::ComputeStartRespectingGranularityAlgorithm(
start, granularity);
}
PositionInFlatTree ComputeEndRespectingGranularity(
const PositionInFlatTree& start,
const PositionInFlatTreeWithAffinity& end,
TextGranularity granularity) {
return GranularityAdjuster::ComputeEndRespectingGranularityAlgorithm(
start, end, granularity);
}
SelectionInDOMTree SelectionAdjuster::AdjustSelectionRespectingGranularity(
const SelectionInDOMTree& selection,
TextGranularity granularity) {
return GranularityAdjuster::AdjustSelection(selection, granularity);
}
SelectionInFlatTree SelectionAdjuster::AdjustSelectionRespectingGranularity(
const SelectionInFlatTree& selection,
TextGranularity granularity) {
return GranularityAdjuster::AdjustSelection(selection, granularity);
}
class ShadowBoundaryAdjuster final {
STATIC_ONLY(ShadowBoundaryAdjuster);
public:
template <typename Strategy>
static SelectionTemplate<Strategy> AdjustSelection(
const SelectionTemplate<Strategy>& selection) {
if (!selection.IsRange())
return selection;
const EphemeralRangeTemplate<Strategy> expanded_range =
selection.ComputeRange();
const EphemeralRangeTemplate<Strategy> shadow_adjusted_range =
selection.IsBaseFirst()
? EphemeralRangeTemplate<Strategy>(
expanded_range.StartPosition(),
AdjustSelectionEndToAvoidCrossingShadowBoundaries(
expanded_range))
: EphemeralRangeTemplate<Strategy>(
AdjustSelectionStartToAvoidCrossingShadowBoundaries(
expanded_range),
expanded_range.EndPosition());
return ComputeAdjustedSelection(selection, shadow_adjusted_range);
}
private:
static Node* EnclosingShadowHost(Node* node) {
for (Node* runner = node; runner;
runner = FlatTreeTraversal::Parent(*runner)) {
if (IsShadowHost(runner))
return runner;
}
return nullptr;
}
static bool IsEnclosedBy(const PositionInFlatTree& position,
const Node& node) {
DCHECK(position.IsNotNull());
Node* anchor_node = position.AnchorNode();
if (anchor_node == node)
return !position.IsAfterAnchor() && !position.IsBeforeAnchor();
return FlatTreeTraversal::IsDescendantOf(*anchor_node, node);
}
static Node* EnclosingShadowHostForStart(const PositionInFlatTree& position) {
Node* node = position.NodeAsRangeFirstNode();
if (!node)
return nullptr;
Node* shadow_host = EnclosingShadowHost(node);
if (!shadow_host)
return nullptr;
if (!IsEnclosedBy(position, *shadow_host))
return nullptr;
return IsUserSelectContain(*shadow_host) ? shadow_host : nullptr;
}
static Node* EnclosingShadowHostForEnd(const PositionInFlatTree& position) {
Node* node = position.NodeAsRangeLastNode();
if (!node)
return nullptr;
Node* shadow_host = EnclosingShadowHost(node);
if (!shadow_host)
return nullptr;
if (!IsEnclosedBy(position, *shadow_host))
return nullptr;
return IsUserSelectContain(*shadow_host) ? shadow_host : nullptr;
}
static PositionInFlatTree AdjustPositionInFlatTreeForStart(
const PositionInFlatTree& position,
Node* shadow_host) {
if (IsEnclosedBy(position, *shadow_host)) {
if (position.IsBeforeChildren())
return PositionInFlatTree::BeforeNode(*shadow_host);
return PositionInFlatTree::AfterNode(*shadow_host);
}
// We use |firstChild|'s after instead of beforeAllChildren for backward
// compatibility. The positions are same but the anchors would be different,
// and selection painting uses anchor nodes.
if (Node* first_child = FlatTreeTraversal::FirstChild(*shadow_host))
return PositionInFlatTree::BeforeNode(*first_child);
return PositionInFlatTree();
}
static Position AdjustPositionForEnd(const Position& current_position,
Node* start_container_node) {
TreeScope& tree_scope = start_container_node->GetTreeScope();
DCHECK(current_position.ComputeContainerNode()->GetTreeScope() !=
tree_scope);
if (Node* ancestor = tree_scope.AncestorInThisScope(
current_position.ComputeContainerNode())) {
if (ancestor->contains(start_container_node))
return Position::AfterNode(*ancestor);
return Position::BeforeNode(*ancestor);
}
if (Node* last_child = tree_scope.RootNode().lastChild())
return Position::AfterNode(*last_child);
return Position();
}
static PositionInFlatTree AdjustPositionInFlatTreeForEnd(
const PositionInFlatTree& position,
Node* shadow_host) {
if (IsEnclosedBy(position, *shadow_host)) {
if (position.IsAfterChildren())
return PositionInFlatTree::AfterNode(*shadow_host);
return PositionInFlatTree::BeforeNode(*shadow_host);
}
// We use |lastChild|'s after instead of afterAllChildren for backward
// compatibility. The positions are same but the anchors would be different,
// and selection painting uses anchor nodes.
if (Node* last_child = FlatTreeTraversal::LastChild(*shadow_host))
return PositionInFlatTree::AfterNode(*last_child);
return PositionInFlatTree();
}
static Position AdjustPositionForStart(const Position& current_position,
Node* end_container_node) {
TreeScope& tree_scope = end_container_node->GetTreeScope();
DCHECK(current_position.ComputeContainerNode()->GetTreeScope() !=
tree_scope);
if (Node* ancestor = tree_scope.AncestorInThisScope(
current_position.ComputeContainerNode())) {
if (ancestor->contains(end_container_node))
return Position::BeforeNode(*ancestor);
return Position::AfterNode(*ancestor);
}
if (Node* first_child = tree_scope.RootNode().firstChild())
return Position::BeforeNode(*first_child);
return Position();
}
// TODO(hajimehoshi): Checking treeScope is wrong when a node is
// distributed, but we leave it as it is for backward compatibility.
static bool IsCrossingShadowBoundaries(const EphemeralRange& range) {
DCHECK(range.IsNotNull());
return range.StartPosition().AnchorNode()->GetTreeScope() !=
range.EndPosition().AnchorNode()->GetTreeScope();
}
static Position AdjustSelectionStartToAvoidCrossingShadowBoundaries(
const EphemeralRange& range) {
DCHECK(range.IsNotNull());
if (!IsCrossingShadowBoundaries(range))
return range.StartPosition();
return AdjustPositionForStart(range.StartPosition(),
range.EndPosition().ComputeContainerNode());
}
static Position AdjustSelectionEndToAvoidCrossingShadowBoundaries(
const EphemeralRange& range) {
DCHECK(range.IsNotNull());
if (!IsCrossingShadowBoundaries(range))
return range.EndPosition();
return AdjustPositionForEnd(range.EndPosition(),
range.StartPosition().ComputeContainerNode());
}
static PositionInFlatTree AdjustSelectionStartToAvoidCrossingShadowBoundaries(
const EphemeralRangeInFlatTree& range) {
Node* const shadow_host_start =
EnclosingShadowHostForStart(range.StartPosition());
Node* const shadow_host_end =
EnclosingShadowHostForEnd(range.EndPosition());
if (shadow_host_start == shadow_host_end)
return range.StartPosition();
Node* const shadow_host =
shadow_host_end ? shadow_host_end : shadow_host_start;
return AdjustPositionInFlatTreeForStart(range.StartPosition(), shadow_host);
}
static PositionInFlatTree AdjustSelectionEndToAvoidCrossingShadowBoundaries(
const EphemeralRangeInFlatTree& range) {
Node* const shadow_host_start =
EnclosingShadowHostForStart(range.StartPosition());
Node* const shadow_host_end =
EnclosingShadowHostForEnd(range.EndPosition());
if (shadow_host_start == shadow_host_end)
return range.EndPosition();
Node* const shadow_host =
shadow_host_start ? shadow_host_start : shadow_host_end;
return AdjustPositionInFlatTreeForEnd(range.EndPosition(), shadow_host);
}
};
SelectionInDOMTree
SelectionAdjuster::AdjustSelectionToAvoidCrossingShadowBoundaries(
const SelectionInDOMTree& selection) {
return ShadowBoundaryAdjuster::AdjustSelection(selection);
}
SelectionInFlatTree
SelectionAdjuster::AdjustSelectionToAvoidCrossingShadowBoundaries(
const SelectionInFlatTree& selection) {
return ShadowBoundaryAdjuster::AdjustSelection(selection);
}
class EditingBoundaryAdjuster final {
STATIC_ONLY(EditingBoundaryAdjuster);
public:
template <typename Strategy>
static SelectionTemplate<Strategy> AdjustSelection(
const SelectionTemplate<Strategy>& selection) {
const auto adjusted = AdjustExtent(selection);
// TODO(editing-dev): This DCHECK now fails on crossing <body> selection.
// Test ApplyBlockElementCommandTest.selectionCrossingOverBody has base
// outside of <body> and extent inside of <body>, after adjustment, new
// extent is still inside of <body>, so RBE is not the same.
// DCHECK_EQ(
// &RootBoundaryElementOf<Strategy>(
// *selection.Base().ComputeContainerNode()),
// &RootBoundaryElementOf<Strategy>(*adjusted.ComputeContainerNode()))
// << std::endl
// << selection << std::endl
// << adjusted;
return typename SelectionTemplate<Strategy>::Builder(selection)
.Extend(adjusted)
.Build();
}
private:
template <typename Strategy>
static bool IsEditingBoundary(const Node& node,
const Node& previous_node,
bool is_previous_node_editable) {
return HasEditableStyle(node) != is_previous_node_editable;
}
// Returns the highest ancestor of |start| along the parent chain, so that
// all node in between them including the ancestor have the same
// HasEditableStyle() bit with |start|. Note that it only consider the <body>
// subtree.
template <typename Strategy>
static const Node& RootBoundaryElementOf(const Node& start) {
if (IsA<HTMLBodyElement>(start))
return start;
const bool is_editable = HasEditableStyle(start);
const Node* result = &start;
for (const Node& ancestor : Strategy::AncestorsOf(start)) {
if (IsEditingBoundary<Strategy>(ancestor, *result, is_editable))
break;
result = &ancestor;
if (IsA<HTMLBodyElement>(*result))
break;
}
return *result;
}
// TODO(editing-dev): The input |selection| for this function might cross
// shadow boundary in DOM tree in flat tree selection case. We still want to
// adjust the selection on DOM tree since currently editibility is defined on
// DOM tree according to spec, so we need to deal with shadow boundary in this
// function for flat tree selection. We ended with no good way but just
// templated the DOM tree algorithm including |RootBoundaryElementOf()| for
// flat tree.
template <typename Strategy>
static PositionTemplate<Strategy> AdjustExtent(
const SelectionTemplate<Strategy>& selection) {
DCHECK(!selection.IsNone()) << selection;
const Node* const base_node = selection.Base().ComputeContainerNode();
const Node* const extent_node = selection.Extent().ComputeContainerNode();
// In the same node, no need to adjust.
if (base_node == extent_node)
return selection.Extent();
const Node& base_rbe = RootBoundaryElementOf<Strategy>(*base_node);
const Node& extent_rbe = RootBoundaryElementOf<Strategy>(*extent_node);
// In the same RBE, no need to adjust.
if (base_rbe == extent_rbe)
return selection.Extent();
// |extent_rbe| is not in |base_rbe| subtree, in this case, the result
// should be the first/last position in the |base_rbe| subtree.
if (!Strategy::IsDescendantOf(extent_rbe, base_rbe)) {
if (selection.IsBaseFirst())
return PositionTemplate<Strategy>::LastPositionInNode(base_rbe);
return PositionTemplate<Strategy>::FirstPositionInNode(base_rbe);
}
// |extent_rbe| is in |base_rbe| subtree. We want to find the last boundary
// the selection crossed from extent. Which is the highest ancestor node of
// extent in |base_rbe| subtree that RBE(ancestor) != |base_rbe|.
const Node* boundary = &extent_rbe;
const Node* previous_ancestor = &extent_rbe;
bool previous_editable = HasEditableStyle(extent_rbe);
for (const Node& ancestor : Strategy::AncestorsOf(extent_rbe)) {
if (IsEditingBoundary<Strategy>(ancestor, *previous_ancestor,
previous_editable))
boundary = previous_ancestor;
if (ancestor == base_rbe || IsA<HTMLBodyElement>(ancestor))
break;
previous_editable = HasEditableStyle(ancestor);
previous_ancestor = &ancestor;
}
if (selection.IsBaseFirst())
return PositionTemplate<Strategy>::BeforeNode(*boundary);
return PositionTemplate<Strategy>::AfterNode(*boundary);
}
};
template <>
inline bool
EditingBoundaryAdjuster::IsEditingBoundary<EditingInFlatTreeStrategy>(
const Node& node,
const Node& previous_node,
bool is_previous_node_editable) {
// We want to treat shadow host as not editable element if |previous_node|
// is in the shadow tree attached to the shadow host.
if (IsShadowHost(&node) && is_previous_node_editable &&
previous_node.OwnerShadowHost() == &node)
return true;
return HasEditableStyle(node) != is_previous_node_editable;
}
SelectionInDOMTree
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
const SelectionInDOMTree& selection) {
return EditingBoundaryAdjuster::AdjustSelection(selection);
}
SelectionInFlatTree
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
const SelectionInFlatTree& selection) {
return EditingBoundaryAdjuster::AdjustSelection(selection);
}
class SelectionTypeAdjuster final {
STATIC_ONLY(SelectionTypeAdjuster);
public:
template <typename Strategy>
static SelectionTemplate<Strategy> AdjustSelection(
const SelectionTemplate<Strategy>& selection) {
if (selection.IsNone())
return selection;
const EphemeralRangeTemplate<Strategy>& range = selection.ComputeRange();
DCHECK(!NeedsLayoutTreeUpdate(range.StartPosition())) << range;
if (range.IsCollapsed() ||
// TODO(editing-dev): Consider this canonicalization is really needed.
MostBackwardCaretPosition(range.StartPosition()) ==
MostBackwardCaretPosition(range.EndPosition())) {
return typename SelectionTemplate<Strategy>::Builder()
.Collapse(PositionWithAffinityTemplate<Strategy>(
range.StartPosition(), selection.Affinity()))
.Build();
}
// "Constrain" the selection to be the smallest equivalent range of
// nodes. This is a somewhat arbitrary choice, but experience shows that
// it is useful to make to make the selection "canonical" (if only for
// purposes of comparing selections). This is an ideal point of the code
// to do this operation, since all selection changes that result in a
// RANGE come through here before anyone uses it.
// TODO(editing-dev): Consider this canonicalization is really needed.
const EphemeralRangeTemplate<Strategy> minimal_range(
MostForwardCaretPosition(range.StartPosition()),
MostBackwardCaretPosition(range.EndPosition()));
if (minimal_range.IsCollapsed() || selection.IsBaseFirst()) {
return typename SelectionTemplate<Strategy>::Builder()
.SetAsForwardSelection(minimal_range)
.Build();
}
return typename SelectionTemplate<Strategy>::Builder()
.SetAsBackwardSelection(minimal_range)
.Build();
}
};
SelectionInDOMTree SelectionAdjuster::AdjustSelectionType(
const SelectionInDOMTree& selection) {
return SelectionTypeAdjuster::AdjustSelection(selection);
}
SelectionInFlatTree SelectionAdjuster::AdjustSelectionType(
const SelectionInFlatTree& selection) {
return SelectionTypeAdjuster::AdjustSelection(selection);
}
} // namespace blink