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