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 3 // found in the LICENSE file. 4 5 #include "chrome/browser/favicon/favicon_handler.h" 6 7 #include "build/build_config.h" 8 9 #include <algorithm> 10 #include <vector> 11 12 #include "base/bind.h" 13 #include "base/bind_helpers.h" 14 #include "base/memory/ref_counted_memory.h" 15 #include "chrome/browser/bookmarks/bookmark_service.h" 16 #include "chrome/browser/favicon/favicon_service_factory.h" 17 #include "chrome/browser/favicon/favicon_util.h" 18 #include "chrome/browser/history/select_favicon_frames.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "content/public/browser/favicon_status.h" 21 #include "content/public/browser/navigation_entry.h" 22 #include "skia/ext/image_operations.h" 23 #include "ui/gfx/codec/png_codec.h" 24 #include "ui/gfx/image/image.h" 25 #include "ui/gfx/image/image_skia.h" 26 #include "ui/gfx/image/image_util.h" 27 28 using content::FaviconURL; 29 using content::NavigationEntry; 30 31 namespace { 32 33 // Size (along each axis) of a touch icon. This currently corresponds to 34 // the apple touch icon for iPad. 35 const int kTouchIconSize = 144; 36 37 // Returns chrome::IconType the given icon_type corresponds to. 38 chrome::IconType ToHistoryIconType(FaviconURL::IconType icon_type) { 39 switch (icon_type) { 40 case FaviconURL::FAVICON: 41 return chrome::FAVICON; 42 case FaviconURL::TOUCH_ICON: 43 return chrome::TOUCH_ICON; 44 case FaviconURL::TOUCH_PRECOMPOSED_ICON: 45 return chrome::TOUCH_PRECOMPOSED_ICON; 46 case FaviconURL::INVALID_ICON: 47 return chrome::INVALID_ICON; 48 } 49 NOTREACHED(); 50 return chrome::INVALID_ICON; 51 } 52 53 // Get the maximal icon size in pixels for a icon of type |icon_type| for the 54 // current platform. 55 int GetMaximalIconSize(chrome::IconType icon_type) { 56 switch (icon_type) { 57 case chrome::FAVICON: 58 #if defined(OS_ANDROID) 59 return 192; 60 #else 61 return gfx::ImageSkia::GetMaxSupportedScale() * gfx::kFaviconSize; 62 #endif 63 case chrome::TOUCH_ICON: 64 case chrome::TOUCH_PRECOMPOSED_ICON: 65 return kTouchIconSize; 66 case chrome::INVALID_ICON: 67 return 0; 68 } 69 NOTREACHED(); 70 return 0; 71 } 72 73 bool DoUrlAndIconMatch(const FaviconURL& favicon_url, 74 const GURL& url, 75 chrome::IconType icon_type) { 76 return favicon_url.icon_url == url && 77 favicon_url.icon_type == static_cast<FaviconURL::IconType>(icon_type); 78 } 79 80 // Returns true if all of the icon URLs and icon types in |bitmap_results| are 81 // identical and if they match the icon URL and icon type in |favicon_url|. 82 // Returns false if |bitmap_results| is empty. 83 bool DoUrlsAndIconsMatch( 84 const FaviconURL& favicon_url, 85 const std::vector<chrome::FaviconBitmapResult>& bitmap_results) { 86 if (bitmap_results.empty()) 87 return false; 88 89 chrome::IconType icon_type = ToHistoryIconType(favicon_url.icon_type); 90 91 for (size_t i = 0; i < bitmap_results.size(); ++i) { 92 if (favicon_url.icon_url != bitmap_results[i].icon_url || 93 icon_type != bitmap_results[i].icon_type) { 94 return false; 95 } 96 } 97 return true; 98 } 99 100 std::string UrlWithoutFragment(const GURL& gurl) { 101 GURL::Replacements replacements; 102 replacements.ClearRef(); 103 return gurl.ReplaceComponents(replacements).spec(); 104 } 105 106 bool UrlMatches(const GURL& gurl_a, const GURL& gurl_b) { 107 return UrlWithoutFragment(gurl_a) == UrlWithoutFragment(gurl_b); 108 } 109 110 // Return true if |bitmap_result| is expired. 111 bool IsExpired(const chrome::FaviconBitmapResult& bitmap_result) { 112 return bitmap_result.expired; 113 } 114 115 // Return true if |bitmap_result| is valid. 116 bool IsValid(const chrome::FaviconBitmapResult& bitmap_result) { 117 return bitmap_result.is_valid(); 118 } 119 120 // Returns true if at least one of the bitmaps in |bitmap_results| is expired or 121 // if |bitmap_results| is missing favicons for |desired_size_in_dip| and one of 122 // the scale factors in FaviconUtil::GetFaviconScaleFactors(). 123 bool HasExpiredOrIncompleteResult( 124 int desired_size_in_dip, 125 const std::vector<chrome::FaviconBitmapResult>& bitmap_results) { 126 // Check if at least one of the bitmaps is expired. 127 std::vector<chrome::FaviconBitmapResult>::const_iterator it = 128 std::find_if(bitmap_results.begin(), bitmap_results.end(), IsExpired); 129 if (it != bitmap_results.end()) 130 return true; 131 132 // Any favicon size is good if the desired size is 0. 133 if (desired_size_in_dip == 0) 134 return false; 135 136 // Check if the favicon for at least one of the scale factors is missing. 137 // |bitmap_results| should always be complete for data inserted by 138 // FaviconHandler as the FaviconHandler stores favicons resized to all 139 // of FaviconUtil::GetFaviconScaleFactors() into the history backend. 140 // Examples of when |bitmap_results| can be incomplete: 141 // - Favicons inserted into the history backend by sync. 142 // - Favicons for imported bookmarks. 143 std::vector<gfx::Size> favicon_sizes; 144 for (size_t i = 0; i < bitmap_results.size(); ++i) 145 favicon_sizes.push_back(bitmap_results[i].pixel_size); 146 147 std::vector<ui::ScaleFactor> scale_factors = 148 FaviconUtil::GetFaviconScaleFactors(); 149 for (size_t i = 0; i < scale_factors.size(); ++i) { 150 int edge_size_in_pixel = floor( 151 desired_size_in_dip * ui::GetImageScale(scale_factors[i])); 152 std::vector<gfx::Size>::iterator it = std::find(favicon_sizes.begin(), 153 favicon_sizes.end(), gfx::Size(edge_size_in_pixel, edge_size_in_pixel)); 154 if (it == favicon_sizes.end()) 155 return true; 156 } 157 return false; 158 } 159 160 // Returns true if at least one of |bitmap_results| is valid. 161 bool HasValidResult( 162 const std::vector<chrome::FaviconBitmapResult>& bitmap_results) { 163 std::vector<chrome::FaviconBitmapResult>::const_iterator it = 164 std::find_if(bitmap_results.begin(), bitmap_results.end(), IsValid); 165 return it != bitmap_results.end(); 166 } 167 168 } // namespace 169 170 //////////////////////////////////////////////////////////////////////////////// 171 172 FaviconHandler::DownloadRequest::DownloadRequest() 173 : icon_type(chrome::INVALID_ICON) { 174 } 175 176 FaviconHandler::DownloadRequest::~DownloadRequest() { 177 } 178 179 FaviconHandler::DownloadRequest::DownloadRequest( 180 const GURL& url, 181 const GURL& image_url, 182 chrome::IconType icon_type) 183 : url(url), 184 image_url(image_url), 185 icon_type(icon_type) { 186 } 187 188 //////////////////////////////////////////////////////////////////////////////// 189 190 FaviconHandler::FaviconCandidate::FaviconCandidate() 191 : score(0), 192 icon_type(chrome::INVALID_ICON) { 193 } 194 195 FaviconHandler::FaviconCandidate::~FaviconCandidate() { 196 } 197 198 FaviconHandler::FaviconCandidate::FaviconCandidate( 199 const GURL& url, 200 const GURL& image_url, 201 const gfx::Image& image, 202 float score, 203 chrome::IconType icon_type) 204 : url(url), 205 image_url(image_url), 206 image(image), 207 score(score), 208 icon_type(icon_type) { 209 } 210 211 //////////////////////////////////////////////////////////////////////////////// 212 213 FaviconHandler::FaviconHandler(Profile* profile, 214 FaviconHandlerDelegate* delegate, 215 Type icon_type) 216 : got_favicon_from_history_(false), 217 favicon_expired_or_incomplete_(false), 218 icon_types_(icon_type == FAVICON ? chrome::FAVICON : 219 chrome::TOUCH_ICON | chrome::TOUCH_PRECOMPOSED_ICON), 220 profile_(profile), 221 delegate_(delegate) { 222 DCHECK(profile_); 223 DCHECK(delegate_); 224 } 225 226 FaviconHandler::~FaviconHandler() { 227 } 228 229 void FaviconHandler::FetchFavicon(const GURL& url) { 230 cancelable_task_tracker_.TryCancelAll(); 231 232 url_ = url; 233 234 favicon_expired_or_incomplete_ = got_favicon_from_history_ = false; 235 image_urls_.clear(); 236 237 // Request the favicon from the history service. In parallel to this the 238 // renderer is going to notify us (well WebContents) when the favicon url is 239 // available. 240 if (GetFaviconService()) { 241 GetFaviconForURL( 242 url_, 243 icon_types_, 244 base::Bind(&FaviconHandler::OnFaviconDataForInitialURL, 245 base::Unretained(this)), 246 &cancelable_task_tracker_); 247 } 248 } 249 250 FaviconService* FaviconHandler::GetFaviconService() { 251 return FaviconServiceFactory::GetForProfile( 252 profile_, Profile::EXPLICIT_ACCESS); 253 } 254 255 bool FaviconHandler::UpdateFaviconCandidate(const GURL& url, 256 const GURL& image_url, 257 const gfx::Image& image, 258 float score, 259 chrome::IconType icon_type) { 260 bool update_candidate = false; 261 bool exact_match = score == 1; 262 if (preferred_icon_size() == 0) { 263 // No preferred size, use this icon. 264 update_candidate = true; 265 exact_match = true; 266 } else if (favicon_candidate_.icon_type == chrome::INVALID_ICON) { 267 // No current candidate, use this. 268 update_candidate = true; 269 } else { 270 if (exact_match) { 271 // Exact match, use this. 272 update_candidate = true; 273 } else { 274 // Compare against current candidate. 275 if (score > favicon_candidate_.score) 276 update_candidate = true; 277 } 278 } 279 if (update_candidate) { 280 favicon_candidate_ = FaviconCandidate( 281 url, image_url, image, score, icon_type); 282 } 283 return exact_match; 284 } 285 286 void FaviconHandler::SetFavicon( 287 const GURL& url, 288 const GURL& icon_url, 289 const gfx::Image& image, 290 chrome::IconType icon_type) { 291 if (GetFaviconService() && ShouldSaveFavicon(url)) 292 SetHistoryFavicons(url, icon_url, icon_type, image); 293 294 if (UrlMatches(url, url_) && icon_type == chrome::FAVICON) { 295 NavigationEntry* entry = GetEntry(); 296 if (entry) 297 UpdateFavicon(entry, icon_url, image); 298 } 299 } 300 301 void FaviconHandler::UpdateFavicon(NavigationEntry* entry, 302 const std::vector<chrome::FaviconBitmapResult>& favicon_bitmap_results) { 303 gfx::Image resized_image = FaviconUtil::SelectFaviconFramesFromPNGs( 304 favicon_bitmap_results, 305 FaviconUtil::GetFaviconScaleFactors(), 306 preferred_icon_size()); 307 // The history service sends back results for a single icon URL, so it does 308 // not matter which result we get the |icon_url| from. 309 const GURL icon_url = favicon_bitmap_results.empty() ? 310 GURL() : favicon_bitmap_results[0].icon_url; 311 UpdateFavicon(entry, icon_url, resized_image); 312 } 313 314 void FaviconHandler::UpdateFavicon(NavigationEntry* entry, 315 const GURL& icon_url, 316 const gfx::Image& image) { 317 // No matter what happens, we need to mark the favicon as being set. 318 entry->GetFavicon().valid = true; 319 320 bool icon_url_changed = (entry->GetFavicon().url != icon_url); 321 entry->GetFavicon().url = icon_url; 322 323 if (image.IsEmpty()) 324 return; 325 326 gfx::Image image_with_adjusted_colorspace = image; 327 FaviconUtil::SetFaviconColorSpace(&image_with_adjusted_colorspace); 328 329 entry->GetFavicon().image = image_with_adjusted_colorspace; 330 NotifyFaviconUpdated(icon_url_changed); 331 } 332 333 void FaviconHandler::OnUpdateFaviconURL( 334 int32 page_id, 335 const std::vector<FaviconURL>& candidates) { 336 337 image_urls_.clear(); 338 favicon_candidate_ = FaviconCandidate(); 339 for (std::vector<FaviconURL>::const_iterator i = candidates.begin(); 340 i != candidates.end(); ++i) { 341 if (!i->icon_url.is_empty() && (i->icon_type & icon_types_)) 342 image_urls_.push_back(*i); 343 } 344 345 // TODO(davemoore) Should clear on empty url. Currently we ignore it. 346 // This appears to be what FF does as well. 347 if (image_urls_.empty()) 348 return; 349 350 if (!GetFaviconService()) 351 return; 352 353 ProcessCurrentUrl(); 354 } 355 356 void FaviconHandler::ProcessCurrentUrl() { 357 DCHECK(!image_urls_.empty()); 358 359 NavigationEntry* entry = GetEntry(); 360 if (!entry) 361 return; 362 363 // For FAVICON. 364 if (current_candidate()->icon_type == FaviconURL::FAVICON) { 365 if (!favicon_expired_or_incomplete_ && entry->GetFavicon().valid && 366 DoUrlAndIconMatch(*current_candidate(), entry->GetFavicon().url, 367 chrome::FAVICON)) 368 return; 369 } else if (!favicon_expired_or_incomplete_ && got_favicon_from_history_ && 370 HasValidResult(history_results_) && 371 DoUrlsAndIconsMatch(*current_candidate(), history_results_)) { 372 return; 373 } 374 375 if (got_favicon_from_history_) 376 DownloadFaviconOrAskHistory(entry->GetURL(), current_candidate()->icon_url, 377 ToHistoryIconType(current_candidate()->icon_type)); 378 } 379 380 void FaviconHandler::OnDidDownloadFavicon( 381 int id, 382 const GURL& image_url, 383 const std::vector<SkBitmap>& bitmaps, 384 const std::vector<gfx::Size>& original_bitmap_sizes) { 385 DownloadRequests::iterator i = download_requests_.find(id); 386 if (i == download_requests_.end()) { 387 // Currently WebContents notifies us of ANY downloads so that it is 388 // possible to get here. 389 return; 390 } 391 392 if (current_candidate() && 393 DoUrlAndIconMatch(*current_candidate(), image_url, i->second.icon_type)) { 394 float score = 0.0f; 395 std::vector<ui::ScaleFactor> scale_factors = 396 FaviconUtil::GetFaviconScaleFactors(); 397 gfx::Image image(SelectFaviconFrames(bitmaps, 398 original_bitmap_sizes, 399 scale_factors, 400 preferred_icon_size(), 401 &score)); 402 403 // The downloaded icon is still valid when there is no FaviconURL update 404 // during the downloading. 405 bool request_next_icon = true; 406 if (!bitmaps.empty()) { 407 request_next_icon = !UpdateFaviconCandidate( 408 i->second.url, image_url, image, score, i->second.icon_type); 409 } 410 if (request_next_icon && GetEntry() && image_urls_.size() > 1) { 411 // Remove the first member of image_urls_ and process the remaining. 412 image_urls_.pop_front(); 413 ProcessCurrentUrl(); 414 } else if (favicon_candidate_.icon_type != chrome::INVALID_ICON) { 415 // No more icons to request, set the favicon from the candidate. 416 SetFavicon(favicon_candidate_.url, 417 favicon_candidate_.image_url, 418 favicon_candidate_.image, 419 favicon_candidate_.icon_type); 420 // Reset candidate. 421 image_urls_.clear(); 422 favicon_candidate_ = FaviconCandidate(); 423 } 424 } 425 download_requests_.erase(i); 426 } 427 428 NavigationEntry* FaviconHandler::GetEntry() { 429 NavigationEntry* entry = delegate_->GetActiveEntry(); 430 if (entry && UrlMatches(entry->GetURL(), url_)) 431 return entry; 432 433 // If the URL has changed out from under us (as will happen with redirects) 434 // return NULL. 435 return NULL; 436 } 437 438 int FaviconHandler::DownloadFavicon(const GURL& image_url, 439 int max_bitmap_size) { 440 if (!image_url.is_valid()) { 441 NOTREACHED(); 442 return 0; 443 } 444 return delegate_->StartDownload(image_url, max_bitmap_size); 445 } 446 447 void FaviconHandler::UpdateFaviconMappingAndFetch( 448 const GURL& page_url, 449 const GURL& icon_url, 450 chrome::IconType icon_type, 451 const FaviconService::FaviconResultsCallback& callback, 452 CancelableTaskTracker* tracker) { 453 // TODO(pkotwicz): pass in all of |image_urls_| to 454 // UpdateFaviconMappingsAndFetch(). 455 std::vector<GURL> icon_urls; 456 icon_urls.push_back(icon_url); 457 GetFaviconService()->UpdateFaviconMappingsAndFetch( 458 page_url, icon_urls, icon_type, preferred_icon_size(), callback, tracker); 459 } 460 461 void FaviconHandler::GetFavicon( 462 const GURL& icon_url, 463 chrome::IconType icon_type, 464 const FaviconService::FaviconResultsCallback& callback, 465 CancelableTaskTracker* tracker) { 466 GetFaviconService()->GetFavicon( 467 icon_url, icon_type, preferred_icon_size(), callback, tracker); 468 } 469 470 void FaviconHandler::GetFaviconForURL( 471 const GURL& page_url, 472 int icon_types, 473 const FaviconService::FaviconResultsCallback& callback, 474 CancelableTaskTracker* tracker) { 475 GetFaviconService()->GetFaviconForURL( 476 FaviconService::FaviconForURLParams(page_url, icon_types, 477 preferred_icon_size()), 478 callback, 479 tracker); 480 } 481 482 void FaviconHandler::SetHistoryFavicons(const GURL& page_url, 483 const GURL& icon_url, 484 chrome::IconType icon_type, 485 const gfx::Image& image) { 486 GetFaviconService()->SetFavicons(page_url, icon_url, icon_type, image); 487 } 488 489 bool FaviconHandler::ShouldSaveFavicon(const GURL& url) { 490 if (!profile_->IsOffTheRecord()) 491 return true; 492 493 // Otherwise store the favicon if the page is bookmarked. 494 BookmarkService* bookmark_service = 495 BookmarkService::FromBrowserContext(profile_); 496 return bookmark_service && bookmark_service->IsBookmarked(url); 497 } 498 499 void FaviconHandler::NotifyFaviconUpdated(bool icon_url_changed) { 500 delegate_->NotifyFaviconUpdated(icon_url_changed); 501 } 502 503 void FaviconHandler::OnFaviconDataForInitialURL( 504 const std::vector<chrome::FaviconBitmapResult>& favicon_bitmap_results) { 505 NavigationEntry* entry = GetEntry(); 506 if (!entry) 507 return; 508 509 got_favicon_from_history_ = true; 510 history_results_ = favicon_bitmap_results; 511 512 bool has_results = !favicon_bitmap_results.empty(); 513 favicon_expired_or_incomplete_ = has_results && HasExpiredOrIncompleteResult( 514 preferred_icon_size(), favicon_bitmap_results); 515 516 if (has_results && icon_types_ == chrome::FAVICON && 517 !entry->GetFavicon().valid && 518 (!current_candidate() || 519 DoUrlsAndIconsMatch(*current_candidate(), favicon_bitmap_results))) { 520 if (HasValidResult(favicon_bitmap_results)) { 521 // The db knows the favicon (although it may be out of date) and the entry 522 // doesn't have an icon. Set the favicon now, and if the favicon turns out 523 // to be expired (or the wrong url) we'll fetch later on. This way the 524 // user doesn't see a flash of the default favicon. 525 UpdateFavicon(entry, favicon_bitmap_results); 526 } else { 527 // If |favicon_bitmap_results| does not have any valid results, treat the 528 // favicon as if it's expired. 529 // TODO(pkotwicz): Do something better. 530 favicon_expired_or_incomplete_ = true; 531 } 532 } 533 534 if (has_results && !favicon_expired_or_incomplete_) { 535 if (current_candidate() && 536 !DoUrlsAndIconsMatch(*current_candidate(), favicon_bitmap_results)) { 537 // Mapping in the database is wrong. DownloadFavIconOrAskHistory will 538 // update the mapping for this url and download the favicon if we don't 539 // already have it. 540 DownloadFaviconOrAskHistory(entry->GetURL(), 541 current_candidate()->icon_url, 542 static_cast<chrome::IconType>(current_candidate()->icon_type)); 543 } 544 } else if (current_candidate()) { 545 // We know the official url for the favicon, by either don't have the 546 // favicon or its expired. Continue on to DownloadFaviconOrAskHistory to 547 // either download or check history again. 548 DownloadFaviconOrAskHistory(entry->GetURL(), current_candidate()->icon_url, 549 ToHistoryIconType(current_candidate()->icon_type)); 550 } 551 // else we haven't got the icon url. When we get it we'll ask the 552 // renderer to download the icon. 553 } 554 555 void FaviconHandler::DownloadFaviconOrAskHistory( 556 const GURL& page_url, 557 const GURL& icon_url, 558 chrome::IconType icon_type) { 559 if (favicon_expired_or_incomplete_) { 560 // We have the mapping, but the favicon is out of date. Download it now. 561 ScheduleDownload(page_url, icon_url, icon_type); 562 } else if (GetFaviconService()) { 563 // We don't know the favicon, but we may have previously downloaded the 564 // favicon for another page that shares the same favicon. Ask for the 565 // favicon given the favicon URL. 566 if (profile_->IsOffTheRecord()) { 567 GetFavicon( 568 icon_url, icon_type, 569 base::Bind(&FaviconHandler::OnFaviconData, base::Unretained(this)), 570 &cancelable_task_tracker_); 571 } else { 572 // Ask the history service for the icon. This does two things: 573 // 1. Attempts to fetch the favicon data from the database. 574 // 2. If the favicon exists in the database, this updates the database to 575 // include the mapping between the page url and the favicon url. 576 // This is asynchronous. The history service will call back when done. 577 // Issue the request and associate the current page ID with it. 578 UpdateFaviconMappingAndFetch( 579 page_url, icon_url, icon_type, 580 base::Bind(&FaviconHandler::OnFaviconData, base::Unretained(this)), 581 &cancelable_task_tracker_); 582 } 583 } 584 } 585 586 void FaviconHandler::OnFaviconData( 587 const std::vector<chrome::FaviconBitmapResult>& favicon_bitmap_results) { 588 NavigationEntry* entry = GetEntry(); 589 if (!entry) 590 return; 591 592 bool has_results = !favicon_bitmap_results.empty(); 593 bool has_expired_or_incomplete_result = HasExpiredOrIncompleteResult( 594 preferred_icon_size(), favicon_bitmap_results); 595 596 if (has_results && icon_types_ == chrome::FAVICON) { 597 if (HasValidResult(favicon_bitmap_results)) { 598 // There is a favicon, set it now. If expired we'll download the current 599 // one again, but at least the user will get some icon instead of the 600 // default and most likely the current one is fine anyway. 601 UpdateFavicon(entry, favicon_bitmap_results); 602 } 603 if (has_expired_or_incomplete_result) { 604 // The favicon is out of date. Request the current one. 605 ScheduleDownload(entry->GetURL(), entry->GetFavicon().url, 606 chrome::FAVICON); 607 } 608 } else if (current_candidate() && 609 (!has_results || has_expired_or_incomplete_result || 610 !(DoUrlsAndIconsMatch(*current_candidate(), favicon_bitmap_results)))) { 611 // We don't know the favicon, it is out of date or its type is not same as 612 // one got from page. Request the current one. 613 ScheduleDownload(entry->GetURL(), current_candidate()->icon_url, 614 ToHistoryIconType(current_candidate()->icon_type)); 615 } 616 history_results_ = favicon_bitmap_results; 617 } 618 619 int FaviconHandler::ScheduleDownload( 620 const GURL& url, 621 const GURL& image_url, 622 chrome::IconType icon_type) { 623 // A max bitmap size is specified to avoid receiving huge bitmaps in 624 // OnDidDownloadFavicon(). See FaviconHandlerDelegate::StartDownload() 625 // for more details about the max bitmap size. 626 const int download_id = DownloadFavicon(image_url, 627 GetMaximalIconSize(icon_type)); 628 if (download_id) { 629 // Download ids should be unique. 630 DCHECK(download_requests_.find(download_id) == download_requests_.end()); 631 download_requests_[download_id] = 632 DownloadRequest(url, image_url, icon_type); 633 } 634 635 return download_id; 636 } 637