| /* |
| * 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/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/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.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(); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| // 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(); |
| UpdateAllLifecyclePhasesForTest(); |
| // 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()); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| // 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()); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| // 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); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| // 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()); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| // 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(""); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| // No more reference to marked node. |
| ThreadState::Current()->CollectAllGarbage(); |
| EXPECT_EQ(0u, MarkerController().Markers().size()); |
| } |
| |
| // For http://crbug.com/862900 |
| TEST_F(DocumentMarkerControllerTest, SynchronousMutationNotificationAfterGC) { |
| SetBodyContent("<b><i>foo</i></b>"); |
| Persistent<Text> sibling_text = CreateTextNode("bar"); |
| { |
| Element* parent = |
| ToElement(GetDocument().body()->firstChild()->firstChild()); |
| parent->parentNode()->AppendChild(sibling_text); |
| MarkNodeContents(parent); |
| EXPECT_EQ(1u, MarkerController().Markers().size()); |
| parent->parentNode()->RemoveChild(parent); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| |
| // GC the marked node, so it disappears from WeakMember collections. |
| ThreadState::Current()->CollectAllGarbage(); |
| EXPECT_EQ(0u, MarkerController().Markers().size()); |
| |
| // Trigger SynchronousMutationNotifier::NotifyUpdateCharacterData(). |
| // This matches the conditions for the crashes in crbug.com/862960. |
| sibling_text->setData("baz"); |
| } |
| |
| 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(html_names::kStyleAttr, "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, |
| ws::mojom::ImeTextSpanThickness::kThin, Color::kBlack); |
| MarkerController().AddCompositionMarker( |
| EphemeralRange(Position(text, 1), Position(text, 3)), Color::kTransparent, |
| ws::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::MarkerTypes::All()); |
| |
| 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::MarkerTypes::All()); |
| |
| 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::MarkerTypes::All()); |
| |
| 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, RemoveSpellingMarkersUnderAllWords) { |
| SetBodyContent("<div contenteditable>foo</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| ASSERT_NE(text->GetLayoutObject(), nullptr); |
| |
| const EphemeralRange marker_range(Position(text, 0), Position(text, 3)); |
| text->GetLayoutObject()->ClearPaintInvalidationFlags(); |
| MarkerController().AddSpellingMarker(marker_range); |
| EXPECT_TRUE(text->GetLayoutObject()->ShouldCheckForPaintInvalidation()); |
| ASSERT_EQ(1u, MarkerController().Markers().size()); |
| |
| text->GetLayoutObject()->ClearPaintInvalidationFlags(); |
| MarkerController().RemoveSpellingMarkersUnderWords({"foo"}); |
| EXPECT_TRUE(text->GetLayoutObject()->ShouldCheckForPaintInvalidation()); |
| ASSERT_EQ(0u, MarkerController().Markers().size()); |
| } |
| |
| 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(*ToText(text), marker.Tag()); |
| EXPECT_EQ(0u, MarkerController().Markers().size()); |
| } |
| |
| TEST_F(DocumentMarkerControllerTest, RemoveSuggestionMarkerInRangeOnFinish) { |
| SetBodyContent("<div contenteditable>foo</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| // Add a regular suggestion marker, RemoveSuggestionMarkerInRangeOnFinish() |
| // should not remove it. |
| MarkerController().AddSuggestionMarker( |
| EphemeralRange(Position(text, 0), Position(text, 2)), |
| SuggestionMarkerProperties()); |
| |
| ASSERT_EQ(1u, MarkerController().Markers().size()); |
| MarkerController().RemoveSuggestionMarkerInRangeOnFinish( |
| EphemeralRangeInFlatTree(PositionInFlatTree(text, 0), |
| PositionInFlatTree(text, 2))); |
| |
| EXPECT_EQ(1u, MarkerController().Markers().size()); |
| |
| const SuggestionMarker& marker = |
| *ToSuggestionMarker(MarkerController().Markers()[0]); |
| MarkerController().RemoveSuggestionMarkerByTag(*ToText(text), marker.Tag()); |
| ASSERT_EQ(0u, MarkerController().Markers().size()); |
| |
| // Add a suggestion marker which need to be removed after finish composing, |
| // RemoveSuggestionMarkerInRangeOnFinish() should remove it. |
| MarkerController().AddSuggestionMarker( |
| EphemeralRange(Position(text, 0), Position(text, 2)), |
| SuggestionMarkerProperties::Builder() |
| .SetRemoveOnFinishComposing(true) |
| .Build()); |
| |
| ASSERT_EQ(1u, MarkerController().Markers().size()); |
| |
| MarkerController().RemoveSuggestionMarkerInRangeOnFinish( |
| EphemeralRangeInFlatTree(PositionInFlatTree(text, 0), |
| PositionInFlatTree(text, 2))); |
| 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::MarkerTypes::Misspelling()); |
| |
| 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::MarkerTypes::Misspelling()); |
| |
| 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::MarkerTypes::Misspelling()); |
| |
| 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::MarkerTypes::Misspelling()); |
| |
| 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::MarkerTypes::TextMatch()); |
| 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 |