web_app.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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      extensions::FileHandlers::GetFileHandlers(extension));
167
168  std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
169  for (size_t i = 0; i < kNumDesiredSizes; ++i) {
170    int size = kDesiredSizes[i];
171    extensions::ExtensionResource resource =
172        extensions::IconsInfo::GetIconResource(
173            extension, size, ExtensionIconSet::MATCH_EXACTLY);
174    if (!resource.empty()) {
175      info_list.push_back(extensions::ImageLoader::ImageRepresentation(
176          resource,
177          extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
178          gfx::Size(size, size),
179          ui::SCALE_FACTOR_100P));
180    }
181  }
182
183  if (info_list.empty()) {
184    size_t i = kNumDesiredSizes - 1;
185    int size = kDesiredSizes[i];
186
187    // If there is no icon at the desired sizes, we will resize what we can get.
188    // Making a large icon smaller is preferred to making a small icon larger,
189    // so look for a larger icon first:
190    extensions::ExtensionResource resource =
191        extensions::IconsInfo::GetIconResource(
192            extension, size, ExtensionIconSet::MATCH_BIGGER);
193    if (resource.empty()) {
194      resource = extensions::IconsInfo::GetIconResource(
195          extension, size, ExtensionIconSet::MATCH_SMALLER);
196    }
197    info_list.push_back(extensions::ImageLoader::ImageRepresentation(
198        resource,
199        extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
200        gfx::Size(size, size),
201        ui::SCALE_FACTOR_100P));
202  }
203
204  // |info_list| may still be empty at this point, in which case
205  // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
206  // image and exit immediately.
207  extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
208      extension,
209      info_list,
210      base::Bind(&OnImageLoaded, shortcut_info, file_handlers_info, callback));
211}
212
213void IgnoreFileHandlersInfo(
214    const web_app::ShortcutInfoCallback& shortcut_info_callback,
215    const web_app::ShortcutInfo& shortcut_info,
216    const extensions::FileHandlersInfo& file_handlers_info) {
217  shortcut_info_callback.Run(shortcut_info);
218}
219
220}  // namespace
221
222namespace web_app {
223
224// The following string is used to build the directory name for
225// shortcuts to chrome applications (the kind which are installed
226// from a CRX).  Application shortcuts to URLs use the {host}_{path}
227// for the name of this directory.  Hosts can't include an underscore.
228// By starting this string with an underscore, we ensure that there
229// are no naming conflicts.
230static const char* kCrxAppPrefix = "_crx_";
231
232namespace internals {
233
234base::FilePath GetSanitizedFileName(const base::string16& name) {
235#if defined(OS_WIN)
236  base::string16 file_name = name;
237#else
238  std::string file_name = base::UTF16ToUTF8(name);
239#endif
240  file_util::ReplaceIllegalCharactersInPath(&file_name, '_');
241  return base::FilePath(file_name);
242}
243
244bool CreateShortcutsOnFileThread(
245    ShortcutCreationReason reason,
246    const web_app::ShortcutLocations& locations,
247    const web_app::ShortcutInfo& shortcut_info) {
248  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
249
250  return CreateShortcutsWithInfoOnFileThread(
251      reason, locations, shortcut_info, extensions::FileHandlersInfo(NULL));
252}
253
254}  // namespace internals
255
256web_app::ShortcutInfo::ShortcutInfo()
257    : is_platform_app(false) {
258}
259
260web_app::ShortcutInfo::~ShortcutInfo() {}
261
262web_app::ShortcutLocations::ShortcutLocations()
263    : on_desktop(false),
264      applications_menu_location(APP_MENU_LOCATION_NONE),
265      in_quick_launch_bar(false)
266#if defined(OS_POSIX)
267      , hidden(false)
268#endif
269      {
270}
271
272void GetShortcutInfoForTab(content::WebContents* web_contents,
273                           web_app::ShortcutInfo* info) {
274  DCHECK(info);  // Must provide a valid info.
275
276  const FaviconTabHelper* favicon_tab_helper =
277      FaviconTabHelper::FromWebContents(web_contents);
278  const extensions::TabHelper* extensions_tab_helper =
279      extensions::TabHelper::FromWebContents(web_contents);
280  const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info();
281
282  info->url = app_info.app_url.is_empty() ? web_contents->GetURL() :
283                                            app_info.app_url;
284  info->title = app_info.title.empty() ?
285      (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) :
286                                          web_contents->GetTitle()) :
287      app_info.title;
288  info->description = app_info.description;
289  info->favicon.Add(favicon_tab_helper->GetFavicon());
290
291  Profile* profile =
292      Profile::FromBrowserContext(web_contents->GetBrowserContext());
293  info->profile_path = profile->GetPath();
294}
295
296#if !defined(OS_WIN)
297void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
298#endif
299
300web_app::ShortcutInfo ShortcutInfoForExtensionAndProfile(
301    const extensions::Extension* app, Profile* profile) {
302  web_app::ShortcutInfo shortcut_info;
303  shortcut_info.extension_id = app->id();
304  shortcut_info.is_platform_app = app->is_platform_app();
305  shortcut_info.url = extensions::AppLaunchInfo::GetLaunchWebURL(app);
306  shortcut_info.title = base::UTF8ToUTF16(app->name());
307  shortcut_info.description = base::UTF8ToUTF16(app->description());
308  shortcut_info.extension_path = app->path();
309  shortcut_info.profile_path = profile->GetPath();
310  shortcut_info.profile_name =
311      profile->GetPrefs()->GetString(prefs::kProfileName);
312  return shortcut_info;
313}
314
315void UpdateShortcutInfoAndIconForApp(
316    const extensions::Extension* extension,
317    Profile* profile,
318    const web_app::ShortcutInfoCallback& callback) {
319  GetInfoForApp(extension,
320                profile,
321                base::Bind(&IgnoreFileHandlersInfo, callback));
322}
323
324base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
325                                      const std::string& extension_id,
326                                      const GURL& url) {
327  DCHECK(!profile_path.empty());
328  base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
329
330  if (!extension_id.empty()) {
331    return app_data_dir.AppendASCII(
332        GenerateApplicationNameFromExtensionId(extension_id));
333  }
334
335  std::string host(url.host());
336  std::string scheme(url.has_scheme() ? url.scheme() : "http");
337  std::string port(url.has_port() ? url.port() : "80");
338  std::string scheme_port(scheme + "_" + port);
339
340#if defined(OS_WIN)
341  base::FilePath::StringType host_path(base::UTF8ToUTF16(host));
342  base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port));
343#elif defined(OS_POSIX)
344  base::FilePath::StringType host_path(host);
345  base::FilePath::StringType scheme_port_path(scheme_port);
346#endif
347
348  return app_data_dir.Append(host_path).Append(scheme_port_path);
349}
350
351base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
352                                      const extensions::Extension& extension) {
353  return GetWebAppDataDirectory(
354      profile_path,
355      extension.id(),
356      GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension)));
357}
358
359std::string GenerateApplicationNameFromInfo(
360    const web_app::ShortcutInfo& shortcut_info) {
361  if (!shortcut_info.extension_id.empty()) {
362    return web_app::GenerateApplicationNameFromExtensionId(
363        shortcut_info.extension_id);
364  } else {
365    return web_app::GenerateApplicationNameFromURL(
366        shortcut_info.url);
367  }
368}
369
370std::string GenerateApplicationNameFromURL(const GURL& url) {
371  std::string t;
372  t.append(url.host());
373  t.append("_");
374  t.append(url.path());
375  return t;
376}
377
378std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
379  std::string t(web_app::kCrxAppPrefix);
380  t.append(id);
381  return t;
382}
383
384std::string GetExtensionIdFromApplicationName(const std::string& app_name) {
385  std::string prefix(kCrxAppPrefix);
386  if (app_name.substr(0, prefix.length()) != prefix)
387    return std::string();
388  return app_name.substr(prefix.length());
389}
390
391void CreateShortcutsForShortcutInfo(
392    web_app::ShortcutCreationReason reason,
393    const web_app::ShortcutLocations& locations,
394    const web_app::ShortcutInfo& shortcut_info) {
395  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
396
397  BrowserThread::PostTask(
398      BrowserThread::FILE,
399      FROM_HERE,
400      base::Bind(
401          base::IgnoreResult(&web_app::internals::CreateShortcutsOnFileThread),
402          reason, locations, shortcut_info));
403}
404
405void CreateShortcuts(
406    ShortcutCreationReason reason,
407    const web_app::ShortcutLocations& locations,
408    Profile* profile,
409    const extensions::Extension* app) {
410  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
411
412  GetInfoForApp(app,
413                profile,
414                base::Bind(&CreateShortcutsWithInfo, reason, locations));
415}
416
417void DeleteAllShortcuts(Profile* profile, const extensions::Extension* app) {
418  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
419
420  BrowserThread::PostTask(
421      BrowserThread::FILE,
422      FROM_HERE,
423      base::Bind(&DeleteShortcutsOnFileThread,
424                 web_app::ShortcutInfoForExtensionAndProfile(app, profile)));
425}
426
427void UpdateAllShortcuts(const base::string16& old_app_title,
428                        Profile* profile,
429                        const extensions::Extension* app) {
430  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
431
432  GetInfoForApp(app,
433                profile,
434                base::Bind(&UpdateAllShortcutsForShortcutInfo, old_app_title));
435}
436
437bool IsValidUrl(const GURL& url) {
438  static const char* const kValidUrlSchemes[] = {
439      content::kFileScheme,
440      content::kFileSystemScheme,
441      content::kFtpScheme,
442      content::kHttpScheme,
443      content::kHttpsScheme,
444      extensions::kExtensionScheme,
445  };
446
447  for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
448    if (url.SchemeIs(kValidUrlSchemes[i]))
449      return true;
450  }
451
452  return false;
453}
454
455#if defined(TOOLKIT_VIEWS)
456void GetIconsInfo(const WebApplicationInfo& app_info,
457                  IconInfoList* icons) {
458  DCHECK(icons);
459
460  icons->clear();
461  for (size_t i = 0; i < app_info.icons.size(); ++i) {
462    // We only take square shaped icons (i.e. width == height).
463    if (app_info.icons[i].width == app_info.icons[i].height) {
464      icons->push_back(app_info.icons[i]);
465    }
466  }
467
468  std::sort(icons->begin(), icons->end(), &IconPrecedes);
469}
470#endif
471
472#if defined(OS_LINUX)
473std::string GetWMClassFromAppName(std::string app_name) {
474  file_util::ReplaceIllegalCharactersInPath(&app_name, '_');
475  base::TrimString(app_name, "_", &app_name);
476  return app_name;
477}
478#endif
479
480}  // namespace web_app
481