|
OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011 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. | |
4 | |
5 #include "chrome/browser/tab_contents/spelling_menu_observer.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/json/json_reader.h" | |
10 #include "base/json/string_escape.h" | |
11 #include "base/stringprintf.h" | |
12 #include "base/utf_string_conversions.h" | |
13 #include "base/values.h" | |
14 #include "chrome/app/chrome_command_ids.h" | |
15 #include "chrome/browser/prefs/pref_service.h" | |
16 #include "chrome/browser/profiles/profile.h" | |
17 #include "chrome/browser/tab_contents/render_view_context_menu.h" | |
18 #include "chrome/common/pref_names.h" | |
19 #include "content/browser/renderer_host/render_view_host.h" | |
20 #include "googleurl/src/gurl.h" | |
21 #include "grit/generated_resources.h" | |
22 #include "ui/base/l10n/l10n_util.h" | |
23 #include "unicode/uloc.h" | |
24 #include "webkit/glue/context_menu.h" | |
25 | |
26 #if defined(GOOGLE_CHROME_BUILD) | |
27 #include "chrome/browser/spellchecker/internal/spellcheck_internal.h" | |
28 #endif | |
29 | |
30 SpellingMenuObserver::SpellingMenuObserver( | |
31 RenderViewContextMenuDelegate* delegate) | |
32 : delegate_(delegate), | |
33 loading_frame_(0), | |
34 succeeded_(false) { | |
35 } | |
36 | |
37 SpellingMenuObserver::~SpellingMenuObserver() { | |
38 } | |
39 | |
40 void SpellingMenuObserver::InitMenu(const ContextMenuParams& params) { | |
41 // We add this context-menu item only on Google Chrome until we release the | |
42 // Google Spelling API. | |
43 #if defined(GOOGLE_CHROME_BUILD) | |
Ben Goodger (Google)
2011/08/23 16:12:52
I do wonder if this code shouldn't be ifdefed like
Hironori Bono
2011/08/25 11:18:58
Done. I have removed all ifdefs except the above c
| |
44 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
45 | |
46 // Exit if we are not in an ediable element because we add a menu item only | |
Avi (use Gerrit)
2011/08/23 18:38:19
s/ediable/editable/
Hironori Bono
2011/08/25 11:18:58
Done.
| |
47 // for editable elements. | |
48 if (!params.is_editable) | |
49 return; | |
50 | |
51 Profile* profile = delegate_->GetProfile(); | |
52 if (!profile || !profile->GetRequestContext()) | |
53 return; | |
54 | |
55 // Retrieve the misspelled word to be sent to the Spelling service. | |
56 string16 text = params.misspelled_word; | |
57 if (text.empty()) | |
58 return; | |
59 | |
60 // Initialize variables used in OnURLFetchComplete(). We copy the input text | |
61 // to the result text so we can replace its misspelled regions with | |
62 // suggestions. | |
63 loading_frame_ = 0; | |
64 succeeded_ = false; | |
65 result_ = text; | |
66 | |
67 // Add a placeholder item. This item will be updated when we receive a | |
68 // response from the Spelling service. (We do not have to disable this item | |
69 // now since Chrome will call IsCommandIdEnabled() and disable it.) | |
70 loading_message_ = | |
71 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_SPELLING_CHECKING); | |
72 delegate_->AddMenuItem(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION, | |
73 loading_message_); | |
74 | |
75 // Invoke a JSON-RPC call to the Spelling service in the background so we can | |
76 // update the placeholder item when we receive its response. It also starts | |
77 // the animation timer so we can show animation until we receive it. | |
78 const PrefService* pref = profile->GetPrefs(); | |
79 std::string language = | |
80 pref ? pref->GetString(prefs::kSpellCheckDictionary) : "en-US"; | |
81 Invoke(text, language, profile->GetRequestContext()); | |
82 #endif | |
83 } | |
84 | |
85 bool SpellingMenuObserver::IsCommandIdSupported(int command_id) { | |
86 return command_id == IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION; | |
87 } | |
88 | |
89 bool SpellingMenuObserver::IsCommandIdEnabled(int command_id) { | |
90 return command_id == IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION && succeeded_; | |
91 } | |
92 | |
93 void SpellingMenuObserver::ExecuteCommand(int command_id) { | |
94 if (IsCommandIdEnabled(command_id)) | |
95 delegate_->GetRenderViewHost()->Replace(result_); | |
96 } | |
97 | |
98 bool SpellingMenuObserver::Invoke(const string16& text, | |
99 const std::string& locale, | |
100 net::URLRequestContextGetter* context) { | |
101 #if defined(GOOGLE_CHROME_BUILD) | |
102 // Create the parameters needed by Spelling API. Spelling API needs three | |
103 // parameters: ISO language code, ISO3 country code, and text to be checked by | |
104 // the service. On the other hand, Chrome uses an ISO locale ID and ith may | |
105 // not include a country ID, e.g. "fr", "de", etc. To create the input | |
106 // parameters, we convert the UI locale to a full locale ID, and convert the | |
107 // full locale ID to an ISO language code and and ISO3 country code. Also, we | |
108 // convert the given text to a JSON string, i.e. quote all its non-ASCII | |
109 // characters. | |
110 UErrorCode error = U_ZERO_ERROR; | |
111 char id[ULOC_LANG_CAPACITY + ULOC_SCRIPT_CAPACITY + ULOC_COUNTRY_CAPACITY]; | |
112 uloc_addLikelySubtags(locale.c_str(), id, arraysize(id), &error); | |
113 | |
114 error = U_ZERO_ERROR; | |
115 char language[ULOC_LANG_CAPACITY]; | |
116 uloc_getLanguage(id, language, arraysize(language), &error); | |
117 | |
118 const char* country = uloc_getISO3Country(id); | |
119 | |
120 std::string encoded_text; | |
121 base::JsonDoubleQuote(text, false, &encoded_text); | |
122 | |
123 // Format the JSON request to be sent to the Speling service. | |
Avi (use Gerrit)
2011/08/23 18:38:19
s/Speling/Spelling/
Hironori Bono
2011/08/25 11:18:58
Done.
| |
124 static const char kSpellingRequest[] = | |
125 "{" | |
126 "\"method\":\"spelling.check\"," | |
127 "\"apiVersion\":\"v1\"," | |
128 "\"params\":{" | |
129 "\"text\":\"%s\"," | |
130 "\"language\":\"%s\"," | |
131 "\"origin_country\":\"%s\"," | |
132 "\"key\":\"" SPELLING_SERVICE_KEY "\"" | |
133 "}" | |
134 "}"; | |
135 std::string request = base::StringPrintf(kSpellingRequest, | |
136 encoded_text.c_str(), | |
137 language, country); | |
138 | |
139 static const char kSpellingServiceURL[] = SPELLING_SERVICE_URL; | |
140 GURL url = GURL(kSpellingServiceURL); | |
141 fetcher_.reset(new URLFetcher(url, URLFetcher::POST, this)); | |
142 fetcher_->set_request_context(context); | |
143 fetcher_->set_upload_data("application/json", request); | |
144 fetcher_->Start(); | |
145 | |
146 animation_timer_.Start(base::TimeDelta::FromSeconds(1), | |
147 this, &SpellingMenuObserver::OnAnimationTimerExpired); | |
148 | |
149 return true; | |
150 #else | |
151 return false; | |
152 #endif | |
153 } | |
154 | |
155 void SpellingMenuObserver::OnURLFetchComplete( | |
156 const URLFetcher* source, | |
157 const GURL& url, | |
158 const net::URLRequestStatus& status, | |
159 int response, | |
160 const net::ResponseCookies& cookies, | |
161 const std::string& data) { | |
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
163 | |
164 fetcher_.reset(); | |
165 animation_timer_.Stop(); | |
166 | |
167 // Parse the response JSON and replace misspelled words in the |result_| text | |
168 // with their suggestions. | |
169 succeeded_ = ParseResponse(response, data); | |
170 if (!succeeded_) | |
171 result_ = l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_SPELLING_CORRECT); | |
172 | |
173 // Update the menu item with the result text. We enable this item only when | |
174 // the request text has misspelled words. (We disable this item not only when | |
175 // we receive a server error but also when the input text consists only of | |
176 // well-spelled words. For either case, we do not need to replace the input | |
177 // text.) | |
178 delegate_->UpdateMenuItem(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION, succeeded_, | |
179 result_); | |
180 } | |
181 | |
182 bool SpellingMenuObserver::ParseResponse(int response, | |
183 const std::string& data) { | |
184 // When this JSON-RPC call finishes successfully, the Spelling service returns | |
185 // an JSON object listed below. | |
186 // * result - an envelope object representing the result from the APIARY | |
187 // server, which is the JSON-API front-end for the Spelling service. This | |
188 // object consists of the following variable: | |
189 // - spellingCheckResponse (SpellingCheckResponse). | |
190 // * SpellingCheckResponse - an object representing the result from the | |
191 // Spelling service. This object consists of the following variable: | |
192 // - misspellings (optional array of Misspelling) | |
193 // * Misspelling - an object representing a misspelling region and its | |
194 // suggestions. This object consists of the following variables: | |
195 // - charStart (number) - the beginning of the misspelled region; | |
196 // - charLength (number) - the length of the misspelled region; | |
197 // - suggestions (array of string) - the suggestions for the misspelling | |
198 // text, and; | |
199 // - canAutoCorrect (optional boolean) - whether we can use the first | |
200 // suggestion for auto-correction. | |
201 // For example, the Spelling service returns the following JSON when we send a | |
202 // spelling request for "duck goes quisk" as of 16 August, 2011. | |
203 // { | |
204 // "result": { | |
205 // "spellingCheckResponse": { | |
206 // "misspellings": [{ | |
207 // "charStart": 10, | |
208 // "charLength": 5, | |
209 // "suggestions": [{ "suggestion": "quack" }], | |
210 // "canAutoCorrect": false | |
211 // }] | |
212 // } | |
213 // } | |
214 | |
215 // When a server error happened in the APIARY server (including when we cannot | |
216 // get any responses from the server), it returns an HTTP error. | |
217 if ((response / 100) != 2) | |
218 return false; | |
219 | |
220 scoped_ptr<DictionaryValue> value( | |
221 static_cast<DictionaryValue*>(base::JSONReader::Read(data, true))); | |
222 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
223 return false; | |
224 | |
225 // Retrieve the array of Misspelling objects. When the APIARY servier failed | |
226 // calling the Spelling service, it returns a JSON representing the service | |
227 // error. (In this case, its HTTP status is 200.) We just return false for | |
228 // this case. | |
229 ListValue* misspellings = NULL; | |
230 const char kMisspellings[] = "result.spellingCheckResponse.misspellings"; | |
231 if (!value->GetList(kMisspellings, &misspellings)) | |
232 return false; | |
233 | |
234 // For each misspelled region, we replace it with its first suggestion. | |
235 for (size_t i = 0; i < misspellings->GetSize(); ++i) { | |
236 // Retrieve the i-th misspelling region, which represents misspelling text | |
237 // in the request text and its alternative. | |
238 DictionaryValue* misspelling = NULL; | |
239 if (!misspellings->GetDictionary(i, &misspelling)) | |
240 return false; | |
241 | |
242 int start = 0; | |
243 int length = 0; | |
244 ListValue* suggestions = NULL; | |
245 if (!misspelling->GetInteger("charStart", &start) || | |
246 !misspelling->GetInteger("charLength", &length) || | |
247 !misspelling->GetList("suggestions", &suggestions)) { | |
248 return false; | |
249 } | |
250 | |
251 // Retrieve the alternative text and replace the misspelling region with the | |
252 // alternative. | |
253 DictionaryValue* suggestion = NULL; | |
254 string16 text; | |
255 if (!suggestions->GetDictionary(0, &suggestion) || | |
256 !suggestion->GetString("suggestion", &text)) { | |
257 return false; | |
258 } | |
259 result_.replace(start, length, text); | |
260 } | |
261 | |
262 return true; | |
263 } | |
264 | |
265 void SpellingMenuObserver::OnAnimationTimerExpired() { | |
266 if (!fetcher_.get()) | |
267 return; | |
268 | |
269 // Append '.' characters to the end of "Checking". | |
270 loading_frame_ = (loading_frame_ + 1) & 3; | |
271 string16 loading_message = loading_message_; | |
272 for (int i = 0; i < loading_frame_; ++i) | |
273 loading_message.push_back('.'); | |
274 | |
275 // Update the menu item with the text. We disable this item to prevent users | |
276 // from selecting it. | |
277 delegate_->UpdateMenuItem(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION, | |
278 false, loading_message); | |
279 } | |
OLD | NEW |