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