blob: 34765590077891335a57738fba605a1c03934fc4 [file] [log] [blame]
// 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/editing/selection_adjuster.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/html/forms/text_control_element.h"
namespace blink {
class SelectionAdjusterTest : public EditingTestBase {};
// ------------ Shadow boundary adjustment tests --------------
TEST_F(SelectionAdjusterTest, AdjustShadowToCollpasedInDOMTree) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(
"<span><template data-mode=\"open\">a|bc</template></span>^");
const SelectionInDOMTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingShadowBoundaries(
selection);
EXPECT_EQ("<span></span>|", GetSelectionTextFromBody(result));
}
TEST_F(SelectionAdjusterTest, AdjustShadowToCollpasedInFlatTree) {
SetBodyContent("<input value=abc>");
const auto& input = ToTextControl(*GetDocument().QuerySelector("input"));
const SelectionInFlatTree& selection =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::AfterNode(input))
.Extend(
PositionInFlatTree(*input.InnerEditorElement()->firstChild(), 1))
.Build();
const SelectionInFlatTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingShadowBoundaries(
selection);
EXPECT_EQ("<input value=\"abc\"><div>abc</div></input>|",
GetSelectionTextInFlatTreeFromBody(result));
}
// ------------ Editing boundary adjustment tests --------------
// Extracted the related part from delete-non-editable-range-crash.html here,
// because the final result in that test was not WAI.
TEST_F(SelectionAdjusterTest, DeleteNonEditableRange) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(R"HTML(
<div contenteditable>
<blockquote>
<span>^foo<br></span>
barbarbar
</blockquote>
<span contenteditable="false">
<span contenteditable>|</span>
<ol>bar</ol>
</span>
</div>)HTML");
const SelectionInDOMTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div contenteditable>
<blockquote>
<span>^foo<br></span>
barbarbar
</blockquote>
|<span contenteditable="false">
<span contenteditable></span>
<ol>bar</ol>
</span>
</div>)HTML",
GetSelectionTextFromBody(result));
}
// Extracted the related part from format-block-contenteditable-false.html here,
// because the final result in that test was not WAI.
TEST_F(SelectionAdjusterTest, FormatBlockContentEditableFalse) {
const SelectionInDOMTree& selection = SetSelectionTextToBody(R"HTML(
<div contenteditable>
<h1><i>^foo</i><br><i>baz</i></h1>
<div contenteditable="false">|bar</div>
</div>)HTML");
const SelectionInDOMTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div contenteditable>
<h1><i>^foo</i><br><i>baz</i></h1>
|<div contenteditable="false">bar</div>
</div>)HTML",
GetSelectionTextFromBody(result));
}
TEST_F(SelectionAdjusterTest, NestedContentEditableElements) {
// Select from bar to foo.
const SelectionInDOMTree& selection = SetSelectionTextToBody(R"HTML(
<div contenteditable>
<div contenteditable="false">
<div contenteditable>
|foo
</div>
</div>
<br>
bar^
</div>)HTML");
const SelectionInDOMTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div contenteditable>
<div contenteditable="false">
<div contenteditable>
foo
</div>
</div>|
<br>
bar^
</div>)HTML",
GetSelectionTextFromBody(result));
}
TEST_F(SelectionAdjusterTest, ShadowRootAsRootBoundaryElement) {
const char* body_content = "<div id='host'></div>";
const char* shadow_content = "<div id='foo'>foo</div><div id='bar'>bar</div>";
SetBodyContent(body_content);
ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
Element* foo = shadow_root->QuerySelector("#foo");
Element* bar = shadow_root->QuerySelector("#bar");
// DOM tree selection.
const SelectionInDOMTree& selection =
SelectionInDOMTree::Builder()
.Collapse(Position::FirstPositionInNode(*foo))
.Extend(Position::LastPositionInNode(*bar))
.Build();
const SelectionInDOMTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(Position::FirstPositionInNode(*foo), result.Base());
EXPECT_EQ(Position::LastPositionInNode(*bar), result.Extent());
// Flat tree selection.
const SelectionInFlatTree& selection_in_flat_tree =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*foo))
.Extend(PositionInFlatTree::LastPositionInNode(*bar))
.Build();
const SelectionInFlatTree& result_in_flat_tree =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection_in_flat_tree);
EXPECT_EQ(PositionInFlatTree::FirstPositionInNode(*foo),
result_in_flat_tree.Base());
EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*bar),
result_in_flat_tree.Extent());
}
TEST_F(SelectionAdjusterTest, ShadowRootAsRootBoundaryElementEditable) {
const char* body_content = "<div id='host'></div>";
const char* shadow_content =
"foo"
"<div id='bar' contenteditable>bar</div>";
SetBodyContent(body_content);
ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
const Node* foo = shadow_root->firstChild();
const Element* bar = shadow_root->QuerySelector("#bar");
// Select from foo to bar in DOM tree.
const SelectionInDOMTree& selection =
SelectionInDOMTree::Builder()
.Collapse(Position::FirstPositionInNode(*foo))
.Extend(Position::LastPositionInNode(*bar))
.Build();
const SelectionInDOMTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(Position::FirstPositionInNode(*foo), result.Base());
EXPECT_EQ(Position::BeforeNode(*bar), result.Extent());
// Select from foo to bar in flat tree.
const SelectionInFlatTree& selection_in_flat_tree =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*foo))
.Extend(PositionInFlatTree::LastPositionInNode(*bar))
.Build();
const SelectionInFlatTree& result_in_flat_tree =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection_in_flat_tree);
EXPECT_EQ(PositionInFlatTree::FirstPositionInNode(*foo),
result_in_flat_tree.Base());
EXPECT_EQ(PositionInFlatTree::BeforeNode(*bar), result_in_flat_tree.Extent());
// Select from bar to foo in DOM tree.
const SelectionInDOMTree& selection2 =
SelectionInDOMTree::Builder()
.Collapse(Position::LastPositionInNode(*bar))
.Extend(Position::FirstPositionInNode(*foo))
.Build();
const SelectionInDOMTree& result2 =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection2);
EXPECT_EQ(Position::LastPositionInNode(*bar), result2.Base());
EXPECT_EQ(Position::FirstPositionInNode(*bar), result2.Extent());
// Select from bar to foo in flat tree.
const SelectionInFlatTree& selection_in_flat_tree2 =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::LastPositionInNode(*bar))
.Extend(PositionInFlatTree::FirstPositionInNode(*foo))
.Build();
const SelectionInFlatTree& result_in_flat_tree2 =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection_in_flat_tree2);
EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*bar),
result_in_flat_tree2.Base());
EXPECT_EQ(PositionInFlatTree::FirstPositionInNode(*bar),
result_in_flat_tree2.Extent());
}
TEST_F(SelectionAdjusterTest, ShadowDistributedNodesWithoutEditingBoundary) {
const char* body_content = R"HTML(
<div id=host>
<div id=foo slot=foo>foo</div>
<div id=bar slot=bar>bar</div>
</div>)HTML";
const char* shadow_content = R"HTML(
<div>
<div id=s1>111</div>
<slot name=foo></slot>
<div id=s2>222</div>
<slot name=bar></slot>
<div id=s3>333</div>
</div>)HTML";
SetBodyContent(body_content);
Element* host = GetDocument().getElementById("host");
ShadowRoot& shadow_root =
host->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.SetInnerHTMLFromString(shadow_content);
Element* foo = GetDocument().getElementById("foo");
Element* s1 = shadow_root.QuerySelector("#s1");
// Select from 111 to foo.
const SelectionInFlatTree& selection =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*s1))
.Extend(PositionInFlatTree::LastPositionInNode(*foo))
.Build();
const SelectionInFlatTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div id="host">
<div>
<div id="s1">^111</div>
<slot name="foo"><div id="foo" slot="foo">foo|</div></slot>
<div id="s2">222</div>
<slot name="bar"><div id="bar" slot="bar">bar</div></slot>
<div id="s3">333</div>
</div></div>)HTML",
GetSelectionTextInFlatTreeFromBody(result));
// Select from foo to 111.
const SelectionInFlatTree& selection2 =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::LastPositionInNode(*foo))
.Extend(PositionInFlatTree::FirstPositionInNode(*s1))
.Build();
const SelectionInFlatTree& result2 =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection2);
EXPECT_EQ(R"HTML(
<div id="host">
<div>
<div id="s1">|111</div>
<slot name="foo"><div id="foo" slot="foo">foo^</div></slot>
<div id="s2">222</div>
<slot name="bar"><div id="bar" slot="bar">bar</div></slot>
<div id="s3">333</div>
</div></div>)HTML",
GetSelectionTextInFlatTreeFromBody(result2));
}
// This test is just recording the behavior of current implementation, can be
// changed.
TEST_F(SelectionAdjusterTest, ShadowDistributedNodesWithEditingBoundary) {
const char* body_content = R"HTML(
<div contenteditable id=host>
<div id=foo slot=foo>foo</div>
<div id=bar slot=bar>bar</div>
</div>)HTML";
const char* shadow_content = R"HTML(
<div>
<div id=s1>111</div>
<slot name=foo></slot>
<div id=s2>222</div>
<slot name=bar></slot>
<div id=s3>333</div>
</div>)HTML";
SetBodyContent(body_content);
Element* host = GetDocument().getElementById("host");
ShadowRoot& shadow_root =
host->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.SetInnerHTMLFromString(shadow_content);
Element* foo = GetDocument().getElementById("foo");
Element* bar = GetDocument().getElementById("bar");
Element* s1 = shadow_root.QuerySelector("#s1");
Element* s2 = shadow_root.QuerySelector("#s2");
// Select from 111 to foo.
const SelectionInFlatTree& selection =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*s1))
.Extend(PositionInFlatTree::LastPositionInNode(*foo))
.Build();
const SelectionInFlatTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div contenteditable id="host">
<div>
<div id="s1">^111</div>
<slot name="foo">|<div id="foo" slot="foo">foo</div></slot>
<div id="s2">222</div>
<slot name="bar"><div id="bar" slot="bar">bar</div></slot>
<div id="s3">333</div>
</div></div>)HTML",
GetSelectionTextInFlatTreeFromBody(result));
// Select from foo to 111.
const SelectionInFlatTree& selection2 =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::LastPositionInNode(*foo))
.Extend(PositionInFlatTree::FirstPositionInNode(*s1))
.Build();
const SelectionInFlatTree& result2 =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection2);
EXPECT_EQ(R"HTML(
<div contenteditable id="host">
<div>
<div id="s1">111</div>
<slot name="foo"><div id="foo" slot="foo">|foo^</div></slot>
<div id="s2">222</div>
<slot name="bar"><div id="bar" slot="bar">bar</div></slot>
<div id="s3">333</div>
</div></div>)HTML",
GetSelectionTextInFlatTreeFromBody(result2));
// Select from 111 to 222.
const SelectionInFlatTree& selection3 =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*s1))
.Extend(PositionInFlatTree::LastPositionInNode(*s2))
.Build();
const SelectionInFlatTree& result3 =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection3);
EXPECT_EQ(R"HTML(
<div contenteditable id="host">
<div>
<div id="s1">^111</div>
<slot name="foo"><div id="foo" slot="foo">foo</div></slot>
<div id="s2">222|</div>
<slot name="bar"><div id="bar" slot="bar">bar</div></slot>
<div id="s3">333</div>
</div></div>)HTML",
GetSelectionTextInFlatTreeFromBody(result3));
// Select from foo to bar.
const SelectionInFlatTree& selection4 =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*foo))
.Extend(PositionInFlatTree::LastPositionInNode(*bar))
.Build();
const SelectionInFlatTree& result4 =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection4);
EXPECT_EQ(R"HTML(
<div contenteditable id="host">
<div>
<div id="s1">111</div>
<slot name="foo"><div id="foo" slot="foo">^foo|</div></slot>
<div id="s2">222</div>
<slot name="bar"><div id="bar" slot="bar">bar</div></slot>
<div id="s3">333</div>
</div></div>)HTML",
GetSelectionTextInFlatTreeFromBody(result4));
}
TEST_F(SelectionAdjusterTest, EditingBoundaryOutsideOfShadowTree) {
SetBodyContent(R"HTML(
<div>
<div id=base>base</div>
<div id=div1 contenteditable>
55
<div id=host></div>
</div>
</div>)HTML");
ShadowRoot* shadow_root =
SetShadowContent("<div id=extent>extent</div>", "host");
Element* base = GetDocument().getElementById("base");
Element* extent = shadow_root->QuerySelector("#extent");
const SelectionInFlatTree& selection =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*base))
.Extend(PositionInFlatTree::LastPositionInNode(*extent))
.Build();
const SelectionInFlatTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div>
<div id="base">^base</div>
|<div contenteditable id="div1">
55
<div id="host"><div id="extent">extent</div></div>
</div>
</div>)HTML",
GetSelectionTextInFlatTreeFromBody(result));
}
TEST_F(SelectionAdjusterTest, EditingBoundaryInsideOfShadowTree) {
SetBodyContent(R"HTML(
<div>
<div id=base>base</div>
<div id=host>foo</div>
</div>)HTML");
ShadowRoot* shadow_root = SetShadowContent(R"HTML(
<div>
<div>bar</div>
<div contenteditable id=extent>extent</div>
<div>baz</div>
</div>)HTML",
"host");
Element* base = GetDocument().getElementById("base");
Element* extent = shadow_root->QuerySelector("#extent");
const SelectionInFlatTree& selection =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*base))
.Extend(PositionInFlatTree::LastPositionInNode(*extent))
.Build();
const SelectionInFlatTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div>
<div id="base">^base</div>
<div id="host">
<div>
<div>bar</div>
|<div contenteditable id="extent">extent</div>
<div>baz</div>
</div></div>
</div>)HTML",
GetSelectionTextInFlatTreeFromBody(result));
}
// The current behavior of shadow host and shadow tree are editable is we can't
// cross the shadow boundary.
TEST_F(SelectionAdjusterTest, ShadowHostAndShadowTreeAreEditable) {
SetBodyContent(R"HTML(
<div contenteditable>
<div id=foo>foo</div>
<div id=host></div>
</div>)HTML");
ShadowRoot* shadow_root =
SetShadowContent("<div contenteditable id=bar>bar</div>", "host");
Element* foo = GetDocument().getElementById("foo");
Element* bar = shadow_root->QuerySelector("#bar");
// Select from foo to bar.
const SelectionInFlatTree& selection =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::FirstPositionInNode(*foo))
.Extend(PositionInFlatTree::LastPositionInNode(*bar))
.Build();
const SelectionInFlatTree& result =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection);
EXPECT_EQ(R"HTML(
<div contenteditable>
<div id="foo">^foo</div>
<div id="host">|<div contenteditable id="bar">bar</div></div>
</div>)HTML",
GetSelectionTextInFlatTreeFromBody(result));
// Select from bar to foo.
const SelectionInFlatTree& selection2 =
SelectionInFlatTree::Builder()
.Collapse(PositionInFlatTree::LastPositionInNode(*bar))
.Extend(PositionInFlatTree::FirstPositionInNode(*foo))
.Build();
const SelectionInFlatTree& result2 =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
selection2);
EXPECT_EQ(R"HTML(
<div contenteditable>
<div id="foo">foo</div>
<div id="host"><div contenteditable id="bar">|bar^</div></div>
</div>)HTML",
GetSelectionTextInFlatTreeFromBody(result2));
}
} // namespace blink