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/tab_helper.h"
13 #include "chrome/common/extensions/extension_constants.h"
14 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
15 #include "chrome/common/extensions/manifest_handlers/icons_handler.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 "skia/ext/image_operations.h"
21 #include "skia/ext/platform_canvas.h"
22 #include "third_party/skia/include/core/SkBitmap.h"
23 #include "ui/gfx/color_analysis.h"
24 #include "ui/gfx/image/image.h"
26 namespace extensions
{
29 std::map
<int, SkBitmap
> BookmarkAppHelper::ConstrainBitmapsToSizes(
30 const std::vector
<SkBitmap
>& bitmaps
,
31 const std::set
<int>& sizes
) {
32 std::map
<int, SkBitmap
> output_bitmaps
;
33 std::map
<int, SkBitmap
> ordered_bitmaps
;
34 for (std::vector
<SkBitmap
>::const_iterator it
= bitmaps
.begin();
37 DCHECK(it
->width() == it
->height());
38 ordered_bitmaps
[it
->width()] = *it
;
41 std::set
<int>::const_iterator sizes_it
= sizes
.begin();
42 std::map
<int, SkBitmap
>::const_iterator bitmaps_it
= ordered_bitmaps
.begin();
43 while (sizes_it
!= sizes
.end() && bitmaps_it
!= ordered_bitmaps
.end()) {
45 // Find the closest not-smaller bitmap.
46 bitmaps_it
= ordered_bitmaps
.lower_bound(size
);
48 // Ensure the bitmap is valid and smaller than the next allowed size.
49 if (bitmaps_it
!= ordered_bitmaps
.end() &&
50 (sizes_it
== sizes
.end() || bitmaps_it
->second
.width() < *sizes_it
)) {
51 // Resize the bitmap if it does not exactly match the desired size.
52 output_bitmaps
[size
] = bitmaps_it
->second
.width() == size
54 : skia::ImageOperations::Resize(
56 skia::ImageOperations::RESIZE_LANCZOS3
,
61 return output_bitmaps
;
65 void BookmarkAppHelper::GenerateContainerIcon(std::map
<int, SkBitmap
>* bitmaps
,
67 std::map
<int, SkBitmap
>::const_iterator it
=
68 bitmaps
->lower_bound(output_size
);
69 // Do nothing if there is no icon smaller than the desired size or there is
70 // already an icon of |output_size|.
71 if (it
== bitmaps
->begin() || bitmaps
->count(output_size
))
75 // This is the biggest icon smaller than |output_size|.
76 const SkBitmap
& base_icon
= it
->second
;
78 const size_t kBorderRadius
= 5;
79 const size_t kColorStripHeight
= 3;
80 const SkColor kBorderColor
= 0xFFD5D5D5;
81 const SkColor kBackgroundColor
= 0xFFFFFFFF;
83 // Create a separate canvas for the color strip.
84 scoped_ptr
<SkCanvas
> color_strip_canvas(
85 skia::CreateBitmapCanvas(output_size
, output_size
, false));
86 DCHECK(color_strip_canvas
);
88 // Draw a rounded rect of the |base_icon|'s dominant color.
89 SkPaint color_strip_paint
;
90 color_utils::GridSampler sampler
;
91 color_strip_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
92 color_strip_paint
.setColor(color_utils::CalculateKMeanColorOfPNG(
93 gfx::Image::CreateFrom1xBitmap(base_icon
).As1xPNGBytes(),
97 color_strip_canvas
->drawRoundRect(SkRect::MakeWH(output_size
, output_size
),
102 // Erase the top of the rounded rect to leave a color strip.
104 clear_paint
.setColor(SK_ColorTRANSPARENT
);
105 clear_paint
.setXfermodeMode(SkXfermode::kSrc_Mode
);
106 color_strip_canvas
->drawRect(
107 SkRect::MakeWH(output_size
, output_size
- kColorStripHeight
),
110 // Draw each element to an output canvas.
111 scoped_ptr
<SkCanvas
> canvas(
112 skia::CreateBitmapCanvas(output_size
, output_size
, false));
115 // Draw the background.
116 SkPaint background_paint
;
117 background_paint
.setColor(kBackgroundColor
);
118 background_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
119 canvas
->drawRoundRect(SkRect::MakeWH(output_size
, output_size
),
124 // Draw the color strip.
126 color_strip_canvas
->getDevice()->accessBitmap(false), 0, 0);
129 SkPaint border_paint
;
130 border_paint
.setColor(kBorderColor
);
131 border_paint
.setStyle(SkPaint::kStroke_Style
);
132 border_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
133 canvas
->drawRoundRect(SkRect::MakeWH(output_size
, output_size
),
138 // Draw the centered base icon to the output canvas.
139 canvas
->drawBitmap(base_icon
,
140 (output_size
- base_icon
.width()) / 2,
141 (output_size
- base_icon
.height()) / 2);
143 const SkBitmap
& generated_icon
= canvas
->getDevice()->accessBitmap(false);
144 generated_icon
.deepCopyTo(&(*bitmaps
)[output_size
]);
147 BookmarkAppHelper::BookmarkAppHelper(ExtensionService
* service
,
148 WebApplicationInfo web_app_info
,
149 content::WebContents
* contents
)
150 : web_app_info_(web_app_info
),
151 crx_installer_(extensions::CrxInstaller::CreateSilent(service
)) {
153 chrome::NOTIFICATION_CRX_INSTALLER_DONE
,
154 content::Source
<CrxInstaller
>(crx_installer_
.get()));
157 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR
,
158 content::Source
<CrxInstaller
>(crx_installer_
.get()));
160 crx_installer_
->set_error_on_unsupported_requirements(true);
162 // Add urls from the WebApplicationInfo.
163 std::vector
<GURL
> web_app_info_icon_urls
;
164 for (std::vector
<WebApplicationInfo::IconInfo
>::const_iterator it
=
165 web_app_info_
.icons
.begin();
166 it
!= web_app_info_
.icons
.end();
168 if (it
->url
.is_valid())
169 web_app_info_icon_urls
.push_back(it
->url
);
172 favicon_downloader_
.reset(
173 new FaviconDownloader(contents
,
174 web_app_info_icon_urls
,
175 base::Bind(&BookmarkAppHelper::OnIconsDownloaded
,
176 base::Unretained(this))));
179 BookmarkAppHelper::~BookmarkAppHelper() {}
181 void BookmarkAppHelper::Create(const CreateBookmarkAppCallback
& callback
) {
182 callback_
= callback
;
183 favicon_downloader_
->Start();
186 void BookmarkAppHelper::OnIconsDownloaded(
188 const std::map
<GURL
, std::vector
<SkBitmap
> >& bitmaps
) {
189 // The tab has navigated away during the icon download. Cancel the bookmark
192 favicon_downloader_
.reset();
193 callback_
.Run(NULL
, web_app_info_
);
197 // Add the downloaded icons. Extensions only allow certain icon sizes. First
198 // populate icons that match the allowed sizes exactly and then downscale
199 // remaining icons to the closest allowed size that doesn't yet have an icon.
200 std::set
<int> allowed_sizes(extension_misc::kExtensionIconSizes
,
201 extension_misc::kExtensionIconSizes
+
202 extension_misc::kNumExtensionIconSizes
);
203 std::vector
<SkBitmap
> downloaded_icons
;
204 for (FaviconDownloader::FaviconMap::const_iterator map_it
= bitmaps
.begin();
205 map_it
!= bitmaps
.end();
207 for (std::vector
<SkBitmap
>::const_iterator bitmap_it
=
208 map_it
->second
.begin();
209 bitmap_it
!= map_it
->second
.end();
211 if (bitmap_it
->empty() || bitmap_it
->width() != bitmap_it
->height())
214 downloaded_icons
.push_back(*bitmap_it
);
218 // If there are icons that don't match the accepted icon sizes, find the
219 // closest bigger icon to the accepted sizes and resize the icon to it. An
220 // icon will be resized and used for at most one size.
221 std::map
<int, SkBitmap
> resized_bitmaps(
222 ConstrainBitmapsToSizes(downloaded_icons
, allowed_sizes
));
224 // Generate container icons from smaller icons.
225 const int kIconSizesToGenerate
[] = {extension_misc::EXTENSION_ICON_SMALL
,
226 extension_misc::EXTENSION_ICON_MEDIUM
, };
227 const std::set
<int> generate_sizes(
228 kIconSizesToGenerate
,
229 kIconSizesToGenerate
+ arraysize(kIconSizesToGenerate
));
231 // Only generate icons if larger icons don't exist. This means the app
232 // launcher and the taskbar will do their best downsizing large icons and
233 // these container icons are only generated as a last resort against upscaling
235 if (resized_bitmaps
.lower_bound(*generate_sizes
.rbegin()) ==
236 resized_bitmaps
.end()) {
237 // Generate these from biggest to smallest so we don't end up with
238 // concentric container icons.
239 for (std::set
<int>::const_reverse_iterator it
= generate_sizes
.rbegin();
240 it
!= generate_sizes
.rend();
242 GenerateContainerIcon(&resized_bitmaps
, *it
);
246 // Populate the icon data into the WebApplicationInfo we are using to
247 // install the bookmark app.
248 for (std::map
<int, SkBitmap
>::const_iterator resized_bitmaps_it
=
249 resized_bitmaps
.begin();
250 resized_bitmaps_it
!= resized_bitmaps
.end();
251 ++resized_bitmaps_it
) {
252 WebApplicationInfo::IconInfo icon_info
;
253 icon_info
.data
= resized_bitmaps_it
->second
;
254 icon_info
.width
= icon_info
.data
.width();
255 icon_info
.height
= icon_info
.data
.height();
256 web_app_info_
.icons
.push_back(icon_info
);
260 crx_installer_
->InstallWebApp(web_app_info_
);
261 favicon_downloader_
.reset();
264 void BookmarkAppHelper::Observe(int type
,
265 const content::NotificationSource
& source
,
266 const content::NotificationDetails
& details
) {
268 case chrome::NOTIFICATION_CRX_INSTALLER_DONE
: {
269 const Extension
* extension
=
270 content::Details
<const Extension
>(details
).ptr();
272 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension
),
273 web_app_info_
.app_url
);
274 callback_
.Run(extension
, web_app_info_
);
277 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR
:
278 callback_
.Run(NULL
, web_app_info_
);
286 void CreateOrUpdateBookmarkApp(ExtensionService
* service
,
287 WebApplicationInfo
& web_app_info
) {
288 scoped_refptr
<extensions::CrxInstaller
> installer(
289 extensions::CrxInstaller::CreateSilent(service
));
290 installer
->set_error_on_unsupported_requirements(true);
291 installer
->InstallWebApp(web_app_info
);
294 } // namespace extensions