OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/translate/translate_manager.h" | 5 #include "chrome/browser/translate/translate_manager.h" |
6 | 6 |
7 #include "base/command_line.h" | 7 #include "base/command_line.h" |
8 #include "base/compiler_specific.h" | 8 #include "base/compiler_specific.h" |
| 9 #include "base/json/json_reader.h" |
9 #include "base/memory/singleton.h" | 10 #include "base/memory/singleton.h" |
10 #include "base/metrics/histogram.h" | 11 #include "base/metrics/histogram.h" |
11 #include "base/string_split.h" | 12 #include "base/string_split.h" |
12 #include "base/string_util.h" | 13 #include "base/string_util.h" |
| 14 #include "base/values.h" |
13 #include "chrome/browser/autofill/autofill_manager.h" | 15 #include "chrome/browser/autofill/autofill_manager.h" |
14 #include "chrome/browser/browser_process.h" | 16 #include "chrome/browser/browser_process.h" |
15 #include "chrome/browser/prefs/pref_service.h" | 17 #include "chrome/browser/prefs/pref_service.h" |
16 #include "chrome/browser/profiles/profile.h" | 18 #include "chrome/browser/profiles/profile.h" |
17 #include "chrome/browser/tab_contents/language_state.h" | 19 #include "chrome/browser/tab_contents/language_state.h" |
18 #include "chrome/browser/tab_contents/tab_util.h" | 20 #include "chrome/browser/tab_contents/tab_util.h" |
19 #include "chrome/browser/tabs/tab_strip_model.h" | 21 #include "chrome/browser/tabs/tab_strip_model.h" |
20 #include "chrome/browser/translate/page_translated_details.h" | 22 #include "chrome/browser/translate/page_translated_details.h" |
21 #include "chrome/browser/translate/translate_infobar_delegate.h" | 23 #include "chrome/browser/translate/translate_infobar_delegate.h" |
22 #include "chrome/browser/translate/translate_tab_helper.h" | 24 #include "chrome/browser/translate/translate_tab_helper.h" |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 "yi", // Yiddish | 119 "yi", // Yiddish |
118 }; | 120 }; |
119 | 121 |
120 const char* const kTranslateScriptURL = | 122 const char* const kTranslateScriptURL = |
121 "http://translate.google.com/translate_a/element.js?" | 123 "http://translate.google.com/translate_a/element.js?" |
122 "cb=cr.googleTranslate.onTranslateElementLoad"; | 124 "cb=cr.googleTranslate.onTranslateElementLoad"; |
123 const char* const kTranslateScriptHeader = | 125 const char* const kTranslateScriptHeader = |
124 "Google-Translate-Element-Mode: library"; | 126 "Google-Translate-Element-Mode: library"; |
125 const char* const kReportLanguageDetectionErrorURL = | 127 const char* const kReportLanguageDetectionErrorURL = |
126 "http://translate.google.com/translate_error"; | 128 "http://translate.google.com/translate_error"; |
127 | 129 const char* const kLanguageListFetchURL = |
| 130 "http://translate.googleapis.com/translate_a/l?client=chrome&cb=sl"; |
| 131 const int kMaxRetryLanguageListFetch = 5; |
128 const int kTranslateScriptExpirationDelayMS = 24 * 60 * 60 * 1000; // 1 day. | 132 const int kTranslateScriptExpirationDelayMS = 24 * 60 * 60 * 1000; // 1 day. |
129 | 133 |
130 } // namespace | 134 } // namespace |
131 | 135 |
| 136 // This must be kept in sync with the &cb= value in the kLanguageListFetchURL. |
| 137 const char* const TranslateManager::kLanguageListCallbackName = "sl("; |
| 138 const char* const TranslateManager::kTargetLanguagesKey = "tl"; |
| 139 |
132 // static | 140 // static |
133 base::LazyInstance<std::set<std::string> > | 141 base::LazyInstance<std::set<std::string> > |
134 TranslateManager::supported_languages_(base::LINKER_INITIALIZED); | 142 TranslateManager::supported_languages_(base::LINKER_INITIALIZED); |
135 | 143 |
136 TranslateManager::~TranslateManager() { | 144 TranslateManager::~TranslateManager() { |
137 } | 145 } |
138 | 146 |
139 // static | 147 // static |
140 TranslateManager* TranslateManager::GetInstance() { | 148 TranslateManager* TranslateManager::GetInstance() { |
141 return Singleton<TranslateManager>::get(); | 149 return Singleton<TranslateManager>::get(); |
142 } | 150 } |
143 | 151 |
144 // static | 152 // static |
145 bool TranslateManager::IsTranslatableURL(const GURL& url) { | 153 bool TranslateManager::IsTranslatableURL(const GURL& url) { |
146 // A URLs is translatable unless it is one of the following: | 154 // A URLs is translatable unless it is one of the following: |
147 // - an internal URL (chrome:// and others) | 155 // - an internal URL (chrome:// and others) |
148 // - the devtools (which is considered UI) | 156 // - the devtools (which is considered UI) |
149 // - an FTP page (as FTP pages tend to have long lists of filenames that may | 157 // - an FTP page (as FTP pages tend to have long lists of filenames that may |
150 // confuse the CLD) | 158 // confuse the CLD) |
151 return !url.SchemeIs(chrome::kChromeUIScheme) && | 159 return !url.SchemeIs(chrome::kChromeUIScheme) && |
152 !url.SchemeIs(chrome::kChromeDevToolsScheme) && | 160 !url.SchemeIs(chrome::kChromeDevToolsScheme) && |
153 !url.SchemeIs(chrome::kFtpScheme); | 161 !url.SchemeIs(chrome::kFtpScheme); |
154 } | 162 } |
155 | 163 |
156 // static | 164 // static |
| 165 void TranslateManager::SetSupportedLanguages(const std::string& language_list) { |
| 166 // The format is: |
| 167 // sl({'sl': {'XX': 'LanguageName', ...}, 'tl': {'XX': 'LanguageName', ...}}) |
| 168 // Where "sl(" is set in kLanguageListCallbackName |
| 169 // and 'tl' is kTargetLanguagesKey |
| 170 if (!StartsWithASCII(language_list, kLanguageListCallbackName, false) || |
| 171 !EndsWith(language_list, ")", false)) { |
| 172 // We don't have a NOTREACHED here since this can happen in ui_tests, even |
| 173 // though the the BrowserMain function won't call us with parameters.ui_task |
| 174 // is NULL some tests don't set it, so we must bail here. |
| 175 return; |
| 176 } |
| 177 static const size_t kLanguageListCallbackNameLength = |
| 178 strlen(kLanguageListCallbackName); |
| 179 std::string languages_json = language_list.substr( |
| 180 kLanguageListCallbackNameLength, |
| 181 language_list.size() - kLanguageListCallbackNameLength - 1); |
| 182 // JSON doesn't support single quotes though this is what is used on the |
| 183 // translate server so we must replace them with double quotes. |
| 184 ReplaceSubstringsAfterOffset(&languages_json, 0, "'", "\""); |
| 185 scoped_ptr<Value> json_value(base::JSONReader::Read(languages_json, true)); |
| 186 if (json_value == NULL || !json_value->IsType(Value::TYPE_DICTIONARY)) { |
| 187 NOTREACHED(); |
| 188 return; |
| 189 } |
| 190 // The first level dictionary contains two sub-dict, one for source languages |
| 191 // and the other for target languages, we want to use the target languages. |
| 192 DictionaryValue* language_dict = |
| 193 static_cast<DictionaryValue*>(json_value.get()); |
| 194 DictionaryValue* target_languages = NULL; |
| 195 if (!language_dict->GetDictionary(kTargetLanguagesKey, &target_languages) || |
| 196 target_languages == NULL) { |
| 197 NOTREACHED(); |
| 198 return; |
| 199 } |
| 200 // Now we can clear our current state... |
| 201 std::set<std::string>* supported_languages = supported_languages_.Pointer(); |
| 202 supported_languages->clear(); |
| 203 // ... and replace it with the values we just fetched from the server. |
| 204 DictionaryValue::key_iterator iter = target_languages->begin_keys(); |
| 205 for (; iter != target_languages->end_keys(); ++iter) |
| 206 supported_languages_.Pointer()->insert(*iter); |
| 207 } |
| 208 |
| 209 // static |
| 210 void TranslateManager::InitSupportedLanguages() { |
| 211 // If our list of supported languages have not been set yet, we default |
| 212 // to our hard coded list of languages in kSupportedLanguages. |
| 213 if (supported_languages_.Pointer()->empty()) { |
| 214 for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) |
| 215 supported_languages_.Pointer()->insert(kSupportedLanguages[i]); |
| 216 } |
| 217 } |
| 218 |
| 219 // static |
157 void TranslateManager::GetSupportedLanguages( | 220 void TranslateManager::GetSupportedLanguages( |
158 std::vector<std::string>* languages) { | 221 std::vector<std::string>* languages) { |
159 DCHECK(languages && languages->empty()); | 222 DCHECK(languages && languages->empty()); |
160 for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) | 223 InitSupportedLanguages(); |
161 languages->push_back(kSupportedLanguages[i]); | 224 std::set<std::string>* supported_languages = supported_languages_.Pointer(); |
| 225 std::set<std::string>::const_iterator iter = supported_languages->begin(); |
| 226 for (; iter != supported_languages->end(); ++iter) |
| 227 languages->push_back(*iter); |
162 } | 228 } |
163 | 229 |
164 // static | 230 // static |
165 std::string TranslateManager::GetLanguageCode( | 231 std::string TranslateManager::GetLanguageCode( |
166 const std::string& chrome_locale) { | 232 const std::string& chrome_locale) { |
167 // Only remove the country code for country specific languages we don't | 233 // Only remove the country code for country specific languages we don't |
168 // support specifically yet. | 234 // support specifically yet. |
169 if (IsSupportedLanguage(chrome_locale)) | 235 if (IsSupportedLanguage(chrome_locale)) |
170 return chrome_locale; | 236 return chrome_locale; |
171 | 237 |
172 size_t hypen_index = chrome_locale.find('-'); | 238 size_t hypen_index = chrome_locale.find('-'); |
173 if (hypen_index == std::string::npos) | 239 if (hypen_index == std::string::npos) |
174 return chrome_locale; | 240 return chrome_locale; |
175 return chrome_locale.substr(0, hypen_index); | 241 return chrome_locale.substr(0, hypen_index); |
176 } | 242 } |
177 | 243 |
178 // static | 244 // static |
179 bool TranslateManager::IsSupportedLanguage(const std::string& page_language) { | 245 bool TranslateManager::IsSupportedLanguage(const std::string& page_language) { |
180 std::set<std::string>* supported_languages = supported_languages_.Pointer(); | 246 InitSupportedLanguages(); |
181 if (supported_languages->empty()) { | 247 return supported_languages_.Pointer()->count(page_language) != 0; |
182 for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) | |
183 supported_languages->insert(kSupportedLanguages[i]); | |
184 } | |
185 return supported_languages->find(page_language) != supported_languages->end(); | |
186 } | 248 } |
187 | 249 |
188 void TranslateManager::Observe(NotificationType type, | 250 void TranslateManager::Observe(NotificationType type, |
189 const NotificationSource& source, | 251 const NotificationSource& source, |
190 const NotificationDetails& details) { | 252 const NotificationDetails& details) { |
191 switch (type.value) { | 253 switch (type.value) { |
192 case NotificationType::NAV_ENTRY_COMMITTED: { | 254 case NotificationType::NAV_ENTRY_COMMITTED: { |
193 NavigationController* controller = | 255 NavigationController* controller = |
194 Source<NavigationController>(source).ptr(); | 256 Source<NavigationController>(source).ptr(); |
195 content::LoadCommittedDetails* load_details = | 257 content::LoadCommittedDetails* load_details = |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
281 } | 343 } |
282 } | 344 } |
283 | 345 |
284 void TranslateManager::OnURLFetchComplete(const URLFetcher* source, | 346 void TranslateManager::OnURLFetchComplete(const URLFetcher* source, |
285 const GURL& url, | 347 const GURL& url, |
286 const net::URLRequestStatus& status, | 348 const net::URLRequestStatus& status, |
287 int response_code, | 349 int response_code, |
288 const net::ResponseCookies& cookies, | 350 const net::ResponseCookies& cookies, |
289 const std::string& data) { | 351 const std::string& data) { |
290 scoped_ptr<const URLFetcher> delete_ptr(source); | 352 scoped_ptr<const URLFetcher> delete_ptr(source); |
291 DCHECK(translate_script_request_pending_); | 353 DCHECK(translate_script_request_pending_ || language_list_request_pending_); |
292 translate_script_request_pending_ = false; | 354 // We quickly recognize that we are handling a translate script request |
| 355 // if we don't have a language_list_request_pending_. Otherwise we do the |
| 356 // more expensive check of confirming we got the kTranslateScriptURL in the |
| 357 // rare case where we would have both requests pending at the same time. |
| 358 bool translate_script_request = !language_list_request_pending_ || |
| 359 url == GURL(kTranslateScriptURL); |
| 360 // Here we make sure that if we didn't get the translate_script_request, |
| 361 // we actually got a language_list_request. |
| 362 DCHECK(translate_script_request || url == GURL(kLanguageListFetchURL)); |
| 363 if (translate_script_request) |
| 364 translate_script_request_pending_ = false; |
| 365 else |
| 366 language_list_request_pending_ = false; |
| 367 |
293 bool error = | 368 bool error = |
294 (status.status() != net::URLRequestStatus::SUCCESS || | 369 (status.status() != net::URLRequestStatus::SUCCESS || |
295 response_code != 200); | 370 response_code != 200); |
296 | 371 |
297 if (!error) { | 372 if (translate_script_request) { |
298 base::StringPiece str = ResourceBundle::GetSharedInstance(). | 373 if (!error) { |
299 GetRawDataResource(IDR_TRANSLATE_JS); | 374 base::StringPiece str = ResourceBundle::GetSharedInstance(). |
300 DCHECK(translate_script_.empty()); | 375 GetRawDataResource(IDR_TRANSLATE_JS); |
301 str.CopyToString(&translate_script_); | 376 DCHECK(translate_script_.empty()); |
302 translate_script_ += "\n" + data; | 377 str.CopyToString(&translate_script_); |
303 // We'll expire the cached script after some time, to make sure long running | 378 translate_script_ += "\n" + data; |
304 // browsers still get fixes that might get pushed with newer scripts. | 379 // We'll expire the cached script after some time, to make sure long |
305 MessageLoop::current()->PostDelayedTask(FROM_HERE, | 380 // running browsers still get fixes that might get pushed with newer |
306 method_factory_.NewRunnableMethod( | 381 // scripts. |
307 &TranslateManager::ClearTranslateScript), | 382 MessageLoop::current()->PostDelayedTask(FROM_HERE, |
308 translate_script_expiration_delay_); | 383 method_factory_.NewRunnableMethod( |
| 384 &TranslateManager::ClearTranslateScript), |
| 385 translate_script_expiration_delay_); |
| 386 } |
| 387 // Process any pending requests. |
| 388 std::vector<PendingRequest>::const_iterator iter; |
| 389 for (iter = pending_requests_.begin(); iter != pending_requests_.end(); |
| 390 ++iter) { |
| 391 const PendingRequest& request = *iter; |
| 392 TabContents* tab = tab_util::GetTabContentsByID(request.render_process_id, |
| 393 request.render_view_id); |
| 394 if (!tab) { |
| 395 // The tab went away while we were retrieving the script. |
| 396 continue; |
| 397 } |
| 398 NavigationEntry* entry = tab->controller().GetActiveEntry(); |
| 399 if (!entry || entry->page_id() != request.page_id) { |
| 400 // We navigated away from the page the translation was triggered on. |
| 401 continue; |
| 402 } |
| 403 |
| 404 if (error) { |
| 405 ShowInfoBar(tab, TranslateInfoBarDelegate::CreateErrorDelegate( |
| 406 TranslateErrors::NETWORK, tab, |
| 407 request.source_lang, request.target_lang)); |
| 408 } else { |
| 409 // Translate the page. |
| 410 DoTranslatePage(tab, translate_script_, |
| 411 request.source_lang, request.target_lang); |
| 412 } |
| 413 } |
| 414 pending_requests_.clear(); |
| 415 } else { // if (translate_script_request) |
| 416 if (!error) |
| 417 SetSupportedLanguages(data); |
| 418 else |
| 419 VLOG(1) << "Failed to Fetch languages from: " << kLanguageListFetchURL; |
309 } | 420 } |
310 | |
311 // Process any pending requests. | |
312 std::vector<PendingRequest>::const_iterator iter; | |
313 for (iter = pending_requests_.begin(); iter != pending_requests_.end(); | |
314 ++iter) { | |
315 const PendingRequest& request = *iter; | |
316 TabContents* tab = tab_util::GetTabContentsByID(request.render_process_id, | |
317 request.render_view_id); | |
318 if (!tab) { | |
319 // The tab went away while we were retrieving the script. | |
320 continue; | |
321 } | |
322 NavigationEntry* entry = tab->controller().GetActiveEntry(); | |
323 if (!entry || entry->page_id() != request.page_id) { | |
324 // We navigated away from the page the translation was triggered on. | |
325 continue; | |
326 } | |
327 | |
328 if (error) { | |
329 ShowInfoBar(tab, TranslateInfoBarDelegate::CreateErrorDelegate( | |
330 TranslateErrors::NETWORK, tab, | |
331 request.source_lang, request.target_lang)); | |
332 } else { | |
333 // Translate the page. | |
334 DoTranslatePage(tab, translate_script_, | |
335 request.source_lang, request.target_lang); | |
336 } | |
337 } | |
338 pending_requests_.clear(); | |
339 } | 421 } |
340 | 422 |
341 // static | 423 // static |
342 bool TranslateManager::IsShowingTranslateInfobar(TabContents* tab) { | 424 bool TranslateManager::IsShowingTranslateInfobar(TabContents* tab) { |
343 return GetTranslateInfoBarDelegate(tab) != NULL; | 425 return GetTranslateInfoBarDelegate(tab) != NULL; |
344 } | 426 } |
345 | 427 |
346 TranslateManager::TranslateManager() | 428 TranslateManager::TranslateManager() |
347 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), | 429 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), |
348 translate_script_expiration_delay_(kTranslateScriptExpirationDelayMS), | 430 translate_script_expiration_delay_(kTranslateScriptExpirationDelayMS), |
349 translate_script_request_pending_(false) { | 431 translate_script_request_pending_(false), |
| 432 language_list_request_pending_(false) { |
350 notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, | 433 notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, |
351 NotificationService::AllSources()); | 434 NotificationService::AllSources()); |
352 notification_registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED, | 435 notification_registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED, |
353 NotificationService::AllSources()); | 436 NotificationService::AllSources()); |
354 notification_registrar_.Add(this, NotificationType::PAGE_TRANSLATED, | 437 notification_registrar_.Add(this, NotificationType::PAGE_TRANSLATED, |
355 NotificationService::AllSources()); | 438 NotificationService::AllSources()); |
356 } | 439 } |
357 | 440 |
358 void TranslateManager::InitiateTranslation(TabContents* tab, | 441 void TranslateManager::InitiateTranslation(TabContents* tab, |
359 const std::string& page_lang) { | 442 const std::string& page_lang) { |
(...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
612 // black-listed. | 695 // black-listed. |
613 // TODO(jungshik): Once we determine that it's safe to remove English from | 696 // TODO(jungshik): Once we determine that it's safe to remove English from |
614 // the default Accept-Language values for most locales, remove this | 697 // the default Accept-Language values for most locales, remove this |
615 // special-casing. | 698 // special-casing. |
616 if (accept_lang != "en" || is_ui_english) | 699 if (accept_lang != "en" || is_ui_english) |
617 accept_langs_set.insert(accept_lang); | 700 accept_langs_set.insert(accept_lang); |
618 } | 701 } |
619 accept_languages_[prefs] = accept_langs_set; | 702 accept_languages_[prefs] = accept_langs_set; |
620 } | 703 } |
621 | 704 |
| 705 void TranslateManager::FetchLanguageListFromTranslateServer( |
| 706 PrefService* prefs) { |
| 707 if (language_list_request_pending_) |
| 708 return; |
| 709 |
| 710 // We don't want to do this when translate is disabled. |
| 711 DCHECK(prefs != NULL); |
| 712 if (CommandLine::ForCurrentProcess()->HasSwitch( |
| 713 switches::kDisableTranslate) || |
| 714 (prefs != NULL && !prefs->GetBoolean(prefs::kEnableTranslate))) { |
| 715 return; |
| 716 } |
| 717 |
| 718 language_list_request_pending_ = true; |
| 719 URLFetcher* fetcher = URLFetcher::Create(1, GURL(kLanguageListFetchURL), |
| 720 URLFetcher::GET, this); |
| 721 fetcher->set_request_context(Profile::GetDefaultRequestContext()); |
| 722 fetcher->set_max_retries(kMaxRetryLanguageListFetch); |
| 723 fetcher->Start(); |
| 724 } |
| 725 |
622 void TranslateManager::RequestTranslateScript() { | 726 void TranslateManager::RequestTranslateScript() { |
623 if (translate_script_request_pending_) | 727 if (translate_script_request_pending_) |
624 return; | 728 return; |
625 | 729 |
626 translate_script_request_pending_ = true; | 730 translate_script_request_pending_ = true; |
627 URLFetcher* fetcher = URLFetcher::Create(0, GURL(kTranslateScriptURL), | 731 URLFetcher* fetcher = URLFetcher::Create(0, GURL(kTranslateScriptURL), |
628 URLFetcher::GET, this); | 732 URLFetcher::GET, this); |
629 fetcher->set_request_context(Profile::GetDefaultRequestContext()); | 733 fetcher->set_request_context(Profile::GetDefaultRequestContext()); |
630 fetcher->set_extra_request_headers(kTranslateScriptHeader); | 734 fetcher->set_extra_request_headers(kTranslateScriptHeader); |
631 fetcher->Start(); | 735 fetcher->Start(); |
(...skipping 26 matching lines...) Expand all Loading... |
658 TabContentsWrapper* wrapper = | 762 TabContentsWrapper* wrapper = |
659 TabContentsWrapper::GetCurrentWrapperForContents(tab); | 763 TabContentsWrapper::GetCurrentWrapperForContents(tab); |
660 for (size_t i = 0; i < wrapper->infobar_count(); ++i) { | 764 for (size_t i = 0; i < wrapper->infobar_count(); ++i) { |
661 TranslateInfoBarDelegate* delegate = | 765 TranslateInfoBarDelegate* delegate = |
662 wrapper->GetInfoBarDelegateAt(i)->AsTranslateInfoBarDelegate(); | 766 wrapper->GetInfoBarDelegateAt(i)->AsTranslateInfoBarDelegate(); |
663 if (delegate) | 767 if (delegate) |
664 return delegate; | 768 return delegate; |
665 } | 769 } |
666 return NULL; | 770 return NULL; |
667 } | 771 } |
OLD | NEW |