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.
5 #include "chrome/browser/ui/metro_pin_tab_helper_win.h"
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/geometry/rect.h"
33 #include "ui/gfx/geometry/size.h"
34 #include "ui/gfx/image/image.h"
36 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper
);
40 // Histogram name for site-specific tile pinning metrics.
41 const char kMetroPinMetric
[] = "Metro.SecondaryTilePin";
43 // Generate an ID for the tile based on |url_str|. The ID is simply a hash of
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
);
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();
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();
63 return tile_images_dir
;
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;
83 // Fill the tile logo with the dominant color of the favicon bitmap.
84 SkColor dominant_color
= color_utils::CalculateKMeanColorOfBitmap(bitmap
);
86 paint
.setColor(dominant_color
);
87 gfx::Canvas
canvas(gfx::Size(kLogoWidth
, kLogoHeight
), 1.0f
,
89 canvas
.DrawRect(gfx::Rect(0, 0, kLogoWidth
, kLogoHeight
), paint
);
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
);
98 // Now paint the favicon into the tile, leaving some room at the bottom for
100 int left
= (kLogoWidth
- bitmap
.width()) / 2;
101 int top
= (kLogoHeight
- kCaptionHeight
- bitmap
.height()) / 2;
102 canvas
.DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap
), left
, top
);
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
))
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;
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
))
129 base::FilePath default_logo_path
;
130 if (!PathService::Get(base::DIR_MODULE
, &default_logo_path
))
133 default_logo_path
= default_logo_path
.Append(kDefaultLogoFileName
);
134 return base::CopyFile(default_logo_path
, *logo_path
);
137 // UMA reporting callback for site-specific secondary tile creation.
138 void PinPageReportUmaCallback(
139 base::win::MetroSecondaryTilePinUmaResult result
) {
140 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric
,
142 base::win::METRO_PIN_STATE_LIMIT
);
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
149 class PinPageTaskRunner
: public base::RefCountedThreadSafe
<PinPageTaskRunner
> {
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
);
159 void RunOnFileThread();
162 ~PinPageTaskRunner() {}
164 // Details of the page being pinned.
165 const base::string16 title_
;
166 const base::string16 url_
;
169 friend class base::RefCountedThreadSafe
<PinPageTaskRunner
>;
170 DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner
);
173 PinPageTaskRunner::PinPageTaskRunner(const base::string16
& title
,
174 const base::string16
& url
,
175 const SkBitmap
& favicon
)
180 void PinPageTaskRunner::Run() {
181 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
183 content::BrowserThread::PostTask(
184 content::BrowserThread::FILE,
186 base::Bind(&PinPageTaskRunner::RunOnFileThread
, this));
189 void PinPageTaskRunner::RunOnFileThread() {
190 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
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.";
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.";
206 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric
,
207 base::win::METRO_PIN_LOGO_READY
,
208 base::win::METRO_PIN_STATE_LIMIT
);
210 HMODULE metro_module
= base::win::GetMetroModule();
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
) {
222 metro_pin_to_start_screen(tile_id
,
226 base::Bind(&PinPageReportUmaCallback
));
231 class MetroPinTabHelper::FaviconChooser
{
233 FaviconChooser(MetroPinTabHelper
* helper
,
234 const base::string16
& title
,
235 const base::string16
& url
,
236 const SkBitmap
& history_bitmap
);
240 // Pin the page on the FILE thread using the current |best_candidate_| and
241 // delete the FaviconChooser.
242 void UseChosenCandidate();
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
);
249 void AddPendingRequest(int request_id
);
252 // The tab helper that this chooser is operating for.
253 MetroPinTabHelper
* helper_
;
255 // Title and URL of the page being pinned.
256 const base::string16 title_
;
257 const base::string16 url_
;
259 // The best candidate we have so far for the current pin operation.
260 SkBitmap best_candidate_
;
262 // Outstanding favicon download requests.
263 std::set
<int> in_progress_requests_
;
265 DISALLOW_COPY_AND_ASSIGN(FaviconChooser
);
268 MetroPinTabHelper::FaviconChooser::FaviconChooser(
269 MetroPinTabHelper
* helper
,
270 const base::string16
& title
,
271 const base::string16
& url
,
272 const SkBitmap
& history_bitmap
)
276 best_candidate_(history_bitmap
) {}
278 void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() {
279 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
280 scoped_refptr
<PinPageTaskRunner
> runner(
281 new PinPageTaskRunner(title_
, url_
, best_candidate_
));
283 helper_
->FaviconDownloaderFinished();
286 void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
288 const GURL
& image_url
,
289 const std::vector
<SkBitmap
>& bitmaps
) {
290 const int kMaxIconSize
= 32;
292 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
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())
299 in_progress_requests_
.erase(iter
);
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();
306 // If the new bitmap is too big, ignore it.
307 if (iter
->height() > kMaxIconSize
|| iter
->width() > kMaxIconSize
)
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
;
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()) {
322 // Othewise it is our new best candidate.
323 best_candidate_
= *iter
;
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();
332 void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id
) {
333 in_progress_requests_
.insert(request_id
);
336 MetroPinTabHelper::MetroPinTabHelper(content::WebContents
* web_contents
)
337 : content::WebContentsObserver(web_contents
) {
340 MetroPinTabHelper::~MetroPinTabHelper() {}
342 bool MetroPinTabHelper::IsPinned() const {
343 HMODULE metro_module
= base::win::GetMetroModule();
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
) {
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;
361 void MetroPinTabHelper::TogglePinnedToStartScreen() {
363 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric
,
364 base::win::METRO_UNPIN_INITIATED
,
365 base::win::METRO_PIN_STATE_LIMIT
);
366 UnPinPageFromStartScreen();
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.
378 FaviconTabHelper
* favicon_tab_helper
= FaviconTabHelper::FromWebContents(
380 if (favicon_tab_helper
->FaviconIsValid()) {
381 // Only the 1x bitmap data is needed.
382 favicon
= favicon_tab_helper
->GetFavicon().AsImageSkia().GetRepresentation(
386 favicon_chooser_
.reset(new FaviconChooser(this, title
, url_str
, favicon
));
388 if (favicon_url_candidates_
.empty()) {
389 favicon_chooser_
->UseChosenCandidate();
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();
399 favicon_chooser_
->AddPendingRequest(
400 web_contents()->DownloadImage(iter
->icon_url
,
404 base::Bind(&MetroPinTabHelper::DidDownloadFavicon
,
405 base::Unretained(this))));
410 void MetroPinTabHelper::DidNavigateMainFrame(
411 const content::LoadCommittedDetails
& /*details*/,
412 const content::FrameNavigateParams
& /*params*/) {
413 // Cancel any outstanding pin operations once the user navigates away from
415 if (favicon_chooser_
.get())
416 favicon_chooser_
.reset();
417 // Any candidate favicons we have are now out of date so clear them.
418 favicon_url_candidates_
.clear();
421 void MetroPinTabHelper::DidUpdateFaviconURL(
422 const std::vector
<content::FaviconURL
>& candidates
) {
423 favicon_url_candidates_
= candidates
;
426 void MetroPinTabHelper::DidDownloadFavicon(
428 int http_status_code
,
429 const GURL
& image_url
,
430 const std::vector
<SkBitmap
>& bitmaps
,
431 const std::vector
<gfx::Size
>& original_bitmap_sizes
) {
432 if (favicon_chooser_
.get()) {
433 favicon_chooser_
->UpdateCandidate(id
, image_url
, bitmaps
);
437 void MetroPinTabHelper::UnPinPageFromStartScreen() {
438 HMODULE metro_module
= base::win::GetMetroModule();
442 base::win::MetroUnPinFromStartScreen metro_un_pin_from_start_screen
=
443 reinterpret_cast<base::win::MetroUnPinFromStartScreen
>(
444 ::GetProcAddress(metro_module
, "MetroUnPinFromStartScreen"));
445 if (!metro_un_pin_from_start_screen
) {
450 GURL url
= web_contents()->GetURL();
451 base::string16 tile_id
= GenerateTileId(base::UTF8ToUTF16(url
.spec()));
452 metro_un_pin_from_start_screen(tile_id
,
453 base::Bind(&PinPageReportUmaCallback
));
456 void MetroPinTabHelper::FaviconDownloaderFinished() {
457 favicon_chooser_
.reset();