a44c002a1d22e2dee5d1f8573234960223eded56
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / bookmark_app_helper.cc
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
29 namespace {
30
31 void 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
49 namespace extensions {
50
51 // static
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();
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
88 void 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
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)) {
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
202 BookmarkAppHelper::~BookmarkAppHelper() {}
203
204 void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) {
205   callback_ = callback;
206   favicon_downloader_->Start();
207 }
208
209 void 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
287 void 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
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);
315 }
316
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());
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
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);
354 }
355
356 }  // namespace extensions