summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/metro_pin_tab_helper_win.cc
blob: fc0e13c78068909228536be039f1ebaf81b0503b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/metro_pin_tab_helper_win.h"

#include <set>

#include "base/base_paths.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "base/win/metro.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/favicon/favicon_util.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "crypto/sha2.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"

DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper);

namespace {

// Histogram name for site-specific tile pinning metrics.
const char kMetroPinMetric[] = "Metro.SecondaryTilePin";

// Generate an ID for the tile based on |url_str|. The ID is simply a hash of
// the URL.
string16 GenerateTileId(const string16& url_str) {
  uint8 hash[crypto::kSHA256Length];
  crypto::SHA256HashString(UTF16ToUTF8(url_str), hash, sizeof(hash));
  std::string hash_str = base::HexEncode(hash, sizeof(hash));
  return UTF8ToUTF16(hash_str);
}

// Get the path of the directory to store the tile logos in.
base::FilePath GetTileImagesDir() {
  base::FilePath tile_images_dir;
  if (!PathService::Get(chrome::DIR_USER_DATA, &tile_images_dir))
    return base::FilePath();

  tile_images_dir = tile_images_dir.Append(L"TileImages");
  if (!file_util::DirectoryExists(tile_images_dir) &&
      !file_util::CreateDirectory(tile_images_dir))
    return base::FilePath();

  return tile_images_dir;
}

// For the given |image| and |tile_id|, try to create a site specific logo in
// |logo_dir|. The path of any created logo is returned in |logo_path|. Return
// value indicates whether a site specific logo was created.
bool CreateSiteSpecificLogo(const SkBitmap& bitmap,
                            const string16& tile_id,
                            const base::FilePath& logo_dir,
                            base::FilePath* logo_path) {
  const int kLogoWidth = 120;
  const int kLogoHeight = 120;
  const int kBoxWidth = 40;
  const int kBoxHeight = 40;
  const int kCaptionHeight = 20;
  const double kBoxFade = 0.75;

  if (bitmap.isNull())
    return false;

  // Fill the tile logo with the dominant color of the favicon bitmap.
  SkColor dominant_color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
  SkPaint paint;
  paint.setColor(dominant_color);
  gfx::Canvas canvas(gfx::Size(kLogoWidth, kLogoHeight), ui::SCALE_FACTOR_100P,
                     true);
  canvas.DrawRect(gfx::Rect(0, 0, kLogoWidth, kLogoHeight), paint);

  // Now paint a faded square for the favicon to go in.
  color_utils::HSL shift = {-1, -1, kBoxFade};
  paint.setColor(color_utils::HSLShift(dominant_color, shift));
  int box_left = (kLogoWidth - kBoxWidth) / 2;
  int box_top = (kLogoHeight - kCaptionHeight - kBoxHeight) / 2;
  canvas.DrawRect(gfx::Rect(box_left, box_top, kBoxWidth, kBoxHeight), paint);

  // Now paint the favicon into the tile, leaving some room at the bottom for
  // the caption.
  int left = (kLogoWidth - bitmap.width()) / 2;
  int top = (kLogoHeight - kCaptionHeight - bitmap.height()) / 2;
  canvas.DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), left, top);

  SkBitmap logo_bitmap = canvas.ExtractImageRep().sk_bitmap();
  std::vector<unsigned char> logo_png;
  if (!gfx::PNGCodec::EncodeBGRASkBitmap(logo_bitmap, true, &logo_png))
    return false;

  *logo_path = logo_dir.Append(tile_id).ReplaceExtension(L".png");
  return file_util::WriteFile(*logo_path,
                              reinterpret_cast<char*>(&logo_png[0]),
                              logo_png.size()) > 0;
}

// Get the path to the backup logo. If the backup logo already exists in
// |logo_dir|, it will be used, otherwise it will be copied out of the install
// folder. (The version in the install folder is not used as it may disappear
// after an upgrade, causing tiles to lose their images if Windows rebuilds
// its tile image cache.)
// The path to the logo is returned in |logo_path|, with the return value
// indicating success.
bool GetPathToBackupLogo(const base::FilePath& logo_dir,
                         base::FilePath* logo_path) {
  const wchar_t kDefaultLogoFileName[] = L"SecondaryTile.png";
  *logo_path = logo_dir.Append(kDefaultLogoFileName);
  if (file_util::PathExists(*logo_path))
    return true;

  base::FilePath default_logo_path;
  if (!PathService::Get(base::DIR_MODULE, &default_logo_path))
    return false;

  default_logo_path = default_logo_path.Append(kDefaultLogoFileName);
  return file_util::CopyFile(default_logo_path, *logo_path);
}

// UMA reporting callback for site-specific secondary tile creation.
void PinPageReportUmaCallback(
    base::win::MetroSecondaryTilePinUmaResult result) {
  UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                            result,
                            base::win::METRO_PIN_STATE_LIMIT);
}

