// Copyright 2013 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. #include "apps/app_shim/extension_app_shim_handler_mac.h" #include #include "apps/app_shim/app_shim_host_mac.h" #include "base/memory/scoped_ptr.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/common/extensions/extension.h" #include "chrome/test/base/testing_profile.h" #include "content/public/browser/notification_service.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace apps { using extensions::Extension; typedef ShellWindowRegistry::ShellWindowList ShellWindowList; using ::testing::_; using ::testing::Invoke; using ::testing::Return; class MockDelegate : public ExtensionAppShimHandler::Delegate { public: virtual ~MockDelegate() {} MOCK_METHOD1(ProfileExistsForPath, bool(const base::FilePath&)); MOCK_METHOD1(ProfileForPath, Profile*(const base::FilePath&)); MOCK_METHOD2(LoadProfileAsync, void(const base::FilePath&, base::Callback)); MOCK_METHOD2(GetWindows, ShellWindowList(Profile*, const std::string&)); MOCK_METHOD2(GetAppExtension, const Extension*(Profile*, const std::string&)); MOCK_METHOD3(LaunchApp, void(Profile*, const Extension*, const std::vector&)); MOCK_METHOD2(LaunchShim, void(Profile*, const Extension*)); MOCK_METHOD0(MaybeTerminate, void()); void CaptureLoadProfileCallback( const base::FilePath& path, base::Callback callback) { callbacks_[path] = callback; } bool RunLoadProfileCallback( const base::FilePath& path, Profile* profile) { callbacks_[path].Run(profile); return callbacks_.erase(path); } private: std::map > callbacks_; }; class TestingExtensionAppShimHandler : public ExtensionAppShimHandler { public: TestingExtensionAppShimHandler(Delegate* delegate) { set_delegate(delegate); } virtual ~TestingExtensionAppShimHandler() {} MOCK_METHOD3(OnShimFocus, void(Host* host, AppShimFocusType, const std::vector& files)); void RealOnShimFocus(Host* host, AppShimFocusType focus_type, const std::vector& files) { ExtensionAppShimHandler::OnShimFocus(host, focus_type, files); } AppShimHandler::Host* FindHost(Profile* profile, const std::string& app_id) { HostMap::const_iterator it = hosts().find(make_pair(profile, app_id)); return it == hosts().end() ? NULL : it->second; } content::NotificationRegistrar& GetRegistrar() { return registrar(); } private: DISALLOW_COPY_AND_ASSIGN(TestingExtensionAppShimHandler); }; const char kTestAppIdA[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const char kTestAppIdB[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; class FakeHost : public apps::AppShimHandler::Host { public: FakeHost(const base::FilePath& profile_path, const std::string& app_id, TestingExtensionAppShimHandler* handler) : profile_path_(profile_path), app_id_(app_id), handler_(handler), close_count_(0) {} MOCK_METHOD1(OnAppLaunchComplete, void(AppShimLaunchResult)); virtual void OnAppClosed() OVERRIDE { handler_->OnShimClose(this); ++close_count_; } virtual void OnAppHide() OVERRIDE {} virtual void OnAppRequestUserAttention() OVERRIDE {} virtual base::FilePath GetProfilePath() const OVERRIDE { return profile_path_; } virtual std::string GetAppId() const OVERRIDE { return app_id_; } int close_count() { return close_count_; } private: base::FilePath profile_path_; std::string app_id_; TestingExtensionAppShimHandler* handler_; int close_count_; DISALLOW_COPY_AND_ASSIGN(FakeHost); }; class ExtensionAppShimHandlerTest : public testing::Test { protected: ExtensionAppShimHandlerTest() : delegate_(new MockDelegate), handler_(new TestingExtensionAppShimHandler(delegate_)), profile_path_a_("Profile A"), profile_path_b_("Profile B"), host_aa_(profile_path_a_, kTestAppIdA, handler_.get()), host_ab_(profile_path_a_, kTestAppIdB, handler_.get()), host_bb_(profile_path_b_, kTestAppIdB, handler_.get()), host_aa_duplicate_(profile_path_a_, kTestAppIdA, handler_.get()) { base::FilePath extension_path("/fake/path"); base::DictionaryValue manifest; manifest.SetString("name", "Fake Name"); manifest.SetString("version", "1"); std::string error; extension_a_ = Extension::Create( extension_path, extensions::Manifest::INTERNAL, manifest, Extension::NO_FLAGS, kTestAppIdA, &error); EXPECT_TRUE(extension_a_.get()) << error; extension_b_ = Extension::Create( extension_path, extensions::Manifest::INTERNAL, manifest, Extension::NO_FLAGS, kTestAppIdB, &error); EXPECT_TRUE(extension_b_.get()) << error; EXPECT_CALL(*delegate_, ProfileExistsForPath(profile_path_a_)) .WillRepeatedly(Return(true)); EXPECT_CALL(*delegate_, ProfileForPath(profile_path_a_)) .WillRepeatedly(Return(&profile_a_)); EXPECT_CALL(*delegate_, ProfileExistsForPath(profile_path_b_)) .WillRepeatedly(Return(true)); EXPECT_CALL(*delegate_, ProfileForPath(profile_path_b_)) .WillRepeatedly(Return(&profile_b_)); // In most tests, we don't care about the result of GetWindows, it just // needs to be non-empty. ShellWindowList shell_window_list; shell_window_list.push_back(static_cast(NULL)); EXPECT_CALL(*delegate_, GetWindows(_, _)) .WillRepeatedly(Return(shell_window_list)); EXPECT_CALL(*delegate_, GetAppExtension(_, kTestAppIdA)) .WillRepeatedly(Return(extension_a_.get())); EXPECT_CALL(*delegate_, GetAppExtension(_, kTestAppIdB)) .WillRepeatedly(Return(extension_b_.get())); EXPECT_CALL(*delegate_, LaunchApp(_, _, _)) .WillRepeatedly(Return()); } void NormalLaunch(AppShimHandler::Host* host) { handler_->OnShimLaunch(host, APP_SHIM_LAUNCH_NORMAL, std::vector()); } void RegisterOnlyLaunch(AppShimHandler::Host* host) { handler_->OnShimLaunch(host, APP_SHIM_LAUNCH_REGISTER_ONLY, std::vector()); } MockDelegate* delegate_; scoped_ptr handler_; base::FilePath profile_path_a_; base::FilePath profile_path_b_; TestingProfile profile_a_; TestingProfile profile_b_; FakeHost host_aa_; FakeHost host_ab_; FakeHost host_bb_; FakeHost host_aa_duplicate_; scoped_refptr extension_a_; scoped_refptr extension_b_; private: DISALLOW_COPY_AND_ASSIGN(ExtensionAppShimHandlerTest); }; TEST_F(ExtensionAppShimHandlerTest, LaunchFailure) { // Bad profile path. EXPECT_CALL(*delegate_, ProfileExistsForPath(profile_path_a_)) .WillOnce(Return(false)) .WillRepeatedly(Return(true)); EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND)); NormalLaunch(&host_aa_); // App not found. EXPECT_CALL(*delegate_, GetAppExtension(&profile_a_, kTestAppIdA)) .WillOnce(Return(static_cast(NULL))) .WillRepeatedly(Return(extension_a_.get())); EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND)); NormalLaunch(&host_aa_); } TEST_F(ExtensionAppShimHandlerTest, LaunchAndCloseShim) { // Normal startup. NormalLaunch(&host_aa_); EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); NormalLaunch(&host_ab_); EXPECT_EQ(&host_ab_, handler_->FindHost(&profile_a_, kTestAppIdB)); std::vector some_file(1, base::FilePath("some_file")); EXPECT_CALL(*delegate_, LaunchApp(&profile_b_, extension_b_.get(), some_file)); handler_->OnShimLaunch(&host_bb_, APP_SHIM_LAUNCH_NORMAL, some_file); EXPECT_EQ(&host_bb_, handler_->FindHost(&profile_b_, kTestAppIdB)); // Activation when there is a registered shim finishes launch with success and // focuses the app. EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); EXPECT_CALL(*handler_, OnShimFocus(&host_aa_, APP_SHIM_FOCUS_NORMAL, _)); handler_->OnAppActivated(&profile_a_, kTestAppIdA); // Starting and closing a second host just focuses the app. EXPECT_CALL(*handler_, OnShimFocus(&host_aa_duplicate_, APP_SHIM_FOCUS_REOPEN, some_file)); EXPECT_CALL(host_aa_duplicate_, OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST)); handler_->OnShimLaunch(&host_aa_duplicate_, APP_SHIM_LAUNCH_NORMAL, some_file); EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); handler_->OnShimClose(&host_aa_duplicate_); EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); // Normal close. handler_->OnShimClose(&host_aa_); EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); // Closing the second host afterward does nothing. handler_->OnShimClose(&host_aa_duplicate_); EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); } TEST_F(ExtensionAppShimHandlerTest, AppLifetime) { // When the app activates, if there is no shim, start one. EXPECT_CALL(*delegate_, LaunchShim(&profile_a_, extension_a_.get())); handler_->OnAppActivated(&profile_a_, kTestAppIdA); // Normal shim launch adds an entry in the map. // App should not be launched here, but return success to the shim. EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, extension_a_.get(), _)) .Times(0); EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); RegisterOnlyLaunch(&host_aa_); EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); // Return no shell windows for OnShimFocus and OnShimQuit. ShellWindowList shell_window_list; EXPECT_CALL(*delegate_, GetWindows(&profile_a_, kTestAppIdA)) .WillRepeatedly(Return(shell_window_list)); // Non-reopen focus does nothing. EXPECT_CALL(*handler_, OnShimFocus(&host_aa_, APP_SHIM_FOCUS_NORMAL, _)) .WillOnce(Invoke(handler_.get(), &TestingExtensionAppShimHandler::RealOnShimFocus)); EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, extension_a_.get(), _)) .Times(0); handler_->OnShimFocus(&host_aa_, APP_SHIM_FOCUS_NORMAL, std::vector()); // Reopen focus launches the app. EXPECT_CALL(*handler_, OnShimFocus(&host_aa_, APP_SHIM_FOCUS_REOPEN, _)) .WillOnce(Invoke(handler_.get(), &TestingExtensionAppShimHandler::RealOnShimFocus)); std::vector some_file(1, base::FilePath("some_file")); EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, extension_a_.get(), some_file)); handler_->OnShimFocus(&host_aa_, APP_SHIM_FOCUS_REOPEN, some_file); // Quit just closes all the windows. This tests that it doesn't terminate, // but we expect closing all windows triggers a OnAppDeactivated from // AppLifetimeMonitor. handler_->OnShimQuit(&host_aa_); // Closing all windows closes the shim and checks if Chrome should be // terminated. EXPECT_CALL(*delegate_, MaybeTerminate()) .WillOnce(Return()); handler_->OnAppDeactivated(&profile_a_, kTestAppIdA); EXPECT_EQ(1, host_aa_.close_count()); } TEST_F(ExtensionAppShimHandlerTest, MaybeTerminate) { // Launch shims, adding entries in the map. EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); RegisterOnlyLaunch(&host_aa_); EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); EXPECT_CALL(host_ab_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); RegisterOnlyLaunch(&host_ab_); EXPECT_EQ(&host_ab_, handler_->FindHost(&profile_a_, kTestAppIdB)); // Return empty window list. ShellWindowList shell_window_list; EXPECT_CALL(*delegate_, GetWindows(_, _)) .WillRepeatedly(Return(shell_window_list)); // Quitting when there's another shim should not terminate. EXPECT_CALL(*delegate_, MaybeTerminate()) .Times(0); handler_->OnAppDeactivated(&profile_a_, kTestAppIdA); // Quitting when it's the last shim should terminate. EXPECT_CALL(*delegate_, MaybeTerminate()); handler_->OnAppDeactivated(&profile_a_, kTestAppIdB); // Launch a shim again. EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); RegisterOnlyLaunch(&host_aa_); EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA)); // Quitting after a browser window has opened should not terminate. handler_->Observe(chrome::NOTIFICATION_BROWSER_OPENED, content::NotificationService::AllSources(), content::NotificationService::NoDetails()); EXPECT_CALL(*delegate_, MaybeTerminate()) .Times(0); handler_->OnAppDeactivated(&profile_a_, kTestAppIdA); } TEST_F(ExtensionAppShimHandlerTest, RegisterOnly) { // For an APP_SHIM_LAUNCH_REGISTER_ONLY, don't launch the app. EXPECT_CALL(*delegate_, LaunchApp(_, _, _)) .Times(0); EXPECT_CALL(host_aa_, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS)); RegisterOnlyLaunch(&host_aa_); EXPECT_TRUE(handler_->FindHost(&profile_a_, kTestAppIdA)); // Close the shim, removing the entry in the map. handler_->OnShimClose(&host_aa_); EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); } TEST_F(ExtensionAppShimHandlerTest, LoadProfile) { // If the profile is not loaded when an OnShimLaunch arrives, return false // and load the profile asynchronously. Launch the app when the profile is // ready. EXPECT_CALL(*delegate_, ProfileForPath(profile_path_a_)) .WillOnce(Return(static_cast(NULL))) .WillRepeatedly(Return(&profile_a_)); EXPECT_CALL(*delegate_, LoadProfileAsync(profile_path_a_, _)) .WillOnce(Invoke(delegate_, &MockDelegate::CaptureLoadProfileCallback)); NormalLaunch(&host_aa_); EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA)); delegate_->RunLoadProfileCallback(profile_path_a_, &profile_a_); EXPECT_TRUE(handler_->FindHost(&profile_a_, kTestAppIdA)); } } // namespace apps