diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/core/editing/visible_units_line.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/core/editing/visible_units_line.cc | 270 |
1 files changed, 159 insertions, 111 deletions
diff --git a/chromium/third_party/blink/renderer/core/editing/visible_units_line.cc b/chromium/third_party/blink/renderer/core/editing/visible_units_line.cc index 766bba980ae..f18f9d02490 100644 --- a/chromium/third_party/blink/renderer/core/editing/visible_units_line.cc +++ b/chromium/third_party/blink/renderer/core/editing/visible_units_line.cc @@ -46,6 +46,109 @@ namespace { struct VisualOrdering; +// See also InlineBidiResolver::NeedsTrailingSpace() +bool NeedsTrailingSpace(const ComputedStyle& style) { + return style.BreakOnlyAfterWhiteSpace() && style.AutoWrap(); +} + +static PositionWithAffinity AdjustForSoftLineWrap( + const NGInlineCursorPosition& line_box, + const PositionWithAffinity& position) { + DCHECK(line_box.IsLineBox()); + if (position.IsNull()) + return PositionWithAffinity(); + if (!NeedsTrailingSpace(line_box.Style()) || + !line_box.HasSoftWrapToNextLine()) + return position; + // Returns a position after first space causing soft line wrap for editable. + if (!NGOffsetMapping::AcceptsPosition(position.GetPosition())) + return position; + const NGOffsetMapping* mapping = + NGOffsetMapping::GetFor(position.GetPosition()); + const auto offset = mapping->GetTextContentOffset(position.GetPosition()); + if (offset == mapping->GetText().length()) + return position; + const Position adjusted_position = mapping->GetFirstPosition(*offset + 1); + if (adjusted_position.IsNull()) + return position; + DCHECK(IsA<Text>(adjusted_position.AnchorNode())) << adjusted_position; + if (!IsA<Text>(adjusted_position.AnchorNode())) + return position; + if (!adjusted_position.AnchorNode() + ->GetLayoutObject() + ->StyleRef() + .IsCollapsibleWhiteSpace(mapping->GetText()[*offset])) + return position; + return PositionWithAffinity(adjusted_position, + TextAffinity::kUpstreamIfPossible); +} + +template <typename Strategy, typename Ordering> +static PositionWithAffinityTemplate<Strategy> EndPositionForLine( + const PositionWithAffinityTemplate<Strategy>& c) { + if (c.IsNull()) + return PositionWithAffinityTemplate<Strategy>(); + const PositionWithAffinityTemplate<Strategy> adjusted = + ComputeInlineAdjustedPosition(c); + + if (const LayoutBlockFlow* context = + NGInlineFormattingContextOf(adjusted.GetPosition())) { + DCHECK((std::is_same<Ordering, VisualOrdering>::value) || + !RuntimeEnabledFeatures::BidiCaretAffinityEnabled()) + << "Logical line boundary for BidiCaretAffinity is not implemented yet"; + + const NGCaretPosition caret_position = ComputeNGCaretPosition(adjusted); + if (caret_position.IsNull()) { + // TODO(crbug.com/947593): Support |ComputeNGCaretPosition()| on content + // hidden by 'text-overflow:ellipsis' so that we always have a non-null + // |caret_position| here. + return PositionWithAffinityTemplate<Strategy>(); + } + NGInlineCursor line_box = caret_position.cursor; + line_box.MoveToContainingLine(); + const PositionWithAffinity end_position = line_box.PositionForEndOfLine(); + return FromPositionInDOMTree<Strategy>( + AdjustForSoftLineWrap(line_box.Current(), end_position)); + } + + const InlineBox* inline_box = + adjusted.IsNotNull() ? ComputeInlineBoxPosition(c).inline_box : nullptr; + if (!inline_box) { + // There are VisiblePositions at offset 0 in blocks without + // RootInlineBoxes, like empty editable blocks and bordered blocks. + const PositionTemplate<Strategy> p = c.GetPosition(); + if (p.AnchorNode()->GetLayoutObject() && + p.AnchorNode()->GetLayoutObject()->IsLayoutBlock() && + !p.ComputeEditingOffset()) + return c; + return PositionWithAffinityTemplate<Strategy>(); + } + + const RootInlineBox& root_box = inline_box->Root(); + const InlineBox* const end_box = Ordering::EndNonPseudoBoxOf(root_box); + if (!end_box) + return PositionWithAffinityTemplate<Strategy>(); + + const Node* const end_node = end_box->GetLineLayoutItem().NonPseudoNode(); + DCHECK(end_node); + if (IsA<HTMLBRElement>(*end_node)) { + return Ordering::AdjustForSoftLineWrap( + PositionTemplate<Strategy>::BeforeNode(*end_node), c); + } + + auto* end_text_node = DynamicTo<Text>(end_node); + if (end_box->IsInlineTextBox() && end_text_node) { + const InlineTextBox* end_text_box = ToInlineTextBox(end_box); + int end_offset = end_text_box->Start(); + if (!end_text_box->IsLineBreak()) + end_offset += end_text_box->Len(); + return Ordering::AdjustForSoftLineWrap( + PositionTemplate<Strategy>(end_text_node, end_offset), c); + } + return Ordering::AdjustForSoftLineWrap( + PositionTemplate<Strategy>::AfterNode(*end_node), c); +} + template <typename Strategy, typename Ordering> PositionWithAffinityTemplate<Strategy> StartPositionForLine( const PositionWithAffinityTemplate<Strategy>& c) { @@ -70,9 +173,7 @@ PositionWithAffinityTemplate<Strategy> StartPositionForLine( NGInlineCursor line_box = caret_position.cursor; line_box.MoveToContainingLine(); DCHECK(line_box.Current().IsLineBox()) << line_box; - const PhysicalOffset start_point = line_box.Current().LineStartPoint(); - return FromPositionInDOMTree<Strategy>( - line_box.PositionForPointInInlineBox(start_point)); + return FromPositionInDOMTree<Strategy>(line_box.PositionForStartOfLine()); } const InlineBox* inline_box = @@ -116,6 +217,27 @@ struct LogicalOrdering { static const InlineBox* EndNonPseudoBoxOf(const RootInlineBox& root_box) { return root_box.GetLogicalEndNonPseudoBox(); } + + // Make sure the end of line is at the same line as the given input + // position. For a wrapping line, the logical end position for the + // not-last-2-lines might incorrectly hand back the logical beginning of the + // next line. For example, + // <div contenteditable dir="rtl" style="line-break:before-white-space">xyz + // a xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz </div> + // In this case, use the previous position of the computed logical end + // position. + template <typename Strategy> + static PositionWithAffinityTemplate<Strategy> AdjustForSoftLineWrap( + const PositionTemplate<Strategy>& candidate, + const PositionWithAffinityTemplate<Strategy>& current_position) { + const PositionWithAffinityTemplate<Strategy> candidate_position = + PositionWithAffinityTemplate<Strategy>( + candidate, TextAffinity::kUpstreamIfPossible); + if (InSameLogicalLine(current_position, candidate_position)) + return candidate_position; + return PreviousPositionOf(CreateVisiblePosition(candidate_position)) + .ToPositionWithAffinity(); + } }; // Provides start end end of line in visual order for implementing expanding @@ -148,6 +270,31 @@ struct VisualOrdering { } return nullptr; } + + // Make sure the end of line is at the same line as the given input + // position. Else use the previous position to obtain end of line. This + // condition happens when the input position is before the space character + // at the end of a soft-wrapped non-editable line. In this scenario, + // |EndPositionForLine()| would incorrectly hand back a position in the next + // line instead. This fix is to account for the discrepancy between lines + // with "webkit-line-break:after-white-space" style versus lines without + // that style, which would break before a space by default. + template <typename Strategy> + static PositionWithAffinityTemplate<Strategy> AdjustForSoftLineWrap( + const PositionTemplate<Strategy>& candidate, + const PositionWithAffinityTemplate<Strategy>& current_position) { + const PositionWithAffinityTemplate<Strategy> candidate_position = + PositionWithAffinityTemplate<Strategy>( + candidate, TextAffinity::kUpstreamIfPossible); + if (InSameLine(current_position, candidate_position)) + return candidate_position; + const PositionWithAffinityTemplate<Strategy>& adjusted_position = + PreviousPositionOf(CreateVisiblePosition(current_position)) + .ToPositionWithAffinity(); + if (adjusted_position.IsNull()) + return PositionWithAffinityTemplate<Strategy>(); + return EndPositionForLine<Strategy, VisualOrdering>(adjusted_position); + } }; template <typename Strategy> @@ -229,75 +376,6 @@ VisiblePositionInFlatTree LogicalStartOfLine( LogicalStartOfLine(current_position.ToPositionWithAffinity())); } -template <typename Strategy, typename Ordering> -static PositionWithAffinityTemplate<Strategy> EndPositionForLine( - const PositionWithAffinityTemplate<Strategy>& c) { - if (c.IsNull()) - return PositionWithAffinityTemplate<Strategy>(); - const PositionWithAffinityTemplate<Strategy> adjusted = - ComputeInlineAdjustedPosition(c); - - if (const LayoutBlockFlow* context = - NGInlineFormattingContextOf(adjusted.GetPosition())) { - DCHECK((std::is_same<Ordering, VisualOrdering>::value) || - !RuntimeEnabledFeatures::BidiCaretAffinityEnabled()) - << "Logical line boundary for BidiCaretAffinity is not implemented yet"; - - const NGCaretPosition caret_position = ComputeNGCaretPosition(adjusted); - if (caret_position.IsNull()) { - // TODO(crbug.com/947593): Support |ComputeNGCaretPosition()| on content - // hidden by 'text-overflow:ellipsis' so that we always have a non-null - // |caret_position| here. - return PositionWithAffinityTemplate<Strategy>(); - } - NGInlineCursor line_box = caret_position.cursor; - line_box.MoveToContainingLine(); - const PhysicalOffset end_point = line_box.Current().LineEndPoint(); - return FromPositionInDOMTree<Strategy>( - line_box.PositionForPointInInlineBox(end_point)); - } - - const InlineBox* inline_box = - adjusted.IsNotNull() ? ComputeInlineBoxPosition(c).inline_box : nullptr; - if (!inline_box) { - // There are VisiblePositions at offset 0 in blocks without - // RootInlineBoxes, like empty editable blocks and bordered blocks. - const PositionTemplate<Strategy> p = c.GetPosition(); - if (p.AnchorNode()->GetLayoutObject() && - p.AnchorNode()->GetLayoutObject()->IsLayoutBlock() && - !p.ComputeEditingOffset()) - return c; - return PositionWithAffinityTemplate<Strategy>(); - } - - const RootInlineBox& root_box = inline_box->Root(); - const InlineBox* const end_box = Ordering::EndNonPseudoBoxOf(root_box); - if (!end_box) - return PositionWithAffinityTemplate<Strategy>(); - - const Node* const end_node = end_box->GetLineLayoutItem().NonPseudoNode(); - DCHECK(end_node); - if (IsA<HTMLBRElement>(*end_node)) { - return PositionWithAffinityTemplate<Strategy>( - PositionTemplate<Strategy>::BeforeNode(*end_node), - TextAffinity::kUpstreamIfPossible); - } - - auto* end_text_node = DynamicTo<Text>(end_node); - if (end_box->IsInlineTextBox() && end_text_node) { - const InlineTextBox* end_text_box = ToInlineTextBox(end_box); - int end_offset = end_text_box->Start(); - if (!end_text_box->IsLineBreak()) - end_offset += end_text_box->Len(); - return PositionWithAffinityTemplate<Strategy>( - PositionTemplate<Strategy>(end_text_node, end_offset), - TextAffinity::kUpstreamIfPossible); - } - return PositionWithAffinityTemplate<Strategy>( - PositionTemplate<Strategy>::AfterNode(*end_node), - TextAffinity::kUpstreamIfPossible); -} - // TODO(yosin) Rename this function to reflect the fact it ignores bidi levels. template <typename Strategy> static PositionWithAffinityTemplate<Strategy> EndOfLineAlgorithm( @@ -307,26 +385,8 @@ static PositionWithAffinityTemplate<Strategy> EndOfLineAlgorithm( const PositionWithAffinityTemplate<Strategy>& candidate_position = EndPositionForLine<Strategy, VisualOrdering>(current_position); - // Make sure the end of line is at the same line as the given input - // position. Else use the previous position to obtain end of line. This - // condition happens when the input position is before the space character - // at the end of a soft-wrapped non-editable line. In this scenario, - // |endPositionForLine()| would incorrectly hand back a position in the next - // line instead. This fix is to account for the discrepancy between lines - // with "webkit-line-break:after-white-space" style versus lines without - // that style, which would break before a space by default. - if (InSameLine(current_position, candidate_position)) { - return AdjustForwardPositionToAvoidCrossingEditingBoundaries( - candidate_position, current_position.GetPosition()); - } - const PositionWithAffinityTemplate<Strategy>& adjusted_position = - PreviousPositionOf(CreateVisiblePosition(current_position)) - .ToPositionWithAffinity(); - if (adjusted_position.IsNull()) - return PositionWithAffinityTemplate<Strategy>(); return AdjustForwardPositionToAvoidCrossingEditingBoundaries( - EndPositionForLine<Strategy, VisualOrdering>(adjusted_position), - current_position.GetPosition()); + candidate_position, current_position.GetPosition()); } static PositionWithAffinity EndOfLine(const PositionWithAffinity& position) { @@ -366,33 +426,20 @@ static PositionWithAffinityTemplate<Strategy> LogicalEndOfLineAlgorithm( const PositionWithAffinityTemplate<Strategy>& current_position) { // TODO(yosin) this is the current behavior that might need to be fixed. // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. - PositionWithAffinityTemplate<Strategy> vis_pos = + const PositionWithAffinityTemplate<Strategy> candidate_position = EndPositionForLine<Strategy, LogicalOrdering>(current_position); - // Make sure the end of line is at the same line as the given input - // position. For a wrapping line, the logical end position for the - // not-last-2-lines might incorrectly hand back the logical beginning of the - // next line. For example, - // <div contenteditable dir="rtl" style="line-break:before-white-space">xyz - // a xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz </div> - // In this case, use the previous position of the computed logical end - // position. - if (!InSameLogicalLine(current_position, vis_pos)) { - vis_pos = PreviousPositionOf(CreateVisiblePosition(vis_pos)) - .ToPositionWithAffinity(); - } - if (ContainerNode* editable_root = HighestEditableRoot(current_position.GetPosition())) { if (!editable_root->contains( - vis_pos.GetPosition().ComputeContainerNode())) { + candidate_position.GetPosition().ComputeContainerNode())) { return PositionWithAffinityTemplate<Strategy>( PositionTemplate<Strategy>::LastPositionInNode(*editable_root)); } } return AdjustForwardPositionToAvoidCrossingEditingBoundaries( - vis_pos, current_position.GetPosition()); + candidate_position, current_position.GetPosition()); } static PositionWithAffinity LogicalEndOfLine( @@ -435,10 +482,11 @@ static bool InSameLineAlgorithm( if (block1 || block2) { if (block1 != block2) return false; - // TODO(editing-dev): We may incorrectly return false if a position is in - // an empty NG block with height, in which case there is no line box. We - // must handle this case when enabling Layout NG for contenteditable. - return InSameNGLineBox(position1, position2); + if (!InSameNGLineBox(position1, position2)) + return false; + // See (ParameterizedVisibleUnitsLineTest.InSameLineWithMixedEditability + return RootEditableElementOf(position1.GetPosition()) == + RootEditableElementOf(position2.GetPosition()); } // Neither positions are in LayoutNG. Fall through to legacy handling. |