Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / chrome / browser / spellchecker / spelling_service_client.cc
blob888af4a27c3c1a01b3d36b7e3096019e9caa91e0
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"
7 #include <algorithm>
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"
27 #include "url/gurl.h"
29 namespace {
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
35 // service.
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"};
44 } // namespace
46 SpellingServiceClient::SpellingServiceClient() {
49 SpellingServiceClient::~SpellingServiceClient() {
50 STLDeleteContainerPairPointers(spellcheck_fetchers_.begin(),
51 spellcheck_fetchers_.end());
54 bool SpellingServiceClient::RequestTextCheck(
55 content::BrowserContext* context,
56 ServiceType type,
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>());
62 return false;
65 const PrefService* pref = user_prefs::UserPrefs::Get(context);
66 DCHECK(pref);
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,
82 kApostrophe);
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[] =
88 "{"
89 "\"method\":\"spelling.check\","
90 "\"apiVersion\":\"v%d\","
91 "\"params\":{"
92 "\"text\":%s,"
93 "\"language\":\"%s\","
94 "\"originCountry\":\"%s\","
95 "\"key\":%s"
96 "}"
97 "}";
98 std::string api_key = base::GetQuotedJSONString(google_apis::GetAPIKey());
99 std::string request = base::StringPrintf(
100 kSpellingRequest,
101 type,
102 encoded_text.c_str(),
103 language_code.c_str(),
104 country_code.c_str(),
105 api_key.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);
116 fetcher->Start();
117 return true;
120 bool SpellingServiceClient::IsAvailable(
121 content::BrowserContext* context,
122 ServiceType type) {
123 const PrefService* pref = user_prefs::UserPrefs::Get(context);
124 DCHECK(pref);
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
127 // unavailable.
128 if (!pref->GetBoolean(prefs::kEnableContinuousSpellcheck) ||
129 !pref->GetBoolean(prefs::kSpellCheckUseSpellingService) ||
130 context->IsOffTheRecord())
131 return false;
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).
135 std::string locale;
136 pref->GetList(prefs::kSpellCheckDictionaries)->GetString(0, &locale);
137 if (locale.empty())
138 return false;
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
169 // text, and;
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.
174 // {
175 // "result": {
176 // "spellingCheckResponse": {
177 // "misspellings": [{
178 // "charStart": 10,
179 // "charLength": 5,
180 // "suggestions": [{ "suggestion": "quack" }],
181 // "canAutoCorrect": false
182 // }]
183 // }
184 // }
185 // }
186 // If the service is not available, the Spelling service returns JSON with an
187 // error.
188 // {
189 // "error": {
190 // "code": 400,
191 // "message": "Bad Request",
192 // "data": [...]
193 // }
194 // }
195 scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>(
196 base::JSONReader::Read(data, base::JSON_ALLOW_TRAILING_COMMAS)
197 .release()));
198 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
199 return false;
201 // Check for errors from spelling service.
202 base::DictionaryValue* error = NULL;
203 if (value->GetDictionary(kErrorPath, &error))
204 return false;
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))
211 return true;
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))
219 return false;
221 int start = 0;
222 int length = 0;
223 base::ListValue* suggestions = NULL;
224 if (!misspelling->GetInteger("charStart", &start) ||
225 !misspelling->GetInteger("charLength", &length) ||
226 !misspelling->GetList("suggestions", &suggestions)) {
227 return false;
230 base::DictionaryValue* suggestion = NULL;
231 base::string16 replacement;
232 if (!suggestions->GetDictionary(0, &suggestion) ||
233 !suggestion->GetString("suggestion", &replacement)) {
234 return false;
236 SpellCheckResult result(
237 SpellCheckResult::SPELLING, start, length, replacement);
238 results->push_back(result);
240 return true;
243 SpellingServiceClient::TextCheckCallbackData::TextCheckCallbackData(
244 TextCheckCompleteCallback callback,
245 base::string16 text)
246 : callback(callback),
247 text(text) {
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) {
262 std::string data;
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(
274 const GURL& url) {
275 return net::URLFetcher::Create(url, net::URLFetcher::POST, this);