Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / metro_pin_tab_helper_win.cc
blob10736d94c32352642ed6a8c2ee507dccd0ce9fdd
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"
7 #include <set>
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);
39 namespace {
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
45 // the URL.
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;
81 if (bitmap.isNull())
82 return false;
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), 1.0f,
89 true);
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
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);
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;
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))
128 return true;
130 base::FilePath default_logo_path;
131 if (!PathService::Get(base::DIR_MODULE, &default_logo_path))
132 return false;
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,
142 result,
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
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 base::string16& title,
156 const base::string16& url,
157 const SkBitmap& favicon);
159 void Run();
160 void RunOnFileThread();
162 private:
163 ~PinPageTaskRunner() {}
165 // Details of the page being pinned.
166 const base::string16 title_;
167 const base::string16 url_;
168 SkBitmap favicon_;
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)
177 : title_(title),
178 url_(url),
179 favicon_(favicon) {}
181 void PinPageTaskRunner::Run() {
182 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
184 content::BrowserThread::PostTask(
185 content::BrowserThread::FILE,
186 FROM_HERE,
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.";
197 return;
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;
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();
212 if (!metro_module)
213 return;
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;
223 metro_pin_to_start_screen(tile_id,
224 title_,
225 url_,
226 logo_path,
227 base::Bind(&PinPageReportUmaCallback));
230 } // namespace
232 class MetroPinTabHelper::FaviconChooser {
233 public:
234 FaviconChooser(MetroPinTabHelper* helper,
235 const base::string16& title,
236 const base::string16& url,
237 const SkBitmap& history_bitmap);
239 ~FaviconChooser() {}
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);
252 private:
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)
274 : helper_(helper),
275 title_(title),
276 url_(url),
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_));
283 runner->Run();
284 helper_->FaviconDownloaderFinished();
287 void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
288 int id,
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())
298 return;
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();
305 ++iter) {
307 // If the new bitmap is too big, ignore it.
308 if (iter->height() > kMaxIconSize || iter->width() > kMaxIconSize)
309 continue;
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;
314 continue;
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()) {
320 continue;
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();
345 if (!metro_module)
346 return false;
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) {
353 NOTREACHED();
354 return false;
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() {
363 if (IsPinned()) {
364 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
365 base::win::METRO_UNPIN_INITIATED,
366 base::win::METRO_PIN_STATE_LIMIT);
367 UnPinPageFromStartScreen();
368 return;
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.
378 SkBitmap favicon;
379 FaviconTabHelper* favicon_tab_helper = FaviconTabHelper::FromWebContents(
380 web_contents());
381 if (favicon_tab_helper->FaviconIsValid()) {
382 // Only the 1x bitmap data is needed.
383 favicon = favicon_tab_helper->GetFavicon().AsImageSkia().GetRepresentation(
384 1.0f).sk_bitmap();
387 favicon_chooser_.reset(new FaviconChooser(this, title, url_str, favicon));
389 if (favicon_url_candidates_.empty()) {
390 favicon_chooser_->UseChosenCandidate();
391 return;
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();
399 ++iter) {
400 favicon_chooser_->AddPendingRequest(
401 web_contents()->DownloadImage(iter->icon_url,
402 true,
403 max_image_size,
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
414 // the page.
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 int32 page_id,
423 const std::vector<content::FaviconURL>& candidates) {
424 favicon_url_candidates_ = candidates;
427 void MetroPinTabHelper::DidDownloadFavicon(
428 int id,
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();
440 if (!metro_module)
441 return;
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) {
447 NOTREACHED();
448 return;
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();