1// Copyright (c) 2011 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_helper.h"
6
7#include "build/build_config.h"
8
9#include <vector>
10
11#include "base/callback.h"
12#include "base/memory/ref_counted_memory.h"
13#include "chrome/browser/bookmarks/bookmark_model.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/common/icon_messages.h"
16#include "content/browser/renderer_host/render_view_host.h"
17#include "content/browser/tab_contents/navigation_controller.h"
18#include "content/browser/tab_contents/navigation_entry.h"
19#include "content/browser/tab_contents/tab_contents_delegate.h"
20#include "content/browser/tab_contents/tab_contents.h"
21#include "skia/ext/image_operations.h"
22#include "ui/gfx/codec/png_codec.h"
23
24namespace {
25
26// Returns history::IconType the given icon_type corresponds to.
27history::IconType ToHistoryIconType(FaviconURL::IconType icon_type) {
28  switch (icon_type) {
29    case FaviconURL::FAVICON:
30      return history::FAVICON;
31    case FaviconURL::TOUCH_ICON:
32      return history::TOUCH_ICON;
33    case FaviconURL::TOUCH_PRECOMPOSED_ICON:
34      return history::TOUCH_PRECOMPOSED_ICON;
35    case FaviconURL::INVALID_ICON:
36      return history::INVALID_ICON;
37  }
38  NOTREACHED();
39  // Shouldn't reach here, just make compiler happy.
40  return history::INVALID_ICON;
41}
42
43bool DoUrlAndIconMatch(const FaviconURL& favicon_url,
44                       const GURL& url,
45                       history::IconType icon_type) {
46  return favicon_url.icon_url == url &&
47      favicon_url.icon_type == static_cast<FaviconURL::IconType>(icon_type);
48}
49
50}  // namespace
51
52FaviconHelper::DownloadRequest::DownloadRequest()
53    : callback(NULL),
54      icon_type(history::INVALID_ICON) {
55}
56
57FaviconHelper::DownloadRequest::DownloadRequest(const GURL& url,
58                                                const GURL& image_url,
59                                                ImageDownloadCallback* callback,
60                                                history::IconType icon_type)
61    : url(url),
62      image_url(image_url),
63      callback(callback),
64      icon_type(icon_type) {
65}
66
67FaviconHelper::FaviconHelper(TabContents* tab_contents, Type icon_type)
68    : TabContentsObserver(tab_contents),
69      got_favicon_from_history_(false),
70      favicon_expired_(false),
71      icon_types_(icon_type == FAVICON ? history::FAVICON :
72          history::TOUCH_ICON | history::TOUCH_PRECOMPOSED_ICON),
73      current_url_index_(0) {
74}
75
76FaviconHelper::~FaviconHelper() {
77  SkBitmap empty_image;
78
79  // Call pending download callbacks with error to allow caller to clean up.
80  for (DownloadRequests::iterator i = download_requests_.begin();
81       i != download_requests_.end(); ++i) {
82    if (i->second.callback) {
83      i->second.callback->Run(i->first, true, empty_image);
84    }
85  }
86}
87
88void FaviconHelper::FetchFavicon(const GURL& url) {
89  cancelable_consumer_.CancelAllRequests();
90
91  url_ = url;
92
93  favicon_expired_ = got_favicon_from_history_ = false;
94  current_url_index_ = 0;
95  urls_.clear();
96
97  // Request the favicon from the history service. In parallel to this the
98  // renderer is going to notify us (well TabContents) when the favicon url is
99  // available.
100  if (GetFaviconService()) {
101    GetFaviconForURL(url_, icon_types_, &cancelable_consumer_,
102        NewCallback(this, &FaviconHelper::OnFaviconDataForInitialURL));
103  }
104}
105
106int FaviconHelper::DownloadImage(const GURL& image_url,
107                                 int image_size,
108                                 history::IconType icon_type,
109                                 ImageDownloadCallback* callback) {
110  DCHECK(callback);  // Must provide a callback.
111  return ScheduleDownload(GURL(), image_url, image_size, icon_type, callback);
112}
113
114FaviconService* FaviconHelper::GetFaviconService() {
115  return tab_contents()->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
116}
117
118void FaviconHelper::SetFavicon(
119    const GURL& url,
120    const GURL& image_url,
121    const SkBitmap& image,
122    history::IconType icon_type) {
123  const SkBitmap& sized_image = (preferred_icon_size() == 0 ||
124      (preferred_icon_size() == image.width() &&
125       preferred_icon_size() == image.height())) ?
126      image : ConvertToFaviconSize(image);
127
128  if (GetFaviconService() && ShouldSaveFavicon(url)) {
129    std::vector<unsigned char> image_data;
130    gfx::PNGCodec::EncodeBGRASkBitmap(sized_image, false, &image_data);
131    SetHistoryFavicon(url, image_url, image_data, icon_type);
132  }
133
134  if (url == url_ && icon_type == history::FAVICON) {
135    NavigationEntry* entry = GetEntry();
136    if (entry)
137      UpdateFavicon(entry, sized_image);
138  }
139}
140
141void FaviconHelper::UpdateFavicon(NavigationEntry* entry,
142                                  scoped_refptr<RefCountedMemory> data) {
143  SkBitmap image;
144  gfx::PNGCodec::Decode(data->front(), data->size(), &image);
145  UpdateFavicon(entry, image);
146}
147
148void FaviconHelper::UpdateFavicon(NavigationEntry* entry,
149                                  const SkBitmap& image) {
150  // No matter what happens, we need to mark the favicon as being set.
151  entry->favicon().set_is_valid(true);
152
153  if (image.empty())
154    return;
155
156  entry->favicon().set_bitmap(image);
157  tab_contents()->NotifyNavigationStateChanged(TabContents::INVALIDATE_TAB);
158}
159
160void FaviconHelper::OnUpdateFaviconURL(
161    int32 page_id,
162    const std::vector<FaviconURL>& candidates) {
163  NavigationEntry* entry = GetEntry();
164  if (!entry)
165    return;
166
167  bool got_favicon_url_update = false;
168  for (std::vector<FaviconURL>::const_iterator i = candidates.begin();
169       i != candidates.end(); ++i) {
170    if (!i->icon_url.is_empty() && (i->icon_type & icon_types_)) {
171      if (!got_favicon_url_update) {
172        got_favicon_url_update = true;
173        urls_.clear();
174        current_url_index_ = 0;
175      }
176      urls_.push_back(*i);
177    }
178  }
179
180  // TODO(davemoore) Should clear on empty url. Currently we ignore it.
181  // This appears to be what FF does as well.
182  // No URL was added.
183  if (!got_favicon_url_update)
184    return;
185
186  if (!GetFaviconService())
187    return;
188
189  // For FAVICON.
190  if (current_candidate()->icon_type == FaviconURL::FAVICON) {
191    if (!favicon_expired_ && entry->favicon().is_valid() &&
192        DoUrlAndIconMatch(*current_candidate(), entry->favicon().url(),
193                          history::FAVICON))
194      return;
195
196    entry->favicon().set_url(current_candidate()->icon_url);
197  } else if (!favicon_expired_ && got_favicon_from_history_ &&
198              history_icon_.is_valid() &&
199              DoUrlAndIconMatch(
200                  *current_candidate(),
201                  history_icon_.icon_url, history_icon_.icon_type)) {
202    return;
203  }
204
205  if (got_favicon_from_history_)
206    DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
207        ToHistoryIconType(current_candidate()->icon_type));
208}
209
210NavigationEntry* FaviconHelper::GetEntry() {
211  NavigationEntry* entry = tab_contents()->controller().GetActiveEntry();
212  if (entry && entry->url() == url_ &&
213      tab_contents()->IsActiveEntry(entry->page_id())) {
214    return entry;
215  }
216  // If the URL has changed out from under us (as will happen with redirects)
217  // return NULL.
218  return NULL;
219}
220
221int FaviconHelper::DownloadFavicon(const GURL& image_url, int image_size) {
222  return tab_contents()->render_view_host()->DownloadFavicon(image_url,
223                                                             image_size);
224}
225
226void FaviconHelper::UpdateFaviconMappingAndFetch(
227    const GURL& page_url,
228    const GURL& icon_url,
229    history::IconType icon_type,
230    CancelableRequestConsumerBase* consumer,
231    FaviconService::FaviconDataCallback* callback) {
232  GetFaviconService()->UpdateFaviconMappingAndFetch(page_url, icon_url,
233      icon_type, consumer, callback);
234}
235
236void FaviconHelper::GetFavicon(
237    const GURL& icon_url,
238    history::IconType icon_type,
239    CancelableRequestConsumerBase* consumer,
240    FaviconService::FaviconDataCallback* callback) {
241  GetFaviconService()->GetFavicon(icon_url, icon_type, consumer, callback);
242}
243
244void FaviconHelper::GetFaviconForURL(
245    const GURL& page_url,
246    int icon_types,
247    CancelableRequestConsumerBase* consumer,
248    FaviconService::FaviconDataCallback* callback) {
249  GetFaviconService()->GetFaviconForURL(page_url, icon_types, consumer,
250                                        callback);
251}
252
253void FaviconHelper::SetHistoryFavicon(
254    const GURL& page_url,
255    const GURL& icon_url,
256    const std::vector<unsigned char>& image_data,
257    history::IconType icon_type) {
258  GetFaviconService()->SetFavicon(page_url, icon_url, image_data, icon_type);
259}
260
261bool FaviconHelper::ShouldSaveFavicon(const GURL& url) {
262  if (!tab_contents()->profile()->IsOffTheRecord())
263    return true;
264
265  // Otherwise store the favicon if the page is bookmarked.
266  BookmarkModel* bookmark_model = tab_contents()->profile()->GetBookmarkModel();
267  return bookmark_model && bookmark_model->IsBookmarked(url);
268}
269
270bool FaviconHelper::OnMessageReceived(const IPC::Message& message) {
271  bool message_handled = true;
272  IPC_BEGIN_MESSAGE_MAP(FaviconHelper, message)
273    IPC_MESSAGE_HANDLER(IconHostMsg_DidDownloadFavicon, OnDidDownloadFavicon)
274    IPC_MESSAGE_UNHANDLED(message_handled = false)
275  IPC_END_MESSAGE_MAP()
276  return message_handled;
277}
278
279void FaviconHelper::OnDidDownloadFavicon(int id,
280                                         const GURL& image_url,
281                                         bool errored,
282                                         const SkBitmap& image) {
283  DownloadRequests::iterator i = download_requests_.find(id);
284  if (i == download_requests_.end()) {
285    // Currently TabContents notifies us of ANY downloads so that it is
286    // possible to get here.
287    return;
288  }
289
290  if (i->second.callback) {
291    i->second.callback->Run(id, errored, image);
292  } else if (current_candidate() &&
293             DoUrlAndIconMatch(*current_candidate(), image_url,
294                               i->second.icon_type)) {
295    // The downloaded icon is still valid when there is no FaviconURL update
296    // during the downloading.
297    if (!errored) {
298      SetFavicon(i->second.url, image_url, image, i->second.icon_type);
299    } else if (GetEntry() && ++current_url_index_ < urls_.size()) {
300      // Copies all candidate except first one and notifies the FaviconHelper,
301      // so the next candidate can be processed.
302      std::vector<FaviconURL> new_candidates(++urls_.begin(), urls_.end());
303      OnUpdateFaviconURL(0, new_candidates);
304    }
305  }
306  download_requests_.erase(i);
307}
308
309void FaviconHelper::OnFaviconDataForInitialURL(
310    FaviconService::Handle handle,
311    history::FaviconData favicon) {
312  NavigationEntry* entry = GetEntry();
313  if (!entry)
314    return;
315
316  got_favicon_from_history_ = true;
317  history_icon_ = favicon;
318
319  favicon_expired_ = (favicon.known_icon && favicon.expired);
320
321  if (favicon.known_icon && favicon.icon_type == history::FAVICON &&
322      !entry->favicon().is_valid() &&
323      (!current_candidate() ||
324       DoUrlAndIconMatch(
325           *current_candidate(), favicon.icon_url, favicon.icon_type))) {
326    // The db knows the favicon (although it may be out of date) and the entry
327    // doesn't have an icon. Set the favicon now, and if the favicon turns out
328    // to be expired (or the wrong url) we'll fetch later on. This way the
329    // user doesn't see a flash of the default favicon.
330    entry->favicon().set_url(favicon.icon_url);
331    if (favicon.is_valid())
332      UpdateFavicon(entry, favicon.image_data);
333    entry->favicon().set_is_valid(true);
334  }
335
336  if (favicon.known_icon && !favicon.expired) {
337    if (current_candidate() &&
338        !DoUrlAndIconMatch(
339             *current_candidate(), favicon.icon_url, favicon.icon_type)) {
340      // Mapping in the database is wrong. DownloadFavIconOrAskHistory will
341      // update the mapping for this url and download the favicon if we don't
342      // already have it.
343      DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
344          static_cast<history::IconType>(current_candidate()->icon_type));
345    }
346  } else if (current_candidate()) {
347    // We know the official url for the favicon, by either don't have the
348    // favicon or its expired. Continue on to DownloadFaviconOrAskHistory to
349    // either download or check history again.
350    DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
351        ToHistoryIconType(current_candidate()->icon_type));
352  }
353  // else we haven't got the icon url. When we get it we'll ask the
354  // renderer to download the icon.
355}
356
357void FaviconHelper::DownloadFaviconOrAskHistory(
358    const GURL& page_url,
359    const GURL& icon_url,
360    history::IconType icon_type) {
361  if (favicon_expired_) {
362    // We have the mapping, but the favicon is out of date. Download it now.
363    ScheduleDownload(page_url, icon_url, preferred_icon_size(), icon_type,
364                     NULL);
365  } else if (GetFaviconService()) {
366    // We don't know the favicon, but we may have previously downloaded the
367    // favicon for another page that shares the same favicon. Ask for the
368    // favicon given the favicon URL.
369    if (tab_contents()->profile()->IsOffTheRecord()) {
370      GetFavicon(icon_url, icon_type, &cancelable_consumer_,
371          NewCallback(this, &FaviconHelper::OnFaviconData));
372    } else {
373      // Ask the history service for the icon. This does two things:
374      // 1. Attempts to fetch the favicon data from the database.
375      // 2. If the favicon exists in the database, this updates the database to
376      //    include the mapping between the page url and the favicon url.
377      // This is asynchronous. The history service will call back when done.
378      // Issue the request and associate the current page ID with it.
379      UpdateFaviconMappingAndFetch(page_url, icon_url, icon_type,
380          &cancelable_consumer_,
381          NewCallback(this, &FaviconHelper::OnFaviconData));
382    }
383  }
384}
385
386void FaviconHelper::OnFaviconData(FaviconService::Handle handle,
387                                  history::FaviconData favicon) {
388  NavigationEntry* entry = GetEntry();
389  if (!entry)
390    return;
391
392  // No need to update the favicon url. By the time we get here
393  // UpdateFaviconURL will have set the favicon url.
394  if (favicon.icon_type == history::FAVICON) {
395    if (favicon.is_valid()) {
396      // There is a favicon, set it now. If expired we'll download the current
397      // one again, but at least the user will get some icon instead of the
398      // default and most likely the current one is fine anyway.
399      UpdateFavicon(entry, favicon.image_data);
400    }
401    if (!favicon.known_icon || favicon.expired) {
402      // We don't know the favicon, or it is out of date. Request the current
403      // one.
404      ScheduleDownload(entry->url(), entry->favicon().url(),
405                       preferred_icon_size(),
406                       history::FAVICON, NULL);
407    }
408  } else if (current_candidate() && (!favicon.known_icon || favicon.expired ||
409      !(DoUrlAndIconMatch(
410            *current_candidate(), favicon.icon_url, favicon.icon_type)))) {
411    // We don't know the favicon, it is out of date or its type is not same as
412    // one got from page. Request the current one.
413    ScheduleDownload(entry->url(), current_candidate()->icon_url,
414        preferred_icon_size(),
415        ToHistoryIconType(current_candidate()->icon_type), NULL);
416  }
417  history_icon_ = favicon;
418}
419
420int FaviconHelper::ScheduleDownload(const GURL& url,
421                                    const GURL& image_url,
422                                    int image_size,
423                                    history::IconType icon_type,
424                                    ImageDownloadCallback* callback) {
425  const int download_id = DownloadFavicon(image_url, image_size);
426  if (download_id) {
427    // Download ids should be unique.
428    DCHECK(download_requests_.find(download_id) == download_requests_.end());
429    download_requests_[download_id] =
430        DownloadRequest(url, image_url, callback, icon_type);
431  }
432
433  return download_id;
434}
435
436SkBitmap FaviconHelper::ConvertToFaviconSize(const SkBitmap& image) {
437  int width = image.width();
438  int height = image.height();
439  if (width > 0 && height > 0) {
440    calc_favicon_target_size(&width, &height);
441    return skia::ImageOperations::Resize(
442          image, skia::ImageOperations::RESIZE_LANCZOS3,
443          width, height);
444  }
445  return image;
446}
447