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