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 "components/favicon/core/favicon_handler.h" | 5 #include "components/favicon/core/favicon_handler.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cmath> | 8 #include <cmath> |
9 #include <utility> | 9 #include <utility> |
10 #include <vector> | 10 #include <vector> |
11 | 11 |
12 #include "base/bind.h" | 12 #include "base/bind.h" |
13 #include "base/bind_helpers.h" | 13 #include "base/bind_helpers.h" |
14 #include "base/feature_list.h" | |
14 #include "base/memory/ref_counted_memory.h" | 15 #include "base/memory/ref_counted_memory.h" |
15 #include "base/metrics/histogram_macros.h" | 16 #include "base/metrics/histogram_macros.h" |
16 #include "build/build_config.h" | 17 #include "build/build_config.h" |
17 #include "components/favicon/core/favicon_service.h" | 18 #include "components/favicon/core/favicon_service.h" |
18 #include "components/favicon_base/favicon_util.h" | 19 #include "components/favicon_base/favicon_util.h" |
19 #include "components/favicon_base/select_favicon_frames.h" | 20 #include "components/favicon_base/select_favicon_frames.h" |
20 #include "skia/ext/image_operations.h" | 21 #include "skia/ext/image_operations.h" |
21 #include "ui/gfx/codec/png_codec.h" | 22 #include "ui/gfx/codec/png_codec.h" |
22 #include "ui/gfx/image/image_skia.h" | 23 #include "ui/gfx/image/image_skia.h" |
23 #include "ui/gfx/image/image_util.h" | 24 #include "ui/gfx/image/image_util.h" |
24 | 25 |
25 namespace favicon { | 26 namespace favicon { |
27 | |
28 const base::Feature kFaviconsFromWebManifest{"FaviconsFromWebManifest", | |
29 base::FEATURE_DISABLED_BY_DEFAULT}; | |
30 | |
26 namespace { | 31 namespace { |
27 | 32 |
28 const int kNonTouchLargestIconSize = 192; | 33 const int kNonTouchLargestIconSize = 192; |
29 | 34 |
30 // Size (along each axis) of a touch icon. This currently corresponds to | 35 // Size (along each axis) of a touch icon. This currently corresponds to |
31 // the apple touch icon for iPad. | 36 // the apple touch icon for iPad. |
32 const int kTouchIconSize = 144; | 37 const int kTouchIconSize = 144; |
33 | 38 |
34 // Returns true if all of the icon URLs and icon types in |bitmap_results| are | 39 // Returns true if all of the icon URLs and icon types in |bitmap_results| are |
35 // identical and if they match |icon_url| and |icon_type|. Returns false if | 40 // identical and if they match |icon_url| and |icon_type|. Returns false if |
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
221 | 226 |
222 void FaviconHandler::FetchFavicon(const GURL& url) { | 227 void FaviconHandler::FetchFavicon(const GURL& url) { |
223 cancelable_task_tracker_for_page_url_.TryCancelAll(); | 228 cancelable_task_tracker_for_page_url_.TryCancelAll(); |
224 cancelable_task_tracker_for_candidates_.TryCancelAll(); | 229 cancelable_task_tracker_for_candidates_.TryCancelAll(); |
225 | 230 |
226 url_ = url; | 231 url_ = url; |
227 | 232 |
228 initial_history_result_expired_or_incomplete_ = false; | 233 initial_history_result_expired_or_incomplete_ = false; |
229 redownload_icons_ = false; | 234 redownload_icons_ = false; |
230 got_favicon_from_history_ = false; | 235 got_favicon_from_history_ = false; |
236 manifest_download_request_.Cancel(); | |
231 image_download_request_.Cancel(); | 237 image_download_request_.Cancel(); |
238 manifest_url_.reset(); | |
232 candidates_.clear(); | 239 candidates_.clear(); |
233 notification_icon_url_ = GURL(); | 240 notification_icon_url_ = GURL(); |
234 notification_icon_type_ = favicon_base::INVALID_ICON; | 241 notification_icon_type_ = favicon_base::INVALID_ICON; |
235 num_image_download_requests_ = 0; | 242 num_image_download_requests_ = 0; |
236 current_candidate_index_ = 0u; | 243 current_candidate_index_ = 0u; |
237 best_favicon_ = DownloadedFavicon(); | 244 best_favicon_ = DownloadedFavicon(); |
238 | 245 |
239 // Request the favicon from the history service. In parallel to this the | 246 // Request the favicon from the history service. In parallel to this the |
240 // renderer is going to notify us (well WebContents) when the favicon url is | 247 // renderer is going to notify us (well WebContents) when the favicon url is |
241 // available. | 248 // available. |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
309 delegate_->OnFaviconUpdated(url_, handler_type_, icon_url, | 316 delegate_->OnFaviconUpdated(url_, handler_type_, icon_url, |
310 icon_url != notification_icon_url_, | 317 icon_url != notification_icon_url_, |
311 image_with_adjusted_colorspace); | 318 image_with_adjusted_colorspace); |
312 | 319 |
313 notification_icon_url_ = icon_url; | 320 notification_icon_url_ = icon_url; |
314 notification_icon_type_ = icon_type; | 321 notification_icon_type_ = icon_type; |
315 } | 322 } |
316 | 323 |
317 void FaviconHandler::OnUpdateCandidates( | 324 void FaviconHandler::OnUpdateCandidates( |
318 const GURL& page_url, | 325 const GURL& page_url, |
319 const std::vector<FaviconURL>& candidates) { | 326 const std::vector<FaviconURL>& candidates, |
327 const base::Optional<GURL>& manifest_url) { | |
pkotwicz
2017/05/12 06:13:29
I found two bugs :(
Scenario for Bug #1
1) OnUpda
mastiz
2017/05/12 13:31:33
I will work on this, I think you're right bugs exi
mastiz
2017/05/15 14:07:00
I added the two corresponding tests which now pass
| |
328 DCHECK_EQ(page_url, url_); | |
320 if (page_url != url_) | 329 if (page_url != url_) |
321 return; | 330 return; |
322 | 331 |
332 if (base::FeatureList::IsEnabled(kFaviconsFromWebManifest)) { | |
333 // |candidates| or |manifest_url| could have been modified via Javascript. | |
pkotwicz
2017/05/12 06:13:29
Nit: You can move the if() statement on line 334 o
mastiz
2017/05/12 13:31:33
Done.
| |
334 if (manifest_url_ && manifest_url_ == manifest_url) | |
335 return; | |
336 | |
337 manifest_url_ = manifest_url; | |
338 } | |
339 | |
340 non_manifest_original_candidates_ = candidates; | |
341 | |
342 // Check if the manifest was previously blacklisted (e.g. returned a 404) and | |
343 // ignore the manifest URL if that's the case. | |
344 if (manifest_url_ && service_->WasUnableToDownloadFavicon(*manifest_url_)) { | |
345 DVLOG(1) << "Skip failed Manifest: " << *manifest_url_; | |
346 manifest_url_.reset(); | |
347 } | |
348 | |
349 if (manifest_url_) { | |
350 cancelable_task_tracker_for_candidates_.TryCancelAll(); | |
pkotwicz
2017/05/12 06:13:29
For the sake of sanity, should we cancel |manifest
mastiz
2017/05/12 13:31:33
Done.
| |
351 // See if there is a cached favicon for the manifest. | |
352 GetFaviconAndUpdateMappingsUnlessIncognito( | |
353 /*icon_url=*/*manifest_url_, favicon_base::FAVICON, | |
354 base::Bind(&FaviconHandler::OnFaviconDataForManifestFromFaviconService, | |
pkotwicz
2017/05/12 07:20:26
The following scenario is buggy:
1) User is using
pkotwicz
2017/05/12 15:37:42
I think that this bug affects https://www.twitter.
mastiz
2017/05/15 14:07:00
Added UnknownManifestWithoutIconsAndRegularIconInH
pkotwicz
2017/05/16 06:36:35
Yes I would prefer that. That works well with my p
mastiz
2017/05/16 11:05:22
I have some trouble following this discussion, mig
mastiz
2017/05/16 17:53:45
As discussed offline: the original bug is not an a
| |
355 base::Unretained(this))); | |
356 } else { | |
357 // If no manifest available, proceed with the regular candidates only. | |
358 OnGotFinalIconURLCandidates(candidates); | |
359 } | |
360 } | |
361 | |
362 void FaviconHandler::OnFaviconDataForManifestFromFaviconService( | |
363 const std::vector<favicon_base::FaviconRawBitmapResult>& | |
364 favicon_bitmap_results) { | |
365 bool has_valid_result = HasValidResult(favicon_bitmap_results); | |
366 bool has_expired_or_incomplete_result = | |
367 !has_valid_result || HasExpiredOrIncompleteResult(preferred_icon_size(), | |
368 favicon_bitmap_results); | |
369 | |
370 if (has_valid_result) { | |
371 // There is a valid favicon. Notify any observers. It is useful to notify | |
372 // the observers even if the favicon is expired or incomplete (incorrect | |
373 // size) because temporarily showing the user an expired favicon or | |
374 // streched favicon is preferable to showing the user the default favicon. | |
375 NotifyFaviconUpdated(favicon_bitmap_results); | |
376 } | |
377 | |
378 if (has_expired_or_incomplete_result) { | |
379 manifest_download_request_.Reset(base::Bind( | |
380 &FaviconHandler::OnDidDownloadManifest, base::Unretained(this))); | |
381 delegate_->DownloadManifest(*manifest_url_, | |
382 manifest_download_request_.callback()); | |
383 } | |
384 } | |
385 | |
386 void FaviconHandler::OnDidDownloadManifest( | |
387 const std::vector<FaviconURL>& candidates) { | |
388 // Mark manifest download as finished. | |
389 manifest_download_request_.Cancel(); | |
390 | |
391 if (!candidates.empty()) { | |
392 OnGotFinalIconURLCandidates(candidates); | |
393 return; | |
394 } | |
395 | |
396 // If either the downloading of the manifest failed, OR the manifest contains | |
397 // no icons, proceed with the list of icons listed in the HTML. | |
398 DVLOG(1) << "Could not fetch Manifest icons from " << *manifest_url_ | |
399 << ", falling back to inlined ones, which are " | |
400 << non_manifest_original_candidates_.size(); | |
401 | |
402 manifest_url_.reset(); | |
403 OnGotFinalIconURLCandidates(non_manifest_original_candidates_); | |
404 } | |
405 | |
406 void FaviconHandler::OnGotFinalIconURLCandidates( | |
407 const std::vector<FaviconURL>& candidates) { | |
323 std::vector<FaviconCandidate> sorted_candidates; | 408 std::vector<FaviconCandidate> sorted_candidates; |
324 const std::vector<int> desired_pixel_sizes = | 409 const std::vector<int> desired_pixel_sizes = |
325 GetDesiredPixelSizes(handler_type_); | 410 GetDesiredPixelSizes(handler_type_); |
326 for (const FaviconURL& candidate : candidates) { | 411 for (const FaviconURL& candidate : candidates) { |
327 if (!candidate.icon_url.is_empty() && (candidate.icon_type & icon_types_)) { | 412 if (!candidate.icon_url.is_empty() && (candidate.icon_type & icon_types_)) { |
328 sorted_candidates.push_back( | 413 sorted_candidates.push_back( |
329 FaviconCandidate::FromFaviconURL(candidate, desired_pixel_sizes)); | 414 FaviconCandidate::FromFaviconURL(candidate, desired_pixel_sizes)); |
330 } | 415 } |
331 } | 416 } |
332 | 417 |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
431 // Process the next candidate. | 516 // Process the next candidate. |
432 ++current_candidate_index_; | 517 ++current_candidate_index_; |
433 DownloadCurrentCandidateOrAskFaviconService(); | 518 DownloadCurrentCandidateOrAskFaviconService(); |
434 } else { | 519 } else { |
435 // OnDidDownloadFavicon() can only be called after requesting a download, so | 520 // OnDidDownloadFavicon() can only be called after requesting a download, so |
436 // |num_image_download_requests_| can never be 0. | 521 // |num_image_download_requests_| can never be 0. |
437 RecordDownloadAttemptsForHandlerType(handler_type_, | 522 RecordDownloadAttemptsForHandlerType(handler_type_, |
438 num_image_download_requests_); | 523 num_image_download_requests_); |
439 // We have either found the ideal candidate or run out of candidates. | 524 // We have either found the ideal candidate or run out of candidates. |
440 if (best_favicon_.candidate.icon_type != favicon_base::INVALID_ICON) { | 525 if (best_favicon_.candidate.icon_type != favicon_base::INVALID_ICON) { |
441 // No more icons to request, set the favicon from the candidate. | 526 // No more icons to request, set the favicon from the candidate. Note that |
442 SetFavicon(best_favicon_.candidate.icon_url, best_favicon_.image, | 527 // manifest URLs override icon URLs if available. |
pkotwicz
2017/05/12 06:13:29
Nit: "Note that manifest URLs override icon URLs i
mastiz
2017/05/12 13:31:33
Done.
| |
443 best_favicon_.candidate.icon_type); | 528 SetFavicon(manifest_url_.value_or(best_favicon_.candidate.icon_url), |
529 best_favicon_.image, best_favicon_.candidate.icon_type); | |
444 } | 530 } |
445 // Clear download related state. | 531 // Clear download related state. |
446 current_candidate_index_ = candidates_.size(); | 532 current_candidate_index_ = candidates_.size(); |
447 num_image_download_requests_ = 0; | 533 num_image_download_requests_ = 0; |
448 best_favicon_ = DownloadedFavicon(); | 534 best_favicon_ = DownloadedFavicon(); |
449 } | 535 } |
450 } | 536 } |
451 | 537 |
452 const std::vector<GURL> FaviconHandler::GetIconURLs() const { | 538 const std::vector<GURL> FaviconHandler::GetIconURLs() const { |
453 std::vector<GURL> icon_urls; | 539 std::vector<GURL> icon_urls; |
454 for (const FaviconCandidate& candidate : candidates_) | 540 for (const FaviconCandidate& candidate : candidates_) |
455 icon_urls.push_back(candidate.icon_url); | 541 icon_urls.push_back(candidate.icon_url); |
456 return icon_urls; | 542 return icon_urls; |
457 } | 543 } |
458 | 544 |
459 bool FaviconHandler::HasPendingTasksForTest() { | 545 bool FaviconHandler::HasPendingTasksForTest() { |
460 return !image_download_request_.IsCancelled() || | 546 return !image_download_request_.IsCancelled() || |
547 !manifest_download_request_.IsCancelled() || | |
461 cancelable_task_tracker_for_page_url_.HasTrackedTasks() || | 548 cancelable_task_tracker_for_page_url_.HasTrackedTasks() || |
462 cancelable_task_tracker_for_candidates_.HasTrackedTasks(); | 549 cancelable_task_tracker_for_candidates_.HasTrackedTasks(); |
463 } | 550 } |
464 | 551 |
465 bool FaviconHandler::ShouldSaveFavicon() { | 552 bool FaviconHandler::ShouldSaveFavicon() { |
466 if (!delegate_->IsOffTheRecord()) | 553 if (!delegate_->IsOffTheRecord()) |
467 return true; | 554 return true; |
468 | 555 |
469 // Always save favicon if the page is bookmarked. | 556 // Always save favicon if the page is bookmarked. |
470 return delegate_->IsBookmarked(url_); | 557 return delegate_->IsBookmarked(url_); |
471 } | 558 } |
472 | 559 |
473 void FaviconHandler::OnFaviconDataForInitialURLFromFaviconService( | 560 void FaviconHandler::OnFaviconDataForInitialURLFromFaviconService( |
474 const std::vector<favicon_base::FaviconRawBitmapResult>& | 561 const std::vector<favicon_base::FaviconRawBitmapResult>& |
475 favicon_bitmap_results) { | 562 favicon_bitmap_results) { |
476 got_favicon_from_history_ = true; | 563 got_favicon_from_history_ = true; |
477 bool has_valid_result = HasValidResult(favicon_bitmap_results); | 564 bool has_valid_result = HasValidResult(favicon_bitmap_results); |
478 initial_history_result_expired_or_incomplete_ = | 565 initial_history_result_expired_or_incomplete_ = |
479 !has_valid_result || | 566 !has_valid_result || |
480 HasExpiredOrIncompleteResult(preferred_icon_size(), | 567 HasExpiredOrIncompleteResult(preferred_icon_size(), |
481 favicon_bitmap_results); | 568 favicon_bitmap_results); |
482 redownload_icons_ = initial_history_result_expired_or_incomplete_ && | 569 redownload_icons_ = initial_history_result_expired_or_incomplete_ && |
483 !favicon_bitmap_results.empty(); | 570 !favicon_bitmap_results.empty(); |
484 | 571 |
485 if (has_valid_result && (!current_candidate() || | 572 if (has_valid_result) { |
pkotwicz
2017/05/12 06:13:29
Optional: In a separate CL you can remove the if()
| |
486 DoUrlsAndIconsMatch(current_candidate()->icon_url, | 573 if (!current_candidate() || |
487 current_candidate()->icon_type, | 574 DoUrlsAndIconsMatch(current_candidate()->icon_url, |
488 favicon_bitmap_results))) { | 575 current_candidate()->icon_type, |
489 // The db knows the favicon (although it may be out of date) and the entry | 576 favicon_bitmap_results) || |
490 // doesn't have an icon. Set the favicon now, and if the favicon turns out | 577 (manifest_url_ && |
491 // to be expired (or the wrong url) we'll fetch later on. This way the | 578 DoUrlsAndIconsMatch(*manifest_url_, favicon_base::FAVICON, |
492 // user doesn't see a flash of the default favicon. | 579 favicon_bitmap_results))) { |
493 NotifyFaviconUpdated(favicon_bitmap_results); | 580 // The db knows the favicon (although it may be out of date) and the entry |
581 // doesn't have an icon. Set the favicon now, and if the favicon turns out | |
582 // to be expired (or the wrong url) we'll fetch later on. This way the | |
583 // user doesn't see a flash of the default favicon. | |
584 NotifyFaviconUpdated(favicon_bitmap_results); | |
585 } | |
494 } | 586 } |
495 | 587 |
496 if (current_candidate()) | 588 if (current_candidate()) |
497 OnGotInitialHistoryDataAndIconURLCandidates(); | 589 OnGotInitialHistoryDataAndIconURLCandidates(); |
498 } | 590 } |
499 | 591 |
500 void FaviconHandler::DownloadCurrentCandidateOrAskFaviconService() { | 592 void FaviconHandler::DownloadCurrentCandidateOrAskFaviconService() { |
501 GURL icon_url = current_candidate()->icon_url; | 593 const GURL icon_url = current_candidate()->icon_url; |
502 favicon_base::IconType icon_type = current_candidate()->icon_type; | 594 const favicon_base::IconType icon_type = current_candidate()->icon_type; |
503 | 595 // If the icons listed in a manifest are being processed, skip the cache |
504 if (redownload_icons_) { | 596 // lookup for |icon_url| since the manifest's URL is used for caching anyway, |
597 // and this lookup has happened earlier. | |
pkotwicz
2017/05/12 06:13:29
How about: "since the manifest's URL is using for
mastiz
2017/05/12 13:31:33
Done.
| |
598 if (redownload_icons_ || manifest_url_) { | |
505 // We have the mapping, but the favicon is out of date. Download it now. | 599 // We have the mapping, but the favicon is out of date. Download it now. |
506 ScheduleImageDownload(icon_url, icon_type); | 600 ScheduleImageDownload(icon_url, icon_type); |
507 } else { | 601 } else { |
508 // We don't know the favicon, but we may have previously downloaded the | 602 GetFaviconAndUpdateMappingsUnlessIncognito( |
509 // favicon for another page that shares the same favicon. Ask for the | 603 icon_url, icon_type, |
510 // favicon given the favicon URL. | 604 base::Bind(&FaviconHandler::OnFaviconData, base::Unretained(this))); |
511 if (delegate_->IsOffTheRecord()) { | |
512 service_->GetFavicon( | |
513 icon_url, icon_type, preferred_icon_size(), | |
514 base::Bind(&FaviconHandler::OnFaviconData, base::Unretained(this)), | |
515 &cancelable_task_tracker_for_candidates_); | |
516 } else { | |
517 // Ask the history service for the icon. This does two things: | |
518 // 1. Attempts to fetch the favicon data from the database. | |
519 // 2. If the favicon exists in the database, this updates the database to | |
520 // include the mapping between the page url and the favicon url. | |
521 // This is asynchronous. The history service will call back when done. | |
522 service_->UpdateFaviconMappingsAndFetch( | |
523 url_, icon_url, icon_type, preferred_icon_size(), | |
524 base::Bind(&FaviconHandler::OnFaviconData, base::Unretained(this)), | |
525 &cancelable_task_tracker_for_candidates_); | |
526 } | |
527 } | 605 } |
528 } | 606 } |
529 | 607 |
608 void FaviconHandler::GetFaviconAndUpdateMappingsUnlessIncognito( | |
609 const GURL& icon_url, | |
610 favicon_base::IconType icon_type, | |
611 const favicon_base::FaviconResultsCallback& callback) { | |
612 // We don't know the favicon, but we may have previously downloaded the | |
613 // favicon for another page that shares the same favicon. Ask for the | |
614 // favicon given the favicon URL. | |
615 if (delegate_->IsOffTheRecord()) { | |
616 service_->GetFavicon(icon_url, icon_type, preferred_icon_size(), callback, | |
617 &cancelable_task_tracker_for_candidates_); | |
618 } else { | |
619 // Ask the history service for the icon. This does two things: | |
620 // 1. Attempts to fetch the favicon data from the database. | |
621 // 2. If the favicon exists in the database, this updates the database to | |
622 // include the mapping between the page url and the favicon url. | |
623 // This is asynchronous. The history service will call back when done. | |
624 service_->UpdateFaviconMappingsAndFetch( | |
625 url_, icon_url, icon_type, preferred_icon_size(), callback, | |
626 &cancelable_task_tracker_for_candidates_); | |
627 } | |
628 } | |
629 | |
530 void FaviconHandler::OnFaviconData(const std::vector< | 630 void FaviconHandler::OnFaviconData(const std::vector< |
531 favicon_base::FaviconRawBitmapResult>& favicon_bitmap_results) { | 631 favicon_base::FaviconRawBitmapResult>& favicon_bitmap_results) { |
532 bool has_valid_result = HasValidResult(favicon_bitmap_results); | 632 bool has_valid_result = HasValidResult(favicon_bitmap_results); |
533 bool has_expired_or_incomplete_result = | 633 bool has_expired_or_incomplete_result = |
534 !has_valid_result || HasExpiredOrIncompleteResult(preferred_icon_size(), | 634 !has_valid_result || HasExpiredOrIncompleteResult(preferred_icon_size(), |
535 favicon_bitmap_results); | 635 favicon_bitmap_results); |
536 | 636 |
537 if (has_valid_result) { | 637 if (has_valid_result) { |
538 // There is a valid favicon. Notify any observers. It is useful to notify | 638 // There is a valid favicon. Notify any observers. It is useful to notify |
539 // the observers even if the favicon is expired or incomplete (incorrect | 639 // the observers even if the favicon is expired or incomplete (incorrect |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
571 // A max bitmap size is specified to avoid receiving huge bitmaps in | 671 // A max bitmap size is specified to avoid receiving huge bitmaps in |
572 // OnDidDownloadFavicon(). See FaviconDriver::StartDownload() | 672 // OnDidDownloadFavicon(). See FaviconDriver::StartDownload() |
573 // for more details about the max bitmap size. | 673 // for more details about the max bitmap size. |
574 const int download_id = | 674 const int download_id = |
575 delegate_->DownloadImage(image_url, GetMaximalIconSize(handler_type_), | 675 delegate_->DownloadImage(image_url, GetMaximalIconSize(handler_type_), |
576 image_download_request_.callback()); | 676 image_download_request_.callback()); |
577 DCHECK_NE(download_id, 0); | 677 DCHECK_NE(download_id, 0); |
578 } | 678 } |
579 | 679 |
580 } // namespace favicon | 680 } // namespace favicon |
OLD | NEW |