Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / web_applications / web_app_mac.mm
blob32b0367e6e5b60b842eff5078d3617f1e0fa9456
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.
5 #import "chrome/browser/web_applications/web_app_mac.h"
7 #import <Carbon/Carbon.h>
8 #import <Cocoa/Cocoa.h>
10 #include "base/command_line.h"
11 #include "base/files/file_enumerator.h"
12 #include "base/files/file_util.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/mac/foundation_util.h"
15 #include "base/mac/launch_services_util.h"
16 #include "base/mac/mac_util.h"
17 #include "base/mac/scoped_cftyperef.h"
18 #include "base/mac/scoped_nsobject.h"
19 #include "base/metrics/sparse_histogram.h"
20 #include "base/path_service.h"
21 #include "base/process/process_handle.h"
22 #include "base/strings/string16.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/string_split.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/sys_string_conversions.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/version.h"
29 #include "chrome/browser/browser_process.h"
30 #import "chrome/browser/mac/dock.h"
31 #include "chrome/browser/profiles/profile.h"
32 #include "chrome/browser/profiles/profile_manager.h"
33 #include "chrome/browser/shell_integration.h"
34 #include "chrome/browser/ui/app_list/app_list_service.h"
35 #include "chrome/browser/ui/cocoa/key_equivalent_constants.h"
36 #include "chrome/common/chrome_constants.h"
37 #include "chrome/common/chrome_paths.h"
38 #include "chrome/common/chrome_switches.h"
39 #include "chrome/common/chrome_version_info.h"
40 #import "chrome/common/mac/app_mode_common.h"
41 #include "chrome/grit/generated_resources.h"
42 #include "components/crx_file/id_util.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "extensions/browser/extension_registry.h"
45 #include "extensions/common/extension.h"
46 #include "grit/chrome_unscaled_resources.h"
47 #import "skia/ext/skia_utils_mac.h"
48 #include "third_party/skia/include/core/SkBitmap.h"
49 #include "third_party/skia/include/core/SkColor.h"
50 #include "ui/base/l10n/l10n_util.h"
51 #import "ui/base/l10n/l10n_util_mac.h"
52 #include "ui/base/resource/resource_bundle.h"
53 #include "ui/gfx/image/image_family.h"
55 bool g_app_shims_allow_update_and_launch_in_tests = false;
57 namespace {
59 // Launch Services Key to run as an agent app, which doesn't launch in the dock.
60 NSString* const kLSUIElement = @"LSUIElement";
62 class ScopedCarbonHandle {
63  public:
64   ScopedCarbonHandle(size_t initial_size) : handle_(NewHandle(initial_size)) {
65     DCHECK(handle_);
66     DCHECK_EQ(noErr, MemError());
67   }
68   ~ScopedCarbonHandle() { DisposeHandle(handle_); }
70   Handle Get() { return handle_; }
71   char* Data() { return *handle_; }
72   size_t HandleSize() const { return GetHandleSize(handle_); }
74   IconFamilyHandle GetAsIconFamilyHandle() {
75     return reinterpret_cast<IconFamilyHandle>(handle_);
76   }
78   bool WriteDataToFile(const base::FilePath& path) {
79     NSData* data = [NSData dataWithBytes:Data()
80                                   length:HandleSize()];
81     return [data writeToFile:base::mac::FilePathToNSString(path)
82                   atomically:NO];
83   }
85  private:
86   Handle handle_;
89 void ConvertSkiaToARGB(const SkBitmap& bitmap, ScopedCarbonHandle* handle) {
90   CHECK_EQ(4u * bitmap.width() * bitmap.height(), handle->HandleSize());
92   char* argb = handle->Data();
93   SkAutoLockPixels lock(bitmap);
94   for (int y = 0; y < bitmap.height(); ++y) {
95     for (int x = 0; x < bitmap.width(); ++x) {
96       SkColor pixel = bitmap.getColor(x, y);
97       argb[0] = SkColorGetA(pixel);
98       argb[1] = SkColorGetR(pixel);
99       argb[2] = SkColorGetG(pixel);
100       argb[3] = SkColorGetB(pixel);
101       argb += 4;
102     }
103   }
106 // Adds |image| to |icon_family|. Returns true on success, false on failure.
107 bool AddGfxImageToIconFamily(IconFamilyHandle icon_family,
108                              const gfx::Image& image) {
109   // When called via ShowCreateChromeAppShortcutsDialog the ImageFamily will
110   // have all the representations desired here for mac, from the kDesiredSizes
111   // array in web_app.cc.
112   SkBitmap bitmap = image.AsBitmap();
113   if (bitmap.colorType() != kN32_SkColorType ||
114       bitmap.width() != bitmap.height()) {
115     return false;
116   }
118   OSType icon_type;
119   switch (bitmap.width()) {
120     case 512:
121       icon_type = kIconServices512PixelDataARGB;
122       break;
123     case 256:
124       icon_type = kIconServices256PixelDataARGB;
125       break;
126     case 128:
127       icon_type = kIconServices128PixelDataARGB;
128       break;
129     case 48:
130       icon_type = kIconServices48PixelDataARGB;
131       break;
132     case 32:
133       icon_type = kIconServices32PixelDataARGB;
134       break;
135     case 16:
136       icon_type = kIconServices16PixelDataARGB;
137       break;
138     default:
139       return false;
140   }
142   ScopedCarbonHandle raw_data(bitmap.getSize());
143   ConvertSkiaToARGB(bitmap, &raw_data);
144   OSErr result = SetIconFamilyData(icon_family, icon_type, raw_data.Get());
145   DCHECK_EQ(noErr, result);
146   return result == noErr;
149 bool AppShimsDisabledForTest() {
150   // Disable app shims in tests because shims created in ~/Applications will not
151   // be cleaned up.
152   return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType);
155 base::FilePath GetWritableApplicationsDirectory() {
156   base::FilePath path;
157   if (base::mac::GetUserDirectory(NSApplicationDirectory, &path)) {
158     if (!base::DirectoryExists(path)) {
159       if (!base::CreateDirectory(path))
160         return base::FilePath();
162       // Create a zero-byte ".localized" file to inherit localizations from OSX
163       // for folders that have special meaning.
164       base::WriteFile(path.Append(".localized"), NULL, 0);
165     }
166     return base::PathIsWritable(path) ? path : base::FilePath();
167   }
168   return base::FilePath();
171 // Given the path to an app bundle, return the resources directory.
172 base::FilePath GetResourcesPath(const base::FilePath& app_path) {
173   return app_path.Append("Contents").Append("Resources");
176 bool HasExistingExtensionShim(const base::FilePath& destination_directory,
177                               const std::string& extension_id,
178                               const base::FilePath& own_basename) {
179   // Check if there any any other shims for the same extension.
180   base::FileEnumerator enumerator(destination_directory,
181                                   false /* recursive */,
182                                   base::FileEnumerator::DIRECTORIES);
183   for (base::FilePath shim_path = enumerator.Next();
184        !shim_path.empty(); shim_path = enumerator.Next()) {
185     if (shim_path.BaseName() != own_basename &&
186         base::EndsWith(shim_path.RemoveExtension().value(),
187                        extension_id,
188                        base::CompareCase::SENSITIVE)) {
189       return true;
190     }
191   }
193   return false;
196 // Given the path to an app bundle, return the path to the Info.plist file.
197 NSString* GetPlistPath(const base::FilePath& bundle_path) {
198   return base::mac::FilePathToNSString(
199       bundle_path.Append("Contents").Append("Info.plist"));
202 NSMutableDictionary* ReadPlist(NSString* plist_path) {
203   return [NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
206 // Takes the path to an app bundle and checks that the CrAppModeUserDataDir in
207 // the Info.plist starts with the current user_data_dir. This uses starts with
208 // instead of equals because the CrAppModeUserDataDir could be the user_data_dir
209 // or the |app_data_dir_|.
210 bool HasSameUserDataDir(const base::FilePath& bundle_path) {
211   NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path));
212   base::FilePath user_data_dir;
213   PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
214   DCHECK(!user_data_dir.empty());
215   return base::StartsWith(
216       base::SysNSStringToUTF8(
217           [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]),
218       user_data_dir.value(), base::CompareCase::SENSITIVE);
221 void LaunchShimOnFileThread(scoped_ptr<web_app::ShortcutInfo> shortcut_info,
222                             bool launched_after_rebuild) {
223   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
224   base::FilePath shim_path = web_app::GetAppInstallPath(*shortcut_info);
226   if (shim_path.empty() ||
227       !base::PathExists(shim_path) ||
228       !HasSameUserDataDir(shim_path)) {
229     // The user may have deleted the copy in the Applications folder, use the
230     // one in the web app's |app_data_dir_|.
231     base::FilePath app_data_dir = web_app::GetWebAppDataDirectory(
232         shortcut_info->profile_path, shortcut_info->extension_id, GURL());
233     shim_path = app_data_dir.Append(shim_path.BaseName());
234   }
236   if (!base::PathExists(shim_path))
237     return;
239   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
240   command_line.AppendSwitchASCII(
241       app_mode::kLaunchedByChromeProcessId,
242       base::IntToString(base::GetCurrentProcId()));
243   if (launched_after_rebuild)
244     command_line.AppendSwitch(app_mode::kLaunchedAfterRebuild);
245   // Launch without activating (kLSLaunchDontSwitch).
246   base::mac::OpenApplicationWithPath(
247       shim_path, command_line, kLSLaunchDefaults | kLSLaunchDontSwitch, NULL);
250 base::FilePath GetAppLoaderPath() {
251   return base::mac::PathForFrameworkBundleResource(
252       base::mac::NSToCFCast(@"app_mode_loader.app"));
255 void UpdatePlatformShortcutsInternal(
256     const base::FilePath& app_data_path,
257     const base::string16& old_app_title,
258     const web_app::ShortcutInfo& shortcut_info,
259     const extensions::FileHandlersInfo& file_handlers_info) {
260   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
261   if (AppShimsDisabledForTest() &&
262       !g_app_shims_allow_update_and_launch_in_tests) {
263     return;
264   }
266   web_app::WebAppShortcutCreator shortcut_creator(app_data_path, &shortcut_info,
267                                                   file_handlers_info);
268   shortcut_creator.UpdateShortcuts();
271 void UpdateAndLaunchShimOnFileThread(
272     scoped_ptr<web_app::ShortcutInfo> shortcut_info,
273     const extensions::FileHandlersInfo& file_handlers_info) {
274   base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory(
275       shortcut_info->profile_path, shortcut_info->extension_id, GURL());
276   UpdatePlatformShortcutsInternal(shortcut_data_dir, base::string16(),
277                                   *shortcut_info, file_handlers_info);
278   LaunchShimOnFileThread(shortcut_info.Pass(), true);
281 void UpdateAndLaunchShim(
282     scoped_ptr<web_app::ShortcutInfo> shortcut_info,
283     const extensions::FileHandlersInfo& file_handlers_info) {
284   content::BrowserThread::PostTask(
285       content::BrowserThread::FILE, FROM_HERE,
286       base::Bind(&UpdateAndLaunchShimOnFileThread, base::Passed(&shortcut_info),
287                  file_handlers_info));
290 void RebuildAppAndLaunch(scoped_ptr<web_app::ShortcutInfo> shortcut_info) {
291   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
292   if (shortcut_info->extension_id == app_mode::kAppListModeId) {
293     AppListService* app_list_service =
294         AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE);
295     app_list_service->CreateShortcut();
296     app_list_service->Show();
297     return;
298   }
300   ProfileManager* profile_manager = g_browser_process->profile_manager();
301   Profile* profile =
302       profile_manager->GetProfileByPath(shortcut_info->profile_path);
303   if (!profile || !profile_manager->IsValidProfile(profile))
304     return;
306   extensions::ExtensionRegistry* registry =
307       extensions::ExtensionRegistry::Get(profile);
308   const extensions::Extension* extension = registry->GetExtensionById(
309       shortcut_info->extension_id, extensions::ExtensionRegistry::ENABLED);
310   if (!extension || !extension->is_platform_app())
311     return;
313   web_app::GetInfoForApp(extension, profile, base::Bind(&UpdateAndLaunchShim));
316 base::FilePath GetLocalizableAppShortcutsSubdirName() {
317   static const char kChromiumAppDirName[] = "Chromium Apps.localized";
318   static const char kChromeAppDirName[] = "Chrome Apps.localized";
319   static const char kChromeCanaryAppDirName[] = "Chrome Canary Apps.localized";
321   switch (chrome::VersionInfo::GetChannel()) {
322     case chrome::VersionInfo::CHANNEL_UNKNOWN:
323       return base::FilePath(kChromiumAppDirName);
325     case chrome::VersionInfo::CHANNEL_CANARY:
326       return base::FilePath(kChromeCanaryAppDirName);
328     default:
329       return base::FilePath(kChromeAppDirName);
330   }
333 // Creates a canvas the same size as |overlay|, copies the appropriate
334 // representation from |backgound| into it (according to Cocoa), then draws
335 // |overlay| over it using NSCompositeSourceOver.
336 NSImageRep* OverlayImageRep(NSImage* background, NSImageRep* overlay) {
337   DCHECK(background);
338   NSInteger dimension = [overlay pixelsWide];
339   DCHECK_EQ(dimension, [overlay pixelsHigh]);
340   base::scoped_nsobject<NSBitmapImageRep> canvas([[NSBitmapImageRep alloc]
341       initWithBitmapDataPlanes:NULL
342                     pixelsWide:dimension
343                     pixelsHigh:dimension
344                  bitsPerSample:8
345                samplesPerPixel:4
346                       hasAlpha:YES
347                       isPlanar:NO
348                 colorSpaceName:NSCalibratedRGBColorSpace
349                    bytesPerRow:0
350                   bitsPerPixel:0]);
352   // There isn't a colorspace name constant for sRGB, so retag.
353   NSBitmapImageRep* srgb_canvas = [canvas
354       bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]];
355   canvas.reset([srgb_canvas retain]);
357   // Communicate the DIP scale (1.0). TODO(tapted): Investigate HiDPI.
358   [canvas setSize:NSMakeSize(dimension, dimension)];
360   NSGraphicsContext* drawing_context =
361       [NSGraphicsContext graphicsContextWithBitmapImageRep:canvas];
362   [NSGraphicsContext saveGraphicsState];
363   [NSGraphicsContext setCurrentContext:drawing_context];
364   [background drawInRect:NSMakeRect(0, 0, dimension, dimension)
365                 fromRect:NSZeroRect
366                operation:NSCompositeCopy
367                 fraction:1.0];
368   [overlay drawInRect:NSMakeRect(0, 0, dimension, dimension)
369              fromRect:NSZeroRect
370             operation:NSCompositeSourceOver
371              fraction:1.0
372        respectFlipped:NO
373                 hints:0];
374   [NSGraphicsContext restoreGraphicsState];
375   return canvas.autorelease();
378 // Helper function to extract the single NSImageRep held in a resource bundle
379 // image.
380 NSImageRep* ImageRepForResource(int resource_id) {
381   gfx::Image& image =
382       ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
383   NSArray* image_reps = [image.AsNSImage() representations];
384   DCHECK_EQ(1u, [image_reps count]);
385   return [image_reps objectAtIndex:0];
388 // Adds a localized strings file for the Chrome Apps directory using the current
389 // locale. OSX will use this for the display name.
390 // + Chrome Apps.localized (|apps_directory|)
391 // | + .localized
392 // | | en.strings
393 // | | de.strings
394 void UpdateAppShortcutsSubdirLocalizedName(
395     const base::FilePath& apps_directory) {
396   base::FilePath localized = apps_directory.Append(".localized");
397   if (!base::CreateDirectory(localized))
398     return;
400   base::FilePath directory_name = apps_directory.BaseName().RemoveExtension();
401   base::string16 localized_name = ShellIntegration::GetAppShortcutsSubdirName();
402   NSDictionary* strings_dict = @{
403       base::mac::FilePathToNSString(directory_name) :
404           base::SysUTF16ToNSString(localized_name)
405   };
407   std::string locale = l10n_util::NormalizeLocale(
408       l10n_util::GetApplicationLocale(std::string()));
410   NSString* strings_path = base::mac::FilePathToNSString(
411       localized.Append(locale + ".strings"));
412   [strings_dict writeToFile:strings_path
413                  atomically:YES];
415   base::scoped_nsobject<NSImage> folder_icon_image([[NSImage alloc] init]);
417   // Use complete assets for the small icon sizes. -[NSWorkspace setIcon:] has a
418   // bug when dealing with named NSImages where it incorrectly handles alpha
419   // premultiplication. This is most noticable with small assets since the 1px
420   // border is a much larger component of the small icons.
421   // See http://crbug.com/305373 for details.
422   [folder_icon_image addRepresentation:ImageRepForResource(IDR_APPS_FOLDER_16)];
423   [folder_icon_image addRepresentation:ImageRepForResource(IDR_APPS_FOLDER_32)];
425   // Brand larger folder assets with an embossed app launcher logo to conserve
426   // distro size and for better consistency with changing hue across OSX
427   // versions. The folder is textured, so compresses poorly without this.
428   const int kBrandResourceIds[] = {
429     IDR_APPS_FOLDER_OVERLAY_128,
430     IDR_APPS_FOLDER_OVERLAY_512,
431   };
432   NSImage* base_image = [NSImage imageNamed:NSImageNameFolder];
433   for (size_t i = 0; i < arraysize(kBrandResourceIds); ++i) {
434     NSImageRep* with_overlay =
435         OverlayImageRep(base_image, ImageRepForResource(kBrandResourceIds[i]));
436     DCHECK(with_overlay);
437     if (with_overlay)
438       [folder_icon_image addRepresentation:with_overlay];
439   }
440   [[NSWorkspace sharedWorkspace]
441       setIcon:folder_icon_image
442       forFile:base::mac::FilePathToNSString(apps_directory)
443       options:0];
446 void DeletePathAndParentIfEmpty(const base::FilePath& app_path) {
447   DCHECK(!app_path.empty());
448   base::DeleteFile(app_path, true);
449   base::FilePath apps_folder = app_path.DirName();
450   if (base::IsDirectoryEmpty(apps_folder))
451     base::DeleteFile(apps_folder, false);
454 bool IsShimForProfile(const base::FilePath& base_name,
455                       const std::string& profile_base_name) {
456   if (!base::StartsWith(base_name.value(), profile_base_name,
457                         base::CompareCase::SENSITIVE))
458     return false;
460   if (base_name.Extension() != ".app")
461     return false;
463   std::string app_id = base_name.RemoveExtension().value();
464   // Strip (profile_base_name + " ") from the start.
465   app_id = app_id.substr(profile_base_name.size() + 1);
466   return crx_file::id_util::IdIsValid(app_id);
469 std::vector<base::FilePath> GetAllAppBundlesInPath(
470     const base::FilePath& internal_shortcut_path,
471     const std::string& profile_base_name) {
472   std::vector<base::FilePath> bundle_paths;
474   base::FileEnumerator enumerator(internal_shortcut_path,
475                                   true /* recursive */,
476                                   base::FileEnumerator::DIRECTORIES);
477   for (base::FilePath bundle_path = enumerator.Next();
478        !bundle_path.empty(); bundle_path = enumerator.Next()) {
479     if (IsShimForProfile(bundle_path.BaseName(), profile_base_name))
480       bundle_paths.push_back(bundle_path);
481   }
483   return bundle_paths;
486 scoped_ptr<web_app::ShortcutInfo> BuildShortcutInfoFromBundle(
487     const base::FilePath& bundle_path) {
488   NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path));
490   scoped_ptr<web_app::ShortcutInfo> shortcut_info(new web_app::ShortcutInfo);
491   shortcut_info->extension_id = base::SysNSStringToUTF8(
492       [plist valueForKey:app_mode::kCrAppModeShortcutIDKey]);
493   shortcut_info->is_platform_app = true;
494   shortcut_info->url = GURL(base::SysNSStringToUTF8(
495       [plist valueForKey:app_mode::kCrAppModeShortcutURLKey]));
496   shortcut_info->title = base::SysNSStringToUTF16(
497       [plist valueForKey:app_mode::kCrAppModeShortcutNameKey]);
498   shortcut_info->profile_name = base::SysNSStringToUTF8(
499       [plist valueForKey:app_mode::kCrAppModeProfileNameKey]);
501   // Figure out the profile_path. Since the user_data_dir could contain the
502   // path to the web app data dir.
503   base::FilePath user_data_dir = base::mac::NSStringToFilePath(
504       [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]);
505   base::FilePath profile_base_name = base::mac::NSStringToFilePath(
506       [plist valueForKey:app_mode::kCrAppModeProfileDirKey]);
507   if (user_data_dir.DirName().DirName().BaseName() == profile_base_name)
508     shortcut_info->profile_path = user_data_dir.DirName().DirName();
509   else
510     shortcut_info->profile_path = user_data_dir.Append(profile_base_name);
512   return shortcut_info;
515 scoped_ptr<web_app::ShortcutInfo> RecordAppShimErrorAndBuildShortcutInfo(
516     const base::FilePath& bundle_path) {
517   NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path));
518   NSString* version_string = [plist valueForKey:app_mode::kCrBundleVersionKey];
519   if (!version_string) {
520     // Older bundles have the Chrome version in the following key.
521     version_string =
522         [plist valueForKey:app_mode::kCFBundleShortVersionStringKey];
523   }
524   base::Version full_version(base::SysNSStringToUTF8(version_string));
525   uint32_t major_version = 0;
526   if (full_version.IsValid())
527     major_version = full_version.components()[0];
528   UMA_HISTOGRAM_SPARSE_SLOWLY("Apps.AppShimErrorVersion", major_version);
530   return BuildShortcutInfoFromBundle(bundle_path);
533 void UpdateFileTypes(NSMutableDictionary* plist,
534                      const extensions::FileHandlersInfo& file_handlers_info) {
535   NSMutableArray* document_types =
536       [NSMutableArray arrayWithCapacity:file_handlers_info.size()];
538   for (extensions::FileHandlersInfo::const_iterator info_it =
539            file_handlers_info.begin();
540        info_it != file_handlers_info.end();
541        ++info_it) {
542     const extensions::FileHandlerInfo& info = *info_it;
544     NSMutableArray* file_extensions =
545         [NSMutableArray arrayWithCapacity:info.extensions.size()];
546     for (std::set<std::string>::iterator it = info.extensions.begin();
547          it != info.extensions.end();
548          ++it) {
549       [file_extensions addObject:base::SysUTF8ToNSString(*it)];
550     }
552     NSMutableArray* mime_types =
553         [NSMutableArray arrayWithCapacity:info.types.size()];
554     for (std::set<std::string>::iterator it = info.types.begin();
555          it != info.types.end();
556          ++it) {
557       [mime_types addObject:base::SysUTF8ToNSString(*it)];
558     }
560     NSDictionary* type_dictionary = @{
561       // TODO(jackhou): Add the type name and and icon file once the manifest
562       // supports these.
563       // app_mode::kCFBundleTypeNameKey : ,
564       // app_mode::kCFBundleTypeIconFileKey : ,
565       app_mode::kCFBundleTypeExtensionsKey : file_extensions,
566       app_mode::kCFBundleTypeMIMETypesKey : mime_types,
567       app_mode::kCFBundleTypeRoleKey : app_mode::kBundleTypeRoleViewer
568     };
569     [document_types addObject:type_dictionary];
570   }
572   [plist setObject:document_types
573             forKey:app_mode::kCFBundleDocumentTypesKey];
576 void RevealAppShimInFinderForAppOnFileThread(
577     scoped_ptr<web_app::ShortcutInfo> shortcut_info,
578     const base::FilePath& app_path) {
579   web_app::WebAppShortcutCreator shortcut_creator(
580       app_path, shortcut_info.get(), extensions::FileHandlersInfo());
581   shortcut_creator.RevealAppShimInFinder();
584 }  // namespace
586 @interface CrCreateAppShortcutCheckboxObserver : NSObject {
587  @private
588   NSButton* checkbox_;
589   NSButton* continueButton_;
592 - (id)initWithCheckbox:(NSButton*)checkbox
593         continueButton:(NSButton*)continueButton;
594 - (void)startObserving;
595 - (void)stopObserving;
596 @end
598 @implementation CrCreateAppShortcutCheckboxObserver
600 - (id)initWithCheckbox:(NSButton*)checkbox
601         continueButton:(NSButton*)continueButton {
602   if ((self = [super init])) {
603     checkbox_ = checkbox;
604     continueButton_ = continueButton;
605   }
606   return self;
609 - (void)startObserving {
610   [checkbox_ addObserver:self
611               forKeyPath:@"cell.state"
612                  options:0
613                  context:nil];
616 - (void)stopObserving {
617   [checkbox_ removeObserver:self
618                  forKeyPath:@"cell.state"];
621 - (void)observeValueForKeyPath:(NSString*)keyPath
622                       ofObject:(id)object
623                         change:(NSDictionary*)change
624                        context:(void*)context {
625   [continueButton_ setEnabled:([checkbox_ state] == NSOnState)];
628 @end
630 namespace web_app {
632 WebAppShortcutCreator::WebAppShortcutCreator(
633     const base::FilePath& app_data_dir,
634     const ShortcutInfo* shortcut_info,
635     const extensions::FileHandlersInfo& file_handlers_info)
636     : app_data_dir_(app_data_dir),
637       info_(shortcut_info),
638       file_handlers_info_(file_handlers_info) {
639   DCHECK(shortcut_info);
642 WebAppShortcutCreator::~WebAppShortcutCreator() {}
644 base::FilePath WebAppShortcutCreator::GetApplicationsShortcutPath() const {
645   base::FilePath applications_dir = GetApplicationsDirname();
646   return applications_dir.empty() ?
647       base::FilePath() : applications_dir.Append(GetShortcutBasename());
650 base::FilePath WebAppShortcutCreator::GetInternalShortcutPath() const {
651   return app_data_dir_.Append(GetShortcutBasename());
654 base::FilePath WebAppShortcutCreator::GetShortcutBasename() const {
655   std::string app_name;
656   // Check if there should be a separate shortcut made for different profiles.
657   // Such shortcuts will have a |profile_name| set on the ShortcutInfo,
658   // otherwise it will be empty.
659   if (!info_->profile_name.empty()) {
660     app_name += info_->profile_path.BaseName().value();
661     app_name += ' ';
662   }
663   app_name += info_->extension_id;
664   return base::FilePath(app_name).ReplaceExtension("app");
667 bool WebAppShortcutCreator::BuildShortcut(
668     const base::FilePath& staging_path) const {
669   // Update the app's plist and icon in a temp directory. This works around
670   // a Finder bug where the app's icon doesn't properly update.
671   if (!base::CopyDirectory(GetAppLoaderPath(), staging_path, true)) {
672     LOG(ERROR) << "Copying app to staging path: " << staging_path.value()
673                << " failed.";
674     return false;
675   }
677   return UpdatePlist(staging_path) &&
678       UpdateDisplayName(staging_path) &&
679       UpdateIcon(staging_path);
682 size_t WebAppShortcutCreator::CreateShortcutsIn(
683     const std::vector<base::FilePath>& folders) const {
684   size_t succeeded = 0;
686   base::ScopedTempDir scoped_temp_dir;
687   if (!scoped_temp_dir.CreateUniqueTempDir())
688     return 0;
690   base::FilePath app_name = GetShortcutBasename();
691   base::FilePath staging_path = scoped_temp_dir.path().Append(app_name);
692   if (!BuildShortcut(staging_path))
693     return 0;
695   for (std::vector<base::FilePath>::const_iterator it = folders.begin();
696        it != folders.end(); ++it) {
697     const base::FilePath& dst_path = *it;
698     if (!base::CreateDirectory(dst_path)) {
699       LOG(ERROR) << "Creating directory " << dst_path.value() << " failed.";
700       return succeeded;
701     }
703     // Ensure the copy does not merge with stale info.
704     base::DeleteFile(dst_path.Append(app_name), true);
706     if (!base::CopyDirectory(staging_path, dst_path, true)) {
707       LOG(ERROR) << "Copying app to dst path: " << dst_path.value()
708                  << " failed";
709       return succeeded;
710     }
712     // Remove the quarantine attribute from both the bundle and the executable.
713     base::mac::RemoveQuarantineAttribute(dst_path.Append(app_name));
714     base::mac::RemoveQuarantineAttribute(
715         dst_path.Append(app_name)
716             .Append("Contents").Append("MacOS").Append("app_mode_loader"));
717     ++succeeded;
718   }
720   return succeeded;
723 bool WebAppShortcutCreator::CreateShortcuts(
724     ShortcutCreationReason creation_reason,
725     ShortcutLocations creation_locations) {
726   const base::FilePath applications_dir = GetApplicationsDirname();
727   if (applications_dir.empty() ||
728       !base::DirectoryExists(applications_dir.DirName())) {
729     LOG(ERROR) << "Couldn't find an Applications directory to copy app to.";
730     return false;
731   }
733   UpdateAppShortcutsSubdirLocalizedName(applications_dir);
735   // If non-nil, this path is added to the OSX Dock after creating shortcuts.
736   NSString* path_to_add_to_dock = nil;
738   std::vector<base::FilePath> paths;
740   // The app list shim is not tied to a particular profile, so omit the copy
741   // placed under the profile path. For shims, this copy is used when the
742   // version under Applications is removed, and not needed for app list because
743   // setting LSUIElement means there is no Dock "running" status to show.
744   const bool is_app_list = info_->extension_id == app_mode::kAppListModeId;
745   if (is_app_list) {
746     path_to_add_to_dock = base::SysUTF8ToNSString(
747         applications_dir.Append(GetShortcutBasename()).AsUTF8Unsafe());
748   } else {
749     paths.push_back(app_data_dir_);
750   }
752   bool shortcut_visible =
753       creation_locations.applications_menu_location != APP_MENU_LOCATION_HIDDEN;
754   if (shortcut_visible)
755     paths.push_back(applications_dir);
757   DCHECK(!paths.empty());
758   size_t success_count = CreateShortcutsIn(paths);
759   if (success_count == 0)
760     return false;
762   if (!is_app_list)
763     UpdateInternalBundleIdentifier();
765   if (success_count != paths.size())
766     return false;
768   if (creation_locations.in_quick_launch_bar && path_to_add_to_dock &&
769       shortcut_visible) {
770     switch (dock::AddIcon(path_to_add_to_dock, nil)) {
771       case dock::IconAddFailure:
772         // If adding the icon failed, instead reveal the Finder window.
773         RevealAppShimInFinder();
774         break;
775       case dock::IconAddSuccess:
776       case dock::IconAlreadyPresent:
777         break;
778     }
779     return true;
780   }
782   if (creation_reason == SHORTCUT_CREATION_BY_USER)
783     RevealAppShimInFinder();
785   return true;
788 void WebAppShortcutCreator::DeleteShortcuts() {
789   base::FilePath app_path = GetApplicationsShortcutPath();
790   if (!app_path.empty() && HasSameUserDataDir(app_path))
791     DeletePathAndParentIfEmpty(app_path);
793   // In case the user has moved/renamed/copied the app bundle.
794   base::FilePath bundle_path = GetAppBundleById(GetBundleIdentifier());
795   if (!bundle_path.empty() && HasSameUserDataDir(bundle_path))
796     base::DeleteFile(bundle_path, true);
798   // Delete the internal one.
799   DeletePathAndParentIfEmpty(GetInternalShortcutPath());
802 bool WebAppShortcutCreator::UpdateShortcuts() {
803   std::vector<base::FilePath> paths;
804   paths.push_back(app_data_dir_);
806   // Try to update the copy under /Applications. If that does not exist, check
807   // if a matching bundle can be found elsewhere.
808   base::FilePath app_path = GetApplicationsShortcutPath();
809   if (app_path.empty() || !base::PathExists(app_path))
810     app_path = GetAppBundleById(GetBundleIdentifier());
812   if (app_path.empty()) {
813     if (info_->from_bookmark) {
814       // The bookmark app shortcut has been deleted by the user. Restore it, as
815       // the Mac UI for bookmark apps creates the expectation that the app will
816       // be added to Applications.
817       app_path = GetApplicationsDirname();
818       paths.push_back(app_path);
819     }
820   } else {
821     paths.push_back(app_path.DirName());
822   }
824   size_t success_count = CreateShortcutsIn(paths);
825   if (success_count == 0)
826     return false;
828   UpdateInternalBundleIdentifier();
829   return success_count == paths.size() && !app_path.empty();
832 base::FilePath WebAppShortcutCreator::GetApplicationsDirname() const {
833   base::FilePath path = GetWritableApplicationsDirectory();
834   if (path.empty())
835     return path;
837   return path.Append(GetLocalizableAppShortcutsSubdirName());
840 bool WebAppShortcutCreator::UpdatePlist(const base::FilePath& app_path) const {
841   NSString* extension_id = base::SysUTF8ToNSString(info_->extension_id);
842   NSString* extension_title = base::SysUTF16ToNSString(info_->title);
843   NSString* extension_url = base::SysUTF8ToNSString(info_->url.spec());
844   NSString* chrome_bundle_id =
845       base::SysUTF8ToNSString(base::mac::BaseBundleID());
846   NSDictionary* replacement_dict =
847       [NSDictionary dictionaryWithObjectsAndKeys:
848           extension_id, app_mode::kShortcutIdPlaceholder,
849           extension_title, app_mode::kShortcutNamePlaceholder,
850           extension_url, app_mode::kShortcutURLPlaceholder,
851           chrome_bundle_id, app_mode::kShortcutBrowserBundleIDPlaceholder,
852           nil];
854   NSString* plist_path = GetPlistPath(app_path);
855   NSMutableDictionary* plist = ReadPlist(plist_path);
856   NSArray* keys = [plist allKeys];
858   // 1. Fill in variables.
859   for (id key in keys) {
860     NSString* value = [plist valueForKey:key];
861     if (![value isKindOfClass:[NSString class]] || [value length] < 2)
862       continue;
864     // Remove leading and trailing '@'s.
865     NSString* variable =
866         [value substringWithRange:NSMakeRange(1, [value length] - 2)];
868     NSString* substitution = [replacement_dict valueForKey:variable];
869     if (substitution)
870       [plist setObject:substitution forKey:key];
871   }
873   // 2. Fill in other values.
874   [plist setObject:base::SysUTF8ToNSString(chrome::VersionInfo().Version())
875             forKey:app_mode::kCrBundleVersionKey];
876   [plist setObject:base::SysUTF8ToNSString(info_->version_for_display)
877             forKey:app_mode::kCFBundleShortVersionStringKey];
878   [plist setObject:base::SysUTF8ToNSString(GetBundleIdentifier())
879             forKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
880   [plist setObject:base::mac::FilePathToNSString(app_data_dir_)
881             forKey:app_mode::kCrAppModeUserDataDirKey];
882   [plist setObject:base::mac::FilePathToNSString(info_->profile_path.BaseName())
883             forKey:app_mode::kCrAppModeProfileDirKey];
884   [plist setObject:base::SysUTF8ToNSString(info_->profile_name)
885             forKey:app_mode::kCrAppModeProfileNameKey];
886   [plist setObject:[NSNumber numberWithBool:YES]
887             forKey:app_mode::kLSHasLocalizedDisplayNameKey];
888   if (info_->extension_id == app_mode::kAppListModeId) {
889     // Prevent the app list from bouncing in the dock, and getting a run light.
890     [plist setObject:[NSNumber numberWithBool:YES]
891               forKey:kLSUIElement];
892   }
894   base::FilePath app_name = app_path.BaseName().RemoveExtension();
895   [plist setObject:base::mac::FilePathToNSString(app_name)
896             forKey:base::mac::CFToNSCast(kCFBundleNameKey)];
898   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
899           switches::kEnableAppsFileAssociations)) {
900     UpdateFileTypes(plist, file_handlers_info_);
901   }
903   return [plist writeToFile:plist_path
904                  atomically:YES];
907 bool WebAppShortcutCreator::UpdateDisplayName(
908     const base::FilePath& app_path) const {
909   // Localization is used to display the app name (rather than the bundle
910   // filename). OSX searches for the best language in the order of preferred
911   // languages, but one of them must be found otherwise it will default to
912   // the filename.
913   NSString* language = [[NSLocale preferredLanguages] objectAtIndex:0];
914   base::FilePath localized_dir = GetResourcesPath(app_path).Append(
915       base::SysNSStringToUTF8(language) + ".lproj");
916   if (!base::CreateDirectory(localized_dir))
917     return false;
919   NSString* bundle_name = base::SysUTF16ToNSString(info_->title);
920   NSString* display_name = base::SysUTF16ToNSString(info_->title);
921   if (HasExistingExtensionShim(GetApplicationsDirname(), info_->extension_id,
922                                app_path.BaseName())) {
923     display_name = [bundle_name
924         stringByAppendingString:base::SysUTF8ToNSString(
925             " (" + info_->profile_name + ")")];
926   }
928   NSDictionary* strings_plist = @{
929     base::mac::CFToNSCast(kCFBundleNameKey) : bundle_name,
930     app_mode::kCFBundleDisplayNameKey : display_name
931   };
933   NSString* localized_path = base::mac::FilePathToNSString(
934       localized_dir.Append("InfoPlist.strings"));
935   return [strings_plist writeToFile:localized_path
936                          atomically:YES];
939 bool WebAppShortcutCreator::UpdateIcon(const base::FilePath& app_path) const {
940   if (info_->favicon.empty())
941     return true;
943   ScopedCarbonHandle icon_family(0);
944   bool image_added = false;
945   for (gfx::ImageFamily::const_iterator it = info_->favicon.begin();
946        it != info_->favicon.end(); ++it) {
947     if (it->IsEmpty())
948       continue;
950     // Missing an icon size is not fatal so don't fail if adding the bitmap
951     // doesn't work.
952     if (!AddGfxImageToIconFamily(icon_family.GetAsIconFamilyHandle(), *it))
953       continue;
955     image_added = true;
956   }
958   if (!image_added)
959     return false;
961   base::FilePath resources_path = GetResourcesPath(app_path);
962   if (!base::CreateDirectory(resources_path))
963     return false;
965   return icon_family.WriteDataToFile(resources_path.Append("app.icns"));
968 bool WebAppShortcutCreator::UpdateInternalBundleIdentifier() const {
969   NSString* plist_path = GetPlistPath(GetInternalShortcutPath());
970   NSMutableDictionary* plist = ReadPlist(plist_path);
972   [plist setObject:base::SysUTF8ToNSString(GetInternalBundleIdentifier())
973             forKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
974   return [plist writeToFile:plist_path
975                  atomically:YES];
978 base::FilePath WebAppShortcutCreator::GetAppBundleById(
979     const std::string& bundle_id) const {
980   base::ScopedCFTypeRef<CFStringRef> bundle_id_cf(
981       base::SysUTF8ToCFStringRef(bundle_id));
982   CFURLRef url_ref = NULL;
983   OSStatus status = LSFindApplicationForInfo(
984       kLSUnknownCreator, bundle_id_cf.get(), NULL, NULL, &url_ref);
985   if (status != noErr)
986     return base::FilePath();
988   base::ScopedCFTypeRef<CFURLRef> url(url_ref);
989   NSString* path_string = [base::mac::CFToNSCast(url.get()) path];
990   return base::FilePath([path_string fileSystemRepresentation]);
993 std::string WebAppShortcutCreator::GetBundleIdentifier() const {
994   // Replace spaces in the profile path with hyphen.
995   std::string normalized_profile_path;
996   base::ReplaceChars(info_->profile_path.BaseName().value(), " ", "-",
997                      &normalized_profile_path);
999   // This matches APP_MODE_APP_BUNDLE_ID in chrome/chrome.gyp.
1000   std::string bundle_id = base::mac::BaseBundleID() + std::string(".app.") +
1001                           normalized_profile_path + "-" + info_->extension_id;
1003   return bundle_id;
1006 std::string WebAppShortcutCreator::GetInternalBundleIdentifier() const {
1007   return GetBundleIdentifier() + "-internal";
1010 void WebAppShortcutCreator::RevealAppShimInFinder() const {
1011   base::FilePath app_path = GetApplicationsShortcutPath();
1012   if (app_path.empty())
1013     return;
1015   // Check if the app shim exists.
1016   if (base::PathExists(app_path)) {
1017     // Use selectFile to show the contents of parent directory with the app
1018     // shim selected.
1019     [[NSWorkspace sharedWorkspace]
1020                       selectFile:base::mac::FilePathToNSString(app_path)
1021         inFileViewerRootedAtPath:nil];
1022     return;
1023   }
1025   // Otherwise, go up a directory.
1026   app_path = app_path.DirName();
1027   // Check if the Chrome apps folder exists, otherwise go up to ~/Applications.
1028   if (!base::PathExists(app_path))
1029     app_path = app_path.DirName();
1030   // Since |app_path| is a directory, use openFile to show the contents of
1031   // that directory in Finder.
1032   [[NSWorkspace sharedWorkspace]
1033       openFile:base::mac::FilePathToNSString(app_path)];
1036 base::FilePath GetAppInstallPath(const ShortcutInfo& shortcut_info) {
1037   WebAppShortcutCreator shortcut_creator(base::FilePath(), &shortcut_info,
1038                                          extensions::FileHandlersInfo());
1039   return shortcut_creator.GetApplicationsShortcutPath();
1042 void MaybeLaunchShortcut(scoped_ptr<ShortcutInfo> shortcut_info) {
1043   if (AppShimsDisabledForTest() &&
1044       !g_app_shims_allow_update_and_launch_in_tests) {
1045     return;
1046   }
1048   content::BrowserThread::PostTask(
1049       content::BrowserThread::FILE, FROM_HERE,
1050       base::Bind(&LaunchShimOnFileThread, base::Passed(&shortcut_info), false));
1053 bool MaybeRebuildShortcut(const base::CommandLine& command_line) {
1054   if (!command_line.HasSwitch(app_mode::kAppShimError))
1055     return false;
1057   base::PostTaskAndReplyWithResult(
1058       content::BrowserThread::GetBlockingPool(),
1059       FROM_HERE,
1060       base::Bind(&RecordAppShimErrorAndBuildShortcutInfo,
1061                  command_line.GetSwitchValuePath(app_mode::kAppShimError)),
1062       base::Bind(&RebuildAppAndLaunch));
1063   return true;
1066 // Called when the app's ShortcutInfo (with icon) is loaded when creating app
1067 // shortcuts.
1068 void CreateAppShortcutInfoLoaded(
1069     Profile* profile,
1070     const extensions::Extension* app,
1071     const base::Callback<void(bool)>& close_callback,
1072     scoped_ptr<ShortcutInfo> shortcut_info) {
1073   base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]);
1075   NSButton* continue_button = [alert
1076       addButtonWithTitle:l10n_util::GetNSString(IDS_CREATE_SHORTCUTS_COMMIT)];
1077   [continue_button setKeyEquivalent:kKeyEquivalentReturn];
1079   NSButton* cancel_button =
1080       [alert addButtonWithTitle:l10n_util::GetNSString(IDS_CANCEL)];
1081   [cancel_button setKeyEquivalent:kKeyEquivalentEscape];
1083   [alert setMessageText:l10n_util::GetNSString(IDS_CREATE_SHORTCUTS_LABEL)];
1084   [alert setAlertStyle:NSInformationalAlertStyle];
1086   base::scoped_nsobject<NSButton> application_folder_checkbox(
1087       [[NSButton alloc] initWithFrame:NSZeroRect]);
1088   [application_folder_checkbox setButtonType:NSSwitchButton];
1089   [application_folder_checkbox
1090       setTitle:l10n_util::GetNSString(IDS_CREATE_SHORTCUTS_APP_FOLDER_CHKBOX)];
1091   [application_folder_checkbox setState:NSOnState];
1092   [application_folder_checkbox sizeToFit];
1094   base::scoped_nsobject<CrCreateAppShortcutCheckboxObserver> checkbox_observer(
1095       [[CrCreateAppShortcutCheckboxObserver alloc]
1096           initWithCheckbox:application_folder_checkbox
1097             continueButton:continue_button]);
1098   [checkbox_observer startObserving];
1100   [alert setAccessoryView:application_folder_checkbox];
1102   const int kIconPreviewSizePixels = 128;
1103   const int kIconPreviewTargetSize = 64;
1104   const gfx::Image* icon = shortcut_info->favicon.GetBest(
1105       kIconPreviewSizePixels, kIconPreviewSizePixels);
1107   if (icon && !icon->IsEmpty()) {
1108     NSImage* icon_image = icon->ToNSImage();
1109     [icon_image
1110         setSize:NSMakeSize(kIconPreviewTargetSize, kIconPreviewTargetSize)];
1111     [alert setIcon:icon_image];
1112   }
1114   bool dialog_accepted = false;
1115   if ([alert runModal] == NSAlertFirstButtonReturn &&
1116       [application_folder_checkbox state] == NSOnState) {
1117     dialog_accepted = true;
1118     CreateShortcuts(
1119         SHORTCUT_CREATION_BY_USER, ShortcutLocations(), profile, app);
1120   }
1122   [checkbox_observer stopObserving];
1124   if (!close_callback.is_null())
1125     close_callback.Run(dialog_accepted);
1128 void UpdateShortcutsForAllApps(Profile* profile,
1129                                const base::Closure& callback) {
1130   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
1132   extensions::ExtensionRegistry* registry =
1133       extensions::ExtensionRegistry::Get(profile);
1134   if (!registry)
1135     return;
1137   // Update all apps.
1138   scoped_ptr<extensions::ExtensionSet> everything =
1139       registry->GenerateInstalledExtensionsSet();
1140   for (extensions::ExtensionSet::const_iterator it = everything->begin();
1141        it != everything->end(); ++it) {
1142     if (web_app::ShouldCreateShortcutFor(SHORTCUT_CREATION_AUTOMATED, profile,
1143                                          it->get())) {
1144       web_app::UpdateAllShortcuts(base::string16(), profile, it->get());
1145     }
1146   }
1148   callback.Run();
1151 void RevealAppShimInFinderForApp(Profile* profile,
1152                                  const extensions::Extension* app) {
1153   scoped_ptr<web_app::ShortcutInfo> shortcut_info =
1154       ShortcutInfoForExtensionAndProfile(app, profile);
1155   content::BrowserThread::PostTask(
1156       content::BrowserThread::FILE, FROM_HERE,
1157       base::Bind(&RevealAppShimInFinderForAppOnFileThread,
1158                  base::Passed(&shortcut_info), app->path()));
1161 namespace internals {
1163 bool CreatePlatformShortcuts(
1164     const base::FilePath& app_data_path,
1165     scoped_ptr<ShortcutInfo> shortcut_info,
1166     const extensions::FileHandlersInfo& file_handlers_info,
1167     const ShortcutLocations& creation_locations,
1168     ShortcutCreationReason creation_reason) {
1169   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
1170   if (AppShimsDisabledForTest())
1171     return true;
1173   WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get(),
1174                                          file_handlers_info);
1175   return shortcut_creator.CreateShortcuts(creation_reason, creation_locations);
1178 void DeletePlatformShortcuts(const base::FilePath& app_data_path,
1179                              scoped_ptr<ShortcutInfo> shortcut_info) {
1180   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
1181   WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get(),
1182                                          extensions::FileHandlersInfo());
1183   shortcut_creator.DeleteShortcuts();
1186 void UpdatePlatformShortcuts(
1187     const base::FilePath& app_data_path,
1188     const base::string16& old_app_title,
1189     scoped_ptr<ShortcutInfo> shortcut_info,
1190     const extensions::FileHandlersInfo& file_handlers_info) {
1191   UpdatePlatformShortcutsInternal(app_data_path, old_app_title, *shortcut_info,
1192                                   file_handlers_info);
1195 void DeleteAllShortcutsForProfile(const base::FilePath& profile_path) {
1196   const std::string profile_base_name = profile_path.BaseName().value();
1197   std::vector<base::FilePath> bundles = GetAllAppBundlesInPath(
1198       profile_path.Append(chrome::kWebAppDirname), profile_base_name);
1200   for (std::vector<base::FilePath>::const_iterator it = bundles.begin();
1201        it != bundles.end(); ++it) {
1202     scoped_ptr<web_app::ShortcutInfo> shortcut_info =
1203         BuildShortcutInfoFromBundle(*it);
1204     WebAppShortcutCreator shortcut_creator(it->DirName(), shortcut_info.get(),
1205                                            extensions::FileHandlersInfo());
1206     shortcut_creator.DeleteShortcuts();
1207   }
1210 }  // namespace internals
1212 }  // namespace web_app
1214 namespace chrome {
1216 void ShowCreateChromeAppShortcutsDialog(
1217     gfx::NativeWindow /*parent_window*/,
1218     Profile* profile,
1219     const extensions::Extension* app,
1220     const base::Callback<void(bool)>& close_callback) {
1221   web_app::GetShortcutInfoForApp(
1222       app,
1223       profile,
1224       base::Bind(&web_app::CreateAppShortcutInfoLoaded,
1225                  profile,
1226                  app,
1227                  close_callback));
1230 }  // namespace chrome