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