1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/web_applications/web_app_mac.h"
7 #import <Cocoa/Cocoa.h>
11 #include "base/command_line.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/scoped_nsobject.h"
16 #include "base/path_service.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "chrome/common/chrome_paths.h"
20 #include "chrome/common/chrome_switches.h"
21 #import "chrome/common/mac/app_mode_common.h"
22 #include "grit/theme_resources.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 #import "testing/gtest_mac.h"
26 #include "third_party/skia/include/core/SkBitmap.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/image/image.h"
31 using ::testing::Return;
32 using ::testing::NiceMock;
36 const char kFakeChromeBundleId[] = "fake.cfbundleidentifier";
38 class WebAppShortcutCreatorMock : public web_app::WebAppShortcutCreator {
40 WebAppShortcutCreatorMock(const base::FilePath& app_data_dir,
41 const web_app::ShortcutInfo& shortcut_info)
42 : WebAppShortcutCreator(app_data_dir,
44 extensions::FileHandlersInfo()) {}
46 WebAppShortcutCreatorMock(
47 const base::FilePath& app_data_dir,
48 const web_app::ShortcutInfo& shortcut_info,
49 const extensions::FileHandlersInfo& file_handlers_info)
50 : WebAppShortcutCreator(app_data_dir, shortcut_info, file_handlers_info) {
53 MOCK_CONST_METHOD0(GetApplicationsDirname, base::FilePath());
54 MOCK_CONST_METHOD1(GetAppBundleById,
55 base::FilePath(const std::string& bundle_id));
56 MOCK_CONST_METHOD0(RevealAppShimInFinder, void());
59 DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorMock);
62 web_app::ShortcutInfo GetShortcutInfo() {
63 web_app::ShortcutInfo info;
64 info.extension_id = "extensionid";
65 info.extension_path = base::FilePath("/fake/extension/path");
66 info.title = base::ASCIIToUTF16("Shortcut Title");
67 info.url = GURL("http://example.com/");
68 info.profile_path = base::FilePath("user_data_dir").Append("Profile 1");
69 info.profile_name = "profile name";
73 class WebAppShortcutCreatorTest : public testing::Test {
75 WebAppShortcutCreatorTest() {}
77 void SetUp() override {
78 base::mac::SetBaseBundleID(kFakeChromeBundleId);
80 EXPECT_TRUE(temp_app_data_dir_.CreateUniqueTempDir());
81 EXPECT_TRUE(temp_destination_dir_.CreateUniqueTempDir());
82 app_data_dir_ = temp_app_data_dir_.path();
83 destination_dir_ = temp_destination_dir_.path();
85 info_ = GetShortcutInfo();
86 shim_base_name_ = base::FilePath(
87 info_.profile_path.BaseName().value() +
88 " " + info_.extension_id + ".app");
89 internal_shim_path_ = app_data_dir_.Append(shim_base_name_);
90 shim_path_ = destination_dir_.Append(shim_base_name_);
93 base::ScopedTempDir temp_app_data_dir_;
94 base::ScopedTempDir temp_destination_dir_;
95 base::FilePath app_data_dir_;
96 base::FilePath destination_dir_;
98 web_app::ShortcutInfo info_;
99 base::FilePath shim_base_name_;
100 base::FilePath internal_shim_path_;
101 base::FilePath shim_path_;
104 DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorTest);
112 TEST_F(WebAppShortcutCreatorTest, CreateShortcuts) {
113 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
114 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
115 .WillRepeatedly(Return(destination_dir_));
117 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
118 SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
119 EXPECT_TRUE(base::PathExists(shim_path_));
120 EXPECT_TRUE(base::PathExists(destination_dir_));
121 EXPECT_EQ(shim_base_name_, shortcut_creator.GetShortcutBasename());
123 base::FilePath plist_path =
124 shim_path_.Append("Contents").Append("Info.plist");
125 NSDictionary* plist = [NSDictionary dictionaryWithContentsOfFile:
126 base::mac::FilePathToNSString(plist_path)];
127 EXPECT_NSEQ(base::SysUTF8ToNSString(info_.extension_id),
128 [plist objectForKey:app_mode::kCrAppModeShortcutIDKey]);
129 EXPECT_NSEQ(base::SysUTF16ToNSString(info_.title),
130 [plist objectForKey:app_mode::kCrAppModeShortcutNameKey]);
131 EXPECT_NSEQ(base::SysUTF8ToNSString(info_.url.spec()),
132 [plist objectForKey:app_mode::kCrAppModeShortcutURLKey]);
134 // Make sure all values in the plist are actually filled in.
135 for (id key in plist) {
136 id value = [plist valueForKey:key];
137 if (!base::mac::ObjCCast<NSString>(value))
140 EXPECT_EQ([value rangeOfString:@"@APP_"].location, NSNotFound)
141 << [key UTF8String] << ":" << [value UTF8String];
145 TEST_F(WebAppShortcutCreatorTest, UpdateShortcuts) {
146 base::ScopedTempDir other_folder_temp_dir;
147 EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir());
148 base::FilePath other_folder = other_folder_temp_dir.path();
149 base::FilePath other_shim_path = other_folder.Append(shim_base_name_);
151 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
152 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
153 .WillRepeatedly(Return(destination_dir_));
155 std::string expected_bundle_id = kFakeChromeBundleId;
156 expected_bundle_id += ".app.Profile-1-" + info_.extension_id;
157 EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
158 .WillOnce(Return(other_shim_path));
160 EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
162 EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true));
164 EXPECT_TRUE(shortcut_creator.UpdateShortcuts());
165 EXPECT_FALSE(base::PathExists(shim_path_));
166 EXPECT_TRUE(base::PathExists(other_shim_path.Append("Contents")));
168 // Also test case where GetAppBundleById fails.
169 EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
170 .WillOnce(Return(base::FilePath()));
172 EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
174 EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true));
176 EXPECT_FALSE(shortcut_creator.UpdateShortcuts());
177 EXPECT_FALSE(base::PathExists(shim_path_));
178 EXPECT_FALSE(base::PathExists(other_shim_path.Append("Contents")));
181 TEST_F(WebAppShortcutCreatorTest, DeleteShortcuts) {
182 // When using PathService::Override, it calls base::MakeAbsoluteFilePath.
183 // On Mac this prepends "/private" to the path, but points to the same
184 // directory in the file system.
185 app_data_dir_ = base::MakeAbsoluteFilePath(app_data_dir_);
187 base::ScopedTempDir other_folder_temp_dir;
188 EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir());
189 base::FilePath other_folder = other_folder_temp_dir.path();
190 base::FilePath other_shim_path = other_folder.Append(shim_base_name_);
192 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
193 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
194 .WillRepeatedly(Return(destination_dir_));
196 std::string expected_bundle_id = kFakeChromeBundleId;
197 expected_bundle_id += ".app.Profile-1-" + info_.extension_id;
198 EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
199 .WillOnce(Return(other_shim_path));
201 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
202 SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
203 EXPECT_TRUE(base::PathExists(internal_shim_path_));
204 EXPECT_TRUE(base::PathExists(shim_path_));
206 // Create an extra shim in another folder. It should be deleted since its
207 // bundle id matches.
208 EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
209 EXPECT_TRUE(base::PathExists(other_shim_path));
211 // Change the user_data_dir of the shim at shim_path_. It should not be
212 // deleted since its user_data_dir does not match.
213 NSString* plist_path = base::mac::FilePathToNSString(
214 shim_path_.Append("Contents").Append("Info.plist"));
215 NSMutableDictionary* plist =
216 [NSDictionary dictionaryWithContentsOfFile:plist_path];
217 [plist setObject:@"fake_user_data_dir"
218 forKey:app_mode::kCrAppModeUserDataDirKey];
219 [plist writeToFile:plist_path
222 EXPECT_TRUE(PathService::Override(chrome::DIR_USER_DATA, app_data_dir_));
223 shortcut_creator.DeleteShortcuts();
224 EXPECT_FALSE(base::PathExists(internal_shim_path_));
225 EXPECT_TRUE(base::PathExists(shim_path_));
226 EXPECT_FALSE(base::PathExists(other_shim_path));
229 TEST_F(WebAppShortcutCreatorTest, CreateAppListShortcut) {
230 // With an empty |profile_name|, the shortcut path should not have the profile
231 // directory prepended to the extension id on the app bundle name.
232 info_.profile_name.clear();
233 base::FilePath dst_path =
234 destination_dir_.Append(info_.extension_id + ".app");
236 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(base::FilePath(), info_);
237 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
238 .WillRepeatedly(Return(destination_dir_));
239 EXPECT_EQ(dst_path.BaseName(), shortcut_creator.GetShortcutBasename());
242 TEST_F(WebAppShortcutCreatorTest, RunShortcut) {
243 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
244 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
245 .WillRepeatedly(Return(destination_dir_));
247 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
248 SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
249 EXPECT_TRUE(base::PathExists(shim_path_));
251 ssize_t status = getxattr(
252 shim_path_.value().c_str(), "com.apple.quarantine", NULL, 0, 0, 0);
253 EXPECT_EQ(-1, status);
254 EXPECT_EQ(ENOATTR, errno);
257 TEST_F(WebAppShortcutCreatorTest, CreateFailure) {
258 base::FilePath non_existent_path =
259 destination_dir_.Append("not-existent").Append("name.app");
261 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
262 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
263 .WillRepeatedly(Return(non_existent_path));
264 EXPECT_FALSE(shortcut_creator.CreateShortcuts(
265 SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
268 TEST_F(WebAppShortcutCreatorTest, UpdateIcon) {
269 gfx::Image product_logo =
270 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
271 IDR_PRODUCT_LOGO_32);
272 info_.favicon.Add(product_logo);
273 WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_);
275 ASSERT_TRUE(shortcut_creator.UpdateIcon(shim_path_));
276 base::FilePath icon_path =
277 shim_path_.Append("Contents").Append("Resources").Append("app.icns");
279 base::scoped_nsobject<NSImage> image([[NSImage alloc]
280 initWithContentsOfFile:base::mac::FilePathToNSString(icon_path)]);
282 EXPECT_EQ(product_logo.Width(), [image size].width);
283 EXPECT_EQ(product_logo.Height(), [image size].height);
286 TEST_F(WebAppShortcutCreatorTest, RevealAppShimInFinder) {
287 WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_);
288 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
289 .WillRepeatedly(Return(destination_dir_));
291 EXPECT_CALL(shortcut_creator, RevealAppShimInFinder())
293 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
294 SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
296 EXPECT_CALL(shortcut_creator, RevealAppShimInFinder());
297 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
298 SHORTCUT_CREATION_BY_USER, web_app::ShortcutLocations()));
301 TEST_F(WebAppShortcutCreatorTest, FileHandlers) {
302 base::CommandLine::ForCurrentProcess()->AppendSwitch(
303 switches::kEnableAppsFileAssociations);
304 extensions::FileHandlersInfo file_handlers_info;
305 extensions::FileHandlerInfo handler_0;
306 handler_0.extensions.insert("ext0");
307 handler_0.extensions.insert("ext1");
308 handler_0.types.insert("type0");
309 handler_0.types.insert("type1");
310 file_handlers_info.push_back(handler_0);
311 extensions::FileHandlerInfo handler_1;
312 handler_1.extensions.insert("ext2");
313 handler_1.types.insert("type2");
314 file_handlers_info.push_back(handler_1);
316 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(
317 app_data_dir_, info_, file_handlers_info);
318 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
319 .WillRepeatedly(Return(destination_dir_));
320 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
321 SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
323 base::FilePath plist_path =
324 shim_path_.Append("Contents").Append("Info.plist");
325 NSDictionary* plist = [NSDictionary
326 dictionaryWithContentsOfFile:base::mac::FilePathToNSString(plist_path)];
327 NSArray* file_handlers =
328 [plist objectForKey:app_mode::kCFBundleDocumentTypesKey];
330 NSDictionary* file_handler_0 = [file_handlers objectAtIndex:0];
331 EXPECT_NSEQ(app_mode::kBundleTypeRoleViewer,
332 [file_handler_0 objectForKey:app_mode::kCFBundleTypeRoleKey]);
333 NSArray* file_handler_0_extensions =
334 [file_handler_0 objectForKey:app_mode::kCFBundleTypeExtensionsKey];
335 EXPECT_TRUE([file_handler_0_extensions containsObject:@"ext0"]);
336 EXPECT_TRUE([file_handler_0_extensions containsObject:@"ext1"]);
337 NSArray* file_handler_0_types =
338 [file_handler_0 objectForKey:app_mode::kCFBundleTypeMIMETypesKey];
339 EXPECT_TRUE([file_handler_0_types containsObject:@"type0"]);
340 EXPECT_TRUE([file_handler_0_types containsObject:@"type1"]);
342 NSDictionary* file_handler_1 = [file_handlers objectAtIndex:1];
343 EXPECT_NSEQ(app_mode::kBundleTypeRoleViewer,
344 [file_handler_1 objectForKey:app_mode::kCFBundleTypeRoleKey]);
345 NSArray* file_handler_1_extensions =
346 [file_handler_1 objectForKey:app_mode::kCFBundleTypeExtensionsKey];
347 EXPECT_TRUE([file_handler_1_extensions containsObject:@"ext2"]);
348 NSArray* file_handler_1_types =
349 [file_handler_1 objectForKey:app_mode::kCFBundleTypeMIMETypesKey];
350 EXPECT_TRUE([file_handler_1_types containsObject:@"type2"]);
353 } // namespace web_app