• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SpellChecker(Frame * frame)47 SpellChecker::SpellChecker(Frame* frame)
48     : m_frame(frame)
49     , m_requestSequence(0)
50 {
51 }
52 
~SpellChecker()53 SpellChecker::~SpellChecker()
54 {
55 }
56 
client() const57 TextCheckerClient* SpellChecker::client() const
58 {
59     Page* page = m_frame->page();
60     if (!page)
61         return 0;
62     return page->editorClient()->textChecker();
63 }
64 
initRequest(Node * node)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 
clearRequest()80 void SpellChecker::clearRequest()
81 {
82     m_requestNode.clear();
83     m_requestText = String();
84 }
85 
isAsynchronousEnabled() const86 bool SpellChecker::isAsynchronousEnabled() const
87 {
88     return m_frame->settings() && m_frame->settings()->asynchronousSpellCheckingEnabled();
89 }
90 
canCheckAsynchronously(Node * node) const91 bool SpellChecker::canCheckAsynchronously(Node* node) const
92 {
93     return client() && isCheckable(node) && isAsynchronousEnabled() && !isBusy();
94 }
95 
isBusy() const96 bool SpellChecker::isBusy() const
97 {
98     return m_requestNode.get();
99 }
100 
isValid(int sequence) const101 bool SpellChecker::isValid(int sequence) const
102 {
103     return m_requestNode.get() && m_requestText.length() && m_requestSequence == sequence;
104 }
105 
isCheckable(Node * node) const106 bool SpellChecker::isCheckable(Node* node) const
107 {
108     return node && node->renderer();
109 }
110 
requestCheckingFor(TextCheckingTypeMask mask,Node * node)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 
forwardIterator(PositionIterator & iterator,int distance)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 
toMarkerType(TextCheckingType type)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.
didCheck(int sequence,const Vector<TextCheckingResult> & results)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
195