summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/editing/markers/document_marker_controller_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/editing/markers/document_marker_controller_test.cc')
-rw-r--r--chromium/third_party/blink/renderer/core/editing/markers/document_marker_controller_test.cc477
1 files changed, 477 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/core/editing/markers/document_marker_controller_test.cc b/chromium/third_party/blink/renderer/core/editing/markers/document_marker_controller_test.cc
new file mode 100644
index 00000000000..f1bcea1c5a4
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/editing/markers/document_marker_controller_test.cc
@@ -0,0 +1,477 @@
+/*
+ * Copyright (c) 2013, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
+
+#include <memory>
+#include "base/memory/scoped_refptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/range.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
+#include "third_party/blink/renderer/core/editing/markers/suggestion_marker.h"
+#include "third_party/blink/renderer/core/editing/markers/suggestion_marker_properties.h"
+#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+
+namespace blink {
+
+class DocumentMarkerControllerTest : public EditingTestBase {
+ protected:
+ DocumentMarkerController& MarkerController() const {
+ return GetDocument().Markers();
+ }
+
+ Text* CreateTextNode(const char*);
+ void MarkNodeContents(Node*);
+ void MarkNodeContentsTextMatch(Node*);
+};
+
+Text* DocumentMarkerControllerTest::CreateTextNode(const char* text_contents) {
+ return GetDocument().createTextNode(String::FromUTF8(text_contents));
+}
+
+void DocumentMarkerControllerTest::MarkNodeContents(Node* node) {
+ // Force layoutObjects to be created; TextIterator, which is used in
+ // DocumentMarkerControllerTest::addMarker(), needs them.
+ GetDocument().UpdateStyleAndLayout();
+ auto range = EphemeralRange::RangeOfContents(*node);
+ MarkerController().AddSpellingMarker(range);
+}
+
+void DocumentMarkerControllerTest::MarkNodeContentsTextMatch(Node* node) {
+ // Force layoutObjects to be created; TextIterator, which is used in
+ // DocumentMarkerControllerTest::addMarker(), needs them.
+ GetDocument().UpdateStyleAndLayout();
+ auto range = EphemeralRange::RangeOfContents(*node);
+ MarkerController().AddTextMatchMarker(range,
+ TextMatchMarker::MatchStatus::kActive);
+}
+
+TEST_F(DocumentMarkerControllerTest, DidMoveToNewDocument) {
+ SetBodyContent("<b><i>foo</i></b>");
+ Element* parent = ToElement(GetDocument().body()->firstChild()->firstChild());
+ MarkNodeContents(parent);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ Persistent<Document> another_document = Document::CreateForTest();
+ another_document->adoptNode(parent, ASSERT_NO_EXCEPTION);
+
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+ EXPECT_EQ(0u, another_document->Markers().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, NodeWillBeRemovedMarkedByNormalize) {
+ SetBodyContent("<b><i>foo</i></b>");
+ {
+ Element* parent =
+ ToElement(GetDocument().body()->firstChild()->firstChild());
+ parent->AppendChild(CreateTextNode("bar"));
+ MarkNodeContents(parent);
+ EXPECT_EQ(2u, MarkerController().Markers().size());
+ parent->normalize();
+ }
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, NodeWillBeRemovedMarkedByRemoveChildren) {
+ SetBodyContent("<b><i>foo</i></b>");
+ Element* parent = ToElement(GetDocument().body()->firstChild()->firstChild());
+ MarkNodeContents(parent);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ parent->RemoveChildren();
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, NodeWillBeRemovedByRemoveMarked) {
+ SetBodyContent("<b><i>foo</i></b>");
+ {
+ Element* parent =
+ ToElement(GetDocument().body()->firstChild()->firstChild());
+ MarkNodeContents(parent);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ parent->RemoveChild(parent->firstChild());
+ }
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, NodeWillBeRemovedMarkedByRemoveAncestor) {
+ SetBodyContent("<b><i>foo</i></b>");
+ {
+ Element* parent =
+ ToElement(GetDocument().body()->firstChild()->firstChild());
+ MarkNodeContents(parent);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ parent->parentNode()->parentNode()->RemoveChild(parent->parentNode());
+ }
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, NodeWillBeRemovedMarkedByRemoveParent) {
+ SetBodyContent("<b><i>foo</i></b>");
+ {
+ Element* parent =
+ ToElement(GetDocument().body()->firstChild()->firstChild());
+ MarkNodeContents(parent);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ parent->parentNode()->RemoveChild(parent);
+ }
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, NodeWillBeRemovedMarkedByReplaceChild) {
+ SetBodyContent("<b><i>foo</i></b>");
+ {
+ Element* parent =
+ ToElement(GetDocument().body()->firstChild()->firstChild());
+ MarkNodeContents(parent);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ parent->ReplaceChild(CreateTextNode("bar"), parent->firstChild());
+ }
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, NodeWillBeRemovedBySetInnerHTML) {
+ SetBodyContent("<b><i>foo</i></b>");
+ {
+ Element* parent =
+ ToElement(GetDocument().body()->firstChild()->firstChild());
+ MarkNodeContents(parent);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ SetBodyContent("");
+ }
+ // No more reference to marked node.
+ ThreadState::Current()->CollectAllGarbage();
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, UpdateRenderedRects) {
+ SetBodyContent("<div style='margin: 100px'>foo</div>");
+ Element* div = ToElement(GetDocument().body()->firstChild());
+ MarkNodeContentsTextMatch(div);
+ Vector<IntRect> rendered_rects =
+ MarkerController().LayoutRectsForTextMatchMarkers();
+ EXPECT_EQ(1u, rendered_rects.size());
+
+ div->setAttribute(HTMLNames::styleAttr, "margin: 200px");
+ GetDocument().UpdateStyleAndLayout();
+ Vector<IntRect> new_rendered_rects =
+ MarkerController().LayoutRectsForTextMatchMarkers();
+ EXPECT_EQ(1u, new_rendered_rects.size());
+ EXPECT_NE(rendered_rects[0], new_rendered_rects[0]);
+}
+
+TEST_F(DocumentMarkerControllerTest, CompositionMarkersNotMerged) {
+ SetBodyContent("<div style='margin: 100px'>foo</div>");
+ Node* text = GetDocument().body()->firstChild()->firstChild();
+ MarkerController().AddCompositionMarker(
+ EphemeralRange(Position(text, 0), Position(text, 1)), Color::kTransparent,
+ ui::mojom::ImeTextSpanThickness::kThin, Color::kBlack);
+ MarkerController().AddCompositionMarker(
+ EphemeralRange(Position(text, 1), Position(text, 3)), Color::kTransparent,
+ ui::mojom::ImeTextSpanThickness::kThick, Color::kBlack);
+
+ EXPECT_EQ(2u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, SetMarkerActiveTest) {
+ SetBodyContent("<b>foo</b>");
+ Element* b_element = ToElement(GetDocument().body()->firstChild());
+ EphemeralRange ephemeral_range = EphemeralRange::RangeOfContents(*b_element);
+ Position start_b_element =
+ ToPositionInDOMTree(ephemeral_range.StartPosition());
+ Position end_b_element = ToPositionInDOMTree(ephemeral_range.EndPosition());
+ const EphemeralRange range(start_b_element, end_b_element);
+ // Try to make active a marker that doesn't exist.
+ EXPECT_FALSE(MarkerController().SetTextMatchMarkersActive(range, true));
+
+ // Add a marker and try it once more.
+ MarkerController().AddTextMatchMarker(
+ range, TextMatchMarker::MatchStatus::kInactive);
+ EXPECT_EQ(1u, MarkerController().Markers().size());
+ EXPECT_TRUE(MarkerController().SetTextMatchMarkersActive(range, true));
+}
+
+TEST_F(DocumentMarkerControllerTest, RemoveStartOfMarker) {
+ SetBodyContent("<b>abc</b>");
+ Node* b_element = GetDocument().body()->firstChild();
+ Node* text = b_element->firstChild();
+
+ // Add marker under "abc"
+ EphemeralRange marker_range =
+ EphemeralRange(Position(text, 0), Position(text, 3));
+ GetDocument().Markers().AddTextMatchMarker(
+ marker_range, TextMatchMarker::MatchStatus::kInactive);
+
+ // Remove markers that overlap "a"
+ marker_range = EphemeralRange(Position(text, 0), Position(text, 1));
+ GetDocument().Markers().RemoveMarkersInRange(marker_range,
+ DocumentMarker::AllMarkers());
+
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, RemoveMiddleOfMarker) {
+ SetBodyContent("<b>abc</b>");
+ Node* b_element = GetDocument().body()->firstChild();
+ Node* text = b_element->firstChild();
+
+ // Add marker under "abc"
+ EphemeralRange marker_range =
+ EphemeralRange(Position(text, 0), Position(text, 3));
+ GetDocument().Markers().AddTextMatchMarker(
+ marker_range, TextMatchMarker::MatchStatus::kInactive);
+
+ // Remove markers that overlap "b"
+ marker_range = EphemeralRange(Position(text, 1), Position(text, 2));
+ GetDocument().Markers().RemoveMarkersInRange(marker_range,
+ DocumentMarker::AllMarkers());
+
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, RemoveEndOfMarker) {
+ SetBodyContent("<b>abc</b>");
+ Node* b_element = GetDocument().body()->firstChild();
+ Node* text = b_element->firstChild();
+
+ // Add marker under "abc"
+ EphemeralRange marker_range =
+ EphemeralRange(Position(text, 0), Position(text, 3));
+ GetDocument().Markers().AddTextMatchMarker(
+ marker_range, TextMatchMarker::MatchStatus::kInactive);
+
+ // Remove markers that overlap "c"
+ marker_range = EphemeralRange(Position(text, 2), Position(text, 3));
+ GetDocument().Markers().RemoveMarkersInRange(marker_range,
+ DocumentMarker::AllMarkers());
+
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, RemoveSpellingMarkersUnderWords) {
+ SetBodyContent("<div contenteditable>foo</div>");
+ Element* div = GetDocument().QuerySelector("div");
+ Node* text = div->firstChild();
+
+ // Add a spelling marker and a text match marker to "foo".
+ const EphemeralRange marker_range(Position(text, 0), Position(text, 3));
+ MarkerController().AddSpellingMarker(marker_range);
+ MarkerController().AddTextMatchMarker(
+ marker_range, TextMatchMarker::MatchStatus::kInactive);
+
+ MarkerController().RemoveSpellingMarkersUnderWords({"foo"});
+
+ // RemoveSpellingMarkersUnderWords does not remove text match marker.
+ ASSERT_EQ(1u, MarkerController().Markers().size());
+ const DocumentMarker& marker = *MarkerController().Markers()[0];
+ EXPECT_EQ(0u, marker.StartOffset());
+ EXPECT_EQ(3u, marker.EndOffset());
+ EXPECT_EQ(DocumentMarker::kTextMatch, marker.GetType());
+}
+
+TEST_F(DocumentMarkerControllerTest, RemoveSuggestionMarkerByTag) {
+ SetBodyContent("<div contenteditable>foo</div>");
+ Element* div = GetDocument().QuerySelector("div");
+ Node* text = div->firstChild();
+
+ MarkerController().AddSuggestionMarker(
+ EphemeralRange(Position(text, 0), Position(text, 1)),
+ SuggestionMarkerProperties());
+
+ ASSERT_EQ(1u, MarkerController().Markers().size());
+ const SuggestionMarker& marker =
+ *ToSuggestionMarker(MarkerController().Markers()[0]);
+ MarkerController().RemoveSuggestionMarkerByTag(text, marker.Tag());
+ EXPECT_EQ(0u, MarkerController().Markers().size());
+}
+
+TEST_F(DocumentMarkerControllerTest, FirstMarkerIntersectingOffsetRange) {
+ SetBodyContent("<div contenteditable>123456789</div>");
+ GetDocument().UpdateStyleAndLayout();
+ Element* div = GetDocument().QuerySelector("div");
+ Text* text = ToText(div->firstChild());
+
+ // Add a spelling marker on "123"
+ MarkerController().AddSpellingMarker(
+ EphemeralRange(Position(text, 0), Position(text, 3)));
+
+ // Query for a spellcheck marker intersecting "3456"
+ const DocumentMarker* const result =
+ MarkerController().FirstMarkerIntersectingOffsetRange(
+ *text, 2, 6, DocumentMarker::MisspellingMarkers());
+
+ EXPECT_EQ(DocumentMarker::kSpelling, result->GetType());
+ EXPECT_EQ(0u, result->StartOffset());
+ EXPECT_EQ(3u, result->EndOffset());
+}
+
+TEST_F(DocumentMarkerControllerTest,
+ FirstMarkerIntersectingOffsetRange_collapsed) {
+ SetBodyContent("<div contenteditable>123456789</div>");
+ GetDocument().UpdateStyleAndLayout();
+ Element* div = GetDocument().QuerySelector("div");
+ Text* text = ToText(div->firstChild());
+
+ // Add a spelling marker on "123"
+ MarkerController().AddSpellingMarker(
+ EphemeralRange(Position(text, 0), Position(text, 3)));
+
+ // Query for a spellcheck marker containing the position between "1" and "2"
+ const DocumentMarker* const result =
+ MarkerController().FirstMarkerIntersectingOffsetRange(
+ *text, 1, 1, DocumentMarker::MisspellingMarkers());
+
+ EXPECT_EQ(DocumentMarker::kSpelling, result->GetType());
+ EXPECT_EQ(0u, result->StartOffset());
+ EXPECT_EQ(3u, result->EndOffset());
+}
+
+TEST_F(DocumentMarkerControllerTest, MarkersIntersectingRange) {
+ SetBodyContent("<div contenteditable>123456789</div>");
+ Element* div = GetDocument().QuerySelector("div");
+ Node* text = div->firstChild();
+
+ // Add a spelling marker on "123"
+ MarkerController().AddSpellingMarker(
+ EphemeralRange(Position(text, 0), Position(text, 3)));
+ // Add a text match marker on "456"
+ MarkerController().AddTextMatchMarker(
+ EphemeralRange(Position(text, 3), Position(text, 6)),
+ TextMatchMarker::MatchStatus::kInactive);
+ // Add a grammar marker on "789"
+ MarkerController().AddSpellingMarker(
+ EphemeralRange(Position(text, 6), Position(text, 9)));
+
+ // Query for spellcheck markers intersecting "3456". The text match marker
+ // should not be returned, nor should the spelling marker touching the range.
+ const HeapVector<std::pair<Member<Node>, Member<DocumentMarker>>>& results =
+ MarkerController().MarkersIntersectingRange(
+ EphemeralRangeInFlatTree(PositionInFlatTree(text, 2),
+ PositionInFlatTree(text, 6)),
+ DocumentMarker::MisspellingMarkers());
+
+ EXPECT_EQ(1u, results.size());
+ EXPECT_EQ(DocumentMarker::kSpelling, results[0].second->GetType());
+ EXPECT_EQ(0u, results[0].second->StartOffset());
+ EXPECT_EQ(3u, results[0].second->EndOffset());
+}
+
+TEST_F(DocumentMarkerControllerTest, MarkersIntersectingCollapsedRange) {
+ SetBodyContent("<div contenteditable>123456789</div>");
+ Element* div = GetDocument().QuerySelector("div");
+ Node* text = div->firstChild();
+
+ // Add a spelling marker on "123"
+ MarkerController().AddSpellingMarker(
+ EphemeralRange(Position(text, 0), Position(text, 3)));
+
+ // Query for spellcheck markers containing the position between "1" and "2"
+ const HeapVector<std::pair<Member<Node>, Member<DocumentMarker>>>& results =
+ MarkerController().MarkersIntersectingRange(
+ EphemeralRangeInFlatTree(PositionInFlatTree(text, 1),
+ PositionInFlatTree(text, 1)),
+ DocumentMarker::MisspellingMarkers());
+
+ EXPECT_EQ(1u, results.size());
+ EXPECT_EQ(DocumentMarker::kSpelling, results[0].second->GetType());
+ EXPECT_EQ(0u, results[0].second->StartOffset());
+ EXPECT_EQ(3u, results[0].second->EndOffset());
+}
+
+TEST_F(DocumentMarkerControllerTest, MarkersIntersectingRangeWithShadowDOM) {
+ // Set up some shadow elements in a way we know doesn't work properly when
+ // using EphemeralRange instead of EphemeralRangeInFlatTree:
+ // <div>not shadow</div>
+ // <div> (shadow DOM host)
+ // #shadow-root
+ // <div>shadow1</div>
+ // <div>shadow2</div>
+ // Caling MarkersIntersectingRange with an EphemeralRange starting in the
+ // "not shadow" text and ending in the "shadow1" text will crash.
+ SetBodyContent(
+ "<div id=\"not_shadow\">not shadow</div><div id=\"shadow_root\" />");
+ ShadowRoot* shadow_root = SetShadowContent(
+ "<div id=\"shadow1\">shadow1</div><div id=\"shadow2\">shadow2</div>",
+ "shadow_root");
+
+ Element* not_shadow_div = GetDocument().QuerySelector("#not_shadow");
+ Node* not_shadow_text = not_shadow_div->firstChild();
+
+ Element* shadow1 = shadow_root->QuerySelector("#shadow1");
+ Node* shadow1_text = shadow1->firstChild();
+
+ MarkerController().AddTextMatchMarker(
+ EphemeralRange(Position(not_shadow_text, 0),
+ Position(not_shadow_text, 10)),
+ TextMatchMarker::MatchStatus::kInactive);
+
+ const HeapVector<std::pair<Member<Node>, Member<DocumentMarker>>>& results =
+ MarkerController().MarkersIntersectingRange(
+ EphemeralRangeInFlatTree(PositionInFlatTree(not_shadow_text, 9),
+ PositionInFlatTree(shadow1_text, 1)),
+ DocumentMarker::kTextMatch);
+ EXPECT_EQ(1u, results.size());
+}
+
+TEST_F(DocumentMarkerControllerTest, SuggestionMarkersHaveUniqueTags) {
+ SetBodyContent("<div contenteditable>foo</div>");
+ Element* div = GetDocument().QuerySelector("div");
+ Node* text = div->firstChild();
+
+ MarkerController().AddSuggestionMarker(
+ EphemeralRange(Position(text, 0), Position(text, 1)),
+ SuggestionMarkerProperties());
+ MarkerController().AddSuggestionMarker(
+ EphemeralRange(Position(text, 0), Position(text, 1)),
+ SuggestionMarkerProperties());
+
+ EXPECT_EQ(2u, MarkerController().Markers().size());
+ EXPECT_NE(ToSuggestionMarker(MarkerController().Markers()[0])->Tag(),
+ ToSuggestionMarker(MarkerController().Markers()[1])->Tag());
+}
+
+} // namespace blink