web_app_win.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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.h"
6
7#include <shlobj.h>
8
9#include "base/command_line.h"
10#include "base/file_util.h"
11#include "base/logging.h"
12#include "base/md5.h"
13#include "base/path_service.h"
14#include "base/stringprintf.h"
15#include "base/utf_string_conversions.h"
16#include "base/win/shortcut.h"
17#include "base/win/windows_version.h"
18#include "chrome/common/chrome_switches.h"
19#include "chrome/installer/launcher_support/chrome_launcher_support.h"
20#include "chrome/installer/util/browser_distribution.h"
21#include "content/public/browser/browser_thread.h"
22#include "ui/gfx/icon_util.h"
23
24namespace {
25
26const base::FilePath::CharType kIconChecksumFileExt[] =
27    FILE_PATH_LITERAL(".ico.md5");
28
29// Calculates image checksum using MD5.
30void GetImageCheckSum(const SkBitmap& image, base::MD5Digest* digest) {
31  DCHECK(digest);
32
33  SkAutoLockPixels image_lock(image);
34  MD5Sum(image.getPixels(), image.getSize(), digest);
35}
36
37// Saves |image| as an |icon_file| with the checksum.
38bool SaveIconWithCheckSum(const base::FilePath& icon_file,
39                          const SkBitmap& image) {
40  if (!IconUtil::CreateIconFileFromSkBitmap(image, SkBitmap(), icon_file))
41    return false;
42
43  base::MD5Digest digest;
44  GetImageCheckSum(image, &digest);
45
46  base::FilePath cheksum_file(icon_file.ReplaceExtension(kIconChecksumFileExt));
47  return file_util::WriteFile(cheksum_file,
48                              reinterpret_cast<const char*>(&digest),
49                              sizeof(digest)) == sizeof(digest);
50}
51
52// Returns true if |icon_file| is missing or different from |image|.
53bool ShouldUpdateIcon(const base::FilePath& icon_file, const SkBitmap& image) {
54  base::FilePath checksum_file(
55      icon_file.ReplaceExtension(kIconChecksumFileExt));
56
57  // Returns true if icon_file or checksum file is missing.
58  if (!file_util::PathExists(icon_file) ||
59      !file_util::PathExists(checksum_file))
60    return true;
61
62  base::MD5Digest persisted_image_checksum;
63  if (sizeof(persisted_image_checksum) != file_util::ReadFile(checksum_file,
64                      reinterpret_cast<char*>(&persisted_image_checksum),
65                      sizeof(persisted_image_checksum)))
66    return true;
67
68  base::MD5Digest downloaded_image_checksum;
69  GetImageCheckSum(image, &downloaded_image_checksum);
70
71  // Update icon if checksums are not equal.
72  return memcmp(&persisted_image_checksum, &downloaded_image_checksum,
73                sizeof(base::MD5Digest)) != 0;
74}
75
76std::vector<base::FilePath> GetShortcutPaths(
77    const ShellIntegration::ShortcutLocations& creation_locations) {
78  // Shortcut paths under which to create shortcuts.
79  std::vector<base::FilePath> shortcut_paths;
80
81  // Locations to add to shortcut_paths.
82  struct {
83    bool use_this_location;
84    int location_id;
85    const wchar_t* sub_dir;
86  } locations[] = {
87    {
88      creation_locations.on_desktop,
89      base::DIR_USER_DESKTOP,
90      NULL
91    }, {
92      creation_locations.in_applications_menu,
93      base::DIR_START_MENU,
94      NULL
95    }, {
96      creation_locations.in_quick_launch_bar,
97      // For Win7, create_in_quick_launch_bar means pinning to taskbar. Use
98      // base::PATH_START as a flag for this case.
99      (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
100          base::PATH_START : base::DIR_APP_DATA,
101      (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
102          NULL : L"Microsoft\\Internet Explorer\\Quick Launch"
103    }
104  };
105
106  // Populate shortcut_paths.
107  for (int i = 0; i < arraysize(locations); ++i) {
108    if (locations[i].use_this_location) {
109      base::FilePath path;
110
111      // Skip the Win7 case.
112      if (locations[i].location_id == base::PATH_START)
113        continue;
114
115      if (!PathService::Get(locations[i].location_id, &path)) {
116        continue;
117      }
118
119      if (locations[i].sub_dir != NULL)
120        path = path.Append(locations[i].sub_dir);
121
122      shortcut_paths.push_back(path);
123    }
124  }
125
126  return shortcut_paths;
127}
128
129bool ShortcutIsForProfile(const base::FilePath& shortcut_file_name,
130                          const base::FilePath& profile_path) {
131  string16 cmd_line_string;
132  if (base::win::ResolveShortcut(shortcut_file_name, NULL, &cmd_line_string)) {
133    cmd_line_string = L"program " + cmd_line_string;
134    CommandLine shortcut_cmd_line = CommandLine::FromString(cmd_line_string);
135    return shortcut_cmd_line.HasSwitch(switches::kProfileDirectory) &&
136           shortcut_cmd_line.GetSwitchValuePath(switches::kProfileDirectory) ==
137               profile_path.BaseName();
138  }
139
140  return false;
141}
142
143std::vector<base::FilePath> MatchingShortcutsForProfileAndExtension(
144    const base::FilePath& shortcut_path,
145    const base::FilePath& profile_path,
146    const string16& shortcut_name) {
147  std::vector<base::FilePath> shortcut_paths;
148  base::FilePath base_path = shortcut_path.
149      Append(web_app::internals::GetSanitizedFileName(shortcut_name)).
150      ReplaceExtension(FILE_PATH_LITERAL(".lnk"));
151
152  const int fileNamesToCheck = 10;
153  for (int i = 0; i < fileNamesToCheck; ++i) {
154    base::FilePath shortcut_file = base_path;
155    if (i) {
156      shortcut_file = shortcut_file.InsertBeforeExtensionASCII(
157          base::StringPrintf(" (%d)", i));
158    }
159    if (file_util::PathExists(shortcut_file) &&
160        ShortcutIsForProfile(shortcut_file, profile_path)) {
161      shortcut_paths.push_back(shortcut_file);
162    }
163  }
164  return shortcut_paths;
165}
166
167}  // namespace
168
169namespace web_app {
170
171namespace internals {
172
173// Saves |image| to |icon_file| if the file is outdated and refresh shell's
174// icon cache to ensure correct icon is displayed. Returns true if icon_file
175// is up to date or successfully updated.
176bool CheckAndSaveIcon(const base::FilePath& icon_file, const SkBitmap& image) {
177  if (ShouldUpdateIcon(icon_file, image)) {
178    if (SaveIconWithCheckSum(icon_file, image)) {
179      // Refresh shell's icon cache. This call is quite disruptive as user would
180      // see explorer rebuilding the icon cache. It would be great that we find
181      // a better way to achieve this.
182      SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT,
183                     NULL, NULL);
184    } else {
185      return false;
186    }
187  }
188
189  return true;
190}
191
192base::FilePath GetShortcutExecutablePath(
193    const ShellIntegration::ShortcutInfo& shortcut_info) {
194  if (shortcut_info.is_platform_app &&
195      BrowserDistribution::GetDistribution()->AppHostIsSupported() &&
196      chrome_launcher_support::IsAppHostPresent()) {
197    return chrome_launcher_support::GetAnyAppHostPath();
198  }
199
200  return chrome_launcher_support::GetAnyChromePath();
201}
202
203bool CreatePlatformShortcuts(
204    const base::FilePath& web_app_path,
205    const ShellIntegration::ShortcutInfo& shortcut_info,
206    const ShellIntegration::ShortcutLocations& creation_locations) {
207  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
208
209  // Shortcut paths under which to create shortcuts.
210  std::vector<base::FilePath> shortcut_paths =
211      GetShortcutPaths(creation_locations);
212
213  bool pin_to_taskbar = creation_locations.in_quick_launch_bar &&
214                        (base::win::GetVersion() >= base::win::VERSION_WIN7);
215
216  // Create/update the shortcut in the web app path for the "Pin To Taskbar"
217  // option in Win7. We use the web app path shortcut because we will overwrite
218  // it rather than appending unique numbers if the shortcut already exists.
219  // This prevents pinned apps from having unique numbers in their names.
220  if (pin_to_taskbar)
221    shortcut_paths.push_back(web_app_path);
222
223  if (shortcut_paths.empty())
224    return false;
225
226  // Ensure web_app_path exists.
227  if (!file_util::PathExists(web_app_path) &&
228      !file_util::CreateDirectory(web_app_path)) {
229    return false;
230  }
231
232  // Generates file name to use with persisted ico and shortcut file.
233  base::FilePath file_name =
234      web_app::internals::GetSanitizedFileName(shortcut_info.title);
235
236  // Creates an ico file to use with shortcut.
237  base::FilePath icon_file = web_app_path.Append(file_name).ReplaceExtension(
238      FILE_PATH_LITERAL(".ico"));
239  if (!web_app::internals::CheckAndSaveIcon(icon_file,
240        *shortcut_info.favicon.ToSkBitmap())) {
241    return false;
242  }
243
244  base::FilePath target_exe = GetShortcutExecutablePath(shortcut_info);
245  DCHECK(!target_exe.empty());
246
247  // Working directory.
248  base::FilePath working_dir(target_exe.DirName());
249
250  CommandLine cmd_line(CommandLine::NO_PROGRAM);
251  cmd_line = ShellIntegration::CommandLineArgsForLauncher(shortcut_info.url,
252      shortcut_info.extension_id, shortcut_info.profile_path);
253
254  // TODO(evan): we rely on the fact that command_line_string() is
255  // properly quoted for a Windows command line.  The method on
256  // CommandLine should probably be renamed to better reflect that
257  // fact.
258  string16 wide_switches(cmd_line.GetCommandLineString());
259
260  // Sanitize description
261  string16 description = shortcut_info.description;
262  if (description.length() >= MAX_PATH)
263    description.resize(MAX_PATH - 1);
264
265  // Generates app id from web app url and profile path.
266  std::string app_name(web_app::GenerateApplicationNameFromInfo(shortcut_info));
267  string16 app_id(ShellIntegration::GetAppModelIdForProfile(
268      UTF8ToUTF16(app_name), shortcut_info.profile_path));
269
270  bool success = true;
271  for (size_t i = 0; i < shortcut_paths.size(); ++i) {
272    base::FilePath shortcut_file = shortcut_paths[i].Append(file_name).
273        ReplaceExtension(FILE_PATH_LITERAL(".lnk"));
274    if (shortcut_paths[i] != web_app_path) {
275      int unique_number =
276          file_util::GetUniquePathNumber(shortcut_file, FILE_PATH_LITERAL(""));
277      if (unique_number == -1) {
278        success = false;
279        continue;
280      } else if (unique_number > 0) {
281        shortcut_file = shortcut_file.InsertBeforeExtensionASCII(
282            base::StringPrintf(" (%d)", unique_number));
283      }
284    }
285    base::win::ShortcutProperties shortcut_properties;
286    shortcut_properties.set_target(target_exe);
287    shortcut_properties.set_working_dir(working_dir);
288    shortcut_properties.set_arguments(wide_switches);
289    shortcut_properties.set_description(description);
290    shortcut_properties.set_icon(icon_file, 0);
291    shortcut_properties.set_app_id(app_id);
292    shortcut_properties.set_dual_mode(false);
293    success = base::win::CreateOrUpdateShortcutLink(
294        shortcut_file, shortcut_properties,
295        base::win::SHORTCUT_CREATE_ALWAYS) && success;
296  }
297
298  if (success && pin_to_taskbar) {
299    // Use the web app path shortcut for pinning to avoid having unique numbers
300    // in the application name.
301    base::FilePath shortcut_to_pin = web_app_path.Append(file_name).
302        ReplaceExtension(FILE_PATH_LITERAL(".lnk"));
303    success = base::win::TaskbarPinShortcutLink(
304        shortcut_to_pin.value().c_str()) && success;
305  }
306
307  return success;
308}
309
310void UpdatePlatformShortcuts(
311    const base::FilePath& web_app_path,
312    const ShellIntegration::ShortcutInfo& shortcut_info) {
313  // Generates file name to use with persisted ico and shortcut file.
314  base::FilePath file_name =
315      web_app::internals::GetSanitizedFileName(shortcut_info.title);
316
317  // If an icon file exists, and is out of date, replace it with the new icon
318  // and let the shell know the icon has been modified.
319  base::FilePath icon_file = web_app_path.Append(file_name).ReplaceExtension(
320      FILE_PATH_LITERAL(".ico"));
321  if (file_util::PathExists(icon_file)) {
322    web_app::internals::CheckAndSaveIcon(icon_file,
323        *shortcut_info.favicon.ToSkBitmap());
324  }
325}
326
327void DeletePlatformShortcuts(
328    const base::FilePath& web_app_path,
329    const ShellIntegration::ShortcutInfo& shortcut_info) {
330  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
331
332  // Get all possible locations for shortcuts.
333  ShellIntegration::ShortcutLocations all_shortcut_locations;
334  all_shortcut_locations.in_applications_menu = true;
335  all_shortcut_locations.in_quick_launch_bar = true;
336  all_shortcut_locations.on_desktop = true;
337  std::vector<base::FilePath> shortcut_paths = GetShortcutPaths(
338      all_shortcut_locations);
339  if (base::win::GetVersion() >= base::win::VERSION_WIN7)
340    shortcut_paths.push_back(web_app_path);
341
342  for (std::vector<base::FilePath>::const_iterator i = shortcut_paths.begin();
343       i != shortcut_paths.end(); ++i) {
344    std::vector<base::FilePath> shortcut_files =
345        MatchingShortcutsForProfileAndExtension(*i, shortcut_info.profile_path,
346            shortcut_info.title);
347    for (std::vector<base::FilePath>::const_iterator j = shortcut_files.begin();
348         j != shortcut_files.end(); ++j) {
349      // Any shortcut could have been pinned, either by chrome or the user, so
350      // they are all unpinned.
351      base::win::TaskbarUnpinShortcutLink(j->value().c_str());
352      file_util::Delete(*j, false);
353    }
354  }
355}
356
357}  // namespace internals
358
359}  // namespace web_app
360