bookmark_app_helper.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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 // Add urls from the WebApplicationInfo. 187 std::vector<GURL> web_app_info_icon_urls; 188 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = 189 web_app_info_.icons.begin(); 190 it != web_app_info_.icons.end(); 191 ++it) { 192 if (it->url.is_valid()) 193 web_app_info_icon_urls.push_back(it->url); 194 } 195 196 favicon_downloader_.reset( 197 new FaviconDownloader(contents, 198 web_app_info_icon_urls, 199 base::Bind(&BookmarkAppHelper::OnIconsDownloaded, 200 base::Unretained(this)))); 201} 202 203BookmarkAppHelper::~BookmarkAppHelper() {} 204 205void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) { 206 callback_ = callback; 207 favicon_downloader_->Start(); 208} 209 210void BookmarkAppHelper::OnIconsDownloaded( 211 bool success, 212 const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { 213 // The tab has navigated away during the icon download. Cancel the bookmark 214 // app creation. 215 if (!success) { 216 favicon_downloader_.reset(); 217 callback_.Run(NULL, web_app_info_); 218 return; 219 } 220 221 // Add the downloaded icons. Extensions only allow certain icon sizes. First 222 // populate icons that match the allowed sizes exactly and then downscale 223 // remaining icons to the closest allowed size that doesn't yet have an icon. 224 std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes, 225 extension_misc::kExtensionIconSizes + 226 extension_misc::kNumExtensionIconSizes); 227 std::vector<SkBitmap> downloaded_icons; 228 for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin(); 229 map_it != bitmaps.end(); 230 ++map_it) { 231 for (std::vector<SkBitmap>::const_iterator bitmap_it = 232 map_it->second.begin(); 233 bitmap_it != map_it->second.end(); 234 ++bitmap_it) { 235 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height()) 236 continue; 237 238 downloaded_icons.push_back(*bitmap_it); 239 } 240 } 241 242 // If there are icons that don't match the accepted icon sizes, find the 243 // closest bigger icon to the accepted sizes and resize the icon to it. An 244 // icon will be resized and used for at most one size. 245 std::map<int, SkBitmap> resized_bitmaps( 246 ConstrainBitmapsToSizes(downloaded_icons, allowed_sizes)); 247 248 // Generate container icons from smaller icons. 249 const int kIconSizesToGenerate[] = {extension_misc::EXTENSION_ICON_SMALL, 250 extension_misc::EXTENSION_ICON_MEDIUM, }; 251 const std::set<int> generate_sizes( 252 kIconSizesToGenerate, 253 kIconSizesToGenerate + arraysize(kIconSizesToGenerate)); 254 255 // Only generate icons if larger icons don't exist. This means the app 256 // launcher and the taskbar will do their best downsizing large icons and 257 // these icons are only generated as a last resort against upscaling a smaller 258 // icon. 259 if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) == 260 resized_bitmaps.end()) { 261 GURL app_url = web_app_info_.app_url; 262 263 // The letter that will be painted on the generated icon. 264 char icon_letter = ' '; 265 std::string domain_and_registry( 266 net::registry_controlled_domains::GetDomainAndRegistry( 267 app_url, 268 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); 269 if (!domain_and_registry.empty()) { 270 icon_letter = domain_and_registry[0]; 271 } else if (!app_url.host().empty()) { 272 icon_letter = app_url.host()[0]; 273 } 274 275 // The color that will be used for the icon's background. 276 SkColor background_color = SK_ColorBLACK; 277 if (resized_bitmaps.size()) { 278 color_utils::GridSampler sampler; 279 background_color = color_utils::CalculateKMeanColorOfPNG( 280 gfx::Image::CreateFrom1xBitmap(resized_bitmaps.begin()->second) 281 .As1xPNGBytes(), 282 100, 283 568, 284 &sampler); 285 } 286 287 for (std::set<int>::const_iterator it = generate_sizes.begin(); 288 it != generate_sizes.end(); 289 ++it) { 290 GenerateIcon(&resized_bitmaps, *it, background_color, icon_letter); 291 // Also generate the 2x resource for this size. 292 GenerateIcon(&resized_bitmaps, *it * 2, background_color, icon_letter); 293 } 294 } 295 296 // Populate the icon data into the WebApplicationInfo we are using to 297 // install the bookmark app. 298 for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it = 299 resized_bitmaps.begin(); 300 resized_bitmaps_it != resized_bitmaps.end(); 301 ++resized_bitmaps_it) { 302 WebApplicationInfo::IconInfo icon_info; 303 icon_info.data = resized_bitmaps_it->second; 304 icon_info.width = icon_info.data.width(); 305 icon_info.height = icon_info.data.height(); 306 web_app_info_.icons.push_back(icon_info); 307 } 308 309 // Install the app. 310 crx_installer_->InstallWebApp(web_app_info_); 311 favicon_downloader_.reset(); 312} 313 314void BookmarkAppHelper::Observe(int type, 315 const content::NotificationSource& source, 316 const content::NotificationDetails& details) { 317 switch (type) { 318 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { 319 const Extension* extension = 320 content::Details<const Extension>(details).ptr(); 321 DCHECK(extension); 322 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), 323 web_app_info_.app_url); 324 callback_.Run(extension, web_app_info_); 325 break; 326 } 327 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: 328 callback_.Run(NULL, web_app_info_); 329 break; 330 default: 331 NOTREACHED(); 332 break; 333 } 334} 335 336void CreateOrUpdateBookmarkApp(ExtensionService* service, 337 WebApplicationInfo& web_app_info) { 338 scoped_refptr<extensions::CrxInstaller> installer( 339 extensions::CrxInstaller::CreateSilent(service)); 340 installer->set_error_on_unsupported_requirements(true); 341 installer->InstallWebApp(web_app_info); 342} 343 344void GetWebApplicationInfoFromApp( 345 content::BrowserContext* browser_context, 346 const extensions::Extension* extension, 347 const base::Callback<void(const WebApplicationInfo&)> callback) { 348 if (!extension->from_bookmark()) { 349 callback.Run(WebApplicationInfo()); 350 return; 351 } 352 353 WebApplicationInfo web_app_info; 354 web_app_info.app_url = AppLaunchInfo::GetLaunchWebURL(extension); 355 web_app_info.title = base::UTF8ToUTF16(extension->non_localized_name()); 356 web_app_info.description = base::UTF8ToUTF16(extension->description()); 357 358 std::vector<extensions::ImageLoader::ImageRepresentation> info_list; 359 for (size_t i = 0; i < extension_misc::kNumExtensionIconSizes; ++i) { 360 int size = extension_misc::kExtensionIconSizes[i]; 361 extensions::ExtensionResource resource = 362 extensions::IconsInfo::GetIconResource( 363 extension, size, ExtensionIconSet::MATCH_EXACTLY); 364 if (!resource.empty()) { 365 info_list.push_back(extensions::ImageLoader::ImageRepresentation( 366 resource, 367 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 368 gfx::Size(size, size), 369 ui::SCALE_FACTOR_100P)); 370 } 371 } 372 373 extensions::ImageLoader::Get(browser_context)->LoadImageFamilyAsync( 374 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback)); 375} 376 377bool IsValidBookmarkAppUrl(const GURL& url) { 378 URLPattern origin_only_pattern(Extension::kValidWebExtentSchemes); 379 origin_only_pattern.SetMatchAllURLs(true); 380 return url.is_valid() && origin_only_pattern.MatchesURL(url); 381} 382 383} // namespace extensions 384