1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/renderer/spellchecker/spellcheck_provider.h"
7 #include "base/command_line.h"
8 #include "base/metrics/histogram.h"
9 #include "chrome/common/chrome_switches.h"
10 #include "chrome/common/spellcheck_marker.h"
11 #include "chrome/common/spellcheck_messages.h"
12 #include "chrome/common/spellcheck_result.h"
13 #include "chrome/renderer/spellchecker/spellcheck.h"
14 #include "chrome/renderer/spellchecker/spellcheck_language.h"
15 #include "content/public/renderer/render_view.h"
16 #include "third_party/WebKit/public/platform/WebVector.h"
17 #include "third_party/WebKit/public/web/WebDocument.h"
18 #include "third_party/WebKit/public/web/WebElement.h"
19 #include "third_party/WebKit/public/web/WebFrame.h"
20 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h"
21 #include "third_party/WebKit/public/web/WebTextCheckingResult.h"
22 #include "third_party/WebKit/public/web/WebTextDecorationType.h"
23 #include "third_party/WebKit/public/web/WebView.h"
25 using blink::WebElement
;
26 using blink::WebFrame
;
27 using blink::WebString
;
28 using blink::WebTextCheckingCompletion
;
29 using blink::WebTextCheckingResult
;
30 using blink::WebTextDecorationType
;
31 using blink::WebVector
;
33 static_assert(int(blink::WebTextDecorationTypeSpelling
) ==
34 int(SpellCheckResult::SPELLING
), "mismatching enums");
35 static_assert(int(blink::WebTextDecorationTypeGrammar
) ==
36 int(SpellCheckResult::GRAMMAR
), "mismatching enums");
37 static_assert(int(blink::WebTextDecorationTypeInvisibleSpellcheck
) ==
38 int(SpellCheckResult::INVISIBLE
), "mismatching enums");
40 SpellCheckProvider::SpellCheckProvider(
41 content::RenderView
* render_view
,
42 SpellCheck
* spellcheck
)
43 : content::RenderViewObserver(render_view
),
44 content::RenderViewObserverTracker
<SpellCheckProvider
>(render_view
),
45 spelling_panel_visible_(false),
46 spellcheck_(spellcheck
) {
48 if (render_view
) { // NULL in unit tests.
49 render_view
->GetWebView()->setSpellCheckClient(this);
50 EnableSpellcheck(spellcheck_
->IsSpellcheckEnabled());
54 SpellCheckProvider::~SpellCheckProvider() {
57 void SpellCheckProvider::RequestTextChecking(
58 const base::string16
& text
,
59 WebTextCheckingCompletion
* completion
,
60 const std::vector
<SpellCheckMarker
>& markers
) {
61 // Ignore invalid requests.
62 if (text
.empty() || !HasWordCharacters(text
, 0)) {
63 completion
->didCancelCheckingText();
67 // Try to satisfy check from cache.
68 if (SatisfyRequestFromCache(text
, completion
))
71 // Send this text to a browser. A browser checks the user profile and send
72 // this text to the Spelling service only if a user enables this feature.
73 last_request_
.clear();
74 last_results_
.assign(blink::WebVector
<blink::WebTextCheckingResult
>());
76 #if defined(USE_BROWSER_SPELLCHECKER)
77 // Text check (unified request for grammar and spell check) is only
78 // available for browser process, so we ask the system spellchecker
79 // over IPC or return an empty result if the checker is not
81 Send(new SpellCheckHostMsg_RequestTextCheck(
83 text_check_completions_
.Add(completion
),
87 Send(new SpellCheckHostMsg_CallSpellingService(
89 text_check_completions_
.Add(completion
),
92 #endif // !USE_BROWSER_SPELLCHECKER
95 bool SpellCheckProvider::OnMessageReceived(const IPC::Message
& message
) {
97 IPC_BEGIN_MESSAGE_MAP(SpellCheckProvider
, message
)
98 #if !defined(USE_BROWSER_SPELLCHECKER)
99 IPC_MESSAGE_HANDLER(SpellCheckMsg_RespondSpellingService
,
100 OnRespondSpellingService
)
102 #if defined(USE_BROWSER_SPELLCHECKER)
103 IPC_MESSAGE_HANDLER(SpellCheckMsg_AdvanceToNextMisspelling
,
104 OnAdvanceToNextMisspelling
)
105 IPC_MESSAGE_HANDLER(SpellCheckMsg_RespondTextCheck
, OnRespondTextCheck
)
106 IPC_MESSAGE_HANDLER(SpellCheckMsg_ToggleSpellPanel
, OnToggleSpellPanel
)
108 IPC_MESSAGE_UNHANDLED(handled
= false)
109 IPC_END_MESSAGE_MAP()
113 void SpellCheckProvider::FocusedNodeChanged(const blink::WebNode
& unused
) {
114 #if defined(USE_BROWSER_SPELLCHECKER)
115 WebFrame
* frame
= render_view()->GetWebView()->focusedFrame();
116 WebElement element
= frame
->document().isNull() ? WebElement() :
117 frame
->document().focusedElement();
118 bool enabled
= !element
.isNull() && render_view()->IsEditableNode(element
);
120 bool checked
= enabled
&& render_view()->GetWebView() &&
121 frame
->isContinuousSpellCheckingEnabled();
123 Send(new SpellCheckHostMsg_ToggleSpellCheck(routing_id(), enabled
, checked
));
124 #endif // USE_BROWSER_SPELLCHECKER
127 void SpellCheckProvider::spellCheck(
128 const WebString
& text
,
131 WebVector
<WebString
>* optional_suggestions
) {
132 base::string16
word(text
);
133 std::vector
<base::string16
> suggestions
;
134 const int kWordStart
= 0;
135 spellcheck_
->SpellCheckWord(
136 word
.c_str(), kWordStart
, word
.size(), routing_id(),
137 &offset
, &length
, optional_suggestions
? & suggestions
: NULL
);
138 if (optional_suggestions
) {
139 *optional_suggestions
= suggestions
;
140 UMA_HISTOGRAM_COUNTS("SpellCheck.api.check.suggestions", word
.size());
142 UMA_HISTOGRAM_COUNTS("SpellCheck.api.check", word
.size());
143 // If optional_suggestions is not requested, the API is called
144 // for marking. So we use this for counting markable words.
145 Send(new SpellCheckHostMsg_NotifyChecked(routing_id(), word
, 0 < length
));
149 void SpellCheckProvider::checkTextOfParagraph(
150 const blink::WebString
& text
,
151 blink::WebTextCheckingTypeMask mask
,
152 blink::WebVector
<blink::WebTextCheckingResult
>* results
) {
156 if (!(mask
& blink::WebTextCheckingTypeSpelling
))
159 // TODO(groby): As far as I can tell, this method is never invoked.
160 // UMA results seem to support that. Investigate, clean up if true.
162 spellcheck_
->SpellCheckParagraph(text
, results
);
163 UMA_HISTOGRAM_COUNTS("SpellCheck.api.paragraph", text
.length());
166 void SpellCheckProvider::requestCheckingOfText(
167 const WebString
& text
,
168 const WebVector
<uint32
>& markers
,
169 const WebVector
<unsigned>& marker_offsets
,
170 WebTextCheckingCompletion
* completion
) {
171 std::vector
<SpellCheckMarker
> spellcheck_markers
;
172 for (size_t i
= 0; i
< markers
.size(); ++i
) {
173 spellcheck_markers
.push_back(
174 SpellCheckMarker(markers
[i
], marker_offsets
[i
]));
176 RequestTextChecking(text
, completion
, spellcheck_markers
);
177 UMA_HISTOGRAM_COUNTS("SpellCheck.api.async", text
.length());
180 WebString
SpellCheckProvider::autoCorrectWord(const WebString
& word
) {
181 const base::CommandLine
& command_line
=
182 *base::CommandLine::ForCurrentProcess();
183 if (command_line
.HasSwitch(switches::kEnableSpellingAutoCorrect
)) {
184 UMA_HISTOGRAM_COUNTS("SpellCheck.api.autocorrect", word
.length());
185 return spellcheck_
->GetAutoCorrectionWord(word
, routing_id());
187 return base::string16();
190 void SpellCheckProvider::showSpellingUI(bool show
) {
191 #if defined(USE_BROWSER_SPELLCHECKER)
192 UMA_HISTOGRAM_BOOLEAN("SpellCheck.api.showUI", show
);
193 Send(new SpellCheckHostMsg_ShowSpellingPanel(routing_id(), show
));
197 bool SpellCheckProvider::isShowingSpellingUI() {
198 return spelling_panel_visible_
;
201 void SpellCheckProvider::updateSpellingUIWithMisspelledWord(
202 const WebString
& word
) {
203 #if defined(USE_BROWSER_SPELLCHECKER)
204 Send(new SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord(routing_id(),
209 #if !defined(USE_BROWSER_SPELLCHECKER)
210 void SpellCheckProvider::OnRespondSpellingService(
213 const base::string16
& line
,
214 const std::vector
<SpellCheckResult
>& results
) {
215 WebTextCheckingCompletion
* completion
=
216 text_check_completions_
.Lookup(identifier
);
219 text_check_completions_
.Remove(identifier
);
221 // If |succeeded| is false, we use local spellcheck as a fallback.
223 spellcheck_
->RequestTextChecking(line
, completion
);
227 // Double-check the returned spellchecking results with our spellchecker to
228 // visualize the differences between ours and the on-line spellchecker.
229 blink::WebVector
<blink::WebTextCheckingResult
> textcheck_results
;
230 spellcheck_
->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER
,
235 completion
->didFinishCheckingText(textcheck_results
);
237 // Cache the request and the converted results.
238 last_request_
= line
;
239 last_results_
.swap(textcheck_results
);
243 bool SpellCheckProvider::HasWordCharacters(
244 const base::string16
& text
,
246 const base::char16
* data
= text
.data();
247 int length
= text
.length();
248 while (index
< length
) {
250 U16_NEXT(data
, index
, length
, code
);
251 UErrorCode error
= U_ZERO_ERROR
;
252 if (uscript_getScript(code
, &error
) != USCRIPT_COMMON
)
258 #if defined(USE_BROWSER_SPELLCHECKER)
259 void SpellCheckProvider::OnAdvanceToNextMisspelling() {
260 if (!render_view()->GetWebView())
262 render_view()->GetWebView()->focusedFrame()->executeCommand(
263 WebString::fromUTF8("AdvanceToNextMisspelling"));
266 void SpellCheckProvider::OnRespondTextCheck(
268 const base::string16
& line
,
269 const std::vector
<SpellCheckResult
>& results
) {
270 // TODO(groby): Unify with SpellCheckProvider::OnRespondSpellingService
272 WebTextCheckingCompletion
* completion
=
273 text_check_completions_
.Lookup(identifier
);
276 text_check_completions_
.Remove(identifier
);
277 blink::WebVector
<blink::WebTextCheckingResult
> textcheck_results
;
278 spellcheck_
->CreateTextCheckingResults(SpellCheck::DO_NOT_MODIFY
,
283 completion
->didFinishCheckingText(textcheck_results
);
285 // TODO(groby): Add request caching once OSX reports back original request.
286 // (cf. SpellCheckProvider::OnRespondSpellingService)
287 // Cache the request and the converted results.
290 void SpellCheckProvider::OnToggleSpellPanel(bool is_currently_visible
) {
291 if (!render_view()->GetWebView())
293 // We need to tell the webView whether the spelling panel is visible or not so
294 // that it won't need to make ipc calls later.
295 spelling_panel_visible_
= is_currently_visible
;
296 render_view()->GetWebView()->focusedFrame()->executeCommand(
297 WebString::fromUTF8("ToggleSpellPanel"));
301 void SpellCheckProvider::EnableSpellcheck(bool enable
) {
302 if (!render_view()->GetWebView())
305 WebFrame
* frame
= render_view()->GetWebView()->focusedFrame();
306 frame
->enableContinuousSpellChecking(enable
);
308 frame
->removeSpellingMarkers();
311 bool SpellCheckProvider::SatisfyRequestFromCache(
312 const base::string16
& text
,
313 WebTextCheckingCompletion
* completion
) {
314 size_t last_length
= last_request_
.length();
316 // Send back the |last_results_| if the |last_request_| is a substring of
317 // |text| and |text| does not have more words to check. Provider cannot cancel
318 // the spellcheck request here, because WebKit might have discarded the
319 // previous spellcheck results and erased the spelling markers in response to
320 // the user editing the text.
321 base::string16
request(text
);
322 size_t text_length
= request
.length();
323 if (text_length
>= last_length
&&
324 !request
.compare(0, last_length
, last_request_
)) {
325 if (text_length
== last_length
|| !HasWordCharacters(text
, last_length
)) {
326 completion
->didFinishCheckingText(last_results_
);
330 int length
= static_cast<int>(text_length
);
331 U16_PREV(text
.data(), 0, length
, code
);
332 UErrorCode error
= U_ZERO_ERROR
;
333 if (uscript_getScript(code
, &error
) != USCRIPT_COMMON
) {
334 completion
->didCancelCheckingText();
338 // Create a subset of the cached results and return it if the given text is a
339 // substring of the cached text.
340 if (text_length
< last_length
&&
341 !last_request_
.compare(0, text_length
, request
)) {
342 size_t result_size
= 0;
343 for (size_t i
= 0; i
< last_results_
.size(); ++i
) {
344 size_t start
= last_results_
[i
].location
;
345 size_t end
= start
+ last_results_
[i
].length
;
346 if (start
<= text_length
&& end
<= text_length
)
349 if (result_size
> 0) {
350 blink::WebVector
<blink::WebTextCheckingResult
> results(result_size
);
351 for (size_t i
= 0; i
< result_size
; ++i
) {
352 results
[i
].decoration
= last_results_
[i
].decoration
;
353 results
[i
].location
= last_results_
[i
].location
;
354 results
[i
].length
= last_results_
[i
].length
;
355 results
[i
].replacement
= last_results_
[i
].replacement
;
357 completion
->didFinishCheckingText(results
);