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/file_util.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/mac/foundation_util.h"
14 #include "base/mac/scoped_nsobject.h"
15 #include "base/path_service.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/common/chrome_paths.h"
19 #import "chrome/common/mac/app_mode_common.h"
20 #include "grit/theme_resources.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #import "testing/gtest_mac.h"
24 #include "third_party/skia/include/core/SkBitmap.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/image/image.h"
29 using ::testing::Return;
30 using ::testing::NiceMock;
34 const char kFakeChromeBundleId[] = "fake.cfbundleidentifier";
36 class WebAppShortcutCreatorMock : public web_app::WebAppShortcutCreator {
38 explicit WebAppShortcutCreatorMock(
39 const base::FilePath& app_data_dir,
40 const ShellIntegration::ShortcutInfo& shortcut_info)
41 : WebAppShortcutCreator(app_data_dir,
45 MOCK_CONST_METHOD0(GetApplicationsDirname, base::FilePath());
46 MOCK_CONST_METHOD1(GetAppBundleById,
47 base::FilePath(const std::string& bundle_id));
48 MOCK_CONST_METHOD0(RevealAppShimInFinder, void());
51 DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorMock);
54 ShellIntegration::ShortcutInfo GetShortcutInfo() {
55 ShellIntegration::ShortcutInfo info;
56 info.extension_id = "extensionid";
57 info.extension_path = base::FilePath("/fake/extension/path");
58 info.title = base::ASCIIToUTF16("Shortcut Title");
59 info.url = GURL("http://example.com/");
60 info.profile_path = base::FilePath("user_data_dir").Append("Profile 1");
61 info.profile_name = "profile name";
65 class WebAppShortcutCreatorTest : public testing::Test {
67 WebAppShortcutCreatorTest() {}
69 virtual void SetUp() {
70 base::mac::SetBaseBundleID(kFakeChromeBundleId);
72 EXPECT_TRUE(temp_app_data_dir_.CreateUniqueTempDir());
73 EXPECT_TRUE(temp_destination_dir_.CreateUniqueTempDir());
74 app_data_dir_ = temp_app_data_dir_.path();
75 destination_dir_ = temp_destination_dir_.path();
77 info_ = GetShortcutInfo();
78 shim_base_name_ = base::FilePath(
79 info_.profile_path.BaseName().value() +
80 " " + info_.extension_id + ".app");
81 internal_shim_path_ = app_data_dir_.Append(shim_base_name_);
82 shim_path_ = destination_dir_.Append(shim_base_name_);
85 base::ScopedTempDir temp_app_data_dir_;
86 base::ScopedTempDir temp_destination_dir_;
87 base::FilePath app_data_dir_;
88 base::FilePath destination_dir_;
90 ShellIntegration::ShortcutInfo info_;
91 base::FilePath shim_base_name_;
92 base::FilePath internal_shim_path_;
93 base::FilePath shim_path_;
96 DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorTest);
104 TEST_F(WebAppShortcutCreatorTest, CreateShortcuts) {
105 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
106 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
107 .WillRepeatedly(Return(destination_dir_));
109 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
110 SHORTCUT_CREATION_AUTOMATED, ShellIntegration::ShortcutLocations()));
111 EXPECT_TRUE(base::PathExists(shim_path_));
112 EXPECT_TRUE(base::PathExists(destination_dir_));
113 EXPECT_EQ(shim_base_name_, shortcut_creator.GetShortcutBasename());
115 base::FilePath plist_path =
116 shim_path_.Append("Contents").Append("Info.plist");
117 NSDictionary* plist = [NSDictionary dictionaryWithContentsOfFile:
118 base::mac::FilePathToNSString(plist_path)];
119 EXPECT_NSEQ(base::SysUTF8ToNSString(info_.extension_id),
120 [plist objectForKey:app_mode::kCrAppModeShortcutIDKey]);
121 EXPECT_NSEQ(base::SysUTF16ToNSString(info_.title),
122 [plist objectForKey:app_mode::kCrAppModeShortcutNameKey]);
123 EXPECT_NSEQ(base::SysUTF8ToNSString(info_.url.spec()),
124 [plist objectForKey:app_mode::kCrAppModeShortcutURLKey]);
126 // Make sure all values in the plist are actually filled in.
127 for (id key in plist) {
128 id value = [plist valueForKey:key];
129 if (!base::mac::ObjCCast<NSString>(value))
132 EXPECT_EQ([value rangeOfString:@"@APP_"].location, NSNotFound)
133 << [key UTF8String] << ":" << [value UTF8String];
137 TEST_F(WebAppShortcutCreatorTest, UpdateShortcuts) {
138 base::ScopedTempDir other_folder_temp_dir;
139 EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir());
140 base::FilePath other_folder = other_folder_temp_dir.path();
141 base::FilePath other_shim_path = other_folder.Append(shim_base_name_);
143 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
144 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
145 .WillRepeatedly(Return(destination_dir_));
147 std::string expected_bundle_id = kFakeChromeBundleId;
148 expected_bundle_id += ".app.Profile-1-" + info_.extension_id;
149 EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
150 .WillOnce(Return(other_shim_path));
152 EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
154 EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true));
156 EXPECT_TRUE(shortcut_creator.UpdateShortcuts());
157 EXPECT_FALSE(base::PathExists(shim_path_));
158 EXPECT_TRUE(base::PathExists(other_shim_path.Append("Contents")));
160 // Also test case where GetAppBundleById fails.
161 EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
162 .WillOnce(Return(base::FilePath()));
164 EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
166 EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true));
168 EXPECT_FALSE(shortcut_creator.UpdateShortcuts());
169 EXPECT_FALSE(base::PathExists(shim_path_));
170 EXPECT_FALSE(base::PathExists(other_shim_path.Append("Contents")));
173 TEST_F(WebAppShortcutCreatorTest, DeleteShortcuts) {
174 // When using PathService::Override, it calls base::MakeAbsoluteFilePath.
175 // On Mac this prepends "/private" to the path, but points to the same
176 // directory in the file system.
177 app_data_dir_ = base::MakeAbsoluteFilePath(app_data_dir_);
179 base::ScopedTempDir other_folder_temp_dir;
180 EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir());
181 base::FilePath other_folder = other_folder_temp_dir.path();
182 base::FilePath other_shim_path = other_folder.Append(shim_base_name_);
184 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
185 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
186 .WillRepeatedly(Return(destination_dir_));
188 std::string expected_bundle_id = kFakeChromeBundleId;
189 expected_bundle_id += ".app.Profile-1-" + info_.extension_id;
190 EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
191 .WillOnce(Return(other_shim_path));
193 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
194 SHORTCUT_CREATION_AUTOMATED, ShellIntegration::ShortcutLocations()));
195 EXPECT_TRUE(base::PathExists(internal_shim_path_));
196 EXPECT_TRUE(base::PathExists(shim_path_));
198 // Create an extra shim in another folder. It should be deleted since its
199 // bundle id matches.
200 EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
201 EXPECT_TRUE(base::PathExists(other_shim_path));
203 // Change the user_data_dir of the shim at shim_path_. It should not be
204 // deleted since its user_data_dir does not match.
205 NSString* plist_path = base::mac::FilePathToNSString(
206 shim_path_.Append("Contents").Append("Info.plist"));
207 NSMutableDictionary* plist =
208 [NSDictionary dictionaryWithContentsOfFile:plist_path];
209 [plist setObject:@"fake_user_data_dir"
210 forKey:app_mode::kCrAppModeUserDataDirKey];
211 [plist writeToFile:plist_path
214 EXPECT_TRUE(PathService::Override(chrome::DIR_USER_DATA, app_data_dir_));
215 shortcut_creator.DeleteShortcuts();
216 EXPECT_FALSE(base::PathExists(internal_shim_path_));
217 EXPECT_TRUE(base::PathExists(shim_path_));
218 EXPECT_FALSE(base::PathExists(other_shim_path));
221 TEST_F(WebAppShortcutCreatorTest, CreateAppListShortcut) {
222 // With an empty |profile_name|, the shortcut path should not have the profile
223 // directory prepended to the extension id on the app bundle name.
224 info_.profile_name.clear();
225 base::FilePath dst_path =
226 destination_dir_.Append(info_.extension_id + ".app");
228 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(base::FilePath(), info_);
229 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
230 .WillRepeatedly(Return(destination_dir_));
231 EXPECT_EQ(dst_path.BaseName(), shortcut_creator.GetShortcutBasename());
234 TEST_F(WebAppShortcutCreatorTest, RunShortcut) {
235 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
236 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
237 .WillRepeatedly(Return(destination_dir_));
239 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
240 SHORTCUT_CREATION_AUTOMATED, ShellIntegration::ShortcutLocations()));
241 EXPECT_TRUE(base::PathExists(shim_path_));
243 ssize_t status = getxattr(
244 shim_path_.value().c_str(), "com.apple.quarantine", NULL, 0, 0, 0);
245 EXPECT_EQ(-1, status);
246 EXPECT_EQ(ENOATTR, errno);
249 TEST_F(WebAppShortcutCreatorTest, CreateFailure) {
250 base::FilePath non_existent_path =
251 destination_dir_.Append("not-existent").Append("name.app");
253 NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_, info_);
254 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
255 .WillRepeatedly(Return(non_existent_path));
256 EXPECT_FALSE(shortcut_creator.CreateShortcuts(
257 SHORTCUT_CREATION_AUTOMATED, ShellIntegration::ShortcutLocations()));
260 TEST_F(WebAppShortcutCreatorTest, UpdateIcon) {
261 gfx::Image product_logo =
262 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
263 IDR_PRODUCT_LOGO_32);
264 info_.favicon.Add(product_logo);
265 WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_);
267 ASSERT_TRUE(shortcut_creator.UpdateIcon(shim_path_));
268 base::FilePath icon_path =
269 shim_path_.Append("Contents").Append("Resources").Append("app.icns");
271 base::scoped_nsobject<NSImage> image([[NSImage alloc]
272 initWithContentsOfFile:base::mac::FilePathToNSString(icon_path)]);
274 EXPECT_EQ(product_logo.Width(), [image size].width);
275 EXPECT_EQ(product_logo.Height(), [image size].height);
278 TEST_F(WebAppShortcutCreatorTest, RevealAppShimInFinder) {
279 WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_);
280 EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
281 .WillRepeatedly(Return(destination_dir_));
283 EXPECT_CALL(shortcut_creator, RevealAppShimInFinder())
285 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
286 SHORTCUT_CREATION_AUTOMATED, ShellIntegration::ShortcutLocations()));
288 EXPECT_CALL(shortcut_creator, RevealAppShimInFinder());
289 EXPECT_TRUE(shortcut_creator.CreateShortcuts(
290 SHORTCUT_CREATION_BY_USER, ShellIntegration::ShortcutLocations()));
293 } // namespace web_app