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