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