| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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/google_url_tracker.h" | 5 #include "chrome/browser/google_url_tracker.h" |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "app/l10n_util.h" |
| 9 #include "base/compiler_specific.h" | 10 #include "base/compiler_specific.h" |
| 10 #include "base/string_util.h" | 11 #include "base/string_util.h" |
| 11 #include "base/utf_string_conversions.h" | 12 #include "base/utf_string_conversions.h" |
| 12 #include "chrome/browser/browser_process.h" | 13 #include "chrome/browser/browser_process.h" |
| 13 #include "chrome/browser/pref_service.h" | 14 #include "chrome/browser/pref_service.h" |
| 14 #include "chrome/browser/profile.h" | 15 #include "chrome/browser/profile.h" |
| 16 #include "chrome/browser/search_engines/template_url.h" |
| 17 #include "chrome/browser/tab_contents/infobar_delegate.h" |
| 18 #include "chrome/browser/tab_contents/navigation_controller.h" |
| 19 #include "chrome/browser/tab_contents/tab_contents.h" |
| 20 #include "chrome/common/net/url_fetcher_protect.h" |
| 15 #include "chrome/common/notification_service.h" | 21 #include "chrome/common/notification_service.h" |
| 16 #include "chrome/common/pref_names.h" | 22 #include "chrome/common/pref_names.h" |
| 23 #include "grit/generated_resources.h" |
| 17 #include "net/base/load_flags.h" | 24 #include "net/base/load_flags.h" |
| 18 #include "net/url_request/url_request_status.h" | 25 #include "net/url_request/url_request_status.h" |
| 19 | 26 |
| 20 const char GoogleURLTracker::kDefaultGoogleHomepage[] = | 27 const char GoogleURLTracker::kDefaultGoogleHomepage[] = |
| 21 "http://www.google.com/"; | 28 "http://www.google.com/"; |
| 29 const char GoogleURLTracker::kSearchDomainCheckURL[] = |
| 30 "https://www.google.com/searchdomaincheck?format=domain&type=chrome"; |
| 31 |
| 32 namespace { |
| 33 |
| 34 class GoogleURLTrackerInfoBarDelegate : public ConfirmInfoBarDelegate { |
| 35 public: |
| 36 GoogleURLTrackerInfoBarDelegate(TabContents* tab_contents, |
| 37 GoogleURLTracker* google_url_tracker, |
| 38 const GURL& new_google_url) |
| 39 : ConfirmInfoBarDelegate(tab_contents), |
| 40 google_url_tracker_(google_url_tracker), |
| 41 new_google_url_(new_google_url) {} |
| 42 |
| 43 // ConfirmInfoBarDelegate |
| 44 virtual string16 GetMessageText() const { |
| 45 // TODO(ukai): change new_google_url to google_base_domain? |
| 46 return l10n_util::GetStringFUTF16(IDS_GOOGLE_URL_TRACKER_INFOBAR_MESSAGE, |
| 47 UTF8ToUTF16(new_google_url_.spec())); |
| 48 } |
| 49 |
| 50 virtual int GetButtons() const { |
| 51 return BUTTON_OK | BUTTON_CANCEL; |
| 52 } |
| 53 |
| 54 virtual string16 GetButtonLabel(InfoBarButton button) const { |
| 55 return l10n_util::GetStringUTF16((button == BUTTON_OK) ? |
| 56 IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL : |
| 57 IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL); |
| 58 } |
| 59 |
| 60 virtual bool Accept() { |
| 61 google_url_tracker_->AcceptGoogleURL(new_google_url_); |
| 62 google_url_tracker_->RedoSearch(); |
| 63 return true; |
| 64 } |
| 65 |
| 66 virtual void InfoBarClosed() { |
| 67 google_url_tracker_->InfoBarClosed(); |
| 68 delete this; |
| 69 } |
| 70 |
| 71 private: |
| 72 virtual ~GoogleURLTrackerInfoBarDelegate() {} |
| 73 |
| 74 GoogleURLTracker* google_url_tracker_; |
| 75 const GURL new_google_url_; |
| 76 |
| 77 DISALLOW_COPY_AND_ASSIGN(GoogleURLTrackerInfoBarDelegate); |
| 78 }; |
| 79 |
| 80 } // anonymous namespace |
| 81 |
| 82 InfoBarDelegate* GoogleURLTracker::InfoBarDelegateFactory::CreateInfoBar( |
| 83 TabContents* tab_contents, |
| 84 GoogleURLTracker* google_url_tracker, |
| 85 const GURL& new_google_url) { |
| 86 InfoBarDelegate* infobar = |
| 87 new GoogleURLTrackerInfoBarDelegate(tab_contents, |
| 88 google_url_tracker, |
| 89 new_google_url); |
| 90 tab_contents->AddInfoBar(infobar); |
| 91 return infobar; |
| 92 } |
| 22 | 93 |
| 23 GoogleURLTracker::GoogleURLTracker() | 94 GoogleURLTracker::GoogleURLTracker() |
| 24 : google_url_(g_browser_process->local_state()->GetString( | 95 : google_url_(g_browser_process->local_state()->GetString( |
| 25 prefs::kLastKnownGoogleURL)), | 96 prefs::kLastKnownGoogleURL)), |
| 26 ALLOW_THIS_IN_INITIALIZER_LIST(fetcher_factory_(this)), | 97 ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)), |
| 98 fetcher_id_(0), |
| 27 in_startup_sleep_(true), | 99 in_startup_sleep_(true), |
| 28 already_fetched_(false), | 100 already_fetched_(false), |
| 29 need_to_fetch_(false), | 101 need_to_fetch_(false), |
| 30 request_context_available_(!!Profile::GetDefaultRequestContext()) { | 102 request_context_available_(!!Profile::GetDefaultRequestContext()), |
| 103 need_to_prompt_(false), |
| 104 controller_(NULL), |
| 105 infobar_factory_(new InfoBarDelegateFactory), |
| 106 infobar_(NULL) { |
| 31 registrar_.Add(this, NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE, | 107 registrar_.Add(this, NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE, |
| 32 NotificationService::AllSources()); | 108 NotificationService::AllSources()); |
| 33 | 109 |
| 110 net::NetworkChangeNotifier::AddObserver(this); |
| 111 |
| 112 // Configure to max_retries at most kMaxRetries times for 5xx errors. |
| 113 URLFetcherProtectEntry* protect = |
| 114 URLFetcherProtectManager::GetInstance()->Register( |
| 115 GURL(kSearchDomainCheckURL).host()); |
| 116 static const int kMaxRetries = 5; |
| 117 protect->SetMaxRetries(kMaxRetries); |
| 118 |
| 34 // Because this function can be called during startup, when kicking off a URL | 119 // Because this function can be called during startup, when kicking off a URL |
| 35 // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully | 120 // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully |
| 36 // long enough to be after startup, but still get results back quickly. | 121 // long enough to be after startup, but still get results back quickly. |
| 37 // Ideally, instead of this timer, we'd do something like "check if the | 122 // Ideally, instead of this timer, we'd do something like "check if the |
| 38 // browser is starting up, and if so, come back later", but there is currently | 123 // browser is starting up, and if so, come back later", but there is currently |
| 39 // no function to do this. | 124 // no function to do this. |
| 40 static const int kStartFetchDelayMS = 5000; | 125 static const int kStartFetchDelayMS = 5000; |
| 41 MessageLoop::current()->PostDelayedTask(FROM_HERE, | 126 MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| 42 fetcher_factory_.NewRunnableMethod(&GoogleURLTracker::FinishSleep), | 127 runnable_method_factory_.NewRunnableMethod( |
| 128 &GoogleURLTracker::FinishSleep), |
| 43 kStartFetchDelayMS); | 129 kStartFetchDelayMS); |
| 44 } | 130 } |
| 45 | 131 |
| 46 GoogleURLTracker::~GoogleURLTracker() { | 132 GoogleURLTracker::~GoogleURLTracker() { |
| 133 runnable_method_factory_.RevokeAll(); |
| 134 net::NetworkChangeNotifier::RemoveObserver(this); |
| 47 } | 135 } |
| 48 | 136 |
| 49 // static | 137 // static |
| 50 GURL GoogleURLTracker::GoogleURL() { | 138 GURL GoogleURLTracker::GoogleURL() { |
| 51 const GoogleURLTracker* const tracker = | 139 const GoogleURLTracker* const tracker = |
| 52 g_browser_process->google_url_tracker(); | 140 g_browser_process->google_url_tracker(); |
| 53 return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage); | 141 return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage); |
| 54 } | 142 } |
| 55 | 143 |
| 56 // static | 144 // static |
| 57 void GoogleURLTracker::RequestServerCheck() { | 145 void GoogleURLTracker::RequestServerCheck() { |
| 58 GoogleURLTracker* const tracker = g_browser_process->google_url_tracker(); | 146 GoogleURLTracker* const tracker = g_browser_process->google_url_tracker(); |
| 59 if (tracker) | 147 if (tracker) |
| 60 tracker->SetNeedToFetch(); | 148 tracker->SetNeedToFetch(); |
| 61 } | 149 } |
| 62 | 150 |
| 63 // static | 151 // static |
| 64 void GoogleURLTracker::RegisterPrefs(PrefService* prefs) { | 152 void GoogleURLTracker::RegisterPrefs(PrefService* prefs) { |
| 65 prefs->RegisterStringPref(prefs::kLastKnownGoogleURL, | 153 prefs->RegisterStringPref(prefs::kLastKnownGoogleURL, |
| 66 kDefaultGoogleHomepage); | 154 kDefaultGoogleHomepage); |
| 155 prefs->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string()); |
| 67 } | 156 } |
| 68 | 157 |
| 69 // static | 158 // static |
| 70 bool GoogleURLTracker::CheckAndConvertToGoogleBaseURL(const GURL& url, | 159 void GoogleURLTracker::GoogleURLSearchCommitted() { |
| 71 GURL* base_url) { | 160 GoogleURLTracker* tracker = g_browser_process->google_url_tracker(); |
| 72 // Only allow updates if the new URL appears to be on google.xx, google.co.xx, | 161 if (tracker) |
| 73 // or google.com.xx. Cases other than this are either malicious, or doorway | 162 tracker->SearchCommitted(); |
| 74 // pages for hotel WiFi connections and the like. | |
| 75 // NOTE: Obviously the above is not as secure as whitelisting all known Google | |
| 76 // frontpage domains, but for now we're trying to prevent login pages etc. | |
| 77 // from ruining the user experience, rather than preventing hijacking. | |
| 78 std::vector<std::string> host_components; | |
| 79 SplitStringDontTrim(url.host(), '.', &host_components); | |
| 80 if (host_components.size() < 2) | |
| 81 return false; | |
| 82 size_t google_component = host_components.size() - 2; | |
| 83 const std::string& component = host_components[google_component]; | |
| 84 if (component != "google") { | |
| 85 if ((host_components.size() < 3) || | |
| 86 ((component != "co") && (component != "com"))) | |
| 87 return false; | |
| 88 google_component = host_components.size() - 3; | |
| 89 if (host_components[google_component] != "google") | |
| 90 return false; | |
| 91 } | |
| 92 // For Google employees only: If the URL appears to be on | |
| 93 // [*.]corp.google.com, it's likely a doorway (e.g. | |
| 94 // wifi.corp.google.com), so ignore it. | |
| 95 if ((google_component > 0) && | |
| 96 (host_components[google_component - 1] == "corp")) | |
| 97 return false; | |
| 98 | |
| 99 // If the url's path does not begin "/intl/", reset it to "/". Other paths | |
| 100 // represent services such as iGoogle that are irrelevant to the baseURL. | |
| 101 *base_url = url.path().compare(0, 6, "/intl/") ? url.GetWithEmptyPath() : url; | |
| 102 return true; | |
| 103 } | 163 } |
| 104 | 164 |
| 105 void GoogleURLTracker::SetNeedToFetch() { | 165 void GoogleURLTracker::SetNeedToFetch() { |
| 106 need_to_fetch_ = true; | 166 need_to_fetch_ = true; |
| 107 StartFetchIfDesirable(); | 167 StartFetchIfDesirable(); |
| 108 } | 168 } |
| 109 | 169 |
| 110 void GoogleURLTracker::FinishSleep() { | 170 void GoogleURLTracker::FinishSleep() { |
| 111 in_startup_sleep_ = false; | 171 in_startup_sleep_ = false; |
| 112 StartFetchIfDesirable(); | 172 StartFetchIfDesirable(); |
| 113 } | 173 } |
| 114 | 174 |
| 115 void GoogleURLTracker::StartFetchIfDesirable() { | 175 void GoogleURLTracker::StartFetchIfDesirable() { |
| 116 // Bail if a fetch isn't appropriate right now. This function will be called | 176 // Bail if a fetch isn't appropriate right now. This function will be called |
| 117 // again each time one of the preconditions changes, so we'll fetch | 177 // again each time one of the preconditions changes, so we'll fetch |
| 118 // immediately once all of them are met. | 178 // immediately once all of them are met. |
| 119 // | 179 // |
| 120 // See comments in header on the class, on RequestServerCheck(), and on the | 180 // See comments in header on the class, on RequestServerCheck(), and on the |
| 121 // various members here for more detail on exactly what the conditions are. | 181 // various members here for more detail on exactly what the conditions are. |
| 122 if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_ || | 182 if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_ || |
| 123 !request_context_available_) | 183 !request_context_available_) |
| 124 return; | 184 return; |
| 125 | 185 |
| 126 need_to_fetch_ = false; | 186 already_fetched_ = true; |
| 127 already_fetched_ = true; // If fetching fails, we don't bother to reset this | 187 fetcher_.reset(URLFetcher::Create(fetcher_id_, GURL(kSearchDomainCheckURL), |
| 128 // flag; we just live with an outdated URL for this | 188 URLFetcher::GET, this)); |
| 129 // run of the browser. | 189 ++fetcher_id_; |
| 130 fetcher_.reset(new URLFetcher(GURL(kDefaultGoogleHomepage), URLFetcher::HEAD, | |
| 131 this)); | |
| 132 // We don't want this fetch to affect existing state in the profile. For | 190 // We don't want this fetch to affect existing state in the profile. For |
| 133 // example, if a user has no Google cookies, this automatic check should not | 191 // example, if a user has no Google cookies, this automatic check should not |
| 134 // cause one to be set, lest we alarm the user. | 192 // cause one to be set, lest we alarm the user. |
| 135 fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE | | 193 fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE | |
| 136 net::LOAD_DO_NOT_SAVE_COOKIES); | 194 net::LOAD_DO_NOT_SAVE_COOKIES); |
| 137 fetcher_->set_request_context(Profile::GetDefaultRequestContext()); | 195 fetcher_->set_request_context(Profile::GetDefaultRequestContext()); |
| 138 fetcher_->Start(); | 196 fetcher_->Start(); |
| 139 } | 197 } |
| 140 | 198 |
| 141 void GoogleURLTracker::OnURLFetchComplete(const URLFetcher* source, | 199 void GoogleURLTracker::OnURLFetchComplete(const URLFetcher* source, |
| 142 const GURL& url, | 200 const GURL& url, |
| 143 const URLRequestStatus& status, | 201 const URLRequestStatus& status, |
| 144 int response_code, | 202 int response_code, |
| 145 const ResponseCookies& cookies, | 203 const ResponseCookies& cookies, |
| 146 const std::string& data) { | 204 const std::string& data) { |
| 147 // Delete the fetcher on this function's exit. | 205 // Delete the fetcher on this function's exit. |
| 148 scoped_ptr<URLFetcher> clean_up_fetcher(fetcher_.release()); | 206 scoped_ptr<URLFetcher> clean_up_fetcher(fetcher_.release()); |
| 149 | 207 |
| 150 // Don't update the URL if the request didn't succeed. | 208 // Don't update the URL if the request didn't succeed. |
| 151 if (!status.is_success() || (response_code != 200)) | 209 if (!status.is_success() || (response_code != 200)) { |
| 210 already_fetched_ = false; |
| 211 return; |
| 212 } |
| 213 |
| 214 // See if the response data was one we want to use, and if so, convert to the |
| 215 // appropriate Google base URL. |
| 216 std::string url_str; |
| 217 TrimWhitespace(data, TRIM_ALL, &url_str); |
| 218 |
| 219 if (!StartsWithASCII(url_str, ".google.", false)) |
| 152 return; | 220 return; |
| 153 | 221 |
| 154 // See if the response URL was one we want to use, and if so, convert to the | 222 fetched_google_url_ = GURL("http://www" + url_str); |
| 155 // appropriate Google base URL. | 223 GURL last_prompted_url( |
| 156 GURL base_url; | 224 g_browser_process->local_state()->GetString( |
| 157 if (!CheckAndConvertToGoogleBaseURL(url, &base_url)) | 225 prefs::kLastPromptedGoogleURL)); |
| 226 need_to_prompt_ = false; |
| 227 // On the very first run of Chrome, when we've never looked up the URL at all, |
| 228 // we should just silently switch over to whatever we get immediately. |
| 229 if (last_prompted_url.is_empty()) { |
| 230 AcceptGoogleURL(fetched_google_url_); |
| 231 // Set fetched_google_url_ as an initial value of last prompted URL. |
| 232 g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL, |
| 233 fetched_google_url_.spec()); |
| 158 return; | 234 return; |
| 235 } |
| 159 | 236 |
| 160 // Update the saved base URL if it has changed. | 237 if (fetched_google_url_ == last_prompted_url) |
| 161 const std::string base_url_str(base_url.spec()); | 238 return; |
| 162 if (g_browser_process->local_state()->GetString(prefs::kLastKnownGoogleURL) != | 239 if (fetched_google_url_ == google_url_) { |
| 163 base_url_str) { | 240 // The user came back to their original location after having temporarily |
| 164 g_browser_process->local_state()->SetString(prefs::kLastKnownGoogleURL, | 241 // moved. Reset the prompted URL so we'll prompt again if they move again. |
| 165 base_url_str); | 242 g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL, |
| 166 google_url_ = base_url; | 243 fetched_google_url_.spec()); |
| 167 NotificationService::current()->Notify(NotificationType::GOOGLE_URL_UPDATED, | 244 return; |
| 168 NotificationService::AllSources(), | |
| 169 NotificationService::NoDetails()); | |
| 170 } | 245 } |
| 246 |
| 247 need_to_prompt_ = true; |
| 248 } |
| 249 |
| 250 void GoogleURLTracker::AcceptGoogleURL(const GURL& new_google_url) { |
| 251 google_url_ = new_google_url; |
| 252 g_browser_process->local_state()->SetString(prefs::kLastKnownGoogleURL, |
| 253 google_url_.spec()); |
| 254 NotificationService::current()->Notify(NotificationType::GOOGLE_URL_UPDATED, |
| 255 NotificationService::AllSources(), |
| 256 NotificationService::NoDetails()); |
| 257 need_to_prompt_ = false; |
| 258 } |
| 259 |
| 260 void GoogleURLTracker::InfoBarClosed() { |
| 261 registrar_.RemoveAll(); |
| 262 controller_ = NULL; |
| 263 infobar_ = NULL; |
| 264 search_url_ = GURL(); |
| 265 } |
| 266 |
| 267 void GoogleURLTracker::RedoSearch() { |
| 268 // re-do the user's search on the new domain. |
| 269 DCHECK(controller_); |
| 270 url_canon::Replacements<char> replacements; |
| 271 replacements.SetHost(google_url_.host().data(), |
| 272 url_parse::Component(0, google_url_.host().length())); |
| 273 search_url_ = search_url_.ReplaceComponents(replacements); |
| 274 if (search_url_.is_valid()) |
| 275 controller_->tab_contents()->OpenURL(search_url_, GURL(), CURRENT_TAB, |
| 276 PageTransition::GENERATED); |
| 171 } | 277 } |
| 172 | 278 |
| 173 void GoogleURLTracker::Observe(NotificationType type, | 279 void GoogleURLTracker::Observe(NotificationType type, |
| 174 const NotificationSource& source, | 280 const NotificationSource& source, |
| 175 const NotificationDetails& details) { | 281 const NotificationDetails& details) { |
| 176 DCHECK_EQ(NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE, type.value); | 282 switch (type.value) { |
| 177 request_context_available_ = true; | 283 case NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE: |
| 284 request_context_available_ = true; |
| 285 StartFetchIfDesirable(); |
| 286 break; |
| 287 |
| 288 case NotificationType::NAV_ENTRY_PENDING: |
| 289 // If we've already received a notification for the same controller, we |
| 290 // should reset infobar as that indicates that the page is being |
| 291 // re-loaded |
| 292 if (!infobar_ && |
| 293 controller_ == Source<NavigationController>(source).ptr()) { |
| 294 infobar_ = NULL; |
| 295 } else if (!controller_) { |
| 296 controller_ = Source<NavigationController>(source).ptr(); |
| 297 NavigationEntry* entry = controller_->pending_entry(); |
| 298 DCHECK(entry); |
| 299 search_url_ = entry->url(); |
| 300 |
| 301 // Start listening for the commit notification. We also need to listen |
| 302 // for the tab close command since that means the load will never |
| 303 // commit! |
| 304 registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, |
| 305 Source<NavigationController>(controller_)); |
| 306 registrar_.Add(this, NotificationType::TAB_CLOSED, |
| 307 Source<NavigationController>(controller_)); |
| 308 } |
| 309 break; |
| 310 |
| 311 case NotificationType::NAV_ENTRY_COMMITTED: |
| 312 DCHECK(controller_); |
| 313 registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED, |
| 314 Source<NavigationController>(controller_)); |
| 315 ShowGoogleURLInfoBarIfNecessary(controller_->tab_contents()); |
| 316 break; |
| 317 |
| 318 case NotificationType::TAB_CLOSED: |
| 319 registrar_.RemoveAll(); |
| 320 controller_ = NULL; |
| 321 infobar_ = NULL; |
| 322 break; |
| 323 |
| 324 default: |
| 325 NOTREACHED() << "Unknown notification received:" << type.value; |
| 326 } |
| 327 } |
| 328 |
| 329 void GoogleURLTracker::OnIPAddressChanged() { |
| 330 already_fetched_ = false; |
| 178 StartFetchIfDesirable(); | 331 StartFetchIfDesirable(); |
| 179 } | 332 } |
| 333 |
| 334 void GoogleURLTracker::SearchCommitted() { |
| 335 registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING, |
| 336 NotificationService::AllSources()); |
| 337 } |
| 338 |
| 339 void GoogleURLTracker::ShowGoogleURLInfoBarIfNecessary( |
| 340 TabContents* tab_contents) { |
| 341 if (!need_to_prompt_) |
| 342 return; |
| 343 if (infobar_) |
| 344 return; |
| 345 DCHECK(!fetched_google_url_.is_empty()); |
| 346 DCHECK(infobar_factory_.get()); |
| 347 |
| 348 infobar_ = infobar_factory_->CreateInfoBar(tab_contents, |
| 349 this, |
| 350 fetched_google_url_); |
| 351 g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL, |
| 352 fetched_google_url_.spec()); |
| 353 } |
| OLD | NEW |