Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / metro_pin_tab_helper_win.cc
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/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"
36
37 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper);
38
39 namespace {
40
41 // Histogram name for site-specific tile pinning metrics.
42 const char kMetroPinMetric[] = "Metro.SecondaryTilePin";
43
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);
51 }
52
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();
58
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();
63
64   return tile_images_dir;
65 }
66
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;
80
81   if (bitmap.isNull())
82     return false;
83
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);
91
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);
98
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);
104
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;
109
110   *logo_path = logo_dir.Append(tile_id).ReplaceExtension(L".png");
111   return base::WriteFile(*logo_path,
112                          reinterpret_cast<char*>(&logo_png[0]),
113                          logo_png.size()) > 0;
114 }
115
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;
129
130   base::FilePath default_logo_path;
131   if (!PathService::Get(base::DIR_MODULE, &default_logo_path))
132     return false;
133
134   default_logo_path = default_logo_path.Append(kDefaultLogoFileName);
135   return base::CopyFile(default_logo_path, *logo_path);
136 }
137
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);
144 }
145
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);
158
159   void Run();
160   void RunOnFileThread();
161
162  private:
163   ~PinPageTaskRunner() {}
164
165   // Details of the page being pinned.
166   const base::string16 title_;
167   const base::string16 url_;
168   SkBitmap favicon_;
169
170   friend class base::RefCountedThreadSafe<PinPageTaskRunner>;
171   DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner);
172 };
173
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) {}
180
181 void PinPageTaskRunner::Run() {
182   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
183
184   content::BrowserThread::PostTask(
185       content::BrowserThread::FILE,
186       FROM_HERE,
187       base::Bind(&PinPageTaskRunner::RunOnFileThread, this));
188 }
189
190 void PinPageTaskRunner::RunOnFileThread() {
191   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
192
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;
198   }
199
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;
205   }
206
207   UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
208                             base::win::METRO_PIN_LOGO_READY,
209                             base::win::METRO_PIN_STATE_LIMIT);
210
211   HMODULE metro_module = base::win::GetMetroModule();
212   if (!metro_module)
213     return;
214
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;
221   }
222
223   metro_pin_to_start_screen(tile_id,
224                             title_,
225                             url_,
226                             logo_path,
227                             base::Bind(&PinPageReportUmaCallback));
228 }
229
230 }  // namespace
231
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);
238
239   ~FaviconChooser() {}
240
241   // Pin the page on the FILE thread using the current |best_candidate_| and
242   // delete the FaviconChooser.
243   void UseChosenCandidate();
244
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);
249
250   void AddPendingRequest(int request_id);
251
252  private:
253   // The tab helper that this chooser is operating for.
254   MetroPinTabHelper* helper_;
255
256   // Title and URL of the page being pinned.
257   const base::string16 title_;
258   const base::string16 url_;
259
260   // The best candidate we have so far for the current pin operation.
261   SkBitmap best_candidate_;
262
263   // Outstanding favicon download requests.
264   std::set<int> in_progress_requests_;
265
266   DISALLOW_COPY_AND_ASSIGN(FaviconChooser);
267 };
268
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) {}
278
279 void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() {
280   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
281   scoped_refptr<PinPageTaskRunner> runner(
282       new PinPageTaskRunner(title_, url_, best_candidate_));
283   runner->Run();
284   helper_->FaviconDownloaderFinished();
285 }
286
287 void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
288     int id,
289     const GURL& image_url,
290     const std::vector<SkBitmap>& bitmaps) {
291   const int kMaxIconSize = 32;
292
293   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
294
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;
299
300   in_progress_requests_.erase(iter);
301
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) {
306
307     // If the new bitmap is too big, ignore it.
308     if (iter->height() > kMaxIconSize || iter->width() > kMaxIconSize)
309       continue;
310
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;
315     }
316
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;
321     }
322
323     // Othewise it is our new best candidate.
324     best_candidate_ = *iter;
325   }
326
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();
331 }
332
333 void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id) {
334   in_progress_requests_.insert(request_id);
335 }
336
337 MetroPinTabHelper::MetroPinTabHelper(content::WebContents* web_contents)
338     : content::WebContentsObserver(web_contents) {
339 }
340
341 MetroPinTabHelper::~MetroPinTabHelper() {}
342
343 bool MetroPinTabHelper::IsPinned() const {
344   HMODULE metro_module = base::win::GetMetroModule();
345   if (!metro_module)
346     return false;
347
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;
355   }
356
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;
360 }
361
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;
369   }
370
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();
385   }
386
387   favicon_chooser_.reset(new FaviconChooser(this, title, url_str, favicon));
388
389   if (favicon_url_candidates_.empty()) {
390     favicon_chooser_->UseChosenCandidate();
391     return;
392   }
393
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))));
406   }
407
408 }
409
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();
419 }
420
421 void MetroPinTabHelper::DidUpdateFaviconURL(
422     const std::vector<content::FaviconURL>& candidates) {
423   favicon_url_candidates_ = candidates;
424 }
425
426 void MetroPinTabHelper::DidDownloadFavicon(
427     int id,
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);
434   }
435 }
436
437 void MetroPinTabHelper::UnPinPageFromStartScreen() {
438   HMODULE metro_module = base::win::GetMetroModule();
439   if (!metro_module)
440     return;
441
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) {
446     NOTREACHED();
447     return;
448   }
449
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));
454 }
455
456 void MetroPinTabHelper::FaviconDownloaderFinished() {
457   favicon_chooser_.reset();
458 }