web_app.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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/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/extensions/manifest_handlers/icons_handler.h"
21#include "chrome/common/pref_names.h"
22#include "chrome/common/url_constants.h"
23#include "content/public/browser/browser_thread.h"
24#include "extensions/common/constants.h"
25#include "extensions/common/extension.h"
26#include "grit/theme_resources.h"
27#include "skia/ext/image_operations.h"
28#include "third_party/skia/include/core/SkBitmap.h"
29#include "ui/base/resource/resource_bundle.h"
30#include "ui/gfx/image/image.h"
31#include "ui/gfx/image/image_family.h"
32#include "ui/gfx/image/image_skia.h"
33
34#if defined(OS_WIN)
35#include "ui/gfx/icon_util.h"
36#endif
37
38using content::BrowserThread;
39
40namespace {
41
42#if defined(OS_MACOSX)
43const int kDesiredSizes[] = {16, 32, 128, 256, 512};
44const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
45#elif defined(OS_LINUX)
46// Linux supports icons of any size. FreeDesktop Icon Theme Specification states
47// that "Minimally you should install a 48x48 icon in the hicolor theme."
48const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
49const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
50#elif defined(OS_WIN)
51const int* kDesiredSizes = IconUtil::kIconDimensions;
52const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
53#else
54const int kDesiredSizes[] = {32};
55const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
56#endif
57
58#if defined(TOOLKIT_VIEWS)
59// Predicator for sorting images from largest to smallest.
60bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
61                  const WebApplicationInfo::IconInfo& right) {
62  return left.width < right.width;
63}
64#endif
65
66void DeleteShortcutsOnFileThread(
67    const ShellIntegration::ShortcutInfo& shortcut_info) {
68  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
69
70  base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory(
71      shortcut_info.profile_path, shortcut_info.extension_id, GURL());
72  return web_app::internals::DeletePlatformShortcuts(
73      shortcut_data_dir, shortcut_info);
74}
75
76void UpdateShortcutsOnFileThread(
77    const base::string16& old_app_title,
78    const ShellIntegration::ShortcutInfo& shortcut_info) {
79  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
80
81  base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory(
82      shortcut_info.profile_path, shortcut_info.extension_id, GURL());
83  return web_app::internals::UpdatePlatformShortcuts(
84      shortcut_data_dir, old_app_title, shortcut_info);
85}
86
87void OnImageLoaded(ShellIntegration::ShortcutInfo shortcut_info,
88                   web_app::ShortcutInfoCallback callback,
89                   const gfx::ImageFamily& image_family) {
90  // If the image failed to load (e.g. if the resource being loaded was empty)
91  // use the standard application icon.
92  if (image_family.empty()) {
93    gfx::Image default_icon =
94        ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
95    int size = kDesiredSizes[kNumDesiredSizes - 1];
96    SkBitmap bmp = skia::ImageOperations::Resize(
97          *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
98          size, size);
99    gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bmp);
100    // We are on the UI thread, and this image is needed from the FILE thread,
101    // for creating shortcut icon files.
102    image_skia.MakeThreadSafe();
103    shortcut_info.favicon.Add(gfx::Image(image_skia));
104  } else {
105    shortcut_info.favicon = image_family;
106  }
107
108  callback.Run(shortcut_info);
109}
110
111}  // namespace
112
113namespace web_app {
114
115// The following string is used to build the directory name for
116// shortcuts to chrome applications (the kind which are installed
117// from a CRX).  Application shortcuts to URLs use the {host}_{path}
118// for the name of this directory.  Hosts can't include an underscore.
119// By starting this string with an underscore, we ensure that there
120// are no naming conflicts.
121static const char* kCrxAppPrefix = "_crx_";
122
123namespace internals {
124
125base::FilePath GetSanitizedFileName(const base::string16& name) {
126#if defined(OS_WIN)
127  base::string16 file_name = name;
128#else
129  std::string file_name = base::UTF16ToUTF8(name);
130#endif
131  file_util::ReplaceIllegalCharactersInPath(&file_name, '_');
132  return base::FilePath(file_name);
133}
134
135}  // namespace internals
136
137ShellIntegration::ShortcutInfo ShortcutInfoForExtensionAndProfile(
138    const extensions::Extension* app, Profile* profile) {
139  ShellIntegration::ShortcutInfo shortcut_info;
140  shortcut_info.extension_id = app->id();
141  shortcut_info.is_platform_app = app->is_platform_app();
142  shortcut_info.url = extensions::AppLaunchInfo::GetLaunchWebURL(app);
143  shortcut_info.title = base::UTF8ToUTF16(app->name());
144  shortcut_info.description = base::UTF8ToUTF16(app->description());
145  shortcut_info.extension_path = app->path();
146  shortcut_info.profile_path = profile->GetPath();
147  shortcut_info.profile_name =
148      profile->GetPrefs()->GetString(prefs::kProfileName);
149  return shortcut_info;
150}
151
152void UpdateShortcutInfoAndIconForApp(
153    const extensions::Extension* extension,
154    Profile* profile,
155    const web_app::ShortcutInfoCallback& callback) {
156  ShellIntegration::ShortcutInfo shortcut_info =
157      ShortcutInfoForExtensionAndProfile(extension, profile);
158
159  std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
160  for (size_t i = 0; i < kNumDesiredSizes; ++i) {
161    int size = kDesiredSizes[i];
162    extensions::ExtensionResource resource =
163        extensions::IconsInfo::GetIconResource(
164            extension, size, ExtensionIconSet::MATCH_EXACTLY);
165    if (!resource.empty()) {
166      info_list.push_back(extensions::ImageLoader::ImageRepresentation(
167          resource,
168          extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
169          gfx::Size(size, size),
170          ui::SCALE_FACTOR_100P));
171    }
172  }
173
174  if (info_list.empty()) {
175    size_t i = kNumDesiredSizes - 1;
176    int size = kDesiredSizes[i];
177
178    // If there is no icon at the desired sizes, we will resize what we can get.
179    // Making a large icon smaller is preferred to making a small icon larger,
180    // so look for a larger icon first:
181    extensions::ExtensionResource resource =
182        extensions::IconsInfo::GetIconResource(
183            extension, size, ExtensionIconSet::MATCH_BIGGER);
184    if (resource.empty()) {
185      resource = extensions::IconsInfo::GetIconResource(
186          extension, size, ExtensionIconSet::MATCH_SMALLER);
187    }
188    info_list.push_back(extensions::ImageLoader::ImageRepresentation(
189        resource,
190        extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
191        gfx::Size(size, size),
192        ui::SCALE_FACTOR_100P));
193  }
194
195  // |info_list| may still be empty at this point, in which case
196  // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
197  // image and exit immediately.
198  extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
199      extension,
200      info_list,
201      base::Bind(&OnImageLoaded, shortcut_info, callback));
202}
203
204base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
205                                      const std::string& extension_id,
206                                      const GURL& url) {
207  DCHECK(!profile_path.empty());
208  base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
209
210  if (!extension_id.empty()) {
211    return app_data_dir.AppendASCII(
212        GenerateApplicationNameFromExtensionId(extension_id));
213  }
214
215  std::string host(url.host());
216  std::string scheme(url.has_scheme() ? url.scheme() : "http");
217  std::string port(url.has_port() ? url.port() : "80");
218  std::string scheme_port(scheme + "_" + port);
219
220#if defined(OS_WIN)
221  base::FilePath::StringType host_path(base::UTF8ToUTF16(host));
222  base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port));
223#elif defined(OS_POSIX)
224  base::FilePath::StringType host_path(host);
225  base::FilePath::StringType scheme_port_path(scheme_port);
226#endif
227
228  return app_data_dir.Append(host_path).Append(scheme_port_path);
229}
230
231base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
232                                      const extensions::Extension& extension) {
233  return GetWebAppDataDirectory(
234      profile_path,
235      extension.id(),
236      GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension)));
237}
238
239std::string GenerateApplicationNameFromInfo(
240    const ShellIntegration::ShortcutInfo& shortcut_info) {
241  if (!shortcut_info.extension_id.empty()) {
242    return web_app::GenerateApplicationNameFromExtensionId(
243        shortcut_info.extension_id);
244  } else {
245    return web_app::GenerateApplicationNameFromURL(
246        shortcut_info.url);
247  }
248}
249
250std::string GenerateApplicationNameFromURL(const GURL& url) {
251  std::string t;
252  t.append(url.host());
253  t.append("_");
254  t.append(url.path());
255  return t;
256}
257
258std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
259  std::string t(web_app::kCrxAppPrefix);
260  t.append(id);
261  return t;
262}
263
264std::string GetExtensionIdFromApplicationName(const std::string& app_name) {
265  std::string prefix(kCrxAppPrefix);
266  if (app_name.substr(0, prefix.length()) != prefix)
267    return std::string();
268  return app_name.substr(prefix.length());
269}
270
271void CreateShortcuts(
272    const ShellIntegration::ShortcutInfo& shortcut_info,
273    const ShellIntegration::ShortcutLocations& creation_locations,
274    ShortcutCreationReason creation_reason) {
275  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
276
277  BrowserThread::PostTask(
278      BrowserThread::FILE,
279      FROM_HERE,
280      base::Bind(base::IgnoreResult(&CreateShortcutsOnFileThread),
281                 shortcut_info, creation_locations, creation_reason));
282}
283
284void DeleteAllShortcuts(const ShellIntegration::ShortcutInfo& shortcut_info) {
285  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
286
287  BrowserThread::PostTask(
288      BrowserThread::FILE,
289      FROM_HERE,
290      base::Bind(&DeleteShortcutsOnFileThread, shortcut_info));
291}
292
293void UpdateAllShortcuts(const base::string16& old_app_title,
294                        const ShellIntegration::ShortcutInfo& shortcut_info) {
295  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
296
297  BrowserThread::PostTask(
298      BrowserThread::FILE,
299      FROM_HERE,
300      base::Bind(&UpdateShortcutsOnFileThread, old_app_title, shortcut_info));
301}
302
303bool CreateShortcutsOnFileThread(
304    const ShellIntegration::ShortcutInfo& shortcut_info,
305    const ShellIntegration::ShortcutLocations& creation_locations,
306    ShortcutCreationReason creation_reason) {
307  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
308
309  base::FilePath shortcut_data_dir = GetWebAppDataDirectory(
310      shortcut_info.profile_path, shortcut_info.extension_id,
311      shortcut_info.url);
312  return internals::CreatePlatformShortcuts(shortcut_data_dir, shortcut_info,
313                                            creation_locations,
314                                            creation_reason);
315}
316
317bool IsValidUrl(const GURL& url) {
318  static const char* const kValidUrlSchemes[] = {
319      content::kFileScheme,
320      content::kFileSystemScheme,
321      content::kFtpScheme,
322      content::kHttpScheme,
323      content::kHttpsScheme,
324      extensions::kExtensionScheme,
325  };
326
327  for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
328    if (url.SchemeIs(kValidUrlSchemes[i]))
329      return true;
330  }
331
332  return false;
333}
334
335#if defined(TOOLKIT_VIEWS)
336void GetIconsInfo(const WebApplicationInfo& app_info,
337                  IconInfoList* icons) {
338  DCHECK(icons);
339
340  icons->clear();
341  for (size_t i = 0; i < app_info.icons.size(); ++i) {
342    // We only take square shaped icons (i.e. width == height).
343    if (app_info.icons[i].width == app_info.icons[i].height) {
344      icons->push_back(app_info.icons[i]);
345    }
346  }
347
348  std::sort(icons->begin(), icons->end(), &IconPrecedes);
349}
350#endif
351
352#if defined(OS_LINUX)
353std::string GetWMClassFromAppName(std::string app_name) {
354  file_util::ReplaceIllegalCharactersInPath(&app_name, '_');
355  base::TrimString(app_name, "_", &app_name);
356  return app_name;
357}
358#endif
359
360}  // namespace web_app
361