web_app.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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
46using content::BrowserThread;
47
48namespace {
49
50#if defined(OS_MACOSX)
51const int kDesiredSizes[] = {16, 32, 128, 256, 512};
52const 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."
56const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
57const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
58#elif defined(OS_WIN)
59const int* kDesiredSizes = IconUtil::kIconDimensions;
60const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
61#else
62const int kDesiredSizes[] = {32};
63const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
64#endif
65
66#if defined(TOOLKIT_VIEWS)
67// Predicator for sorting images from largest to smallest.
68bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
69                  const WebApplicationInfo::IconInfo& right) {
70  return left.width < right.width;
71}
72#endif
73
74base::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
80void 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
92void 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
117void 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
126namespace 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.
134static const char kCrxAppPrefix[] = "_crx_";
135
136namespace internals {
137
138base::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
150ShortcutInfo::ShortcutInfo()
151    : is_platform_app(false) {
152}
153
154ShortcutInfo::~ShortcutInfo() {}
155
156ShortcutLocations::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)
163void 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)
189void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
190#endif
191
192ShortcutInfo 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
207void 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
262void GetShortcutInfoForApp(const extensions::Extension* extension,
263                           Profile* profile,
264                           const ShortcutInfoCallback& callback) {
265  GetInfoForApp(
266      extension, profile, base::Bind(&IgnoreFileHandlersInfo, callback));
267}
268
269bool 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
276base::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
303base::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
311std::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
318std::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
326std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
327  std::string t(kCrxAppPrefix);
328  t.append(id);
329  return t;
330}
331
332std::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
339void 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
357void 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
370void 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
382void 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
392bool 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)
411void 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)
428std::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