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 #include "chrome/browser/web_applications/web_app_win.h"
      6 
      7 #include <shlobj.h>
      8 
      9 #include "base/command_line.h"
     10 #include "base/file_util.h"
     11 #include "base/files/file_enumerator.h"
     12 #include "base/logging.h"
     13 #include "base/md5.h"
     14 #include "base/path_service.h"
     15 #include "base/strings/string_piece.h"
     16 #include "base/strings/stringprintf.h"
     17 #include "base/strings/utf_string_conversions.h"
     18 #include "base/win/shortcut.h"
     19 #include "base/win/windows_version.h"
     20 #include "chrome/browser/web_applications/web_app.h"
     21 #include "chrome/common/chrome_switches.h"
     22 #include "chrome/installer/util/browser_distribution.h"
     23 #include "chrome/installer/util/shell_util.h"
     24 #include "chrome/installer/util/util_constants.h"
     25 #include "content/public/browser/browser_thread.h"
     26 #include "ui/gfx/icon_util.h"
     27 #include "ui/gfx/image/image.h"
     28 #include "ui/gfx/image/image_family.h"
     29 
     30 namespace {
     31 
     32 const base::FilePath::CharType kIconChecksumFileExt[] =
     33     FILE_PATH_LITERAL(".ico.md5");
     34 
     35 // Width and height of icons exported to .ico files.
     36 
     37 // Calculates checksum of an icon family using MD5.
     38 // The checksum is derived from all of the icons in the family.
     39 void GetImageCheckSum(const gfx::ImageFamily& image, base::MD5Digest* digest) {
     40   DCHECK(digest);
     41   base::MD5Context md5_context;
     42   base::MD5Init(&md5_context);
     43 
     44   for (gfx::ImageFamily::const_iterator it = image.begin(); it != image.end();
     45        ++it) {
     46     SkBitmap bitmap = it->AsBitmap();
     47 
     48     SkAutoLockPixels image_lock(bitmap);
     49     base::StringPiece image_data(
     50         reinterpret_cast<const char*>(bitmap.getPixels()), bitmap.getSize());
     51     base::MD5Update(&md5_context, image_data);
     52   }
     53 
     54   base::MD5Final(digest, &md5_context);
     55 }
     56 
     57 // Saves |image| as an |icon_file| with the checksum.
     58 bool SaveIconWithCheckSum(const base::FilePath& icon_file,
     59                           const gfx::ImageFamily& image) {
     60   if (!IconUtil::CreateIconFileFromImageFamily(image, icon_file))
     61     return false;
     62 
     63   base::MD5Digest digest;
     64   GetImageCheckSum(image, &digest);
     65 
     66   base::FilePath cheksum_file(icon_file.ReplaceExtension(kIconChecksumFileExt));
     67   return file_util::WriteFile(cheksum_file,
     68                               reinterpret_cast<const char*>(&digest),
     69                               sizeof(digest)) == sizeof(digest);
     70 }
     71 
     72 // Returns true if |icon_file| is missing or different from |image|.
     73 bool ShouldUpdateIcon(const base::FilePath& icon_file,
     74                       const gfx::ImageFamily& image) {
     75   base::FilePath checksum_file(
     76       icon_file.ReplaceExtension(kIconChecksumFileExt));
     77 
     78   // Returns true if icon_file or checksum file is missing.
     79   if (!base::PathExists(icon_file) ||
     80       !base::PathExists(checksum_file))
     81     return true;
     82 
     83   base::MD5Digest persisted_image_checksum;
     84   if (sizeof(persisted_image_checksum) != base::ReadFile(checksum_file,
     85                       reinterpret_cast<char*>(&persisted_image_checksum),
     86                       sizeof(persisted_image_checksum)))
     87     return true;
     88 
     89   base::MD5Digest downloaded_image_checksum;
     90   GetImageCheckSum(image, &downloaded_image_checksum);
     91 
     92   // Update icon if checksums are not equal.
     93   return memcmp(&persisted_image_checksum, &downloaded_image_checksum,
     94                 sizeof(base::MD5Digest)) != 0;
     95 }
     96 
     97 // Returns true if |shortcut_file_name| matches profile |profile_path|, and has
     98 // an --app-id flag.
     99 bool IsAppShortcutForProfile(const base::FilePath& shortcut_file_name,
    100                              const base::FilePath& profile_path) {
    101   base::string16 cmd_line_string;
    102   if (base::win::ResolveShortcut(shortcut_file_name, NULL, &cmd_line_string)) {
    103     cmd_line_string = L"program " + cmd_line_string;
    104     CommandLine shortcut_cmd_line = CommandLine::FromString(cmd_line_string);
    105     return shortcut_cmd_line.HasSwitch(switches::kProfileDirectory) &&
    106            shortcut_cmd_line.GetSwitchValuePath(switches::kProfileDirectory) ==
    107                profile_path.BaseName() &&
    108            shortcut_cmd_line.HasSwitch(switches::kAppId);
    109   }
    110 
    111   return false;
    112 }
    113 
    114 // Finds shortcuts in |shortcut_path| that match profile for |profile_path| and
    115 // extension with title |shortcut_name|.
    116 // If |shortcut_name| is empty, finds all shortcuts matching |profile_path|.
    117 std::vector<base::FilePath> FindAppShortcutsByProfileAndTitle(
    118     const base::FilePath& shortcut_path,
    119     const base::FilePath& profile_path,
    120     const base::string16& shortcut_name) {
    121   std::vector<base::FilePath> shortcut_paths;
    122 
    123   if (shortcut_name.empty()) {
    124     // Find all shortcuts for this profile.
    125     base::FileEnumerator files(shortcut_path, false,
    126                                base::FileEnumerator::FILES,
    127                                FILE_PATH_LITERAL("*.lnk"));
    128     base::FilePath shortcut_file = files.Next();
    129     while (!shortcut_file.empty()) {
    130       if (IsAppShortcutForProfile(shortcut_file, profile_path))
    131         shortcut_paths.push_back(shortcut_file);
    132       shortcut_file = files.Next();
    133     }
    134   } else {
    135     // Find all shortcuts matching |shortcut_name|.
    136     base::FilePath base_path = shortcut_path.
    137         Append(web_app::internals::GetSanitizedFileName(shortcut_name)).
    138         AddExtension(FILE_PATH_LITERAL(".lnk"));
    139 
    140     const int fileNamesToCheck = 10;
    141     for (int i = 0; i < fileNamesToCheck; ++i) {
    142       base::FilePath shortcut_file = base_path;
    143       if (i > 0) {
    144         shortcut_file = shortcut_file.InsertBeforeExtensionASCII(
    145             base::StringPrintf(" (%d)", i));
    146       }
    147       if (base::PathExists(shortcut_file) &&
    148           IsAppShortcutForProfile(shortcut_file, profile_path)) {
    149         shortcut_paths.push_back(shortcut_file);
    150       }
    151     }
    152   }
    153 
    154   return shortcut_paths;
    155 }
    156 
    157 // Creates application shortcuts in a given set of paths.
    158 // |shortcut_paths| is a list of directories in which shortcuts should be
    159 // created. If |creation_reason| is SHORTCUT_CREATION_AUTOMATED and there is an
    160 // existing shortcut to this app for this profile, does nothing (succeeding).
    161 // Returns true on success, false on failure.
    162 // Must be called on the FILE thread.
    163 bool CreateShortcutsInPaths(
    164     const base::FilePath& web_app_path,
    165     const ShellIntegration::ShortcutInfo& shortcut_info,
    166     const std::vector<base::FilePath>& shortcut_paths,
    167     web_app::ShortcutCreationReason creation_reason,
    168     std::vector<base::FilePath>* out_filenames) {
    169   // Ensure web_app_path exists.
    170   if (!base::PathExists(web_app_path) &&
    171       !base::CreateDirectory(web_app_path)) {
    172     return false;
    173   }
    174 
    175   // Generates file name to use with persisted ico and shortcut file.
    176   base::FilePath file_name =
    177       web_app::internals::GetSanitizedFileName(shortcut_info.title);
    178 
    179   // Creates an ico file to use with shortcut.
    180   base::FilePath icon_file = web_app_path.Append(file_name).AddExtension(
    181       FILE_PATH_LITERAL(".ico"));
    182   if (!web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info.favicon)) {
    183     return false;
    184   }
    185 
    186   base::FilePath chrome_exe;
    187   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    188     NOTREACHED();
    189     return false;
    190   }
    191 
    192   // Working directory.
    193   base::FilePath working_dir(chrome_exe.DirName());
    194 
    195   CommandLine cmd_line(CommandLine::NO_PROGRAM);
    196   cmd_line = ShellIntegration::CommandLineArgsForLauncher(shortcut_info.url,
    197       shortcut_info.extension_id, shortcut_info.profile_path);
    198 
    199   // TODO(evan): we rely on the fact that command_line_string() is
    200   // properly quoted for a Windows command line.  The method on
    201   // CommandLine should probably be renamed to better reflect that
    202   // fact.
    203   base::string16 wide_switches(cmd_line.GetCommandLineString());
    204 
    205   // Sanitize description
    206   base::string16 description = shortcut_info.description;
    207   if (description.length() >= MAX_PATH)
    208     description.resize(MAX_PATH - 1);
    209 
    210   // Generates app id from web app url and profile path.
    211   std::string app_name(web_app::GenerateApplicationNameFromInfo(shortcut_info));
    212   base::string16 app_id(ShellIntegration::GetAppModelIdForProfile(
    213       UTF8ToUTF16(app_name), shortcut_info.profile_path));
    214 
    215   bool success = true;
    216   for (size_t i = 0; i < shortcut_paths.size(); ++i) {
    217     base::FilePath shortcut_file = shortcut_paths[i].Append(file_name).
    218         AddExtension(installer::kLnkExt);
    219     if (creation_reason == web_app::SHORTCUT_CREATION_AUTOMATED) {
    220       // Check whether there is an existing shortcut to this app.
    221       std::vector<base::FilePath> shortcut_files =
    222           FindAppShortcutsByProfileAndTitle(shortcut_paths[i],
    223                                             shortcut_info.profile_path,
    224                                             shortcut_info.title);
    225       if (!shortcut_files.empty())
    226         continue;
    227     }
    228     if (shortcut_paths[i] != web_app_path) {
    229       int unique_number =
    230           file_util::GetUniquePathNumber(shortcut_file, FILE_PATH_LITERAL(""));
    231       if (unique_number == -1) {
    232         success = false;
    233         continue;
    234       } else if (unique_number > 0) {
    235         shortcut_file = shortcut_file.InsertBeforeExtensionASCII(
    236             base::StringPrintf(" (%d)", unique_number));
    237       }
    238     }
    239     base::win::ShortcutProperties shortcut_properties;
    240     shortcut_properties.set_target(chrome_exe);
    241     shortcut_properties.set_working_dir(working_dir);
    242     shortcut_properties.set_arguments(wide_switches);
    243     shortcut_properties.set_description(description);
    244     shortcut_properties.set_icon(icon_file, 0);
    245     shortcut_properties.set_app_id(app_id);
    246     shortcut_properties.set_dual_mode(false);
    247     if (!base::PathExists(shortcut_file.DirName()) &&
    248         !base::CreateDirectory(shortcut_file.DirName())) {
    249       NOTREACHED();
    250       return false;
    251     }
    252     success = base::win::CreateOrUpdateShortcutLink(
    253         shortcut_file, shortcut_properties,
    254         base::win::SHORTCUT_CREATE_ALWAYS) && success;
    255     if (out_filenames)
    256       out_filenames->push_back(shortcut_file);
    257   }
    258 
    259   return success;
    260 }
    261 
    262 // Gets the directories with shortcuts for an app, and deletes the shortcuts.
    263 // This will search the standard locations for shortcuts named |title| that open
    264 // in the profile with |profile_path|.
    265 // |was_pinned_to_taskbar| will be set to true if there was previously a
    266 // shortcut pinned to the taskbar for this app; false otherwise.
    267 // If |web_app_path| is empty, this will not delete shortcuts from the web app
    268 // directory. If |title| is empty, all shortcuts for this profile will be
    269 // deleted.
    270 // |shortcut_paths| will be populated with a list of directories where shortcuts
    271 // for this app were found (and deleted). This will delete duplicate shortcuts,
    272 // but only return each path once, even if it contained multiple deleted
    273 // shortcuts. Both of these may be NULL.
    274 void GetShortcutLocationsAndDeleteShortcuts(
    275     const base::FilePath& web_app_path,
    276     const base::FilePath& profile_path,
    277     const base::string16& title,
    278     bool* was_pinned_to_taskbar,
    279     std::vector<base::FilePath>* shortcut_paths) {
    280   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
    281 
    282   // Get all possible locations for shortcuts.
    283   ShellIntegration::ShortcutLocations all_shortcut_locations;
    284   all_shortcut_locations.in_quick_launch_bar = true;
    285   all_shortcut_locations.on_desktop = true;
    286   // Delete shortcuts from the Chrome Apps subdirectory.
    287   // This matches the subdir name set by CreateApplicationShortcutView::Accept
    288   // for Chrome apps (not URL apps, but this function does not apply for them).
    289   all_shortcut_locations.applications_menu_location =
    290       ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
    291   std::vector<base::FilePath> all_paths = web_app::internals::GetShortcutPaths(
    292       all_shortcut_locations);
    293   if (base::win::GetVersion() >= base::win::VERSION_WIN7 &&
    294       !web_app_path.empty()) {
    295     all_paths.push_back(web_app_path);
    296   }
    297 
    298   if (was_pinned_to_taskbar) {
    299     // Determine if there is a link to this app in the TaskBar pin directory.
    300     base::FilePath taskbar_pin_path;
    301     if (PathService::Get(base::DIR_TASKBAR_PINS, &taskbar_pin_path)) {
    302       std::vector<base::FilePath> taskbar_pin_files =
    303           FindAppShortcutsByProfileAndTitle(taskbar_pin_path, profile_path,
    304                                             title);
    305       *was_pinned_to_taskbar = !taskbar_pin_files.empty();
    306     } else {
    307       *was_pinned_to_taskbar = false;
    308     }
    309   }
    310 
    311   for (std::vector<base::FilePath>::const_iterator i = all_paths.begin();
    312        i != all_paths.end(); ++i) {
    313     std::vector<base::FilePath> shortcut_files =
    314         FindAppShortcutsByProfileAndTitle(*i, profile_path, title);
    315     if (shortcut_paths && !shortcut_files.empty()) {
    316       shortcut_paths->push_back(*i);
    317     }
    318     for (std::vector<base::FilePath>::const_iterator j = shortcut_files.begin();
    319          j != shortcut_files.end(); ++j) {
    320       // Any shortcut could have been pinned, either by chrome or the user, so
    321       // they are all unpinned.
    322       base::win::TaskbarUnpinShortcutLink(j->value().c_str());
    323       base::DeleteFile(*j, false);
    324     }
    325   }
    326 }
    327 
    328 }  // namespace
    329 
    330 namespace web_app {
    331 
    332 base::FilePath CreateShortcutInWebAppDir(
    333     const base::FilePath& web_app_dir,
    334     const ShellIntegration::ShortcutInfo& shortcut_info) {
    335   std::vector<base::FilePath> paths;
    336   paths.push_back(web_app_dir);
    337   std::vector<base::FilePath> out_filenames;
    338   CreateShortcutsInPaths(web_app_dir, shortcut_info, paths,
    339                          SHORTCUT_CREATION_BY_USER, &out_filenames);
    340   DCHECK_EQ(out_filenames.size(), 1u);
    341   return out_filenames[0];
    342 }
    343 
    344 namespace internals {
    345 
    346 // Saves |image| to |icon_file| if the file is outdated and refresh shell's
    347 // icon cache to ensure correct icon is displayed. Returns true if icon_file
    348 // is up to date or successfully updated.
    349 bool CheckAndSaveIcon(const base::FilePath& icon_file,
    350                       const gfx::ImageFamily& image) {
    351   if (ShouldUpdateIcon(icon_file, image)) {
    352     if (SaveIconWithCheckSum(icon_file, image)) {
    353       // Refresh shell's icon cache. This call is quite disruptive as user would
    354       // see explorer rebuilding the icon cache. It would be great that we find
    355       // a better way to achieve this.
    356       SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT,
    357                      NULL, NULL);
    358     } else {
    359       return false;
    360     }
    361   }
    362 
    363   return true;
    364 }
    365 
    366 bool CreatePlatformShortcuts(
    367     const base::FilePath& web_app_path,
    368     const ShellIntegration::ShortcutInfo& shortcut_info,
    369     const ShellIntegration::ShortcutLocations& creation_locations,
    370     ShortcutCreationReason creation_reason) {
    371   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
    372 
    373   // Shortcut paths under which to create shortcuts.
    374   std::vector<base::FilePath> shortcut_paths =
    375       GetShortcutPaths(creation_locations);
    376 
    377   bool pin_to_taskbar = creation_locations.in_quick_launch_bar &&
    378                         (base::win::GetVersion() >= base::win::VERSION_WIN7);
    379 
    380   // Create/update the shortcut in the web app path for the "Pin To Taskbar"
    381   // option in Win7. We use the web app path shortcut because we will overwrite
    382   // it rather than appending unique numbers if the shortcut already exists.
    383   // This prevents pinned apps from having unique numbers in their names.
    384   if (pin_to_taskbar)
    385     shortcut_paths.push_back(web_app_path);
    386 
    387   if (shortcut_paths.empty())
    388     return false;
    389 
    390   if (!CreateShortcutsInPaths(web_app_path, shortcut_info, shortcut_paths,
    391                               creation_reason, NULL))
    392     return false;
    393 
    394   if (pin_to_taskbar) {
    395     base::FilePath file_name =
    396         web_app::internals::GetSanitizedFileName(shortcut_info.title);
    397     // Use the web app path shortcut for pinning to avoid having unique numbers
    398     // in the application name.
    399     base::FilePath shortcut_to_pin = web_app_path.Append(file_name).
    400         AddExtension(installer::kLnkExt);
    401     if (!base::win::TaskbarPinShortcutLink(shortcut_to_pin.value().c_str()))
    402       return false;
    403   }
    404 
    405   return true;
    406 }
    407 
    408 void UpdatePlatformShortcuts(
    409     const base::FilePath& web_app_path,
    410     const base::string16& old_app_title,
    411     const ShellIntegration::ShortcutInfo& shortcut_info) {
    412   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
    413 
    414   // Generates file name to use with persisted ico and shortcut file.
    415   base::FilePath file_name =
    416       web_app::internals::GetSanitizedFileName(shortcut_info.title);
    417 
    418   if (old_app_title != shortcut_info.title) {
    419     // The app's title has changed. Delete all existing app shortcuts and
    420     // recreate them in any locations they already existed (but do not add them
    421     // to locations where they do not currently exist).
    422     bool was_pinned_to_taskbar;
    423     std::vector<base::FilePath> shortcut_paths;
    424     GetShortcutLocationsAndDeleteShortcuts(
    425         web_app_path, shortcut_info.profile_path, old_app_title,
    426         &was_pinned_to_taskbar, &shortcut_paths);
    427     CreateShortcutsInPaths(web_app_path, shortcut_info, shortcut_paths,
    428                            SHORTCUT_CREATION_BY_USER, NULL);
    429     // If the shortcut was pinned to the taskbar,
    430     // GetShortcutLocationsAndDeleteShortcuts will have deleted it. In that
    431     // case, re-pin it.
    432     if (was_pinned_to_taskbar) {
    433       base::FilePath file_name =
    434           web_app::internals::GetSanitizedFileName(shortcut_info.title);
    435       // Use the web app path shortcut for pinning to avoid having unique
    436       // numbers in the application name.
    437       base::FilePath shortcut_to_pin = web_app_path.Append(file_name).
    438           AddExtension(installer::kLnkExt);
    439       base::win::TaskbarPinShortcutLink(shortcut_to_pin.value().c_str());
    440     }
    441   }
    442 
    443   // If an icon file exists, and is out of date, replace it with the new icon
    444   // and let the shell know the icon has been modified.
    445   base::FilePath icon_file = web_app_path.Append(file_name).AddExtension(
    446       FILE_PATH_LITERAL(".ico"));
    447   if (base::PathExists(icon_file)) {
    448     web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info.favicon);
    449   }
    450 }
    451 
    452 void DeletePlatformShortcuts(
    453     const base::FilePath& web_app_path,
    454     const ShellIntegration::ShortcutInfo& shortcut_info) {
    455   GetShortcutLocationsAndDeleteShortcuts(
    456       web_app_path, shortcut_info.profile_path, shortcut_info.title, NULL,
    457       NULL);
    458 
    459   // If there are no more shortcuts in the Chrome Apps subdirectory, remove it.
    460   base::FilePath chrome_apps_dir;
    461   if (ShellUtil::GetShortcutPath(
    462           ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR,
    463           BrowserDistribution::GetDistribution(),
    464           ShellUtil::CURRENT_USER,
    465           &chrome_apps_dir)) {
    466     if (base::IsDirectoryEmpty(chrome_apps_dir))
    467       base::DeleteFile(chrome_apps_dir, false);
    468   }
    469 }
    470 
    471 void DeleteAllShortcutsForProfile(const base::FilePath& profile_path) {
    472   GetShortcutLocationsAndDeleteShortcuts(base::FilePath(), profile_path, L"",
    473                                          NULL, NULL);
    474 
    475   // If there are no more shortcuts in the Chrome Apps subdirectory, remove it.
    476   base::FilePath chrome_apps_dir;
    477   if (ShellUtil::GetShortcutPath(
    478           ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR,
    479           BrowserDistribution::GetDistribution(),
    480           ShellUtil::CURRENT_USER,
    481           &chrome_apps_dir)) {
    482     if (base::IsDirectoryEmpty(chrome_apps_dir))
    483       base::DeleteFile(chrome_apps_dir, false);
    484   }
    485 }
    486 
    487 std::vector<base::FilePath> GetShortcutPaths(
    488     const ShellIntegration::ShortcutLocations& creation_locations) {
    489   // Shortcut paths under which to create shortcuts.
    490   std::vector<base::FilePath> shortcut_paths;
    491   // Locations to add to shortcut_paths.
    492   struct {
    493     bool use_this_location;
    494     ShellUtil::ShortcutLocation location_id;
    495   } locations[] = {
    496     {
    497       creation_locations.on_desktop,
    498       ShellUtil::SHORTCUT_LOCATION_DESKTOP
    499     }, {
    500       creation_locations.applications_menu_location ==
    501           ShellIntegration::APP_MENU_LOCATION_ROOT,
    502       ShellUtil::SHORTCUT_LOCATION_START_MENU_ROOT
    503     }, {
    504       creation_locations.applications_menu_location ==
    505           ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROME,
    506       ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_DIR
    507     }, {
    508       creation_locations.applications_menu_location ==
    509           ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS,
    510       ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR
    511     }, {
    512       // For Win7+, |in_quick_launch_bar| indicates that we are pinning to
    513       // taskbar. This needs to be handled by callers.
    514       creation_locations.in_quick_launch_bar &&
    515           base::win::GetVersion() < base::win::VERSION_WIN7,
    516       ShellUtil::SHORTCUT_LOCATION_QUICK_LAUNCH
    517     }
    518   };
    519 
    520   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    521   // Populate shortcut_paths.
    522   for (int i = 0; i < arraysize(locations); ++i) {
    523     if (locations[i].use_this_location) {
    524       base::FilePath path;
    525       if (!ShellUtil::GetShortcutPath(locations[i].location_id,
    526                                       dist,
    527                                       ShellUtil::CURRENT_USER,
    528                                       &path)) {
    529         NOTREACHED();
    530         continue;
    531       }
    532       shortcut_paths.push_back(path);
    533     }
    534   }
    535   return shortcut_paths;
    536 }
    537 
    538 }  // namespace internals
    539 
    540 }  // namespace web_app
    541