blob: a675bfba5246826ca5524cd5af31d9bc6b14ffbd [file] [log] [blame]
[email protected]178f8512012-02-09 01:49:361// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]ed5431872009-11-17 08:39:512// 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
[email protected]87212702011-12-08 21:17:197#include "base/bind.h"
[email protected]8806d3b2012-04-13 06:46:348#include "base/bind_helpers.h"
[email protected]ed5431872009-11-17 08:39:519#include "base/file_util.h"
[email protected]a0b60cfd2011-04-06 18:02:4110#include "base/i18n/file_util_icu.h"
[email protected]f18d37ef2014-04-01 11:17:4211#include "base/prefs/pref_service.h"
[email protected]9f0abdb2013-06-10 21:49:3412#include "base/strings/string_util.h"
[email protected]e309f312013-06-07 21:50:0813#include "base/strings/utf_string_conversions.h"
[email protected]7f070d42011-03-09 20:25:3214#include "base/threading/thread.h"
[email protected]090e1ee72014-06-03 13:08:4015#include "chrome/browser/extensions/extension_ui_util.h"
[email protected]f18d37ef2014-04-01 11:17:4216#include "chrome/browser/profiles/profile.h"
[email protected]eabfdae92009-12-11 06:13:5117#include "chrome/common/chrome_constants.h"
[email protected]9922e922013-05-06 08:09:5518#include "chrome/common/chrome_version_info.h"
[email protected]6b414c232013-06-05 07:53:3419#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
[email protected]f18d37ef2014-04-01 11:17:4220#include "chrome/common/pref_names.h"
[email protected]c38831a12011-10-28 12:44:4921#include "content/public/browser/browser_thread.h"
[email protected]090e1ee72014-06-03 13:08:4022#include "extensions/browser/extension_registry.h"
[email protected]326e6f02014-06-20 04:53:3723#include "extensions/browser/image_loader.h"
[email protected]885c0e92012-11-13 20:27:4224#include "extensions/common/constants.h"
[email protected]e4452d32013-11-15 23:07:4125#include "extensions/common/extension.h"
[email protected]090e1ee72014-06-03 13:08:4026#include "extensions/common/extension_set.h"
[email protected]0db486f2014-04-09 19:32:2227#include "extensions/common/manifest_handlers/icons_handler.h"
[email protected]f18d37ef2014-04-01 11:17:4228#include "grit/theme_resources.h"
29#include "skia/ext/image_operations.h"
30#include "third_party/skia/include/core/SkBitmap.h"
31#include "ui/base/resource/resource_bundle.h"
32#include "ui/gfx/image/image.h"
33#include "ui/gfx/image/image_family.h"
34#include "ui/gfx/image/image_skia.h"
[email protected]cca6f392014-05-28 21:32:2635#include "url/url_constants.h"
[email protected]f18d37ef2014-04-01 11:17:4236
37#if defined(OS_WIN)
38#include "ui/gfx/icon_util.h"
39#endif
[email protected]ed5431872009-11-17 08:39:5140
[email protected]c7264f42014-06-22 21:23:0941#if defined(TOOLKIT_VIEWS)
42#include "chrome/browser/extensions/tab_helper.h"
43#include "chrome/browser/favicon/favicon_tab_helper.h"
44#endif
45
[email protected]631bb742011-11-02 11:29:3946using content::BrowserThread;
47
[email protected]ed5431872009-11-17 08:39:5148namespace {
49
[email protected]f18d37ef2014-04-01 11:17:4250#if defined(OS_MACOSX)
51const int kDesiredSizes[] = {16, 32, 128, 256, 512};
52const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
53#elif defined(OS_LINUX)
54// Linux supports icons of any size. FreeDesktop Icon Theme Specification states
55// that "Minimally you should install a 48x48 icon in the hicolor theme."
56const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
57const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
58#elif defined(OS_WIN)
59const int* kDesiredSizes = IconUtil::kIconDimensions;
60const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
61#else
62const int kDesiredSizes[] = {32};
63const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
64#endif
65
[email protected]16f765632010-09-21 21:31:2766#if defined(TOOLKIT_VIEWS)
[email protected]eabfdae92009-12-11 06:13:5167// Predicator for sorting images from largest to smallest.
[email protected]38789d82010-11-17 06:03:4468bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
69 const WebApplicationInfo::IconInfo& right) {
[email protected]eabfdae92009-12-11 06:13:5170 return left.width < right.width;
71}
[email protected]16f765632010-09-21 21:31:2772#endif
[email protected]eabfdae92009-12-11 06:13:5173
[email protected]238dd7b2014-05-23 07:07:2074base::FilePath GetShortcutDataDir(const web_app::ShortcutInfo& shortcut_info) {
75 return web_app::GetWebAppDataDirectory(shortcut_info.profile_path,
76 shortcut_info.extension_id,
77 shortcut_info.url);
[email protected]e66ba952012-10-09 09:59:4478}
79
[email protected]e6be3fe2014-04-11 13:17:5180void UpdateAllShortcutsForShortcutInfo(
81 const base::string16& old_app_title,
[email protected]2e0424a2014-04-15 13:02:1582 const web_app::ShortcutInfo& shortcut_info,
[email protected]e6be3fe2014-04-11 13:17:5183 const extensions::FileHandlersInfo& file_handlers_info) {
84 BrowserThread::PostTask(
85 BrowserThread::FILE,
86 FROM_HERE,
[email protected]238dd7b2014-05-23 07:07:2087 base::Bind(&web_app::internals::UpdatePlatformShortcuts,
88 GetShortcutDataDir(shortcut_info),
[email protected]e6be3fe2014-04-11 13:17:5189 old_app_title, shortcut_info, file_handlers_info));
[email protected]14885082014-04-08 04:41:2890}
91
[email protected]2e0424a2014-04-15 13:02:1592void OnImageLoaded(web_app::ShortcutInfo shortcut_info,
[email protected]e6be3fe2014-04-11 13:17:5193 extensions::FileHandlersInfo file_handlers_info,
[email protected]5bbfbae2014-06-18 18:26:3894 web_app::InfoCallback callback,
[email protected]f18d37ef2014-04-01 11:17:4295 const gfx::ImageFamily& image_family) {
96 // If the image failed to load (e.g. if the resource being loaded was empty)
97 // use the standard application icon.
98 if (image_family.empty()) {
99 gfx::Image default_icon =
100 ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
101 int size = kDesiredSizes[kNumDesiredSizes - 1];
102 SkBitmap bmp = skia::ImageOperations::Resize(
103 *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
104 size, size);
105 gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bmp);
106 // We are on the UI thread, and this image is needed from the FILE thread,
107 // for creating shortcut icon files.
108 image_skia.MakeThreadSafe();
109 shortcut_info.favicon.Add(gfx::Image(image_skia));
110 } else {
111 shortcut_info.favicon = image_family;
112 }
113
[email protected]e6be3fe2014-04-11 13:17:51114 callback.Run(shortcut_info, file_handlers_info);
115}
116
[email protected]5bbfbae2014-06-18 18:26:38117void IgnoreFileHandlersInfo(
118 const web_app::ShortcutInfoCallback& shortcut_info_callback,
119 const web_app::ShortcutInfo& shortcut_info,
120 const extensions::FileHandlersInfo& file_handlers_info) {
121 shortcut_info_callback.Run(shortcut_info);
122}
123
124} // namespace
125
126namespace web_app {
127
128// The following string is used to build the directory name for
129// shortcuts to chrome applications (the kind which are installed
130// from a CRX). Application shortcuts to URLs use the {host}_{path}
131// for the name of this directory. Hosts can't include an underscore.
132// By starting this string with an underscore, we ensure that there
133// are no naming conflicts.
134static const char kCrxAppPrefix[] = "_crx_";
135
136namespace internals {
137
[email protected]0085863a2013-12-06 21:19:03138base::FilePath GetSanitizedFileName(const base::string16& name) {
[email protected]b6b72222012-02-11 02:04:13139#if defined(OS_WIN)
[email protected]0085863a2013-12-06 21:19:03140 base::string16 file_name = name;
[email protected]b6b72222012-02-11 02:04:13141#else
[email protected]ab6df3b12013-12-24 23:32:26142 std::string file_name = base::UTF16ToUTF8(name);
[email protected]b6b72222012-02-11 02:04:13143#endif
144 file_util::ReplaceIllegalCharactersInPath(&file_name, '_');
[email protected]650b2d52013-02-10 03:41:45145 return base::FilePath(file_name);
[email protected]b6b72222012-02-11 02:04:13146}
147
[email protected]f847e6082011-03-24 00:08:26148} // namespace internals
149
[email protected]9b1b5fe2014-05-15 08:23:17150ShortcutInfo::ShortcutInfo()
[email protected]2e0424a2014-04-15 13:02:15151 : is_platform_app(false) {
152}
153
[email protected]9b1b5fe2014-05-15 08:23:17154ShortcutInfo::~ShortcutInfo() {}
[email protected]2e0424a2014-04-15 13:02:15155
[email protected]9b1b5fe2014-05-15 08:23:17156ShortcutLocations::ShortcutLocations()
[email protected]2e0424a2014-04-15 13:02:15157 : on_desktop(false),
158 applications_menu_location(APP_MENU_LOCATION_NONE),
[email protected]da0349e2014-06-11 07:38:28159 in_quick_launch_bar(false) {
[email protected]2e0424a2014-04-15 13:02:15160}
161
[email protected]c7264f42014-06-22 21:23:09162#if defined(TOOLKIT_VIEWS)
[email protected]2d1d16f2014-04-03 18:18:05163void GetShortcutInfoForTab(content::WebContents* web_contents,
[email protected]9b1b5fe2014-05-15 08:23:17164 ShortcutInfo* info) {
[email protected]2d1d16f2014-04-03 18:18:05165 DCHECK(info); // Must provide a valid info.
166
167 const FaviconTabHelper* favicon_tab_helper =
168 FaviconTabHelper::FromWebContents(web_contents);
169 const extensions::TabHelper* extensions_tab_helper =
170 extensions::TabHelper::FromWebContents(web_contents);
171 const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info();
172
173 info->url = app_info.app_url.is_empty() ? web_contents->GetURL() :
174 app_info.app_url;
175 info->title = app_info.title.empty() ?
176 (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) :
177 web_contents->GetTitle()) :
178 app_info.title;
179 info->description = app_info.description;
180 info->favicon.Add(favicon_tab_helper->GetFavicon());
181
182 Profile* profile =
183 Profile::FromBrowserContext(web_contents->GetBrowserContext());
184 info->profile_path = profile->GetPath();
185}
[email protected]c7264f42014-06-22 21:23:09186#endif
[email protected]2d1d16f2014-04-03 18:18:05187
188#if !defined(OS_WIN)
189void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
190#endif
191
[email protected]9b1b5fe2014-05-15 08:23:17192ShortcutInfo ShortcutInfoForExtensionAndProfile(
[email protected]f18d37ef2014-04-01 11:17:42193 const extensions::Extension* app, Profile* profile) {
[email protected]9b1b5fe2014-05-15 08:23:17194 ShortcutInfo shortcut_info;
[email protected]f18d37ef2014-04-01 11:17:42195 shortcut_info.extension_id = app->id();
196 shortcut_info.is_platform_app = app->is_platform_app();
197 shortcut_info.url = extensions::AppLaunchInfo::GetLaunchWebURL(app);
198 shortcut_info.title = base::UTF8ToUTF16(app->name());
199 shortcut_info.description = base::UTF8ToUTF16(app->description());
200 shortcut_info.extension_path = app->path();
201 shortcut_info.profile_path = profile->GetPath();
202 shortcut_info.profile_name =
203 profile->GetPrefs()->GetString(prefs::kProfileName);
204 return shortcut_info;
205}
206
[email protected]e886528e2014-07-01 10:18:41207void GetInfoForApp(const extensions::Extension* extension,
208 Profile* profile,
209 const InfoCallback& callback) {
210 web_app::ShortcutInfo shortcut_info =
211 web_app::ShortcutInfoForExtensionAndProfile(extension, profile);
212 const std::vector<extensions::FileHandlerInfo>* file_handlers =
213 extensions::FileHandlers::GetFileHandlers(extension);
214 extensions::FileHandlersInfo file_handlers_info =
215 file_handlers ? *file_handlers : extensions::FileHandlersInfo();
216
217 std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
218 for (size_t i = 0; i < kNumDesiredSizes; ++i) {
219 int size = kDesiredSizes[i];
220 extensions::ExtensionResource resource =
221 extensions::IconsInfo::GetIconResource(
222 extension, size, ExtensionIconSet::MATCH_EXACTLY);
223 if (!resource.empty()) {
224 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
225 resource,
226 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
227 gfx::Size(size, size),
228 ui::SCALE_FACTOR_100P));
229 }
230 }
231
232 if (info_list.empty()) {
233 size_t i = kNumDesiredSizes - 1;
234 int size = kDesiredSizes[i];
235
236 // If there is no icon at the desired sizes, we will resize what we can get.
237 // Making a large icon smaller is preferred to making a small icon larger,
238 // so look for a larger icon first:
239 extensions::ExtensionResource resource =
240 extensions::IconsInfo::GetIconResource(
241 extension, size, ExtensionIconSet::MATCH_BIGGER);
242 if (resource.empty()) {
243 resource = extensions::IconsInfo::GetIconResource(
244 extension, size, ExtensionIconSet::MATCH_SMALLER);
245 }
246 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
247 resource,
248 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
249 gfx::Size(size, size),
250 ui::SCALE_FACTOR_100P));
251 }
252
253 // |info_list| may still be empty at this point, in which case
254 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
255 // image and exit immediately.
256 extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
257 extension,
258 info_list,
259 base::Bind(&OnImageLoaded, shortcut_info, file_handlers_info, callback));
260}
261
262void GetShortcutInfoForApp(const extensions::Extension* extension,
263 Profile* profile,
264 const ShortcutInfoCallback& callback) {
265 GetInfoForApp(
[email protected]5bbfbae2014-06-18 18:26:38266 extension, profile, base::Bind(&IgnoreFileHandlersInfo, callback));
[email protected]f18d37ef2014-04-01 11:17:42267}
268
[email protected]090e1ee72014-06-03 13:08:40269bool ShouldCreateShortcutFor(Profile* profile,
270 const extensions::Extension* extension) {
271 return extension->is_platform_app() &&
[email protected]0e5c10fa2014-06-11 17:14:57272 extension->location() != extensions::Manifest::COMPONENT &&
273 extensions::ui_util::CanDisplayInAppLauncher(extension, profile);
[email protected]090e1ee72014-06-03 13:08:40274}
275
[email protected]650b2d52013-02-10 03:41:45276base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
277 const std::string& extension_id,
278 const GURL& url) {
[email protected]c002e752012-08-10 12:50:11279 DCHECK(!profile_path.empty());
[email protected]650b2d52013-02-10 03:41:45280 base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
[email protected]2b80909f2012-02-24 04:50:21281
282 if (!extension_id.empty()) {
283 return app_data_dir.AppendASCII(
284 GenerateApplicationNameFromExtensionId(extension_id));
285 }
286
287 std::string host(url.host());
288 std::string scheme(url.has_scheme() ? url.scheme() : "http");
289 std::string port(url.has_port() ? url.port() : "80");
290 std::string scheme_port(scheme + "_" + port);
291
292#if defined(OS_WIN)
[email protected]ab6df3b12013-12-24 23:32:26293 base::FilePath::StringType host_path(base::UTF8ToUTF16(host));
294 base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port));
[email protected]2b80909f2012-02-24 04:50:21295#elif defined(OS_POSIX)
[email protected]650b2d52013-02-10 03:41:45296 base::FilePath::StringType host_path(host);
297 base::FilePath::StringType scheme_port_path(scheme_port);
[email protected]2b80909f2012-02-24 04:50:21298#endif
299
300 return app_data_dir.Append(host_path).Append(scheme_port_path);
301}
302
[email protected]650b2d52013-02-10 03:41:45303base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
304 const extensions::Extension& extension) {
[email protected]2b80909f2012-02-24 04:50:21305 return GetWebAppDataDirectory(
[email protected]6b414c232013-06-05 07:53:34306 profile_path,
307 extension.id(),
308 GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension)));
[email protected]2b80909f2012-02-24 04:50:21309}
310
[email protected]9b1b5fe2014-05-15 08:23:17311std::string GenerateApplicationNameFromInfo(const ShortcutInfo& shortcut_info) {
[email protected]238dd7b2014-05-23 07:07:20312 if (!shortcut_info.extension_id.empty())
[email protected]9b1b5fe2014-05-15 08:23:17313 return GenerateApplicationNameFromExtensionId(shortcut_info.extension_id);
[email protected]238dd7b2014-05-23 07:07:20314 else
[email protected]9b1b5fe2014-05-15 08:23:17315 return GenerateApplicationNameFromURL(shortcut_info.url);
[email protected]a0b60cfd2011-04-06 18:02:41316}
317
[email protected]57ecc4b2010-08-11 03:02:51318std::string GenerateApplicationNameFromURL(const GURL& url) {
[email protected]86b54012009-11-19 09:18:50319 std::string t;
320 t.append(url.host());
321 t.append("_");
322 t.append(url.path());
[email protected]57ecc4b2010-08-11 03:02:51323 return t;
[email protected]86b54012009-11-19 09:18:50324}
325
[email protected]2f1c09d2011-01-14 14:58:14326std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
[email protected]9b1b5fe2014-05-15 08:23:17327 std::string t(kCrxAppPrefix);
[email protected]2f1c09d2011-01-14 14:58:14328 t.append(id);
329 return t;
330}
331
[email protected]edee3faf2011-05-25 21:40:10332std::string GetExtensionIdFromApplicationName(const std::string& app_name) {
333 std::string prefix(kCrxAppPrefix);
334 if (app_name.substr(0, prefix.length()) != prefix)
335 return std::string();
336 return app_name.substr(prefix.length());
337}
338
[email protected]371bd2e2014-07-01 07:10:02339void CreateShortcutsWithInfo(
340 ShortcutCreationReason reason,
341 const ShortcutLocations& locations,
342 const ShortcutInfo& shortcut_info,
343 const extensions::FileHandlersInfo& file_handlers_info) {
[email protected]0b7df36d2012-07-11 09:50:47344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
345
346 BrowserThread::PostTask(
347 BrowserThread::FILE,
348 FROM_HERE,
[email protected]371bd2e2014-07-01 07:10:02349 base::Bind(base::IgnoreResult(&internals::CreatePlatformShortcuts),
350 GetShortcutDataDir(shortcut_info),
351 shortcut_info,
352 file_handlers_info,
353 locations,
354 reason));
[email protected]f8a31292014-04-04 05:45:56355}
356
[email protected]9b1b5fe2014-05-15 08:23:17357void CreateShortcuts(ShortcutCreationReason reason,
358 const ShortcutLocations& locations,
359 Profile* profile,
360 const extensions::Extension* app) {
[email protected]14885082014-04-08 04:41:28361 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
362
[email protected]090e1ee72014-06-03 13:08:40363 if (!ShouldCreateShortcutFor(profile, app))
364 return;
365
[email protected]e886528e2014-07-01 10:18:41366 GetInfoForApp(
[email protected]5bbfbae2014-06-18 18:26:38367 app, profile, base::Bind(&CreateShortcutsWithInfo, reason, locations));
[email protected]14885082014-04-08 04:41:28368}
369
370void DeleteAllShortcuts(Profile* profile, const extensions::Extension* app) {
[email protected]f8a31292014-04-04 05:45:56371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
372
[email protected]238dd7b2014-05-23 07:07:20373 ShortcutInfo shortcut_info =
374 ShortcutInfoForExtensionAndProfile(app, profile);
[email protected]f8a31292014-04-04 05:45:56375 BrowserThread::PostTask(
376 BrowserThread::FILE,
377 FROM_HERE,
[email protected]238dd7b2014-05-23 07:07:20378 base::Bind(&web_app::internals::DeletePlatformShortcuts,
379 GetShortcutDataDir(shortcut_info), shortcut_info));
[email protected]ed5431872009-11-17 08:39:51380}
381
[email protected]0085863a2013-12-06 21:19:03382void UpdateAllShortcuts(const base::string16& old_app_title,
[email protected]14885082014-04-08 04:41:28383 Profile* profile,
384 const extensions::Extension* app) {
[email protected]e66ba952012-10-09 09:59:44385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
386
[email protected]e886528e2014-07-01 10:18:41387 GetInfoForApp(app,
388 profile,
389 base::Bind(&UpdateAllShortcutsForShortcutInfo, old_app_title));
[email protected]8806d3b2012-04-13 06:46:34390}
391
[email protected]12ea22a2009-11-19 07:17:23392bool IsValidUrl(const GURL& url) {
393 static const char* const kValidUrlSchemes[] = {
[email protected]cca6f392014-05-28 21:32:26394 url::kFileScheme,
395 url::kFileSystemScheme,
396 url::kFtpScheme,
[email protected]e8ca69c2014-05-07 15:31:19397 url::kHttpScheme,
398 url::kHttpsScheme,
[email protected]885c0e92012-11-13 20:27:42399 extensions::kExtensionScheme,
[email protected]12ea22a2009-11-19 07:17:23400 };
401
402 for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
403 if (url.SchemeIs(kValidUrlSchemes[i]))
404 return true;
405 }
406
407 return false;
408}
409
[email protected]be3df2b2010-06-25 21:39:07410#if defined(TOOLKIT_VIEWS)
[email protected]38789d82010-11-17 06:03:44411void GetIconsInfo(const WebApplicationInfo& app_info,
[email protected]eabfdae92009-12-11 06:13:51412 IconInfoList* icons) {
413 DCHECK(icons);
414
415 icons->clear();
416 for (size_t i = 0; i < app_info.icons.size(); ++i) {
417 // We only take square shaped icons (i.e. width == height).
418 if (app_info.icons[i].width == app_info.icons[i].height) {
419 icons->push_back(app_info.icons[i]);
420 }
421 }
422
423 std::sort(icons->begin(), icons->end(), &IconPrecedes);
424}
[email protected]be3df2b2010-06-25 21:39:07425#endif
[email protected]eabfdae92009-12-11 06:13:51426
[email protected]f93a77452013-09-02 05:26:35427#if defined(OS_LINUX)
[email protected]a0b60cfd2011-04-06 18:02:41428std::string GetWMClassFromAppName(std::string app_name) {
429 file_util::ReplaceIllegalCharactersInPath(&app_name, '_');
[email protected]466c9862013-12-03 22:05:28430 base::TrimString(app_name, "_", &app_name);
[email protected]a0b60cfd2011-04-06 18:02:41431 return app_name;
432}
433#endif
434
[email protected]f847e6082011-03-24 00:08:26435} // namespace web_app