| OLD | NEW |
| (Empty) |
| 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 <stdint.h> | |
| 8 | |
| 9 #include <set> | |
| 10 | |
| 11 #include "base/base_paths.h" | |
| 12 #include "base/bind.h" | |
| 13 #include "base/files/file_path.h" | |
| 14 #include "base/files/file_util.h" | |
| 15 #include "base/logging.h" | |
| 16 #include "base/macros.h" | |
| 17 #include "base/memory/ref_counted.h" | |
| 18 #include "base/memory/ref_counted_memory.h" | |
| 19 #include "base/metrics/histogram.h" | |
| 20 #include "base/path_service.h" | |
| 21 #include "base/strings/string_number_conversions.h" | |
| 22 #include "base/strings/utf_string_conversions.h" | |
| 23 #include "base/win/metro.h" | |
| 24 #include "chrome/common/chrome_paths.h" | |
| 25 #include "components/favicon/content/content_favicon_driver.h" | |
| 26 #include "content/public/browser/browser_thread.h" | |
| 27 #include "content/public/browser/web_contents.h" | |
| 28 #include "crypto/sha2.h" | |
| 29 #include "third_party/skia/include/core/SkCanvas.h" | |
| 30 #include "third_party/skia/include/core/SkColor.h" | |
| 31 #include "ui/gfx/canvas.h" | |
| 32 #include "ui/gfx/codec/png_codec.h" | |
| 33 #include "ui/gfx/color_analysis.h" | |
| 34 #include "ui/gfx/color_utils.h" | |
| 35 #include "ui/gfx/geometry/rect.h" | |
| 36 #include "ui/gfx/geometry/size.h" | |
| 37 #include "ui/gfx/image/image.h" | |
| 38 | |
| 39 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper); | |
| 40 | |
| 41 namespace { | |
| 42 | |
| 43 // Histogram name for site-specific tile pinning metrics. | |
| 44 const char kMetroPinMetric[] = "Metro.SecondaryTilePin"; | |
| 45 | |
| 46 // Generate an ID for the tile based on |url_str|. The ID is simply a hash of | |
| 47 // the URL. | |
| 48 base::string16 GenerateTileId(const base::string16& url_str) { | |
| 49 uint8_t hash[crypto::kSHA256Length]; | |
| 50 crypto::SHA256HashString(base::UTF16ToUTF8(url_str), hash, sizeof(hash)); | |
| 51 std::string hash_str = base::HexEncode(hash, sizeof(hash)); | |
| 52 return base::UTF8ToUTF16(hash_str); | |
| 53 } | |
| 54 | |
| 55 // Get the path of the directory to store the tile logos in. | |
| 56 base::FilePath GetTileImagesDir() { | |
| 57 base::FilePath tile_images_dir; | |
| 58 if (!PathService::Get(chrome::DIR_USER_DATA, &tile_images_dir)) | |
| 59 return base::FilePath(); | |
| 60 | |
| 61 tile_images_dir = tile_images_dir.Append(L"TileImages"); | |
| 62 if (!base::DirectoryExists(tile_images_dir) && | |
| 63 !base::CreateDirectory(tile_images_dir)) | |
| 64 return base::FilePath(); | |
| 65 | |
| 66 return tile_images_dir; | |
| 67 } | |
| 68 | |
| 69 // For the given |image| and |tile_id|, try to create a site specific logo in | |
| 70 // |logo_dir|. The path of any created logo is returned in |logo_path|. Return | |
| 71 // value indicates whether a site specific logo was created. | |
| 72 bool CreateSiteSpecificLogo(const SkBitmap& bitmap, | |
| 73 const base::string16& tile_id, | |
| 74 const base::FilePath& logo_dir, | |
| 75 base::FilePath* logo_path) { | |
| 76 const int kLogoWidth = 120; | |
| 77 const int kLogoHeight = 120; | |
| 78 const int kBoxWidth = 40; | |
| 79 const int kBoxHeight = 40; | |
| 80 const int kCaptionHeight = 20; | |
| 81 const double kBoxFade = 0.75; | |
| 82 | |
| 83 if (bitmap.isNull()) | |
| 84 return false; | |
| 85 | |
| 86 // Fill the tile logo with the dominant color of the favicon bitmap. | |
| 87 SkColor dominant_color = color_utils::CalculateKMeanColorOfBitmap(bitmap); | |
| 88 SkPaint paint; | |
| 89 paint.setColor(dominant_color); | |
| 90 gfx::Canvas canvas(gfx::Size(kLogoWidth, kLogoHeight), 1.0f, | |
| 91 true); | |
| 92 canvas.DrawRect(gfx::Rect(0, 0, kLogoWidth, kLogoHeight), paint); | |
| 93 | |
| 94 // Now paint a faded square for the favicon to go in. | |
| 95 color_utils::HSL shift = {-1, -1, kBoxFade}; | |
| 96 paint.setColor(color_utils::HSLShift(dominant_color, shift)); | |
| 97 int box_left = (kLogoWidth - kBoxWidth) / 2; | |
| 98 int box_top = (kLogoHeight - kCaptionHeight - kBoxHeight) / 2; | |
| 99 canvas.DrawRect(gfx::Rect(box_left, box_top, kBoxWidth, kBoxHeight), paint); | |
| 100 | |
| 101 // Now paint the favicon into the tile, leaving some room at the bottom for | |
| 102 // the caption. | |
| 103 int left = (kLogoWidth - bitmap.width()) / 2; | |
| 104 int top = (kLogoHeight - kCaptionHeight - bitmap.height()) / 2; | |
| 105 canvas.DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), left, top); | |
| 106 | |
| 107 SkBitmap logo_bitmap = canvas.ExtractImageRep().sk_bitmap(); | |
| 108 std::vector<unsigned char> logo_png; | |
| 109 if (!gfx::PNGCodec::EncodeBGRASkBitmap(logo_bitmap, true, &logo_png)) | |
| 110 return false; | |
| 111 | |
| 112 *logo_path = logo_dir.Append(tile_id).ReplaceExtension(L".png"); | |
| 113 return base::WriteFile(*logo_path, | |
| 114 reinterpret_cast<char*>(&logo_png[0]), | |
| 115 logo_png.size()) > 0; | |
| 116 } | |
| 117 | |
| 118 // Get the path to the backup logo. If the backup logo already exists in | |
| 119 // |logo_dir|, it will be used, otherwise it will be copied out of the install | |
| 120 // folder. (The version in the install folder is not used as it may disappear | |
| 121 // after an upgrade, causing tiles to lose their images if Windows rebuilds | |
| 122 // its tile image cache.) | |
| 123 // The path to the logo is returned in |logo_path|, with the return value | |
| 124 // indicating success. | |
| 125 bool GetPathToBackupLogo(const base::FilePath& logo_dir, | |
| 126 base::FilePath* logo_path) { | |
| 127 const wchar_t kDefaultLogoFileName[] = L"SecondaryTile.png"; | |
| 128 *logo_path = logo_dir.Append(kDefaultLogoFileName); | |
| 129 if (base::PathExists(*logo_path)) | |
| 130 return true; | |
| 131 | |
| 132 base::FilePath default_logo_path; | |
| 133 if (!PathService::Get(base::DIR_MODULE, &default_logo_path)) | |
| 134 return false; | |
| 135 | |
| 136 default_logo_path = default_logo_path.Append(kDefaultLogoFileName); | |
| 137 return base::CopyFile(default_logo_path, *logo_path); | |
| 138 } | |
| 139 | |
| 140 // UMA reporting callback for site-specific secondary tile creation. | |
| 141 void PinPageReportUmaCallback( | |
| 142 base::win::MetroSecondaryTilePinUmaResult result) { | |
| 143 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric, | |
| 144 result, | |
| 145 base::win::METRO_PIN_STATE_LIMIT); | |
| 146 } | |
| 147 | |
| 148 // The PinPageTaskRunner class performs the necessary FILE thread actions to | |
| 149 // pin a page, such as generating or copying the tile image file. When it | |
| 150 // has performed these actions it will send the tile creation request to the | |
| 151 // metro driver. | |
| 152 class PinPageTaskRunner : public base::RefCountedThreadSafe<PinPageTaskRunner> { | |
| 153 public: | |
| 154 // Creates a task runner for the pinning operation with the given details. | |
| 155 // |favicon| can be a null image (i.e. favicon.isNull() can be true), in | |
| 156 // which case the backup tile image will be used. | |
| 157 PinPageTaskRunner(const base::string16& title, | |
| 158 const base::string16& url, | |
| 159 const SkBitmap& favicon); | |
| 160 | |
| 161 void Run(); | |
| 162 void RunOnFileThread(); | |
| 163 | |
| 164 private: | |
| 165 ~PinPageTaskRunner() {} | |
| 166 | |
| 167 // Details of the page being pinned. | |
| 168 const base::string16 title_; | |
| 169 const base::string16 url_; | |
| 170 SkBitmap favicon_; | |
| 171 | |
| 172 friend class base::RefCountedThreadSafe<PinPageTaskRunner>; | |
| 173 DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner); | |
| 174 }; | |
| 175 | |
| 176 PinPageTaskRunner::PinPageTaskRunner(const base::string16& title, | |
| 177 const base::string16& url, | |
| 178 const SkBitmap& favicon) | |
| 179 : title_(title), | |
| 180 url_(url), | |
| 181 favicon_(favicon) {} | |
| 182 | |
| 183 void PinPageTaskRunner::Run() { | |
| 184 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 185 | |
| 186 content::BrowserThread::PostTask( | |
| 187 content::BrowserThread::FILE, | |
| 188 FROM_HERE, | |
| 189 base::Bind(&PinPageTaskRunner::RunOnFileThread, this)); | |
| 190 } | |
| 191 | |
| 192 void PinPageTaskRunner::RunOnFileThread() { | |
| 193 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); | |
| 194 | |
| 195 base::string16 tile_id = GenerateTileId(url_); | |
| 196 base::FilePath logo_dir = GetTileImagesDir(); | |
| 197 if (logo_dir.empty()) { | |
| 198 LOG(ERROR) << "Could not create directory to store tile image."; | |
| 199 return; | |
| 200 } | |
| 201 | |
| 202 base::FilePath logo_path; | |
| 203 if (!CreateSiteSpecificLogo(favicon_, tile_id, logo_dir, &logo_path) && | |
| 204 !GetPathToBackupLogo(logo_dir, &logo_path)) { | |
| 205 LOG(ERROR) << "Count not get path to logo tile."; | |
| 206 return; | |
| 207 } | |
| 208 | |
| 209 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric, | |
| 210 base::win::METRO_PIN_LOGO_READY, | |
| 211 base::win::METRO_PIN_STATE_LIMIT); | |
| 212 | |
| 213 HMODULE metro_module = base::win::GetMetroModule(); | |
| 214 if (!metro_module) | |
| 215 return; | |
| 216 | |
| 217 base::win::MetroPinToStartScreen metro_pin_to_start_screen = | |
| 218 reinterpret_cast<base::win::MetroPinToStartScreen>( | |
| 219 ::GetProcAddress(metro_module, "MetroPinToStartScreen")); | |
| 220 if (!metro_pin_to_start_screen) { | |
| 221 NOTREACHED(); | |
| 222 return; | |
| 223 } | |
| 224 | |
| 225 metro_pin_to_start_screen(tile_id, | |
| 226 title_, | |
| 227 url_, | |
| 228 logo_path, | |
| 229 base::Bind(&PinPageReportUmaCallback)); | |
| 230 } | |
| 231 | |
| 232 } // namespace | |
| 233 | |
| 234 class MetroPinTabHelper::FaviconChooser { | |
| 235 public: | |
| 236 FaviconChooser(MetroPinTabHelper* helper, | |
| 237 const base::string16& title, | |
| 238 const base::string16& url, | |
| 239 const SkBitmap& history_bitmap); | |
| 240 | |
| 241 ~FaviconChooser() {} | |
| 242 | |
| 243 // Pin the page on the FILE thread using the current |best_candidate_| and | |
| 244 // delete the FaviconChooser. | |
| 245 void UseChosenCandidate(); | |
| 246 | |
| 247 // Update the |best_candidate_| with the newly downloaded favicons provided. | |
| 248 void UpdateCandidate(int id, | |
| 249 const GURL& image_url, | |
| 250 const std::vector<SkBitmap>& bitmaps); | |
| 251 | |
| 252 void AddPendingRequest(int request_id); | |
| 253 | |
| 254 private: | |
| 255 // The tab helper that this chooser is operating for. | |
| 256 MetroPinTabHelper* helper_; | |
| 257 | |
| 258 // Title and URL of the page being pinned. | |
| 259 const base::string16 title_; | |
| 260 const base::string16 url_; | |
| 261 | |
| 262 // The best candidate we have so far for the current pin operation. | |
| 263 SkBitmap best_candidate_; | |
| 264 | |
| 265 // Outstanding favicon download requests. | |
| 266 std::set<int> in_progress_requests_; | |
| 267 | |
| 268 DISALLOW_COPY_AND_ASSIGN(FaviconChooser); | |
| 269 }; | |
| 270 | |
| 271 MetroPinTabHelper::FaviconChooser::FaviconChooser( | |
| 272 MetroPinTabHelper* helper, | |
| 273 const base::string16& title, | |
| 274 const base::string16& url, | |
| 275 const SkBitmap& history_bitmap) | |
| 276 : helper_(helper), | |
| 277 title_(title), | |
| 278 url_(url), | |
| 279 best_candidate_(history_bitmap) {} | |
| 280 | |
| 281 void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() { | |
| 282 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 283 scoped_refptr<PinPageTaskRunner> runner( | |
| 284 new PinPageTaskRunner(title_, url_, best_candidate_)); | |
| 285 runner->Run(); | |
| 286 helper_->FaviconDownloaderFinished(); | |
| 287 } | |
| 288 | |
| 289 void MetroPinTabHelper::FaviconChooser::UpdateCandidate( | |
| 290 int id, | |
| 291 const GURL& image_url, | |
| 292 const std::vector<SkBitmap>& bitmaps) { | |
| 293 const int kMaxIconSize = 32; | |
| 294 | |
| 295 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 296 | |
| 297 std::set<int>::iterator iter = in_progress_requests_.find(id); | |
| 298 // Check that this request is one of ours. | |
| 299 if (iter == in_progress_requests_.end()) | |
| 300 return; | |
| 301 | |
| 302 in_progress_requests_.erase(iter); | |
| 303 | |
| 304 // Process the bitmaps, keeping the one that is best so far. | |
| 305 for (std::vector<SkBitmap>::const_iterator iter = bitmaps.begin(); | |
| 306 iter != bitmaps.end(); | |
| 307 ++iter) { | |
| 308 | |
| 309 // If the new bitmap is too big, ignore it. | |
| 310 if (iter->height() > kMaxIconSize || iter->width() > kMaxIconSize) | |
| 311 continue; | |
| 312 | |
| 313 // If we don't have a best candidate yet, this is better so just grab it. | |
| 314 if (best_candidate_.isNull()) { | |
| 315 best_candidate_ = *iter; | |
| 316 continue; | |
| 317 } | |
| 318 | |
| 319 // If it is smaller than our best one so far, ignore it. | |
| 320 if (iter->height() <= best_candidate_.height() || | |
| 321 iter->width() <= best_candidate_.width()) { | |
| 322 continue; | |
| 323 } | |
| 324 | |
| 325 // Othewise it is our new best candidate. | |
| 326 best_candidate_ = *iter; | |
| 327 } | |
| 328 | |
| 329 // If there are no more outstanding requests, pin the page on the FILE thread. | |
| 330 // Once this happens this downloader has done its job, so delete it. | |
| 331 if (in_progress_requests_.empty()) | |
| 332 UseChosenCandidate(); | |
| 333 } | |
| 334 | |
| 335 void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id) { | |
| 336 in_progress_requests_.insert(request_id); | |
| 337 } | |
| 338 | |
| 339 MetroPinTabHelper::MetroPinTabHelper(content::WebContents* web_contents) | |
| 340 : content::WebContentsObserver(web_contents) { | |
| 341 } | |
| 342 | |
| 343 MetroPinTabHelper::~MetroPinTabHelper() {} | |
| 344 | |
| 345 bool MetroPinTabHelper::IsPinned() const { | |
| 346 HMODULE metro_module = base::win::GetMetroModule(); | |
| 347 if (!metro_module) | |
| 348 return false; | |
| 349 | |
| 350 typedef BOOL (*MetroIsPinnedToStartScreen)(const base::string16&); | |
| 351 MetroIsPinnedToStartScreen metro_is_pinned_to_start_screen = | |
| 352 reinterpret_cast<MetroIsPinnedToStartScreen>( | |
| 353 ::GetProcAddress(metro_module, "MetroIsPinnedToStartScreen")); | |
| 354 if (!metro_is_pinned_to_start_screen) { | |
| 355 NOTREACHED(); | |
| 356 return false; | |
| 357 } | |
| 358 | |
| 359 GURL url = web_contents()->GetURL(); | |
| 360 base::string16 tile_id = GenerateTileId(base::UTF8ToUTF16(url.spec())); | |
| 361 return metro_is_pinned_to_start_screen(tile_id) != 0; | |
| 362 } | |
| 363 | |
| 364 void MetroPinTabHelper::TogglePinnedToStartScreen() { | |
| 365 if (IsPinned()) { | |
| 366 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric, | |
| 367 base::win::METRO_UNPIN_INITIATED, | |
| 368 base::win::METRO_PIN_STATE_LIMIT); | |
| 369 UnPinPageFromStartScreen(); | |
| 370 return; | |
| 371 } | |
| 372 | |
| 373 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric, | |
| 374 base::win::METRO_PIN_INITIATED, | |
| 375 base::win::METRO_PIN_STATE_LIMIT); | |
| 376 GURL url = web_contents()->GetURL(); | |
| 377 base::string16 url_str = base::UTF8ToUTF16(url.spec()); | |
| 378 base::string16 title = web_contents()->GetTitle(); | |
| 379 // TODO(oshima): Use scoped_ptr::Pass to pass it to other thread. | |
| 380 SkBitmap favicon; | |
| 381 favicon::FaviconDriver* favicon_driver = | |
| 382 favicon::ContentFaviconDriver::FromWebContents(web_contents()); | |
| 383 if (favicon_driver->FaviconIsValid()) { | |
| 384 // Only the 1x bitmap data is needed. | |
| 385 favicon = favicon_driver->GetFavicon() | |
| 386 .AsImageSkia() | |
| 387 .GetRepresentation(1.0f) | |
| 388 .sk_bitmap(); | |
| 389 } | |
| 390 | |
| 391 favicon_chooser_.reset(new FaviconChooser(this, title, url_str, favicon)); | |
| 392 | |
| 393 if (favicon_url_candidates_.empty()) { | |
| 394 favicon_chooser_->UseChosenCandidate(); | |
| 395 return; | |
| 396 } | |
| 397 | |
| 398 // Request all the candidates. | |
| 399 int max_image_size = 0; // Do not resize images. | |
| 400 for (std::vector<content::FaviconURL>::const_iterator iter = | |
| 401 favicon_url_candidates_.begin(); | |
| 402 iter != favicon_url_candidates_.end(); | |
| 403 ++iter) { | |
| 404 favicon_chooser_->AddPendingRequest( | |
| 405 web_contents()->DownloadImage(iter->icon_url, | |
| 406 true, | |
| 407 max_image_size, | |
| 408 false, | |
| 409 base::Bind(&MetroPinTabHelper::DidDownloadFavicon, | |
| 410 base::Unretained(this)))); | |
| 411 } | |
| 412 | |
| 413 } | |
| 414 | |
| 415 void MetroPinTabHelper::DidNavigateMainFrame( | |
| 416 const content::LoadCommittedDetails& /*details*/, | |
| 417 const content::FrameNavigateParams& /*params*/) { | |
| 418 // Cancel any outstanding pin operations once the user navigates away from | |
| 419 // the page. | |
| 420 if (favicon_chooser_.get()) | |
| 421 favicon_chooser_.reset(); | |
| 422 // Any candidate favicons we have are now out of date so clear them. | |
| 423 favicon_url_candidates_.clear(); | |
| 424 } | |
| 425 | |
| 426 void MetroPinTabHelper::DidUpdateFaviconURL( | |
| 427 const std::vector<content::FaviconURL>& candidates) { | |
| 428 favicon_url_candidates_ = candidates; | |
| 429 } | |
| 430 | |
| 431 void MetroPinTabHelper::DidDownloadFavicon( | |
| 432 int id, | |
| 433 int http_status_code, | |
| 434 const GURL& image_url, | |
| 435 const std::vector<SkBitmap>& bitmaps, | |
| 436 const std::vector<gfx::Size>& original_bitmap_sizes) { | |
| 437 if (favicon_chooser_.get()) { | |
| 438 favicon_chooser_->UpdateCandidate(id, image_url, bitmaps); | |
| 439 } | |
| 440 } | |
| 441 | |
| 442 void MetroPinTabHelper::UnPinPageFromStartScreen() { | |
| 443 HMODULE metro_module = base::win::GetMetroModule(); | |
| 444 if (!metro_module) | |
| 445 return; | |
| 446 | |
| 447 base::win::MetroUnPinFromStartScreen metro_un_pin_from_start_screen = | |
| 448 reinterpret_cast<base::win::MetroUnPinFromStartScreen>( | |
| 449 ::GetProcAddress(metro_module, "MetroUnPinFromStartScreen")); | |
| 450 if (!metro_un_pin_from_start_screen) { | |
| 451 NOTREACHED(); | |
| 452 return; | |
| 453 } | |
| 454 | |
| 455 GURL url = web_contents()->GetURL(); | |
| 456 base::string16 tile_id = GenerateTileId(base::UTF8ToUTF16(url.spec())); | |
| 457 metro_un_pin_from_start_screen(tile_id, | |
| 458 base::Bind(&PinPageReportUmaCallback)); | |
| 459 } | |
| 460 | |
| 461 void MetroPinTabHelper::FaviconDownloaderFinished() { | |
| 462 favicon_chooser_.reset(); | |
| 463 } | |
| OLD | NEW |