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