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/browser/spellchecker/spelling_service_client.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/string_escape.h"
11 #include "base/logging.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/common/spellcheck_common.h"
20 #include "chrome/common/spellcheck_result.h"
21 #include "components/data_use_measurement/core/data_use_user_data.h"
22 #include "components/user_prefs/user_prefs.h"
23 #include "content/public/browser/browser_context.h"
24 #include "google_apis/google_api_keys.h"
25 #include "net/base/load_flags.h"
26 #include "net/url_request/url_fetcher.h"
31 // The URL for requesting spell checking and sending user feedback.
32 const char kSpellingServiceURL
[] = "https://www.googleapis.com/rpc";
34 // The location of spellcheck suggestions in JSON response from spelling
36 const char kMisspellingsPath
[] = "result.spellingCheckResponse.misspellings";
38 // The location of error messages in JSON response from spelling service.
39 const char kErrorPath
[] = "error";
41 // Languages currently supported by SPELLCHECK.
42 const char* const kValidLanguages
[] = {"en", "es", "fi", "da"};
46 SpellingServiceClient::SpellingServiceClient() {
49 SpellingServiceClient::~SpellingServiceClient() {
50 STLDeleteContainerPairPointers(spellcheck_fetchers_
.begin(),
51 spellcheck_fetchers_
.end());
54 bool SpellingServiceClient::RequestTextCheck(
55 content::BrowserContext
* context
,
57 const base::string16
& text
,
58 const TextCheckCompleteCallback
& callback
) {
59 DCHECK(type
== SUGGEST
|| type
== SPELLCHECK
);
60 if (!context
|| !IsAvailable(context
, type
)) {
61 callback
.Run(false, text
, std::vector
<SpellCheckResult
>());
65 const PrefService
* pref
= user_prefs::UserPrefs::Get(context
);
68 std::string dictionary
;
69 pref
->GetList(prefs::kSpellCheckDictionaries
)->GetString(0, &dictionary
);
71 std::string language_code
;
72 std::string country_code
;
73 chrome::spellcheck_common::GetISOLanguageCountryCodeFromLocale(
74 dictionary
, &language_code
, &country_code
);
76 // Replace typographical apostrophes with typewriter apostrophes, so that
77 // server word breaker behaves correctly.
78 const base::char16 kApostrophe
= 0x27;
79 const base::char16 kRightSingleQuotationMark
= 0x2019;
80 base::string16 text_copy
= text
;
81 std::replace(text_copy
.begin(), text_copy
.end(), kRightSingleQuotationMark
,
84 // Format the JSON request to be sent to the Spelling service.
85 std::string encoded_text
= base::GetQuotedJSONString(text_copy
);
87 static const char kSpellingRequest
[] =
89 "\"method\":\"spelling.check\","
90 "\"apiVersion\":\"v%d\","
93 "\"language\":\"%s\","
94 "\"originCountry\":\"%s\","
98 std::string api_key
= base::GetQuotedJSONString(google_apis::GetAPIKey());
99 std::string request
= base::StringPrintf(
102 encoded_text
.c_str(),
103 language_code
.c_str(),
104 country_code
.c_str(),
107 GURL url
= GURL(kSpellingServiceURL
);
108 net::URLFetcher
* fetcher
= CreateURLFetcher(url
).release();
109 data_use_measurement::DataUseUserData::AttachToFetcher(
110 fetcher
, data_use_measurement::DataUseUserData::SPELL_CHECKER
);
111 fetcher
->SetRequestContext(context
->GetRequestContext());
112 fetcher
->SetUploadData("application/json", request
);
113 fetcher
->SetLoadFlags(
114 net::LOAD_DO_NOT_SEND_COOKIES
| net::LOAD_DO_NOT_SAVE_COOKIES
);
115 spellcheck_fetchers_
[fetcher
] = new TextCheckCallbackData(callback
, text
);
120 bool SpellingServiceClient::IsAvailable(
121 content::BrowserContext
* context
,
123 const PrefService
* pref
= user_prefs::UserPrefs::Get(context
);
125 // If prefs don't allow spellchecking, if the context is off the record, or if
126 // multilingual spellchecking is enabled the spelling service should be
128 if (!pref
->GetBoolean(prefs::kEnableContinuousSpellcheck
) ||
129 !pref
->GetBoolean(prefs::kSpellCheckUseSpellingService
) ||
130 context
->IsOffTheRecord())
133 // If the locale for spelling has not been set, the user has not decided to
134 // use spellcheck so we don't do anything remote (suggest or spelling).
136 pref
->GetList(prefs::kSpellCheckDictionaries
)->GetString(0, &locale
);
140 // Finally, if all options are available, we only enable only SUGGEST
141 // if SPELLCHECK is not available for our language because SPELLCHECK results
142 // are a superset of SUGGEST results.
143 for (const char* language
: kValidLanguages
) {
144 if (!locale
.compare(0, 2, language
))
145 return type
== SPELLCHECK
;
148 // Only SUGGEST is allowed.
149 return type
== SUGGEST
;
152 bool SpellingServiceClient::ParseResponse(
153 const std::string
& data
,
154 std::vector
<SpellCheckResult
>* results
) {
155 // When this JSON-RPC call finishes successfully, the Spelling service returns
156 // an JSON object listed below.
157 // * result - an envelope object representing the result from the APIARY
158 // server, which is the JSON-API front-end for the Spelling service. This
159 // object consists of the following variable:
160 // - spellingCheckResponse (SpellingCheckResponse).
161 // * SpellingCheckResponse - an object representing the result from the
162 // Spelling service. This object consists of the following variable:
163 // - misspellings (optional array of Misspelling)
164 // * Misspelling - an object representing a misspelling region and its
165 // suggestions. This object consists of the following variables:
166 // - charStart (number) - the beginning of the misspelled region;
167 // - charLength (number) - the length of the misspelled region;
168 // - suggestions (array of string) - the suggestions for the misspelling
170 // - canAutoCorrect (optional boolean) - whether we can use the first
171 // suggestion for auto-correction.
172 // For example, the Spelling service returns the following JSON when we send a
173 // spelling request for "duck goes quisk" as of 16 August, 2011.
176 // "spellingCheckResponse": {
177 // "misspellings": [{
180 // "suggestions": [{ "suggestion": "quack" }],
181 // "canAutoCorrect": false
186 // If the service is not available, the Spelling service returns JSON with an
191 // "message": "Bad Request",
195 scoped_ptr
<base::DictionaryValue
> value(static_cast<base::DictionaryValue
*>(
196 base::JSONReader::Read(data
, base::JSON_ALLOW_TRAILING_COMMAS
)
198 if (!value
.get() || !value
->IsType(base::Value::TYPE_DICTIONARY
))
201 // Check for errors from spelling service.
202 base::DictionaryValue
* error
= NULL
;
203 if (value
->GetDictionary(kErrorPath
, &error
))
206 // Retrieve the array of Misspelling objects. When the input text does not
207 // have misspelled words, it returns an empty JSON. (In this case, its HTTP
208 // status is 200.) We just return true for this case.
209 base::ListValue
* misspellings
= NULL
;
210 if (!value
->GetList(kMisspellingsPath
, &misspellings
))
213 for (size_t i
= 0; i
< misspellings
->GetSize(); ++i
) {
214 // Retrieve the i-th misspelling region and put it to the given vector. When
215 // the Spelling service sends two or more suggestions, we read only the
216 // first one because SpellCheckResult can store only one suggestion.
217 base::DictionaryValue
* misspelling
= NULL
;
218 if (!misspellings
->GetDictionary(i
, &misspelling
))
223 base::ListValue
* suggestions
= NULL
;
224 if (!misspelling
->GetInteger("charStart", &start
) ||
225 !misspelling
->GetInteger("charLength", &length
) ||
226 !misspelling
->GetList("suggestions", &suggestions
)) {
230 base::DictionaryValue
* suggestion
= NULL
;
231 base::string16 replacement
;
232 if (!suggestions
->GetDictionary(0, &suggestion
) ||
233 !suggestion
->GetString("suggestion", &replacement
)) {
236 SpellCheckResult
result(
237 SpellCheckResult::SPELLING
, start
, length
, replacement
);
238 results
->push_back(result
);
243 SpellingServiceClient::TextCheckCallbackData::TextCheckCallbackData(
244 TextCheckCompleteCallback callback
,
246 : callback(callback
),
250 SpellingServiceClient::TextCheckCallbackData::~TextCheckCallbackData() {
253 void SpellingServiceClient::OnURLFetchComplete(
254 const net::URLFetcher
* source
) {
255 DCHECK(spellcheck_fetchers_
[source
]);
256 scoped_ptr
<const net::URLFetcher
> fetcher(source
);
257 scoped_ptr
<TextCheckCallbackData
>
258 callback_data(spellcheck_fetchers_
[fetcher
.get()]);
259 bool success
= false;
260 std::vector
<SpellCheckResult
> results
;
261 if (fetcher
->GetResponseCode() / 100 == 2) {
263 fetcher
->GetResponseAsString(&data
);
264 success
= ParseResponse(data
, &results
);
266 spellcheck_fetchers_
.erase(fetcher
.get());
268 // The callback may release the last (transitive) dependency on |this|. It
269 // MUST be the last function called.
270 callback_data
->callback
.Run(success
, callback_data
->text
, results
);
273 scoped_ptr
<net::URLFetcher
> SpellingServiceClient::CreateURLFetcher(
275 return net::URLFetcher::Create(url
, net::URLFetcher::POST
, this);