// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "chrome/browser/web_applications/web_app_mac.h" #import #include #include #include "base/command_line.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_nsobject.h" #include "base/path_service.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #import "chrome/common/mac/app_mode_common.h" #include "grit/theme_resources.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #import "testing/gtest_mac.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image.h" using ::testing::_; using ::testing::Return; using ::testing::NiceMock; namespace { const char kFakeChromeBundleId[] = "fake.cfbundleidentifier"; class WebAppShortcutCreatorMock : public web_app::WebAppShortcutCreator { public: WebAppShortcutCreatorMock(const base::FilePath& app_data_dir, const web_app::ShortcutInfo& shortcut_info) : WebAppShortcutCreator(app_data_dir, shortcut_info, extensions::FileHandlersInfo()) {} WebAppShortcutCreatorMock( const base::FilePath& app_data_dir, const web_app::ShortcutInfo& shortcut_info, const extensions::FileHandlersInfo& file_handlers_info) : WebAppShortcutCreator(app_data_dir, shortcut_info, file_handlers_info) { } MOCK_CONST_METHOD0(GetApplicationsDirname, base::FilePath()); MOCK_CONST_METHOD1(GetAppBundleById, base::FilePath(const std::string& bundle_id)); MOCK_CONST_METHOD0(RevealAppShimInFinder, void()); private: DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorMock); }; web_app::ShortcutInfo GetShortcutInfo() { web_app::ShortcutInfo info; info.extension_id = "extensionid"; info.extension_path = base::FilePath("/fake/extension/path"); info.title = base::ASCIIToUTF16("Shortcut Title"); info.url = GURL("http://example.com/"); info.profile_path = base::FilePath("user_data_dir").Append("Profile 1"); info.profile_name = "profile name"; return info; } class WebAppShortcutCreatorTest : public testing::Test { protected: WebAppShortcutCreatorTest() {} virtual void SetUp() { base::mac::SetBaseBundleID(kFakeChromeBundleId); EXPECT_TRUE(temp_app_data_dir_.CreateUniqueTempDir()); EXPECT_TRUE(temp_destination_dir_.CreateUniqueTempDir()); app_data_dir_ = temp_app_data_dir_.path(); destination_dir_ = temp_destination_dir_.path(); info_ = GetShortcutInfo(); shim_base_name_ = base::FilePath( info_.profile_path.BaseName().value() + " " + info_.extension_id + ".app"); internal_shim_path_ = app_data_dir_.Append(shim_base_name_); shim_path_ = destination_dir_.Append(shim_base_name_); } base::ScopedTempDir temp_app_data_dir_; base::ScopedTempDir temp_destination_dir_; base::FilePath app_data_dir_; base::FilePath destination_dir_; web_app::ShortcutInfo info_; base::FilePath shim_base_name_; base::FilePath internal_shim_path_; base::FilePath shim_path_; private: DISALLOW_COPY_AND_ASSIGN(WebAppShortcutCreatorTest); }; } // namespace namespace web_app { TEST_F(WebAppShortcutCreatorTest, CreateShortcuts) { NiceMock shortcut_creator(app_data_dir_, info_); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(destination_dir_)); EXPECT_TRUE(shortcut_creator.CreateShortcuts( SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations())); EXPECT_TRUE(base::PathExists(shim_path_)); EXPECT_TRUE(base::PathExists(destination_dir_)); EXPECT_EQ(shim_base_name_, shortcut_creator.GetShortcutBasename()); base::FilePath plist_path = shim_path_.Append("Contents").Append("Info.plist"); NSDictionary* plist = [NSDictionary dictionaryWithContentsOfFile: base::mac::FilePathToNSString(plist_path)]; EXPECT_NSEQ(base::SysUTF8ToNSString(info_.extension_id), [plist objectForKey:app_mode::kCrAppModeShortcutIDKey]); EXPECT_NSEQ(base::SysUTF16ToNSString(info_.title), [plist objectForKey:app_mode::kCrAppModeShortcutNameKey]); EXPECT_NSEQ(base::SysUTF8ToNSString(info_.url.spec()), [plist objectForKey:app_mode::kCrAppModeShortcutURLKey]); // Make sure all values in the plist are actually filled in. for (id key in plist) { id value = [plist valueForKey:key]; if (!base::mac::ObjCCast(value)) continue; EXPECT_EQ([value rangeOfString:@"@APP_"].location, NSNotFound) << [key UTF8String] << ":" << [value UTF8String]; } } TEST_F(WebAppShortcutCreatorTest, UpdateShortcuts) { base::ScopedTempDir other_folder_temp_dir; EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir()); base::FilePath other_folder = other_folder_temp_dir.path(); base::FilePath other_shim_path = other_folder.Append(shim_base_name_); NiceMock shortcut_creator(app_data_dir_, info_); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(destination_dir_)); std::string expected_bundle_id = kFakeChromeBundleId; expected_bundle_id += ".app.Profile-1-" + info_.extension_id; EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id)) .WillOnce(Return(other_shim_path)); EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path)); EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true)); EXPECT_TRUE(shortcut_creator.UpdateShortcuts()); EXPECT_FALSE(base::PathExists(shim_path_)); EXPECT_TRUE(base::PathExists(other_shim_path.Append("Contents"))); // Also test case where GetAppBundleById fails. EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id)) .WillOnce(Return(base::FilePath())); EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path)); EXPECT_TRUE(base::DeleteFile(other_shim_path.Append("Contents"), true)); EXPECT_FALSE(shortcut_creator.UpdateShortcuts()); EXPECT_FALSE(base::PathExists(shim_path_)); EXPECT_FALSE(base::PathExists(other_shim_path.Append("Contents"))); } TEST_F(WebAppShortcutCreatorTest, DeleteShortcuts) { // When using PathService::Override, it calls base::MakeAbsoluteFilePath. // On Mac this prepends "/private" to the path, but points to the same // directory in the file system. app_data_dir_ = base::MakeAbsoluteFilePath(app_data_dir_); base::ScopedTempDir other_folder_temp_dir; EXPECT_TRUE(other_folder_temp_dir.CreateUniqueTempDir()); base::FilePath other_folder = other_folder_temp_dir.path(); base::FilePath other_shim_path = other_folder.Append(shim_base_name_); NiceMock shortcut_creator(app_data_dir_, info_); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(destination_dir_)); std::string expected_bundle_id = kFakeChromeBundleId; expected_bundle_id += ".app.Profile-1-" + info_.extension_id; EXPECT_CALL(shortcut_creator, GetAppBundleById(expected_bundle_id)) .WillOnce(Return(other_shim_path)); EXPECT_TRUE(shortcut_creator.CreateShortcuts( SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations())); EXPECT_TRUE(base::PathExists(internal_shim_path_)); EXPECT_TRUE(base::PathExists(shim_path_)); // Create an extra shim in another folder. It should be deleted since its // bundle id matches. EXPECT_TRUE(shortcut_creator.BuildShortcut(other_shim_path)); EXPECT_TRUE(base::PathExists(other_shim_path)); // Change the user_data_dir of the shim at shim_path_. It should not be // deleted since its user_data_dir does not match. NSString* plist_path = base::mac::FilePathToNSString( shim_path_.Append("Contents").Append("Info.plist")); NSMutableDictionary* plist = [NSDictionary dictionaryWithContentsOfFile:plist_path]; [plist setObject:@"fake_user_data_dir" forKey:app_mode::kCrAppModeUserDataDirKey]; [plist writeToFile:plist_path atomically:YES]; EXPECT_TRUE(PathService::Override(chrome::DIR_USER_DATA, app_data_dir_)); shortcut_creator.DeleteShortcuts(); EXPECT_FALSE(base::PathExists(internal_shim_path_)); EXPECT_TRUE(base::PathExists(shim_path_)); EXPECT_FALSE(base::PathExists(other_shim_path)); } TEST_F(WebAppShortcutCreatorTest, CreateAppListShortcut) { // With an empty |profile_name|, the shortcut path should not have the profile // directory prepended to the extension id on the app bundle name. info_.profile_name.clear(); base::FilePath dst_path = destination_dir_.Append(info_.extension_id + ".app"); NiceMock shortcut_creator(base::FilePath(), info_); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(destination_dir_)); EXPECT_EQ(dst_path.BaseName(), shortcut_creator.GetShortcutBasename()); } TEST_F(WebAppShortcutCreatorTest, RunShortcut) { NiceMock shortcut_creator(app_data_dir_, info_); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(destination_dir_)); EXPECT_TRUE(shortcut_creator.CreateShortcuts( SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations())); EXPECT_TRUE(base::PathExists(shim_path_)); ssize_t status = getxattr( shim_path_.value().c_str(), "com.apple.quarantine", NULL, 0, 0, 0); EXPECT_EQ(-1, status); EXPECT_EQ(ENOATTR, errno); } TEST_F(WebAppShortcutCreatorTest, CreateFailure) { base::FilePath non_existent_path = destination_dir_.Append("not-existent").Append("name.app"); NiceMock shortcut_creator(app_data_dir_, info_); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(non_existent_path)); EXPECT_FALSE(shortcut_creator.CreateShortcuts( SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations())); } TEST_F(WebAppShortcutCreatorTest, UpdateIcon) { gfx::Image product_logo = ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( IDR_PRODUCT_LOGO_32); info_.favicon.Add(product_logo); WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_); ASSERT_TRUE(shortcut_creator.UpdateIcon(shim_path_)); base::FilePath icon_path = shim_path_.Append("Contents").Append("Resources").Append("app.icns"); base::scoped_nsobject image([[NSImage alloc] initWithContentsOfFile:base::mac::FilePathToNSString(icon_path)]); EXPECT_TRUE(image); EXPECT_EQ(product_logo.Width(), [image size].width); EXPECT_EQ(product_logo.Height(), [image size].height); } TEST_F(WebAppShortcutCreatorTest, RevealAppShimInFinder) { WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(destination_dir_)); EXPECT_CALL(shortcut_creator, RevealAppShimInFinder()) .Times(0); EXPECT_TRUE(shortcut_creator.CreateShortcuts( SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations())); EXPECT_CALL(shortcut_creator, RevealAppShimInFinder()); EXPECT_TRUE(shortcut_creator.CreateShortcuts( SHORTCUT_CREATION_BY_USER, web_app::ShortcutLocations())); } TEST_F(WebAppShortcutCreatorTest, FileHandlers) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableAppsFileAssociations); extensions::FileHandlersInfo file_handlers_info; extensions::FileHandlerInfo handler_0; handler_0.extensions.insert("ext0"); handler_0.extensions.insert("ext1"); handler_0.types.insert("type0"); handler_0.types.insert("type1"); file_handlers_info.push_back(handler_0); extensions::FileHandlerInfo handler_1; handler_1.extensions.insert("ext2"); handler_1.types.insert("type2"); file_handlers_info.push_back(handler_1); NiceMock shortcut_creator( app_data_dir_, info_, file_handlers_info); EXPECT_CALL(shortcut_creator, GetApplicationsDirname()) .WillRepeatedly(Return(destination_dir_)); EXPECT_TRUE(shortcut_creator.CreateShortcuts( SHORTCUT_CREATION_AUTOMATED, web_app::ShortcutLocations())); base::FilePath plist_path = shim_path_.Append("Contents").Append("Info.plist"); NSDictionary* plist = [NSDictionary dictionaryWithContentsOfFile:base::mac::FilePathToNSString(plist_path)]; NSArray* file_handlers = [plist objectForKey:app_mode::kCFBundleDocumentTypesKey]; NSDictionary* file_handler_0 = [file_handlers objectAtIndex:0]; EXPECT_NSEQ(app_mode::kBundleTypeRoleViewer, [file_handler_0 objectForKey:app_mode::kCFBundleTypeRoleKey]); NSArray* file_handler_0_extensions = [file_handler_0 objectForKey:app_mode::kCFBundleTypeExtensionsKey]; EXPECT_TRUE([file_handler_0_extensions containsObject:@"ext0"]); EXPECT_TRUE([file_handler_0_extensions containsObject:@"ext1"]); NSArray* file_handler_0_types = [file_handler_0 objectForKey:app_mode::kCFBundleTypeMIMETypesKey]; EXPECT_TRUE([file_handler_0_types containsObject:@"type0"]); EXPECT_TRUE([file_handler_0_types containsObject:@"type1"]); NSDictionary* file_handler_1 = [file_handlers objectAtIndex:1]; EXPECT_NSEQ(app_mode::kBundleTypeRoleViewer, [file_handler_1 objectForKey:app_mode::kCFBundleTypeRoleKey]); NSArray* file_handler_1_extensions = [file_handler_1 objectForKey:app_mode::kCFBundleTypeExtensionsKey]; EXPECT_TRUE([file_handler_1_extensions containsObject:@"ext2"]); NSArray* file_handler_1_types = [file_handler_1 objectForKey:app_mode::kCFBundleTypeMIMETypesKey]; EXPECT_TRUE([file_handler_1_types containsObject:@"type2"]); } } // namespace web_app