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 |