web_app.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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/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/api/file_handlers/file_handlers_parser.h"
22#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
23#include "chrome/common/pref_names.h"
24#include "chrome/common/url_constants.h"
25#include "content/public/browser/browser_thread.h"
26#include "extensions/common/constants.h"
27#include "extensions/common/extension.h"
28#include "extensions/common/manifest_handlers/icons_handler.h"
29#include "grit/theme_resources.h"
30#include "skia/ext/image_operations.h"
31#include "third_party/skia/include/core/SkBitmap.h"
32#include "ui/base/resource/resource_bundle.h"
33#include "ui/gfx/image/image.h"
34#include "ui/gfx/image/image_family.h"
35#include "ui/gfx/image/image_skia.h"
36
37#if defined(OS_WIN)
38#include "ui/gfx/icon_util.h"
39#endif
40
41using content::BrowserThread;
42
43namespace {
44
45typedef base::Callback<void(const web_app::ShortcutInfo&,
46                            const extensions::FileHandlersInfo&)> InfoCallback;
47
48#if defined(OS_MACOSX)
49const int kDesiredSizes[] = {16, 32, 128, 256, 512};
50const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
51#elif defined(OS_LINUX)
52// Linux supports icons of any size. FreeDesktop Icon Theme Specification states
53// that "Minimally you should install a 48x48 icon in the hicolor theme."
54const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
55const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
56#elif defined(OS_WIN)
57const int* kDesiredSizes = IconUtil::kIconDimensions;
58const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
59#else
60const int kDesiredSizes[] = {32};
61const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
62#endif
63
64#if defined(TOOLKIT_VIEWS)
65// Predicator for sorting images from largest to smallest.
66bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
67                  const WebApplicationInfo::IconInfo& right) {
68  return left.width < right.width;
69}
70#endif
71
72bool CreateShortcutsWithInfoOnFileThread(
73    web_app::ShortcutCreationReason reason,
74    const web_app::ShortcutLocations& locations,
75    const web_app::ShortcutInfo& shortcut_info,
76    const extensions::FileHandlersInfo& file_handlers_info) {
77  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
78
79  base::FilePath shortcut_data_dir =
80      web_app::GetWebAppDataDirectory(shortcut_info.profile_path,
81                                      shortcut_info.extension_id,
82                                      shortcut_info.url);
83  return web_app::internals::CreatePlatformShortcuts(
84      shortcut_data_dir, shortcut_info, file_handlers_info, locations, reason);
85}
86
87void DeleteShortcutsOnFileThread(
88    const web_app::ShortcutInfo& shortcut_info) {
89  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
90
91  base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory(
92      shortcut_info.profile_path, shortcut_info.extension_id, GURL());
93  return web_app::internals::DeletePlatformShortcuts(
94      shortcut_data_dir, shortcut_info);
95}
96
97void UpdateShortcutsOnFileThread(
98    const base::string16& old_app_title,
99    const web_app::ShortcutInfo& shortcut_info,
100    const extensions::FileHandlersInfo& file_handlers_info) {
101  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
102
103  base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory(
104      shortcut_info.profile_path, shortcut_info.extension_id, GURL());
105  return web_app::internals::UpdatePlatformShortcuts(
106      shortcut_data_dir, old_app_title, shortcut_info, file_handlers_info);
107}
108
109void CreateShortcutsWithInfo(
110    web_app::ShortcutCreationReason reason,
111    const web_app::ShortcutLocations& locations,
112    const web_app::ShortcutInfo& shortcut_info,
113    const extensions::FileHandlersInfo& file_handlers_info) {
114  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
115
116  BrowserThread::PostTask(
117      BrowserThread::FILE,
118      FROM_HERE,
119      base::Bind(
120          base::IgnoreResult(&CreateShortcutsWithInfoOnFileThread),
121          reason, locations, shortcut_info, file_handlers_info));
122}
123
124void UpdateAllShortcutsForShortcutInfo(
125    const base::string16& old_app_title,
126    const web_app::ShortcutInfo& shortcut_info,
127    const extensions::FileHandlersInfo& file_handlers_info) {
128  BrowserThread::PostTask(
129      BrowserThread::FILE,
130      FROM_HERE,
131      base::Bind(&UpdateShortcutsOnFileThread,
132                 old_app_title, shortcut_info, file_handlers_info));
133}
134
135void OnImageLoaded(web_app::ShortcutInfo shortcut_info,
136                   extensions::FileHandlersInfo file_handlers_info,
137                   InfoCallback callback,
138                   const gfx::ImageFamily& image_family) {
139  // If the image failed to load (e.g. if the resource being loaded was empty)
140  // use the standard application icon.
141  if (image_family.empty()) {
142    gfx::Image default_icon =
143        ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
144    int size = kDesiredSizes[kNumDesiredSizes - 1];
145    SkBitmap bmp = skia::ImageOperations::Resize(
146          *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
147          size, size);
148    gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bmp);
149    // We are on the UI thread, and this image is needed from the FILE thread,
150    // for creating shortcut icon files.
151    image_skia.MakeThreadSafe();
152    shortcut_info.favicon.Add(gfx::Image(image_skia));
153  } else {
154    shortcut_info.favicon = image_family;
155  }
156
157  callback.Run(shortcut_info, file_handlers_info);
158}
159
160void GetInfoForApp(const extensions::Extension* extension,
161                   Profile* profile,
162                   const InfoCallback& callback) {
163  web_app::ShortcutInfo shortcut_info =
164      web_app::ShortcutInfoForExtensionAndProfile(extension, profile);
165  extensions::FileHandlersInfo file_handlers_info;
166  const std::vector<extensions::FileHandlerInfo>* file_handlers =
167      extensions::FileHandlers::GetFileHandlers(extension);
168  if (file_handlers)
169    file_handlers_info.handlers = *file_handlers;
170
171  std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
172  for (size_t i = 0; i < kNumDesiredSizes; ++i) {
173    int size = kDesiredSizes[i];
174    extensions::ExtensionResource resource =
175        extensions::IconsInfo::GetIconResource(
176            extension, size, ExtensionIconSet::MATCH_EXACTLY);
177    if (!resource.empty()) {
178      info_list.push_back(extensions::ImageLoader::ImageRepresentation(
179          resource,
180          extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
181          gfx::Size(size, size),
182          ui::SCALE_FACTOR_100P));
183    }
184  }
185
186  if (info_list.empty()) {
187    size_t i = kNumDesiredSizes - 1;
188    int size = kDesiredSizes[i];
189
190    // If there is no icon at the desired sizes, we will resize what we can get.
191    // Making a large icon smaller is preferred to making a small icon larger,
192    // so look for a larger icon first:
193    extensions::ExtensionResource resource =
194        extensions::IconsInfo::GetIconResource(
195            extension, size, ExtensionIconSet::MATCH_BIGGER);
196    if (resource.empty()) {
197      resource = extensions::IconsInfo::GetIconResource(
198          extension, size, ExtensionIconSet::MATCH_SMALLER);
199    }
200    info_list.push_back(extensions::ImageLoader::ImageRepresentation(
201        resource,
202        extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
203        gfx::Size(size, size),
204        ui::SCALE_FACTOR_100P));
205  }
206
207  // |info_list| may still be empty at this point, in which case
208  // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
209  // image and exit immediately.
210  extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
211      extension,
212      info_list,
213      base::Bind(&OnImageLoaded, shortcut_info, file_handlers_info, callback));
214}
215
216void IgnoreFileHandlersInfo(
217    const web_app::ShortcutInfoCallback& shortcut_info_callback,
218    const web_app::ShortcutInfo& shortcut_info,
219    const extensions::FileHandlersInfo& file_handlers_info) {
220  shortcut_info_callback.Run(shortcut_info);
221}
222
223}  // namespace
224
225namespace web_app {
226
227// The following string is used to build the directory name for
228// shortcuts to chrome applications (the kind which are installed
229// from a CRX).  Application shortcuts to URLs use the {host}_{path}
230// for the name of this directory.  Hosts can't include an underscore.
231// By starting this string with an underscore, we ensure that there
232// are no naming conflicts.
233static const char* kCrxAppPrefix = "_crx_";
234
235namespace internals {
236
237base::FilePath GetSanitizedFileName(const base::string16& name) {
238#if defined(OS_WIN)
239  base::string16 file_name = name;
240#else
241  std::string file_name = base::UTF16ToUTF8(name);
242#endif
243  file_util::ReplaceIllegalCharactersInPath(&file_name, '_');
244  return base::FilePath(file_name);
245}
246
247bool CreateShortcutsOnFileThread(
248    ShortcutCreationReason reason,
249    const web_app::ShortcutLocations& locations,
250    const web_app::ShortcutInfo& shortcut_info) {
251  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
252
253  return CreateShortcutsWithInfoOnFileThread(
254      reason, locations, shortcut_info, extensions::FileHandlersInfo());
255}
256
257}  // namespace internals
258
259web_app::ShortcutInfo::ShortcutInfo()
260    : is_platform_app(false) {
261}
262
263web_app::ShortcutInfo::~ShortcutInfo() {}
264
265web_app::ShortcutLocations::ShortcutLocations()
266    : on_desktop(false),
267      applications_menu_location(APP_MENU_LOCATION_NONE),
268      in_quick_launch_bar(false)
269#if defined(OS_POSIX)
270      , hidden(false)
271#endif
272      {
273}
274
275void GetShortcutInfoForTab(content::WebContents* web_contents,
276                           web_app::ShortcutInfo* info) {
277  DCHECK(info);  // Must provide a valid info.
278
279  const FaviconTabHelper* favicon_tab_helper =
280      FaviconTabHelper::FromWebContents(web_contents);
281  const extensions::TabHelper* extensions_tab_helper =
282      extensions::TabHelper::FromWebContents(web_contents);
283  const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info();
284
285  info->url = app_info.app_url.is_empty() ? web_contents->GetURL() :
286                                            app_info.app_url;
287  info->title = app_info.title.empty() ?
288      (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) :
289                                          web_contents->GetTitle()) :
290      app_info.title;
291  info->description = app_info.description;
292  info->favicon.Add(favicon_tab_helper->GetFavicon());
293
294  Profile* profile =
295      Profile::FromBrowserContext(web_contents->GetBrowserContext());
296  info->profile_path = profile->GetPath();
297}
298
299#if !defined(OS_WIN)
300void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
301#endif
302
303web_app::ShortcutInfo ShortcutInfoForExtensionAndProfile(
304    const extensions::Extension* app, Profile* profile) {
305  web_app::ShortcutInfo shortcut_info;
306  shortcut_info.extension_id = app->id();
307  shortcut_info.is_platform_app = app->is_platform_app();
308  shortcut_info.url = extensions::AppLaunchInfo::GetLaunchWebURL(app);
309  shortcut_info.title = base::UTF8ToUTF16(app->name());
310  shortcut_info.description = base::UTF8ToUTF16(app->description());
311  shortcut_info.extension_path = app->path();
312  shortcut_info.profile_path = profile->GetPath();
313  shortcut_info.profile_name =
314      profile->GetPrefs()->GetString(prefs::kProfileName);
315  return shortcut_info;
316}
317
318void UpdateShortcutInfoAndIconForApp(
319    const extensions::Extension* extension,
320    Profile* profile,
321    const web_app::ShortcutInfoCallback& callback) {
322  GetInfoForApp(extension,
323                profile,
324                base::Bind(&IgnoreFileHandlersInfo, callback));
325}
326
327base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
328                                      const std::string& extension_id,
329                                      const GURL& url) {
330  DCHECK(!profile_path.empty());
331  base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
332
333  if (!extension_id.empty()) {
334    return app_data_dir.AppendASCII(
335        GenerateApplicationNameFromExtensionId(extension_id));
336  }
337
338  std::string host(url.host());
339  std::string scheme(url.has_scheme() ? url.scheme() : "http");
340  std::string port(url.has_port() ? url.port() : "80");
341  std::string scheme_port(scheme + "_" + port);
342
343#if defined(OS_WIN)
344  base::FilePath::StringType host_path(base::UTF8ToUTF16(host));
345  base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port));
346#elif defined(OS_POSIX)
347  base::FilePath::StringType host_path(host);
348  base::FilePath::StringType scheme_port_path(scheme_port);
349#endif
350
351  return app_data_dir.Append(host_path).Append(scheme_port_path);
352}
353
354base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
355                                      const extensions::Extension& extension) {
356  return GetWebAppDataDirectory(
357      profile_path,
358      extension.id(),
359      GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension)));
360}
361
362std::string GenerateApplicationNameFromInfo(
363    const web_app::ShortcutInfo& shortcut_info) {
364  if (!shortcut_info.extension_id.empty()) {
365    return web_app::GenerateApplicationNameFromExtensionId(
366        shortcut_info.extension_id);
367  } else {
368    return web_app::GenerateApplicationNameFromURL(
369        shortcut_info.url);
370  }
371}
372
373std::string GenerateApplicationNameFromURL(const GURL& url) {
374  std::string t;
375  t.append(url.host());
376  t.append("_");
377  t.append(url.path());
378  return t;
379}
380
381std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
382  std::string t(web_app::kCrxAppPrefix);
383  t.append(id);
384  return t;
385}
386
387std::string GetExtensionIdFromApplicationName(const std::string& app_name) {
388  std::string prefix(kCrxAppPrefix);
389  if (app_name.substr(0, prefix.length()) != prefix)
390    return std::string();
391  return app_name.substr(prefix.length());
392}
393
394void CreateShortcutsForShortcutInfo(
395    web_app::ShortcutCreationReason reason,
396    const web_app::ShortcutLocations& locations,
397    const web_app::ShortcutInfo& shortcut_info) {
398  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
399
400  BrowserThread::PostTask(
401      BrowserThread::FILE,
402      FROM_HERE,
403      base::Bind(
404          base::IgnoreResult(&web_app::internals::CreateShortcutsOnFileThread),
405          reason, locations, shortcut_info));
406}
407
408void CreateShortcuts(
409    ShortcutCreationReason reason,
410    const web_app::ShortcutLocations& locations,
411    Profile* profile,
412    const extensions::Extension* app) {
413  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
414
415  GetInfoForApp(app,
416                profile,
417                base::Bind(&CreateShortcutsWithInfo, reason, locations));
418}
419
420void DeleteAllShortcuts(Profile* profile, const extensions::Extension* app) {
421  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
422
423  BrowserThread::PostTask(
424      BrowserThread::FILE,
425      FROM_HERE,
426      base::Bind(&DeleteShortcutsOnFileThread,
427                 web_app::ShortcutInfoForExtensionAndProfile(app, profile)));
428}
429
430void UpdateAllShortcuts(const base::string16& old_app_title,
431                        Profile* profile,
432                        const extensions::Extension* app) {
433  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
434
435  GetInfoForApp(app,
436                profile,
437                base::Bind(&UpdateAllShortcutsForShortcutInfo, old_app_title));
438}
439
440bool IsValidUrl(const GURL& url) {
441  static const char* const kValidUrlSchemes[] = {
442      content::kFileScheme,
443      content::kFileSystemScheme,
444      content::kFtpScheme,
445      content::kHttpScheme,
446      content::kHttpsScheme,
447      extensions::kExtensionScheme,
448  };
449
450  for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
451    if (url.SchemeIs(kValidUrlSchemes[i]))
452      return true;
453  }
454
455  return false;
456}
457
458#if defined(TOOLKIT_VIEWS)
459void GetIconsInfo(const WebApplicationInfo& app_info,
460                  IconInfoList* icons) {
461  DCHECK(icons);
462
463  icons->clear();
464  for (size_t i = 0; i < app_info.icons.size(); ++i) {
465    // We only take square shaped icons (i.e. width == height).
466    if (app_info.icons[i].width == app_info.icons[i].height) {
467      icons->push_back(app_info.icons[i]);
468    }
469  }
470
471  std::sort(icons->begin(), icons->end(), &IconPrecedes);
472}
473#endif
474
475#if defined(OS_LINUX)
476std::string GetWMClassFromAppName(std::string app_name) {
477  file_util::ReplaceIllegalCharactersInPath(&app_name, '_');
478  base::TrimString(app_name, "_", &app_name);
479  return app_name;
480}
481#endif
482
483}  // namespace web_app
484