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