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