Commit 226a83f9 authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

Remove NGCaretNavigator

This is a follow-up of changing left/right caret movements from visual
to logical. As NGCaretNavigator is designed for visual caret movements,
it no longer serves any purpose here and hence is also removed.

Bug: 958831
Change-Id: I2b18cf81ca9a997851e05617927c608b4831abbe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1602975Reviewed-by: default avatarEmil A Eklund <eae@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#658277}
parent 643463d3
......@@ -1982,7 +1982,6 @@ jumbo_source_set("unit_tests") {
"layout/ng/exclusions/ng_exclusion_space_test.cc",
"layout/ng/geometry/ng_box_strut_test.cc",
"layout/ng/inline/ng_baseline_test.cc",
"layout/ng/inline/ng_caret_navigator_test.cc",
"layout/ng/inline/ng_caret_position_test.cc",
"layout/ng/inline/ng_inline_fragment_traversal_test.cc",
"layout/ng/inline/ng_inline_items_builder_test.cc",
......
......@@ -31,14 +31,13 @@
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/local_caret_rect.h"
#include "third_party/blink/renderer/core/editing/ng_flat_tree_shorthands.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/geometry/float_quad.h"
......@@ -100,36 +99,10 @@ VisiblePositionTemplate<Strategy> VisiblePositionTemplate<Strategy>::Create(
const PositionWithAffinityTemplate<Strategy> upstream_position(
deep_position, TextAffinity::kUpstream);
if (!InSameLine(downstream_position, upstream_position))
if (AbsoluteCaretBoundsOf(downstream_position) !=
AbsoluteCaretBoundsOf(upstream_position)) {
return VisiblePositionTemplate<Strategy>(upstream_position);
if (!NGOffsetMapping::AcceptsPosition(ToPositionInDOMTree(deep_position))) {
// editing/selection/mixed-editability-10.html reaches here.
// We can't check bidi in such case. Use downstream as the default.
// TODO(xiaochengh): Investigate why we reach here and how to work around.
return VisiblePositionTemplate<Strategy>(downstream_position);
}
// Check if the position is at bidi boundary.
const LayoutObject* layout_object =
deep_position.AnchorNode()->GetLayoutObject();
DCHECK(layout_object) << position_with_affinity;
if (!layout_object->IsInline())
return VisiblePositionTemplate<Strategy>(downstream_position);
LayoutBlockFlow* const context =
NGOffsetMapping::GetInlineFormattingContextOf(*layout_object);
DCHECK(context);
DCHECK(context->IsLayoutNGMixin());
const NGOffsetMapping* mapping = NGInlineNode::GetOffsetMapping(context);
DCHECK(mapping);
const base::Optional<unsigned> offset =
mapping->GetTextContentOffset(ToPositionInDOMTree(deep_position));
DCHECK(offset.has_value());
if (NGCaretNavigator(*context).OffsetIsBidiBoundary(offset.value()))
return VisiblePositionTemplate<Strategy>(upstream_position);
return VisiblePositionTemplate<Strategy>(downstream_position);
}
......
......@@ -329,8 +329,6 @@ blink_core_sources("layout") {
"ng/inline/ng_baseline.h",
"ng/inline/ng_bidi_paragraph.cc",
"ng/inline/ng_bidi_paragraph.h",
"ng/inline/ng_caret_navigator.cc",
"ng/inline/ng_caret_navigator.h",
"ng/inline/ng_caret_position.cc",
"ng/inline/ng_caret_position.h",
"ng/inline/ng_caret_rect.cc",
......
......@@ -309,11 +309,6 @@ In a bird's‐eye view, it consists of two parts:
content of an inline formatting context (computed in [pre-layout]) and DOM
positions in the context. See [design doc](https://goo.gl/CJbxky) for details.
[NGCaretNavigator] provides functions for inspecting bidi levels and visual
ordering of text content, and supports visual left/right caret movements in the
text. See [design doc](http://bit.ly/2QVAwGq) for details.
[ICU BiDi]: http://userguide.icu-project.org/transforms/bidi
[UAX#9 Unicode Bidirectional Algorithm]: http://unicode.org/reports/tr9/
[UAX#9 Resolving Embedding Levels]: http://www.unicode.org/reports/tr9/#Resolving_Embedding_Levels
......@@ -322,7 +317,6 @@ text. See [design doc](http://bit.ly/2QVAwGq) for details.
[FontBaseline]: ../../../platform/fonts/FontBaseline.h
[NGBaselineAlgorithmType]: ng_baseline.h
[NGBaselineRequest]: ng_baseline.h
[NGCaretNavigator]: ng_caret_navigator.h
[NGBidiParagraph]: ng_bidi_paragraph.h
[NGBlockNode]: ../ng_block_node.h
[NGBoxFragment]: ../ng_box_fragment.h
......
// Copyright 2018 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 "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_bidi_paragraph.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
namespace blink {
std::ostream& operator<<(std::ostream& out,
const NGCaretNavigator::Position& position) {
return out << position.index << "/"
<< (position.IsBeforeCharacter() ? "BeforeCharacter"
: "AfterCharacter");
}
NGCaretNavigator::~NGCaretNavigator() = default;
NGCaretNavigator::NGCaretNavigator(const LayoutBlockFlow& context)
: context_(context),
disallow_transition_(context.GetDocument().Lifecycle()) {
DCHECK(RuntimeEnabledFeatures::BidiCaretAffinityEnabled());
DCHECK(context.IsLayoutNGMixin());
DCHECK(context.ChildrenInline());
DCHECK(context.GetNGInlineNodeData());
DCHECK(!context.GetDocument().NeedsLayoutTreeUpdate());
}
const NGInlineNodeData& NGCaretNavigator::GetData() const {
return *context_.GetNGInlineNodeData();
}
const String& NGCaretNavigator::GetText() const {
return GetData().text_content;
}
bool NGCaretNavigator::IsBidiEnabled() const {
return GetData().IsBidiEnabled();
}
UBiDiLevel NGCaretNavigator::BidiLevelAt(unsigned index) const {
DCHECK_LT(index, GetText().length());
if (!IsBidiEnabled())
return 0;
return GetData().FindItemForTextOffset(index).BidiLevel();
}
TextDirection NGCaretNavigator::TextDirectionAt(unsigned index) const {
UBiDiLevel level = BidiLevelAt(index);
return DirectionFromLevel(level);
}
bool NGCaretNavigator::OffsetIsBidiBoundary(unsigned offset) const {
DCHECK_LE(offset, GetText().length());
if (!IsBidiEnabled())
return false;
if (!offset || offset == GetText().length())
return false;
return BidiLevelAt(offset - 1) != BidiLevelAt(offset);
}
NGCaretNavigator::Position
NGCaretNavigator::CaretPositionFromTextContentOffsetAndAffinity(
unsigned offset,
TextAffinity affinity) const {
DCHECK_LE(offset, GetText().length());
// Callers sometimes pass in (0, upstream) or (length, downstream), which
// originate from legacy callers. Make sure they are fixed up.
// TODO(xiaochengh): Catch and eliminate such callers.
if (affinity == TextAffinity::kUpstream) {
if (offset)
return {offset - 1, PositionAnchorType::kAfter};
return {0, PositionAnchorType::kBefore};
}
if (offset < GetText().length())
return {offset, PositionAnchorType::kBefore};
return {GetText().length() - 1, PositionAnchorType::kAfter};
}
// static
NGCaretNavigator::MoveDirection NGCaretNavigator::OppositeDirectionOf(
MoveDirection direction) {
if (direction == MoveDirection::kTowardsLeft)
return MoveDirection::kTowardsRight;
return MoveDirection::kTowardsLeft;
}
// static
bool NGCaretNavigator::TowardsSameDirection(MoveDirection move_direction,
TextDirection text_direction) {
if (IsLtr(text_direction))
return move_direction == MoveDirection::kTowardsRight;
return move_direction == MoveDirection::kTowardsLeft;
}
NGCaretNavigator::Line NGCaretNavigator::ContainingLineOf(
unsigned index) const {
DCHECK_LT(index, GetText().length());
// TODO(xiaochengh): Make it work for multi-col
DCHECK(context_.CurrentFragment());
unsigned last_line_end = 0;
for (const auto child : context_.CurrentFragment()->Children()) {
if (!child->IsLineBox())
continue;
const auto* line = To<NGPhysicalLineBoxFragment>(child.get());
const auto* token = To<NGInlineBreakToken>(line->BreakToken());
const unsigned line_end =
token->IsFinished() ? GetText().length() : token->TextOffset();
if (line_end > index)
return {last_line_end, line_end, line->BaseDirection()};
last_line_end = line_end;
}
NOTREACHED();
return {};
}
bool NGCaretNavigator::IsValidCaretPosition(const Position& position) const {
unsigned index = position.index;
if (position.IsAfterCharacter() && IsLineBreak(index))
return false;
if (IsCollapsedSpaceByLineWrap(index))
return false;
if (IsIgnoredInCaretMovement(index))
return false;
return true;
}
bool NGCaretNavigator::IsCollapsibleWhitespace(unsigned index) const {
DCHECK_LT(index, GetText().length());
if (GetText()[index] != kSpaceCharacter)
return false;
const NGInlineItem& item = GetData().FindItemForTextOffset(index);
return item.Style()->CollapseWhiteSpace();
}
bool NGCaretNavigator::IsLineBreak(unsigned index) const {
DCHECK_LT(index, GetText().length());
return GetText()[index] == kNewlineCharacter;
}
bool NGCaretNavigator::IsCollapsedSpaceByLineWrap(unsigned index) const {
DCHECK_LT(index, GetText().length());
if (!IsCollapsibleWhitespace(index))
return false;
return index + 1 == ContainingLineOf(index).end_offset;
}
bool NGCaretNavigator::IsIgnoredInCaretMovement(unsigned index) const {
DCHECK_LT(index, GetText().length());
const NGInlineItem& item = GetData().FindItemForTextOffset(index);
// Caret navigation works on text, atomic inlines and non-ZWS controls only.
switch (item.Type()) {
case NGInlineItem::kText:
case NGInlineItem::kAtomicInline:
break;
case NGInlineItem::kControl:
if (GetText()[index] == kZeroWidthSpaceCharacter)
return true;
break;
default:
return true;
}
// Ignore CSS generate contents.
// TODO(xiaochengh): This might be general enough to be merged into
// |NGInlineItem| as a member function.
DCHECK(item.GetLayoutObject());
const LayoutObject* object = item.GetLayoutObject();
if (const auto* text_fragment = ToLayoutTextFragmentOrNull(object)) {
// ::first-letter |LayoutTextFragment| returns null for |GetNode()|. Check
// |AssociatedTextNode()| to see if it's created by a text node.
if (!text_fragment->AssociatedTextNode())
return true;
} else {
if (!object->NonPseudoNode())
return true;
}
// Ignore collapsed whitespaces that not visually at line end due to bidi.
// Caret movement should move over them as if they don't exist to match the
// existing behavior.
return IsCollapsedSpaceByLineWrap(index) &&
index != VisualLastCharacterOf(ContainingLineOf(index));
}
bool NGCaretNavigator::IsEnterableChildContext(unsigned index) const {
DCHECK_LT(index, GetText().length());
if (GetText()[index] != kObjectReplacementCharacter)
return false;
const NGInlineItem& item = GetData().FindItemForTextOffset(index);
if (item.Type() != NGInlineItem::kAtomicInline)
return false;
DCHECK(item.GetLayoutObject());
const LayoutObject* object = item.GetLayoutObject();
if (!object->IsLayoutBlockFlow())
return false;
if (!object->NonPseudoNode() || !object->GetNode()->IsElementNode())
return false;
const Element* node = ToElement(object->GetNode());
return !node->GetShadowRoot() || !node->GetShadowRoot()->IsUserAgent();
}
NGCaretNavigator::Position NGCaretNavigator::LeftEdgeOf(unsigned index) const {
return EdgeOfInternal(index, MoveDirection::kTowardsLeft);
}
NGCaretNavigator::Position NGCaretNavigator::RightEdgeOf(unsigned index) const {
return EdgeOfInternal(index, MoveDirection::kTowardsRight);
}
NGCaretNavigator::Position NGCaretNavigator::EdgeOfInternal(
unsigned index,
MoveDirection edge_direction) const {
DCHECK_LT(index, GetText().length());
const TextDirection character_direction = TextDirectionAt(index);
return {index, TowardsSameDirection(edge_direction, character_direction)
? PositionAnchorType::kAfter
: PositionAnchorType::kBefore};
}
NGCaretNavigator::VisualCharacterMovementResult
NGCaretNavigator::LeftCharacterOf(unsigned index) const {
return MoveCharacterInternal(index, MoveDirection::kTowardsLeft);
}
NGCaretNavigator::VisualCharacterMovementResult
NGCaretNavigator::RightCharacterOf(unsigned index) const {
return MoveCharacterInternal(index, MoveDirection::kTowardsRight);
}
Vector<int32_t, 32> NGCaretNavigator::CharacterIndicesInVisualOrder(
const Line& line) const {
DCHECK(IsBidiEnabled());
Vector<UBiDiLevel, 32> levels;
levels.ReserveCapacity(line.end_offset - line.start_offset);
for (unsigned i = line.start_offset; i < line.end_offset; ++i)
levels.push_back(BidiLevelAt(i));
Vector<int32_t, 32> indices(levels.size());
NGBidiParagraph::IndicesInVisualOrder(levels, &indices);
for (auto& index : indices)
index += line.start_offset;
return indices;
}
unsigned NGCaretNavigator::VisualMostForwardCharacterOf(
const Line& line,
MoveDirection direction) const {
if (!IsBidiEnabled()) {
if (direction == MoveDirection::kTowardsLeft)
return line.start_offset;
return line.end_offset - 1;
}
const auto indices_in_visual_order = CharacterIndicesInVisualOrder(line);
if (direction == MoveDirection::kTowardsLeft)
return indices_in_visual_order.front();
return indices_in_visual_order.back();
}
unsigned NGCaretNavigator::VisualFirstCharacterOf(const Line& line) const {
return VisualMostForwardCharacterOf(line, IsLtr(line.base_direction)
? MoveDirection::kTowardsLeft
: MoveDirection::kTowardsRight);
}
unsigned NGCaretNavigator::VisualLastCharacterOf(const Line& line) const {
return VisualMostForwardCharacterOf(line, IsLtr(line.base_direction)
? MoveDirection::kTowardsRight
: MoveDirection::kTowardsLeft);
}
NGCaretNavigator::VisualCharacterMovementResult
NGCaretNavigator::MoveCharacterInternal(unsigned index,
MoveDirection move_direction) const {
const Line line = ContainingLineOf(index);
if (index == VisualMostForwardCharacterOf(line, move_direction)) {
if (TowardsSameDirection(move_direction, line.base_direction)) {
if (line.end_offset == GetText().length())
return {VisualMovementResultType::kAfterContext, base::nullopt};
const Line next_line = ContainingLineOf(line.end_offset);
return {VisualMovementResultType::kWithinContext,
VisualFirstCharacterOf(next_line)};
}
if (!line.start_offset)
return {VisualMovementResultType::kBeforeContext, base::nullopt};
const Line last_line = ContainingLineOf(line.start_offset - 1);
return {VisualMovementResultType::kWithinContext,
VisualLastCharacterOf(last_line)};
}
if (!IsBidiEnabled()) {
if (move_direction == MoveDirection::kTowardsLeft)
return {VisualMovementResultType::kWithinContext, index - 1};
return {VisualMovementResultType::kWithinContext, index + 1};
}
Vector<int32_t, 32> indices_in_visual_order =
CharacterIndicesInVisualOrder(line);
const int32_t* visual_location = std::find(
indices_in_visual_order.begin(), indices_in_visual_order.end(), index);
DCHECK_NE(visual_location, indices_in_visual_order.end());
if (move_direction == MoveDirection::kTowardsLeft) {
DCHECK_NE(visual_location, indices_in_visual_order.begin());
return {VisualMovementResultType::kWithinContext,
*std::prev(visual_location)};
}
DCHECK_NE(std::next(visual_location), indices_in_visual_order.end());
return {VisualMovementResultType::kWithinContext,
*std::next(visual_location)};
}
NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::LeftPositionOf(
const Position& caret_position) const {
return MoveCaretInternal(caret_position, MoveDirection::kTowardsLeft);
}
NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::RightPositionOf(
const Position& caret_position) const {
return MoveCaretInternal(caret_position, MoveDirection::kTowardsRight);
}
NGCaretNavigator::UnvalidatedVisualCaretMovementResult
NGCaretNavigator::MoveCaretWithoutValidation(
const Position& caret_position,
MoveDirection move_direction) const {
const unsigned index = caret_position.index;
const MoveDirection opposite_direction = OppositeDirectionOf(move_direction);
if (caret_position == EdgeOfInternal(index, opposite_direction)) {
// TODO(xiaochengh): Consider grapheme cluster
return {VisualMovementResultType::kWithinContext,
EdgeOfInternal(index, move_direction),
!IsIgnoredInCaretMovement(index)};
}
VisualCharacterMovementResult forward_character =
MoveCharacterInternal(index, move_direction);
if (!forward_character.IsWithinContext())
return {forward_character.type};
DCHECK(forward_character.index.has_value());
const Position forward_caret =
EdgeOfInternal(*forward_character.index, opposite_direction);
return {VisualMovementResultType::kWithinContext, forward_caret};
}
NGCaretNavigator::VisualCaretMovementResult NGCaretNavigator::MoveCaretInternal(
const Position& caret_position,
MoveDirection move_direction) const {
bool has_passed_character = false;
base::Optional<Position> last_position;
for (Position runner = caret_position;
!has_passed_character || !IsValidCaretPosition(runner);) {
const UnvalidatedVisualCaretMovementResult next =
MoveCaretWithoutValidation(runner, move_direction);
if (next.type != VisualMovementResultType::kWithinContext)
return {next.type, base::nullopt};
if (next.has_passed_character) {
has_passed_character = true;
const unsigned last_passed_character = next.position->index;
if (IsEnterableChildContext(last_passed_character))
return {VisualMovementResultType::kEnteredChildContext, runner};
}
runner = *next.position;
last_position = runner;
// TODO(xiaochengh): Handle the case where we reach a different line with a
// different base direction, which occurs with 'unicode-bidi: plain-text'.
}
DCHECK(last_position.has_value());
return {VisualMovementResultType::kWithinContext, *last_position};
}
NGCaretNavigator::Position NGCaretNavigator::LeftmostPositionInFirstLine()
const {
Line first_line = ContainingLineOf(0);
unsigned leftmost_character =
VisualMostForwardCharacterOf(first_line, MoveDirection::kTowardsLeft);
// TODO(xiaochengh): Handle if the caret position is invalid.
return LeftEdgeOf(leftmost_character);
}
NGCaretNavigator::Position NGCaretNavigator::RightmostPositionInFirstLine()
const {
Line first_line = ContainingLineOf(0);
unsigned rightmost_character =
VisualMostForwardCharacterOf(first_line, MoveDirection::kTowardsRight);
// TODO(xiaochengh): Handle if the caret position is invalid.
return RightEdgeOf(rightmost_character);
}
NGCaretNavigator::Position NGCaretNavigator::LeftmostPositionInLastLine()
const {
Line last_line = ContainingLineOf(GetText().length() - 1);
unsigned leftmost_character =
VisualMostForwardCharacterOf(last_line, MoveDirection::kTowardsLeft);
// TODO(xiaochengh): Handle if the caret position is invalid.
return LeftEdgeOf(leftmost_character);
}
NGCaretNavigator::Position NGCaretNavigator::RightmostPositionInLastLine()
const {
Line last_line = ContainingLineOf(GetText().length() - 1);
unsigned rightmost_character =
VisualMostForwardCharacterOf(last_line, MoveDirection::kTowardsRight);
// TODO(xiaochengh): Handle if the caret position is invalid.
return RightEdgeOf(rightmost_character);
}
} // namespace blink
// Copyright 2018 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_
#include "base/optional.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/document_lifecycle.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/platform/text/text_direction.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include <unicode/ubidi.h>
namespace blink {
class LayoutBlockFlow;
struct NGInlineNodeData;
// Hosts the |text_content| of an inline formatting context and provides
// bidi-related utilities, including checking bidi levels, computing visual
// left/right characters and visual left/right caret movements.
// Design doc: http://bit.ly/2QVAwGq
class CORE_EXPORT NGCaretNavigator {
STACK_ALLOCATED();
public:
explicit NGCaretNavigator(const LayoutBlockFlow&);
~NGCaretNavigator();
const String& GetText() const;
bool IsBidiEnabled() const;
// Abstraction of a caret position in |text_|.
enum class PositionAnchorType { kBefore, kAfter };
struct Position {
// |index| is character index the |text_| string.
unsigned index;
PositionAnchorType type;
bool IsBeforeCharacter() const {
return type == PositionAnchorType::kBefore;
}
bool IsAfterCharacter() const { return type == PositionAnchorType::kAfter; }
bool operator==(const Position& other) const {
return index == other.index && type == other.type;
}
};
// Returns the bidi level or resolved direction of the character at the given
// logical |index|.
UBiDiLevel BidiLevelAt(unsigned index) const;
TextDirection TextDirectionAt(unsigned index) const;
// Returns true if the characters at indexes |offset - 1| and |offset| both
// exist and are at different bidi levels.
bool OffsetIsBidiBoundary(unsigned offset) const;
// Converts an (offset, affinity) pair into a |Position| type of this class.
// Intiontionally long name to indicate the hackiness for handling legacy
// callers.
Position CaretPositionFromTextContentOffsetAndAffinity(
unsigned offset,
TextAffinity affinity) const;
// Returns the visual left/right edge caret position of the character at the
// given logical |index|.
Position LeftEdgeOf(unsigned index) const;
Position RightEdgeOf(unsigned index) const;
// Left/right visual movements
// TODO(xiaochengh): Handle the following
// - Grapheme clusters
enum class VisualMovementResultType {
kWithinContext,
kBeforeContext,
kAfterContext,
kEnteredChildContext
};
// Given the character at the logical |index|, returns the logical index of
// the character at its left/right side.
struct VisualCharacterMovementResult {
bool IsWithinContext() const {
return type == VisualMovementResultType::kWithinContext;
}
bool IsBeforeContext() const {
return type == VisualMovementResultType::kBeforeContext;
}
bool IsAfterContext() const {
return type == VisualMovementResultType::kAfterContext;
}
VisualMovementResultType type;
base::Optional<unsigned> index;
};
VisualCharacterMovementResult LeftCharacterOf(unsigned index) const;
VisualCharacterMovementResult RightCharacterOf(unsigned index) const;
// Given a caret position, moves it left/right by one grapheme cluster and
// returns the result.
// Note: If we end up entering an inline block, the result |Position| is
// either before or after the inline block, depending on from which side the
// inline block is entered. For example:
// RightPositionOf(abc|<inline-block>def</inline-block>ghi)
// -> {inline-block, PositionAnchorType::kBefore}
// LeftPositionOf(abc<inline-block>def</inline-block>|ghi)
// -> {inline-block, PositionAnchorType::kAfter}
struct VisualCaretMovementResult {
bool IsWithinContext() const {
return type == VisualMovementResultType::kWithinContext;
}
bool IsBeforeContext() const {
return type == VisualMovementResultType::kBeforeContext;
}
bool IsAfterContext() const {
return type == VisualMovementResultType::kAfterContext;
}
bool HasEnteredChildContext() const {
return type == VisualMovementResultType::kEnteredChildContext;
}
VisualMovementResultType type;
base::Optional<Position> position;
};
VisualCaretMovementResult LeftPositionOf(const Position&) const;
VisualCaretMovementResult RightPositionOf(const Position&) const;
// TODO(xiaochengh): Specify and implement the behavior in edge cases, e.g.,
// when the leftmost character of the first line is CSS-generated.
Position LeftmostPositionInFirstLine() const;
Position RightmostPositionInFirstLine() const;
Position LeftmostPositionInLastLine() const;
Position RightmostPositionInLastLine() const;
private:
// A caret position is invalid if it is:
// - kAfter to a line break character.
// - Anchored to a collapsible space that's removed by line wrap.
// - Anchored to a character that's ignored in caret movement.
bool IsValidCaretPosition(const Position&) const;
bool IsLineBreak(unsigned index) const;
bool IsCollapsibleWhitespace(unsigned index) const;
bool IsCollapsedSpaceByLineWrap(unsigned index) const;
bool IsIgnoredInCaretMovement(unsigned index) const;
// Returns true if the character at |index| represents a child block
// formatting context that can be entered by caret navigation. Such contexts
// must be atomic inlines (inline block, inline table, ...) and must not host
// user agent shadow tree (which excludes, e.g., <input> and image alt text).
bool IsEnterableChildContext(unsigned index) const;
enum class MoveDirection { kTowardsLeft, kTowardsRight };
static MoveDirection OppositeDirectionOf(MoveDirection);
static bool TowardsSameDirection(MoveDirection, TextDirection);
// ------ Line-related functions ------
// A line contains a consecutive substring of |GetText()|. The lines should
// not overlap, and should together cover the entire |GetText()|.
struct Line {
unsigned start_offset;
unsigned end_offset;
TextDirection base_direction;
};
Line ContainingLineOf(unsigned index) const;
Vector<int32_t, 32> CharacterIndicesInVisualOrder(const Line&) const;
unsigned VisualMostForwardCharacterOf(const Line&,
MoveDirection direction) const;
unsigned VisualLastCharacterOf(const Line&) const;
unsigned VisualFirstCharacterOf(const Line&) const;
// ------ Implementation of public visual movement functions ------
Position EdgeOfInternal(unsigned index, MoveDirection) const;
VisualCharacterMovementResult MoveCharacterInternal(unsigned index,
MoveDirection) const;
VisualCaretMovementResult MoveCaretInternal(const Position&,
MoveDirection) const;
// Performs a "minimal" caret movement to the left/right without validating
// the result. The result might be invalid due to, e.g., anchored to an
// unallowed character, being visually the same as the input, etc. It's a
// subroutine of |MoveCaretInternal|, who keeps calling it until both of the
// folliwng are satisfied:
// - We've reached a valid caret position.
// - During the process, the caret has moved passing a character on which
// |IsIgnoredInCaretMovement| is false (indicated by |has_passed_character|).
struct UnvalidatedVisualCaretMovementResult {
VisualMovementResultType type;
base::Optional<Position> position;
bool has_passed_character = false;
};
UnvalidatedVisualCaretMovementResult MoveCaretWithoutValidation(
const Position&,
MoveDirection) const;
const NGInlineNodeData& GetData() const;
const LayoutBlockFlow& context_;
DocumentLifecycle::DisallowTransitionScope disallow_transition_;
};
CORE_EXPORT std::ostream& operator<<(std::ostream&,
const NGCaretNavigator::Position&);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_CARET_NAVIGATOR_H_
// Copyright 2018 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 "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
namespace blink {
class NGCaretNavigatorTest : public RenderingTest,
private ScopedBidiCaretAffinityForTest {
public:
NGCaretNavigatorTest() : ScopedBidiCaretAffinityForTest(true) {}
void SetupHtml(const char* id, String html) {
SetBodyInnerHTML(html);
block_flow_ = To<LayoutBlockFlow>(GetLayoutObjectByElementId(id));
DCHECK(block_flow_);
DCHECK(block_flow_->IsLayoutNGMixin());
DCHECK(block_flow_->ChildrenInline());
}
UBiDiLevel BidiLevelAt(unsigned index) const {
return NGCaretNavigator(*block_flow_).BidiLevelAt(index);
}
NGCaretNavigator::VisualCharacterMovementResult LeftCharacterOf(
unsigned index) const {
return NGCaretNavigator(*block_flow_).LeftCharacterOf(index);
}
NGCaretNavigator::VisualCharacterMovementResult RightCharacterOf(
unsigned index) const {
return NGCaretNavigator(*block_flow_).RightCharacterOf(index);
}
NGCaretNavigator::Position CaretBefore(unsigned index) const {
return {index, NGCaretNavigator::PositionAnchorType::kBefore};
}
NGCaretNavigator::Position CaretAfter(unsigned index) const {
return {index, NGCaretNavigator::PositionAnchorType::kAfter};
}
NGCaretNavigator::VisualCaretMovementResult LeftPositionOf(
const NGCaretNavigator::Position& position) const {
return NGCaretNavigator(*block_flow_).LeftPositionOf(position);
}
NGCaretNavigator::VisualCaretMovementResult RightPositionOf(
const NGCaretNavigator::Position& position) const {
return NGCaretNavigator(*block_flow_).RightPositionOf(position);
}
protected:
const LayoutBlockFlow* block_flow_;
};
TEST_F(NGCaretNavigatorTest, BidiLevelAtBasic) {
SetupHtml("container",
"<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
EXPECT_EQ(0u, BidiLevelAt(0));
EXPECT_EQ(0u, BidiLevelAt(1));
EXPECT_EQ(0u, BidiLevelAt(2));
EXPECT_EQ(1u, BidiLevelAt(3));
EXPECT_EQ(1u, BidiLevelAt(4));
EXPECT_EQ(1u, BidiLevelAt(5));
EXPECT_EQ(2u, BidiLevelAt(6));
EXPECT_EQ(2u, BidiLevelAt(7));
EXPECT_EQ(2u, BidiLevelAt(8));
}
TEST_F(NGCaretNavigatorTest, LeftCharacterOfBasic) {
SetupHtml("container",
"<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
EXPECT_TRUE(LeftCharacterOf(0).IsBeforeContext());
EXPECT_TRUE(LeftCharacterOf(1).IsWithinContext());
EXPECT_EQ(0u, *LeftCharacterOf(1).index);
EXPECT_TRUE(LeftCharacterOf(2).IsWithinContext());
EXPECT_EQ(1u, *LeftCharacterOf(2).index);
EXPECT_TRUE(LeftCharacterOf(3).IsWithinContext());
EXPECT_EQ(4u, *LeftCharacterOf(3).index);
EXPECT_TRUE(LeftCharacterOf(4).IsWithinContext());
EXPECT_EQ(5u, *LeftCharacterOf(4).index);
EXPECT_TRUE(LeftCharacterOf(5).IsWithinContext());
EXPECT_EQ(8u, *LeftCharacterOf(5).index);
EXPECT_TRUE(LeftCharacterOf(6).IsWithinContext());
EXPECT_EQ(2u, *LeftCharacterOf(6).index);
EXPECT_TRUE(LeftCharacterOf(7).IsWithinContext());
EXPECT_EQ(6u, *LeftCharacterOf(7).index);
EXPECT_TRUE(LeftCharacterOf(8).IsWithinContext());
EXPECT_EQ(7u, *LeftCharacterOf(8).index);
}
TEST_F(NGCaretNavigatorTest, RightCharacterOfBasic) {
SetupHtml("container",
"<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
EXPECT_TRUE(RightCharacterOf(0).IsWithinContext());
EXPECT_EQ(1u, *RightCharacterOf(0).index);
EXPECT_TRUE(RightCharacterOf(1).IsWithinContext());
EXPECT_EQ(2u, *RightCharacterOf(1).index);
EXPECT_TRUE(RightCharacterOf(2).IsWithinContext());
EXPECT_EQ(6u, *RightCharacterOf(2).index);
EXPECT_TRUE(RightCharacterOf(3).IsAfterContext());
EXPECT_TRUE(RightCharacterOf(4).IsWithinContext());
EXPECT_EQ(3u, *RightCharacterOf(4).index);
EXPECT_TRUE(RightCharacterOf(5).IsWithinContext());
EXPECT_EQ(4u, *RightCharacterOf(5).index);
EXPECT_TRUE(RightCharacterOf(6).IsWithinContext());
EXPECT_EQ(7u, *RightCharacterOf(6).index);
EXPECT_TRUE(RightCharacterOf(7).IsWithinContext());
EXPECT_EQ(8u, *RightCharacterOf(7).index);
EXPECT_TRUE(RightCharacterOf(8).IsWithinContext());
EXPECT_EQ(5u, *RightCharacterOf(8).index);
}
TEST_F(NGCaretNavigatorTest, LeftPositionOfBasic) {
SetupHtml("container",
"<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
EXPECT_TRUE(LeftPositionOf(CaretAfter(0)).IsWithinContext());
EXPECT_EQ(CaretBefore(0), *LeftPositionOf(CaretAfter(0)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(1)).IsWithinContext());
EXPECT_EQ(CaretBefore(0), *LeftPositionOf(CaretBefore(1)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(1)).IsWithinContext());
EXPECT_EQ(CaretBefore(1), *LeftPositionOf(CaretAfter(1)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(2)).IsWithinContext());
EXPECT_EQ(CaretBefore(1), *LeftPositionOf(CaretBefore(2)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(2)).IsWithinContext());
EXPECT_EQ(CaretBefore(2), *LeftPositionOf(CaretAfter(2)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(3)).IsWithinContext());
EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretBefore(3)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext());
EXPECT_EQ(CaretAfter(4), *LeftPositionOf(CaretAfter(3)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
EXPECT_EQ(CaretAfter(4), *LeftPositionOf(CaretBefore(4)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext());
EXPECT_EQ(CaretAfter(5), *LeftPositionOf(CaretAfter(4)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext());
EXPECT_EQ(CaretAfter(5), *LeftPositionOf(CaretBefore(5)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(5)).IsWithinContext());
EXPECT_EQ(CaretBefore(8), *LeftPositionOf(CaretAfter(5)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(6)).IsWithinContext());
EXPECT_EQ(CaretBefore(2), *LeftPositionOf(CaretBefore(6)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(6)).IsWithinContext());
EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(6)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(7)).IsWithinContext());
EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretBefore(7)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(7)).IsWithinContext());
EXPECT_EQ(CaretBefore(7), *LeftPositionOf(CaretAfter(7)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(8)).IsWithinContext());
EXPECT_EQ(CaretBefore(7), *LeftPositionOf(CaretBefore(8)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(8)).IsWithinContext());
EXPECT_EQ(CaretBefore(8), *LeftPositionOf(CaretAfter(8)).position);
}
TEST_F(NGCaretNavigatorTest, RightPositionOfBasic) {
SetupHtml("container",
"<div id=container>abc&#x05D0;&#x05D1;&#x05D2;123</div>");
EXPECT_TRUE(RightPositionOf(CaretBefore(0)).IsWithinContext());
EXPECT_EQ(CaretAfter(0), *RightPositionOf(CaretBefore(0)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(0)).IsWithinContext());
EXPECT_EQ(CaretAfter(1), *RightPositionOf(CaretAfter(0)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(1)).IsWithinContext());
EXPECT_EQ(CaretAfter(1), *RightPositionOf(CaretBefore(1)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(1)).IsWithinContext());
EXPECT_EQ(CaretAfter(2), *RightPositionOf(CaretAfter(1)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(2)).IsWithinContext());
EXPECT_EQ(CaretAfter(2), *RightPositionOf(CaretBefore(2)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
EXPECT_EQ(CaretAfter(6), *RightPositionOf(CaretAfter(2)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsAfterContext());
EXPECT_TRUE(RightPositionOf(CaretAfter(3)).IsWithinContext());
EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretAfter(3)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretBefore(4)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(4)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(5)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(5)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(5)).IsWithinContext());
EXPECT_EQ(CaretBefore(5), *RightPositionOf(CaretAfter(5)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(6)).IsWithinContext());
EXPECT_EQ(CaretAfter(6), *RightPositionOf(CaretBefore(6)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsWithinContext());
EXPECT_EQ(CaretAfter(7), *RightPositionOf(CaretAfter(6)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(7)).IsWithinContext());
EXPECT_EQ(CaretAfter(7), *RightPositionOf(CaretBefore(7)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(7)).IsWithinContext());
EXPECT_EQ(CaretAfter(8), *RightPositionOf(CaretAfter(7)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(8)).IsWithinContext());
EXPECT_EQ(CaretAfter(8), *RightPositionOf(CaretBefore(8)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(8)).IsWithinContext());
EXPECT_EQ(CaretBefore(5), *RightPositionOf(CaretAfter(8)).position);
}
// Tests below check caret movement crossing line boundaries
TEST_F(NGCaretNavigatorTest, HardLineBreak) {
SetupHtml("container", "<div id=container>abc<br>def</div>");
EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(2)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(3)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext());
}
TEST_F(NGCaretNavigatorTest, SoftLineWrapAtSpace) {
SetupHtml("container", "<div id=container style=\"width:0\">abc def</div>");
EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(2)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretBefore(3)).position);
EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
EXPECT_EQ(CaretAfter(2), *LeftPositionOf(CaretBefore(4)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext());
}
TEST_F(NGCaretNavigatorTest, BidiAndSoftLineWrapAtSpaceLtr) {
LoadAhem();
SetupHtml("container",
"<div id=container style='font: 10px/10px Ahem; width: 100px'>"
"before &#x05D0;&#x05D1;&#x05D2;&#x05D3; "
"&#x05D4;&#x05D5;&#x05D6;&#x05D7;&#x05D8;&#x05D9;"
"&#x05DA;&#x05DB;&#x05DC;&#x05DD;&#x05DE;&#x05DF;"
"&#x05E0;&#x05E1;&#x05E2;&#x05E3;&#x05E4;&#x05E5;"
"</div>");
// Moving left from "|before DCBA" should be before context
EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
// Moving right from "before |DCBA" should yield "before D|CBA"
EXPECT_TRUE(RightPositionOf(CaretAfter(10)).IsWithinContext());
EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(10)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsWithinContext());
EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(6)).position);
// Moving left from "before |DCBA" should yield "before| DCBA"
EXPECT_TRUE(LeftPositionOf(CaretAfter(10)).IsWithinContext());
EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(10)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(6)).IsWithinContext());
EXPECT_EQ(CaretBefore(6), *LeftPositionOf(CaretAfter(6)).position);
// Moving right from "before DCBA|" should yield "V|UTSRQPONMLKJIHGFE"
EXPECT_TRUE(RightPositionOf(CaretBefore(7)).IsWithinContext());
EXPECT_EQ(CaretBefore(29), *RightPositionOf(CaretBefore(7)).position);
// Moving left from "|VUTSRQPONMLKJIHGFE" should yield "before DCB|A"
EXPECT_TRUE(LeftPositionOf(CaretAfter(29)).IsWithinContext());
EXPECT_EQ(CaretAfter(7), *LeftPositionOf(CaretAfter(29)).position);
// Moving right from "VUTSRQPONMLKJIHGFE|" should be after context
EXPECT_TRUE(RightPositionOf(CaretBefore(12)).IsAfterContext());
}
TEST_F(NGCaretNavigatorTest, BidiAndSoftLineWrapAtSpaceRtl) {
LoadAhem();
SetupHtml(
"container",
"<div dir=rtl id=container style='font: 10px/10px Ahem; width: 120px'>"
"&#x05D0;&#x05D1;&#x05D2;&#x05D3; after encyclopedia"
"</div>");
// Moving right from "after DCBA|" should be before context
EXPECT_TRUE(RightPositionOf(CaretBefore(0)).IsBeforeContext());
// Moving left from "after| DCBA" should yield "afte|r DCBA"
EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(9), *LeftPositionOf(CaretAfter(4)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(9)).IsWithinContext());
EXPECT_EQ(CaretBefore(9), *LeftPositionOf(CaretAfter(9)).position);
// Moving right from "after| DCBA" should yield "after |DCBA"
EXPECT_TRUE(RightPositionOf(CaretAfter(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(4)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(9)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *RightPositionOf(CaretAfter(9)).position);
// Moving left from "|after DCBA" should yield "encyclopedi|a"
EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext());
EXPECT_EQ(CaretBefore(22), *LeftPositionOf(CaretBefore(5)).position);
// Moving right from "encyclopedia|" should yield "a|fter DCBA"
EXPECT_TRUE(RightPositionOf(CaretAfter(22)).IsWithinContext());
EXPECT_EQ(CaretAfter(5), *RightPositionOf(CaretAfter(22)).position);
// Moving left from "|encyclopedia" should be after context
EXPECT_TRUE(LeftPositionOf(CaretBefore(11)).IsAfterContext());
}
TEST_F(NGCaretNavigatorTest, SoftLineWrapAtHyphen) {
SetupHtml("container", "<div id=container style=\"width:0\">abc-def</div>");
EXPECT_TRUE(LeftPositionOf(CaretBefore(0)).IsBeforeContext());
// 3 -> 4
EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretAfter(2)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretBefore(3)).position);
// 4 -> 5
EXPECT_TRUE(RightPositionOf(CaretAfter(3)).IsWithinContext());
EXPECT_EQ(CaretAfter(4), *RightPositionOf(CaretAfter(3)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(4)).IsWithinContext());
EXPECT_EQ(CaretAfter(4), *RightPositionOf(CaretBefore(4)).position);
// 5 -> 4
EXPECT_TRUE(LeftPositionOf(CaretBefore(5)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *LeftPositionOf(CaretBefore(5)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(4), *LeftPositionOf(CaretAfter(4)).position);
// 4 -> 3
EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext());
EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretAfter(3)).position);
EXPECT_TRUE(RightPositionOf(CaretAfter(6)).IsAfterContext());
}
TEST_F(NGCaretNavigatorTest, MoveOverPseudoElementInBidi) {
SetupHtml("container",
"<style>.bidi::before,.bidi::after{content:'a\\05D0 b'}</style>"
"<div id=container>&#x05D0;&#x05D1; &#x05D2;&#x05D3; "
"<span class=bidi>&#x05D4;&#x05D5;</span>"
" &#x05D6;&#x05D7; &#x05D8;&#x05D9;</div>");
// Text: "AB CD aAbEFaAb GH IJ"
// Rendered as: "DC BA aAbFEaAb JI HG"
// Moving right from "BA |" should arrive at "F|E"
EXPECT_TRUE(RightPositionOf(CaretAfter(5)).IsWithinContext());
EXPECT_EQ(CaretBefore(10), *RightPositionOf(CaretAfter(5)).position);
// Moving left from "|FE" should arrive at "BA| "
EXPECT_TRUE(LeftPositionOf(CaretAfter(10)).IsWithinContext());
EXPECT_EQ(CaretBefore(5), *LeftPositionOf(CaretAfter(10)).position);
// Moving right from "FE|" should arrive at " |JI"
EXPECT_TRUE(RightPositionOf(CaretBefore(9)).IsWithinContext());
EXPECT_EQ(CaretAfter(14), *RightPositionOf(CaretBefore(9)).position);
// Moving left from "| JI" should arrive at "F|E"
EXPECT_TRUE(LeftPositionOf(CaretBefore(14)).IsWithinContext());
EXPECT_EQ(CaretAfter(9), *LeftPositionOf(CaretBefore(14)).position);
}
TEST_F(NGCaretNavigatorTest, EnterableInlineBlock) {
SetupHtml("container",
"<div id=container>foo"
"<span style='display:inline-block'>bar</span>"
"baz</div>");
// Moving right from "foo|" should enter the span from front.
EXPECT_TRUE(RightPositionOf(CaretAfter(2)).HasEnteredChildContext());
EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretAfter(2)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(3)).HasEnteredChildContext());
EXPECT_EQ(CaretBefore(3), *RightPositionOf(CaretBefore(3)).position);
// Moving left from "|baz" should enter the span from behind.
EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).HasEnteredChildContext());
EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretBefore(4)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).HasEnteredChildContext());
EXPECT_EQ(CaretAfter(3), *LeftPositionOf(CaretAfter(3)).position);
}
TEST_F(NGCaretNavigatorTest, UnenterableInlineBlock) {
SetupHtml("container",
"<div id=container>foo"
"<input value=bar>"
"baz</div>");
// Moving right from "foo|" should reach "<input>|".
EXPECT_TRUE(RightPositionOf(CaretAfter(2)).IsWithinContext());
EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretAfter(2)).position);
EXPECT_TRUE(RightPositionOf(CaretBefore(3)).IsWithinContext());
EXPECT_EQ(CaretAfter(3), *RightPositionOf(CaretBefore(3)).position);
// Moving left from "|baz" should reach "|<input>".
EXPECT_TRUE(LeftPositionOf(CaretBefore(4)).IsWithinContext());
EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretBefore(4)).position);
EXPECT_TRUE(LeftPositionOf(CaretAfter(3)).IsWithinContext());
EXPECT_EQ(CaretBefore(3), *LeftPositionOf(CaretAfter(3)).position);
}
} // namespace blink
......@@ -12,7 +12,6 @@
#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/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
#include "third_party/blink/renderer/platform/text/character.h"
......@@ -509,16 +508,6 @@ Position NGOffsetMapping::GetLastPosition(unsigned offset) const {
return CreatePositionForOffsetMapping(node, dom_offset);
}
PositionWithAffinity NGOffsetMapping::GetPositionWithAffinity(
const NGCaretNavigator::Position& position) const {
if (position.IsBeforeCharacter()) {
return PositionWithAffinity(GetLastPosition(position.index),
TextAffinity::kDownstream);
}
return PositionWithAffinity(GetFirstPosition(position.index + 1),
TextAffinity::kUpstream);
}
bool NGOffsetMapping::HasBidiControlCharactersOnly(unsigned start,
unsigned end) const {
DCHECK_LE(start, end);
......
......@@ -9,7 +9,6 @@
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/editing/forward.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
......@@ -219,13 +218,6 @@ class CORE_EXPORT NGOffsetMapping {
Position GetFirstPosition(unsigned) const;
Position GetLastPosition(unsigned) const;
// Converts the given caret position on text content to a PositionWithAffinity
// in DOM. If |position| is before a character, the function creates a
// downstream position before |GetLastPosition()| of the character; otherwise,
// it returns an upstream position after |GetFirstPosition()| of the character
PositionWithAffinity GetPositionWithAffinity(
const NGCaretNavigator::Position& position) const;
// Returns all NGOffsetMappingUnits whose text content ranges has non-empty
// (but possibly collapsed) intersection with (start, end). Note that units
// that only "touch" |start| or |end| are excluded.
......
......@@ -7,7 +7,6 @@
#include <utility>
#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/ng/inline/ng_caret_navigator.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
namespace blink {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment