Home | History | Annotate | Download | only in extensions
      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:
     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) {}
     52   virtual ~GeneratedIconImageSource() {}
     53 
     54  private:
     55   // gfx::CanvasImageSource overrides:
     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 
    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
    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
    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 
    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 
    206 BookmarkAppHelper::~BookmarkAppHelper() {}
    207 
    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 
    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 
    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 
    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 
    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 
    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