Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / web_applications / web_app_mac_unittest.mm
blob101eff0a2133a9ea418868d60006c859f1746a53
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>
8 #include <errno.h>
9 #include <sys/xattr.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/memory/scoped_ptr.h"
17 #include "base/path_service.h"
18 #include "base/strings/sys_string_conversions.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/common/chrome_paths.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/common/chrome_version_info.h"
23 #import "chrome/common/mac/app_mode_common.h"
24 #include "grit/theme_resources.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #import "testing/gtest_mac.h"
28 #include "third_party/skia/include/core/SkBitmap.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "ui/gfx/image/image.h"
32 using ::testing::_;
33 using ::testing::Return;
34 using ::testing::NiceMock;
36 namespace {
38 const char kFakeChromeBundleId[] = "fake.cfbundleidentifier";
40 class WebAppShortcutCreatorMock : public web_app::WebAppShortcutCreator {
41  public:
42   WebAppShortcutCreatorMock(const base::FilePath& app_data_dir,
43                             const web_app::ShortcutInfo* shortcut_info)
44       : WebAppShortcutCreator(app_data_dir,
45                               shortcut_info,
46                               extensions::FileHandlersInfo()) {}
48   WebAppShortcutCreatorMock(
49       const base::FilePath& app_data_dir,
50       const web_app::ShortcutInfo* shortcut_info,
51       const extensions::FileHandlersInfo& file_handlers_info)
52       : WebAppShortcutCreator(app_data_dir, shortcut_info, file_handlers_info) {
53   }
55   MOCK_CONST_METHOD0(GetApplicationsDirname, base::FilePath());
56   MOCK_CONST_METHOD1(GetAppBundleById,
57                      base::FilePath(const std::string& bundle_id));
58   MOCK_CONST_METHOD0(RevealAppShimInFinder, void());
60  private:
61   DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorMock);
64 scoped_ptr<web_app::ShortcutInfo> GetShortcutInfo() {
65   scoped_ptr<web_app::ShortcutInfo> info(new web_app::ShortcutInfo);
66   info->extension_id = "extensionid";
67   info->extension_path = base::FilePath("/fake/extension/path");
68   info->title = base::ASCIIToUTF16("Shortcut Title");
69   info->url = GURL("http://example.com/");
70   info->profile_path = base::FilePath("user_data_dir").Append("Profile 1");
71   info->profile_name = "profile name";
72   info->version_for_display = "stable 1.0";
73   info->from_bookmark = false;
74   return info;
77 class WebAppShortcutCreatorTest : public testing::Test {
78  protected:
79   WebAppShortcutCreatorTest() {}
81   void SetUp() override {
82     base::mac::SetBaseBundleID(kFakeChromeBundleId);
84     EXPECT_TRUE(temp_app_data_dir_.CreateUniqueTempDir());
85     EXPECT_TRUE(temp_destination_dir_.CreateUniqueTempDir());
86     app_data_dir_ = temp_app_data_dir_.path();
87     destination_dir_ = temp_destination_dir_.path();
89     info_ = GetShortcutInfo();
90     shim_base_name_ = base::FilePath(info_->profile_path.BaseName().value() +
91                                      " " + info_->extension_id + ".app");
92     internal_shim_path_ = app_data_dir_.Append(shim_base_name_);
93     shim_path_ = destination_dir_.Append(shim_base_name_);
94   }
96   base::ScopedTempDir temp_app_data_dir_;
97   base::ScopedTempDir temp_destination_dir_;
98   base::FilePath app_data_dir_;
99   base::FilePath destination_dir_;
101   scoped_ptr<web_app::ShortcutInfo> info_;
102   base::FilePath shim_base_name_;
103   base::FilePath internal_shim_path_;
104   base::FilePath shim_path_;
106  private:
107   DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorTest);
111 }  // namespace
113 namespace web_app {
115 TEST_F(WebAppShortcutCreatorTest, CreateShortcuts) {
116   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_,
117                                                        info_.get());
118   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
119       .WillRepeatedly(Return(destination_dir_));
121   EXPECT_TRUE(shortcut_creator.CreateShortcuts(
122       SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
123   EXPECT_TRUE(base::PathExists(shim_path_));
124   EXPECT_TRUE(base::PathExists(destination_dir_));
125   EXPECT_EQ(shim_base_name_, shortcut_creator.GetShortcutBasename());
127   base::FilePath plist_path =
128       shim_path_.Append("Contents").Append("Info.plist");
129   NSDictionary* plist = [NSDictionary dictionaryWithContentsOfFile:
130       base::mac::FilePathToNSString(plist_path)];
131   EXPECT_NSEQ(base::SysUTF8ToNSString(info_->extension_id),
132               [plist objectForKey:app_mode::kCrAppModeShortcutIDKey]);
133   EXPECT_NSEQ(base::SysUTF16ToNSString(info_->title),
134               [plist objectForKey:app_mode::kCrAppModeShortcutNameKey]);
135   EXPECT_NSEQ(base::SysUTF8ToNSString(info_->url.spec()),
136               [plist objectForKey:app_mode::kCrAppModeShortcutURLKey]);
138   EXPECT_NSEQ(base::SysUTF8ToNSString(chrome::VersionInfo().Version()),
139               [plist objectForKey:app_mode::kCrBundleVersionKey]);
140   EXPECT_NSEQ(base::SysUTF8ToNSString(info_->version_for_display),
141               [plist objectForKey:app_mode::kCFBundleShortVersionStringKey]);
143   // Make sure all values in the plist are actually filled in.
144   for (id key in plist) {
145     id value = [plist valueForKey:key];
146     if (!base::mac::ObjCCast<NSString>(value))
147       continue;
149     EXPECT_EQ([value rangeOfString:@"@APP_"].location, NSNotFound)
150         << [key UTF8String] << ":" << [value UTF8String];
151   }
154 TEST_F(WebAppShortcutCreatorTest, UpdateShortcuts) {
155   base::ScopedTempDir other_folder_temp_dir;
156   EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir());
157   base::FilePath other_folder = other_folder_temp_dir.path();
158   base::FilePath other_shim_path = other_folder.Append(shim_base_name_);
160   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_,
161                                                        info_.get());
162   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
163       .WillRepeatedly(Return(destination_dir_));
165   std::string expected_bundle_id = kFakeChromeBundleId;
166   expected_bundle_id += ".app.Profile-1-" + info_->extension_id;
167   EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
168       .WillOnce(Return(other_shim_path));
170   EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
172   EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true));
174   EXPECT_TRUE(shortcut_creator.UpdateShortcuts());
175   EXPECT_FALSE(base::PathExists(shim_path_));
176   EXPECT_TRUE(base::PathExists(other_shim_path.Append("Contents")));
178   // Also test case where GetAppBundleById fails.
179   EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
180       .WillOnce(Return(base::FilePath()));
182   EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
184   EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true));
186   EXPECT_FALSE(shortcut_creator.UpdateShortcuts());
187   EXPECT_FALSE(base::PathExists(shim_path_));
188   EXPECT_FALSE(base::PathExists(other_shim_path.Append("Contents")));
191 TEST_F(WebAppShortcutCreatorTest, UpdateBookmarkAppShortcut) {
192   base::ScopedTempDir other_folder_temp_dir;
193   EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir());
194   base::FilePath other_folder = other_folder_temp_dir.path();
195   base::FilePath other_shim_path = other_folder.Append(shim_base_name_);
196   info_->from_bookmark = true;
198   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_,
199                                                        info_.get());
200   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
201       .WillRepeatedly(Return(destination_dir_));
203   std::string expected_bundle_id = kFakeChromeBundleId;
204   expected_bundle_id += ".app.Profile-1-" + info_->extension_id;
206   EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
207       .WillOnce(Return(shim_path_));
209   EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
211   EXPECT_TRUE(base::DeleteFile(other_shim_path, true));
213   // The original shim should be recreated.
214   EXPECT_TRUE(shortcut_creator.UpdateShortcuts());
215   EXPECT_TRUE(base::PathExists(shim_path_));
216   EXPECT_FALSE(base::PathExists(other_shim_path.Append("Contents")));
219 TEST_F(WebAppShortcutCreatorTest, DeleteShortcuts) {
220   // When using PathService::Override, it calls base::MakeAbsoluteFilePath.
221   // On Mac this prepends "/private" to the path, but points to the same
222   // directory in the file system.
223   app_data_dir_ = base::MakeAbsoluteFilePath(app_data_dir_);
225   base::ScopedTempDir other_folder_temp_dir;
226   EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir());
227   base::FilePath other_folder = other_folder_temp_dir.path();
228   base::FilePath other_shim_path = other_folder.Append(shim_base_name_);
230   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_,
231                                                        info_.get());
232   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
233       .WillRepeatedly(Return(destination_dir_));
235   std::string expected_bundle_id = kFakeChromeBundleId;
236   expected_bundle_id += ".app.Profile-1-" + info_->extension_id;
237   EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id))
238       .WillOnce(Return(other_shim_path));
240   EXPECT_TRUE(shortcut_creator.CreateShortcuts(
241       SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
242   EXPECT_TRUE(base::PathExists(internal_shim_path_));
243   EXPECT_TRUE(base::PathExists(shim_path_));
245   // Create an extra shim in another folder. It should be deleted since its
246   // bundle id matches.
247   EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path));
248   EXPECT_TRUE(base::PathExists(other_shim_path));
250   // Change the user_data_dir of the shim at shim_path_. It should not be
251   // deleted since its user_data_dir does not match.
252   NSString* plist_path = base::mac::FilePathToNSString(
253       shim_path_.Append("Contents").Append("Info.plist"));
254   NSMutableDictionary* plist =
255       [NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
256   [plist setObject:@"fake_user_data_dir"
257             forKey:app_mode::kCrAppModeUserDataDirKey];
258   [plist writeToFile:plist_path
259           atomically:YES];
261   EXPECT_TRUE(PathService::Override(chrome::DIR_USER_DATA, app_data_dir_));
262   shortcut_creator.DeleteShortcuts();
263   EXPECT_FALSE(base::PathExists(internal_shim_path_));
264   EXPECT_TRUE(base::PathExists(shim_path_));
265   EXPECT_FALSE(base::PathExists(other_shim_path));
268 TEST_F(WebAppShortcutCreatorTest, CreateAppListShortcut) {
269   // With an empty |profile_name|, the shortcut path should not have the profile
270   // directory prepended to the extension id on the app bundle name.
271   info_->profile_name.clear();
272   base::FilePath dst_path =
273       destination_dir_.Append(info_->extension_id + ".app");
275   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(base::FilePath(),
276                                                        info_.get());
277   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
278       .WillRepeatedly(Return(destination_dir_));
279   EXPECT_EQ(dst_path.BaseName(), shortcut_creator.GetShortcutBasename());
282 TEST_F(WebAppShortcutCreatorTest, RunShortcut) {
283   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_,
284                                                        info_.get());
285   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
286       .WillRepeatedly(Return(destination_dir_));
288   EXPECT_TRUE(shortcut_creator.CreateShortcuts(
289       SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
290   EXPECT_TRUE(base::PathExists(shim_path_));
292   ssize_t status = getxattr(
293       shim_path_.value().c_str(), "com.apple.quarantine", NULL, 0, 0, 0);
294   EXPECT_EQ(-1, status);
295   EXPECT_EQ(ENOATTR, errno);
298 TEST_F(WebAppShortcutCreatorTest, CreateFailure) {
299   base::FilePath non_existent_path =
300       destination_dir_.Append("not-existent").Append("name.app");
302   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_,
303                                                        info_.get());
304   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
305       .WillRepeatedly(Return(non_existent_path));
306   EXPECT_FALSE(shortcut_creator.CreateShortcuts(
307       SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
310 TEST_F(WebAppShortcutCreatorTest, UpdateIcon) {
311   gfx::Image product_logo =
312       ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
313           IDR_PRODUCT_LOGO_32);
314   info_->favicon.Add(product_logo);
315   WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_.get());
317   ASSERT_TRUE(shortcut_creator.UpdateIcon(shim_path_));
318   base::FilePath icon_path =
319       shim_path_.Append("Contents").Append("Resources").Append("app.icns");
321   base::scoped_nsobject<NSImage> image([[NSImage alloc]
322       initWithContentsOfFile:base::mac::FilePathToNSString(icon_path)]);
323   EXPECT_TRUE(image);
324   EXPECT_EQ(product_logo.Width(), [image size].width);
325   EXPECT_EQ(product_logo.Height(), [image size].height);
328 TEST_F(WebAppShortcutCreatorTest, RevealAppShimInFinder) {
329   WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_.get());
330   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
331       .WillRepeatedly(Return(destination_dir_));
333   EXPECT_CALL(shortcut_creator, RevealAppShimInFinder())
334       .Times(0);
335   EXPECT_TRUE(shortcut_creator.CreateShortcuts(
336       SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
338   EXPECT_CALL(shortcut_creator, RevealAppShimInFinder());
339   EXPECT_TRUE(shortcut_creator.CreateShortcuts(
340       SHORTCUT_CREATION_BY_USER, web_app::ShortcutLocations()));
343 TEST_F(WebAppShortcutCreatorTest, FileHandlers) {
344   base::CommandLine::ForCurrentProcess()->AppendSwitch(
345       switches::kEnableAppsFileAssociations);
346   extensions::FileHandlersInfo file_handlers_info;
347   extensions::FileHandlerInfo handler_0;
348   handler_0.extensions.insert("ext0");
349   handler_0.extensions.insert("ext1");
350   handler_0.types.insert("type0");
351   handler_0.types.insert("type1");
352   file_handlers_info.push_back(handler_0);
353   extensions::FileHandlerInfo handler_1;
354   handler_1.extensions.insert("ext2");
355   handler_1.types.insert("type2");
356   file_handlers_info.push_back(handler_1);
358   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(
359       app_data_dir_, info_.get(), file_handlers_info);
360   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
361       .WillRepeatedly(Return(destination_dir_));
362   EXPECT_TRUE(shortcut_creator.CreateShortcuts(
363       SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations()));
365   base::FilePath plist_path =
366       shim_path_.Append("Contents").Append("Info.plist");
367   NSDictionary* plist = [NSDictionary
368       dictionaryWithContentsOfFile:base::mac::FilePathToNSString(plist_path)];
369   NSArray* file_handlers =
370       [plist objectForKey:app_mode::kCFBundleDocumentTypesKey];
372   NSDictionary* file_handler_0 = [file_handlers objectAtIndex:0];
373   EXPECT_NSEQ(app_mode::kBundleTypeRoleViewer,
374               [file_handler_0 objectForKey:app_mode::kCFBundleTypeRoleKey]);
375   NSArray* file_handler_0_extensions =
376       [file_handler_0 objectForKey:app_mode::kCFBundleTypeExtensionsKey];
377   EXPECT_TRUE([file_handler_0_extensions containsObject:@"ext0"]);
378   EXPECT_TRUE([file_handler_0_extensions containsObject:@"ext1"]);
379   NSArray* file_handler_0_types =
380       [file_handler_0 objectForKey:app_mode::kCFBundleTypeMIMETypesKey];
381   EXPECT_TRUE([file_handler_0_types containsObject:@"type0"]);
382   EXPECT_TRUE([file_handler_0_types containsObject:@"type1"]);
384   NSDictionary* file_handler_1 = [file_handlers objectAtIndex:1];
385   EXPECT_NSEQ(app_mode::kBundleTypeRoleViewer,
386               [file_handler_1 objectForKey:app_mode::kCFBundleTypeRoleKey]);
387   NSArray* file_handler_1_extensions =
388       [file_handler_1 objectForKey:app_mode::kCFBundleTypeExtensionsKey];
389   EXPECT_TRUE([file_handler_1_extensions containsObject:@"ext2"]);
390   NSArray* file_handler_1_types =
391       [file_handler_1 objectForKey:app_mode::kCFBundleTypeMIMETypesKey];
392   EXPECT_TRUE([file_handler_1_types containsObject:@"type2"]);
395 }  // namespace web_app