• 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/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.
IconPrecedes(const WebApplicationInfo::IconInfo & left,const WebApplicationInfo::IconInfo & right)68 bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
69                   const WebApplicationInfo::IconInfo& right) {
70   return left.width < right.width;
71 }
72 #endif
73 
GetShortcutDataDir(const web_app::ShortcutInfo & shortcut_info)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 
UpdateAllShortcutsForShortcutInfo(const base::string16 & old_app_title,const web_app::ShortcutInfo & shortcut_info,const extensions::FileHandlersInfo & file_handlers_info)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 
OnImageLoaded(web_app::ShortcutInfo shortcut_info,extensions::FileHandlersInfo file_handlers_info,web_app::InfoCallback callback,const gfx::ImageFamily & image_family)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 
IgnoreFileHandlersInfo(const web_app::ShortcutInfoCallback & shortcut_info_callback,const web_app::ShortcutInfo & shortcut_info,const extensions::FileHandlersInfo & file_handlers_info)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 
GetSanitizedFileName(const base::string16 & name)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 
ShortcutInfo()150 ShortcutInfo::ShortcutInfo()
151     : is_platform_app(false) {
152 }
153 
~ShortcutInfo()154 ShortcutInfo::~ShortcutInfo() {}
155 
ShortcutLocations()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)
GetShortcutInfoForTab(content::WebContents * web_contents,ShortcutInfo * info)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)
UpdateShortcutForTabContents(content::WebContents * web_contents)189 void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
190 #endif
191 
ShortcutInfoForExtensionAndProfile(const extensions::Extension * app,Profile * profile)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 
GetInfoForApp(const extensions::Extension * extension,Profile * profile,const InfoCallback & callback)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 
GetShortcutInfoForApp(const extensions::Extension * extension,Profile * profile,const ShortcutInfoCallback & callback)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 
ShouldCreateShortcutFor(Profile * profile,const extensions::Extension * extension)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 
GetWebAppDataDirectory(const base::FilePath & profile_path,const std::string & extension_id,const GURL & url)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 
GetWebAppDataDirectory(const base::FilePath & profile_path,const extensions::Extension & extension)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 
GenerateApplicationNameFromInfo(const ShortcutInfo & shortcut_info)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 
GenerateApplicationNameFromURL(const GURL & url)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 
GenerateApplicationNameFromExtensionId(const std::string & id)326 std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
327   std::string t(kCrxAppPrefix);
328   t.append(id);
329   return t;
330 }
331 
GetExtensionIdFromApplicationName(const std::string & app_name)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 
CreateShortcutsWithInfo(ShortcutCreationReason reason,const ShortcutLocations & locations,const ShortcutInfo & shortcut_info,const extensions::FileHandlersInfo & file_handlers_info)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 
CreateShortcuts(ShortcutCreationReason reason,const ShortcutLocations & locations,Profile * profile,const extensions::Extension * app)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 
DeleteAllShortcuts(Profile * profile,const extensions::Extension * app)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 
UpdateAllShortcuts(const base::string16 & old_app_title,Profile * profile,const extensions::Extension * app)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 
IsValidUrl(const GURL & url)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)
GetIconsInfo(const WebApplicationInfo & app_info,IconInfoList * icons)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)
GetWMClassFromAppName(std::string app_name)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