c6ca9269036d54cd9251bed54d79cfd907108632
[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 "TextIterator.h"
43 #include "htmlediting.h"
44
45 namespace WebCore {
46
47 SpellChecker::SpellChecker(Frame* frame)
48     : m_frame(frame)
49     , m_requestSequence(0)
50 {
51 }
52
53 SpellChecker::~SpellChecker()
54 {
55 }
56
57 TextCheckerClient* SpellChecker::client() const
58 {
59     Page* page = m_frame->page();
60     if (!page)
61         return 0;
62     return page->editorClient()->textChecker();
63 }
64
65 bool SpellChecker::initRequest(Node* node)
66 {
67     ASSERT(canCheckAsynchronously(node));
68
69     String text = node->textContent();
70     if (!text.length())
71         return false;
72
73     m_requestNode = node;
74     m_requestText = text;
75     m_requestSequence++;
76
77     return true;
78 }
79
80 void SpellChecker::clearRequest()
81 {
82     m_requestNode.clear();
83     m_requestText = String();
84 }
85
86 bool SpellChecker::isAsynchronousEnabled() const
87 {
88     return m_frame->settings() && m_frame->settings()->asynchronousSpellCheckingEnabled();
89 }
90
91 bool SpellChecker::canCheckAsynchronously(Node* node) const
92 {
93     return client() && isCheckable(node) && isAsynchronousEnabled() && !isBusy();
94 }
95
96 bool SpellChecker::isBusy() const
97 {
98     return m_requestNode.get();
99 }
100
101 bool SpellChecker::isValid(int sequence) const
102 {
103     return m_requestNode.get() && m_requestText.length() && m_requestSequence == sequence;
104 }
105
106 bool SpellChecker::isCheckable(Node* node) const
107 {
108     return node && node->renderer();
109 }
110
111 void SpellChecker::requestCheckingFor(TextCheckingTypeMask mask, Node* node)
112 {
113     ASSERT(canCheckAsynchronously(node));
114
115     if (!initRequest(node))
116         return;
117     client()->requestCheckingOfString(this, m_requestSequence, mask, m_requestText);
118 }
119
120 static bool forwardIterator(PositionIterator& iterator, int distance)
121 {
122     int remaining = distance;
123     while (!iterator.atEnd()) {
124         if (iterator.node()->isCharacterDataNode()) {
125             int length = lastOffsetForEditing(iterator.node());
126             int last = length - iterator.offsetInLeafNode();
127             if (remaining < last) {
128                 iterator.setOffsetInLeafNode(iterator.offsetInLeafNode() + remaining);
129                 return true;
130             }
131
132             remaining -= last;
133             iterator.setOffsetInLeafNode(iterator.offsetInLeafNode() + last);
134         }
135
136         iterator.increment();
137     }
138
139     return false;    
140 }
141
142 static DocumentMarker::MarkerType toMarkerType(TextCheckingType type)
143 {
144     if (type == TextCheckingTypeSpelling)
145         return DocumentMarker::Spelling;
146     ASSERT(type == TextCheckingTypeGrammar);
147     return DocumentMarker::Grammar;
148 }
149
150 // Currenntly ignoring TextCheckingResult::details but should be handled. See Bug 56368.
151 void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& results)
152 {
153     if (!isValid(sequence))
154         return;
155
156     if (!m_requestNode->renderer()) {
157         clearRequest();
158         return;
159     }
160
161     int startOffset = 0;
162     PositionIterator start = firstPositionInOrBeforeNode(m_requestNode.get());
163     for (size_t i = 0; i < results.size(); ++i) {
164         if (results[i].type != TextCheckingTypeSpelling && results[i].type != TextCheckingTypeGrammar)
165             continue;
166
167         // To avoid moving the position backward, we assume the given results are sorted with
168         // startOffset as the ones returned by [NSSpellChecker requestCheckingOfString:].
169         ASSERT(startOffset <= results[i].location);
170         if (!forwardIterator(start, results[i].location - startOffset))
171             break;
172         PositionIterator end = start;
173         if (!forwardIterator(end, results[i].length))
174             break;
175
176         // Users or JavaScript applications may change text while a spell-checker checks its
177         // spellings in the background. To avoid adding markers to the words modified by users or
178         // JavaScript applications, retrieve the words in the specified region and compare them with
179         // the original ones.
180         RefPtr<Range> range = Range::create(m_requestNode->document(), start, end);
181         // FIXME: Use textContent() compatible string conversion.
182         String destination = range->text();
183         String source = m_requestText.substring(results[i].location, results[i].length);
184         if (destination == source)
185             m_requestNode->document()->markers()->addMarker(range.get(), toMarkerType(results[i].type));
186
187         startOffset = results[i].location;
188     }
189
190     clearRequest();
191 }
192
193
194 } // namespace WebCore