bookmark_app_helper.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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 "base/strings/utf_string_conversions.h" 8#include "chrome/browser/chrome_notification_types.h" 9#include "chrome/browser/extensions/crx_installer.h" 10#include "chrome/browser/extensions/extension_service.h" 11#include "chrome/browser/extensions/favicon_downloader.h" 12#include "chrome/browser/extensions/image_loader.h" 13#include "chrome/browser/extensions/tab_helper.h" 14#include "chrome/common/extensions/extension_constants.h" 15#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 16#include "content/public/browser/notification_service.h" 17#include "content/public/browser/notification_source.h" 18#include "content/public/browser/web_contents.h" 19#include "extensions/common/extension.h" 20#include "extensions/common/manifest_handlers/icons_handler.h" 21#include "extensions/common/url_pattern.h" 22#include "skia/ext/image_operations.h" 23#include "skia/ext/platform_canvas.h" 24#include "third_party/skia/include/core/SkBitmap.h" 25#include "ui/gfx/color_analysis.h" 26#include "ui/gfx/image/image.h" 27#include "ui/gfx/image/image_family.h" 28 29namespace { 30 31void OnIconsLoaded( 32 WebApplicationInfo web_app_info, 33 const base::Callback<void(const WebApplicationInfo&)> callback, 34 const gfx::ImageFamily& image_family) { 35 for (gfx::ImageFamily::const_iterator it = image_family.begin(); 36 it != image_family.end(); 37 ++it) { 38 WebApplicationInfo::IconInfo icon_info; 39 icon_info.data = *it->ToSkBitmap(); 40 icon_info.width = icon_info.data.width(); 41 icon_info.height = icon_info.data.height(); 42 web_app_info.icons.push_back(icon_info); 43 } 44 callback.Run(web_app_info); 45} 46 47} // namespace 48 49namespace extensions { 50 51// static 52std::map<int, SkBitmap> BookmarkAppHelper::ConstrainBitmapsToSizes( 53 const std::vector<SkBitmap>& bitmaps, 54 const std::set<int>& sizes) { 55 std::map<int, SkBitmap> output_bitmaps; 56 std::map<int, SkBitmap> ordered_bitmaps; 57 for (std::vector<SkBitmap>::const_iterator it = bitmaps.begin(); 58 it != bitmaps.end(); 59 ++it) { 60 DCHECK(it->width() == it->height()); 61 ordered_bitmaps[it->width()] = *it; 62 } 63 64 std::set<int>::const_iterator sizes_it = sizes.begin(); 65 std::map<int, SkBitmap>::const_iterator bitmaps_it = ordered_bitmaps.begin(); 66 while (sizes_it != sizes.end() && bitmaps_it != ordered_bitmaps.end()) { 67 int size = *sizes_it; 68 // Find the closest not-smaller bitmap. 69 bitmaps_it = ordered_bitmaps.lower_bound(size); 70 ++sizes_it; 71 // Ensure the bitmap is valid and smaller than the next allowed size. 72 if (bitmaps_it != ordered_bitmaps.end() && 73 (sizes_it == sizes.end() || bitmaps_it->second.width() < *sizes_it)) { 74 // Resize the bitmap if it does not exactly match the desired size. 75 output_bitmaps[size] = bitmaps_it->second.width() == size 76 ? bitmaps_it->second 77 : skia::ImageOperations::Resize( 78 bitmaps_it->second, 79 skia::ImageOperations::RESIZE_LANCZOS3, 80 size, 81 size); 82 } 83 } 84 return output_bitmaps; 85} 86 87// static 88void BookmarkAppHelper::GenerateContainerIcon(std::map<int, SkBitmap>* bitmaps, 89 int output_size) { 90 std::map<int, SkBitmap>::const_iterator it = 91 bitmaps->lower_bound(output_size); 92 // Do nothing if there is no icon smaller than the desired size or there is 93 // already an icon of |output_size|. 94 if (it == bitmaps->begin() || bitmaps->count(output_size)) 95 return; 96 97 --it; 98 // This is the biggest icon smaller than |output_size|. 99 const SkBitmap& base_icon = it->second; 100 101 const size_t kBorderRadius = 5; 102 const size_t kColorStripHeight = 3; 103 const SkColor kBorderColor = 0xFFD5D5D5; 104 const SkColor kBackgroundColor = 0xFFFFFFFF; 105 106 // Create a separate canvas for the color strip. 107 scoped_ptr<SkCanvas> color_strip_canvas( 108 skia::CreateBitmapCanvas(output_size, output_size, false)); 109 DCHECK(color_strip_canvas); 110 111 // Draw a rounded rect of the |base_icon|'s dominant color. 112 SkPaint color_strip_paint; 113 color_utils::GridSampler sampler; 114 color_strip_paint.setFlags(SkPaint::kAntiAlias_Flag); 115 color_strip_paint.setColor(color_utils::CalculateKMeanColorOfPNG( 116 gfx::Image::CreateFrom1xBitmap(base_icon).As1xPNGBytes(), 117 100, 118 665, 119 &sampler)); 120 color_strip_canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size), 121 kBorderRadius, 122 kBorderRadius, 123 color_strip_paint); 124 125 // Erase the top of the rounded rect to leave a color strip. 126 SkPaint clear_paint; 127 clear_paint.setColor(SK_ColorTRANSPARENT); 128 clear_paint.setXfermodeMode(SkXfermode::kSrc_Mode); 129 color_strip_canvas->drawRect( 130 SkRect::MakeWH(output_size, output_size - kColorStripHeight), 131 clear_paint); 132 133 // Draw each element to an output canvas. 134 scoped_ptr<SkCanvas> canvas( 135 skia::CreateBitmapCanvas(output_size, output_size, false)); 136 DCHECK(canvas); 137 138 // Draw the background. 139 SkPaint background_paint; 140 background_paint.setColor(kBackgroundColor); 141 background_paint.setFlags(SkPaint::kAntiAlias_Flag); 142 canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size), 143 kBorderRadius, 144 kBorderRadius, 145 background_paint); 146 147 // Draw the color strip. 148 canvas->drawBitmap( 149 color_strip_canvas->getDevice()->accessBitmap(false), 0, 0); 150 151 // Draw the border. 152 SkPaint border_paint; 153 border_paint.setColor(kBorderColor); 154 border_paint.setStyle(SkPaint::kStroke_Style); 155 border_paint.setFlags(SkPaint::kAntiAlias_Flag); 156 canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size), 157 kBorderRadius, 158 kBorderRadius, 159 border_paint); 160 161 // Draw the centered base icon to the output canvas. 162 canvas->drawBitmap(base_icon, 163 (output_size - base_icon.width()) / 2, 164 (output_size - base_icon.height()) / 2); 165 166 const SkBitmap& generated_icon = canvas->getDevice()->accessBitmap(false); 167 generated_icon.deepCopyTo(&(*bitmaps)[output_size]); 168} 169 170BookmarkAppHelper::BookmarkAppHelper(ExtensionService* service, 171 WebApplicationInfo web_app_info, 172 content::WebContents* contents) 173 : web_app_info_(web_app_info), 174 crx_installer_(extensions::CrxInstaller::CreateSilent(service)) { 175 registrar_.Add(this, 176 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 177 content::Source<CrxInstaller>(crx_installer_.get())); 178 179 registrar_.Add(this, 180 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 181 content::Source<CrxInstaller>(crx_installer_.get())); 182 183 crx_installer_->set_error_on_unsupported_requirements(true); 184 185 // Add urls from the WebApplicationInfo. 186 std::vector<GURL> web_app_info_icon_urls; 187 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = 188 web_app_info_.icons.begin(); 189 it != web_app_info_.icons.end(); 190 ++it) { 191 if (it->url.is_valid()) 192 web_app_info_icon_urls.push_back(it->url); 193 } 194 195 favicon_downloader_.reset( 196 new FaviconDownloader(contents, 197 web_app_info_icon_urls, 198 base::Bind(&BookmarkAppHelper::OnIconsDownloaded, 199 base::Unretained(this)))); 200} 201 202BookmarkAppHelper::~BookmarkAppHelper() {} 203 204void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) { 205 callback_ = callback; 206 favicon_downloader_->Start(); 207} 208 209void BookmarkAppHelper::OnIconsDownloaded( 210 bool success, 211 const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { 212 // The tab has navigated away during the icon download. Cancel the bookmark 213 // app creation. 214 if (!success) { 215 favicon_downloader_.reset(); 216 callback_.Run(NULL, web_app_info_); 217 return; 218 } 219 220 // Add the downloaded icons. Extensions only allow certain icon sizes. First 221 // populate icons that match the allowed sizes exactly and then downscale 222 // remaining icons to the closest allowed size that doesn't yet have an icon. 223 std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes, 224 extension_misc::kExtensionIconSizes + 225 extension_misc::kNumExtensionIconSizes); 226 std::vector<SkBitmap> downloaded_icons; 227 for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin(); 228 map_it != bitmaps.end(); 229 ++map_it) { 230 for (std::vector<SkBitmap>::const_iterator bitmap_it = 231 map_it->second.begin(); 232 bitmap_it != map_it->second.end(); 233 ++bitmap_it) { 234 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height()) 235 continue; 236 237 downloaded_icons.push_back(*bitmap_it); 238 } 239 } 240 241 // If there are icons that don't match the accepted icon sizes, find the 242 // closest bigger icon to the accepted sizes and resize the icon to it. An 243 // icon will be resized and used for at most one size. 244 std::map<int, SkBitmap> resized_bitmaps( 245 ConstrainBitmapsToSizes(downloaded_icons, allowed_sizes)); 246 247 // Generate container icons from smaller icons. 248 const int kIconSizesToGenerate[] = {extension_misc::EXTENSION_ICON_SMALL, 249 extension_misc::EXTENSION_ICON_MEDIUM, }; 250 const std::set<int> generate_sizes( 251 kIconSizesToGenerate, 252 kIconSizesToGenerate + arraysize(kIconSizesToGenerate)); 253 254 // Only generate icons if larger icons don't exist. This means the app 255 // launcher and the taskbar will do their best downsizing large icons and 256 // these container icons are only generated as a last resort against upscaling 257 // a smaller icon. 258 if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) == 259 resized_bitmaps.end()) { 260 // Generate these from biggest to smallest so we don't end up with 261 // concentric container icons. 262 for (std::set<int>::const_reverse_iterator it = generate_sizes.rbegin(); 263 it != generate_sizes.rend(); 264 ++it) { 265 GenerateContainerIcon(&resized_bitmaps, *it); 266 } 267 } 268 269 // Populate the icon data into the WebApplicationInfo we are using to 270 // install the bookmark app. 271 for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it = 272 resized_bitmaps.begin(); 273 resized_bitmaps_it != resized_bitmaps.end(); 274 ++resized_bitmaps_it) { 275 WebApplicationInfo::IconInfo icon_info; 276 icon_info.data = resized_bitmaps_it->second; 277 icon_info.width = icon_info.data.width(); 278 icon_info.height = icon_info.data.height(); 279 web_app_info_.icons.push_back(icon_info); 280 } 281 282 // Install the app. 283 crx_installer_->InstallWebApp(web_app_info_); 284 favicon_downloader_.reset(); 285} 286 287void BookmarkAppHelper::Observe(int type, 288 const content::NotificationSource& source, 289 const content::NotificationDetails& details) { 290 switch (type) { 291 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { 292 const Extension* extension = 293 content::Details<const Extension>(details).ptr(); 294 DCHECK(extension); 295 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), 296 web_app_info_.app_url); 297 callback_.Run(extension, web_app_info_); 298 break; 299 } 300 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: 301 callback_.Run(NULL, web_app_info_); 302 break; 303 default: 304 NOTREACHED(); 305 break; 306 } 307} 308 309void CreateOrUpdateBookmarkApp(ExtensionService* service, 310 WebApplicationInfo& web_app_info) { 311 scoped_refptr<extensions::CrxInstaller> installer( 312 extensions::CrxInstaller::CreateSilent(service)); 313 installer->set_error_on_unsupported_requirements(true); 314 installer->InstallWebApp(web_app_info); 315} 316 317void GetWebApplicationInfoFromApp( 318 content::BrowserContext* browser_context, 319 const extensions::Extension* extension, 320 const base::Callback<void(const WebApplicationInfo&)> callback) { 321 if (!extension->from_bookmark()) { 322 callback.Run(WebApplicationInfo()); 323 return; 324 } 325 326 WebApplicationInfo web_app_info; 327 web_app_info.app_url = AppLaunchInfo::GetLaunchWebURL(extension); 328 web_app_info.title = base::UTF8ToUTF16(extension->non_localized_name()); 329 web_app_info.description = base::UTF8ToUTF16(extension->description()); 330 331 std::vector<extensions::ImageLoader::ImageRepresentation> info_list; 332 for (size_t i = 0; i < extension_misc::kNumExtensionIconSizes; ++i) { 333 int size = extension_misc::kExtensionIconSizes[i]; 334 extensions::ExtensionResource resource = 335 extensions::IconsInfo::GetIconResource( 336 extension, size, ExtensionIconSet::MATCH_EXACTLY); 337 if (!resource.empty()) { 338 info_list.push_back(extensions::ImageLoader::ImageRepresentation( 339 resource, 340 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 341 gfx::Size(size, size), 342 ui::SCALE_FACTOR_100P)); 343 } 344 } 345 346 extensions::ImageLoader::Get(browser_context)->LoadImageFamilyAsync( 347 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback)); 348} 349 350bool IsValidBookmarkAppUrl(const GURL& url) { 351 URLPattern origin_only_pattern(Extension::kValidWebExtentSchemes); 352 origin_only_pattern.SetMatchAllURLs(true); 353 return url.is_valid() && origin_only_pattern.MatchesURL(url); 354} 355 356} // namespace extensions 357