bookmark_app_helper.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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 3// found in the LICENSE file. 4 5#include "chrome/browser/extensions/bookmark_app_helper.h" 6 7#include <cctype> 8 9#include "base/strings/utf_string_conversions.h" 10#include "chrome/browser/chrome_notification_types.h" 11#include "chrome/browser/extensions/crx_installer.h" 12#include "chrome/browser/extensions/extension_service.h" 13#include "chrome/browser/extensions/favicon_downloader.h" 14#include "chrome/browser/extensions/image_loader.h" 15#include "chrome/browser/extensions/tab_helper.h" 16#include "chrome/common/extensions/extension_constants.h" 17#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 18#include "content/public/browser/notification_service.h" 19#include "content/public/browser/notification_source.h" 20#include "content/public/browser/web_contents.h" 21#include "extensions/common/constants.h" 22#include "extensions/common/extension.h" 23#include "extensions/common/manifest_handlers/icons_handler.h" 24#include "extensions/common/url_pattern.h" 25#include "grit/platform_locale_settings.h" 26#include "net/base/registry_controlled_domains/registry_controlled_domain.h" 27#include "skia/ext/image_operations.h" 28#include "skia/ext/platform_canvas.h" 29#include "third_party/skia/include/core/SkBitmap.h" 30#include "ui/base/l10n/l10n_util.h" 31#include "ui/base/resource/resource_bundle.h" 32#include "ui/gfx/canvas.h" 33#include "ui/gfx/color_analysis.h" 34#include "ui/gfx/color_utils.h" 35#include "ui/gfx/font.h" 36#include "ui/gfx/font_list.h" 37#include "ui/gfx/image/canvas_image_source.h" 38#include "ui/gfx/image/image.h" 39#include "ui/gfx/image/image_family.h" 40#include "ui/gfx/rect.h" 41 42namespace { 43 44// Overlays a shortcut icon over the bottom left corner of a given image. 45class GeneratedIconImageSource : public gfx::CanvasImageSource { 46 public: 47 explicit GeneratedIconImageSource(char letter, SkColor color, int output_size) 48 : gfx::CanvasImageSource(gfx::Size(output_size, output_size), false), 49 letter_(letter), 50 color_(color), 51 output_size_(output_size) {} 52 virtual ~GeneratedIconImageSource() {} 53 54 private: 55 // gfx::CanvasImageSource overrides: 56 virtual void Draw(gfx::Canvas* canvas) OVERRIDE { 57 const unsigned char kLuminanceThreshold = 190; 58 const int icon_size = output_size_ * 3 / 4; 59 const int icon_inset = output_size_ / 8; 60 const size_t border_radius = output_size_ / 16; 61 const size_t font_size = output_size_ * 7 / 16; 62 63 std::string font_name = 64 l10n_util::GetStringUTF8(IDS_SANS_SERIF_FONT_FAMILY); 65#if defined(OS_CHROMEOS) 66 const std::string kChromeOSFontFamily = "Noto Sans"; 67 font_name = kChromeOSFontFamily; 68#endif 69 70 // Draw a rounded rect of the given |color|. 71 SkPaint background_paint; 72 background_paint.setFlags(SkPaint::kAntiAlias_Flag); 73 background_paint.setColor(color_); 74 75 gfx::Rect icon_rect(icon_inset, icon_inset, icon_size, icon_size); 76 canvas->DrawRoundRect(icon_rect, border_radius, background_paint); 77 78 // The text rect's size needs to be odd to center the text correctly. 79 gfx::Rect text_rect(icon_inset, icon_inset, icon_size + 1, icon_size + 1); 80 // Draw the letter onto the rounded rect. The letter's color depends on the 81 // luminance of |color|. 82 unsigned char luminance = color_utils::GetLuminanceForColor(color_); 83 canvas->DrawStringRectWithFlags( 84 base::string16(1, std::toupper(letter_)), 85 gfx::FontList(gfx::Font(font_name, font_size)), 86 luminance > kLuminanceThreshold ? SK_ColorBLACK : SK_ColorWHITE, 87 text_rect, 88 gfx::Canvas::TEXT_ALIGN_CENTER); 89 } 90 91 char letter_; 92 93 SkColor color_; 94 95 int output_size_; 96 97 DISALLOW_COPY_AND_ASSIGN(GeneratedIconImageSource); 98}; 99 100void OnIconsLoaded( 101 WebApplicationInfo web_app_info, 102 const base::Callback<void(const WebApplicationInfo&)> callback, 103 const gfx::ImageFamily& image_family) { 104 for (gfx::ImageFamily::const_iterator it = image_family.begin(); 105 it != image_family.end(); 106 ++it) { 107 WebApplicationInfo::IconInfo icon_info; 108 icon_info.data = *it->ToSkBitmap(); 109 icon_info.width = icon_info.data.width(); 110 icon_info.height = icon_info.data.height(); 111 web_app_info.icons.push_back(icon_info); 112 } 113 callback.Run(web_app_info); 114} 115 116} // namespace 117 118namespace extensions { 119 120// static 121std::map<int, SkBitmap> BookmarkAppHelper::ConstrainBitmapsToSizes( 122 const std::vector<SkBitmap>& bitmaps, 123 const std::set<int>& sizes) { 124 std::map<int, SkBitmap> output_bitmaps; 125 std::map<int, SkBitmap> ordered_bitmaps; 126 for (std::vector<SkBitmap>::const_iterator it = bitmaps.begin(); 127 it != bitmaps.end(); 128 ++it) { 129 DCHECK(it->width() == it->height()); 130 ordered_bitmaps[it->width()] = *it; 131 } 132 133 std::set<int>::const_iterator sizes_it = sizes.begin(); 134 std::map<int, SkBitmap>::const_iterator bitmaps_it = ordered_bitmaps.begin(); 135 while (sizes_it != sizes.end() && bitmaps_it != ordered_bitmaps.end()) { 136 int size = *sizes_it; 137 // Find the closest not-smaller bitmap. 138 bitmaps_it = ordered_bitmaps.lower_bound(size); 139 ++sizes_it; 140 // Ensure the bitmap is valid and smaller than the next allowed size. 141 if (bitmaps_it != ordered_bitmaps.end() && 142 (sizes_it == sizes.end() || bitmaps_it->second.width() < *sizes_it)) { 143 // Resize the bitmap if it does not exactly match the desired size. 144 output_bitmaps[size] = bitmaps_it->second.width() == size 145 ? bitmaps_it->second 146 : skia::ImageOperations::Resize( 147 bitmaps_it->second, 148 skia::ImageOperations::RESIZE_LANCZOS3, 149 size, 150 size); 151 } 152 } 153 return output_bitmaps; 154} 155 156// static 157void BookmarkAppHelper::GenerateIcon(std::map<int, SkBitmap>* bitmaps, 158 int output_size, 159 SkColor color, 160 char letter) { 161 // Do nothing if there is already an icon of |output_size|. 162 if (bitmaps->count(output_size)) 163 return; 164 165 gfx::ImageSkia icon_image( 166 new GeneratedIconImageSource(letter, color, output_size), 167 gfx::Size(output_size, output_size)); 168 icon_image.bitmap()->deepCopyTo(&(*bitmaps)[output_size]); 169} 170 171BookmarkAppHelper::BookmarkAppHelper(ExtensionService* service, 172 WebApplicationInfo web_app_info, 173 content::WebContents* contents) 174 : web_app_info_(web_app_info), 175 crx_installer_(extensions::CrxInstaller::CreateSilent(service)) { 176 registrar_.Add(this, 177 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 178 content::Source<CrxInstaller>(crx_installer_.get())); 179 180 registrar_.Add(this, 181 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 182 content::Source<CrxInstaller>(crx_installer_.get())); 183 184 crx_installer_->set_error_on_unsupported_requirements(true); 185 186 if (!contents) 187 return; 188 189 // Add urls from the WebApplicationInfo. 190 std::vector<GURL> web_app_info_icon_urls; 191 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = 192 web_app_info_.icons.begin(); 193 it != web_app_info_.icons.end(); 194 ++it) { 195 if (it->url.is_valid()) 196 web_app_info_icon_urls.push_back(it->url); 197 } 198 199 favicon_downloader_.reset( 200 new FaviconDownloader(contents, 201 web_app_info_icon_urls, 202 base::Bind(&BookmarkAppHelper::OnIconsDownloaded, 203 base::Unretained(this)))); 204} 205 206BookmarkAppHelper::~BookmarkAppHelper() {} 207 208void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) { 209 callback_ = callback; 210 211 if (favicon_downloader_.get()) 212 favicon_downloader_->Start(); 213 else 214 OnIconsDownloaded(true, std::map<GURL, std::vector<SkBitmap> >()); 215} 216 217void BookmarkAppHelper::OnIconsDownloaded( 218 bool success, 219 const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { 220 // The tab has navigated away during the icon download. Cancel the bookmark 221 // app creation. 222 if (!success) { 223 favicon_downloader_.reset(); 224 callback_.Run(NULL, web_app_info_); 225 return; 226 } 227 228 // Add the downloaded icons. Extensions only allow certain icon sizes. First 229 // populate icons that match the allowed sizes exactly and then downscale 230 // remaining icons to the closest allowed size that doesn't yet have an icon. 231 std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes, 232 extension_misc::kExtensionIconSizes + 233 extension_misc::kNumExtensionIconSizes); 234 std::vector<SkBitmap> downloaded_icons; 235 for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin(); 236 map_it != bitmaps.end(); 237 ++map_it) { 238 for (std::vector<SkBitmap>::const_iterator bitmap_it = 239 map_it->second.begin(); 240 bitmap_it != map_it->second.end(); 241 ++bitmap_it) { 242 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height()) 243 continue; 244 245 downloaded_icons.push_back(*bitmap_it); 246 } 247 } 248 249 // Add all existing icons from WebApplicationInfo. 250 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = 251 web_app_info_.icons.begin(); 252 it != web_app_info_.icons.end(); 253 ++it) { 254 const SkBitmap& icon = it->data; 255 if (!icon.drawsNothing() && icon.width() == icon.height()) 256 downloaded_icons.push_back(icon); 257 } 258 259 web_app_info_.icons.clear(); 260 261 // If there are icons that don't match the accepted icon sizes, find the 262 // closest bigger icon to the accepted sizes and resize the icon to it. An 263 // icon will be resized and used for at most one size. 264 std::map<int, SkBitmap> resized_bitmaps( 265 ConstrainBitmapsToSizes(downloaded_icons, allowed_sizes)); 266 267 // Generate container icons from smaller icons. 268 const int kIconSizesToGenerate[] = {extension_misc::EXTENSION_ICON_SMALL, 269 extension_misc::EXTENSION_ICON_MEDIUM, }; 270 const std::set<int> generate_sizes( 271 kIconSizesToGenerate, 272 kIconSizesToGenerate + arraysize(kIconSizesToGenerate)); 273 274 // Only generate icons if larger icons don't exist. This means the app 275 // launcher and the taskbar will do their best downsizing large icons and 276 // these icons are only generated as a last resort against upscaling a smaller 277 // icon. 278 if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) == 279 resized_bitmaps.end()) { 280 GURL app_url = web_app_info_.app_url; 281 282 // The letter that will be painted on the generated icon. 283 char icon_letter = ' '; 284 std::string domain_and_registry( 285 net::registry_controlled_domains::GetDomainAndRegistry( 286 app_url, 287 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); 288 if (!domain_and_registry.empty()) { 289 icon_letter = domain_and_registry[0]; 290 } else if (!app_url.host().empty()) { 291 icon_letter = app_url.host()[0]; 292 } 293 294 // The color that will be used for the icon's background. 295 SkColor background_color = SK_ColorBLACK; 296 if (resized_bitmaps.size()) { 297 color_utils::GridSampler sampler; 298 background_color = color_utils::CalculateKMeanColorOfBitmap( 299 resized_bitmaps.begin()->second); 300 } 301 302 for (std::set<int>::const_iterator it = generate_sizes.begin(); 303 it != generate_sizes.end(); 304 ++it) { 305 GenerateIcon(&resized_bitmaps, *it, background_color, icon_letter); 306 // Also generate the 2x resource for this size. 307 GenerateIcon(&resized_bitmaps, *it * 2, background_color, icon_letter); 308 } 309 } 310 311 // Populate the icon data into the WebApplicationInfo we are using to 312 // install the bookmark app. 313 for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it = 314 resized_bitmaps.begin(); 315 resized_bitmaps_it != resized_bitmaps.end(); 316 ++resized_bitmaps_it) { 317 WebApplicationInfo::IconInfo icon_info; 318 icon_info.data = resized_bitmaps_it->second; 319 icon_info.width = icon_info.data.width(); 320 icon_info.height = icon_info.data.height(); 321 web_app_info_.icons.push_back(icon_info); 322 } 323 324 // Install the app. 325 crx_installer_->InstallWebApp(web_app_info_); 326 favicon_downloader_.reset(); 327} 328 329void BookmarkAppHelper::Observe(int type, 330 const content::NotificationSource& source, 331 const content::NotificationDetails& details) { 332 switch (type) { 333 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { 334 const Extension* extension = 335 content::Details<const Extension>(details).ptr(); 336 DCHECK(extension); 337 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), 338 web_app_info_.app_url); 339 callback_.Run(extension, web_app_info_); 340 break; 341 } 342 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: 343 callback_.Run(NULL, web_app_info_); 344 break; 345 default: 346 NOTREACHED(); 347 break; 348 } 349} 350 351void CreateOrUpdateBookmarkApp(ExtensionService* service, 352 WebApplicationInfo& web_app_info) { 353 scoped_refptr<extensions::CrxInstaller> installer( 354 extensions::CrxInstaller::CreateSilent(service)); 355 installer->set_error_on_unsupported_requirements(true); 356 installer->InstallWebApp(web_app_info); 357} 358 359void GetWebApplicationInfoFromApp( 360 content::BrowserContext* browser_context, 361 const extensions::Extension* extension, 362 const base::Callback<void(const WebApplicationInfo&)> callback) { 363 if (!extension->from_bookmark()) { 364 callback.Run(WebApplicationInfo()); 365 return; 366 } 367 368 WebApplicationInfo web_app_info; 369 web_app_info.app_url = AppLaunchInfo::GetLaunchWebURL(extension); 370 web_app_info.title = base::UTF8ToUTF16(extension->non_localized_name()); 371 web_app_info.description = base::UTF8ToUTF16(extension->description()); 372 373 std::vector<extensions::ImageLoader::ImageRepresentation> info_list; 374 for (size_t i = 0; i < extension_misc::kNumExtensionIconSizes; ++i) { 375 int size = extension_misc::kExtensionIconSizes[i]; 376 extensions::ExtensionResource resource = 377 extensions::IconsInfo::GetIconResource( 378 extension, size, ExtensionIconSet::MATCH_EXACTLY); 379 if (!resource.empty()) { 380 info_list.push_back(extensions::ImageLoader::ImageRepresentation( 381 resource, 382 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 383 gfx::Size(size, size), 384 ui::SCALE_FACTOR_100P)); 385 } 386 } 387 388 extensions::ImageLoader::Get(browser_context)->LoadImageFamilyAsync( 389 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback)); 390} 391 392bool IsValidBookmarkAppUrl(const GURL& url) { 393 URLPattern origin_only_pattern(Extension::kValidWebExtentSchemes); 394 origin_only_pattern.SetMatchAllURLs(true); 395 return url.is_valid() && origin_only_pattern.MatchesURL(url); 396} 397 398} // namespace extensions 399