| // Copyright 2016 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 "core/editing/spellcheck/SpellChecker.h" |
| |
| #include "core/editing/Editor.h" |
| #include "core/editing/EphemeralRange.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/SelectionTemplate.h" |
| #include "core/editing/markers/DocumentMarkerController.h" |
| #include "core/editing/markers/SpellCheckMarker.h" |
| #include "core/editing/spellcheck/SpellCheckRequester.h" |
| #include "core/editing/spellcheck/SpellCheckTestBase.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameView.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/forms/HTMLInputElement.h" |
| |
| namespace blink { |
| |
| class SpellCheckerTest : public SpellCheckTestBase { |
| protected: |
| int LayoutCount() const { return Page().GetFrameView().LayoutCount(); } |
| DummyPageHolder& Page() const { return GetDummyPageHolder(); } |
| |
| void ForceLayout(); |
| }; |
| |
| void SpellCheckerTest::ForceLayout() { |
| LocalFrameView& frame_view = Page().GetFrameView(); |
| IntRect frame_rect = frame_view.FrameRect(); |
| frame_rect.SetWidth(frame_rect.Width() + 1); |
| frame_rect.SetHeight(frame_rect.Height() + 1); |
| Page().GetFrameView().SetFrameRect(frame_rect); |
| GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| } |
| |
| TEST_F(SpellCheckerTest, AdvanceToNextMisspellingWithEmptyInputNoCrash) { |
| SetBodyContent("<input placeholder='placeholder'>abc"); |
| UpdateAllLifecyclePhases(); |
| Element* input = GetDocument().QuerySelector("input"); |
| input->focus(); |
| // Do not crash in advanceToNextMisspelling. |
| GetSpellChecker().AdvanceToNextMisspelling(false); |
| } |
| |
| // Regression test for crbug.com/701309 |
| TEST_F(SpellCheckerTest, AdvanceToNextMisspellingWithImageInTableNoCrash) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "<table><tr><td>" |
| "<img src=foo.jpg>" |
| "</td></tr></table>" |
| "zz zz zz" |
| "</div>"); |
| GetDocument().QuerySelector("div")->focus(); |
| UpdateAllLifecyclePhases(); |
| |
| // Do not crash in advanceToNextMisspelling. |
| GetSpellChecker().AdvanceToNextMisspelling(false); |
| } |
| |
| // Regression test for crbug.com/728801 |
| TEST_F(SpellCheckerTest, AdvancedToNextMisspellingWrapSearchNoCrash) { |
| SetBodyContent("<div contenteditable> zz zz zz </div>"); |
| |
| Element* div = GetDocument().QuerySelector("div"); |
| div->focus(); |
| Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .Collapse(Position::LastPositionInNode(*div)) |
| .Build()); |
| UpdateAllLifecyclePhases(); |
| |
| GetSpellChecker().AdvanceToNextMisspelling(false); |
| } |
| |
| TEST_F(SpellCheckerTest, SpellCheckDoesNotCauseUpdateLayout) { |
| SetBodyContent("<input>"); |
| HTMLInputElement* input = |
| ToHTMLInputElement(GetDocument().QuerySelector("input")); |
| input->focus(); |
| input->setValue("Hello, input field"); |
| GetDocument().UpdateStyleAndLayout(); |
| |
| Position new_position(input->InnerEditorElement()->firstChild(), 3); |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder().Collapse(new_position).Build()); |
| ASSERT_EQ(3u, input->selectionStart()); |
| |
| EXPECT_TRUE(GetSpellChecker().IsSpellCheckingEnabled()); |
| ForceLayout(); |
| int start_count = LayoutCount(); |
| GetSpellChecker().RespondToChangedSelection(); |
| EXPECT_EQ(start_count, LayoutCount()); |
| } |
| |
| TEST_F(SpellCheckerTest, MarkAndReplaceForHandlesMultipleReplacements) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "spllchck" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| EphemeralRange range_to_check = |
| EphemeralRange(Position(text, 0), Position(text, 8)); |
| |
| SpellCheckRequest* request = SpellCheckRequest::Create(range_to_check, 0); |
| |
| TextCheckingResult result; |
| result.decoration = TextDecorationType::kTextDecorationTypeSpelling; |
| result.location = 0; |
| result.length = 8; |
| result.replacements = Vector<String>({"spellcheck", "spillchuck"}); |
| |
| GetDocument().GetFrame()->GetSpellChecker().MarkAndReplaceFor( |
| request, Vector<TextCheckingResult>({result})); |
| |
| ASSERT_EQ(1u, GetDocument().Markers().Markers().size()); |
| |
| // The Spelling marker's description should be a newline-separated list of the |
| // suggested replacements |
| EXPECT_EQ( |
| "spellcheck\nspillchuck", |
| ToSpellCheckMarker(GetDocument().Markers().Markers()[0])->Description()); |
| } |
| |
| TEST_F(SpellCheckerTest, GetSpellCheckMarkerUnderSelection_FirstCharSelected) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "spllchck" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 8))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 0), Position(text, 1)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_NE(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, GetSpellCheckMarkerUnderSelection_LastCharSelected) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "spllchck" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 8))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 7), Position(text, 8)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_NE(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, |
| GetSpellCheckMarkerUnderSelection_SingleCharWordSelected) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "s" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 1))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 0), Position(text, 1)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_NE(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, |
| GetSpellCheckMarkerUnderSelection_CaretLeftOfSingleCharWord) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "s" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 1))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 0), Position(text, 0)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_EQ(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, |
| GetSpellCheckMarkerUnderSelection_CaretRightOfSingleCharWord) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "s" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 1))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 1), Position(text, 1)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_EQ(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, |
| GetSpellCheckMarkerUnderSelection_CaretLeftOfMultiCharWord) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "spllchck" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 8))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 0), Position(text, 0)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_EQ(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, |
| GetSpellCheckMarkerUnderSelection_CaretRightOfMultiCharWord) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "spllchck" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 8))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 8), Position(text, 8)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_EQ(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, GetSpellCheckMarkerUnderSelection_CaretMiddleOfWord) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "spllchck" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 8))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 4), Position(text, 4)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_NE(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, |
| GetSpellCheckMarkerUnderSelection_CaretOneCharLeftOfMisspelling) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "a spllchck" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 2), Position(text, 10))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 1), Position(text, 1)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_EQ(nullptr, result.first); |
| } |
| |
| TEST_F(SpellCheckerTest, |
| GetSpellCheckMarkerUnderSelection_CaretOneCharRightOfMisspelling) { |
| SetBodyContent( |
| "<div contenteditable>" |
| "spllchck a" |
| "</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| |
| GetDocument().Markers().AddSpellingMarker( |
| EphemeralRange(Position(text, 0), Position(text, 8))); |
| |
| GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( |
| SelectionInDOMTree::Builder() |
| .SetBaseAndExtent(Position(text, 9), Position(text, 9)) |
| .Build()); |
| |
| std::pair<Node*, SpellCheckMarker*> result = |
| GetDocument() |
| .GetFrame() |
| ->GetSpellChecker() |
| .GetSpellCheckMarkerUnderSelection(); |
| EXPECT_EQ(nullptr, result.first); |
| } |
| |
| } // namespace blink |