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