web_app.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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
43using content::BrowserThread;
44
45namespace {
46
47#if defined(OS_MACOSX)
48const int kDesiredSizes[] = {16, 32, 128, 256, 512};
49const 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."
53const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
54const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
55#elif defined(OS_WIN)
56const int* kDesiredSizes = IconUtil::kIconDimensions;
57const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
58#else
59const int kDesiredSizes[] = {32};
60const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
61#endif
62
63#if defined(TOOLKIT_VIEWS)
64// Predicator for sorting images from largest to smallest.
65bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
66                  const WebApplicationInfo::IconInfo& right) {
67  return left.width < right.width;
68}
69#endif
70
71base::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
77void 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
93void 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
105void 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
130void 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
139namespace 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.
147static const char kCrxAppPrefix[] = "_crx_";
148
149namespace internals {
150
151void 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
206base::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
216bool 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
228ShortcutInfo::ShortcutInfo()
229    : is_platform_app(false) {
230}
231
232ShortcutInfo::~ShortcutInfo() {}
233
234ShortcutLocations::ShortcutLocations()
235    : on_desktop(false),
236      applications_menu_location(APP_MENU_LOCATION_NONE),
237      in_quick_launch_bar(false) {
238}
239
240void 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)
265void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
266#endif
267
268ShortcutInfo 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
283void 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
290bool 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
297base::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
324base::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
332std::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
339std::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
347std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
348  std::string t(kCrxAppPrefix);
349  t.append(id);
350  return t;
351}
352
353std::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
360void 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
373void 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
386void 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
398void 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
409bool 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)
428void 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)
445std::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