// The PinPageTaskRunner class performs the necessary FILE thread actions to
// pin a page, such as generating or copying the tile image file. When it
// has performed these actions it will send the tile creation request to the
// metro driver.
class PinPageTaskRunner : public base::RefCountedThreadSafe<PinPageTaskRunner> {
 public:
  // Creates a task runner for the pinning operation with the given details.
  // |favicon| can be a null image (i.e. favicon.isNull() can be true), in
  // which case the backup tile image will be used.
  PinPageTaskRunner(const string16& title,
                    const string16& url,
                    const SkBitmap& favicon);

  void Run();
  void RunOnFileThread();

 private:
  ~PinPageTaskRunner() {}

  // Details of the page being pinned.
  const string16 title_;
  const string16 url_;
  SkBitmap favicon_;

  friend class base::RefCountedThreadSafe<PinPageTaskRunner>;
  DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner);
};

PinPageTaskRunner::PinPageTaskRunner(const string16& title,
                                     const string16& url,
                                     const SkBitmap& favicon)
    : title_(title),
      url_(url),
      favicon_(favicon) {}

void PinPageTaskRunner::Run() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));

  content::BrowserThread::PostTask(
      content::BrowserThread::FILE,
      FROM_HERE,
      base::Bind(&PinPageTaskRunner::RunOnFileThread, this));
}

void PinPageTaskRunner::RunOnFileThread() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));

  string16 tile_id = GenerateTileId(url_);
  base::FilePath logo_dir = GetTileImagesDir();
  if (logo_dir.empty()) {
    LOG(ERROR) << "Could not create directory to store tile image.";
    return;
  }

  base::FilePath logo_path;
  if (!CreateSiteSpecificLogo(favicon_, tile_id, logo_dir, &logo_path) &&
      !GetPathToBackupLogo(logo_dir, &logo_path)) {
    LOG(ERROR) << "Count not get path to logo tile.";
    return;
  }

  UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                            base::win::METRO_PIN_LOGO_READY,
                            base::win::METRO_PIN_STATE_LIMIT);

  HMODULE metro_module = base::win::GetMetroModule();
  if (!metro_module)
    return;

  base::win::MetroPinToStartScreen metro_pin_to_start_screen =
      reinterpret_cast<base::win::MetroPinToStartScreen>(
          ::GetProcAddress(metro_module, "MetroPinToStartScreen"));
  if (!metro_pin_to_start_screen) {
    NOTREACHED();
    return;
  }

  metro_pin_to_start_screen(tile_id,
                            title_,
                            url_,
                            logo_path,
                            base::Bind(&PinPageReportUmaCallback));
}

}  // namespace

class MetroPinTabHelper::FaviconChooser {
 public:
  FaviconChooser(MetroPinTabHelper* helper,
                 const string16& title,
                 const string16& url,
                 const SkBitmap& history_bitmap);

  ~FaviconChooser() {}

  // Pin the page on the FILE thread using the current |best_candidate_| and
  // delete the FaviconChooser.
  void UseChosenCandidate();

  // Update the |best_candidate_| with the newly downloaded favicons provided.
  void UpdateCandidate(int id,
                       const GURL& image_url,
                       int requested_size,
                       const std::vector<SkBitmap>& bitmaps);

  void AddPendingRequest(int request_id);

 private:
  // The tab helper that this chooser is operating for.
  MetroPinTabHelper* helper_;

  // Title and URL of the page being pinned.
  const string16 title_;
  const string16 url_;

  // The best candidate we have so far for the current pin operation.
  SkBitmap best_candidate_;

  // Outstanding favicon download requests.
  std::set<int> in_progress_requests_;

  DISALLOW_COPY_AND_ASSIGN(FaviconChooser);
};

MetroPinTabHelper::FaviconChooser::FaviconChooser(
    MetroPinTabHelper* helper,
    const string16& title,
    const string16& url,
    const SkBitmap& history_bitmap)
        : helper_(helper),
          title_(title),
          url_(url),
          best_candidate_(history_bitmap) {}

void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  scoped_refptr<PinPageTaskRunner> runner(
      new PinPageTaskRunner(title_, url_, best_candidate_));
  runner->Run();
  helper_->FaviconDownloaderFinished();
}

void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
    int id,
    const GURL& image_url,
    int requested_size,
    const std::vector<SkBitmap>& bitmaps) {
  const int kMaxIconSize = 32;

  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));

  std::set<int>::iterator iter = in_progress_requests_.find(id);
  // Check that this request is one of ours.
  if (iter == in_progress_requests_.end())
    return;

  in_progress_requests_.erase(iter);

  // Process the bitmaps, keeping the one that is best so far.
  for (std::vector<SkBitmap>::const_iterator iter = bitmaps.begin();
       iter != bitmaps.end();
       ++iter) {

    // If the new bitmap is too big, ignore it.
    if (iter->height() > kMaxIconSize || iter->width() > kMaxIconSize)
      continue;

    // If we don't have a best candidate yet, this is better so just grab it.
    if (best_candidate_.isNull()) {
      best_candidate_ = *iter;
      continue;
    }

    // If it is smaller than our best one so far, ignore it.
    if (iter->height() <= best_candidate_.height() ||
        iter->width() <= best_candidate_.width()) {
      continue;
    }

    // Othewise it is our new best candidate.
    best_candidate_ = *iter;
  }

  // If there are no more outstanding requests, pin the page on the FILE thread.
  // Once this happens this downloader has done its job, so delete it.
  if (in_progress_requests_.empty())
    UseChosenCandidate();
}

