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