Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / spellchecker / spelling_service_client.cc
blob06ce0a5853931ea4b3b3059dba065b318b22e5d8
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/user_prefs/user_prefs.h"
22 #include "content/public/browser/browser_context.h"
23 #include "google_apis/google_api_keys.h"
24 #include "net/base/load_flags.h"
25 #include "net/url_request/url_fetcher.h"
26 #include "url/gurl.h"
28 namespace {
30 // The URL for requesting spell checking and sending user feedback.
31 const char kSpellingServiceURL[] = "https://www.googleapis.com/rpc";
33 // The location of spellcheck suggestions in JSON response from spelling
34 // service.
35 const char kMisspellingsPath[] = "result.spellingCheckResponse.misspellings";
37 // The location of error messages in JSON response from spelling service.
38 const char kErrorPath[] = "error";
40 // Languages currently supported by SPELLCHECK.
41 const char* const kValidLanguages[] = {"en", "es", "fi", "da"};
43 } // namespace
45 SpellingServiceClient::SpellingServiceClient() {
48 SpellingServiceClient::~SpellingServiceClient() {
49 STLDeleteContainerPairPointers(spellcheck_fetchers_.begin(),
50 spellcheck_fetchers_.end());
53 bool SpellingServiceClient::RequestTextCheck(
54 content::BrowserContext* context,
55 ServiceType type,
56 const base::string16& text,
57 const TextCheckCompleteCallback& callback) {
58 DCHECK(type == SUGGEST || type == SPELLCHECK);
59 if (!context || !IsAvailable(context, type)) {
60 callback.Run(false, text, std::vector<SpellCheckResult>());
61 return false;
64 const PrefService* pref = user_prefs::UserPrefs::Get(context);
65 DCHECK(pref);
67 std::string dictionary;
68 pref->GetList(prefs::kSpellCheckDictionaries)->GetString(0, &dictionary);
70 std::string language_code;
71 std::string country_code;
72 chrome::spellcheck_common::GetISOLanguageCountryCodeFromLocale(
73 dictionary, &language_code, &country_code);
75 // Replace typographical apostrophes with typewriter apostrophes, so that
76 // server word breaker behaves correctly.
77 const base::char16 kApostrophe = 0x27;
78 const base::char16 kRightSingleQuotationMark = 0x2019;
79 base::string16 text_copy = text;
80 std::replace(text_copy.begin(), text_copy.end(), kRightSingleQuotationMark,
81 kApostrophe);
83 // Format the JSON request to be sent to the Spelling service.
84 std::string encoded_text = base::GetQuotedJSONString(text_copy);
86 static const char kSpellingRequest[] =
87 "{"
88 "\"method\":\"spelling.check\","
89 "\"apiVersion\":\"v%d\","
90 "\"params\":{"
91 "\"text\":%s,"
92 "\"language\":\"%s\","
93 "\"originCountry\":\"%s\","
94 "\"key\":%s"
95 "}"
96 "}";
97 std::string api_key = base::GetQuotedJSONString(google_apis::GetAPIKey());
98 std::string request = base::StringPrintf(
99 kSpellingRequest,
100 type,
101 encoded_text.c_str(),
102 language_code.c_str(),
103 country_code.c_str(),
104 api_key.c_str());
106 GURL url = GURL(kSpellingServiceURL);
107 net::URLFetcher* fetcher = CreateURLFetcher(url).release();
108 fetcher->SetRequestContext(context->GetRequestContext());
109 fetcher->SetUploadData("application/json", request);
110 fetcher->SetLoadFlags(
111 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
112 spellcheck_fetchers_[fetcher] = new TextCheckCallbackData(callback, text);
113 fetcher->Start();
114 return true;
117 bool SpellingServiceClient::IsAvailable(
118 content::BrowserContext* context,
119 ServiceType type) {
120 const PrefService* pref = user_prefs::UserPrefs::Get(context);
121 DCHECK(pref);
122 // If prefs don't allow spellchecking, if the context is off the record, or if
123 // multilingual spellchecking is enabled the spelling service should be
124 // unavailable.
125 if (!pref->GetBoolean(prefs::kEnableContinuousSpellcheck) ||
126 !pref->GetBoolean(prefs::kSpellCheckUseSpellingService) ||
127 context->IsOffTheRecord())
128 return false;
130 // If the locale for spelling has not been set, the user has not decided to
131 // use spellcheck so we don't do anything remote (suggest or spelling).
132 std::string locale;
133 pref->GetList(prefs::kSpellCheckDictionaries)->GetString(0, &locale);
134 if (locale.empty())
135 return false;
137 // Finally, if all options are available, we only enable only SUGGEST
138 // if SPELLCHECK is not available for our language because SPELLCHECK results
139 // are a superset of SUGGEST results.
140 for (const char* language : kValidLanguages) {
141 if (!locale.compare(0, 2, language))
142 return type == SPELLCHECK;
145 // Only SUGGEST is allowed.
146 return type == SUGGEST;
149 bool SpellingServiceClient::ParseResponse(
150 const std::string& data,
151 std::vector<SpellCheckResult>* results) {
152 // When this JSON-RPC call finishes successfully, the Spelling service returns
153 // an JSON object listed below.
154 // * result - an envelope object representing the result from the APIARY
155 // server, which is the JSON-API front-end for the Spelling service. This
156 // object consists of the following variable:
157 // - spellingCheckResponse (SpellingCheckResponse).
158 // * SpellingCheckResponse - an object representing the result from the
159 // Spelling service. This object consists of the following variable:
160 // - misspellings (optional array of Misspelling)
161 // * Misspelling - an object representing a misspelling region and its
162 // suggestions. This object consists of the following variables:
163 // - charStart (number) - the beginning of the misspelled region;
164 // - charLength (number) - the length of the misspelled region;
165 // - suggestions (array of string) - the suggestions for the misspelling
166 // text, and;
167 // - canAutoCorrect (optional boolean) - whether we can use the first
168 // suggestion for auto-correction.
169 // For example, the Spelling service returns the following JSON when we send a
170 // spelling request for "duck goes quisk" as of 16 August, 2011.
171 // {
172 // "result": {
173 // "spellingCheckResponse": {
174 // "misspellings": [{
175 // "charStart": 10,
176 // "charLength": 5,
177 // "suggestions": [{ "suggestion": "quack" }],
178 // "canAutoCorrect": false
179 // }]
180 // }
181 // }
182 // }
183 // If the service is not available, the Spelling service returns JSON with an
184 // error.
185 // {
186 // "error": {
187 // "code": 400,
188 // "message": "Bad Request",
189 // "data": [...]
190 // }
191 // }
192 scoped_ptr<base::DictionaryValue> value(
193 static_cast<base::DictionaryValue*>(base::JSONReader::DeprecatedRead(
194 data, base::JSON_ALLOW_TRAILING_COMMAS)));
195 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
196 return false;
198 // Check for errors from spelling service.
199 base::DictionaryValue* error = NULL;
200 if (value->GetDictionary(kErrorPath, &error))
201 return false;
203 // Retrieve the array of Misspelling objects. When the input text does not
204 // have misspelled words, it returns an empty JSON. (In this case, its HTTP
205 // status is 200.) We just return true for this case.
206 base::ListValue* misspellings = NULL;
207 if (!value->GetList(kMisspellingsPath, &misspellings))
208 return true;
210 for (size_t i = 0; i < misspellings->GetSize(); ++i) {
211 // Retrieve the i-th misspelling region and put it to the given vector. When
212 // the Spelling service sends two or more suggestions, we read only the
213 // first one because SpellCheckResult can store only one suggestion.
214 base::DictionaryValue* misspelling = NULL;
215 if (!misspellings->GetDictionary(i, &misspelling))
216 return false;
218 int start = 0;
219 int length = 0;
220 base::ListValue* suggestions = NULL;
221 if (!misspelling->GetInteger("charStart", &start) ||
222 !misspelling->GetInteger("charLength", &length) ||
223 !misspelling->GetList("suggestions", &suggestions)) {
224 return false;
227 base::DictionaryValue* suggestion = NULL;
228 base::string16 replacement;
229 if (!suggestions->GetDictionary(0, &suggestion) ||
230 !suggestion->GetString("suggestion", &replacement)) {
231 return false;
233 SpellCheckResult result(
234 SpellCheckResult::SPELLING, start, length, replacement);
235 results->push_back(result);
237 return true;
240 SpellingServiceClient::TextCheckCallbackData::TextCheckCallbackData(
241 TextCheckCompleteCallback callback,
242 base::string16 text)
243 : callback(callback),
244 text(text) {
247 SpellingServiceClient::TextCheckCallbackData::~TextCheckCallbackData() {
250 void SpellingServiceClient::OnURLFetchComplete(
251 const net::URLFetcher* source) {
252 DCHECK(spellcheck_fetchers_[source]);
253 scoped_ptr<const net::URLFetcher> fetcher(source);
254 scoped_ptr<TextCheckCallbackData>
255 callback_data(spellcheck_fetchers_[fetcher.get()]);
256 bool success = false;
257 std::vector<SpellCheckResult> results;
258 if (fetcher->GetResponseCode() / 100 == 2) {
259 std::string data;
260 fetcher->GetResponseAsString(&data);
261 success = ParseResponse(data, &results);
263 spellcheck_fetchers_.erase(fetcher.get());
265 // The callback may release the last (transitive) dependency on |this|. It
266 // MUST be the last function called.
267 callback_data->callback.Run(success, callback_data->text, results);
270 scoped_ptr<net::URLFetcher> SpellingServiceClient::CreateURLFetcher(
271 const GURL& url) {
272 return net::URLFetcher::Create(url, net::URLFetcher::POST, this);