• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <cctype>
8 
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/extensions/crx_installer.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/favicon_downloader.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/common/extensions/extension_constants.h"
16 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
17 #include "content/public/browser/notification_service.h"
18 #include "content/public/browser/notification_source.h"
19 #include "content/public/browser/web_contents.h"
20 #include "extensions/browser/image_loader.h"
21 #include "extensions/common/constants.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/manifest_handlers/icons_handler.h"
24 #include "extensions/common/url_pattern.h"
25 #include "grit/platform_locale_settings.h"
26 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
27 #include "skia/ext/image_operations.h"
28 #include "skia/ext/platform_canvas.h"
29 #include "third_party/skia/include/core/SkBitmap.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/canvas.h"
33 #include "ui/gfx/color_analysis.h"
34 #include "ui/gfx/color_utils.h"
35 #include "ui/gfx/font.h"
36 #include "ui/gfx/font_list.h"
37 #include "ui/gfx/image/canvas_image_source.h"
38 #include "ui/gfx/image/image.h"
39 #include "ui/gfx/image/image_family.h"
40 #include "ui/gfx/rect.h"
41 
42 namespace {
43 
44 // Overlays a shortcut icon over the bottom left corner of a given image.
45 class GeneratedIconImageSource : public gfx::CanvasImageSource {
46  public:
GeneratedIconImageSource(char letter,SkColor color,int output_size)47   explicit GeneratedIconImageSource(char letter, SkColor color, int output_size)
48       : gfx::CanvasImageSource(gfx::Size(output_size, output_size), false),
49         letter_(letter),
50         color_(color),
51         output_size_(output_size) {}
~GeneratedIconImageSource()52   virtual ~GeneratedIconImageSource() {}
53 
54  private:
55   // gfx::CanvasImageSource overrides:
Draw(gfx::Canvas * canvas)56   virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
57     const unsigned char kLuminanceThreshold = 190;
58     const int icon_size = output_size_ * 3 / 4;
59     const int icon_inset = output_size_ / 8;
60     const size_t border_radius = output_size_ / 16;
61     const size_t font_size = output_size_ * 7 / 16;
62 
63     std::string font_name =
64         l10n_util::GetStringUTF8(IDS_SANS_SERIF_FONT_FAMILY);
65 #if defined(OS_CHROMEOS)
66     const std::string kChromeOSFontFamily = "Noto Sans";
67     font_name = kChromeOSFontFamily;
68 #endif
69 
70     // Draw a rounded rect of the given |color|.
71     SkPaint background_paint;
72     background_paint.setFlags(SkPaint::kAntiAlias_Flag);
73     background_paint.setColor(color_);
74 
75     gfx::Rect icon_rect(icon_inset, icon_inset, icon_size, icon_size);
76     canvas->DrawRoundRect(icon_rect, border_radius, background_paint);
77 
78     // The text rect's size needs to be odd to center the text correctly.
79     gfx::Rect text_rect(icon_inset, icon_inset, icon_size + 1, icon_size + 1);
80     // Draw the letter onto the rounded rect. The letter's color depends on the
81     // luminance of |color|.
82     unsigned char luminance = color_utils::GetLuminanceForColor(color_);
83     canvas->DrawStringRectWithFlags(
84         base::string16(1, std::toupper(letter_)),
85         gfx::FontList(gfx::Font(font_name, font_size)),
86         luminance > kLuminanceThreshold ? SK_ColorBLACK : SK_ColorWHITE,
87         text_rect,
88         gfx::Canvas::TEXT_ALIGN_CENTER);
89   }
90 
91   char letter_;
92 
93   SkColor color_;
94 
95   int output_size_;
96 
97   DISALLOW_COPY_AND_ASSIGN(GeneratedIconImageSource);
98 };
99 
OnIconsLoaded(WebApplicationInfo web_app_info,const base::Callback<void (const WebApplicationInfo &)> callback,const gfx::ImageFamily & image_family)100 void OnIconsLoaded(
101     WebApplicationInfo web_app_info,
102     const base::Callback<void(const WebApplicationInfo&)> callback,
103     const gfx::ImageFamily& image_family) {
104   for (gfx::ImageFamily::const_iterator it = image_family.begin();
105        it != image_family.end();
106        ++it) {
107     WebApplicationInfo::IconInfo icon_info;
108     icon_info.data = *it->ToSkBitmap();
109     icon_info.width = icon_info.data.width();
110     icon_info.height = icon_info.data.height();
111     web_app_info.icons.push_back(icon_info);
112   }
113   callback.Run(web_app_info);
114 }
115 
116 }  // namespace
117 
118 namespace extensions {
119 
120 // static
ConstrainBitmapsToSizes(const std::vector<SkBitmap> & bitmaps,const std::set<int> & sizes)121 std::map<int, SkBitmap> BookmarkAppHelper::ConstrainBitmapsToSizes(
122     const std::vector<SkBitmap>& bitmaps,
123     const std::set<int>& sizes) {
124   std::map<int, SkBitmap> output_bitmaps;
125   std::map<int, SkBitmap> ordered_bitmaps;
126   for (std::vector<SkBitmap>::const_iterator it = bitmaps.begin();
127        it != bitmaps.end();
128        ++it) {
129     DCHECK(it->width() == it->height());
130     ordered_bitmaps[it->width()] = *it;
131   }
132 
133   std::set<int>::const_iterator sizes_it = sizes.begin();
134   std::map<int, SkBitmap>::const_iterator bitmaps_it = ordered_bitmaps.begin();
135   while (sizes_it != sizes.end() && bitmaps_it != ordered_bitmaps.end()) {
136     int size = *sizes_it;
137     // Find the closest not-smaller bitmap.
138     bitmaps_it = ordered_bitmaps.lower_bound(size);
139     ++sizes_it;
140     // Ensure the bitmap is valid and smaller than the next allowed size.
141     if (bitmaps_it != ordered_bitmaps.end() &&
142         (sizes_it == sizes.end() || bitmaps_it->second.width() < *sizes_it)) {
143       // Resize the bitmap if it does not exactly match the desired size.
144       output_bitmaps[size] = bitmaps_it->second.width() == size
145                                  ? bitmaps_it->second
146                                  : skia::ImageOperations::Resize(
147                                        bitmaps_it->second,
148                                        skia::ImageOperations::RESIZE_LANCZOS3,
149                                        size,
150                                        size);
151     }
152   }
153   return output_bitmaps;
154 }
155 
156 // static
GenerateIcon(std::map<int,SkBitmap> * bitmaps,int output_size,SkColor color,char letter)157 void BookmarkAppHelper::GenerateIcon(std::map<int, SkBitmap>* bitmaps,
158                                      int output_size,
159                                      SkColor color,
160                                      char letter) {
161   // Do nothing if there is already an icon of |output_size|.
162   if (bitmaps->count(output_size))
163     return;
164 
165   gfx::ImageSkia icon_image(
166       new GeneratedIconImageSource(letter, color, output_size),
167       gfx::Size(output_size, output_size));
168   icon_image.bitmap()->deepCopyTo(&(*bitmaps)[output_size]);
169 }
170 
BookmarkAppHelper(ExtensionService * service,WebApplicationInfo web_app_info,content::WebContents * contents)171 BookmarkAppHelper::BookmarkAppHelper(ExtensionService* service,
172                                      WebApplicationInfo web_app_info,
173                                      content::WebContents* contents)
174     : web_app_info_(web_app_info),
175       crx_installer_(extensions::CrxInstaller::CreateSilent(service)) {
176   registrar_.Add(this,
177                  chrome::NOTIFICATION_CRX_INSTALLER_DONE,
178                  content::Source<CrxInstaller>(crx_installer_.get()));
179 
180   registrar_.Add(this,
181                  chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
182                  content::Source<CrxInstaller>(crx_installer_.get()));
183 
184   crx_installer_->set_error_on_unsupported_requirements(true);
185 
186   if (!contents)
187     return;
188 
189   // Add urls from the WebApplicationInfo.
190   std::vector<GURL> web_app_info_icon_urls;
191   for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it =
192            web_app_info_.icons.begin();
193        it != web_app_info_.icons.end();
194        ++it) {
195     if (it->url.is_valid())
196       web_app_info_icon_urls.push_back(it->url);
197   }
198 
199   favicon_downloader_.reset(
200       new FaviconDownloader(contents,
201                             web_app_info_icon_urls,
202                             base::Bind(&BookmarkAppHelper::OnIconsDownloaded,
203                                        base::Unretained(this))));
204 }
205 
~BookmarkAppHelper()206 BookmarkAppHelper::~BookmarkAppHelper() {}
207 
Create(const CreateBookmarkAppCallback & callback)208 void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) {
209   callback_ = callback;
210 
211   if (favicon_downloader_.get())
212     favicon_downloader_->Start();
213   else
214     OnIconsDownloaded(true, std::map<GURL, std::vector<SkBitmap> >());
215 }
216 
OnIconsDownloaded(bool success,const std::map<GURL,std::vector<SkBitmap>> & bitmaps)217 void BookmarkAppHelper::OnIconsDownloaded(
218     bool success,
219     const std::map<GURL, std::vector<SkBitmap> >& bitmaps) {
220   // The tab has navigated away during the icon download. Cancel the bookmark
221   // app creation.
222   if (!success) {
223     favicon_downloader_.reset();
224     callback_.Run(NULL, web_app_info_);
225     return;
226   }
227 
228   // Add the downloaded icons. Extensions only allow certain icon sizes. First
229   // populate icons that match the allowed sizes exactly and then downscale
230   // remaining icons to the closest allowed size that doesn't yet have an icon.
231   std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes,
232                               extension_misc::kExtensionIconSizes +
233                                   extension_misc::kNumExtensionIconSizes);
234   std::vector<SkBitmap> downloaded_icons;
235   for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin();
236        map_it != bitmaps.end();
237        ++map_it) {
238     for (std::vector<SkBitmap>::const_iterator bitmap_it =
239              map_it->second.begin();
240          bitmap_it != map_it->second.end();
241          ++bitmap_it) {
242       if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height())
243         continue;
244 
245       downloaded_icons.push_back(*bitmap_it);
246     }
247   }
248 
249   // Add all existing icons from WebApplicationInfo.
250   for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it =
251            web_app_info_.icons.begin();
252        it != web_app_info_.icons.end();
253        ++it) {
254     const SkBitmap& icon = it->data;
255     if (!icon.drawsNothing() && icon.width() == icon.height())
256       downloaded_icons.push_back(icon);
257   }
258 
259   web_app_info_.icons.clear();
260 
261   // If there are icons that don't match the accepted icon sizes, find the
262   // closest bigger icon to the accepted sizes and resize the icon to it. An
263   // icon will be resized and used for at most one size.
264   std::map<int, SkBitmap> resized_bitmaps(
265       ConstrainBitmapsToSizes(downloaded_icons, allowed_sizes));
266 
267   // Generate container icons from smaller icons.
268   const int kIconSizesToGenerate[] = {extension_misc::EXTENSION_ICON_SMALL,
269                                       extension_misc::EXTENSION_ICON_MEDIUM, };
270   const std::set<int> generate_sizes(
271       kIconSizesToGenerate,
272       kIconSizesToGenerate + arraysize(kIconSizesToGenerate));
273 
274   // Only generate icons if larger icons don't exist. This means the app
275   // launcher and the taskbar will do their best downsizing large icons and
276   // these icons are only generated as a last resort against upscaling a smaller
277   // icon.
278   if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) ==
279       resized_bitmaps.end()) {
280     GURL app_url = web_app_info_.app_url;
281 
282     // The letter that will be painted on the generated icon.
283     char icon_letter = ' ';
284     std::string domain_and_registry(
285         net::registry_controlled_domains::GetDomainAndRegistry(
286             app_url,
287             net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
288     if (!domain_and_registry.empty()) {
289       icon_letter = domain_and_registry[0];
290     } else if (!app_url.host().empty()) {
291       icon_letter = app_url.host()[0];
292     }
293 
294     // The color that will be used for the icon's background.
295     SkColor background_color = SK_ColorBLACK;
296     if (resized_bitmaps.size()) {
297       color_utils::GridSampler sampler;
298       background_color = color_utils::CalculateKMeanColorOfBitmap(
299           resized_bitmaps.begin()->second);
300     }
301 
302     for (std::set<int>::const_iterator it = generate_sizes.begin();
303          it != generate_sizes.end();
304          ++it) {
305       GenerateIcon(&resized_bitmaps, *it, background_color, icon_letter);
306       // Also generate the 2x resource for this size.
307       GenerateIcon(&resized_bitmaps, *it * 2, background_color, icon_letter);
308     }
309   }
310 
311   // Populate the icon data into the WebApplicationInfo we are using to
312   // install the bookmark app.
313   for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it =
314            resized_bitmaps.begin();
315        resized_bitmaps_it != resized_bitmaps.end();
316        ++resized_bitmaps_it) {
317     WebApplicationInfo::IconInfo icon_info;
318     icon_info.data = resized_bitmaps_it->second;
319     icon_info.width = icon_info.data.width();
320     icon_info.height = icon_info.data.height();
321     web_app_info_.icons.push_back(icon_info);
322   }
323 
324   // Install the app.
325   crx_installer_->InstallWebApp(web_app_info_);
326   favicon_downloader_.reset();
327 }
328 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)329 void BookmarkAppHelper::Observe(int type,
330                                 const content::NotificationSource& source,
331                                 const content::NotificationDetails& details) {
332   switch (type) {
333     case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
334       const Extension* extension =
335           content::Details<const Extension>(details).ptr();
336       DCHECK(extension);
337       DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension),
338                 web_app_info_.app_url);
339       callback_.Run(extension, web_app_info_);
340       break;
341     }
342     case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR:
343       callback_.Run(NULL, web_app_info_);
344       break;
345     default:
346       NOTREACHED();
347       break;
348   }
349 }
350 
CreateOrUpdateBookmarkApp(ExtensionService * service,WebApplicationInfo & web_app_info)351 void CreateOrUpdateBookmarkApp(ExtensionService* service,
352                                WebApplicationInfo& web_app_info) {
353   scoped_refptr<extensions::CrxInstaller> installer(
354       extensions::CrxInstaller::CreateSilent(service));
355   installer->set_error_on_unsupported_requirements(true);
356   installer->InstallWebApp(web_app_info);
357 }
358 
GetWebApplicationInfoFromApp(content::BrowserContext * browser_context,const extensions::Extension * extension,const base::Callback<void (const WebApplicationInfo &)> callback)359 void GetWebApplicationInfoFromApp(
360     content::BrowserContext* browser_context,
361     const extensions::Extension* extension,
362     const base::Callback<void(const WebApplicationInfo&)> callback) {
363   if (!extension->from_bookmark()) {
364     callback.Run(WebApplicationInfo());
365     return;
366   }
367 
368   WebApplicationInfo web_app_info;
369   web_app_info.app_url = AppLaunchInfo::GetLaunchWebURL(extension);
370   web_app_info.title = base::UTF8ToUTF16(extension->non_localized_name());
371   web_app_info.description = base::UTF8ToUTF16(extension->description());
372 
373   std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
374   for (size_t i = 0; i < extension_misc::kNumExtensionIconSizes; ++i) {
375     int size = extension_misc::kExtensionIconSizes[i];
376     extensions::ExtensionResource resource =
377         extensions::IconsInfo::GetIconResource(
378             extension, size, ExtensionIconSet::MATCH_EXACTLY);
379     if (!resource.empty()) {
380       info_list.push_back(extensions::ImageLoader::ImageRepresentation(
381           resource,
382           extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
383           gfx::Size(size, size),
384           ui::SCALE_FACTOR_100P));
385     }
386   }
387 
388   extensions::ImageLoader::Get(browser_context)->LoadImageFamilyAsync(
389       extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback));
390 }
391 
IsValidBookmarkAppUrl(const GURL & url)392 bool IsValidBookmarkAppUrl(const GURL& url) {
393   URLPattern origin_only_pattern(Extension::kValidWebExtentSchemes);
394   origin_only_pattern.SetMatchAllURLs(true);
395   return url.is_valid() && origin_only_pattern.MatchesURL(url);
396 }
397 
398 }  // namespace extensions
399