Commit 4c414662 authored by Yoshifumi Inoue's avatar Yoshifumi Inoue Committed by Commit Bot

Introduce TextMappingOffset::InlineContents class

This patch introduces |TextMappingOffset::InlineContents| class to represent
a run of inline layout objects with iterator as preparation of computing
inline layout object run from |LayoutBlockFlow|.

Bug: 829234
Change-Id: If2fc23545b3983c7798553460d1cf06d08dd337c
Reviewed-on: https://chromium-review.googlesource.com/1018923Reviewed-by: default avatarXiaocheng Hu <xiaochengh@chromium.org>
Commit-Queue: Yoshifumi Inoue <yosin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557409}
parent 3d1eea29
......@@ -5,6 +5,8 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_TEXT_OFFSET_MAPPING_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_TEXT_OFFSET_MAPPING_H_
#include <iosfwd>
#include <iterator>
#include "base/macros.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
......@@ -16,24 +18,140 @@
namespace blink {
class LayoutBlock;
class LayoutBlockFlow;
// Mapping between position and text offset in |LayoutBlock| == CSS Block
// with using characters from |TextIterator|.
// Mapping between position and text offset in "inline contents" with using
// characters from |TextIterator|.
//
// This class is similar to |NGOffsetMapping| which uses |text_offset_| in
// |NGInlineNodeData| except for
// The "inline contents" is a similar to "inline formatting context" defined
// in CSS 2.1 specification or |LayoutBlockFlow| for inline contents, except
// for:
// - Including characters from "display:inline-block".
// - Exclude "float" or "positioned" appeared in middle of inline contents
// - Treats characters with CSS property "-webkit-text-security" as "x"
// instead of a bullet (U+2022), which breaks words.
// - Contains characters in float and inline-blocks. |NGOffsetMapping| treats
// them as one object replacement character (U+FFFC). See |CollectInlines|
// in |NGInlineNode|.
class CORE_EXPORT TextOffsetMapping final {
STACK_ALLOCATED();
public:
// Constructor |TextOffsetMapping| for specified |LayoutBlock|.
explicit TextOffsetMapping(const LayoutBlock&);
// |InlineContents| represents "inline contents" in a range of layout tree.
class CORE_EXPORT InlineContents final {
STACK_ALLOCATED();
public:
// |first| and |last|(inclusive) represent inline layout object run, they
// should be descendants of |block_flow|.
InlineContents(const LayoutBlockFlow& block_flow,
const LayoutObject& first,
const LayoutObject& last);
// |block_flow| must be non-anonymous empty block or block containing only
// anonymous object.
InlineContents(const LayoutBlockFlow& block_flow);
InlineContents() = default;
bool operator==(const InlineContents& other) const;
bool operator!=(const InlineContents& other) const {
return !operator==(other);
}
const LayoutBlockFlow* GetEmptyBlock() const;
const LayoutObject& FirstLayoutObject() const;
const LayoutObject& LastLayoutObject() const;
EphemeralRangeInFlatTree GetRange() const;
bool IsNotNull() const { return !IsNull(); }
bool IsNull() const { return !block_flow_; }
// Returns |InlineContents| from |block_flow_| toward last of layout tree.
static InlineContents NextOf(const InlineContents&);
// Returns |InlineContents| from |block_flow_| toward first of layout tree.
static InlineContents PreviousOf(const InlineContents&);
private:
friend class TextOffsetMapping;
const LayoutBlockFlow* block_flow_ = nullptr;
const LayoutObject* first_ = nullptr;
const LayoutObject* last_ = nullptr;
};
// |BackwardRange| class is used with range-for to traverse inline contents
// toward start of document.
class CORE_EXPORT BackwardRange final {
STACK_ALLOCATED();
public:
class CORE_EXPORT Iterator
: public std::iterator<std::input_iterator_tag, InlineContents> {
STACK_ALLOCATED();
public:
explicit Iterator(const InlineContents& current) : current_(current) {}
Iterator() = default;
InlineContents operator*() const;
void operator++();
bool operator==(const Iterator& other) const {
return current_ == other.current_;
}
bool operator!=(const Iterator& other) const {
return !operator==(other);
}
private:
InlineContents current_;
};
explicit BackwardRange(const InlineContents& start) : start_(start) {}
Iterator begin() const { return Iterator(start_); }
Iterator end() const { return Iterator(); }
private:
const InlineContents start_;
};
// |ForwardRange| class is used with range-for to traverse inline contents
// toward end of document.
class CORE_EXPORT ForwardRange final {
STACK_ALLOCATED();
public:
class CORE_EXPORT Iterator
: public std::iterator<std::forward_iterator_tag, InlineContents> {
STACK_ALLOCATED();
public:
explicit Iterator(const InlineContents& current) : current_(current) {}
Iterator() = default;
InlineContents operator*() const;
void operator++();
bool operator==(const Iterator& other) const {
return current_ == other.current_;
}
bool operator!=(const Iterator& other) const {
return !operator==(other);
}
private:
InlineContents current_;
};
explicit ForwardRange(const InlineContents& start) : start_(start) {}
Iterator begin() const { return Iterator(start_); }
Iterator end() const { return Iterator(); }
private:
const InlineContents start_;
};
// Constructor |TextOffsetMapping| for the |inline_contents|.
explicit TextOffsetMapping(const InlineContents& inline_contents);
~TextOffsetMapping() = default;
......@@ -61,13 +179,27 @@ class CORE_EXPORT TextOffsetMapping final {
// This function is used for computing trailing whitespace after word.
unsigned FindNonWhitespaceCharacterFrom(unsigned offset) const;
// Helper functions to constructor |TextOffsetMapping|.
static const LayoutBlock& ComputeContainigBlock(const PositionInFlatTree&);
static LayoutBlock* NextBlockFor(const LayoutBlock&);
static LayoutBlock* PreviousBlockFor(const LayoutBlock&);
// Helper functions to construct |TextOffsetMapping|.
// Returns a |BackwardRange| for backward iteration of |InlineContents|
// from |InlineContens| containing |position|.
static BackwardRange BackwardRangeOf(const PositionInFlatTree& position);
// Returns a |ForwardRange| for forward iteration of |InlineContents|
// from |InlineContens| containing |position|.
static ForwardRange ForwardRangeOf(const PositionInFlatTree& position);
// Returns |LayoutBlockFlow| satisfying |IsInlineContents()| from |position|
// (inclusive) toward start of document, or null if no such |LayoutBlockFlow|.
static InlineContents FindBackwardInlineContents(
const PositionInFlatTree& position);
// Returns |LayoutBlockFlow| satisfying |IsInlineContents()| from |position|
// (inclusive) toward end of document, or null if no such |LayoutBlockFlow|.
static InlineContents FindForwardInlineContents(const PositionInFlatTree&);
private:
TextOffsetMapping(const LayoutBlock&, const TextIteratorBehavior);
TextOffsetMapping(const InlineContents&, const TextIteratorBehavior&);
const TextIteratorBehavior behavior_;
const EphemeralRangeInFlatTree range_;
......@@ -76,6 +208,9 @@ class CORE_EXPORT TextOffsetMapping final {
DISALLOW_COPY_AND_ASSIGN(TextOffsetMapping);
};
CORE_EXPORT std::ostream& operator<<(std::ostream&,
const TextOffsetMapping::InlineContents&);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_TEXT_OFFSET_MAPPING_H_
......@@ -9,6 +9,7 @@
#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/testing/editing_test_base.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
......@@ -27,8 +28,7 @@ class ParameterizedTextOffsetMappingTest
std::string ComputeTextOffset(const std::string& selection_text) {
const PositionInFlatTree position =
ToPositionInFlatTree(SetSelectionTextToBody(selection_text).Base());
TextOffsetMapping mapping(
TextOffsetMapping::ComputeContainigBlock(position));
TextOffsetMapping mapping(GetInlineContents(position));
const String text = mapping.GetText();
const int offset = mapping.ComputeTextOffset(position);
StringBuilder builder;
......@@ -42,8 +42,7 @@ class ParameterizedTextOffsetMappingTest
std::string GetRange(const std::string& selection_text) {
const PositionInFlatTree position =
ToPositionInFlatTree(SetSelectionTextToBody(selection_text).Base());
TextOffsetMapping mapping(
TextOffsetMapping::ComputeContainigBlock(position));
TextOffsetMapping mapping(GetInlineContents(position));
return GetSelectionTextInFlatTreeFromBody(
SelectionInFlatTree::Builder()
.SetBaseAndExtent(mapping.GetRange())
......@@ -52,7 +51,7 @@ class ParameterizedTextOffsetMappingTest
std::string GetPositionBefore(const std::string& html_text, int offset) {
SetBodyContent(html_text);
TextOffsetMapping mapping(TextOffsetMapping::ComputeContainigBlock(
TextOffsetMapping mapping(GetInlineContents(
PositionInFlatTree(*GetDocument().body()->firstChild(), 0)));
return GetSelectionTextInFlatTreeFromBody(
SelectionInFlatTree::Builder()
......@@ -62,13 +61,22 @@ class ParameterizedTextOffsetMappingTest
std::string GetPositionAfter(const std::string& html_text, int offset) {
SetBodyContent(html_text);
TextOffsetMapping mapping(TextOffsetMapping::ComputeContainigBlock(
TextOffsetMapping mapping(GetInlineContents(
PositionInFlatTree(*GetDocument().body()->firstChild(), 0)));
return GetSelectionTextInFlatTreeFromBody(
SelectionInFlatTree::Builder()
.Collapse(mapping.GetPositionAfter(offset))
.Build());
}
private:
static TextOffsetMapping::InlineContents GetInlineContents(
const PositionInFlatTree& position) {
const TextOffsetMapping::InlineContents inline_contents =
TextOffsetMapping::FindForwardInlineContents(position);
DCHECK(inline_contents.IsNotNull()) << position;
return inline_contents;
}
};
INSTANTIATE_TEST_CASE_P(All,
......@@ -207,9 +215,123 @@ TEST_P(ParameterizedTextOffsetMappingTest, RangeOfBlockWithTABLE) {
<< "After TABLE";
}
// |InlineContents| can represent an empty block.
// See LinkSelectionClickEventsTest.SingleAndDoubleClickWillBeHandled
TEST_P(ParameterizedTextOffsetMappingTest, RangeOfEmptyBlock) {
EXPECT_EQ("<div><p>abc</p><p>|</p><p>ghi</p></div>",
GetRange("<div><p>abc</p><p>|</p><p>ghi</p></div>"));
const PositionInFlatTree position = ToPositionInFlatTree(
SetSelectionTextToBody(
"<div><p>abc</p><p id='target'>|</p><p>ghi</p></div>")
.Base());
const LayoutObject* const target_layout_object =
GetDocument().getElementById("target")->GetLayoutObject();
const TextOffsetMapping::InlineContents inline_contents =
TextOffsetMapping::FindForwardInlineContents(position);
ASSERT_TRUE(inline_contents.IsNotNull());
EXPECT_EQ(target_layout_object, inline_contents.GetEmptyBlock());
EXPECT_EQ(inline_contents,
TextOffsetMapping::FindBackwardInlineContents(position));
}
// http://crbug.com/832497
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithCollapsedWhitespace) {
// Whitespaces after <div> is collapsed.
EXPECT_EQ(" <div> ^<a></a>|</div>", GetRange("| <div> <a></a></div>"));
}
// http://crbug.com//832055
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithMulticol) {
InsertStyleElement("div { columns: 3 100px; }");
EXPECT_EQ("<div>^<b>foo|</b></div>", GetRange("<div><b>foo|</b></div>"));
}
// http://crbug.com/832101
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithNestedFloat) {
InsertStyleElement("b, i { float: right; }");
// Note: Legacy: BODY is inline, NG: BODY is block.
EXPECT_EQ(LayoutNGEnabled() ? "<b>^abc <i>def</i> ghi|</b>xyz"
: "^<b>abc <i>def</i> ghi</b>xyz|",
GetRange("<b>abc <i>d|ef</i> ghi</b>xyz"));
}
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithNestedInlineBlock) {
InsertStyleElement("b, i { display: inline-block; }");
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("|<b>a <i>b</i> d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>|a <i>b</i> d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a| <i>b</i> d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a |<i>b</i> d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a <i>|b</i> d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a <i>b|</i> d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a <i>b</i>| d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a <i>b</i> |d</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a <i>b</i> d|</b>e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a <i>b</i> d</b>|e"));
EXPECT_EQ("^<b>a <i>b</i> d</b>e|", GetRange("<b>a <i>b</i> d</b>e|"));
}
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithInlineBlockBlock) {
InsertStyleElement("b { display:inline-block; }");
// TODO(editing-dev): We should have "^a<b>b|<p>"
EXPECT_EQ("^a<b>b<p>c</p>d</b>e|", GetRange("|a<b>b<p>c</p>d</b>e"));
EXPECT_EQ("^a<b>b<p>c</p>d</b>e|", GetRange("a|<b>b<p>c</p>d</b>e"));
EXPECT_EQ("a<b>^b|<p>c</p>d</b>e", GetRange("a<b>|b<p>c</p>d</b>e"));
EXPECT_EQ("a<b>^b|<p>c</p>d</b>e", GetRange("a<b>b|<p>c</p>d</b>e"));
EXPECT_EQ("a<b>b<p>^c|</p>d</b>e", GetRange("a<b>b<p>|c</p>d</b>e"));
EXPECT_EQ("a<b>b<p>^c|</p>d</b>e", GetRange("a<b>b<p>c|</p>d</b>e"));
EXPECT_EQ("a<b>b<p>c</p>^d|</b>e", GetRange("a<b>b<p>c</p>|d</b>e"));
EXPECT_EQ("^a<b>b<p>c</p>d</b>e|", GetRange("a<b>b<p>c</p>d</b>|e"));
EXPECT_EQ("^a<b>b<p>c</p>d</b>e|", GetRange("a<b>b<p>c</p>d</b>e|"));
}
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithInlineBlockBlocks) {
InsertStyleElement("b { display:inline-block; }");
// TODO(editing-dev): We should have "^a|"
EXPECT_EQ("^a<b><p>b</p><p>c</p></b>d|",
GetRange("|a<b><p>b</p><p>c</p></b>d"));
EXPECT_EQ("^a<b><p>b</p><p>c</p></b>d|",
GetRange("a|<b><p>b</p><p>c</p></b>d"));
EXPECT_EQ("a<b><p>^b|</p><p>c</p></b>d",
GetRange("a<b>|<p>b</p><p>c</p></b>d"));
EXPECT_EQ("a<b><p>^b|</p><p>c</p></b>d",
GetRange("a<b><p>|b</p><p>c</p></b>d"));
EXPECT_EQ("a<b><p>^b|</p><p>c</p></b>d",
GetRange("a<b><p>b|</p><p>c</p></b>d"));
EXPECT_EQ("a<b><p>b</p><p>^c|</p></b>d",
GetRange("a<b><p>b</p>|<p>c</p></b>d"));
EXPECT_EQ("a<b><p>b</p><p>^c|</p></b>d",
GetRange("a<b><p>b</p><p>|c</p></b>d"));
EXPECT_EQ("a<b><p>b</p><p>^c|</p></b>d",
GetRange("a<b><p>b</p><p>c|</p></b>d"));
EXPECT_EQ("^a<b><p>b</p><p>c</p></b>d|",
GetRange("a<b><p>b</p><p>c</p>|</b>d"));
EXPECT_EQ("^a<b><p>b</p><p>c</p></b>d|",
GetRange("a<b><p>b</p><p>c</p></b>|d"));
EXPECT_EQ("^a<b><p>b</p><p>c</p></b>d|",
GetRange("a<b><p>b</p><p>c</p></b>d|"));
}
// http://crbug.com/832101
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithNestedPosition) {
InsertStyleElement("b, i { position: fixed; }");
EXPECT_EQ("<b>abc <i>^def|</i> ghi</b>xyz",
GetRange("<b>abc <i>d|ef</i> ghi</b>xyz"));
}
// http://crbug.com//834623
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithSelect) {
EXPECT_EQ(
"^<select>"
"<slot name=\"user-agent-custom-assign-slot\"></slot>"
"</select>foo|",
GetRange("<select>|</select>foo"));
}
// http://crbug.com//832350
TEST_P(ParameterizedTextOffsetMappingTest, RangeWithShadowDOM) {
EXPECT_EQ("<div><slot>^abc|</slot></div>",
GetRange("<div>"
"<template data-mode='open'><slot></slot></template>"
"|abc"
"</div>"));
}
TEST_P(ParameterizedTextOffsetMappingTest, GetPositionBefore) {
......
......@@ -34,7 +34,7 @@
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/text_offset_mapping.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/layout/layout_block.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/text/text_boundaries.h"
......@@ -82,23 +82,20 @@ PositionTemplate<Strategy> EndOfWordAlgorithm(
PositionInFlatTree NextWordPositionInternal(
const PositionInFlatTree& position) {
DCHECK(position.IsNotNull());
PositionInFlatTree block_end_position;
for (const LayoutBlock* block =
&TextOffsetMapping::ComputeContainigBlock(position);
block; block = TextOffsetMapping::NextBlockFor(*block)) {
const TextOffsetMapping mapping(*block);
PositionInFlatTree last_position = position;
for (const auto& inline_contents :
TextOffsetMapping::ForwardRangeOf(position)) {
const TextOffsetMapping mapping(inline_contents);
const String text = mapping.GetText();
const int offset =
block_end_position.IsNull() ? mapping.ComputeTextOffset(position) : 0;
last_position == position ? mapping.ComputeTextOffset(position) : 0;
const int word_end =
FindNextWordForward(text.Characters16(), text.length(), offset);
if (offset < word_end)
return mapping.GetPositionAfter(word_end);
block_end_position = mapping.GetRange().EndPosition();
last_position = mapping.GetRange().EndPosition();
}
// TODO(yosin): Once we have a case, we should remove following |DCHECK()|.
DCHECK(block_end_position.IsNotNull()) << block_end_position;
return block_end_position;
return last_position;
}
unsigned PreviousWordPositionBoundary(
......
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