| 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 |