Refactoring: SpellChecker::requestCheckingFor should take Range instead of Node.
[WebKit-https.git] / Source / WebCore / editing / SpellChecker.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "SpellChecker.h"
28
29 #include "Document.h"
30 #include "DocumentMarkerController.h"
31 #include "EditorClient.h"
32 #include "Frame.h"
33 #include "HTMLInputElement.h"
34 #include "HTMLTextAreaElement.h"
35 #include "Node.h"
36 #include "Page.h"
37 #include "PositionIterator.h"
38 #include "Range.h"
39 #include "RenderObject.h"
40 #include "Settings.h"
41 #include "TextCheckerClient.h"
42 #include "TextCheckingHelper.h"
43 #include "TextIterator.h"
44 #include "htmlediting.h"
45
46 namespace WebCore {
47
48 SpellChecker::SpellChecker(Frame* frame)
49     : m_frame(frame)
50     , m_requestSequence(0)
51 {
52 }
53
54 SpellChecker::~SpellChecker()
55 {
56 }
57
58 TextCheckerClient* SpellChecker::client() const
59 {
60     Page* page = m_frame->page();
61     if (!page)
62         return 0;
63     return page->editorClient()->textChecker();
64 }
65
66 bool SpellChecker::initRequest(PassRefPtr<Range> range)
67 {
68     ASSERT(canCheckAsynchronously(range.get()));
69
70     String text = range->text();
71     if (!text.length())
72         return false;
73
74     m_requestRange = range;
75     m_requestText = text;
76     m_requestSequence++;
77
78     return true;
79 }
80
81 void SpellChecker::clearRequest()
82 {
83     m_requestRange.clear();
84     m_requestText = String();
85 }
86
87 bool SpellChecker::isAsynchronousEnabled() const
88 {
89     return m_frame->settings() && m_frame->settings()->asynchronousSpellCheckingEnabled();
90 }
91
92 bool SpellChecker::canCheckAsynchronously(Range* range) const
93 {
94     return client() && isCheckable(range) && isAsynchronousEnabled() && !isBusy();
95 }
96
97 bool SpellChecker::isBusy() const
98 {
99     return m_requestRange.get();
100 }
101
102 bool SpellChecker::isValid(int sequence) const
103 {
104     return m_requestRange.get() && m_requestText.length() && m_requestSequence == sequence;
105 }
106
107 bool SpellChecker::isCheckable(Range* range) const
108 {
109     return range && range->firstNode() && range->firstNode()->renderer();
110 }
111
112 void SpellChecker::requestCheckingFor(TextCheckingTypeMask mask, PassRefPtr<Range> range)
113 {
114     if (!canCheckAsynchronously(range.get()))
115         return;
116
117     doRequestCheckingFor(mask, range);
118 }
119
120 void SpellChecker::doRequestCheckingFor(TextCheckingTypeMask mask, PassRefPtr<Range> range)
121 {
122     ASSERT(canCheckAsynchronously(range.get()));
123
124     if (!initRequest(range))
125         return;
126     client()->requestCheckingOfString(this, m_requestSequence, mask, m_requestText);
127 }
128
129 static bool forwardIterator(PositionIterator& iterator, int distance)
130 {
131     int remaining = distance;
132     while (!iterator.atEnd()) {
133         if (iterator.node()->isCharacterDataNode()) {
134             int length = lastOffsetForEditing(iterator.node());
135             int last = length - iterator.offsetInLeafNode();
136             if (remaining < last) {
137                 iterator.setOffsetInLeafNode(iterator.offsetInLeafNode() + remaining);
138                 return true;
139             }
140
141             remaining -= last;
142             iterator.setOffsetInLeafNode(iterator.offsetInLeafNode() + last);
143         }
144
145         iterator.increment();
146     }
147
148     return false;    
149 }
150
151 static DocumentMarker::MarkerType toMarkerType(TextCheckingType type)
152 {
153     if (type == TextCheckingTypeSpelling)
154         return DocumentMarker::Spelling;
155     ASSERT(type == TextCheckingTypeGrammar);
156     return DocumentMarker::Grammar;
157 }
158
159 // Currenntly ignoring TextCheckingResult::details but should be handled. See Bug 56368.
160 void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& results)
161 {
162     if (!isValid(sequence))
163         return;
164
165     if (!isCheckable(m_requestRange.get())) {
166         clearRequest();
167         return;
168     }
169
170     int startOffset = 0;
171     PositionIterator start = m_requestRange->startPosition();
172     for (size_t i = 0; i < results.size(); ++i) {
173         if (results[i].type != TextCheckingTypeSpelling && results[i].type != TextCheckingTypeGrammar)
174             continue;
175
176         // To avoid moving the position backward, we assume the given results are sorted with
177         // startOffset as the ones returned by [NSSpellChecker requestCheckingOfString:].
178         ASSERT(startOffset <= results[i].location);
179         if (!forwardIterator(start, results[i].location - startOffset))
180             break;
181         PositionIterator end = start;
182         if (!forwardIterator(end, results[i].length))
183             break;
184
185         // Users or JavaScript applications may change text while a spell-checker checks its
186         // spellings in the background. To avoid adding markers to the words modified by users or
187         // JavaScript applications, retrieve the words in the specified region and compare them with
188         // the original ones.
189         RefPtr<Range> range = Range::create(m_requestRange->ownerDocument(), start, end);
190         // FIXME: Use textContent() compatible string conversion.
191         String destination = range->text();
192         String source = m_requestText.substring(results[i].location, results[i].length);
193         if (destination == source)
194             m_requestRange->ownerDocument()->markers()->addMarker(range.get(), toMarkerType(results[i].type));
195
196         startOffset = results[i].location;
197     }
198
199     clearRequest();
200 }
201
202
203 } // namespace WebCore