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.
5 #include "chrome/browser/extensions/bookmark_app_helper.h"
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"
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();
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);
44 callback.Run(web_app_info);
49 namespace extensions {
52 std::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();
60 DCHECK(it->width() == it->height());
61 ordered_bitmaps[it->width()] = *it;
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()) {
68 // Find the closest not-smaller bitmap.
69 bitmaps_it = ordered_bitmaps.lower_bound(size);
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
77 : skia::ImageOperations::Resize(
79 skia::ImageOperations::RESIZE_LANCZOS3,
84 return output_bitmaps;
88 void BookmarkAppHelper::GenerateContainerIcon(std::map<int, SkBitmap>* bitmaps,
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))
98 // This is the biggest icon smaller than |output_size|.
99 const SkBitmap& base_icon = it->second;
101 const size_t kBorderRadius = 5;
102 const size_t kColorStripHeight = 3;
103 const SkColor kBorderColor = 0xFFD5D5D5;
104 const SkColor kBackgroundColor = 0xFFFFFFFF;
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);
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(),
120 color_strip_canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size),
125 // Erase the top of the rounded rect to leave a color strip.
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),
133 // Draw each element to an output canvas.
134 scoped_ptr<SkCanvas> canvas(
135 skia::CreateBitmapCanvas(output_size, output_size, false));
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),
147 // Draw the color strip.
149 color_strip_canvas->getDevice()->accessBitmap(false), 0, 0);
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),
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);
166 const SkBitmap& generated_icon = canvas->getDevice()->accessBitmap(false);
167 generated_icon.deepCopyTo(&(*bitmaps)[output_size]);
170 BookmarkAppHelper::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)) {
176 chrome::NOTIFICATION_CRX_INSTALLER_DONE,
177 content::Source<CrxInstaller>(crx_installer_.get()));
180 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
181 content::Source<CrxInstaller>(crx_installer_.get()));
183 crx_installer_->set_error_on_unsupported_requirements(true);
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();
191 if (it->url.is_valid())
192 web_app_info_icon_urls.push_back(it->url);
195 favicon_downloader_.reset(
196 new FaviconDownloader(contents,
197 web_app_info_icon_urls,
198 base::Bind(&BookmarkAppHelper::OnIconsDownloaded,
199 base::Unretained(this))));
202 BookmarkAppHelper::~BookmarkAppHelper() {}
204 void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) {
205 callback_ = callback;
206 favicon_downloader_->Start();
209 void BookmarkAppHelper::OnIconsDownloaded(
211 const std::map<GURL, std::vector<SkBitmap> >& bitmaps) {
212 // The tab has navigated away during the icon download. Cancel the bookmark
215 favicon_downloader_.reset();
216 callback_.Run(NULL, web_app_info_);
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();
230 for (std::vector<SkBitmap>::const_iterator bitmap_it =
231 map_it->second.begin();
232 bitmap_it != map_it->second.end();
234 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height())
237 downloaded_icons.push_back(*bitmap_it);
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));
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));
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
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();
265 GenerateContainerIcon(&resized_bitmaps, *it);
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);
283 crx_installer_->InstallWebApp(web_app_info_);
284 favicon_downloader_.reset();
287 void BookmarkAppHelper::Observe(int type,
288 const content::NotificationSource& source,
289 const content::NotificationDetails& details) {
291 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
292 const Extension* extension =
293 content::Details<const Extension>(details).ptr();
295 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension),
296 web_app_info_.app_url);
297 callback_.Run(extension, web_app_info_);
300 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR:
301 callback_.Run(NULL, web_app_info_);
309 void 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);
317 void 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());
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());
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(
340 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
341 gfx::Size(size, size),
342 ui::SCALE_FACTOR_100P));
346 extensions::ImageLoader::Get(browser_context)->LoadImageFamilyAsync(
347 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback));
350 bool 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);
356 } // namespace extensions