Home | History | Annotate | Download | only in ui
      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/ui/metro_pin_tab_helper_win.h"
      6 
      7 #include <set>
      8 
      9 #include "base/base_paths.h"
     10 #include "base/bind.h"
     11 #include "base/file_util.h"
     12 #include "base/files/file_path.h"
     13 #include "base/logging.h"
     14 #include "base/memory/ref_counted.h"
     15 #include "base/memory/ref_counted_memory.h"
     16 #include "base/metrics/histogram.h"
     17 #include "base/path_service.h"
     18 #include "base/strings/string_number_conversions.h"
     19 #include "base/strings/utf_string_conversions.h"
     20 #include "base/win/metro.h"
     21 #include "chrome/browser/favicon/favicon_tab_helper.h"
     22 #include "chrome/browser/favicon/favicon_util.h"
     23 #include "chrome/common/chrome_paths.h"
     24 #include "content/public/browser/browser_thread.h"
     25 #include "content/public/browser/web_contents.h"
     26 #include "crypto/sha2.h"
     27 #include "third_party/skia/include/core/SkCanvas.h"
     28 #include "third_party/skia/include/core/SkColor.h"
     29 #include "ui/gfx/canvas.h"
     30 #include "ui/gfx/codec/png_codec.h"
     31 #include "ui/gfx/color_analysis.h"
     32 #include "ui/gfx/color_utils.h"
     33 #include "ui/gfx/image/image.h"
     34 #include "ui/gfx/rect.h"
     35 #include "ui/gfx/size.h"
     36 
     37 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper);
     38 
     39 namespace {
     40 
     41 // Histogram name for site-specific tile pinning metrics.
     42 const char kMetroPinMetric[] = "Metro.SecondaryTilePin";
     43 
     44 // Generate an ID for the tile based on |url_str|. The ID is simply a hash of
     45 // the URL.
     46 string16 GenerateTileId(const string16& url_str) {
     47   uint8 hash[crypto::kSHA256Length];
     48   crypto::SHA256HashString(UTF16ToUTF8(url_str), hash, sizeof(hash));
     49   std::string hash_str = base::HexEncode(hash, sizeof(hash));
     50   return UTF8ToUTF16(hash_str);
     51 }
     52 
     53 // Get the path of the directory to store the tile logos in.
     54 base::FilePath GetTileImagesDir() {
     55   base::FilePath tile_images_dir;
     56   if (!PathService::Get(chrome::DIR_USER_DATA, &tile_images_dir))
     57     return base::FilePath();
     58 
     59   tile_images_dir = tile_images_dir.Append(L"TileImages");
     60   if (!base::DirectoryExists(tile_images_dir) &&
     61       !file_util::CreateDirectory(tile_images_dir))
     62     return base::FilePath();
     63 
     64   return tile_images_dir;
     65 }
     66 
     67 // For the given |image| and |tile_id|, try to create a site specific logo in
     68 // |logo_dir|. The path of any created logo is returned in |logo_path|. Return
     69 // value indicates whether a site specific logo was created.
     70 bool CreateSiteSpecificLogo(const SkBitmap& bitmap,
     71                             const string16& tile_id,
     72                             const base::FilePath& logo_dir,
     73                             base::FilePath* logo_path) {
     74   const int kLogoWidth = 120;
     75   const int kLogoHeight = 120;
     76   const int kBoxWidth = 40;
     77   const int kBoxHeight = 40;
     78   const int kCaptionHeight = 20;
     79   const double kBoxFade = 0.75;
     80 
     81   if (bitmap.isNull())
     82     return false;
     83 
     84   // Fill the tile logo with the dominant color of the favicon bitmap.
     85   SkColor dominant_color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
     86   SkPaint paint;
     87   paint.setColor(dominant_color);
     88   gfx::Canvas canvas(gfx::Size(kLogoWidth, kLogoHeight), ui::SCALE_FACTOR_100P,
     89                      true);
     90   canvas.DrawRect(gfx::Rect(0, 0, kLogoWidth, kLogoHeight), paint);
     91 
     92   // Now paint a faded square for the favicon to go in.
     93   color_utils::HSL shift = {-1, -1, kBoxFade};
     94   paint.setColor(color_utils::HSLShift(dominant_color, shift));
     95   int box_left = (kLogoWidth - kBoxWidth) / 2;
     96   int box_top = (kLogoHeight - kCaptionHeight - kBoxHeight) / 2;
     97   canvas.DrawRect(gfx::Rect(box_left, box_top, kBoxWidth, kBoxHeight), paint);
     98 
     99   // Now paint the favicon into the tile, leaving some room at the bottom for
    100   // the caption.
    101   int left = (kLogoWidth - bitmap.width()) / 2;
    102   int top = (kLogoHeight - kCaptionHeight - bitmap.height()) / 2;
    103   canvas.DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), left, top);
    104 
    105   SkBitmap logo_bitmap = canvas.ExtractImageRep().sk_bitmap();
    106   std::vector<unsigned char> logo_png;
    107   if (!gfx::PNGCodec::EncodeBGRASkBitmap(logo_bitmap, true, &logo_png))
    108     return false;
    109 
    110   *logo_path = logo_dir.Append(tile_id).ReplaceExtension(L".png");
    111   return file_util::WriteFile(*logo_path,
    112                               reinterpret_cast<char*>(&logo_png[0]),
    113                               logo_png.size()) > 0;
    114 }
    115 
    116 // Get the path to the backup logo. If the backup logo already exists in
    117 // |logo_dir|, it will be used, otherwise it will be copied out of the install
    118 // folder. (The version in the install folder is not used as it may disappear
    119 // after an upgrade, causing tiles to lose their images if Windows rebuilds
    120 // its tile image cache.)
    121 // The path to the logo is returned in |logo_path|, with the return value
    122 // indicating success.
    123 bool GetPathToBackupLogo(const base::FilePath& logo_dir,
    124                          base::FilePath* logo_path) {
    125   const wchar_t kDefaultLogoFileName[] = L"SecondaryTile.png";
    126   *logo_path = logo_dir.Append(kDefaultLogoFileName);
    127   if (base::PathExists(*logo_path))
    128     return true;
    129 
    130   base::FilePath default_logo_path;
    131   if (!PathService::Get(base::DIR_MODULE, &default_logo_path))
    132     return false;
    133 
    134   default_logo_path = default_logo_path.Append(kDefaultLogoFileName);
    135   return base::CopyFile(default_logo_path, *logo_path);
    136 }
    137 
    138 // UMA reporting callback for site-specific secondary tile creation.
    139 void PinPageReportUmaCallback(
    140     base::win::MetroSecondaryTilePinUmaResult result) {
    141   UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
    142                             result,
    143                             base::win::METRO_PIN_STATE_LIMIT);
    144 }
    145 
    146 // The PinPageTaskRunner class performs the necessary FILE thread actions to
    147 // pin a page, such as generating or copying the tile image file. When it
    148 // has performed these actions it will send the tile creation request to the
    149 // metro driver.
    150 class PinPageTaskRunner : public base::RefCountedThreadSafe<PinPageTaskRunner> {
    151  public:
    152   // Creates a task runner for the pinning operation with the given details.
    153   // |favicon| can be a null image (i.e. favicon.isNull() can be true), in
    154   // which case the backup tile image will be used.
    155   PinPageTaskRunner(const string16& title,
    156                     const string16& url,
    157                     const SkBitmap& favicon);
    158 
    159   void Run();
    160   void RunOnFileThread();
    161 
    162  private:
    163   ~PinPageTaskRunner() {}
    164 
    165   // Details of the page being pinned.
    166   const string16 title_;
    167   const string16 url_;
    168   SkBitmap favicon_;
    169 
    170   friend class base::RefCountedThreadSafe<PinPageTaskRunner>;
    171   DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner);
    172 };
    173 
    174 PinPageTaskRunner::PinPageTaskRunner(const string16& title,
    175                                      const string16& url,
    176                                      const SkBitmap& favicon)
    177     : title_(title),
    178       url_(url),
    179       favicon_(favicon) {}
    180 
    181 void PinPageTaskRunner::Run() {
    182   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    183 
    184   content::BrowserThread::PostTask(
    185       content::BrowserThread::FILE,
    186       FROM_HERE,
    187       base::Bind(&PinPageTaskRunner::RunOnFileThread, this));
    188 }
    189 
    190 void PinPageTaskRunner::RunOnFileThread() {
    191   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
    192 
    193   string16 tile_id = GenerateTileId(url_);
    194   base::FilePath logo_dir = GetTileImagesDir();
    195   if (logo_dir.empty()) {
    196     LOG(ERROR) << "Could not create directory to store tile image.";
    197     return;
    198   }
    199 
    200   base::FilePath logo_path;
    201   if (!CreateSiteSpecificLogo(favicon_, tile_id, logo_dir, &logo_path) &&
    202       !GetPathToBackupLogo(logo_dir, &logo_path)) {
    203     LOG(ERROR) << "Count not get path to logo tile.";
    204     return;
    205   }
    206 
    207   UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
    208                             base::win::METRO_PIN_LOGO_READY,
    209                             base::win::METRO_PIN_STATE_LIMIT);
    210 
    211   HMODULE metro_module = base::win::GetMetroModule();
    212   if (!metro_module)
    213     return;
    214 
    215   base::win::MetroPinToStartScreen metro_pin_to_start_screen =
    216       reinterpret_cast<base::win::MetroPinToStartScreen>(
    217           ::GetProcAddress(metro_module, "MetroPinToStartScreen"));
    218   if (!metro_pin_to_start_screen) {
    219     NOTREACHED();
    220     return;
    221   }
    222 
    223   metro_pin_to_start_screen(tile_id,
    224                             title_,
    225                             url_,
    226                             logo_path,
    227                             base::Bind(&PinPageReportUmaCallback));
    228 }
    229 
    230 }  // namespace
    231 
    232 class MetroPinTabHelper::FaviconChooser {
    233  public:
    234   FaviconChooser(MetroPinTabHelper* helper,
    235                  const string16& title,
    236                  const string16& url,
    237                  const SkBitmap& history_bitmap);
    238 
    239   ~FaviconChooser() {}
    240 
    241   // Pin the page on the FILE thread using the current |best_candidate_| and
    242   // delete the FaviconChooser.
    243   void UseChosenCandidate();
    244 
    245   // Update the |best_candidate_| with the newly downloaded favicons provided.
    246   void UpdateCandidate(int id,
    247                        const GURL& image_url,
    248                        int requested_size,
    249                        const std::vector<SkBitmap>& bitmaps);
    250 
    251   void AddPendingRequest(int request_id);
    252 
    253  private:
    254   // The tab helper that this chooser is operating for.
    255   MetroPinTabHelper* helper_;
    256 
    257   // Title and URL of the page being pinned.
    258   const string16 title_;
    259   const string16 url_;
    260 
    261   // The best candidate we have so far for the current pin operation.
    262   SkBitmap best_candidate_;
    263 
    264   // Outstanding favicon download requests.
    265   std::set<int> in_progress_requests_;
    266 
    267   DISALLOW_COPY_AND_ASSIGN(FaviconChooser);
    268 };
    269 
    270 MetroPinTabHelper::FaviconChooser::FaviconChooser(
    271     MetroPinTabHelper* helper,
    272     const string16& title,
    273     const string16& url,
    274     const SkBitmap& history_bitmap)
    275         : helper_(helper),
    276           title_(title),
    277           url_(url),
    278           best_candidate_(history_bitmap) {}
    279 
    280 void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() {
    281   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    282   scoped_refptr<PinPageTaskRunner> runner(
    283       new PinPageTaskRunner(title_, url_, best_candidate_));
    284   runner->Run();
    285   helper_->FaviconDownloaderFinished();
    286 }
    287 
    288 void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
    289     int id,
    290     const GURL& image_url,
    291     int requested_size,
    292     const std::vector<SkBitmap>& bitmaps) {
    293   const int kMaxIconSize = 32;
    294 
    295   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    296 
    297   std::set<int>::iterator iter = in_progress_requests_.find(id);
    298   // Check that this request is one of ours.
    299   if (iter == in_progress_requests_.end())
    300     return;
    301 
    302   in_progress_requests_.erase(iter);
    303 
    304   // Process the bitmaps, keeping the one that is best so far.
    305   for (std::vector<SkBitmap>::const_iterator iter = bitmaps.begin();
    306        iter != bitmaps.end();
    307        ++iter) {
    308 
    309     // If the new bitmap is too big, ignore it.
    310     if (iter->height() > kMaxIconSize || iter->width() > kMaxIconSize)
    311       continue;
    312 
    313     // If we don't have a best candidate yet, this is better so just grab it.
    314     if (best_candidate_.isNull()) {
    315       best_candidate_ = *iter;
    316       continue;
    317     }
    318 
    319     // If it is smaller than our best one so far, ignore it.
    320     if (iter->height() <= best_candidate_.height() ||
    321         iter->width() <= best_candidate_.width()) {
    322       continue;
    323     }
    324 
    325     // Othewise it is our new best candidate.
    326     best_candidate_ = *iter;
    327   }
    328 
    329   // If there are no more outstanding requests, pin the page on the FILE thread.
    330   // Once this happens this downloader has done its job, so delete it.
    331   if (in_progress_requests_.empty())
    332     UseChosenCandidate();
    333 }
    334 
    335 void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id) {
    336   in_progress_requests_.insert(request_id);
    337 }
    338 
    339 MetroPinTabHelper::MetroPinTabHelper(content::WebContents* web_contents)
    340     : content::WebContentsObserver(web_contents) {
    341 }
    342 
    343 MetroPinTabHelper::~MetroPinTabHelper() {}
    344 
    345 bool MetroPinTabHelper::IsPinned() const {
    346   HMODULE metro_module = base::win::GetMetroModule();
    347   if (!metro_module)
    348     return false;
    349 
    350   typedef BOOL (*MetroIsPinnedToStartScreen)(const string16&);
    351   MetroIsPinnedToStartScreen metro_is_pinned_to_start_screen =
    352       reinterpret_cast<MetroIsPinnedToStartScreen>(
    353           ::GetProcAddress(metro_module, "MetroIsPinnedToStartScreen"));
    354   if (!metro_is_pinned_to_start_screen) {
    355     NOTREACHED();
    356     return false;
    357   }
    358 
    359   GURL url = web_contents()->GetURL();
    360   string16 tile_id = GenerateTileId(UTF8ToUTF16(url.spec()));
    361   return metro_is_pinned_to_start_screen(tile_id) != 0;
    362 }
    363 
    364 void MetroPinTabHelper::TogglePinnedToStartScreen() {
    365   if (IsPinned()) {
    366     UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
    367                               base::win::METRO_UNPIN_INITIATED,
    368                               base::win::METRO_PIN_STATE_LIMIT);
    369     UnPinPageFromStartScreen();
    370     return;
    371   }
    372 
    373   UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
    374                             base::win::METRO_PIN_INITIATED,
    375                             base::win::METRO_PIN_STATE_LIMIT);
    376   GURL url = web_contents()->GetURL();
    377   string16 url_str = UTF8ToUTF16(url.spec());
    378   string16 title = web_contents()->GetTitle();
    379   // TODO(oshima): Use scoped_ptr::Pass to pass it to other thread.
    380   SkBitmap favicon;
    381   FaviconTabHelper* favicon_tab_helper = FaviconTabHelper::FromWebContents(
    382       web_contents());
    383   if (favicon_tab_helper->FaviconIsValid()) {
    384     // Only the 1x bitmap data is needed.
    385     favicon = favicon_tab_helper->GetFavicon().AsImageSkia().GetRepresentation(
    386         ui::SCALE_FACTOR_100P).sk_bitmap();
    387   }
    388 
    389   favicon_chooser_.reset(new FaviconChooser(this, title, url_str, favicon));
    390 
    391   if (favicon_url_candidates_.empty()) {
    392     favicon_chooser_->UseChosenCandidate();
    393     return;
    394   }
    395 
    396   // Request all the candidates.
    397   int preferred_image_size = 0;  // Request the first image.
    398   int max_image_size = 0;  // Do not resize images.
    399   for (std::vector<content::FaviconURL>::const_iterator iter =
    400            favicon_url_candidates_.begin();
    401        iter != favicon_url_candidates_.end();
    402        ++iter) {
    403     favicon_chooser_->AddPendingRequest(
    404         web_contents()->DownloadImage(iter->icon_url,
    405             true,
    406             preferred_image_size,
    407             max_image_size,
    408             base::Bind(&MetroPinTabHelper::DidDownloadFavicon,
    409                        base::Unretained(this))));
    410   }
    411 
    412 }
    413 
    414 void MetroPinTabHelper::DidNavigateMainFrame(
    415     const content::LoadCommittedDetails& /*details*/,
    416     const content::FrameNavigateParams& /*params*/) {
    417   // Cancel any outstanding pin operations once the user navigates away from
    418   // the page.
    419   if (favicon_chooser_.get())
    420     favicon_chooser_.reset();
    421   // Any candidate favicons we have are now out of date so clear them.
    422   favicon_url_candidates_.clear();
    423 }
    424 
    425 void MetroPinTabHelper::DidUpdateFaviconURL(
    426     int32 page_id,
    427     const std::vector<content::FaviconURL>& candidates) {
    428   favicon_url_candidates_ = candidates;
    429 }
    430 
    431 void MetroPinTabHelper::DidDownloadFavicon(
    432     int id,
    433     int http_status_code,
    434     const GURL& image_url,
    435     int requested_size,
    436     const std::vector<SkBitmap>& bitmaps) {
    437   if (favicon_chooser_.get()) {
    438     favicon_chooser_->UpdateCandidate(id, image_url, requested_size, bitmaps);
    439   }
    440 }
    441 
    442 void MetroPinTabHelper::UnPinPageFromStartScreen() {
    443   HMODULE metro_module = base::win::GetMetroModule();
    444   if (!metro_module)
    445     return;
    446 
    447   base::win::MetroUnPinFromStartScreen metro_un_pin_from_start_screen =
    448       reinterpret_cast<base::win::MetroUnPinFromStartScreen>(
    449           ::GetProcAddress(metro_module, "MetroUnPinFromStartScreen"));
    450   if (!metro_un_pin_from_start_screen) {
    451     NOTREACHED();
    452     return;
    453   }
    454 
    455   GURL url = web_contents()->GetURL();
    456   string16 tile_id = GenerateTileId(UTF8ToUTF16(url.spec()));
    457   metro_un_pin_from_start_screen(tile_id,
    458                                  base::Bind(&PinPageReportUmaCallback));
    459 }
    460 
    461 void MetroPinTabHelper::FaviconDownloaderFinished() {
    462   favicon_chooser_.reset();
    463 }
    464