| 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 |