1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/shell_integration_win.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/test/test_shortcut_win.h"
19 #include "base/win/scoped_com_initializer.h"
20 #include "chrome/browser/shell_integration.h"
21 #include "chrome/browser/web_applications/os_integration/web_app_shortcut_win.h"
22 #include "chrome/browser/web_applications/web_app_helpers.h"
23 #include "chrome/common/chrome_constants.h"
24 #include "chrome/common/chrome_paths_internal.h"
25 #include "chrome/install_static/install_util.h"
26 #include "chrome/installer/util/install_util.h"
27 #include "chrome/installer/util/shell_util.h"
28 #include "chrome/installer/util/util_constants.h"
29 #include "testing/gtest/include/gtest/gtest.h"
31 namespace shell_integration {
36 struct ShortcutTestObject {
38 base::win::ShortcutProperties properties;
41 class ShellIntegrationWinMigrateShortcutTest : public testing::Test {
43 ShellIntegrationWinMigrateShortcutTest(
44 const ShellIntegrationWinMigrateShortcutTest&) = delete;
45 ShellIntegrationWinMigrateShortcutTest& operator=(
46 const ShellIntegrationWinMigrateShortcutTest&) = delete;
49 ShellIntegrationWinMigrateShortcutTest() {}
51 void SetUp() override {
52 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
54 temp_dir_sub_dir_.CreateUniqueTempDirUnderPath(temp_dir_.GetPath()));
55 // A path to a random target.
56 base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &other_target_);
58 // This doesn't need to actually have a base name of "chrome.exe".
59 base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &chrome_exe_);
61 chrome_app_id_ = ShellUtil::GetBrowserModelId(true);
63 base::FilePath default_user_data_dir;
64 chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
65 base::FilePath default_profile_path =
66 default_user_data_dir.AppendASCII(chrome::kInitialProfile);
67 non_default_user_data_dir_ = base::FilePath(FILE_PATH_LITERAL("root"))
68 .Append(FILE_PATH_LITERAL("Non Default Data Dir"));
69 non_default_profile_ = L"NonDefault";
70 non_default_profile_chrome_app_id_ = GetAppUserModelIdForBrowser(
71 default_user_data_dir.Append(non_default_profile_));
72 non_default_user_data_dir_chrome_app_id_ = GetAppUserModelIdForBrowser(
73 non_default_user_data_dir_.AppendASCII(chrome::kInitialProfile));
74 non_default_user_data_dir_and_profile_chrome_app_id_ =
75 GetAppUserModelIdForBrowser(
76 non_default_user_data_dir_.Append(non_default_profile_));
78 extension_id_ = L"chromiumexampleappidforunittests";
79 std::wstring app_name =
80 base::UTF8ToWide(web_app::GenerateApplicationNameFromAppId(
81 base::WideToUTF8(extension_id_)));
82 extension_app_id_ = GetAppUserModelIdForApp(app_name, default_profile_path);
83 non_default_profile_extension_app_id_ = GetAppUserModelIdForApp(
84 app_name, default_user_data_dir.Append(non_default_profile_));
87 // Creates a test shortcut corresponding to |shortcut_properties| and resets
88 // |shortcut_properties| after copying it to an internal structure for later
90 void AddTestShortcutAndResetProperties(
91 const base::FilePath& shortcut_dir,
92 base::win::ShortcutProperties* shortcut_properties) {
93 ShortcutTestObject shortcut_test_object;
94 base::FilePath shortcut_path = shortcut_dir.Append(
95 L"Shortcut " + base::NumberToWString(shortcuts_.size()) +
97 shortcut_test_object.path = shortcut_path;
98 shortcut_test_object.properties = *shortcut_properties;
99 shortcuts_.push_back(shortcut_test_object);
100 ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink(
101 shortcut_path, *shortcut_properties,
102 base::win::ShortcutOperation::kCreateAlways));
103 shortcut_properties->options = 0U;
106 void CreateShortcuts() {
107 // A temporary object to pass properties to
108 // AddTestShortcutAndResetProperties().
109 base::win::ShortcutProperties temp_properties;
111 // Shortcut 0 doesn't point to chrome.exe and thus should never be migrated.
112 temp_properties.set_target(other_target_);
113 temp_properties.set_app_id(L"Dumbo");
114 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
115 temp_dir_.GetPath(), &temp_properties));
117 // Shortcut 1 points to chrome.exe and thus should be migrated.
118 temp_properties.set_target(chrome_exe_);
119 temp_properties.set_app_id(L"Dumbo");
120 temp_properties.set_dual_mode(false);
121 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
122 temp_dir_.GetPath(), &temp_properties));
124 // Shortcut 2 points to chrome.exe, but already has the right appid and thus
125 // should only be migrated if dual_mode is desired.
126 temp_properties.set_target(chrome_exe_);
127 temp_properties.set_app_id(chrome_app_id_);
128 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
129 temp_dir_.GetPath(), &temp_properties));
131 // Shortcut 3 is like shortcut 1, but it's appid is a prefix of the expected
132 // appid instead of being totally different.
133 std::wstring chrome_app_id_is_prefix(chrome_app_id_);
134 chrome_app_id_is_prefix.push_back(L'1');
135 temp_properties.set_target(chrome_exe_);
136 temp_properties.set_app_id(chrome_app_id_is_prefix);
137 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
138 temp_dir_.GetPath(), &temp_properties));
140 // Shortcut 4 is like shortcut 1, but it's appid is of the same size as the
142 std::wstring same_size_as_chrome_app_id(chrome_app_id_.size(), L'1');
143 temp_properties.set_target(chrome_exe_);
144 temp_properties.set_app_id(same_size_as_chrome_app_id);
145 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
146 temp_dir_.GetPath(), &temp_properties));
148 // Shortcut 5 doesn't have an app_id, nor is dual_mode even set; they should
149 // be set as expected upon migration.
150 temp_properties.set_target(chrome_exe_);
151 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
152 temp_dir_.GetPath(), &temp_properties));
154 // Shortcut 6 has a non-default profile directory and so should get a non-
156 temp_properties.set_target(chrome_exe_);
157 temp_properties.set_app_id(L"Dumbo");
158 temp_properties.set_arguments(
159 L"--profile-directory=" + non_default_profile_);
160 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
161 temp_dir_.GetPath(), &temp_properties));
163 // Shortcut 7 has a non-default user data directory and so should get a non-
165 temp_properties.set_target(chrome_exe_);
166 temp_properties.set_app_id(L"Dumbo");
167 temp_properties.set_arguments(
168 L"--user-data-dir=\"" + non_default_user_data_dir_.value() + L"\"");
169 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
170 temp_dir_.GetPath(), &temp_properties));
172 // Shortcut 8 has a non-default user data directory as well as a non-default
173 // profile directory and so should get a non-default app id.
174 temp_properties.set_target(chrome_exe_);
175 temp_properties.set_app_id(L"Dumbo");
176 temp_properties.set_arguments(
177 L"--user-data-dir=\"" + non_default_user_data_dir_.value() + L"\" " +
178 L"--profile-directory=" + non_default_profile_);
179 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
180 temp_dir_.GetPath(), &temp_properties));
182 // Shortcut 9 is a shortcut to an app and should get an app id for that app
183 // rather than the chrome app id.
184 temp_properties.set_target(chrome_exe_);
185 temp_properties.set_app_id(L"Dumbo");
186 temp_properties.set_arguments(
187 L"--app-id=" + extension_id_);
188 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
189 temp_dir_.GetPath(), &temp_properties));
191 // Shortcut 10 is a shortcut to an app with a non-default profile and should
192 // get an app id for that app with a non-default app id rather than the
194 temp_properties.set_target(chrome_exe_);
195 temp_properties.set_app_id(L"Dumbo");
196 temp_properties.set_arguments(
197 L"--app-id=" + extension_id_ +
198 L" --profile-directory=" + non_default_profile_);
199 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
200 temp_dir_.GetPath(), &temp_properties));
202 // Shortcut 11 points to chrome.exe, already has the right appid, and has
203 // dual_mode set and thus should only be migrated if dual_mode is being
205 temp_properties.set_target(chrome_exe_);
206 temp_properties.set_app_id(chrome_app_id_);
207 temp_properties.set_dual_mode(true);
208 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
209 temp_dir_.GetPath(), &temp_properties));
211 // Shortcut 12 is similar to 11 but with dual_mode explicitly set to false.
212 temp_properties.set_target(chrome_exe_);
213 temp_properties.set_app_id(chrome_app_id_);
214 temp_properties.set_dual_mode(false);
215 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
216 temp_dir_.GetPath(), &temp_properties));
218 // Shortcut 13 is like shortcut 1, but it's appid explicitly includes the
220 std::wstring chrome_app_id_with_default_profile =
221 chrome_app_id_ + L".Default";
222 temp_properties.set_target(chrome_exe_);
223 temp_properties.set_app_id(chrome_app_id_with_default_profile);
224 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
225 temp_dir_.GetPath(), &temp_properties));
228 base::win::ScopedCOMInitializer com_initializer_;
230 base::ScopedTempDir temp_dir_;
232 // Used to test migration of shortcuts in ImplicitApps sub-directories.
233 base::ScopedTempDir temp_dir_sub_dir_;
236 std::vector<ShortcutTestObject> shortcuts_;
238 // The path to a fake chrome.exe.
239 base::FilePath chrome_exe_;
241 // The path to a random target.
242 base::FilePath other_target_;
244 // Chrome's AppUserModelId.
245 std::wstring chrome_app_id_;
247 // A profile that isn't the Default profile.
248 std::wstring non_default_profile_;
250 // A user data dir that isn't the default.
251 base::FilePath non_default_user_data_dir_;
253 // Chrome's AppUserModelId for the non-default profile.
254 std::wstring non_default_profile_chrome_app_id_;
256 // Chrome's AppUserModelId for the non-default user data dir.
257 std::wstring non_default_user_data_dir_chrome_app_id_;
259 // Chrome's AppUserModelId for the non-default user data dir and non-default
261 std::wstring non_default_user_data_dir_and_profile_chrome_app_id_;
263 // An example extension id of an example app.
264 std::wstring extension_id_;
266 // The app id of the example app for the default profile and user data dir.
267 std::wstring extension_app_id_;
269 // The app id of the example app for the non-default profile.
270 std::wstring non_default_profile_extension_app_id_;
275 TEST_F(ShellIntegrationWinMigrateShortcutTest, ClearDualModeAndAdjustAppIds) {
277 // 10 shortcuts should have their app id updated below and shortcut 11 should
278 // be migrated away from dual_mode for a total of 11 shortcuts migrated.
280 MigrateShortcutsInPathInternal(chrome_exe_, temp_dir_.GetPath()));
282 // Shortcut 1, 3, 4, 5, 6, 7, 8, 9, 10, and 13 should have had their app_id
284 shortcuts_[1].properties.set_app_id(chrome_app_id_);
285 shortcuts_[3].properties.set_app_id(chrome_app_id_);
286 shortcuts_[4].properties.set_app_id(chrome_app_id_);
287 shortcuts_[5].properties.set_app_id(chrome_app_id_);
288 shortcuts_[6].properties.set_app_id(non_default_profile_chrome_app_id_);
289 shortcuts_[7].properties.set_app_id(non_default_user_data_dir_chrome_app_id_);
290 shortcuts_[8].properties.set_app_id(
291 non_default_user_data_dir_and_profile_chrome_app_id_);
292 shortcuts_[9].properties.set_app_id(extension_app_id_);
293 shortcuts_[10].properties.set_app_id(non_default_profile_extension_app_id_);
294 shortcuts_[13].properties.set_app_id(chrome_app_id_);
296 // No shortcut should still have the dual_mode property.
297 for (size_t i = 0; i < shortcuts_.size(); ++i)
298 shortcuts_[i].properties.set_dual_mode(false);
300 for (size_t i = 0; i < shortcuts_.size(); ++i) {
302 base::win::ValidateShortcut(shortcuts_[i].path, shortcuts_[i].properties);
305 // Make sure shortcuts are not re-migrated.
307 MigrateShortcutsInPathInternal(chrome_exe_, temp_dir_.GetPath()));
310 // Test that chrome_proxy.exe shortcuts (PWA) have their app_id migrated
311 // to not include the default profile name. This tests both shortcuts in the
312 // DIR_TASKBAR_PINS and sub-directories of DIR_IMPLICIT_APP_SHORTCUTS.
313 TEST_F(ShellIntegrationWinMigrateShortcutTest, MigrateChromeProxyTest) {
314 // Create shortcut to chrome_proxy_exe in executable directory,
315 // using the default profile, with the AppModelId not containing the
317 base::win::ShortcutProperties temp_properties;
318 temp_properties.set_target(web_app::GetChromeProxyPath());
319 temp_properties.set_app_id(L"Dumbo.Default");
320 ASSERT_NO_FATAL_FAILURE(
321 AddTestShortcutAndResetProperties(temp_dir_.GetPath(), &temp_properties));
322 temp_properties.set_target(web_app::GetChromeProxyPath());
323 temp_properties.set_app_id(L"Dumbo2.Default");
324 ASSERT_NO_FATAL_FAILURE(AddTestShortcutAndResetProperties(
325 temp_dir_sub_dir_.GetPath(), &temp_properties));
327 // Check that a chrome proxy shortcut whose app_id is just the extension app
328 // id has its AUMI migrated to start with the browser's app_id.
329 // It technically doesn't matter what ShortcutProperties's app_id is,
330 // since the migration is based on ShortcutProperties.arguments.
331 temp_properties.set_target(web_app::GetChromeProxyPath());
332 temp_properties.set_app_id(L"Dumbo3.Default");
333 base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
334 GURL(), base::WideToUTF8(extension_id_), base::FilePath(), "");
335 ASSERT_EQ(cmd_line.GetCommandLineString(), L" --app-id=" + extension_id_);
336 temp_properties.set_arguments(cmd_line.GetCommandLineString());
337 ASSERT_NO_FATAL_FAILURE(
338 AddTestShortcutAndResetProperties(temp_dir_.GetPath(), &temp_properties));
340 // Check that a chrome proxy shortcut with a kApp url in its command line
341 // has its AUMI migrated to start with the browser's app_id.
342 temp_properties.set_target(web_app::GetChromeProxyPath());
343 temp_properties.set_app_id(L"Dumbo4.Default");
344 GURL url("http://www.example.com");
345 cmd_line = shell_integration::CommandLineArgsForLauncher(
346 url, std::string(), base::FilePath(), "");
347 ASSERT_EQ(cmd_line.GetCommandLineString(), L" --app=http://www.example.com/");
348 temp_properties.set_arguments(cmd_line.GetCommandLineString());
349 ASSERT_NO_FATAL_FAILURE(
350 AddTestShortcutAndResetProperties(temp_dir_.GetPath(), &temp_properties));
352 MigrateTaskbarPinsCallback(temp_dir_.GetPath(), temp_dir_.GetPath());
353 // Verify that the migrated shortcut in temp_dir_ does not contain the default
355 shortcuts_[0].properties.set_app_id(chrome_app_id_);
356 base::win::ValidateShortcut(shortcuts_[0].path, shortcuts_[0].properties);
357 // Verify that the migrated shortcut in temp_dir_sub does not contain the
358 // default profile name.
359 shortcuts_[1].properties.set_app_id(chrome_app_id_);
360 base::win::ValidateShortcut(shortcuts_[1].path, shortcuts_[1].properties);
362 shortcuts_[2].properties.set_app_id(extension_app_id_);
363 base::win::ValidateShortcut(shortcuts_[2].path, shortcuts_[2].properties);
365 shortcuts_[3].properties.set_app_id(GetAppUserModelIdForApp(
366 base::UTF8ToWide(web_app::GenerateApplicationNameFromURL(url)),
368 base::win::ValidateShortcut(shortcuts_[3].path, shortcuts_[3].properties);
371 // This test verifies that MigrateTaskbarPins does a case-insensitive
372 // comparison when comparing the shortcut target with the chrome exe path.
373 TEST_F(ShellIntegrationWinMigrateShortcutTest, MigrateMixedCaseDirTest) {
374 base::win::ShortcutProperties temp_properties;
375 base::FilePath chrome_proxy_path(web_app::GetChromeProxyPath());
376 ASSERT_EQ(chrome_proxy_path.Extension(), FILE_PATH_LITERAL(".exe"));
377 temp_properties.set_target(
378 chrome_proxy_path.ReplaceExtension(FILE_PATH_LITERAL("EXE")));
379 temp_properties.set_app_id(L"Dumbo.Default");
380 ASSERT_NO_FATAL_FAILURE(
381 AddTestShortcutAndResetProperties(temp_dir_.GetPath(), &temp_properties));
382 MigrateTaskbarPinsCallback(temp_dir_.GetPath(), temp_dir_.GetPath());
383 // Verify that the shortcut was migrated, i.e., its app_id does not contain
384 // the default profile name.
385 shortcuts_[0].properties.set_app_id(chrome_app_id_);
386 base::win::ValidateShortcut(shortcuts_[0].path, shortcuts_[0].properties);
389 TEST(ShellIntegrationWinTest, GetAppModelIdForProfileTest) {
390 const std::wstring base_app_id(install_static::GetBaseAppId());
392 // Empty profile path should get chrome::kBrowserAppID
393 std::wstring app_name = L"app";
394 std::wstring expected_model_id_without_profile =
395 base_app_id + L"." + app_name;
396 base::FilePath empty_path;
397 EXPECT_EQ(expected_model_id_without_profile,
398 GetAppUserModelIdForApp(app_name, empty_path));
400 // Default profile path should get chrome::kBrowserAppID
401 base::FilePath default_user_data_dir;
402 chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
403 base::FilePath default_profile_path =
404 default_user_data_dir.AppendASCII(chrome::kInitialProfile);
405 EXPECT_EQ(expected_model_id_without_profile,
406 GetAppUserModelIdForApp(app_name, default_profile_path));
408 // Non-default profile path should get chrome::kBrowserAppID joined with
410 base::FilePath profile_path(FILE_PATH_LITERAL("root"));
411 profile_path = profile_path.Append(FILE_PATH_LITERAL("udd"));
412 profile_path = profile_path.Append(FILE_PATH_LITERAL("User Data - Test"));
413 EXPECT_EQ(expected_model_id_without_profile + L".udd.UserDataTest",
414 GetAppUserModelIdForApp(app_name, profile_path));
418 } // namespace shell_integration