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/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"
37 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper
);
41 // Histogram name for site-specific tile pinning metrics.
42 const char kMetroPinMetric
[] = "Metro.SecondaryTilePin";
44 // Generate an ID for the tile based on |url_str|. The ID is simply a hash of
46 base::string16
GenerateTileId(const base::string16
& url_str
) {
47 uint8 hash
[crypto::kSHA256Length
];
48 crypto::SHA256HashString(base::UTF16ToUTF8(url_str
), hash
, sizeof(hash
));
49 std::string hash_str
= base::HexEncode(hash
, sizeof(hash
));
50 return base::UTF8ToUTF16(hash_str
);
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();
59 tile_images_dir
= tile_images_dir
.Append(L
"TileImages");
60 if (!base::DirectoryExists(tile_images_dir
) &&
61 !base::CreateDirectory(tile_images_dir
))
62 return base::FilePath();
64 return tile_images_dir
;
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 base::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;
84 // Fill the tile logo with the dominant color of the favicon bitmap.
85 SkColor dominant_color
= color_utils::CalculateKMeanColorOfBitmap(bitmap
);
87 paint
.setColor(dominant_color
);
88 gfx::Canvas
canvas(gfx::Size(kLogoWidth
, kLogoHeight
), 1.0f
,
90 canvas
.DrawRect(gfx::Rect(0, 0, kLogoWidth
, kLogoHeight
), paint
);
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
);
99 // Now paint the favicon into the tile, leaving some room at the bottom for
101 int left
= (kLogoWidth
- bitmap
.width()) / 2;
102 int top
= (kLogoHeight
- kCaptionHeight
- bitmap
.height()) / 2;
103 canvas
.DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap
), left
, top
);
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
))
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;
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
))
130 base::FilePath default_logo_path
;
131 if (!PathService::Get(base::DIR_MODULE
, &default_logo_path
))
134 default_logo_path
= default_logo_path
.Append(kDefaultLogoFileName
);
135 return base::CopyFile(default_logo_path
, *logo_path
);
138 // UMA reporting callback for site-specific secondary tile creation.
139 void PinPageReportUmaCallback(
140 base::win::MetroSecondaryTilePinUmaResult result
) {
141 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric
,
143 base::win::METRO_PIN_STATE_LIMIT
);
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
150 class PinPageTaskRunner
: public base::RefCountedThreadSafe
<PinPageTaskRunner
> {
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 base::string16
& title
,
156 const base::string16
& url
,
157 const SkBitmap
& favicon
);
160 void RunOnFileThread();
163 ~PinPageTaskRunner() {}
165 // Details of the page being pinned.
166 const base::string16 title_
;
167 const base::string16 url_
;
170 friend class base::RefCountedThreadSafe
<PinPageTaskRunner
>;
171 DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner
);
174 PinPageTaskRunner::PinPageTaskRunner(const base::string16
& title
,
175 const base::string16
& url
,
176 const SkBitmap
& favicon
)
181 void PinPageTaskRunner::Run() {
182 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
184 content::BrowserThread::PostTask(
185 content::BrowserThread::FILE,
187 base::Bind(&PinPageTaskRunner::RunOnFileThread
, this));
190 void PinPageTaskRunner::RunOnFileThread() {
191 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
193 base::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.";
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.";
207 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric
,
208 base::win::METRO_PIN_LOGO_READY
,
209 base::win::METRO_PIN_STATE_LIMIT
);
211 HMODULE metro_module
= base::win::GetMetroModule();
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
) {
223 metro_pin_to_start_screen(tile_id
,
227 base::Bind(&PinPageReportUmaCallback
));
232 class MetroPinTabHelper::FaviconChooser
{
234 FaviconChooser(MetroPinTabHelper
* helper
,
235 const base::string16
& title
,
236 const base::string16
& url
,
237 const SkBitmap
& history_bitmap
);
241 // Pin the page on the FILE thread using the current |best_candidate_| and
242 // delete the FaviconChooser.
243 void UseChosenCandidate();
245 // Update the |best_candidate_| with the newly downloaded favicons provided.
246 void UpdateCandidate(int id
,
247 const GURL
& image_url
,
248 const std::vector
<SkBitmap
>& bitmaps
);
250 void AddPendingRequest(int request_id
);
253 // The tab helper that this chooser is operating for.
254 MetroPinTabHelper
* helper_
;
256 // Title and URL of the page being pinned.
257 const base::string16 title_
;
258 const base::string16 url_
;
260 // The best candidate we have so far for the current pin operation.
261 SkBitmap best_candidate_
;
263 // Outstanding favicon download requests.
264 std::set
<int> in_progress_requests_
;
266 DISALLOW_COPY_AND_ASSIGN(FaviconChooser
);
269 MetroPinTabHelper::FaviconChooser::FaviconChooser(
270 MetroPinTabHelper
* helper
,
271 const base::string16
& title
,
272 const base::string16
& url
,
273 const SkBitmap
& history_bitmap
)
277 best_candidate_(history_bitmap
) {}
279 void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() {
280 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
281 scoped_refptr
<PinPageTaskRunner
> runner(
282 new PinPageTaskRunner(title_
, url_
, best_candidate_
));
284 helper_
->FaviconDownloaderFinished();
287 void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
289 const GURL
& image_url
,
290 const std::vector
<SkBitmap
>& bitmaps
) {
291 const int kMaxIconSize
= 32;
293 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
295 std::set
<int>::iterator iter
= in_progress_requests_
.find(id
);
296 // Check that this request is one of ours.
297 if (iter
== in_progress_requests_
.end())
300 in_progress_requests_
.erase(iter
);
302 // Process the bitmaps, keeping the one that is best so far.
303 for (std::vector
<SkBitmap
>::const_iterator iter
= bitmaps
.begin();
304 iter
!= bitmaps
.end();
307 // If the new bitmap is too big, ignore it.
308 if (iter
->height() > kMaxIconSize
|| iter
->width() > kMaxIconSize
)
311 // If we don't have a best candidate yet, this is better so just grab it.
312 if (best_candidate_
.isNull()) {
313 best_candidate_
= *iter
;
317 // If it is smaller than our best one so far, ignore it.
318 if (iter
->height() <= best_candidate_
.height() ||
319 iter
->width() <= best_candidate_
.width()) {
323 // Othewise it is our new best candidate.
324 best_candidate_
= *iter
;
327 // If there are no more outstanding requests, pin the page on the FILE thread.
328 // Once this happens this downloader has done its job, so delete it.
329 if (in_progress_requests_
.empty())
330 UseChosenCandidate();
333 void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id
) {
334 in_progress_requests_
.insert(request_id
);
337 MetroPinTabHelper::MetroPinTabHelper(content::WebContents
* web_contents
)
338 : content::WebContentsObserver(web_contents
) {
341 MetroPinTabHelper::~MetroPinTabHelper() {}
343 bool MetroPinTabHelper::IsPinned() const {
344 HMODULE metro_module
= base::win::GetMetroModule();
348 typedef BOOL (*MetroIsPinnedToStartScreen
)(const base::string16
&);
349 MetroIsPinnedToStartScreen metro_is_pinned_to_start_screen
=
350 reinterpret_cast<MetroIsPinnedToStartScreen
>(
351 ::GetProcAddress(metro_module
, "MetroIsPinnedToStartScreen"));
352 if (!metro_is_pinned_to_start_screen
) {
357 GURL url
= web_contents()->GetURL();
358 base::string16 tile_id
= GenerateTileId(base::UTF8ToUTF16(url
.spec()));
359 return metro_is_pinned_to_start_screen(tile_id
) != 0;
362 void MetroPinTabHelper::TogglePinnedToStartScreen() {
364 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric
,
365 base::win::METRO_UNPIN_INITIATED
,
366 base::win::METRO_PIN_STATE_LIMIT
);
367 UnPinPageFromStartScreen();
371 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric
,
372 base::win::METRO_PIN_INITIATED
,
373 base::win::METRO_PIN_STATE_LIMIT
);
374 GURL url
= web_contents()->GetURL();
375 base::string16 url_str
= base::UTF8ToUTF16(url
.spec());
376 base::string16 title
= web_contents()->GetTitle();
377 // TODO(oshima): Use scoped_ptr::Pass to pass it to other thread.
379 FaviconTabHelper
* favicon_tab_helper
= FaviconTabHelper::FromWebContents(
381 if (favicon_tab_helper
->FaviconIsValid()) {
382 // Only the 1x bitmap data is needed.
383 favicon
= favicon_tab_helper
->GetFavicon().AsImageSkia().GetRepresentation(
387 favicon_chooser_
.reset(new FaviconChooser(this, title
, url_str
, favicon
));
389 if (favicon_url_candidates_
.empty()) {
390 favicon_chooser_
->UseChosenCandidate();
394 // Request all the candidates.
395 int max_image_size
= 0; // Do not resize images.
396 for (std::vector
<content::FaviconURL
>::const_iterator iter
=
397 favicon_url_candidates_
.begin();
398 iter
!= favicon_url_candidates_
.end();
400 favicon_chooser_
->AddPendingRequest(
401 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(
423 const std::vector
<content::FaviconURL
>& candidates
) {
424 favicon_url_candidates_
= candidates
;
427 void MetroPinTabHelper::DidDownloadFavicon(
429 int http_status_code
,
430 const GURL
& image_url
,
431 const std::vector
<SkBitmap
>& bitmaps
,
432 const std::vector
<gfx::Size
>& original_bitmap_sizes
) {
433 if (favicon_chooser_
.get()) {
434 favicon_chooser_
->UpdateCandidate(id
, image_url
, bitmaps
);
438 void MetroPinTabHelper::UnPinPageFromStartScreen() {
439 HMODULE metro_module
= base::win::GetMetroModule();
443 base::win::MetroUnPinFromStartScreen metro_un_pin_from_start_screen
=
444 reinterpret_cast<base::win::MetroUnPinFromStartScreen
>(
445 ::GetProcAddress(metro_module
, "MetroUnPinFromStartScreen"));
446 if (!metro_un_pin_from_start_screen
) {
451 GURL url
= web_contents()->GetURL();
452 base::string16 tile_id
= GenerateTileId(base::UTF8ToUTF16(url
.spec()));
453 metro_un_pin_from_start_screen(tile_id
,
454 base::Bind(&PinPageReportUmaCallback
));
457 void MetroPinTabHelper::FaviconDownloaderFinished() {
458 favicon_chooser_
.reset();