void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id) {
  in_progress_requests_.insert(request_id);
}

MetroPinTabHelper::MetroPinTabHelper(content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents) {
}

MetroPinTabHelper::~MetroPinTabHelper() {}

bool MetroPinTabHelper::IsPinned() const {
  HMODULE metro_module = base::win::GetMetroModule();
  if (!metro_module)
    return false;

  typedef BOOL (*MetroIsPinnedToStartScreen)(const string16&);
  MetroIsPinnedToStartScreen metro_is_pinned_to_start_screen =
      reinterpret_cast<MetroIsPinnedToStartScreen>(
          ::GetProcAddress(metro_module, "MetroIsPinnedToStartScreen"));
  if (!metro_is_pinned_to_start_screen) {
    NOTREACHED();
    return false;
  }

  GURL url = web_contents()->GetURL();
  string16 tile_id = GenerateTileId(UTF8ToUTF16(url.spec()));
  return metro_is_pinned_to_start_screen(tile_id) != 0;
}

void MetroPinTabHelper::TogglePinnedToStartScreen() {
  if (IsPinned()) {
    UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                              base::win::METRO_UNPIN_INITIATED,
                              base::win::METRO_PIN_STATE_LIMIT);
    UnPinPageFromStartScreen();
    return;
  }

  UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                            base::win::METRO_PIN_INITIATED,
                            base::win::METRO_PIN_STATE_LIMIT);
  GURL url = web_contents()->GetURL();
  string16 url_str = UTF8ToUTF16(url.spec());
  string16 title = web_contents()->GetTitle();
  // TODO(oshima): Use scoped_ptr::Pass to pass it to other thread.
  SkBitmap favicon;
  FaviconTabHelper* favicon_tab_helper = FaviconTabHelper::FromWebContents(
      web_contents());
  if (favicon_tab_helper->FaviconIsValid()) {
    // Only the 1x bitmap data is needed.
    favicon = favicon_tab_helper->GetFavicon().AsImageSkia().GetRepresentation(
        ui::SCALE_FACTOR_100P).sk_bitmap();
  }

  favicon_chooser_.reset(new FaviconChooser(this, title, url_str, favicon));

  if (favicon_url_candidates_.empty()) {
    favicon_chooser_->UseChosenCandidate();
    return;
  }

  // Request all the candidates.
  int image_size = 0; // Request the full sized image.
  for (std::vector<content::FaviconURL>::const_iterator iter =
           favicon_url_candidates_.begin();
       iter != favicon_url_candidates_.end();
       ++iter) {
    favicon_chooser_->AddPendingRequest(
        web_contents()->DownloadFavicon(iter->icon_url,
            true,
            image_size,
            base::Bind(&MetroPinTabHelper::DidDownloadFavicon,
                       base::Unretained(this))));
  }

}

void MetroPinTabHelper::DidNavigateMainFrame(
    const content::LoadCommittedDetails& /*details*/,
    const content::FrameNavigateParams& /*params*/) {
  // Cancel any outstanding pin operations once the user navigates away from
  // the page.
  if (favicon_chooser_.get())
    favicon_chooser_.reset();
  // Any candidate favicons we have are now out of date so clear them.
  favicon_url_candidates_.clear();
}

void MetroPinTabHelper::DidUpdateFaviconURL(
    int32 page_id,
    const std::vector<content::FaviconURL>& candidates) {
  favicon_url_candidates_ = candidates;
}

void MetroPinTabHelper::DidDownloadFavicon(
    int id,
    const GURL& image_url,
    int requested_size,
    const std::vector<SkBitmap>& bitmaps) {
  if (favicon_chooser_.get()) {
    favicon_chooser_->UpdateCandidate(id, image_url, requested_size, bitmaps);
  }
}

void MetroPinTabHelper::UnPinPageFromStartScreen() {
  HMODULE metro_module = base::win::GetMetroModule();
  if (!metro_module)
    return;

  base::win::MetroUnPinFromStartScreen metro_un_pin_from_start_screen =
      reinterpret_cast<base::win::MetroUnPinFromStartScreen>(
          ::GetProcAddress(metro_module, "MetroUnPinFromStartScreen"));
  if (!metro_un_pin_from_start_screen) {
    NOTREACHED();
    return;
  }

  GURL url = web_contents()->GetURL();
  string16 tile_id = GenerateTileId(UTF8ToUTF16(url.spec()));
  metro_un_pin_from_start_screen(tile_id,
                                 base::Bind(&PinPageReportUmaCallback));
}

void MetroPinTabHelper::FaviconDownloaderFinished() {
  favicon_chooser_.reset();
}