ExtensionSyncService: listen for relevant changes instead of being explicitly called...
[chromium-blink-merge.git] / chrome / browser / web_applications / web_app_mac.mm
blobc77e9abe427eb7a275289852cd847517a4bae1eb
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/channel_info.h"
37 #include "chrome/common/chrome_constants.h"
38 #include "chrome/common/chrome_paths.h"
39 #include "chrome/common/chrome_switches.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 "components/version_info/version_info.h"
44 #include "content/public/browser/browser_thread.h"
45 #include "extensions/browser/extension_registry.h"
46 #include "extensions/common/extension.h"
47 #include "grit/chrome_unscaled_resources.h"
48 #import "skia/ext/skia_utils_mac.h"
49 #include "third_party/skia/include/core/SkBitmap.h"
50 #include "third_party/skia/include/core/SkColor.h"
51 #include "ui/base/l10n/l10n_util.h"
52 #import "ui/base/l10n/l10n_util_mac.h"
53 #include "ui/base/resource/resource_bundle.h"
54 #include "ui/gfx/image/image_family.h"
56 bool g_app_shims_allow_update_and_launch_in_tests = false;
58 namespace {
60 // Launch Services Key to run as an agent app, which doesn't launch in the dock.
61 NSString* const kLSUIElement = @"LSUIElement";
63 class ScopedCarbonHandle {
64  public:
65   ScopedCarbonHandle(size_t initial_size) : handle_(NewHandle(initial_size)) {
66     DCHECK(handle_);
67     DCHECK_EQ(noErr, MemError());
68   }
69   ~ScopedCarbonHandle() { DisposeHandle(handle_); }
71   Handle Get() { return handle_; }
72   char* Data() { return *handle_; }
73   size_t HandleSize() const { return GetHandleSize(handle_); }
75   IconFamilyHandle GetAsIconFamilyHandle() {
76     return reinterpret_cast<IconFamilyHandle>(handle_);
77   }
79   bool WriteDataToFile(const base::FilePath& path) {
80     NSData* data = [NSData dataWithBytes:Data()
81                                   length:HandleSize()];
82     return [data writeToFile:base::mac::FilePathToNSString(path)
83                   atomically:NO];
84   }
86  private:
87   Handle handle_;
90 void ConvertSkiaToARGB(const SkBitmap& bitmap, ScopedCarbonHandle* handle) {
91   CHECK_EQ(4u * bitmap.width() * bitmap.height(), handle->HandleSize());
93   char* argb = handle->Data();
94   SkAutoLockPixels lock(bitmap);
95   for (int y = 0; y < bitmap.height(); ++y) {
96     for (int x = 0; x < bitmap.width(); ++x) {
97       SkColor pixel = bitmap.getColor(x, y);
98       argb[0] = SkColorGetA(pixel);
99       argb[1] = SkColorGetR(pixel);
100       argb[2] = SkColorGetG(pixel);
101       argb[3] = SkColorGetB(pixel);
102       argb += 4;
103     }
104   }
107 // Adds |image| to |icon_family|. Returns true on success, false on failure.
108 bool AddGfxImageToIconFamily(IconFamilyHandle icon_family,
109                              const gfx::Image& image) {
110   // When called via ShowCreateChromeAppShortcutsDialog the ImageFamily will
111   // have all the representations desired here for mac, from the kDesiredSizes
112   // array in web_app.cc.
113   SkBitmap bitmap = image.AsBitmap();
114   if (bitmap.colorType() != kN32_SkColorType ||
115       bitmap.width() != bitmap.height()) {
116     return false;
117   }
119   OSType icon_type;
120   switch (bitmap.width()) {
121     case 512:
122       icon_type = kIconServices512PixelDataARGB;
123       break;
124     case 256:
125       icon_type = kIconServices256PixelDataARGB;
126       break;
127     case 128:
128       icon_type = kIconServices128PixelDataARGB;
129       break;
130     case 48:
131       icon_type = kIconServices48PixelDataARGB;
132       break;
133     case 32:
134       icon_type = kIconServices32PixelDataARGB;
135       break;
136     case 16:
137       icon_type = kIconServices16PixelDataARGB;
138       break;
139     default:
140       return false;
141   }
143   ScopedCarbonHandle raw_data(bitmap.getSize());
144   ConvertSkiaToARGB(bitmap, &raw_data);
145   OSErr result = SetIconFamilyData(icon_family, icon_type, raw_data.Get());
146   DCHECK_EQ(noErr, result);
147   return result == noErr;
150 bool AppShimsDisabledForTest() {
151   // Disable app shims in tests because shims created in ~/Applications will not
152   // be cleaned up.
153   return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType);
156 base::FilePath GetWritableApplicationsDirectory() {
157   base::FilePath path;
158   if (base::mac::GetUserDirectory(NSApplicationDirectory, &path)) {
159     if (!base::DirectoryExists(path)) {
160       if (!base::CreateDirectory(path))
161         return base::FilePath();
163       // Create a zero-byte ".localized" file to inherit localizations from OSX
164       // for folders that have special meaning.
165       base::WriteFile(path.Append(".localized"), NULL, 0);
166     }
167     return base::PathIsWritable(path) ? path : base::FilePath();
168   }
169   return base::FilePath();
172 // Given the path to an app bundle, return the resources directory.
173 base::FilePath GetResourcesPath(const base::FilePath& app_path) {
174   return app_path.Append("Contents").Append("Resources");
177 bool HasExistingExtensionShim(const base::FilePath& destination_directory,
178                               const std::string& extension_id,
179                               const base::FilePath& own_basename) {
180   // Check if there any any other shims for the same extension.
181   base::FileEnumerator enumerator(destination_directory,
182                                   false /* recursive */,
183                                   base::FileEnumerator::DIRECTORIES);
184   for (base::FilePath shim_path = enumerator.Next();
185        !shim_path.empty(); shim_path = enumerator.Next()) {
186     if (shim_path.BaseName() != own_basename &&
187         base::EndsWith(shim_path.RemoveExtension().value(),
188                        extension_id,
189                        base::CompareCase::SENSITIVE)) {
190       return true;
191     }
192   }
194   return false;
197 // Given the path to an app bundle, return the path to the Info.plist file.
198 NSString* GetPlistPath(const base::FilePath& bundle_path) {
199   return base::mac::FilePathToNSString(
200       bundle_path.Append("Contents").Append("Info.plist"));
203 NSMutableDictionary* ReadPlist(NSString* plist_path) {
204   return [NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
207 // Takes the path to an app bundle and checks that the CrAppModeUserDataDir in
208 // the Info.plist starts with the current user_data_dir. This uses starts with
209 // instead of equals because the CrAppModeUserDataDir could be the user_data_dir
210 // or the |app_data_dir_|.
211 bool HasSameUserDataDir(const base::FilePath& bundle_path) {
212   NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path));
213   base::FilePath user_data_dir;
214   PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
215   DCHECK(!user_data_dir.empty());
216   return base::StartsWith(
217       base::SysNSStringToUTF8(
218           [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]),
219       user_data_dir.value(), base::CompareCase::SENSITIVE);
222 void LaunchShimOnFileThread(scoped_ptr<web_app::ShortcutInfo> shortcut_info,
223                             bool launched_after_rebuild) {
224   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
225   base::FilePath shim_path = web_app::GetAppInstallPath(*shortcut_info);
227   if (shim_path.empty() ||
228       !base::PathExists(shim_path) ||
229       !HasSameUserDataDir(shim_path)) {
230     // The user may have deleted the copy in the Applications folder, use the
231     // one in the web app's |app_data_dir_|.
232     base::FilePath app_data_dir = web_app::GetWebAppDataDirectory(
233         shortcut_info->profile_path, shortcut_info->extension_id, GURL());
234     shim_path = app_data_dir.Append(shim_path.BaseName());
235   }
237   if (!base::PathExists(shim_path))
238     return;
240   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
241   command_line.AppendSwitchASCII(
242       app_mode::kLaunchedByChromeProcessId,
243       base::IntToString(base::GetCurrentProcId()));
244   if (launched_after_rebuild)
245     command_line.AppendSwitch(app_mode::kLaunchedAfterRebuild);
246   // Launch without activating (kLSLaunchDontSwitch).
247   base::mac::OpenApplicationWithPath(
248       shim_path, command_line, kLSLaunchDefaults | kLSLaunchDontSwitch, NULL);
251 base::FilePath GetAppLoaderPath() {
252   return base::mac::PathForFrameworkBundleResource(
253       base::mac::NSToCFCast(@"app_mode_loader.app"));
256 void UpdatePlatformShortcutsInternal(
257     const base::FilePath& app_data_path,
258     const base::string16& old_app_title,
259     const web_app::ShortcutInfo& shortcut_info,
260     const extensions::FileHandlersInfo& file_handlers_info) {
261   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
262   if (AppShimsDisabledForTest() &&
263       !g_app_shims_allow_update_and_launch_in_tests) {
264     return;
265   }
267   web_app::WebAppShortcutCreator shortcut_creator(app_data_path, &shortcut_info,
268                                                   file_handlers_info);
269   shortcut_creator.UpdateShortcuts();
272 void UpdateAndLaunchShimOnFileThread(
273     scoped_ptr<web_app::ShortcutInfo> shortcut_info,
274     const extensions::FileHandlersInfo& file_handlers_info) {
275   base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory(
276       shortcut_info->profile_path, shortcut_info->extension_id, GURL());
277   UpdatePlatformShortcutsInternal(shortcut_data_dir, base::string16(),
278                                   *shortcut_info, file_handlers_info);
279   LaunchShimOnFileThread(shortcut_info.Pass(), true);
282 void UpdateAndLaunchShim(
283     scoped_ptr<web_app::ShortcutInfo> shortcut_info,
284     const extensions::FileHandlersInfo& file_handlers_info) {
285   content::BrowserThread::PostTask(
286       content::BrowserThread::FILE, FROM_HERE,
287       base::Bind(&UpdateAndLaunchShimOnFileThread, base::Passed(&shortcut_info),
288                  file_handlers_info));
291 void RebuildAppAndLaunch(scoped_ptr<web_app::ShortcutInfo> shortcut_info) {
292   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
293   if (shortcut_info->extension_id == app_mode::kAppListModeId) {
294     AppListService* app_list_service =
295         AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE);
296     app_list_service->CreateShortcut();
297     app_list_service->Show();
298     return;
299   }
301   ProfileManager* profile_manager = g_browser_process->profile_manager();
302   Profile* profile =
303       profile_manager->GetProfileByPath(shortcut_info->profile_path);
304   if (!profile || !profile_manager->IsValidProfile(profile))
305     return;
307   extensions::ExtensionRegistry* registry =
308       extensions::ExtensionRegistry::Get(profile);
309   const extensions::Extension* extension = registry->GetExtensionById(
310       shortcut_info->extension_id, extensions::ExtensionRegistry::ENABLED);
311   if (!extension || !extension->is_platform_app())
312     return;
314   web_app::GetInfoForApp(extension, profile, base::Bind(&UpdateAndLaunchShim));
317 base::FilePath GetLocalizableAppShortcutsSubdirName() {
318   static const char kChromiumAppDirName[] = "Chromium Apps.localized";
319   static const char kChromeAppDirName[] = "Chrome Apps.localized";
320   static const char kChromeCanaryAppDirName[] = "Chrome Canary Apps.localized";
322   switch (chrome::GetChannel()) {
323     case version_info::Channel::UNKNOWN:
324       return base::FilePath(kChromiumAppDirName);
326     case version_info::Channel::CANARY:
327       return base::FilePath(kChromeCanaryAppDirName);
329     default:
330       return base::FilePath(kChromeAppDirName);
331   }
334 // Creates a canvas the same size as |overlay|, copies the appropriate
335 // representation from |backgound| into it (according to Cocoa), then draws
336 // |overlay| over it using NSCompositeSourceOver.
337 NSImageRep* OverlayImageRep(NSImage* background, NSImageRep* overlay) {
338   DCHECK(background);
339   NSInteger dimension = [overlay pixelsWide];
340   DCHECK_EQ(dimension, [overlay pixelsHigh]);
341   base::scoped_nsobject<NSBitmapImageRep> canvas([[NSBitmapImageRep alloc]
342       initWithBitmapDataPlanes:NULL
343                     pixelsWide:dimension
344                     pixelsHigh:dimension
345                  bitsPerSample:8
346                samplesPerPixel:4
347                       hasAlpha:YES
348                       isPlanar:NO
349                 colorSpaceName:NSCalibratedRGBColorSpace
350                    bytesPerRow:0
351                   bitsPerPixel:0]);
353   // There isn't a colorspace name constant for sRGB, so retag.
354   NSBitmapImageRep* srgb_canvas = [canvas
355       bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]];
356   canvas.reset([srgb_canvas retain]);
358   // Communicate the DIP scale (1.0). TODO(tapted): Investigate HiDPI.
359   [canvas setSize:NSMakeSize(dimension, dimension)];
361   NSGraphicsContext* drawing_context =
362       [NSGraphicsContext graphicsContextWithBitmapImageRep:canvas];
363   [NSGraphicsContext saveGraphicsState];
364   [NSGraphicsContext setCurrentContext:drawing_context];
365   [background drawInRect:NSMakeRect(0, 0, dimension, dimension)
366                 fromRect:NSZeroRect
367                operation:NSCompositeCopy
368                 fraction:1.0];
369   [overlay drawInRect:NSMakeRect(0, 0, dimension, dimension)
370              fromRect:NSZeroRect
371             operation:NSCompositeSourceOver
372              fraction:1.0
373        respectFlipped:NO
374                 hints:0];
375   [NSGraphicsContext restoreGraphicsState];
376   return canvas.autorelease();
379 // Helper function to extract the single NSImageRep held in a resource bundle
380 // image.
381 NSImageRep* ImageRepForResource(int resource_id) {
382   gfx::Image& image =
383       ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
384   NSArray* image_reps = [image.AsNSImage() representations];
385   DCHECK_EQ(1u, [image_reps count]);
386   return [image_reps objectAtIndex:0];
389 // Adds a localized strings file for the Chrome Apps directory using the current
390 // locale. OSX will use this for the display name.
391 // + Chrome Apps.localized (|apps_directory|)
392 // | + .localized
393 // | | en.strings
394 // | | de.strings
395 void UpdateAppShortcutsSubdirLocalizedName(
396     const base::FilePath& apps_directory) {
397   base::FilePath localized = apps_directory.Append(".localized");
398   if (!base::CreateDirectory(localized))
399     return;
401   base::FilePath directory_name = apps_directory.BaseName().RemoveExtension();
402   base::string16 localized_name = ShellIntegration::GetAppShortcutsSubdirName();
403   NSDictionary* strings_dict = @{
404       base::mac::FilePathToNSString(directory_name) :
405           base::SysUTF16ToNSString(localized_name)
406   };
408   std::string locale = l10n_util::NormalizeLocale(
409       l10n_util::GetApplicationLocale(std::string()));
411   NSString* strings_path = base::mac::FilePathToNSString(
412       localized.Append(locale + ".strings"));
413   [strings_dict writeToFile:strings_path
414                  atomically:YES];
416   base::scoped_nsobject<NSImage> folder_icon_image([[NSImage alloc] init]);
418   // Use complete assets for the small icon sizes. -[NSWorkspace setIcon:] has a
419   // bug when dealing with named NSImages where it incorrectly handles alpha
420   // premultiplication. This is most noticable with small assets since the 1px
421   // border is a much larger component of the small icons.
422   // See http://crbug.com/305373 for details.
423   [folder_icon_image addRepresentation:ImageRepForResource(IDR_APPS_FOLDER_16)];
424   [folder_icon_image addRepresentation:ImageRepForResource(IDR_APPS_FOLDER_32)];
426   // Brand larger folder assets with an embossed app launcher logo to conserve
427   // distro size and for better consistency with changing hue across OSX
428   // versions. The folder is textured, so compresses poorly without this.
429   const int kBrandResourceIds[] = {
430     IDR_APPS_FOLDER_OVERLAY_128,
431     IDR_APPS_FOLDER_OVERLAY_512,
432   };
433   NSImage* base_image = [NSImage imageNamed:NSImageNameFolder];
434   for (size_t i = 0; i < arraysize(kBrandResourceIds); ++i) {
435     NSImageRep* with_overlay =
436         OverlayImageRep(base_image, ImageRepForResource(kBrandResourceIds[i]));
437     DCHECK(with_overlay);
438     if (with_overlay)
439       [folder_icon_image addRepresentation:with_overlay];
440   }
441   [[NSWorkspace sharedWorkspace]
442       setIcon:folder_icon_image
443       forFile:base::mac::FilePathToNSString(apps_directory)
444       options:0];
447 void DeletePathAndParentIfEmpty(const base::FilePath& app_path) {
448   DCHECK(!app_path.empty());
449   base::DeleteFile(app_path, true);
450   base::FilePath apps_folder = app_path.DirName();
451   if (base::IsDirectoryEmpty(apps_folder))
452     base::DeleteFile(apps_folder, false);
455 bool IsShimForProfile(const base::FilePath& base_name,
456                       const std::string& profile_base_name) {
457   if (!base::StartsWith(base_name.value(), profile_base_name,
458                         base::CompareCase::SENSITIVE))
459     return false;
461   if (base_name.Extension() != ".app")
462     return false;
464   std::string app_id = base_name.RemoveExtension().value();
465   // Strip (profile_base_name + " ") from the start.
466   app_id = app_id.substr(profile_base_name.size() + 1);
467   return crx_file::id_util::IdIsValid(app_id);
470 std::vector<base::FilePath> GetAllAppBundlesInPath(
471     const base::FilePath& internal_shortcut_path,
472     const std::string& profile_base_name) {
473   std::vector<base::FilePath> bundle_paths;
475   base::FileEnumerator enumerator(internal_shortcut_path,
476                                   true /* recursive */,
477                                   base::FileEnumerator::DIRECTORIES);
478   for (base::FilePath bundle_path = enumerator.Next();
479        !bundle_path.empty(); bundle_path = enumerator.Next()) {
480     if (IsShimForProfile(bundle_path.BaseName(), profile_base_name))
481       bundle_paths.push_back(bundle_path);
482   }
484   return bundle_paths;
487 scoped_ptr<web_app::ShortcutInfo> BuildShortcutInfoFromBundle(
488     const base::FilePath& bundle_path) {
489   NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path));
491   scoped_ptr<web_app::ShortcutInfo> shortcut_info(new web_app::ShortcutInfo);
492   shortcut_info->extension_id = base::SysNSStringToUTF8(
493       [plist valueForKey:app_mode::kCrAppModeShortcutIDKey]);
494   shortcut_info->is_platform_app = true;
495   shortcut_info->url = GURL(base::SysNSStringToUTF8(
496       [plist valueForKey:app_mode::kCrAppModeShortcutURLKey]));
497   shortcut_info->title = base::SysNSStringToUTF16(
498       [plist valueForKey:app_mode::kCrAppModeShortcutNameKey]);
499   shortcut_info->profile_name = base::SysNSStringToUTF8(
500       [plist valueForKey:app_mode::kCrAppModeProfileNameKey]);
502   // Figure out the profile_path. Since the user_data_dir could contain the
503   // path to the web app data dir.
504   base::FilePath user_data_dir = base::mac::NSStringToFilePath(
505       [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]);
506   base::FilePath profile_base_name = base::mac::NSStringToFilePath(
507       [plist valueForKey:app_mode::kCrAppModeProfileDirKey]);
508   if (user_data_dir.DirName().DirName().BaseName() == profile_base_name)
509     shortcut_info->profile_path = user_data_dir.DirName().DirName();
510   else
511     shortcut_info->profile_path = user_data_dir.Append(profile_base_name);
513   return shortcut_info;
516 scoped_ptr<web_app::ShortcutInfo> RecordAppShimErrorAndBuildShortcutInfo(
517     const base::FilePath& bundle_path) {
518   NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path));
519   NSString* version_string = [plist valueForKey:app_mode::kCrBundleVersionKey];
520   if (!version_string) {
521     // Older bundles have the Chrome version in the following key.
522     version_string =
523         [plist valueForKey:app_mode::kCFBundleShortVersionStringKey];
524   }
525   base::Version full_version(base::SysNSStringToUTF8(version_string));
526   uint32_t major_version = 0;
527   if (full_version.IsValid())
528     major_version = full_version.components()[0];
529   UMA_HISTOGRAM_SPARSE_SLOWLY("Apps.AppShimErrorVersion", major_version);
531   return BuildShortcutInfoFromBundle(bundle_path);
534 void UpdateFileTypes(NSMutableDictionary* plist,
535                      const extensions::FileHandlersInfo& file_handlers_info) {
536   NSMutableArray* document_types =
537       [NSMutableArray arrayWithCapacity:file_handlers_info.size()];
539   for (extensions::FileHandlersInfo::const_iterator info_it =
540            file_handlers_info.begin();
541        info_it != file_handlers_info.end();
542        ++info_it) {
543     const extensions::FileHandlerInfo& info = *info_it;
545     NSMutableArray* file_extensions =
546         [NSMutableArray arrayWithCapacity:info.extensions.size()];
547     for (std::set<std::string>::iterator it = info.extensions.begin();
548          it != info.extensions.end();
549          ++it) {
550       [file_extensions addObject:base::SysUTF8ToNSString(*it)];
551     }
553     NSMutableArray* mime_types =
554         [NSMutableArray arrayWithCapacity:info.types.size()];
555     for (std::set<std::string>::iterator it = info.types.begin();
556          it != info.types.end();
557          ++it) {
558       [mime_types addObject:base::SysUTF8ToNSString(*it)];
559     }
561     NSDictionary* type_dictionary = @{
562       // TODO(jackhou): Add the type name and and icon file once the manifest
563       // supports these.
564       // app_mode::kCFBundleTypeNameKey : ,
565       // app_mode::kCFBundleTypeIconFileKey : ,
566       app_mode::kCFBundleTypeExtensionsKey : file_extensions,
567       app_mode::kCFBundleTypeMIMETypesKey : mime_types,
568       app_mode::kCFBundleTypeRoleKey : app_mode::kBundleTypeRoleViewer
569     };
570     [document_types addObject:type_dictionary];
571   }
573   [plist setObject:document_types
574             forKey:app_mode::kCFBundleDocumentTypesKey];
577 void RevealAppShimInFinderForAppOnFileThread(
578     scoped_ptr<web_app::ShortcutInfo> shortcut_info,
579     const base::FilePath& app_path) {
580   web_app::WebAppShortcutCreator shortcut_creator(
581       app_path, shortcut_info.get(), extensions::FileHandlersInfo());
582   shortcut_creator.RevealAppShimInFinder();
585 }  // namespace
587 @interface CrCreateAppShortcutCheckboxObserver : NSObject {
588  @private
589   NSButton* checkbox_;
590   NSButton* continueButton_;
593 - (id)initWithCheckbox:(NSButton*)checkbox
594         continueButton:(NSButton*)continueButton;
595 - (void)startObserving;
596 - (void)stopObserving;
597 @end
599 @implementation CrCreateAppShortcutCheckboxObserver
601 - (id)initWithCheckbox:(NSButton*)checkbox
602         continueButton:(NSButton*)continueButton {
603   if ((self = [super init])) {
604     checkbox_ = checkbox;
605     continueButton_ = continueButton;
606   }
607   return self;
610 - (void)startObserving {
611   [checkbox_ addObserver:self
612               forKeyPath:@"cell.state"
613                  options:0
614                  context:nil];
617 - (void)stopObserving {
618   [checkbox_ removeObserver:self
619                  forKeyPath:@"cell.state"];
622 - (void)observeValueForKeyPath:(NSString*)keyPath
623                       ofObject:(id)object
624                         change:(NSDictionary*)change
625                        context:(void*)context {
626   [continueButton_ setEnabled:([checkbox_ state] == NSOnState)];
629 @end
631 namespace web_app {
633 WebAppShortcutCreator::WebAppShortcutCreator(
634     const base::FilePath& app_data_dir,
635     const ShortcutInfo* shortcut_info,
636     const extensions::FileHandlersInfo& file_handlers_info)
637     : app_data_dir_(app_data_dir),
638       info_(shortcut_info),
639       file_handlers_info_(file_handlers_info) {
640   DCHECK(shortcut_info);
643 WebAppShortcutCreator::~WebAppShortcutCreator() {}
645 base::FilePath WebAppShortcutCreator::GetApplicationsShortcutPath() const {
646   base::FilePath applications_dir = GetApplicationsDirname();
647   return applications_dir.empty() ?
648       base::FilePath() : applications_dir.Append(GetShortcutBasename());
651 base::FilePath WebAppShortcutCreator::GetInternalShortcutPath() const {
652   return app_data_dir_.Append(GetShortcutBasename());
655 base::FilePath WebAppShortcutCreator::GetShortcutBasename() const {
656   std::string app_name;
657   // Check if there should be a separate shortcut made for different profiles.
658   // Such shortcuts will have a |profile_name| set on the ShortcutInfo,
659   // otherwise it will be empty.
660   if (!info_->profile_name.empty()) {
661     app_name += info_->profile_path.BaseName().value();
662     app_name += ' ';
663   }
664   app_name += info_->extension_id;
665   return base::FilePath(app_name).ReplaceExtension("app");
668 bool WebAppShortcutCreator::BuildShortcut(
669     const base::FilePath& staging_path) const {
670   // Update the app's plist and icon in a temp directory. This works around
671   // a Finder bug where the app's icon doesn't properly update.
672   if (!base::CopyDirectory(GetAppLoaderPath(), staging_path, true)) {
673     LOG(ERROR) << "Copying app to staging path: " << staging_path.value()
674                << " failed.";
675     return false;
676   }
678   return UpdatePlist(staging_path) &&
679       UpdateDisplayName(staging_path) &&
680       UpdateIcon(staging_path);
683 size_t WebAppShortcutCreator::CreateShortcutsIn(
684     const std::vector<base::FilePath>& folders) const {
685   size_t succeeded = 0;
687   base::ScopedTempDir scoped_temp_dir;
688   if (!scoped_temp_dir.CreateUniqueTempDir())
689     return 0;
691   base::FilePath app_name = GetShortcutBasename();
692   base::FilePath staging_path = scoped_temp_dir.path().Append(app_name);
693   if (!BuildShortcut(staging_path))
694     return 0;
696   for (std::vector<base::FilePath>::const_iterator it = folders.begin();
697        it != folders.end(); ++it) {
698     const base::FilePath& dst_path = *it;
699     if (!base::CreateDirectory(dst_path)) {
700       LOG(ERROR) << "Creating directory " << dst_path.value() << " failed.";
701       return succeeded;
702     }
704     // Ensure the copy does not merge with stale info.
705     base::DeleteFile(dst_path.Append(app_name), true);
707     if (!base::CopyDirectory(staging_path, dst_path, true)) {
708       LOG(ERROR) << "Copying app to dst path: " << dst_path.value()
709                  << " failed";
710       return succeeded;
711     }
713     // Remove the quarantine attribute from both the bundle and the executable.
714     base::mac::RemoveQuarantineAttribute(dst_path.Append(app_name));
715     base::mac::RemoveQuarantineAttribute(
716         dst_path.Append(app_name)
717             .Append("Contents").Append("MacOS").Append("app_mode_loader"));
718     ++succeeded;
719   }
721   return succeeded;
724 bool WebAppShortcutCreator::CreateShortcuts(
725     ShortcutCreationReason creation_reason,
726     ShortcutLocations creation_locations) {
727   const base::FilePath applications_dir = GetApplicationsDirname();
728   if (applications_dir.empty() ||
729       !base::DirectoryExists(applications_dir.DirName())) {
730     LOG(ERROR) << "Couldn't find an Applications directory to copy app to.";
731     return false;
732   }
734   UpdateAppShortcutsSubdirLocalizedName(applications_dir);
736   // If non-nil, this path is added to the OSX Dock after creating shortcuts.
737   NSString* path_to_add_to_dock = nil;
739   std::vector<base::FilePath> paths;
741   // The app list shim is not tied to a particular profile, so omit the copy
742   // placed under the profile path. For shims, this copy is used when the
743   // version under Applications is removed, and not needed for app list because
744   // setting LSUIElement means there is no Dock "running" status to show.
745   const bool is_app_list = info_->extension_id == app_mode::kAppListModeId;
746   if (is_app_list) {
747     path_to_add_to_dock = base::SysUTF8ToNSString(
748         applications_dir.Append(GetShortcutBasename()).AsUTF8Unsafe());
749   } else {
750     paths.push_back(app_data_dir_);
751   }
753   bool shortcut_visible =
754       creation_locations.applications_menu_location != APP_MENU_LOCATION_HIDDEN;
755   if (shortcut_visible)
756     paths.push_back(applications_dir);
758   DCHECK(!paths.empty());
759   size_t success_count = CreateShortcutsIn(paths);
760   if (success_count == 0)
761     return false;
763   if (!is_app_list)
764     UpdateInternalBundleIdentifier();
766   if (success_count != paths.size())
767     return false;
769   if (creation_locations.in_quick_launch_bar && path_to_add_to_dock &&
770       shortcut_visible) {
771     switch (dock::AddIcon(path_to_add_to_dock, nil)) {
772       case dock::IconAddFailure:
773         // If adding the icon failed, instead reveal the Finder window.
774         RevealAppShimInFinder();
775         break;
776       case dock::IconAddSuccess:
777       case dock::IconAlreadyPresent:
778         break;
779     }
780     return true;
781   }
783   if (creation_reason == SHORTCUT_CREATION_BY_USER)
784     RevealAppShimInFinder();
786   return true;
789 void WebAppShortcutCreator::DeleteShortcuts() {
790   base::FilePath app_path = GetApplicationsShortcutPath();
791   if (!app_path.empty() && HasSameUserDataDir(app_path))
792     DeletePathAndParentIfEmpty(app_path);
794   // In case the user has moved/renamed/copied the app bundle.
795   base::FilePath bundle_path = GetAppBundleById(GetBundleIdentifier());
796   if (!bundle_path.empty() && HasSameUserDataDir(bundle_path))
797     base::DeleteFile(bundle_path, true);
799   // Delete the internal one.
800   DeletePathAndParentIfEmpty(GetInternalShortcutPath());
803 bool WebAppShortcutCreator::UpdateShortcuts() {
804   std::vector<base::FilePath> paths;
805   paths.push_back(app_data_dir_);
807   // Try to update the copy under /Applications. If that does not exist, check
808   // if a matching bundle can be found elsewhere.
809   base::FilePath app_path = GetApplicationsShortcutPath();
810   if (app_path.empty() || !base::PathExists(app_path))
811     app_path = GetAppBundleById(GetBundleIdentifier());
813   if (app_path.empty()) {
814     if (info_->from_bookmark) {
815       // The bookmark app shortcut has been deleted by the user. Restore it, as
816       // the Mac UI for bookmark apps creates the expectation that the app will
817       // be added to Applications.
818       app_path = GetApplicationsDirname();
819       paths.push_back(app_path);
820     }
821   } else {
822     paths.push_back(app_path.DirName());
823   }
825   size_t success_count = CreateShortcutsIn(paths);
826   if (success_count == 0)
827     return false;
829   UpdateInternalBundleIdentifier();
830   return success_count == paths.size() && !app_path.empty();
833 base::FilePath WebAppShortcutCreator::GetApplicationsDirname() const {
834   base::FilePath path = GetWritableApplicationsDirectory();
835   if (path.empty())
836     return path;
838   return path.Append(GetLocalizableAppShortcutsSubdirName());
841 bool WebAppShortcutCreator::UpdatePlist(const base::FilePath& app_path) const {
842   NSString* extension_id = base::SysUTF8ToNSString(info_->extension_id);
843   NSString* extension_title = base::SysUTF16ToNSString(info_->title);
844   NSString* extension_url = base::SysUTF8ToNSString(info_->url.spec());
845   NSString* chrome_bundle_id =
846       base::SysUTF8ToNSString(base::mac::BaseBundleID());
847   NSDictionary* replacement_dict =
848       [NSDictionary dictionaryWithObjectsAndKeys:
849           extension_id, app_mode::kShortcutIdPlaceholder,
850           extension_title, app_mode::kShortcutNamePlaceholder,
851           extension_url, app_mode::kShortcutURLPlaceholder,
852           chrome_bundle_id, app_mode::kShortcutBrowserBundleIDPlaceholder,
853           nil];
855   NSString* plist_path = GetPlistPath(app_path);
856   NSMutableDictionary* plist = ReadPlist(plist_path);
857   NSArray* keys = [plist allKeys];
859   // 1. Fill in variables.
860   for (id key in keys) {
861     NSString* value = [plist valueForKey:key];
862     if (![value isKindOfClass:[NSString class]] || [value length] < 2)
863       continue;
865     // Remove leading and trailing '@'s.
866     NSString* variable =
867         [value substringWithRange:NSMakeRange(1, [value length] - 2)];
869     NSString* substitution = [replacement_dict valueForKey:variable];
870     if (substitution)
871       [plist setObject:substitution forKey:key];
872   }
874   // 2. Fill in other values.
875   [plist setObject:base::SysUTF8ToNSString(version_info::GetVersionNumber())
876             forKey:app_mode::kCrBundleVersionKey];
877   [plist setObject:base::SysUTF8ToNSString(info_->version_for_display)
878             forKey:app_mode::kCFBundleShortVersionStringKey];
879   [plist setObject:base::SysUTF8ToNSString(GetBundleIdentifier())
880             forKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
881   [plist setObject:base::mac::FilePathToNSString(app_data_dir_)
882             forKey:app_mode::kCrAppModeUserDataDirKey];
883   [plist setObject:base::mac::FilePathToNSString(info_->profile_path.BaseName())
884             forKey:app_mode::kCrAppModeProfileDirKey];
885   [plist setObject:base::SysUTF8ToNSString(info_->profile_name)
886             forKey:app_mode::kCrAppModeProfileNameKey];
887   [plist setObject:[NSNumber numberWithBool:YES]
888             forKey:app_mode::kLSHasLocalizedDisplayNameKey];
889   if (info_->extension_id == app_mode::kAppListModeId) {
890     // Prevent the app list from bouncing in the dock, and getting a run light.
891     [plist setObject:[NSNumber numberWithBool:YES]
892               forKey:kLSUIElement];
893   }
895   base::FilePath app_name = app_path.BaseName().RemoveExtension();
896   [plist setObject:base::mac::FilePathToNSString(app_name)
897             forKey:base::mac::CFToNSCast(kCFBundleNameKey)];
899   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
900           switches::kEnableAppsFileAssociations)) {
901     UpdateFileTypes(plist, file_handlers_info_);
902   }
904   return [plist writeToFile:plist_path
905                  atomically:YES];
908 bool WebAppShortcutCreator::UpdateDisplayName(
909     const base::FilePath& app_path) const {
910   // Localization is used to display the app name (rather than the bundle
911   // filename). OSX searches for the best language in the order of preferred
912   // languages, but one of them must be found otherwise it will default to
913   // the filename.
914   NSString* language = [[NSLocale preferredLanguages] objectAtIndex:0];
915   base::FilePath localized_dir = GetResourcesPath(app_path).Append(
916       base::SysNSStringToUTF8(language) + ".lproj");
917   if (!base::CreateDirectory(localized_dir))
918     return false;
920   NSString* bundle_name = base::SysUTF16ToNSString(info_->title);
921   NSString* display_name = base::SysUTF16ToNSString(info_->title);
922   if (HasExistingExtensionShim(GetApplicationsDirname(), info_->extension_id,
923                                app_path.BaseName())) {
924     display_name = [bundle_name
925         stringByAppendingString:base::SysUTF8ToNSString(
926             " (" + info_->profile_name + ")")];
927   }
929   NSDictionary* strings_plist = @{
930     base::mac::CFToNSCast(kCFBundleNameKey) : bundle_name,
931     app_mode::kCFBundleDisplayNameKey : display_name
932   };
934   NSString* localized_path = base::mac::FilePathToNSString(
935       localized_dir.Append("InfoPlist.strings"));
936   return [strings_plist writeToFile:localized_path
937                          atomically:YES];
940 bool WebAppShortcutCreator::UpdateIcon(const base::FilePath& app_path) const {
941   if (info_->favicon.empty())
942     return true;
944   ScopedCarbonHandle icon_family(0);
945   bool image_added = false;
946   for (gfx::ImageFamily::const_iterator it = info_->favicon.begin();
947        it != info_->favicon.end(); ++it) {
948     if (it->IsEmpty())
949       continue;
951     // Missing an icon size is not fatal so don't fail if adding the bitmap
952     // doesn't work.
953     if (!AddGfxImageToIconFamily(icon_family.GetAsIconFamilyHandle(), *it))
954       continue;
956     image_added = true;
957   }
959   if (!image_added)
960     return false;
962   base::FilePath resources_path = GetResourcesPath(app_path);
963   if (!base::CreateDirectory(resources_path))
964     return false;
966   return icon_family.WriteDataToFile(resources_path.Append("app.icns"));
969 bool WebAppShortcutCreator::UpdateInternalBundleIdentifier() const {
970   NSString* plist_path = GetPlistPath(GetInternalShortcutPath());
971   NSMutableDictionary* plist = ReadPlist(plist_path);
973   [plist setObject:base::SysUTF8ToNSString(GetInternalBundleIdentifier())
974             forKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
975   return [plist writeToFile:plist_path
976                  atomically:YES];
979 base::FilePath WebAppShortcutCreator::GetAppBundleById(
980     const std::string& bundle_id) const {
981   base::ScopedCFTypeRef<CFStringRef> bundle_id_cf(
982       base::SysUTF8ToCFStringRef(bundle_id));
983   CFURLRef url_ref = NULL;
984   OSStatus status = LSFindApplicationForInfo(
985       kLSUnknownCreator, bundle_id_cf.get(), NULL, NULL, &url_ref);
986   if (status != noErr)
987     return base::FilePath();
989   base::ScopedCFTypeRef<CFURLRef> url(url_ref);
990   NSString* path_string = [base::mac::CFToNSCast(url.get()) path];
991   return base::FilePath([path_string fileSystemRepresentation]);
994 std::string WebAppShortcutCreator::GetBundleIdentifier() const {
995   // Replace spaces in the profile path with hyphen.
996   std::string normalized_profile_path;
997   base::ReplaceChars(info_->profile_path.BaseName().value(), " ", "-",
998                      &normalized_profile_path);
1000   // This matches APP_MODE_APP_BUNDLE_ID in chrome/chrome.gyp.
1001   std::string bundle_id = base::mac::BaseBundleID() + std::string(".app.") +
1002                           normalized_profile_path + "-" + info_->extension_id;
1004   return bundle_id;
1007 std::string WebAppShortcutCreator::GetInternalBundleIdentifier() const {
1008   return GetBundleIdentifier() + "-internal";
1011 void WebAppShortcutCreator::RevealAppShimInFinder() const {
1012   base::FilePath app_path = GetApplicationsShortcutPath();
1013   if (app_path.empty())
1014     return;
1016   // Check if the app shim exists.
1017   if (base::PathExists(app_path)) {
1018     // Use selectFile to show the contents of parent directory with the app
1019     // shim selected.
1020     [[NSWorkspace sharedWorkspace]
1021                       selectFile:base::mac::FilePathToNSString(app_path)
1022         inFileViewerRootedAtPath:nil];
1023     return;
1024   }
1026   // Otherwise, go up a directory.
1027   app_path = app_path.DirName();
1028   // Check if the Chrome apps folder exists, otherwise go up to ~/Applications.
1029   if (!base::PathExists(app_path))
1030     app_path = app_path.DirName();
1031   // Since |app_path| is a directory, use openFile to show the contents of
1032   // that directory in Finder.
1033   [[NSWorkspace sharedWorkspace]
1034       openFile:base::mac::FilePathToNSString(app_path)];
1037 base::FilePath GetAppInstallPath(const ShortcutInfo& shortcut_info) {
1038   WebAppShortcutCreator shortcut_creator(base::FilePath(), &shortcut_info,
1039                                          extensions::FileHandlersInfo());
1040   return shortcut_creator.GetApplicationsShortcutPath();
1043 void MaybeLaunchShortcut(scoped_ptr<ShortcutInfo> shortcut_info) {
1044   if (AppShimsDisabledForTest() &&
1045       !g_app_shims_allow_update_and_launch_in_tests) {
1046     return;
1047   }
1049   content::BrowserThread::PostTask(
1050       content::BrowserThread::FILE, FROM_HERE,
1051       base::Bind(&LaunchShimOnFileThread, base::Passed(&shortcut_info), false));
1054 bool MaybeRebuildShortcut(const base::CommandLine& command_line) {
1055   if (!command_line.HasSwitch(app_mode::kAppShimError))
1056     return false;
1058   base::PostTaskAndReplyWithResult(
1059       content::BrowserThread::GetBlockingPool(),
1060       FROM_HERE,
1061       base::Bind(&RecordAppShimErrorAndBuildShortcutInfo,
1062                  command_line.GetSwitchValuePath(app_mode::kAppShimError)),
1063       base::Bind(&RebuildAppAndLaunch));
1064   return true;
1067 // Called when the app's ShortcutInfo (with icon) is loaded when creating app
1068 // shortcuts.
1069 void CreateAppShortcutInfoLoaded(
1070     Profile* profile,
1071     const extensions::Extension* app,
1072     const base::Callback<void(bool)>& close_callback,
1073     scoped_ptr<ShortcutInfo> shortcut_info) {
1074   base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]);
1076   NSButton* continue_button = [alert
1077       addButtonWithTitle:l10n_util::GetNSString(IDS_CREATE_SHORTCUTS_COMMIT)];
1078   [continue_button setKeyEquivalent:kKeyEquivalentReturn];
1080   NSButton* cancel_button =
1081       [alert addButtonWithTitle:l10n_util::GetNSString(IDS_CANCEL)];
1082   [cancel_button setKeyEquivalent:kKeyEquivalentEscape];
1084   [alert setMessageText:l10n_util::GetNSString(IDS_CREATE_SHORTCUTS_LABEL)];
1085   [alert setAlertStyle:NSInformationalAlertStyle];
1087   base::scoped_nsobject<NSButton> application_folder_checkbox(
1088       [[NSButton alloc] initWithFrame:NSZeroRect]);
1089   [application_folder_checkbox setButtonType:NSSwitchButton];
1090   [application_folder_checkbox
1091       setTitle:l10n_util::GetNSString(IDS_CREATE_SHORTCUTS_APP_FOLDER_CHKBOX)];
1092   [application_folder_checkbox setState:NSOnState];
1093   [application_folder_checkbox sizeToFit];
1095   base::scoped_nsobject<CrCreateAppShortcutCheckboxObserver> checkbox_observer(
1096       [[CrCreateAppShortcutCheckboxObserver alloc]
1097           initWithCheckbox:application_folder_checkbox
1098             continueButton:continue_button]);
1099   [checkbox_observer startObserving];
1101   [alert setAccessoryView:application_folder_checkbox];
1103   const int kIconPreviewSizePixels = 128;
1104   const int kIconPreviewTargetSize = 64;
1105   const gfx::Image* icon = shortcut_info->favicon.GetBest(
1106       kIconPreviewSizePixels, kIconPreviewSizePixels);
1108   if (icon && !icon->IsEmpty()) {
1109     NSImage* icon_image = icon->ToNSImage();
1110     [icon_image
1111         setSize:NSMakeSize(kIconPreviewTargetSize, kIconPreviewTargetSize)];
1112     [alert setIcon:icon_image];
1113   }
1115   bool dialog_accepted = false;
1116   if ([alert runModal] == NSAlertFirstButtonReturn &&
1117       [application_folder_checkbox state] == NSOnState) {
1118     dialog_accepted = true;
1119     CreateShortcuts(
1120         SHORTCUT_CREATION_BY_USER, ShortcutLocations(), profile, app);
1121   }
1123   [checkbox_observer stopObserving];
1125   if (!close_callback.is_null())
1126     close_callback.Run(dialog_accepted);
1129 void UpdateShortcutsForAllApps(Profile* profile,
1130                                const base::Closure& callback) {
1131   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
1133   extensions::ExtensionRegistry* registry =
1134       extensions::ExtensionRegistry::Get(profile);
1135   if (!registry)
1136     return;
1138   // Update all apps.
1139   scoped_ptr<extensions::ExtensionSet> everything =
1140       registry->GenerateInstalledExtensionsSet();
1141   for (extensions::ExtensionSet::const_iterator it = everything->begin();
1142        it != everything->end(); ++it) {
1143     if (web_app::ShouldCreateShortcutFor(SHORTCUT_CREATION_AUTOMATED, profile,
1144                                          it->get())) {
1145       web_app::UpdateAllShortcuts(base::string16(), profile, it->get());
1146     }
1147   }
1149   callback.Run();
1152 void RevealAppShimInFinderForApp(Profile* profile,
1153                                  const extensions::Extension* app) {
1154   scoped_ptr<web_app::ShortcutInfo> shortcut_info =
1155       ShortcutInfoForExtensionAndProfile(app, profile);
1156   content::BrowserThread::PostTask(
1157       content::BrowserThread::FILE, FROM_HERE,
1158       base::Bind(&RevealAppShimInFinderForAppOnFileThread,
1159                  base::Passed(&shortcut_info), app->path()));
1162 namespace internals {
1164 bool CreatePlatformShortcuts(
1165     const base::FilePath& app_data_path,
1166     scoped_ptr<ShortcutInfo> shortcut_info,
1167     const extensions::FileHandlersInfo& file_handlers_info,
1168     const ShortcutLocations& creation_locations,
1169     ShortcutCreationReason creation_reason) {
1170   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
1171   if (AppShimsDisabledForTest())
1172     return true;
1174   WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get(),
1175                                          file_handlers_info);
1176   return shortcut_creator.CreateShortcuts(creation_reason, creation_locations);
1179 void DeletePlatformShortcuts(const base::FilePath& app_data_path,
1180                              scoped_ptr<ShortcutInfo> shortcut_info) {
1181   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
1182   WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get(),
1183                                          extensions::FileHandlersInfo());
1184   shortcut_creator.DeleteShortcuts();
1187 void UpdatePlatformShortcuts(
1188     const base::FilePath& app_data_path,
1189     const base::string16& old_app_title,
1190     scoped_ptr<ShortcutInfo> shortcut_info,
1191     const extensions::FileHandlersInfo& file_handlers_info) {
1192   UpdatePlatformShortcutsInternal(app_data_path, old_app_title, *shortcut_info,
1193                                   file_handlers_info);
1196 void DeleteAllShortcutsForProfile(const base::FilePath& profile_path) {
1197   const std::string profile_base_name = profile_path.BaseName().value();
1198   std::vector<base::FilePath> bundles = GetAllAppBundlesInPath(
1199       profile_path.Append(chrome::kWebAppDirname), profile_base_name);
1201   for (std::vector<base::FilePath>::const_iterator it = bundles.begin();
1202        it != bundles.end(); ++it) {
1203     scoped_ptr<web_app::ShortcutInfo> shortcut_info =
1204         BuildShortcutInfoFromBundle(*it);
1205     WebAppShortcutCreator shortcut_creator(it->DirName(), shortcut_info.get(),
1206                                            extensions::FileHandlersInfo());
1207     shortcut_creator.DeleteShortcuts();
1208   }
1211 }  // namespace internals
1213 }  // namespace web_app
1215 namespace chrome {
1217 void ShowCreateChromeAppShortcutsDialog(
1218     gfx::NativeWindow /*parent_window*/,
1219     Profile* profile,
1220     const extensions::Extension* app,
1221     const base::Callback<void(bool)>& close_callback) {
1222   web_app::GetShortcutInfoForApp(
1223       app,
1224       profile,
1225       base::Bind(&web_app::CreateAppShortcutInfoLoaded,
1226                  profile,
1227                  app,
1228                  close_callback));
1231 }  // namespace chrome