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.
5 #include "chrome/browser/favicon/favicon_tab_helper.h"
7 #include "base/command_line.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/strings/string_util.h"
10 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/favicon/favicon_service_factory.h"
13 #include "chrome/browser/history/history_service_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/search/search.h"
16 #include "chrome/common/chrome_constants.h"
17 #include "chrome/common/url_constants.h"
18 #include "components/bookmarks/browser/bookmark_model.h"
19 #include "components/favicon/content/favicon_url_util.h"
20 #include "components/favicon/core/favicon_driver_observer.h"
21 #include "components/favicon/core/favicon_handler.h"
22 #include "components/favicon/core/favicon_service.h"
23 #include "components/favicon_base/favicon_types.h"
24 #include "components/history/core/browser/history_service.h"
25 #include "content/public/browser/favicon_status.h"
26 #include "content/public/browser/invalidate_type.h"
27 #include "content/public/browser/navigation_controller.h"
28 #include "content/public/browser/navigation_details.h"
29 #include "content/public/browser/navigation_entry.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/render_view_host.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_contents_delegate.h"
34 #include "content/public/common/favicon_url.h"
35 #include "ui/base/ui_base_switches.h"
36 #include "ui/gfx/codec/png_codec.h"
37 #include "ui/gfx/image/image.h"
38 #include "ui/gfx/image/image_skia.h"
39 #include "ui/gfx/image/image_skia_rep.h"
41 using content::FaviconStatus
;
42 using content::NavigationController
;
43 using content::NavigationEntry
;
44 using content::WebContents
;
46 DEFINE_WEB_CONTENTS_USER_DATA_KEY(FaviconTabHelper
);
50 // Returns whether icon NTP is enabled by experiment.
51 // TODO(huangs): Remove all 3 copies of this routine once Icon NTP launches.
52 bool IsIconNTPEnabled() {
53 // Note: It's important to query the field trial state first, to ensure that
54 // UMA reports the correct group.
55 const std::string group_name
= base::FieldTrialList::FindFullName("IconNTP");
56 using base::CommandLine
;
57 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableIconNtp
))
59 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableIconNtp
))
62 return StartsWithASCII(group_name
, "Enabled", true);
65 #if defined(OS_ANDROID) || defined(OS_IOS)
66 const bool kDownloadLargestIcon
= true;
67 const bool kEnableTouchIcon
= true;
69 const bool kDownloadLargestIcon
= false;
70 const bool kEnableTouchIcon
= false;
76 void FaviconTabHelper::CreateForWebContents(
77 content::WebContents
* web_contents
) {
79 if (FromWebContents(web_contents
))
82 Profile
* original_profile
=
83 Profile::FromBrowserContext(web_contents
->GetBrowserContext())
84 ->GetOriginalProfile();
85 web_contents
->SetUserData(
89 FaviconServiceFactory::GetForProfile(
90 original_profile
, ServiceAccessType::IMPLICIT_ACCESS
),
91 HistoryServiceFactory::GetForProfile(
92 original_profile
, ServiceAccessType::IMPLICIT_ACCESS
),
93 BookmarkModelFactory::GetForProfileIfExists(original_profile
)));
96 FaviconTabHelper::FaviconTabHelper(WebContents
* web_contents
,
97 favicon::FaviconService
* favicon_service
,
98 history::HistoryService
* history_service
,
99 bookmarks::BookmarkModel
* bookmark_model
)
100 : content::WebContentsObserver(web_contents
),
101 favicon_service_(favicon_service
),
102 history_service_(history_service
),
103 bookmark_model_(bookmark_model
) {
104 favicon_handler_
.reset(new favicon::FaviconHandler(
105 favicon_service_
, this, favicon::FaviconHandler::FAVICON
,
106 kDownloadLargestIcon
));
107 if (kEnableTouchIcon
) {
108 touch_icon_handler_
.reset(new favicon::FaviconHandler(
109 favicon_service_
, this, favicon::FaviconHandler::TOUCH
,
110 kDownloadLargestIcon
));
112 if (IsIconNTPEnabled()) {
113 large_icon_handler_
.reset(new favicon::FaviconHandler(
114 favicon_service_
, this, favicon::FaviconHandler::LARGE
, true));
118 FaviconTabHelper::~FaviconTabHelper() {
121 void FaviconTabHelper::FetchFavicon(const GURL
& url
) {
122 favicon_handler_
->FetchFavicon(url
);
123 if (touch_icon_handler_
.get())
124 touch_icon_handler_
->FetchFavicon(url
);
125 if (large_icon_handler_
.get())
126 large_icon_handler_
->FetchFavicon(url
);
129 gfx::Image
FaviconTabHelper::GetFavicon() const {
130 // Like GetTitle(), we also want to use the favicon for the last committed
131 // entry rather than a pending navigation entry.
132 const NavigationController
& controller
= web_contents()->GetController();
133 NavigationEntry
* entry
= controller
.GetTransientEntry();
135 return entry
->GetFavicon().image
;
137 entry
= controller
.GetLastCommittedEntry();
139 return entry
->GetFavicon().image
;
143 bool FaviconTabHelper::FaviconIsValid() const {
144 const NavigationController
& controller
= web_contents()->GetController();
145 NavigationEntry
* entry
= controller
.GetTransientEntry();
147 return entry
->GetFavicon().valid
;
149 entry
= controller
.GetLastCommittedEntry();
151 return entry
->GetFavicon().valid
;
156 bool FaviconTabHelper::ShouldDisplayFavicon() {
157 // Always display a throbber during pending loads.
158 const NavigationController
& controller
= web_contents()->GetController();
159 if (controller
.GetLastCommittedEntry() && controller
.GetPendingEntry())
162 GURL url
= web_contents()->GetURL();
163 if (url
.SchemeIs(content::kChromeUIScheme
) &&
164 url
.host() == chrome::kChromeUINewTabHost
) {
168 // No favicon on Instant New Tab Pages.
169 if (chrome::IsInstantNTP(web_contents()))
175 void FaviconTabHelper::SaveFavicon() {
176 GURL active_url
= GetActiveURL();
177 if (active_url
.is_empty())
180 // Make sure the page is in history, otherwise adding the favicon does
182 if (!history_service_
)
184 history_service_
->AddPageNoVisitForBookmark(active_url
, GetActiveTitle());
186 if (!favicon_service_
)
188 if (!GetActiveFaviconValidity())
190 GURL favicon_url
= GetActiveFaviconURL();
191 if (favicon_url
.is_empty())
193 gfx::Image image
= GetActiveFaviconImage();
196 favicon_service_
->SetFavicons(active_url
, favicon_url
, favicon_base::FAVICON
,
200 void FaviconTabHelper::AddObserver(favicon::FaviconDriverObserver
* observer
) {
201 observer_list_
.AddObserver(observer
);
204 void FaviconTabHelper::RemoveObserver(
205 favicon::FaviconDriverObserver
* observer
) {
206 observer_list_
.RemoveObserver(observer
);
209 int FaviconTabHelper::StartDownload(const GURL
& url
, int max_image_size
) {
210 if (favicon_service_
&& favicon_service_
->WasUnableToDownloadFavicon(url
)) {
211 DVLOG(1) << "Skip Failed FavIcon: " << url
;
215 bool bypass_cache
= (bypass_cache_page_url_
== GetActiveURL());
216 bypass_cache_page_url_
= GURL();
218 return web_contents()->DownloadImage(
219 url
, true, max_image_size
, bypass_cache
,
220 base::Bind(&FaviconTabHelper::DidDownloadFavicon
,
221 base::Unretained(this)));
224 bool FaviconTabHelper::IsOffTheRecord() {
225 DCHECK(web_contents());
226 return web_contents()->GetBrowserContext()->IsOffTheRecord();
229 bool FaviconTabHelper::IsBookmarked(const GURL
& url
) {
230 return bookmark_model_
&& bookmark_model_
->IsBookmarked(url
);
233 GURL
FaviconTabHelper::GetActiveURL() {
234 NavigationEntry
* entry
= web_contents()->GetController().GetActiveEntry();
235 return entry
? entry
->GetURL() : GURL();
238 base::string16
FaviconTabHelper::GetActiveTitle() {
239 NavigationEntry
* entry
= web_contents()->GetController().GetActiveEntry();
240 return entry
? entry
->GetTitle() : base::string16();
243 bool FaviconTabHelper::GetActiveFaviconValidity() {
244 return GetFaviconStatus().valid
;
247 void FaviconTabHelper::SetActiveFaviconValidity(bool valid
) {
248 GetFaviconStatus().valid
= valid
;
251 GURL
FaviconTabHelper::GetActiveFaviconURL() {
252 return GetFaviconStatus().url
;
255 void FaviconTabHelper::SetActiveFaviconURL(const GURL
& url
) {
256 GetFaviconStatus().url
= url
;
259 gfx::Image
FaviconTabHelper::GetActiveFaviconImage() {
260 return GetFaviconStatus().image
;
263 void FaviconTabHelper::SetActiveFaviconImage(const gfx::Image
& image
) {
264 GetFaviconStatus().image
= image
;
267 void FaviconTabHelper::OnFaviconAvailable(const gfx::Image
& image
,
268 const GURL
& icon_url
,
269 bool is_active_favicon
) {
270 if (is_active_favicon
) {
271 // No matter what happens, we need to mark the favicon as being set.
272 SetActiveFaviconValidity(true);
273 bool icon_url_changed
= GetActiveFaviconURL() != icon_url
;
274 SetActiveFaviconURL(icon_url
);
279 SetActiveFaviconImage(image
);
280 content::NotificationService::current()->Notify(
281 chrome::NOTIFICATION_FAVICON_UPDATED
,
282 content::Source
<WebContents
>(web_contents()),
283 content::Details
<bool>(&icon_url_changed
));
284 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB
);
286 if (!image
.IsEmpty()) {
287 FOR_EACH_OBSERVER(favicon::FaviconDriverObserver
, observer_list_
,
288 OnFaviconAvailable(image
));
292 void FaviconTabHelper::DidDownloadFavicon(
294 int http_status_code
,
295 const GURL
& image_url
,
296 const std::vector
<SkBitmap
>& bitmaps
,
297 const std::vector
<gfx::Size
>& original_bitmap_sizes
) {
298 if (bitmaps
.empty() && http_status_code
== 404) {
299 DVLOG(1) << "Failed to Download Favicon:" << image_url
;
300 if (favicon_service_
)
301 favicon_service_
->UnableToDownloadFavicon(image_url
);
304 favicon_handler_
->OnDidDownloadFavicon(id
, image_url
, bitmaps
,
305 original_bitmap_sizes
);
306 if (touch_icon_handler_
.get()) {
307 touch_icon_handler_
->OnDidDownloadFavicon(id
, image_url
, bitmaps
,
308 original_bitmap_sizes
);
310 if (large_icon_handler_
.get()) {
311 large_icon_handler_
->OnDidDownloadFavicon(id
, image_url
, bitmaps
,
312 original_bitmap_sizes
);
316 content::FaviconStatus
& FaviconTabHelper::GetFaviconStatus() {
317 DCHECK(web_contents()->GetController().GetActiveEntry());
318 return web_contents()->GetController().GetActiveEntry()->GetFavicon();
321 void FaviconTabHelper::DidStartNavigationToPendingEntry(
323 NavigationController::ReloadType reload_type
) {
324 if (reload_type
== NavigationController::NO_RELOAD
|| IsOffTheRecord())
327 bypass_cache_page_url_
= url
;
329 if (favicon_service_
) {
330 favicon_service_
->SetFaviconOutOfDateForPage(url
);
331 if (reload_type
== NavigationController::RELOAD_IGNORING_CACHE
)
332 favicon_service_
->ClearUnableToDownloadFavicons();
336 void FaviconTabHelper::DidNavigateMainFrame(
337 const content::LoadCommittedDetails
& details
,
338 const content::FrameNavigateParams
& params
) {
339 favicon_urls_
.clear();
341 // Wait till the user navigates to a new URL to start checking the cache
342 // again. The cache may be ignored for non-reload navigations (e.g.
343 // history.replace() in-page navigation). This is allowed to increase the
344 // likelihood that "reloading a page ignoring the cache" redownloads the
345 // favicon. In particular, a page may do an in-page navigation before
346 // FaviconHandler has the time to determine that the favicon needs to be
348 GURL url
= details
.entry
->GetURL();
349 if (url
!= bypass_cache_page_url_
)
350 bypass_cache_page_url_
= GURL();
352 // Get the favicon, either from history or request it from the net.
356 void FaviconTabHelper::DidUpdateFaviconURL(
357 const std::vector
<content::FaviconURL
>& candidates
) {
358 DCHECK(!candidates
.empty());
359 favicon_urls_
= candidates
;
360 std::vector
<favicon::FaviconURL
> favicon_urls
=
361 favicon::FaviconURLsFromContentFaviconURLs(candidates
);
362 favicon_handler_
->OnUpdateFaviconURL(favicon_urls
);
363 if (touch_icon_handler_
.get())
364 touch_icon_handler_
->OnUpdateFaviconURL(favicon_urls
);
365 if (large_icon_handler_
.get())
366 large_icon_handler_
->OnUpdateFaviconURL(favicon_urls
);