• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
GenerateTileId(const base::string16 & url_str)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.
GetTileImagesDir()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.
CreateSiteSpecificLogo(const SkBitmap & bitmap,const base::string16 & tile_id,const base::FilePath & logo_dir,base::FilePath * logo_path)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.
GetPathToBackupLogo(const base::FilePath & logo_dir,base::FilePath * logo_path)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.
PinPageReportUmaCallback(base::win::MetroSecondaryTilePinUmaResult result)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:
~PinPageTaskRunner()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 
PinPageTaskRunner(const base::string16 & title,const base::string16 & url,const SkBitmap & favicon)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 
Run()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 
RunOnFileThread()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 
~FaviconChooser()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 
FaviconChooser(MetroPinTabHelper * helper,const base::string16 & title,const base::string16 & url,const SkBitmap & history_bitmap)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 
UseChosenCandidate()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 
UpdateCandidate(int id,const GURL & image_url,const std::vector<SkBitmap> & bitmaps)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 
AddPendingRequest(int request_id)332 void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id) {
333   in_progress_requests_.insert(request_id);
334 }
335 
MetroPinTabHelper(content::WebContents * web_contents)336 MetroPinTabHelper::MetroPinTabHelper(content::WebContents* web_contents)
337     : content::WebContentsObserver(web_contents) {
338 }
339 
~MetroPinTabHelper()340 MetroPinTabHelper::~MetroPinTabHelper() {}
341 
IsPinned() const342 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 
TogglePinnedToStartScreen()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 
DidNavigateMainFrame(const content::LoadCommittedDetails &,const content::FrameNavigateParams &)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 
DidUpdateFaviconURL(const std::vector<content::FaviconURL> & candidates)420 void MetroPinTabHelper::DidUpdateFaviconURL(
421     const std::vector<content::FaviconURL>& candidates) {
422   favicon_url_candidates_ = candidates;
423 }
424 
DidDownloadFavicon(int id,int http_status_code,const GURL & image_url,const std::vector<SkBitmap> & bitmaps,const std::vector<gfx::Size> & original_bitmap_sizes)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 
UnPinPageFromStartScreen()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 
FaviconDownloaderFinished()455 void MetroPinTabHelper::FaviconDownloaderFinished() {
456   favicon_chooser_.reset();
457 }
458