| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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/chrome_to_mobile_service.h" | 5 #include "chrome/browser/chrome_to_mobile_service.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/command_line.h" | 8 #include "base/command_line.h" |
| 9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
| 10 #include "base/guid.h" | 10 #include "base/guid.h" |
| (...skipping 25 matching lines...) Expand all Loading... |
| 36 #include "content/public/browser/notification_details.h" | 36 #include "content/public/browser/notification_details.h" |
| 37 #include "content/public/browser/notification_source.h" | 37 #include "content/public/browser/notification_source.h" |
| 38 #include "content/public/browser/web_contents.h" | 38 #include "content/public/browser/web_contents.h" |
| 39 #include "google/cacheinvalidation/include/types.h" | 39 #include "google/cacheinvalidation/include/types.h" |
| 40 #include "google/cacheinvalidation/types.pb.h" | 40 #include "google/cacheinvalidation/types.pb.h" |
| 41 #include "google_apis/gaia/gaia_constants.h" | 41 #include "google_apis/gaia/gaia_constants.h" |
| 42 #include "google_apis/gaia/gaia_urls.h" | 42 #include "google_apis/gaia/gaia_urls.h" |
| 43 #include "google_apis/gaia/oauth2_access_token_fetcher.h" | 43 #include "google_apis/gaia/oauth2_access_token_fetcher.h" |
| 44 #include "net/base/escape.h" | 44 #include "net/base/escape.h" |
| 45 #include "net/base/load_flags.h" | 45 #include "net/base/load_flags.h" |
| 46 #include "net/http/http_status_code.h" |
| 46 #include "net/url_request/url_fetcher.h" | 47 #include "net/url_request/url_fetcher.h" |
| 47 #include "net/url_request/url_request_context_getter.h" | 48 #include "net/url_request/url_request_context_getter.h" |
| 48 #include "sync/notifier/invalidation_util.h" | 49 #include "sync/notifier/invalidation_util.h" |
| 49 | 50 |
| 50 namespace { | 51 namespace { |
| 51 | 52 |
| 52 // The maximum number of retries for the URLFetcher requests. | 53 // The maximum number of retries for the URLFetcher requests. |
| 53 const size_t kMaxRetries = 1; | 54 const size_t kMaxRetries = 5; |
| 54 | 55 |
| 55 // The number of hours to delay before retrying authentication on failure. | 56 // The number of hours to delay before retrying certain failed operations. |
| 56 const size_t kAuthRetryDelayHours = 6; | 57 const size_t kDelayHours = 1; |
| 57 | |
| 58 // The number of hours before subsequent search requests are allowed. | |
| 59 // This value is used to throttle expensive cloud print search requests. | |
| 60 // Note that this limitation does not hold across application restarts. | |
| 61 const int kSearchRequestDelayHours = 24; | |
| 62 | 58 |
| 63 // The sync invalidation object ID for Chrome to Mobile's mobile device list. | 59 // The sync invalidation object ID for Chrome to Mobile's mobile device list. |
| 64 // This corresponds with cloud print's server-side invalidation object ID. | 60 // This corresponds with cloud print's server-side invalidation object ID. |
| 65 // Meaning: "U" == "User", "CM" == "Chrome to Mobile", "MLST" == "Mobile LiST". | 61 // Meaning: "U" == "User", "CM" == "Chrome to Mobile", "MLST" == "Mobile LiST". |
| 66 const char kSyncInvalidationObjectIdChromeToMobileDeviceList[] = "UCMMLST"; | 62 const char kSyncInvalidationObjectIdChromeToMobileDeviceList[] = "UCMMLST"; |
| 67 | 63 |
| 68 // The cloud print OAuth2 scope and 'printer' type of compatible mobile devices. | 64 // The cloud print OAuth2 scope and 'printer' type of compatible mobile devices. |
| 69 const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint"; | 65 const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint"; |
| 70 const char kTypeAndroid[] = "ANDROID_CHROME_SNAPSHOT"; | 66 const char kTypeAndroid[] = "ANDROID_CHROME_SNAPSHOT"; |
| 71 const char kTypeIOS[] = "IOS_CHROME_SNAPSHOT"; | 67 const char kTypeIOS[] = "IOS_CHROME_SNAPSHOT"; |
| (...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 305 profile_ ? ProfileSyncServiceFactory::GetForProfile(profile_) : NULL; | 301 profile_ ? ProfileSyncServiceFactory::GetForProfile(profile_) : NULL; |
| 306 if (profile_sync_service) | 302 if (profile_sync_service) |
| 307 profile_sync_service->UnregisterInvalidationHandler(this); | 303 profile_sync_service->UnregisterInvalidationHandler(this); |
| 308 } | 304 } |
| 309 | 305 |
| 310 void ChromeToMobileService::OnURLFetchComplete(const net::URLFetcher* source) { | 306 void ChromeToMobileService::OnURLFetchComplete(const net::URLFetcher* source) { |
| 311 if (source->GetURL() == GetSearchURL(cloud_print_url_)) | 307 if (source->GetURL() == GetSearchURL(cloud_print_url_)) |
| 312 HandleSearchResponse(source); | 308 HandleSearchResponse(source); |
| 313 else | 309 else |
| 314 HandleSubmitResponse(source); | 310 HandleSubmitResponse(source); |
| 311 |
| 312 // Remove the URLFetcher from the ScopedVector; this deletes the URLFetcher. |
| 313 for (ScopedVector<net::URLFetcher>::iterator it = url_fetchers_.begin(); |
| 314 it != url_fetchers_.end(); ++it) { |
| 315 if (*it == source) { |
| 316 url_fetchers_.erase(it); |
| 317 break; |
| 318 } |
| 319 } |
| 315 } | 320 } |
| 316 | 321 |
| 317 void ChromeToMobileService::Observe( | 322 void ChromeToMobileService::Observe( |
| 318 int type, | 323 int type, |
| 319 const content::NotificationSource& source, | 324 const content::NotificationSource& source, |
| 320 const content::NotificationDetails& details) { | 325 const content::NotificationDetails& details) { |
| 321 DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_AVAILABLE); | 326 DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_AVAILABLE); |
| 322 TokenService::TokenAvailableDetails* token_details = | 327 TokenService::TokenAvailableDetails* token_details = |
| 323 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); | 328 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); |
| 324 // Invalidate the cloud print access token on Gaia login token updates. | 329 // Invalidate the cloud print access token on Gaia login token updates. |
| 325 if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken) | 330 if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken || |
| 331 token_details->service() == GaiaConstants::kGaiaOAuth2LoginAccessToken) |
| 326 access_token_.clear(); | 332 access_token_.clear(); |
| 327 } | 333 } |
| 328 | 334 |
| 329 void ChromeToMobileService::OnGetTokenSuccess( | 335 void ChromeToMobileService::OnGetTokenSuccess( |
| 330 const std::string& access_token, | 336 const std::string& access_token, |
| 331 const base::Time& expiration_time) { | 337 const base::Time& expiration_time) { |
| 332 DCHECK(!access_token.empty()); | 338 DCHECK(!access_token.empty()); |
| 333 access_token_fetcher_.reset(); | 339 access_token_fetcher_.reset(); |
| 334 auth_retry_timer_.Stop(); | 340 auth_retry_timer_.Stop(); |
| 335 access_token_ = access_token; | 341 access_token_ = access_token; |
| 336 | 342 |
| 337 while (!task_queue_.empty()) { | 343 while (!task_queue_.empty()) { |
| 338 // Post all tasks that were queued and waiting on a valid access token. | 344 // Post all tasks that were queued and waiting on a valid access token. |
| 339 if (!content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, | 345 if (!content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| 340 task_queue_.front())) { | 346 task_queue_.front())) { |
| 341 NOTREACHED(); | 347 NOTREACHED(); |
| 342 } | 348 } |
| 343 task_queue_.pop(); | 349 task_queue_.pop(); |
| 344 } | 350 } |
| 345 } | 351 } |
| 346 | 352 |
| 347 void ChromeToMobileService::OnGetTokenFailure( | 353 void ChromeToMobileService::OnGetTokenFailure( |
| 348 const GoogleServiceAuthError& error) { | 354 const GoogleServiceAuthError& error) { |
| 355 LogMetric(BAD_TOKEN); |
| 356 access_token_.clear(); |
| 349 access_token_fetcher_.reset(); | 357 access_token_fetcher_.reset(); |
| 350 auth_retry_timer_.Stop(); | 358 auth_retry_timer_.Stop(); |
| 351 | 359 |
| 352 auth_retry_timer_.Start( | 360 base::TimeDelta delay = std::max(base::TimeDelta::FromHours(kDelayHours), |
| 353 FROM_HERE, base::TimeDelta::FromHours(kAuthRetryDelayHours), | 361 auth_retry_timer_.GetCurrentDelay() * 2); |
| 354 this, &ChromeToMobileService::RequestAccessToken); | 362 auth_retry_timer_.Start(FROM_HERE, delay, this, |
| 363 &ChromeToMobileService::RequestAccessToken); |
| 364 |
| 365 // Clear the mobile list, which may be (or become) out of date. |
| 366 ListValue empty; |
| 367 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, empty); |
| 368 UpdateCommandState(); |
| 355 } | 369 } |
| 356 | 370 |
| 357 void ChromeToMobileService::OnNotificationsEnabled() { | 371 void ChromeToMobileService::OnNotificationsEnabled() { |
| 358 sync_invalidation_enabled_ = true; | 372 sync_invalidation_enabled_ = true; |
| 359 UpdateCommandState(); | 373 UpdateCommandState(); |
| 360 } | 374 } |
| 361 | 375 |
| 362 void ChromeToMobileService::OnNotificationsDisabled( | 376 void ChromeToMobileService::OnNotificationsDisabled( |
| 363 syncer::NotificationsDisabledReason reason) { | 377 syncer::NotificationsDisabledReason reason) { |
| 364 sync_invalidation_enabled_ = false; | 378 sync_invalidation_enabled_ = false; |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 415 } else if (observer.get()) { | 429 } else if (observer.get()) { |
| 416 // Signal snapshot generation failure. | 430 // Signal snapshot generation failure. |
| 417 observer->SnapshotGenerated(FilePath(), 0); | 431 observer->SnapshotGenerated(FilePath(), 0); |
| 418 } | 432 } |
| 419 } | 433 } |
| 420 | 434 |
| 421 net::URLFetcher* ChromeToMobileService::CreateRequest() { | 435 net::URLFetcher* ChromeToMobileService::CreateRequest() { |
| 422 net::URLFetcher* request = net::URLFetcher::Create( | 436 net::URLFetcher* request = net::URLFetcher::Create( |
| 423 cloud_print::GetUrlForSubmit(cloud_print_url_), | 437 cloud_print::GetUrlForSubmit(cloud_print_url_), |
| 424 net::URLFetcher::POST, this); | 438 net::URLFetcher::POST, this); |
| 439 url_fetchers_.push_back(request); |
| 425 InitRequest(request); | 440 InitRequest(request); |
| 426 return request; | 441 return request; |
| 427 } | 442 } |
| 428 | 443 |
| 429 void ChromeToMobileService::InitRequest(net::URLFetcher* request) { | 444 void ChromeToMobileService::InitRequest(net::URLFetcher* request) { |
| 430 request->SetRequestContext(profile_->GetRequestContext()); | 445 request->SetRequestContext(profile_->GetRequestContext()); |
| 431 request->SetMaxRetries(kMaxRetries); | 446 request->SetMaxRetries(kMaxRetries); |
| 432 DCHECK(!access_token_.empty()); | 447 DCHECK(!access_token_.empty()); |
| 433 request->SetExtraRequestHeaders("Authorization: OAuth " + | 448 request->SetExtraRequestHeaders("Authorization: OAuth " + |
| 434 access_token_ + "\r\n" + cloud_print::kChromeCloudPrintProxyHeader); | 449 access_token_ + "\r\n" + cloud_print::kChromeCloudPrintProxyHeader); |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 479 request->Start(); | 494 request->Start(); |
| 480 } | 495 } |
| 481 | 496 |
| 482 void ChromeToMobileService::RequestAccessToken() { | 497 void ChromeToMobileService::RequestAccessToken() { |
| 483 // Register to observe Gaia login refresh token updates. | 498 // Register to observe Gaia login refresh token updates. |
| 484 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); | 499 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); |
| 485 if (registrar_.IsEmpty()) | 500 if (registrar_.IsEmpty()) |
| 486 registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, | 501 registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, |
| 487 content::Source<TokenService>(token_service)); | 502 content::Source<TokenService>(token_service)); |
| 488 | 503 |
| 489 // Deny concurrent requests and bail without a valid Gaia login refresh token. | 504 // Deny concurrent requests. |
| 490 if (access_token_fetcher_.get() || !token_service->HasOAuthLoginToken()) | 505 if (access_token_fetcher_.get()) |
| 491 return; | 506 return; |
| 492 | 507 |
| 508 // Handle invalid login refresh tokens as a failure. |
| 509 if (!token_service->HasOAuthLoginToken()) { |
| 510 OnGetTokenFailure(GoogleServiceAuthError(GoogleServiceAuthError::NONE)); |
| 511 return; |
| 512 } |
| 513 |
| 493 auth_retry_timer_.Stop(); | 514 auth_retry_timer_.Stop(); |
| 494 access_token_fetcher_.reset( | 515 access_token_fetcher_.reset( |
| 495 new OAuth2AccessTokenFetcher(this, profile_->GetRequestContext())); | 516 new OAuth2AccessTokenFetcher(this, profile_->GetRequestContext())); |
| 496 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); | 517 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); |
| 497 access_token_fetcher_->Start(gaia_urls->oauth2_chrome_client_id(), | 518 access_token_fetcher_->Start(gaia_urls->oauth2_chrome_client_id(), |
| 498 gaia_urls->oauth2_chrome_client_secret(), | 519 gaia_urls->oauth2_chrome_client_secret(), |
| 499 token_service->GetOAuth2LoginRefreshToken(), | 520 token_service->GetOAuth2LoginRefreshToken(), |
| 500 std::vector<std::string>(1, kCloudPrintAuth)); | 521 std::vector<std::string>(1, kCloudPrintAuth)); |
| 501 } | 522 } |
| 502 | 523 |
| 503 void ChromeToMobileService::RequestDeviceSearch() { | 524 void ChromeToMobileService::RequestDeviceSearch() { |
| 525 search_retry_timer_.Stop(); |
| 504 if (access_token_.empty()) { | 526 if (access_token_.empty()) { |
| 505 // Enqueue this task to perform after obtaining an access token. | 527 // Enqueue this task to perform after obtaining an access token. |
| 506 task_queue_.push(base::Bind(&ChromeToMobileService::RequestDeviceSearch, | 528 task_queue_.push(base::Bind(&ChromeToMobileService::RequestDeviceSearch, |
| 507 weak_ptr_factory_.GetWeakPtr())); | 529 weak_ptr_factory_.GetWeakPtr())); |
| 508 RequestAccessToken(); | 530 RequestAccessToken(); |
| 509 return; | 531 return; |
| 510 } | 532 } |
| 511 | 533 |
| 512 LogMetric(DEVICES_REQUESTED); | 534 LogMetric(DEVICES_REQUESTED); |
| 513 | 535 |
| 514 net::URLFetcher* search_request = net::URLFetcher::Create( | 536 net::URLFetcher* search_request = net::URLFetcher::Create( |
| 515 GetSearchURL(cloud_print_url_), net::URLFetcher::GET, this); | 537 GetSearchURL(cloud_print_url_), net::URLFetcher::GET, this); |
| 538 url_fetchers_.push_back(search_request); |
| 516 InitRequest(search_request); | 539 InitRequest(search_request); |
| 517 search_request->Start(); | 540 search_request->Start(); |
| 518 } | 541 } |
| 519 | 542 |
| 520 void ChromeToMobileService::HandleSearchResponse( | 543 void ChromeToMobileService::HandleSearchResponse( |
| 521 const net::URLFetcher* source) { | 544 const net::URLFetcher* source) { |
| 522 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 545 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 523 DCHECK_EQ(source->GetURL(), GetSearchURL(cloud_print_url_)); | 546 DCHECK_EQ(source->GetURL(), GetSearchURL(cloud_print_url_)); |
| 524 | 547 |
| 548 ListValue mobiles; |
| 525 std::string data; | 549 std::string data; |
| 526 ListValue* list = NULL; | 550 ListValue* list = NULL; |
| 527 DictionaryValue* dictionary = NULL; | 551 DictionaryValue* dictionary = NULL; |
| 528 source->GetResponseAsString(&data); | 552 source->GetResponseAsString(&data); |
| 529 scoped_ptr<Value> json(base::JSONReader::Read(data)); | 553 scoped_ptr<Value> json(base::JSONReader::Read(data)); |
| 530 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && | 554 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && |
| 531 dictionary->GetList(cloud_print::kPrinterListValue, &list)) { | 555 dictionary->GetList(cloud_print::kPrinterListValue, &list)) { |
| 532 ListValue mobiles; | |
| 533 std::string type, name, id; | 556 std::string type, name, id; |
| 534 DictionaryValue* printer = NULL; | 557 DictionaryValue* printer = NULL; |
| 535 DictionaryValue* mobile = NULL; | 558 DictionaryValue* mobile = NULL; |
| 536 for (size_t index = 0; index < list->GetSize(); ++index) { | 559 for (size_t index = 0; index < list->GetSize(); ++index) { |
| 537 if (list->GetDictionary(index, &printer) && | 560 if (list->GetDictionary(index, &printer) && |
| 538 printer->GetString("type", &type) && | 561 printer->GetString("type", &type) && |
| 539 (type.compare(kTypeAndroid) == 0 || type.compare(kTypeIOS) == 0)) { | 562 (type.compare(kTypeAndroid) == 0 || type.compare(kTypeIOS) == 0)) { |
| 540 // Copy just the requisite values from the full |printer| definition. | 563 // Copy just the requisite values from the full |printer| definition. |
| 541 if (printer->GetString("displayName", &name) && | 564 if (printer->GetString("displayName", &name) && |
| 542 printer->GetString("id", &id)) { | 565 printer->GetString("id", &id)) { |
| 543 mobile = new DictionaryValue(); | 566 mobile = new DictionaryValue(); |
| 544 mobile->SetString("type", type); | 567 mobile->SetString("type", type); |
| 545 mobile->SetString("name", name); | 568 mobile->SetString("name", name); |
| 546 mobile->SetString("id", id); | 569 mobile->SetString("id", id); |
| 547 mobiles.Append(mobile); | 570 mobiles.Append(mobile); |
| 548 } else { | 571 } else { |
| 549 NOTREACHED(); | 572 NOTREACHED(); |
| 550 } | 573 } |
| 551 } | 574 } |
| 552 } | 575 } |
| 576 } else if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { |
| 577 LogMetric(BAD_SEARCH_AUTH); |
| 578 // Invalidate the access token and retry a delayed search on access errors. |
| 579 access_token_.clear(); |
| 580 search_retry_timer_.Stop(); |
| 581 base::TimeDelta delay = std::max(base::TimeDelta::FromHours(kDelayHours), |
| 582 search_retry_timer_.GetCurrentDelay() * 2); |
| 583 search_retry_timer_.Start(FROM_HERE, delay, this, |
| 584 &ChromeToMobileService::RequestDeviceSearch); |
| 585 } else { |
| 586 LogMetric(BAD_SEARCH_OTHER); |
| 587 } |
| 553 | 588 |
| 554 // Update the cached mobile device list in profile prefs. | 589 // Update the cached mobile device list in profile prefs. |
| 555 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); | 590 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); |
| 556 | 591 if (HasMobiles()) |
| 557 if (HasMobiles()) | 592 LogMetric(DEVICES_AVAILABLE); |
| 558 LogMetric(DEVICES_AVAILABLE); | 593 UpdateCommandState(); |
| 559 UpdateCommandState(); | |
| 560 } | |
| 561 } | 594 } |
| 562 | 595 |
| 563 void ChromeToMobileService::HandleSubmitResponse( | 596 void ChromeToMobileService::HandleSubmitResponse( |
| 564 const net::URLFetcher* source) { | 597 const net::URLFetcher* source) { |
| 565 // Get the success value from the cloud print server response data. | 598 // Get the success value from the cloud print server response data. |
| 566 std::string data; | 599 std::string data; |
| 600 bool success = false; |
| 567 source->GetResponseAsString(&data); | 601 source->GetResponseAsString(&data); |
| 568 bool success = false; | |
| 569 DictionaryValue* dictionary = NULL; | 602 DictionaryValue* dictionary = NULL; |
| 570 scoped_ptr<Value> json(base::JSONReader::Read(data)); | 603 scoped_ptr<Value> json(base::JSONReader::Read(data)); |
| 571 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary) | 604 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary) { |
| 572 dictionary->GetBoolean("success", &success); | 605 dictionary->GetBoolean("success", &success); |
| 606 int error_code = -1; |
| 607 if (dictionary->GetInteger("errorCode", &error_code)) |
| 608 LogMetric(error_code == 407 ? BAD_SEND_407 : BAD_SEND_ERROR); |
| 609 } else if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { |
| 610 LogMetric(BAD_SEND_AUTH); |
| 611 } else { |
| 612 LogMetric(BAD_SEND_OTHER); |
| 613 } |
| 573 | 614 |
| 574 // Log each URL and [DELAYED_]SNAPSHOT job submission response. | 615 // Log each URL and [DELAYED_]SNAPSHOT job submission response. |
| 575 LogMetric(success ? SEND_SUCCESS : SEND_ERROR); | 616 LogMetric(success ? SEND_SUCCESS : SEND_ERROR); |
| 617 LOG_IF(INFO, !success) << "ChromeToMobile send failed (" << |
| 618 source->GetResponseCode() << "): " << data; |
| 576 | 619 |
| 577 // Get the observer for this job submission response. | 620 // Get the observer for this job submission response. |
| 578 base::WeakPtr<Observer> observer; | 621 base::WeakPtr<Observer> observer; |
| 579 RequestObserverMap::iterator i = request_observer_map_.find(source); | 622 RequestObserverMap::iterator i = request_observer_map_.find(source); |
| 580 if (i != request_observer_map_.end()) { | 623 if (i != request_observer_map_.end()) { |
| 581 observer = i->second; | 624 observer = i->second; |
| 582 request_observer_map_.erase(i); | 625 request_observer_map_.erase(i); |
| 583 } | 626 } |
| 584 | 627 |
| 585 // Check if the observer is waiting on a second response (url or snapshot). | 628 // Check if the observer is waiting on a second response (url or snapshot). |
| 586 for (RequestObserverMap::iterator other = request_observer_map_.begin(); | 629 for (RequestObserverMap::iterator other = request_observer_map_.begin(); |
| 587 observer.get() && (other != request_observer_map_.end()); ++other) { | 630 observer.get() && (other != request_observer_map_.end()); ++other) { |
| 588 if (other->second == observer) { | 631 if (other->second == observer) { |
| 589 // Delay reporting success until the second response is received. | 632 // Delay reporting success until the second response is received. |
| 590 if (success) | 633 if (success) |
| 591 return; | 634 return; |
| 592 | 635 |
| 593 // Report failure below and ignore the second response. | 636 // Report failure below and ignore the second response. |
| 594 request_observer_map_.erase(other); | 637 request_observer_map_.erase(other); |
| 595 break; | 638 break; |
| 596 } | 639 } |
| 597 } | 640 } |
| 598 | 641 |
| 599 if (observer.get()) | 642 if (observer.get()) |
| 600 observer->OnSendComplete(success); | 643 observer->OnSendComplete(success); |
| 601 } | 644 } |
| OLD | NEW |