1 // Copyright 2013 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. 4 5 #include "apps/app_shim/extension_app_shim_handler_mac.h" 6 7 #include <vector> 8 9 #include "apps/app_shim/app_shim_host_mac.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "chrome/browser/chrome_notification_types.h" 12 #include "chrome/test/base/testing_profile.h" 13 #include "content/public/browser/notification_service.h" 14 #include "extensions/common/extension.h" 15 #include "testing/gmock/include/gmock/gmock.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 18 namespace apps { 19 20 using extensions::Extension; 21 typedef AppWindowRegistry::AppWindowList AppWindowList; 22 23 using ::testing::_; 24 using ::testing::Invoke; 25 using ::testing::Return; 26 using ::testing::WithArgs; 27 28 class MockDelegate : public ExtensionAppShimHandler::Delegate { 29 public: 30 virtual ~MockDelegate() {} 31 32 MOCK_METHOD1(ProfileExistsForPath, bool(const base::FilePath&)); 33 MOCK_METHOD1(ProfileForPath, Profile*(const base::FilePath&)); 34 MOCK_METHOD2(LoadProfileAsync, 35 void(const base::FilePath&, 36 base::Callback<void(Profile*)>)); 37 38 MOCK_METHOD2(GetWindows, AppWindowList(Profile*, const std::string&)); 39 40 MOCK_METHOD2(GetAppExtension, const Extension*(Profile*, const std::string&)); 41 MOCK_METHOD3(EnableExtension, void(Profile*, 42 const std::string&, 43 const base::Callback<void()>&)); 44 MOCK_METHOD3(LaunchApp, 45 void(Profile*, 46 const Extension*, 47 const std::vector<base::FilePath>&)); 48 MOCK_METHOD2(LaunchShim, void(Profile*, const Extension*)); 49 50 MOCK_METHOD0(MaybeTerminate, void()); 51 52 void CaptureLoadProfileCallback( 53 const base::FilePath& path, 54 base::Callback<void(Profile*)> callback) { 55 callbacks_[path] = callback; 56 } 57 58 bool RunLoadProfileCallback( 59 const base::FilePath& path, 60 Profile* profile) { 61 callbacks_[path].Run(profile); 62 return callbacks_.erase(path); 63 } 64 65 void RunCallback(const base::Callback<void()>& callback) { 66 callback.Run(); 67 } 68 69 private: 70 std::map<base::FilePath, 71 base::Callback<void(Profile*)> > callbacks_; 72 }; 73 74 class TestingExtensionAppShimHandler : public ExtensionAppShimHandler { 75 public: 76 TestingExtensionAppShimHandler(Delegate* delegate) { 77 set_delegate(delegate); 78 } 79 virtual ~TestingExtensionAppShimHandler() {} 80 81 MOCK_METHOD3(OnShimFocus, 82 void(Host* host, 83 AppShimFocusType, 84 const std::vector<base::FilePath>& files)); 85 86 void RealOnShimFocus(Host* host, 87 AppShimFocusType focus_type, 88 const std::vector<base::FilePath>& files) { 89 ExtensionAppShimHandler::OnShimFocus(host, focus_type, files); 90 } 91 92 AppShimHandler::Host* FindHost(Profile* profile, 93 const std::string& app_id) { 94 HostMap::const_iterator it = hosts().find(make_pair(profile, app_id)); 95 return it == hosts().end() ? NULL : it->second; 96 } 97 98 content::NotificationRegistrar& GetRegistrar() { return registrar(); } 99 100 private: 101 DISALLOW_COPY_AND_ASSIGN(TestingExtensionAppShimHandler); 102 }; 103 104 const char kTestAppIdA[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 105 const char kTestAppIdB[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 106 107 class FakeHost : public apps::AppShimHandler::Host { 108 public: 109 FakeHost(const base::FilePath& profile_path, 110 const std::string& app_id, 111 TestingExtensionAppShimHandler* handler) 112 : profile_path_(profile_path), 113 app_id_(app_id), 114 handler_(handler), 115 close_count_(0) {} 116 117 MOCK_METHOD1(OnAppLaunchComplete, void(AppShimLaunchResult)); 118 119 virtual void OnAppClosed() OVERRIDE { 120 handler_->OnShimClose(this); 121 ++close_count_; 122 } 123 virtual void OnAppHide() OVERRIDE {} 124 virtual void OnAppRequestUserAttention() OVERRIDE {} 125 virtual base::FilePath GetProfilePath() const OVERRIDE { 126 return profile_path_; 127 } 128 virtual std::string GetAppId() const OVERRIDE { return app_id_; } 129 130 int close_count() { return close_count_; } 131 132 private: 133 base::FilePath profile_path_; 134 std::string app_id_; 135 TestingExtensionAppShimHandler* handler_; 136 int close_count_; 137 138 DISALLOW_COPY_AND_ASSIGN(FakeHost); 139 }; 140 141 class ExtensionAppShimHandlerTest : public testing::Test { 142 protected: 143 ExtensionAppShimHandlerTest() 144 : delegate_(new MockDelegate), 145 handler_(new TestingExtensionAppShimHandler(delegate_)), 146 profile_path_a_("Profile A"), 147 profile_path_b_("Profile B"), 148 host_aa_(profile_path_a_, kTestAppIdA, handler_.get()), 149 host_ab_(profile_path_a_, kTestAppIdB, handler_.get()), 150 host_bb_(profile_path_b_, kTestAppIdB, handler_.get()), 151 host_aa_duplicate_(profile_path_a_, kTestAppIdA, handler_.get()) { 152 base::FilePath extension_path("/fake/path"); 153 base::DictionaryValue manifest; 154 manifest.SetString("name", "Fake Name"); 155 manifest.SetString("version", "1"); 156 std::string error; 157 extension_a_ = Extension::Create( 158 extension_path, extensions::Manifest::INTERNAL, manifest, 159 Extension::NO_FLAGS, kTestAppIdA, &error); 160 EXPECT_TRUE(extension_a_.get()) << error; 161 162 extension_b_ = Extension::Create( 163 extension_path, extensions::Manifest::INTERNAL, manifest, 164 Extension::NO_FLAGS, kTestAppIdB, &error); 165 EXPECT_TRUE(extension_b_.get()) << error; 166 167 EXPECT_CALL(*delegate_, ProfileExistsForPath(profile_path_a_)) 168 .WillRepeatedly(Return(true)); 169 EXPECT_CALL(*delegate_, ProfileForPath(profile_path_a_)) 170 .WillRepeatedly(Return(&profile_a_)); 171 EXPECT_CALL(*delegate_, ProfileExistsForPath(profile_path_b_)) 172 .WillRepeatedly(Return(true)); 173 EXPECT_CALL(*delegate_, ProfileForPath(profile_path_b_)) 174 .WillRepeatedly(Return(&profile_b_)); 175 176 // In most tests, we don't care about the result of GetWindows, it just 177 // needs to be non-empty. 178 AppWindowList app_window_list; 179 app_window_list.push_back(static_cast<AppWindow*>(NULL)); 180 EXPECT_CALL(*delegate_, GetWindows(_, _)) 181 .WillRepeatedly(Return(app_window_list)); 182 183 EXPECT_CALL(*delegate_, GetAppExtension(_, kTestAppIdA)) 184 .WillRepeatedly(Return(extension_a_.get())); 185 EXPECT_CALL(*delegate_, GetAppExtension(_, kTestAppIdB)) 186 .WillRepeatedly(Return(extension_b_.get())); 187 EXPECT_CALL(*delegate_, LaunchApp(_, _, _)) 188 .WillRepeatedly(Return()); 189 } 190 191 void NormalLaunch(AppShimHandler::Host* host) { 192 handler_->OnShimLaunch(host, 193 APP_SHIM_LAUNCH_NORMAL, 194 std::vector<base::FilePath>()); 195 } 196 197 void RegisterOnlyLaunch(AppShimHandler::Host* host) { 198 handler_->OnShimLaunch(host, 199 APP_SHIM_LAUNCH_REGISTER_ONLY, 200 std::vector<base::FilePath>()); 201 } 202 203 MockDelegate* delegate_; 204 scoped_ptr<TestingExtensionAppShimHandler> handler_; 205 base::FilePath profile_path_a_; 206 base::FilePath profile_path_b_; 207 TestingProfile profile_a_; 208 TestingProfile profile_b_; 209 FakeHost host_aa_; 210 FakeHost host_ab_; 211 FakeHost host_bb_; 212 FakeHost host_aa_duplicate_; 213 scoped_refptr<Extension> extension_a_; 214 scoped_refptr<Extension> extension_b_; 215 216 private: 217 DISALLOW_COPY_AND_ASSIGN(ExtensionAppShimHandlerTest); 218 }; 219 220 TEST_F(ExtensionAppShimHandlerTest, LaunchProfileNotFound) { 221 // Bad profile path. 222 EXPECT_CALL(*delegate_, ProfileExistsForPath(profile_path_a_)) 223 .WillOnce(Return(false)) 224 .WillRepeatedly(Return(true)); 225 EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND)); 226 NormalLaunch(&host_aa_); 227 } 228 229 TEST_F(ExtensionAppShimHandlerTest, LaunchAppNotFound) { 230 // App not found. 231 EXPECT_CALL(*delegate_, GetAppExtension(&profile_a_, kTestAppIdA)) 232 .WillRepeatedly(Return(static_cast<const Extension*>(NULL))); 233 EXPECT_CALL(*delegate_, EnableExtension(&profile_a_, kTestAppIdA, _)) 234 .WillOnce(WithArgs<2>(Invoke(delegate_, &MockDelegate::RunCallback))); 235 EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND)); 236 NormalLaunch(&host_aa_); 237 } 238 239 TEST_F(ExtensionAppShimHandlerTest, LaunchAppNotEnabled) { 240 // App not found. 241 EXPECT_CALL(*delegate_, GetAppExtension(&profile_a_, kTestAppIdA)) 242 .WillOnce(Return(static_cast<const Extension*>(NULL))) 243 .WillRepeatedly(Return(extension_a_.get())); 244 EXPECT_CALL(*delegate_, EnableExtension(&profile_a_, kTestAppIdA, _)) 245 .WillOnce(WithArgs<2>(Invoke(delegate_, &MockDelegate::RunCallback))); 246 NormalLaunch(&host_aa_); 247 } 248 249 TEST_F(ExtensionAppShimHandlerTest, LaunchAndCloseShim) { 250 // Normal startup. 251 NormalLaunch(&host_aa_); 252 EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); 253 254 NormalLaunch(&host_ab_); 255 EXPECT_EQ(&host_ab_, handler_->FindHost(&profile_a_, kTestAppIdB)); 256 257 std::vector<base::FilePath> some_file(1, base::FilePath("some_file")); 258 EXPECT_CALL(*delegate_, 259 LaunchApp(&profile_b_, extension_b_.get(), some_file)); 260 handler_->OnShimLaunch(&host_bb_, APP_SHIM_LAUNCH_NORMAL, some_file); 261 EXPECT_EQ(&host_bb_, handler_->FindHost(&profile_b_, kTestAppIdB)); 262 263 // Activation when there is a registered shim finishes launch with success and 264 // focuses the app. 265 EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); 266 EXPECT_CALL(*handler_, OnShimFocus(&host_aa_, APP_SHIM_FOCUS_NORMAL, _)); 267 handler_->OnAppActivated(&profile_a_, kTestAppIdA); 268 269 // Starting and closing a second host just focuses the app. 270 EXPECT_CALL(*handler_, OnShimFocus(&host_aa_duplicate_, 271 APP_SHIM_FOCUS_REOPEN, 272 some_file)); 273 EXPECT_CALL(host_aa_duplicate_, 274 OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST)); 275 handler_->OnShimLaunch(&host_aa_duplicate_, 276 APP_SHIM_LAUNCH_NORMAL, 277 some_file); 278 EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); 279 handler_->OnShimClose(&host_aa_duplicate_); 280 EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); 281 282 // Normal close. 283 handler_->OnShimClose(&host_aa_); 284 EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); 285 286 // Closing the second host afterward does nothing. 287 handler_->OnShimClose(&host_aa_duplicate_); 288 EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); 289 } 290 291 TEST_F(ExtensionAppShimHandlerTest, AppLifetime) { 292 // When the app activates, if there is no shim, start one. 293 EXPECT_CALL(*delegate_, LaunchShim(&profile_a_, extension_a_.get())); 294 handler_->OnAppActivated(&profile_a_, kTestAppIdA); 295 296 // Normal shim launch adds an entry in the map. 297 // App should not be launched here, but return success to the shim. 298 EXPECT_CALL(*delegate_, 299 LaunchApp(&profile_a_, extension_a_.get(), _)) 300 .Times(0); 301 EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); 302 RegisterOnlyLaunch(&host_aa_); 303 EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); 304 305 // Return no app windows for OnShimFocus and OnShimQuit. 306 AppWindowList app_window_list; 307 EXPECT_CALL(*delegate_, GetWindows(&profile_a_, kTestAppIdA)) 308 .WillRepeatedly(Return(app_window_list)); 309 310 // Non-reopen focus does nothing. 311 EXPECT_CALL(*handler_, OnShimFocus(&host_aa_, APP_SHIM_FOCUS_NORMAL, _)) 312 .WillOnce(Invoke(handler_.get(), 313 &TestingExtensionAppShimHandler::RealOnShimFocus)); 314 EXPECT_CALL(*delegate_, 315 LaunchApp(&profile_a_, extension_a_.get(), _)) 316 .Times(0); 317 handler_->OnShimFocus(&host_aa_, 318 APP_SHIM_FOCUS_NORMAL, 319 std::vector<base::FilePath>()); 320 321 // Reopen focus launches the app. 322 EXPECT_CALL(*handler_, OnShimFocus(&host_aa_, APP_SHIM_FOCUS_REOPEN, _)) 323 .WillOnce(Invoke(handler_.get(), 324 &TestingExtensionAppShimHandler::RealOnShimFocus)); 325 std::vector<base::FilePath> some_file(1, base::FilePath("some_file")); 326 EXPECT_CALL(*delegate_, 327 LaunchApp(&profile_a_, extension_a_.get(), some_file)); 328 handler_->OnShimFocus(&host_aa_, APP_SHIM_FOCUS_REOPEN, some_file); 329 330 // Quit just closes all the windows. This tests that it doesn't terminate, 331 // but we expect closing all windows triggers a OnAppDeactivated from 332 // AppLifetimeMonitor. 333 handler_->OnShimQuit(&host_aa_); 334 335 // Closing all windows closes the shim and checks if Chrome should be 336 // terminated. 337 EXPECT_CALL(*delegate_, MaybeTerminate()) 338 .WillOnce(Return()); 339 handler_->OnAppDeactivated(&profile_a_, kTestAppIdA); 340 EXPECT_EQ(1, host_aa_.close_count()); 341 } 342 343 TEST_F(ExtensionAppShimHandlerTest, MaybeTerminate) { 344 // Launch shims, adding entries in the map. 345 EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); 346 RegisterOnlyLaunch(&host_aa_); 347 EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); 348 349 EXPECT_CALL(host_ab_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); 350 RegisterOnlyLaunch(&host_ab_); 351 EXPECT_EQ(&host_ab_, handler_->FindHost(&profile_a_, kTestAppIdB)); 352 353 // Return empty window list. 354 AppWindowList app_window_list; 355 EXPECT_CALL(*delegate_, GetWindows(_, _)) 356 .WillRepeatedly(Return(app_window_list)); 357 358 // Quitting when there's another shim should not terminate. 359 EXPECT_CALL(*delegate_, MaybeTerminate()) 360 .Times(0); 361 handler_->OnAppDeactivated(&profile_a_, kTestAppIdA); 362 363 // Quitting when it's the last shim should terminate. 364 EXPECT_CALL(*delegate_, MaybeTerminate()); 365 handler_->OnAppDeactivated(&profile_a_, kTestAppIdB); 366 } 367 368 TEST_F(ExtensionAppShimHandlerTest, RegisterOnly) { 369 // For an APP_SHIM_LAUNCH_REGISTER_ONLY, don't launch the app. 370 EXPECT_CALL(*delegate_, LaunchApp(_, _, _)) 371 .Times(0); 372 EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); 373 RegisterOnlyLaunch(&host_aa_); 374 EXPECT_TRUE(handler_->FindHost(&profile_a_, kTestAppIdA)); 375 376 // Close the shim, removing the entry in the map. 377 handler_->OnShimClose(&host_aa_); 378 EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); 379 } 380 381 TEST_F(ExtensionAppShimHandlerTest, LoadProfile) { 382 // If the profile is not loaded when an OnShimLaunch arrives, return false 383 // and load the profile asynchronously. Launch the app when the profile is 384 // ready. 385 EXPECT_CALL(*delegate_, ProfileForPath(profile_path_a_)) 386 .WillOnce(Return(static_cast<Profile*>(NULL))) 387 .WillRepeatedly(Return(&profile_a_)); 388 EXPECT_CALL(*delegate_, LoadProfileAsync(profile_path_a_, _)) 389 .WillOnce(Invoke(delegate_, &MockDelegate::CaptureLoadProfileCallback)); 390 NormalLaunch(&host_aa_); 391 EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); 392 delegate_->RunLoadProfileCallback(profile_path_a_, &profile_a_); 393 EXPECT_TRUE(handler_->FindHost(&profile_a_, kTestAppIdA)); 394 } 395 396 } // namespace apps 397