Home | History | Annotate | Download | only in web_applications
      1 // Copyright (c) 2012 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/web_applications/web_app.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/file_util.h"
     10 #include "base/i18n/file_util_icu.h"
     11 #include "base/prefs/pref_service.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/threading/thread.h"
     15 #include "chrome/browser/extensions/extension_ui_util.h"
     16 #include "chrome/browser/extensions/tab_helper.h"
     17 #include "chrome/browser/favicon/favicon_tab_helper.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/common/chrome_constants.h"
     20 #include "chrome/common/chrome_version_info.h"
     21 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     22 #include "chrome/common/pref_names.h"
     23 #include "content/public/browser/browser_thread.h"
     24 #include "extensions/browser/extension_registry.h"
     25 #include "extensions/browser/image_loader.h"
     26 #include "extensions/common/constants.h"
     27 #include "extensions/common/extension.h"
     28 #include "extensions/common/extension_set.h"
     29 #include "extensions/common/manifest_handlers/icons_handler.h"
     30 #include "grit/theme_resources.h"
     31 #include "skia/ext/image_operations.h"
     32 #include "third_party/skia/include/core/SkBitmap.h"
     33 #include "ui/base/resource/resource_bundle.h"
     34 #include "ui/gfx/image/image.h"
     35 #include "ui/gfx/image/image_family.h"
     36 #include "ui/gfx/image/image_skia.h"
     37 #include "url/url_constants.h"
     38 
     39 #if defined(OS_WIN)
     40 #include "ui/gfx/icon_util.h"
     41 #endif
     42 
     43 using content::BrowserThread;
     44 
     45 namespace {
     46 
     47 #if defined(OS_MACOSX)
     48 const int kDesiredSizes[] = {16, 32, 128, 256, 512};
     49 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
     50 #elif defined(OS_LINUX)
     51 // Linux supports icons of any size. FreeDesktop Icon Theme Specification states
     52 // that "Minimally you should install a 48x48 icon in the hicolor theme."
     53 const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
     54 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
     55 #elif defined(OS_WIN)
     56 const int* kDesiredSizes = IconUtil::kIconDimensions;
     57 const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
     58 #else
     59 const int kDesiredSizes[] = {32};
     60 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
     61 #endif
     62 
     63 #if defined(TOOLKIT_VIEWS)
     64 // Predicator for sorting images from largest to smallest.
     65 bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
     66                   const WebApplicationInfo::IconInfo& right) {
     67   return left.width < right.width;
     68 }
     69 #endif
     70 
     71 base::FilePath GetShortcutDataDir(const web_app::ShortcutInfo& shortcut_info) {
     72   return web_app::GetWebAppDataDirectory(shortcut_info.profile_path,
     73                                          shortcut_info.extension_id,
     74                                          shortcut_info.url);
     75 }
     76 
     77 void CreateShortcutsWithInfo(
     78     web_app::ShortcutCreationReason reason,
     79     const web_app::ShortcutLocations& locations,
     80     const web_app::ShortcutInfo& shortcut_info,
     81     const extensions::FileHandlersInfo& file_handlers_info) {
     82   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     83 
     84   BrowserThread::PostTask(
     85       BrowserThread::FILE,
     86       FROM_HERE,
     87       base::Bind(
     88           base::IgnoreResult(&web_app::internals::CreatePlatformShortcuts),
     89           GetShortcutDataDir(shortcut_info),
     90           shortcut_info, file_handlers_info, locations, reason));
     91 }
     92 
     93 void UpdateAllShortcutsForShortcutInfo(
     94     const base::string16& old_app_title,
     95     const web_app::ShortcutInfo& shortcut_info,
     96     const extensions::FileHandlersInfo& file_handlers_info) {
     97   BrowserThread::PostTask(
     98       BrowserThread::FILE,
     99       FROM_HERE,
    100       base::Bind(&web_app::internals::UpdatePlatformShortcuts,
    101                  GetShortcutDataDir(shortcut_info),
    102                  old_app_title, shortcut_info, file_handlers_info));
    103 }
    104 
    105 void OnImageLoaded(web_app::ShortcutInfo shortcut_info,
    106                    extensions::FileHandlersInfo file_handlers_info,
    107                    web_app::InfoCallback callback,
    108                    const gfx::ImageFamily& image_family) {
    109   // If the image failed to load (e.g. if the resource being loaded was empty)
    110   // use the standard application icon.
    111   if (image_family.empty()) {
    112     gfx::Image default_icon =
    113         ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
    114     int size = kDesiredSizes[kNumDesiredSizes - 1];
    115     SkBitmap bmp = skia::ImageOperations::Resize(
    116           *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
    117           size, size);
    118     gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bmp);
    119     // We are on the UI thread, and this image is needed from the FILE thread,
    120     // for creating shortcut icon files.
    121     image_skia.MakeThreadSafe();
    122     shortcut_info.favicon.Add(gfx::Image(image_skia));
    123   } else {
    124     shortcut_info.favicon = image_family;
    125   }
    126 
    127   callback.Run(shortcut_info, file_handlers_info);
    128 }
    129 
    130 void IgnoreFileHandlersInfo(
    131     const web_app::ShortcutInfoCallback& shortcut_info_callback,
    132     const web_app::ShortcutInfo& shortcut_info,
    133     const extensions::FileHandlersInfo& file_handlers_info) {
    134   shortcut_info_callback.Run(shortcut_info);
    135 }
    136 
    137 }  // namespace
    138 
    139 namespace web_app {
    140 
    141 // The following string is used to build the directory name for
    142 // shortcuts to chrome applications (the kind which are installed
    143 // from a CRX).  Application shortcuts to URLs use the {host}_{path}
    144 // for the name of this directory.  Hosts can't include an underscore.
    145 // By starting this string with an underscore, we ensure that there
    146 // are no naming conflicts.
    147 static const char kCrxAppPrefix[] = "_crx_";
    148 
    149 namespace internals {
    150 
    151 void GetInfoForApp(const extensions::Extension* extension,
    152                    Profile* profile,
    153                    const InfoCallback& callback) {
    154   web_app::ShortcutInfo shortcut_info =
    155       web_app::ShortcutInfoForExtensionAndProfile(extension, profile);
    156   const std::vector<extensions::FileHandlerInfo>* file_handlers =
    157       extensions::FileHandlers::GetFileHandlers(extension);
    158   extensions::FileHandlersInfo file_handlers_info =
    159       file_handlers ? *file_handlers : extensions::FileHandlersInfo();
    160 
    161   std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
    162   for (size_t i = 0; i < kNumDesiredSizes; ++i) {
    163     int size = kDesiredSizes[i];
    164     extensions::ExtensionResource resource =
    165         extensions::IconsInfo::GetIconResource(
    166             extension, size, ExtensionIconSet::MATCH_EXACTLY);
    167     if (!resource.empty()) {
    168       info_list.push_back(extensions::ImageLoader::ImageRepresentation(
    169           resource,
    170           extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
    171           gfx::Size(size, size),
    172           ui::SCALE_FACTOR_100P));
    173     }
    174   }
    175 
    176   if (info_list.empty()) {
    177     size_t i = kNumDesiredSizes - 1;
    178     int size = kDesiredSizes[i];
    179 
    180     // If there is no icon at the desired sizes, we will resize what we can get.
    181     // Making a large icon smaller is preferred to making a small icon larger,
    182     // so look for a larger icon first:
    183     extensions::ExtensionResource resource =
    184         extensions::IconsInfo::GetIconResource(
    185             extension, size, ExtensionIconSet::MATCH_BIGGER);
    186     if (resource.empty()) {
    187       resource = extensions::IconsInfo::GetIconResource(
    188           extension, size, ExtensionIconSet::MATCH_SMALLER);
    189     }
    190     info_list.push_back(extensions::ImageLoader::ImageRepresentation(
    191         resource,
    192         extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
    193         gfx::Size(size, size),
    194         ui::SCALE_FACTOR_100P));
    195   }
    196 
    197   // |info_list| may still be empty at this point, in which case
    198   // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
    199   // image and exit immediately.
    200   extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
    201       extension,
    202       info_list,
    203       base::Bind(&OnImageLoaded, shortcut_info, file_handlers_info, callback));
    204 }
    205 
    206 base::FilePath GetSanitizedFileName(const base::string16& name) {
    207 #if defined(OS_WIN)
    208   base::string16 file_name = name;
    209 #else
    210   std::string file_name = base::UTF16ToUTF8(name);
    211 #endif
    212   file_util::ReplaceIllegalCharactersInPath(&file_name, '_');
    213   return base::FilePath(file_name);
    214 }
    215 
    216 bool CreateShortcutsOnFileThread(ShortcutCreationReason reason,
    217                                  const ShortcutLocations& locations,
    218                                  const ShortcutInfo& shortcut_info) {
    219   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    220 
    221   return CreatePlatformShortcuts(
    222       GetShortcutDataDir(shortcut_info),
    223       shortcut_info, extensions::FileHandlersInfo(), locations, reason);
    224 }
    225 
    226 }  // namespace internals
    227 
    228 ShortcutInfo::ShortcutInfo()
    229     : is_platform_app(false) {
    230 }
    231 
    232 ShortcutInfo::~ShortcutInfo() {}
    233 
    234 ShortcutLocations::ShortcutLocations()
    235     : on_desktop(false),
    236       applications_menu_location(APP_MENU_LOCATION_NONE),
    237       in_quick_launch_bar(false) {
    238 }
    239 
    240 void GetShortcutInfoForTab(content::WebContents* web_contents,
    241                            ShortcutInfo* info) {
    242   DCHECK(info);  // Must provide a valid info.
    243 
    244   const FaviconTabHelper* favicon_tab_helper =
    245       FaviconTabHelper::FromWebContents(web_contents);
    246   const extensions::TabHelper* extensions_tab_helper =
    247       extensions::TabHelper::FromWebContents(web_contents);
    248   const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info();
    249 
    250   info->url = app_info.app_url.is_empty() ? web_contents->GetURL() :
    251                                             app_info.app_url;
    252   info->title = app_info.title.empty() ?
    253       (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) :
    254                                           web_contents->GetTitle()) :
    255       app_info.title;
    256   info->description = app_info.description;
    257   info->favicon.Add(favicon_tab_helper->GetFavicon());
    258 
    259   Profile* profile =
    260       Profile::FromBrowserContext(web_contents->GetBrowserContext());
    261   info->profile_path = profile->GetPath();
    262 }
    263 
    264 #if !defined(OS_WIN)
    265 void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
    266 #endif
    267 
    268 ShortcutInfo ShortcutInfoForExtensionAndProfile(
    269     const extensions::Extension* app, Profile* profile) {
    270   ShortcutInfo shortcut_info;
    271   shortcut_info.extension_id = app->id();
    272   shortcut_info.is_platform_app = app->is_platform_app();
    273   shortcut_info.url = extensions::AppLaunchInfo::GetLaunchWebURL(app);
    274   shortcut_info.title = base::UTF8ToUTF16(app->name());
    275   shortcut_info.description = base::UTF8ToUTF16(app->description());
    276   shortcut_info.extension_path = app->path();
    277   shortcut_info.profile_path = profile->GetPath();
    278   shortcut_info.profile_name =
    279       profile->GetPrefs()->GetString(prefs::kProfileName);
    280   return shortcut_info;
    281 }
    282 
    283 void UpdateShortcutInfoAndIconForApp(const extensions::Extension* extension,
    284                                      Profile* profile,
    285                                      const ShortcutInfoCallback& callback) {
    286   web_app::internals::GetInfoForApp(
    287       extension, profile, base::Bind(&IgnoreFileHandlersInfo, callback));
    288 }
    289 
    290 bool ShouldCreateShortcutFor(Profile* profile,
    291                              const extensions::Extension* extension) {
    292   return extension->is_platform_app() &&
    293          extension->location() != extensions::Manifest::COMPONENT &&
    294          extensions::ui_util::CanDisplayInAppLauncher(extension, profile);
    295 }
    296 
    297 base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
    298                                       const std::string& extension_id,
    299                                       const GURL& url) {
    300   DCHECK(!profile_path.empty());
    301   base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
    302 
    303   if (!extension_id.empty()) {
    304     return app_data_dir.AppendASCII(
    305         GenerateApplicationNameFromExtensionId(extension_id));
    306   }
    307 
    308   std::string host(url.host());
    309   std::string scheme(url.has_scheme() ? url.scheme() : "http");
    310   std::string port(url.has_port() ? url.port() : "80");
    311   std::string scheme_port(scheme + "_" + port);
    312 
    313 #if defined(OS_WIN)
    314   base::FilePath::StringType host_path(base::UTF8ToUTF16(host));
    315   base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port));
    316 #elif defined(OS_POSIX)
    317   base::FilePath::StringType host_path(host);
    318   base::FilePath::StringType scheme_port_path(scheme_port);
    319 #endif
    320 
    321   return app_data_dir.Append(host_path).Append(scheme_port_path);
    322 }
    323 
    324 base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
    325                                       const extensions::Extension& extension) {
    326   return GetWebAppDataDirectory(
    327       profile_path,
    328       extension.id(),
    329       GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension)));
    330 }
    331 
    332 std::string GenerateApplicationNameFromInfo(const ShortcutInfo& shortcut_info) {
    333   if (!shortcut_info.extension_id.empty())
    334     return GenerateApplicationNameFromExtensionId(shortcut_info.extension_id);
    335   else
    336     return GenerateApplicationNameFromURL(shortcut_info.url);
    337 }
    338 
    339 std::string GenerateApplicationNameFromURL(const GURL& url) {
    340   std::string t;
    341   t.append(url.host());
    342   t.append("_");
    343   t.append(url.path());
    344   return t;
    345 }
    346 
    347 std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
    348   std::string t(kCrxAppPrefix);
    349   t.append(id);
    350   return t;
    351 }
    352 
    353 std::string GetExtensionIdFromApplicationName(const std::string& app_name) {
    354   std::string prefix(kCrxAppPrefix);
    355   if (app_name.substr(0, prefix.length()) != prefix)
    356     return std::string();
    357   return app_name.substr(prefix.length());
    358 }
    359 
    360 void CreateShortcutsForShortcutInfo(ShortcutCreationReason reason,
    361                                     const ShortcutLocations& locations,
    362                                     const ShortcutInfo& shortcut_info) {
    363   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    364 
    365   BrowserThread::PostTask(
    366       BrowserThread::FILE,
    367       FROM_HERE,
    368       base::Bind(
    369           base::IgnoreResult(&web_app::internals::CreateShortcutsOnFileThread),
    370           reason, locations, shortcut_info));
    371 }
    372 
    373 void CreateShortcuts(ShortcutCreationReason reason,
    374                      const ShortcutLocations& locations,
    375                      Profile* profile,
    376                      const extensions::Extension* app) {
    377   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    378 
    379   if (!ShouldCreateShortcutFor(profile, app))
    380     return;
    381 
    382   internals::GetInfoForApp(
    383       app, profile, base::Bind(&CreateShortcutsWithInfo, reason, locations));
    384 }
    385 
    386 void DeleteAllShortcuts(Profile* profile, const extensions::Extension* app) {
    387   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    388 
    389   ShortcutInfo shortcut_info =
    390       ShortcutInfoForExtensionAndProfile(app, profile);
    391   BrowserThread::PostTask(
    392       BrowserThread::FILE,
    393       FROM_HERE,
    394       base::Bind(&web_app::internals::DeletePlatformShortcuts,
    395                  GetShortcutDataDir(shortcut_info), shortcut_info));
    396 }
    397 
    398 void UpdateAllShortcuts(const base::string16& old_app_title,
    399                         Profile* profile,
    400                         const extensions::Extension* app) {
    401   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    402 
    403   internals::GetInfoForApp(
    404       app,
    405       profile,
    406       base::Bind(&UpdateAllShortcutsForShortcutInfo, old_app_title));
    407 }
    408 
    409 bool IsValidUrl(const GURL& url) {
    410   static const char* const kValidUrlSchemes[] = {
    411       url::kFileScheme,
    412       url::kFileSystemScheme,
    413       url::kFtpScheme,
    414       url::kHttpScheme,
    415       url::kHttpsScheme,
    416       extensions::kExtensionScheme,
    417   };
    418 
    419   for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
    420     if (url.SchemeIs(kValidUrlSchemes[i]))
    421       return true;
    422   }
    423 
    424   return false;
    425 }
    426 
    427 #if defined(TOOLKIT_VIEWS)
    428 void GetIconsInfo(const WebApplicationInfo& app_info,
    429                   IconInfoList* icons) {
    430   DCHECK(icons);
    431 
    432   icons->clear();
    433   for (size_t i = 0; i < app_info.icons.size(); ++i) {
    434     // We only take square shaped icons (i.e. width == height).
    435     if (app_info.icons[i].width == app_info.icons[i].height) {
    436       icons->push_back(app_info.icons[i]);
    437     }
    438   }
    439 
    440   std::sort(icons->begin(), icons->end(), &IconPrecedes);
    441 }
    442 #endif
    443 
    444 #if defined(OS_LINUX)
    445 std::string GetWMClassFromAppName(std::string app_name) {
    446   file_util::ReplaceIllegalCharactersInPath(&app_name, '_');
    447   base::TrimString(app_name, "_", &app_name);
    448   return app_name;
    449 }
    450 #endif
    451 
    452 }  // namespace web_app
    453