• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
IconPrecedes(const WebApplicationInfo::IconInfo & left,const WebApplicationInfo::IconInfo & right)65 bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
66                   const WebApplicationInfo::IconInfo& right) {
67   return left.width < right.width;
68 }
69 #endif
70 
GetShortcutDataDir(const web_app::ShortcutInfo & shortcut_info)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 
CreateShortcutsWithInfo(web_app::ShortcutCreationReason reason,const web_app::ShortcutLocations & locations,const web_app::ShortcutInfo & shortcut_info,const extensions::FileHandlersInfo & file_handlers_info)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 
UpdateAllShortcutsForShortcutInfo(const base::string16 & old_app_title,const web_app::ShortcutInfo & shortcut_info,const extensions::FileHandlersInfo & file_handlers_info)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 
OnImageLoaded(web_app::ShortcutInfo shortcut_info,extensions::FileHandlersInfo file_handlers_info,web_app::InfoCallback callback,const gfx::ImageFamily & image_family)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 
IgnoreFileHandlersInfo(const web_app::ShortcutInfoCallback & shortcut_info_callback,const web_app::ShortcutInfo & shortcut_info,const extensions::FileHandlersInfo & file_handlers_info)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 
GetInfoForApp(const extensions::Extension * extension,Profile * profile,const InfoCallback & callback)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 
GetSanitizedFileName(const base::string16 & name)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 
CreateShortcutsOnFileThread(ShortcutCreationReason reason,const ShortcutLocations & locations,const ShortcutInfo & shortcut_info)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 
ShortcutInfo()228 ShortcutInfo::ShortcutInfo()
229     : is_platform_app(false) {
230 }
231 
~ShortcutInfo()232 ShortcutInfo::~ShortcutInfo() {}
233 
ShortcutLocations()234 ShortcutLocations::ShortcutLocations()
235     : on_desktop(false),
236       applications_menu_location(APP_MENU_LOCATION_NONE),
237       in_quick_launch_bar(false) {
238 }
239 
GetShortcutInfoForTab(content::WebContents * web_contents,ShortcutInfo * info)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)
UpdateShortcutForTabContents(content::WebContents * web_contents)265 void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
266 #endif
267 
ShortcutInfoForExtensionAndProfile(const extensions::Extension * app,Profile * profile)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 
UpdateShortcutInfoAndIconForApp(const extensions::Extension * extension,Profile * profile,const ShortcutInfoCallback & callback)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 
ShouldCreateShortcutFor(Profile * profile,const extensions::Extension * extension)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 
GetWebAppDataDirectory(const base::FilePath & profile_path,const std::string & extension_id,const GURL & url)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 
GetWebAppDataDirectory(const base::FilePath & profile_path,const extensions::Extension & extension)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 
GenerateApplicationNameFromInfo(const ShortcutInfo & shortcut_info)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 
GenerateApplicationNameFromURL(const GURL & url)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 
GenerateApplicationNameFromExtensionId(const std::string & id)347 std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
348   std::string t(kCrxAppPrefix);
349   t.append(id);
350   return t;
351 }
352 
GetExtensionIdFromApplicationName(const std::string & app_name)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 
CreateShortcutsForShortcutInfo(ShortcutCreationReason reason,const ShortcutLocations & locations,const ShortcutInfo & shortcut_info)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 
CreateShortcuts(ShortcutCreationReason reason,const ShortcutLocations & locations,Profile * profile,const extensions::Extension * app)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 
DeleteAllShortcuts(Profile * profile,const extensions::Extension * app)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 
UpdateAllShortcuts(const base::string16 & old_app_title,Profile * profile,const extensions::Extension * app)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 
IsValidUrl(const GURL & url)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)
GetIconsInfo(const WebApplicationInfo & app_info,IconInfoList * icons)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)
GetWMClassFromAppName(std::string app_name)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