web_app.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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 extensions::FileHandlers::GetFileHandlers(extension)); 167 168 std::vector<extensions::ImageLoader::ImageRepresentation> info_list; 169 for (size_t i = 0; i < kNumDesiredSizes; ++i) { 170 int size = kDesiredSizes[i]; 171 extensions::ExtensionResource resource = 172 extensions::IconsInfo::GetIconResource( 173 extension, size, ExtensionIconSet::MATCH_EXACTLY); 174 if (!resource.empty()) { 175 info_list.push_back(extensions::ImageLoader::ImageRepresentation( 176 resource, 177 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 178 gfx::Size(size, size), 179 ui::SCALE_FACTOR_100P)); 180 } 181 } 182 183 if (info_list.empty()) { 184 size_t i = kNumDesiredSizes - 1; 185 int size = kDesiredSizes[i]; 186 187 // If there is no icon at the desired sizes, we will resize what we can get. 188 // Making a large icon smaller is preferred to making a small icon larger, 189 // so look for a larger icon first: 190 extensions::ExtensionResource resource = 191 extensions::IconsInfo::GetIconResource( 192 extension, size, ExtensionIconSet::MATCH_BIGGER); 193 if (resource.empty()) { 194 resource = extensions::IconsInfo::GetIconResource( 195 extension, size, ExtensionIconSet::MATCH_SMALLER); 196 } 197 info_list.push_back(extensions::ImageLoader::ImageRepresentation( 198 resource, 199 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 200 gfx::Size(size, size), 201 ui::SCALE_FACTOR_100P)); 202 } 203 204 // |info_list| may still be empty at this point, in which case 205 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty 206 // image and exit immediately. 207 extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync( 208 extension, 209 info_list, 210 base::Bind(&OnImageLoaded, shortcut_info, file_handlers_info, callback)); 211} 212 213void IgnoreFileHandlersInfo( 214 const web_app::ShortcutInfoCallback& shortcut_info_callback, 215 const web_app::ShortcutInfo& shortcut_info, 216 const extensions::FileHandlersInfo& file_handlers_info) { 217 shortcut_info_callback.Run(shortcut_info); 218} 219 220} // namespace 221 222namespace web_app { 223 224// The following string is used to build the directory name for 225// shortcuts to chrome applications (the kind which are installed 226// from a CRX). Application shortcuts to URLs use the {host}_{path} 227// for the name of this directory. Hosts can't include an underscore. 228// By starting this string with an underscore, we ensure that there 229// are no naming conflicts. 230static const char* kCrxAppPrefix = "_crx_"; 231 232namespace internals { 233 234base::FilePath GetSanitizedFileName(const base::string16& name) { 235#if defined(OS_WIN) 236 base::string16 file_name = name; 237#else 238 std::string file_name = base::UTF16ToUTF8(name); 239#endif 240 file_util::ReplaceIllegalCharactersInPath(&file_name, '_'); 241 return base::FilePath(file_name); 242} 243 244bool CreateShortcutsOnFileThread( 245 ShortcutCreationReason reason, 246 const web_app::ShortcutLocations& locations, 247 const web_app::ShortcutInfo& shortcut_info) { 248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 249 250 return CreateShortcutsWithInfoOnFileThread( 251 reason, locations, shortcut_info, extensions::FileHandlersInfo(NULL)); 252} 253 254} // namespace internals 255 256web_app::ShortcutInfo::ShortcutInfo() 257 : is_platform_app(false) { 258} 259 260web_app::ShortcutInfo::~ShortcutInfo() {} 261 262web_app::ShortcutLocations::ShortcutLocations() 263 : on_desktop(false), 264 applications_menu_location(APP_MENU_LOCATION_NONE), 265 in_quick_launch_bar(false) 266#if defined(OS_POSIX) 267 , hidden(false) 268#endif 269 { 270} 271 272void GetShortcutInfoForTab(content::WebContents* web_contents, 273 web_app::ShortcutInfo* info) { 274 DCHECK(info); // Must provide a valid info. 275 276 const FaviconTabHelper* favicon_tab_helper = 277 FaviconTabHelper::FromWebContents(web_contents); 278 const extensions::TabHelper* extensions_tab_helper = 279 extensions::TabHelper::FromWebContents(web_contents); 280 const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info(); 281 282 info->url = app_info.app_url.is_empty() ? web_contents->GetURL() : 283 app_info.app_url; 284 info->title = app_info.title.empty() ? 285 (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) : 286 web_contents->GetTitle()) : 287 app_info.title; 288 info->description = app_info.description; 289 info->favicon.Add(favicon_tab_helper->GetFavicon()); 290 291 Profile* profile = 292 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 293 info->profile_path = profile->GetPath(); 294} 295 296#if !defined(OS_WIN) 297void UpdateShortcutForTabContents(content::WebContents* web_contents) {} 298#endif 299 300web_app::ShortcutInfo ShortcutInfoForExtensionAndProfile( 301 const extensions::Extension* app, Profile* profile) { 302 web_app::ShortcutInfo shortcut_info; 303 shortcut_info.extension_id = app->id(); 304 shortcut_info.is_platform_app = app->is_platform_app(); 305 shortcut_info.url = extensions::AppLaunchInfo::GetLaunchWebURL(app); 306 shortcut_info.title = base::UTF8ToUTF16(app->name()); 307 shortcut_info.description = base::UTF8ToUTF16(app->description()); 308 shortcut_info.extension_path = app->path(); 309 shortcut_info.profile_path = profile->GetPath(); 310 shortcut_info.profile_name = 311 profile->GetPrefs()->GetString(prefs::kProfileName); 312 return shortcut_info; 313} 314 315void UpdateShortcutInfoAndIconForApp( 316 const extensions::Extension* extension, 317 Profile* profile, 318 const web_app::ShortcutInfoCallback& callback) { 319 GetInfoForApp(extension, 320 profile, 321 base::Bind(&IgnoreFileHandlersInfo, callback)); 322} 323 324base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path, 325 const std::string& extension_id, 326 const GURL& url) { 327 DCHECK(!profile_path.empty()); 328 base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname)); 329 330 if (!extension_id.empty()) { 331 return app_data_dir.AppendASCII( 332 GenerateApplicationNameFromExtensionId(extension_id)); 333 } 334 335 std::string host(url.host()); 336 std::string scheme(url.has_scheme() ? url.scheme() : "http"); 337 std::string port(url.has_port() ? url.port() : "80"); 338 std::string scheme_port(scheme + "_" + port); 339 340#if defined(OS_WIN) 341 base::FilePath::StringType host_path(base::UTF8ToUTF16(host)); 342 base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port)); 343#elif defined(OS_POSIX) 344 base::FilePath::StringType host_path(host); 345 base::FilePath::StringType scheme_port_path(scheme_port); 346#endif 347 348 return app_data_dir.Append(host_path).Append(scheme_port_path); 349} 350 351base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path, 352 const extensions::Extension& extension) { 353 return GetWebAppDataDirectory( 354 profile_path, 355 extension.id(), 356 GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension))); 357} 358 359std::string GenerateApplicationNameFromInfo( 360 const web_app::ShortcutInfo& shortcut_info) { 361 if (!shortcut_info.extension_id.empty()) { 362 return web_app::GenerateApplicationNameFromExtensionId( 363 shortcut_info.extension_id); 364 } else { 365 return web_app::GenerateApplicationNameFromURL( 366 shortcut_info.url); 367 } 368} 369 370std::string GenerateApplicationNameFromURL(const GURL& url) { 371 std::string t; 372 t.append(url.host()); 373 t.append("_"); 374 t.append(url.path()); 375 return t; 376} 377 378std::string GenerateApplicationNameFromExtensionId(const std::string& id) { 379 std::string t(web_app::kCrxAppPrefix); 380 t.append(id); 381 return t; 382} 383 384std::string GetExtensionIdFromApplicationName(const std::string& app_name) { 385 std::string prefix(kCrxAppPrefix); 386 if (app_name.substr(0, prefix.length()) != prefix) 387 return std::string(); 388 return app_name.substr(prefix.length()); 389} 390 391void CreateShortcutsForShortcutInfo( 392 web_app::ShortcutCreationReason reason, 393 const web_app::ShortcutLocations& locations, 394 const web_app::ShortcutInfo& shortcut_info) { 395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 396 397 BrowserThread::PostTask( 398 BrowserThread::FILE, 399 FROM_HERE, 400 base::Bind( 401 base::IgnoreResult(&web_app::internals::CreateShortcutsOnFileThread), 402 reason, locations, shortcut_info)); 403} 404 405void CreateShortcuts( 406 ShortcutCreationReason reason, 407 const web_app::ShortcutLocations& locations, 408 Profile* profile, 409 const extensions::Extension* app) { 410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 411 412 GetInfoForApp(app, 413 profile, 414 base::Bind(&CreateShortcutsWithInfo, reason, locations)); 415} 416 417void DeleteAllShortcuts(Profile* profile, const extensions::Extension* app) { 418 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 419 420 BrowserThread::PostTask( 421 BrowserThread::FILE, 422 FROM_HERE, 423 base::Bind(&DeleteShortcutsOnFileThread, 424 web_app::ShortcutInfoForExtensionAndProfile(app, profile))); 425} 426 427void UpdateAllShortcuts(const base::string16& old_app_title, 428 Profile* profile, 429 const extensions::Extension* app) { 430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 431 432 GetInfoForApp(app, 433 profile, 434 base::Bind(&UpdateAllShortcutsForShortcutInfo, old_app_title)); 435} 436 437bool IsValidUrl(const GURL& url) { 438 static const char* const kValidUrlSchemes[] = { 439 content::kFileScheme, 440 content::kFileSystemScheme, 441 content::kFtpScheme, 442 content::kHttpScheme, 443 content::kHttpsScheme, 444 extensions::kExtensionScheme, 445 }; 446 447 for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) { 448 if (url.SchemeIs(kValidUrlSchemes[i])) 449 return true; 450 } 451 452 return false; 453} 454 455#if defined(TOOLKIT_VIEWS) 456void GetIconsInfo(const WebApplicationInfo& app_info, 457 IconInfoList* icons) { 458 DCHECK(icons); 459 460 icons->clear(); 461 for (size_t i = 0; i < app_info.icons.size(); ++i) { 462 // We only take square shaped icons (i.e. width == height). 463 if (app_info.icons[i].width == app_info.icons[i].height) { 464 icons->push_back(app_info.icons[i]); 465 } 466 } 467 468 std::sort(icons->begin(), icons->end(), &IconPrecedes); 469} 470#endif 471 472#if defined(OS_LINUX) 473std::string GetWMClassFromAppName(std::string app_name) { 474 file_util::ReplaceIllegalCharactersInPath(&app_name, '_'); 475 base::TrimString(app_name, "_", &app_name); 476 return app_name; 477} 478#endif 479 480} // namespace web_app 481