Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / web_applications / web_app.cc
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
41 using content::BrowserThread;
42
43 namespace {
44
45 typedef base::Callback<void(const web_app::ShortcutInfo&,
46                             const extensions::FileHandlersInfo&)> InfoCallback;
47
48 #if defined(OS_MACOSX)
49 const int kDesiredSizes[] = {16, 32, 128, 256, 512};
50 const 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."
54 const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
55 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
56 #elif defined(OS_WIN)
57 const int* kDesiredSizes = IconUtil::kIconDimensions;
58 const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
59 #else
60 const int kDesiredSizes[] = {32};
61 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
62 #endif
63
64 #if defined(TOOLKIT_VIEWS)
65 // Predicator for sorting images from largest to smallest.
66 bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
67                   const WebApplicationInfo::IconInfo& right) {
68   return left.width < right.width;
69 }
70 #endif
71
72 bool 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
87 void 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
97 void 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
109 void 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
124 void 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
135 void 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
160 void 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
216 void 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
225 namespace 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.
233 static const char* kCrxAppPrefix = "_crx_";
234
235 namespace internals {
236
237 base::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
247 bool 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
259 web_app::ShortcutInfo::ShortcutInfo()
260     : is_platform_app(false) {
261 }
262
263 web_app::ShortcutInfo::~ShortcutInfo() {}
264
265 web_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
275 void 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)
300 void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
301 #endif
302
303 web_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
318 void 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
327 base::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
354 base::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
362 std::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
373 std::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
381 std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
382   std::string t(web_app::kCrxAppPrefix);
383   t.append(id);
384   return t;
385 }
386
387 std::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
394 void 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
408 void 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
420 void 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
430 void 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
440 bool IsValidUrl(const GURL& url) {
441   static const char* const kValidUrlSchemes[] = {
442       content::kFileScheme,
443       content::kFileSystemScheme,
444       content::kFtpScheme,
445       url::kHttpScheme,
446       url::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)
459 void 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)
476 std::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