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_tab_helper.h"
6
7#include "chrome/browser/chrome_notification_types.h"
8#include "chrome/browser/favicon/chrome_favicon_client.h"
9#include "chrome/browser/favicon/chrome_favicon_client_factory.h"
10#include "chrome/browser/favicon/favicon_handler.h"
11#include "chrome/browser/favicon/favicon_service.h"
12#include "chrome/browser/favicon/favicon_service_factory.h"
13#include "chrome/browser/history/history_service.h"
14#include "chrome/browser/history/history_service_factory.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/search/search.h"
17#include "chrome/common/chrome_constants.h"
18#include "chrome/common/url_constants.h"
19#include "components/favicon_base/favicon_types.h"
20#include "content/public/browser/favicon_status.h"
21#include "content/public/browser/invalidate_type.h"
22#include "content/public/browser/navigation_controller.h"
23#include "content/public/browser/navigation_details.h"
24#include "content/public/browser/navigation_entry.h"
25#include "content/public/browser/notification_service.h"
26#include "content/public/browser/render_view_host.h"
27#include "content/public/browser/web_contents.h"
28#include "content/public/browser/web_contents_delegate.h"
29#include "content/public/common/favicon_url.h"
30#include "ui/gfx/codec/png_codec.h"
31#include "ui/gfx/image/image.h"
32#include "ui/gfx/image/image_skia.h"
33#include "ui/gfx/image/image_skia_rep.h"
34
35using content::FaviconStatus;
36using content::NavigationController;
37using content::NavigationEntry;
38using content::WebContents;
39
40DEFINE_WEB_CONTENTS_USER_DATA_KEY(FaviconTabHelper);
41
42FaviconTabHelper::FaviconTabHelper(WebContents* web_contents)
43    : content::WebContentsObserver(web_contents),
44      profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())) {
45  client_ = ChromeFaviconClientFactory::GetForProfile(profile_);
46#if defined(OS_ANDROID) || defined(OS_IOS)
47  bool download_largest_icon = true;
48#else
49  bool download_largest_icon = false;
50#endif
51  favicon_handler_.reset(new FaviconHandler(
52      client_, this, FaviconHandler::FAVICON, download_largest_icon));
53  if (chrome::kEnableTouchIcon)
54    touch_icon_handler_.reset(new FaviconHandler(
55        client_, this, FaviconHandler::TOUCH, download_largest_icon));
56}
57
58FaviconTabHelper::~FaviconTabHelper() {
59}
60
61void FaviconTabHelper::FetchFavicon(const GURL& url) {
62  favicon_handler_->FetchFavicon(url);
63  if (touch_icon_handler_.get())
64    touch_icon_handler_->FetchFavicon(url);
65}
66
67gfx::Image FaviconTabHelper::GetFavicon() const {
68  // Like GetTitle(), we also want to use the favicon for the last committed
69  // entry rather than a pending navigation entry.
70  const NavigationController& controller = web_contents()->GetController();
71  NavigationEntry* entry = controller.GetTransientEntry();
72  if (entry)
73    return entry->GetFavicon().image;
74
75  entry = controller.GetLastCommittedEntry();
76  if (entry)
77    return entry->GetFavicon().image;
78  return gfx::Image();
79}
80
81bool FaviconTabHelper::FaviconIsValid() const {
82  const NavigationController& controller = web_contents()->GetController();
83  NavigationEntry* entry = controller.GetTransientEntry();
84  if (entry)
85    return entry->GetFavicon().valid;
86
87  entry = controller.GetLastCommittedEntry();
88  if (entry)
89    return entry->GetFavicon().valid;
90
91  return false;
92}
93
94bool FaviconTabHelper::ShouldDisplayFavicon() {
95  // Always display a throbber during pending loads.
96  const NavigationController& controller = web_contents()->GetController();
97  if (controller.GetLastCommittedEntry() && controller.GetPendingEntry())
98    return true;
99
100  GURL url = web_contents()->GetURL();
101  if (url.SchemeIs(content::kChromeUIScheme) &&
102      url.host() == chrome::kChromeUINewTabHost) {
103    return false;
104  }
105
106  // No favicon on Instant New Tab Pages.
107  if (chrome::IsInstantNTP(web_contents()))
108    return false;
109
110  return true;
111}
112
113void FaviconTabHelper::SaveFavicon() {
114  NavigationEntry* entry = web_contents()->GetController().GetActiveEntry();
115  if (!entry || entry->GetURL().is_empty())
116    return;
117
118  // Make sure the page is in history, otherwise adding the favicon does
119  // nothing.
120  HistoryService* history = HistoryServiceFactory::GetForProfile(
121      profile_->GetOriginalProfile(), Profile::IMPLICIT_ACCESS);
122  if (!history)
123    return;
124  history->AddPageNoVisitForBookmark(entry->GetURL(), entry->GetTitle());
125
126  FaviconService* service = FaviconServiceFactory::GetForProfile(
127      profile_->GetOriginalProfile(), Profile::IMPLICIT_ACCESS);
128  if (!service)
129    return;
130  const FaviconStatus& favicon(entry->GetFavicon());
131  if (!favicon.valid || favicon.url.is_empty() ||
132      favicon.image.IsEmpty()) {
133    return;
134  }
135  service->SetFavicons(
136      entry->GetURL(), favicon.url, favicon_base::FAVICON, favicon.image);
137}
138
139int FaviconTabHelper::StartDownload(const GURL& url, int max_image_size) {
140  FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
141      profile_->GetOriginalProfile(), Profile::IMPLICIT_ACCESS);
142  if (favicon_service && favicon_service->WasUnableToDownloadFavicon(url)) {
143    DVLOG(1) << "Skip Failed FavIcon: " << url;
144    return 0;
145  }
146
147  return web_contents()->DownloadImage(
148      url,
149      true,
150      max_image_size,
151      base::Bind(&FaviconTabHelper::DidDownloadFavicon,base::Unretained(this)));
152}
153
154void FaviconTabHelper::NotifyFaviconUpdated(bool icon_url_changed) {
155  content::NotificationService::current()->Notify(
156      chrome::NOTIFICATION_FAVICON_UPDATED,
157      content::Source<WebContents>(web_contents()),
158      content::Details<bool>(&icon_url_changed));
159  web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
160}
161
162bool FaviconTabHelper::IsOffTheRecord() {
163  DCHECK(web_contents());
164  return web_contents()->GetBrowserContext()->IsOffTheRecord();
165}
166
167const gfx::Image FaviconTabHelper::GetActiveFaviconImage() {
168  return GetFaviconStatus().image;
169}
170
171const GURL FaviconTabHelper::GetActiveFaviconURL() {
172  return GetFaviconStatus().url;
173}
174
175bool FaviconTabHelper::GetActiveFaviconValidity() {
176  return GetFaviconStatus().valid;
177}
178
179const GURL FaviconTabHelper::GetActiveURL() {
180  NavigationEntry* entry = web_contents()->GetController().GetActiveEntry();
181  if (!entry || entry->GetURL().is_empty())
182    return GURL();
183  return entry->GetURL();
184}
185
186void FaviconTabHelper::SetActiveFaviconImage(gfx::Image image) {
187  GetFaviconStatus().image = image;
188}
189
190void FaviconTabHelper::SetActiveFaviconURL(GURL url) {
191  GetFaviconStatus().url = url;
192}
193
194void FaviconTabHelper::SetActiveFaviconValidity(bool validity) {
195  GetFaviconStatus().valid = validity;
196}
197
198content::FaviconStatus& FaviconTabHelper::GetFaviconStatus() {
199  DCHECK(web_contents()->GetController().GetActiveEntry());
200  return web_contents()->GetController().GetActiveEntry()->GetFavicon();
201}
202
203void FaviconTabHelper::DidStartNavigationToPendingEntry(
204    const GURL& url,
205    NavigationController::ReloadType reload_type) {
206  if (reload_type != NavigationController::NO_RELOAD &&
207      !profile_->IsOffTheRecord()) {
208    FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
209        profile_, Profile::IMPLICIT_ACCESS);
210    if (favicon_service) {
211      favicon_service->SetFaviconOutOfDateForPage(url);
212      if (reload_type == NavigationController::RELOAD_IGNORING_CACHE)
213        favicon_service->ClearUnableToDownloadFavicons();
214    }
215  }
216}
217
218void FaviconTabHelper::DidNavigateMainFrame(
219    const content::LoadCommittedDetails& details,
220    const content::FrameNavigateParams& params) {
221  favicon_urls_.clear();
222  // Get the favicon, either from history or request it from the net.
223  FetchFavicon(details.entry->GetURL());
224}
225
226// Returns favicon_base::IconType the given icon_type corresponds to.
227// TODO(jif): Move function to /components/favicon_base/content/
228// crbug.com/374281.
229favicon_base::IconType ToChromeIconType(
230    content::FaviconURL::IconType icon_type) {
231  switch (icon_type) {
232    case content::FaviconURL::FAVICON:
233      return favicon_base::FAVICON;
234    case content::FaviconURL::TOUCH_ICON:
235      return favicon_base::TOUCH_ICON;
236    case content::FaviconURL::TOUCH_PRECOMPOSED_ICON:
237      return favicon_base::TOUCH_PRECOMPOSED_ICON;
238    case content::FaviconURL::INVALID_ICON:
239      return favicon_base::INVALID_ICON;
240  }
241  NOTREACHED();
242  return favicon_base::INVALID_ICON;
243}
244
245void FaviconTabHelper::DidUpdateFaviconURL(
246    const std::vector<content::FaviconURL>& candidates) {
247  DCHECK(!candidates.empty());
248  favicon_urls_ = candidates;
249  std::vector<favicon::FaviconURL> favicon_urls;
250  for (size_t i = 0; i < candidates.size(); i++) {
251    const content::FaviconURL& candidate = candidates[i];
252    favicon_urls.push_back(
253        favicon::FaviconURL(candidate.icon_url,
254                            ToChromeIconType(candidate.icon_type),
255                            candidate.icon_sizes));
256  }
257  favicon_handler_->OnUpdateFaviconURL(favicon_urls);
258  if (touch_icon_handler_.get())
259    touch_icon_handler_->OnUpdateFaviconURL(favicon_urls);
260}
261
262void FaviconTabHelper::DidDownloadFavicon(
263    int id,
264    int http_status_code,
265    const GURL& image_url,
266    const std::vector<SkBitmap>& bitmaps,
267    const std::vector<gfx::Size>& original_bitmap_sizes) {
268
269  if (bitmaps.empty() && http_status_code == 404) {
270    DVLOG(1) << "Failed to Download Favicon:" << image_url;
271    FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
272        profile_->GetOriginalProfile(), Profile::IMPLICIT_ACCESS);
273    if (favicon_service)
274      favicon_service->UnableToDownloadFavicon(image_url);
275  }
276
277  favicon_handler_->OnDidDownloadFavicon(
278      id, image_url, bitmaps, original_bitmap_sizes);
279  if (touch_icon_handler_.get()) {
280    touch_icon_handler_->OnDidDownloadFavicon(
281        id, image_url, bitmaps, original_bitmap_sizes);
282  }
283}
284