OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/extensions/bookmark_app_helper.h" | 5 #include "chrome/browser/extensions/bookmark_app_helper.h" |
6 | 6 |
7 #include <cctype> | 7 #include <cctype> |
8 | 8 |
9 #include "base/strings/utf_string_conversions.h" | 9 #include "base/strings/utf_string_conversions.h" |
10 #include "chrome/browser/extensions/crx_installer.h" | 10 #include "chrome/browser/extensions/crx_installer.h" |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
105 ++it) { | 105 ++it) { |
106 WebApplicationInfo::IconInfo icon_info; | 106 WebApplicationInfo::IconInfo icon_info; |
107 icon_info.data = *it->ToSkBitmap(); | 107 icon_info.data = *it->ToSkBitmap(); |
108 icon_info.width = icon_info.data.width(); | 108 icon_info.width = icon_info.data.width(); |
109 icon_info.height = icon_info.data.height(); | 109 icon_info.height = icon_info.data.height(); |
110 web_app_info.icons.push_back(icon_info); | 110 web_app_info.icons.push_back(icon_info); |
111 } | 111 } |
112 callback.Run(web_app_info); | 112 callback.Run(web_app_info); |
113 } | 113 } |
114 | 114 |
| 115 std::set<int> SizesToGenerate() { |
| 116 // Generate container icons from smaller icons. |
| 117 const int kIconSizesToGenerate[] = { |
| 118 extension_misc::EXTENSION_ICON_SMALL, |
| 119 extension_misc::EXTENSION_ICON_MEDIUM, |
| 120 }; |
| 121 return std::set<int>(kIconSizesToGenerate, |
| 122 kIconSizesToGenerate + arraysize(kIconSizesToGenerate)); |
| 123 } |
| 124 |
| 125 void GenerateIcons(std::set<int> generate_sizes, |
| 126 const GURL& app_url, |
| 127 SkColor generated_icon_color, |
| 128 std::map<int, SkBitmap>* bitmap_map) { |
| 129 // The letter that will be painted on the generated icon. |
| 130 char icon_letter = ' '; |
| 131 std::string domain_and_registry( |
| 132 net::registry_controlled_domains::GetDomainAndRegistry( |
| 133 app_url, |
| 134 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); |
| 135 if (!domain_and_registry.empty()) { |
| 136 icon_letter = domain_and_registry[0]; |
| 137 } else if (!app_url.host().empty()) { |
| 138 icon_letter = app_url.host()[0]; |
| 139 } |
| 140 |
| 141 // If no color has been specified, use a dark gray so it will stand out on the |
| 142 // black shelf. |
| 143 if (generated_icon_color == SK_ColorTRANSPARENT) |
| 144 generated_icon_color = SK_ColorDKGRAY; |
| 145 |
| 146 for (std::set<int>::const_iterator it = generate_sizes.begin(); |
| 147 it != generate_sizes.end(); ++it) { |
| 148 extensions::BookmarkAppHelper::GenerateIcon( |
| 149 bitmap_map, *it, generated_icon_color, icon_letter); |
| 150 // Also generate the 2x resource for this size. |
| 151 extensions::BookmarkAppHelper::GenerateIcon( |
| 152 bitmap_map, *it * 2, generated_icon_color, icon_letter); |
| 153 } |
| 154 } |
| 155 |
| 156 void ReplaceWebAppIcons(std::map<int, SkBitmap> bitmap_map, |
| 157 WebApplicationInfo* web_app_info) { |
| 158 web_app_info->icons.clear(); |
| 159 |
| 160 // Populate the icon data into the WebApplicationInfo we are using to |
| 161 // install the bookmark app. |
| 162 for (std::map<int, SkBitmap>::const_iterator bitmap_map_it = |
| 163 bitmap_map.begin(); |
| 164 bitmap_map_it != bitmap_map.end(); ++bitmap_map_it) { |
| 165 WebApplicationInfo::IconInfo icon_info; |
| 166 icon_info.data = bitmap_map_it->second; |
| 167 icon_info.width = icon_info.data.width(); |
| 168 icon_info.height = icon_info.data.height(); |
| 169 web_app_info->icons.push_back(icon_info); |
| 170 } |
| 171 } |
| 172 |
115 } // namespace | 173 } // namespace |
116 | 174 |
117 namespace extensions { | 175 namespace extensions { |
118 | 176 |
119 // static | 177 // static |
120 void BookmarkAppHelper::UpdateWebAppInfoFromManifest( | 178 void BookmarkAppHelper::UpdateWebAppInfoFromManifest( |
121 const content::Manifest& manifest, | 179 const content::Manifest& manifest, |
122 WebApplicationInfo* web_app_info) { | 180 WebApplicationInfo* web_app_info) { |
123 if (!manifest.short_name.is_null()) | 181 if (!manifest.short_name.is_null()) |
124 web_app_info->title = manifest.short_name.string(); | 182 web_app_info->title = manifest.short_name.string(); |
(...skipping 13 matching lines...) Expand all Loading... |
138 for (const auto& icon : manifest.icons) { | 196 for (const auto& icon : manifest.icons) { |
139 // TODO(benwells): Take the declared icon density and sizes into account. | 197 // TODO(benwells): Take the declared icon density and sizes into account. |
140 WebApplicationInfo::IconInfo info; | 198 WebApplicationInfo::IconInfo info; |
141 info.url = icon.src; | 199 info.url = icon.src; |
142 web_app_info->icons.push_back(info); | 200 web_app_info->icons.push_back(info); |
143 } | 201 } |
144 } | 202 } |
145 } | 203 } |
146 | 204 |
147 // static | 205 // static |
148 std::map<int, SkBitmap> BookmarkAppHelper::ConstrainBitmapsToSizes( | |
149 const std::vector<SkBitmap>& bitmaps, | |
150 const std::set<int>& sizes) { | |
151 std::map<int, SkBitmap> output_bitmaps; | |
152 std::map<int, SkBitmap> ordered_bitmaps; | |
153 for (std::vector<SkBitmap>::const_iterator it = bitmaps.begin(); | |
154 it != bitmaps.end(); | |
155 ++it) { | |
156 DCHECK(it->width() == it->height()); | |
157 ordered_bitmaps[it->width()] = *it; | |
158 } | |
159 | |
160 std::set<int>::const_iterator sizes_it = sizes.begin(); | |
161 std::map<int, SkBitmap>::const_iterator bitmaps_it = ordered_bitmaps.begin(); | |
162 while (sizes_it != sizes.end() && bitmaps_it != ordered_bitmaps.end()) { | |
163 int size = *sizes_it; | |
164 // Find the closest not-smaller bitmap. | |
165 bitmaps_it = ordered_bitmaps.lower_bound(size); | |
166 ++sizes_it; | |
167 // Ensure the bitmap is valid and smaller than the next allowed size. | |
168 if (bitmaps_it != ordered_bitmaps.end() && | |
169 (sizes_it == sizes.end() || bitmaps_it->second.width() < *sizes_it)) { | |
170 // Resize the bitmap if it does not exactly match the desired size. | |
171 output_bitmaps[size] = bitmaps_it->second.width() == size | |
172 ? bitmaps_it->second | |
173 : skia::ImageOperations::Resize( | |
174 bitmaps_it->second, | |
175 skia::ImageOperations::RESIZE_LANCZOS3, | |
176 size, | |
177 size); | |
178 } | |
179 } | |
180 return output_bitmaps; | |
181 } | |
182 | |
183 // static | |
184 void BookmarkAppHelper::GenerateIcon(std::map<int, SkBitmap>* bitmaps, | 206 void BookmarkAppHelper::GenerateIcon(std::map<int, SkBitmap>* bitmaps, |
185 int output_size, | 207 int output_size, |
186 SkColor color, | 208 SkColor color, |
187 char letter) { | 209 char letter) { |
188 // Do nothing if there is already an icon of |output_size|. | 210 // Do nothing if there is already an icon of |output_size|. |
189 if (bitmaps->count(output_size)) | 211 if (bitmaps->count(output_size)) |
190 return; | 212 return; |
191 | 213 |
192 gfx::ImageSkia icon_image( | 214 gfx::ImageSkia icon_image( |
193 new GeneratedIconImageSource(letter, color, output_size), | 215 new GeneratedIconImageSource(letter, color, output_size), |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
253 bool success, | 275 bool success, |
254 const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { | 276 const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { |
255 // The tab has navigated away during the icon download. Cancel the bookmark | 277 // The tab has navigated away during the icon download. Cancel the bookmark |
256 // app creation. | 278 // app creation. |
257 if (!success) { | 279 if (!success) { |
258 favicon_downloader_.reset(); | 280 favicon_downloader_.reset(); |
259 callback_.Run(NULL, web_app_info_); | 281 callback_.Run(NULL, web_app_info_); |
260 return; | 282 return; |
261 } | 283 } |
262 | 284 |
263 // Add the downloaded icons. Extensions only allow certain icon sizes. First | |
264 // populate icons that match the allowed sizes exactly and then downscale | |
265 // remaining icons to the closest allowed size that doesn't yet have an icon. | |
266 std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes, | |
267 extension_misc::kExtensionIconSizes + | |
268 extension_misc::kNumExtensionIconSizes); | |
269 std::vector<SkBitmap> downloaded_icons; | 285 std::vector<SkBitmap> downloaded_icons; |
270 for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin(); | 286 for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin(); |
271 map_it != bitmaps.end(); | 287 map_it != bitmaps.end(); |
272 ++map_it) { | 288 ++map_it) { |
273 for (std::vector<SkBitmap>::const_iterator bitmap_it = | 289 for (std::vector<SkBitmap>::const_iterator bitmap_it = |
274 map_it->second.begin(); | 290 map_it->second.begin(); |
275 bitmap_it != map_it->second.end(); | 291 bitmap_it != map_it->second.end(); |
276 ++bitmap_it) { | 292 ++bitmap_it) { |
277 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height()) | 293 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height()) |
278 continue; | 294 continue; |
279 | 295 |
280 downloaded_icons.push_back(*bitmap_it); | 296 downloaded_icons.push_back(*bitmap_it); |
281 } | 297 } |
282 } | 298 } |
283 | 299 |
284 // Add all existing icons from WebApplicationInfo. | 300 // Add all existing icons from WebApplicationInfo. |
285 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = | 301 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = |
286 web_app_info_.icons.begin(); | 302 web_app_info_.icons.begin(); |
287 it != web_app_info_.icons.end(); | 303 it != web_app_info_.icons.end(); |
288 ++it) { | 304 ++it) { |
289 const SkBitmap& icon = it->data; | 305 const SkBitmap& icon = it->data; |
290 if (!icon.drawsNothing() && icon.width() == icon.height()) | 306 if (!icon.drawsNothing() && icon.width() == icon.height()) |
291 downloaded_icons.push_back(icon); | 307 downloaded_icons.push_back(icon); |
292 } | 308 } |
293 | 309 |
294 web_app_info_.icons.clear(); | 310 // Add the downloaded icons. Extensions only allow certain icon sizes. First |
| 311 // populate icons that match the allowed sizes exactly and then downscale |
| 312 // remaining icons to the closest allowed size that doesn't yet have an icon. |
| 313 std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes, |
| 314 extension_misc::kExtensionIconSizes + |
| 315 extension_misc::kNumExtensionIconSizes); |
295 | 316 |
296 // If there are icons that don't match the accepted icon sizes, find the | 317 web_app_info_.generated_icon_color = SK_ColorTRANSPARENT; |
297 // closest bigger icon to the accepted sizes and resize the icon to it. An | 318 // Determine the color that will be used for the icon's background. For this |
298 // icon will be resized and used for at most one size. | 319 // the dominant color of the first icon found is used. |
299 std::map<int, SkBitmap> resized_bitmaps( | 320 if (downloaded_icons.size()) { |
300 ConstrainBitmapsToSizes(downloaded_icons, allowed_sizes)); | 321 color_utils::GridSampler sampler; |
301 | 322 web_app_info_.generated_icon_color = |
302 // Generate container icons from smaller icons. | 323 color_utils::CalculateKMeanColorOfBitmap(downloaded_icons[0]); |
303 const int kIconSizesToGenerate[] = {extension_misc::EXTENSION_ICON_SMALL, | |
304 extension_misc::EXTENSION_ICON_MEDIUM, }; | |
305 const std::set<int> generate_sizes( | |
306 kIconSizesToGenerate, | |
307 kIconSizesToGenerate + arraysize(kIconSizesToGenerate)); | |
308 | |
309 // Only generate icons if larger icons don't exist. This means the app | |
310 // launcher and the taskbar will do their best downsizing large icons and | |
311 // these icons are only generated as a last resort against upscaling a smaller | |
312 // icon. | |
313 if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) == | |
314 resized_bitmaps.end()) { | |
315 GURL app_url = web_app_info_.app_url; | |
316 | |
317 // The letter that will be painted on the generated icon. | |
318 char icon_letter = ' '; | |
319 std::string domain_and_registry( | |
320 net::registry_controlled_domains::GetDomainAndRegistry( | |
321 app_url, | |
322 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); | |
323 if (!domain_and_registry.empty()) { | |
324 icon_letter = domain_and_registry[0]; | |
325 } else if (!app_url.host().empty()) { | |
326 icon_letter = app_url.host()[0]; | |
327 } | |
328 | |
329 // The color that will be used for the icon's background. | |
330 SkColor background_color = SK_ColorBLACK; | |
331 if (resized_bitmaps.size()) { | |
332 color_utils::GridSampler sampler; | |
333 background_color = color_utils::CalculateKMeanColorOfBitmap( | |
334 resized_bitmaps.begin()->second); | |
335 } | |
336 | |
337 for (std::set<int>::const_iterator it = generate_sizes.begin(); | |
338 it != generate_sizes.end(); | |
339 ++it) { | |
340 GenerateIcon(&resized_bitmaps, *it, background_color, icon_letter); | |
341 // Also generate the 2x resource for this size. | |
342 GenerateIcon(&resized_bitmaps, *it * 2, background_color, icon_letter); | |
343 } | |
344 } | 324 } |
345 | 325 |
346 // Populate the icon data into the WebApplicationInfo we are using to | 326 std::set<int> generate_sizes = SizesToGenerate(); |
347 // install the bookmark app. | 327 |
348 for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it = | 328 std::map<int, SkBitmap> generated_icons; |
349 resized_bitmaps.begin(); | 329 // Icons are always generated, replacing the icons that were downloaded. This |
350 resized_bitmaps_it != resized_bitmaps.end(); | 330 // is done so that the icons are consistent across machines. |
351 ++resized_bitmaps_it) { | 331 // TODO(benwells): Use blob sync once it is available to sync the downloaded |
352 WebApplicationInfo::IconInfo icon_info; | 332 // icons, and then only generate when there are required sizes missing. |
353 icon_info.data = resized_bitmaps_it->second; | 333 GenerateIcons(generate_sizes, web_app_info_.app_url, |
354 icon_info.width = icon_info.data.width(); | 334 web_app_info_.generated_icon_color, &generated_icons); |
355 icon_info.height = icon_info.data.height(); | 335 |
356 web_app_info_.icons.push_back(icon_info); | 336 ReplaceWebAppIcons(generated_icons, &web_app_info_); |
357 } | |
358 | 337 |
359 // Install the app. | 338 // Install the app. |
360 crx_installer_->InstallWebApp(web_app_info_); | 339 crx_installer_->InstallWebApp(web_app_info_); |
361 favicon_downloader_.reset(); | 340 favicon_downloader_.reset(); |
362 } | 341 } |
363 | 342 |
364 void BookmarkAppHelper::Observe(int type, | 343 void BookmarkAppHelper::Observe(int type, |
365 const content::NotificationSource& source, | 344 const content::NotificationSource& source, |
366 const content::NotificationDetails& details) { | 345 const content::NotificationDetails& details) { |
367 switch (type) { | 346 switch (type) { |
368 case extensions::NOTIFICATION_CRX_INSTALLER_DONE: { | 347 case extensions::NOTIFICATION_CRX_INSTALLER_DONE: { |
369 const Extension* extension = | 348 const Extension* extension = |
370 content::Details<const Extension>(details).ptr(); | 349 content::Details<const Extension>(details).ptr(); |
371 DCHECK(extension); | 350 DCHECK(extension); |
372 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), | 351 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), |
373 web_app_info_.app_url); | 352 web_app_info_.app_url); |
374 callback_.Run(extension, web_app_info_); | 353 callback_.Run(extension, web_app_info_); |
375 break; | 354 break; |
376 } | 355 } |
377 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR: | 356 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR: |
378 callback_.Run(NULL, web_app_info_); | 357 callback_.Run(NULL, web_app_info_); |
379 break; | 358 break; |
380 default: | 359 default: |
381 NOTREACHED(); | 360 NOTREACHED(); |
382 break; | 361 break; |
383 } | 362 } |
384 } | 363 } |
385 | 364 |
386 void CreateOrUpdateBookmarkApp(ExtensionService* service, | 365 void CreateOrUpdateBookmarkApp(ExtensionService* service, |
387 WebApplicationInfo& web_app_info) { | 366 WebApplicationInfo* web_app_info) { |
388 scoped_refptr<extensions::CrxInstaller> installer( | 367 scoped_refptr<extensions::CrxInstaller> installer( |
389 extensions::CrxInstaller::CreateSilent(service)); | 368 extensions::CrxInstaller::CreateSilent(service)); |
390 installer->set_error_on_unsupported_requirements(true); | 369 installer->set_error_on_unsupported_requirements(true); |
391 installer->InstallWebApp(web_app_info); | 370 if (web_app_info->icons.empty()) { |
| 371 std::map<int, SkBitmap> bitmap_map; |
| 372 GenerateIcons(SizesToGenerate(), web_app_info->app_url, |
| 373 web_app_info->generated_icon_color, &bitmap_map); |
| 374 ReplaceWebAppIcons(bitmap_map, web_app_info); |
| 375 } |
| 376 |
| 377 installer->InstallWebApp(*web_app_info); |
392 } | 378 } |
393 | 379 |
394 void GetWebApplicationInfoFromApp( | 380 void GetWebApplicationInfoFromApp( |
395 content::BrowserContext* browser_context, | 381 content::BrowserContext* browser_context, |
396 const extensions::Extension* extension, | 382 const extensions::Extension* extension, |
397 const base::Callback<void(const WebApplicationInfo&)> callback) { | 383 const base::Callback<void(const WebApplicationInfo&)> callback) { |
398 if (!extension->from_bookmark()) { | 384 if (!extension->from_bookmark()) { |
399 callback.Run(WebApplicationInfo()); | 385 callback.Run(WebApplicationInfo()); |
400 return; | 386 return; |
401 } | 387 } |
(...skipping 22 matching lines...) Expand all Loading... |
424 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback)); | 410 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback)); |
425 } | 411 } |
426 | 412 |
427 bool IsValidBookmarkAppUrl(const GURL& url) { | 413 bool IsValidBookmarkAppUrl(const GURL& url) { |
428 URLPattern origin_only_pattern(Extension::kValidWebExtentSchemes); | 414 URLPattern origin_only_pattern(Extension::kValidWebExtentSchemes); |
429 origin_only_pattern.SetMatchAllURLs(true); | 415 origin_only_pattern.SetMatchAllURLs(true); |
430 return url.is_valid() && origin_only_pattern.MatchesURL(url); | 416 return url.is_valid() && origin_only_pattern.MatchesURL(url); |
431 } | 417 } |
432 | 418 |
433 } // namespace extensions | 419 } // namespace extensions |
OLD | NEW |