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 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 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 "Editing.h"
32#include "Editor.h"
33#include "Frame.h"
34#include "Page.h"
35#include "PositionIterator.h"
36#include "RenderObject.h"
37#include "Settings.h"
38#include "TextCheckerClient.h"
39#include "TextCheckingHelper.h"
40
41namespace WebCore {
42
43SpellCheckRequest::SpellCheckRequest(Ref<Range>&& checkingRange, Ref<Range>&& automaticReplacementRange, Ref<Range>&& paragraphRange, const String& text, OptionSet<TextCheckingType> mask, TextCheckingProcessType processType)
44 : m_checkingRange(WTFMove(checkingRange))
45 , m_automaticReplacementRange(WTFMove(automaticReplacementRange))
46 , m_paragraphRange(WTFMove(paragraphRange))
47 , m_rootEditableElement(m_checkingRange->startContainer().rootEditableElement())
48 , m_requestData(unrequestedTextCheckingSequence, text, mask, processType)
49{
50}
51
52SpellCheckRequest::~SpellCheckRequest() = default;
53
54RefPtr<SpellCheckRequest> SpellCheckRequest::create(OptionSet<TextCheckingType> textCheckingOptions, TextCheckingProcessType processType, Ref<Range>&& checkingRange, Ref<Range>&& automaticReplacementRange, Ref<Range>&& paragraphRange)
55{
56 String text = checkingRange->text();
57 if (!text.length())
58 return nullptr;
59
60 return adoptRef(*new SpellCheckRequest(WTFMove(checkingRange), WTFMove(automaticReplacementRange), WTFMove(paragraphRange), text, textCheckingOptions, processType));
61}
62
63const TextCheckingRequestData& SpellCheckRequest::data() const
64{
65 return m_requestData;
66}
67
68void SpellCheckRequest::didSucceed(const Vector<TextCheckingResult>& results)
69{
70 if (!m_checker)
71 return;
72
73 Ref<SpellCheckRequest> protectedThis(*this);
74 m_checker->didCheckSucceed(m_requestData.sequence(), results);
75 m_checker = nullptr;
76}
77
78void SpellCheckRequest::didCancel()
79{
80 if (!m_checker)
81 return;
82
83 Ref<SpellCheckRequest> protectedThis(*this);
84 m_checker->didCheckCancel(m_requestData.sequence());
85 m_checker = nullptr;
86}
87
88void SpellCheckRequest::setCheckerAndSequence(SpellChecker* requester, int sequence)
89{
90 ASSERT(!m_checker);
91 ASSERT(m_requestData.sequence() == unrequestedTextCheckingSequence);
92 m_checker = requester;
93 m_requestData.m_sequence = sequence;
94}
95
96void SpellCheckRequest::requesterDestroyed()
97{
98 m_checker = nullptr;
99}
100
101SpellChecker::SpellChecker(Frame& frame)
102 : m_frame(frame)
103 , m_lastRequestSequence(0)
104 , m_lastProcessedSequence(0)
105 , m_timerToProcessQueuedRequest(*this, &SpellChecker::timerFiredToProcessQueuedRequest)
106{
107}
108
109SpellChecker::~SpellChecker()
110{
111 if (m_processingRequest)
112 m_processingRequest->requesterDestroyed();
113 for (auto& queue : m_requestQueue)
114 queue->requesterDestroyed();
115}
116
117TextCheckerClient* SpellChecker::client() const
118{
119 Page* page = m_frame.page();
120 if (!page)
121 return nullptr;
122 return page->editorClient().textChecker();
123}
124
125void SpellChecker::timerFiredToProcessQueuedRequest()
126{
127 ASSERT(!m_requestQueue.isEmpty());
128 if (m_requestQueue.isEmpty())
129 return;
130
131 invokeRequest(m_requestQueue.takeFirst());
132}
133
134bool SpellChecker::isAsynchronousEnabled() const
135{
136 return m_frame.settings().asynchronousSpellCheckingEnabled();
137}
138
139bool SpellChecker::canCheckAsynchronously(Range& range) const
140{
141 return client() && isCheckable(range) && isAsynchronousEnabled();
142}
143
144bool SpellChecker::isCheckable(Range& range) const
145{
146 if (!range.firstNode() || !range.firstNode()->renderer())
147 return false;
148 const Node& node = range.startContainer();
149 if (is<Element>(node) && !downcast<Element>(node).isSpellCheckingEnabled())
150 return false;
151 return true;
152}
153
154void SpellChecker::requestCheckingFor(Ref<SpellCheckRequest>&& request)
155{
156 if (!canCheckAsynchronously(request->paragraphRange()))
157 return;
158
159 ASSERT(request->data().sequence() == unrequestedTextCheckingSequence);
160 int sequence = ++m_lastRequestSequence;
161 if (sequence == unrequestedTextCheckingSequence)
162 sequence = ++m_lastRequestSequence;
163
164 request->setCheckerAndSequence(this, sequence);
165
166 if (m_timerToProcessQueuedRequest.isActive() || m_processingRequest) {
167 enqueueRequest(WTFMove(request));
168 return;
169 }
170
171 invokeRequest(WTFMove(request));
172}
173
174void SpellChecker::invokeRequest(Ref<SpellCheckRequest>&& request)
175{
176 ASSERT(!m_processingRequest);
177 if (!client())
178 return;
179 m_processingRequest = WTFMove(request);
180 client()->requestCheckingOfString(*m_processingRequest, m_frame.selection().selection());
181}
182
183void SpellChecker::enqueueRequest(Ref<SpellCheckRequest>&& request)
184{
185 for (auto& queue : m_requestQueue) {
186 if (request->rootEditableElement() != queue->rootEditableElement())
187 continue;
188
189 queue = WTFMove(request);
190 return;
191 }
192
193 m_requestQueue.append(WTFMove(request));
194}
195
196void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& results)
197{
198 ASSERT(m_processingRequest);
199 ASSERT(m_processingRequest->data().sequence() == sequence);
200 if (m_processingRequest->data().sequence() != sequence) {
201 m_requestQueue.clear();
202 return;
203 }
204
205 m_frame.editor().markAndReplaceFor(*m_processingRequest, results);
206
207 if (m_lastProcessedSequence < sequence)
208 m_lastProcessedSequence = sequence;
209
210 m_processingRequest = nullptr;
211 if (!m_requestQueue.isEmpty())
212 m_timerToProcessQueuedRequest.startOneShot(0_s);
213}
214
215void SpellChecker::didCheckSucceed(int sequence, const Vector<TextCheckingResult>& results)
216{
217 TextCheckingRequestData requestData = m_processingRequest->data();
218 if (requestData.sequence() == sequence) {
219 OptionSet<DocumentMarker::MarkerType> markerTypes;
220 if (requestData.checkingTypes().contains(TextCheckingType::Spelling))
221 markerTypes.add(DocumentMarker::Spelling);
222 if (requestData.checkingTypes().contains(TextCheckingType::Grammar))
223 markerTypes.add(DocumentMarker::Grammar);
224 if (!markerTypes.isEmpty())
225 m_frame.document()->markers().removeMarkers(m_processingRequest->checkingRange(), markerTypes);
226 }
227 didCheck(sequence, results);
228}
229
230void SpellChecker::didCheckCancel(int sequence)
231{
232 didCheck(sequence, Vector<TextCheckingResult>());
233}
234
235} // namespace WebCore
236