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 "chrome/browser/apps/app_shim/app_shim_listener.h" 6 7#include <unistd.h> 8 9#include "base/bind.h" 10#include "base/check.h" 11#include "base/files/file_path.h" 12#include "base/files/file_util.h" 13#include "base/mac/foundation_util.h" 14#include "base/macros.h" 15#include "base/optional.h" 16#include "base/path_service.h" 17#include "base/run_loop.h" 18#include "base/threading/thread_restrictions.h" 19#include "chrome/app_shim/app_shim_controller.h" 20#include "chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h" 21#include "chrome/browser/apps/app_shim/test/app_shim_listener_test_api_mac.h" 22#include "chrome/browser/browser_process.h" 23#include "chrome/browser/browser_process_platform_part.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/ui/browser.h" 26#include "chrome/common/chrome_paths.h" 27#include "chrome/common/mac/app_mode_common.h" 28#include "chrome/common/mac/app_shim.mojom.h" 29#include "chrome/test/base/in_process_browser_test.h" 30#include "components/version_info/version_info.h" 31#include "content/public/test/browser_test.h" 32#include "content/public/test/test_utils.h" 33#include "mojo/public/cpp/bindings/pending_associated_receiver.h" 34#include "mojo/public/cpp/bindings/pending_receiver.h" 35#include "mojo/public/cpp/bindings/receiver.h" 36#include "mojo/public/cpp/bindings/remote.h" 37#include "mojo/public/cpp/platform/named_platform_channel.h" 38#include "mojo/public/cpp/platform/platform_channel.h" 39#include "mojo/public/cpp/system/isolated_connection.h" 40 41using OnShimConnectedCallback = 42 chrome::mojom::AppShimHostBootstrap::OnShimConnectedCallback; 43 44const char kTestAppMode[] = "test_app"; 45 46// TODO(https://crbug.com/1042727): Fix test GURL scoping and remove this getter 47// function. 48GURL TestAppUrl() { 49 return GURL("https://example.com"); 50} 51 52// A test version of the AppShimController mojo client in chrome_main_app_mode. 53class TestShimClient : public chrome::mojom::AppShim { 54 public: 55 TestShimClient(); 56 57 // Friend accessor. 58 mojo::PlatformChannelEndpoint ConnectToBrowser( 59 const mojo::NamedPlatformChannel::ServerName& server_name) { 60 return AppShimController::ConnectToBrowser(server_name); 61 } 62 63 mojo::PendingReceiver<chrome::mojom::AppShimHost> GetHostReceiver() { 64 return std::move(host_receiver_); 65 } 66 OnShimConnectedCallback GetOnShimConnectedCallback() { 67 return base::BindOnce(&TestShimClient::OnShimConnectedDone, 68 base::Unretained(this)); 69 } 70 71 mojo::Remote<chrome::mojom::AppShimHostBootstrap>& host_bootstrap() { 72 return host_bootstrap_; 73 } 74 75 // chrome::mojom::AppShim implementation (not used in testing, but can be). 76 void CreateRemoteCocoaApplication( 77 mojo::PendingAssociatedReceiver<remote_cocoa::mojom::Application> 78 receiver) override {} 79 void CreateCommandDispatcherForWidget(uint64_t widget_id) override {} 80 void SetUserAttention( 81 chrome::mojom::AppShimAttentionType attention_type) override {} 82 void SetBadgeLabel(const std::string& badge_label) override {} 83 void UpdateProfileMenu( 84 std::vector<chrome::mojom::ProfileMenuItemPtr> profile_menu_items, 85 bool use_new_picker) override {} 86 87 private: 88 void OnShimConnectedDone( 89 chrome::mojom::AppShimLaunchResult result, 90 mojo::PendingReceiver<chrome::mojom::AppShim> app_shim_receiver) { 91 shim_receiver_.Bind(std::move(app_shim_receiver)); 92 } 93 94 mojo::IsolatedConnection mojo_connection_; 95 mojo::Receiver<chrome::mojom::AppShim> shim_receiver_{this}; 96 mojo::Remote<chrome::mojom::AppShimHost> host_; 97 mojo::PendingReceiver<chrome::mojom::AppShimHost> host_receiver_; 98 mojo::Remote<chrome::mojom::AppShimHostBootstrap> host_bootstrap_; 99 100 DISALLOW_COPY_AND_ASSIGN(TestShimClient); 101}; 102 103TestShimClient::TestShimClient() 104 : host_receiver_(host_.BindNewPipeAndPassReceiver()) { 105 base::FilePath user_data_dir; 106 CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)); 107 108 std::string name_fragment = 109 base::StringPrintf("%s.%s.%s", base::mac::BaseBundleID(), 110 app_mode::kAppShimBootstrapNameFragment, 111 base::MD5String(user_data_dir.value()).c_str()); 112 mojo::PlatformChannelEndpoint endpoint = ConnectToBrowser(name_fragment); 113 114 mojo::ScopedMessagePipeHandle message_pipe = 115 mojo_connection_.Connect(std::move(endpoint)); 116 host_bootstrap_ = mojo::Remote<chrome::mojom::AppShimHostBootstrap>( 117 mojo::PendingRemote<chrome::mojom::AppShimHostBootstrap>( 118 std::move(message_pipe), 0)); 119} 120 121// Browser Test for AppShimListener to test IPC interactions. 122class AppShimListenerBrowserTest : public InProcessBrowserTest, 123 public AppShimHostBootstrap::Client, 124 public chrome::mojom::AppShimHost { 125 public: 126 AppShimListenerBrowserTest() = default; 127 128 protected: 129 // Wait for OnShimProcessConnected, then send a quit, and wait for the 130 // response. Used to test launch behavior. 131 void RunAndExitGracefully(); 132 133 // InProcessBrowserTest overrides: 134 void SetUpOnMainThread() override; 135 void TearDownOnMainThread() override; 136 137 // AppShimHostBootstrap::Client: 138 void OnShimProcessConnected( 139 std::unique_ptr<AppShimHostBootstrap> bootstrap) override; 140 141 std::unique_ptr<TestShimClient> test_client_; 142 std::vector<base::FilePath> last_launch_files_; 143 base::Optional<chrome::mojom::AppShimLaunchType> last_launch_type_; 144 145 private: 146 // chrome::mojom::AppShimHost. 147 void FocusApp() override {} 148 void ReopenApp() override {} 149 void FilesOpened(const std::vector<base::FilePath>& files) override {} 150 void ProfileSelectedFromMenu(const base::FilePath& profile_path) override {} 151 152 std::unique_ptr<base::RunLoop> runner_; 153 mojo::Receiver<chrome::mojom::AppShimHost> receiver_{this}; 154 mojo::Remote<chrome::mojom::AppShim> app_shim_; 155 156 int launch_count_ = 0; 157 158 DISALLOW_COPY_AND_ASSIGN(AppShimListenerBrowserTest); 159}; 160 161void AppShimListenerBrowserTest::RunAndExitGracefully() { 162 runner_ = std::make_unique<base::RunLoop>(); 163 EXPECT_EQ(0, launch_count_); 164 runner_->Run(); // Will stop in OnShimProcessConnected(). 165 EXPECT_EQ(1, launch_count_); 166 test_client_.reset(); 167} 168 169void AppShimListenerBrowserTest::SetUpOnMainThread() { 170 // Can't do this in the constructor, it needs a BrowserProcess. 171 AppShimHostBootstrap::SetClient(this); 172} 173 174void AppShimListenerBrowserTest::TearDownOnMainThread() { 175 AppShimHostBootstrap::SetClient(nullptr); 176} 177 178void AppShimListenerBrowserTest::OnShimProcessConnected( 179 std::unique_ptr<AppShimHostBootstrap> bootstrap) { 180 ++launch_count_; 181 receiver_.Bind(bootstrap->GetAppShimHostReceiver()); 182 last_launch_type_ = bootstrap->GetLaunchType(); 183 last_launch_files_ = bootstrap->GetLaunchFiles(); 184 185 bootstrap->OnConnectedToHost(app_shim_.BindNewPipeAndPassReceiver()); 186 runner_->Quit(); 187} 188 189// Test regular launch, which would ask Chrome to launch the app. 190IN_PROC_BROWSER_TEST_F(AppShimListenerBrowserTest, LaunchNormal) { 191 test_client_.reset(new TestShimClient()); 192 auto app_shim_info = chrome::mojom::AppShimInfo::New(); 193 app_shim_info->profile_path = browser()->profile()->GetPath(); 194 app_shim_info->app_id = kTestAppMode; 195 app_shim_info->app_url = TestAppUrl(); 196 app_shim_info->launch_type = chrome::mojom::AppShimLaunchType::kNormal; 197 test_client_->host_bootstrap()->OnShimConnected( 198 test_client_->GetHostReceiver(), std::move(app_shim_info), 199 test_client_->GetOnShimConnectedCallback()); 200 RunAndExitGracefully(); 201 EXPECT_EQ(chrome::mojom::AppShimLaunchType::kNormal, last_launch_type_); 202 EXPECT_TRUE(last_launch_files_.empty()); 203} 204 205// Test register-only launch, used when Chrome has already launched the app. 206IN_PROC_BROWSER_TEST_F(AppShimListenerBrowserTest, LaunchRegisterOnly) { 207 test_client_.reset(new TestShimClient()); 208 auto app_shim_info = chrome::mojom::AppShimInfo::New(); 209 app_shim_info->profile_path = browser()->profile()->GetPath(); 210 app_shim_info->app_id = kTestAppMode; 211 app_shim_info->app_url = TestAppUrl(); 212 app_shim_info->launch_type = chrome::mojom::AppShimLaunchType::kRegisterOnly; 213 test_client_->host_bootstrap()->OnShimConnected( 214 test_client_->GetHostReceiver(), std::move(app_shim_info), 215 test_client_->GetOnShimConnectedCallback()); 216 217 RunAndExitGracefully(); 218 EXPECT_EQ(chrome::mojom::AppShimLaunchType::kRegisterOnly, 219 *last_launch_type_); 220 EXPECT_TRUE(last_launch_files_.empty()); 221} 222 223// Ensure bootstrap name registers. 224IN_PROC_BROWSER_TEST_F(AppShimListenerBrowserTest, PRE_ReCreate) { 225 test::AppShimListenerTestApi test_api( 226 g_browser_process->platform_part()->app_shim_listener()); 227 EXPECT_TRUE(test_api.mach_acceptor()); 228} 229 230// Ensure the bootstrap name can be re-created after a prior browser process has 231// quit. 232IN_PROC_BROWSER_TEST_F(AppShimListenerBrowserTest, ReCreate) { 233 test::AppShimListenerTestApi test_api( 234 g_browser_process->platform_part()->app_shim_listener()); 235 EXPECT_TRUE(test_api.mach_acceptor()); 236} 237 238// Tests for the files created by AppShimListener. 239class AppShimListenerBrowserTestSymlink : public AppShimListenerBrowserTest { 240 public: 241 AppShimListenerBrowserTestSymlink() {} 242 243 protected: 244 base::FilePath version_path_; 245 246 private: 247 bool SetUpUserDataDirectory() override; 248 void TearDownInProcessBrowserTestFixture() override; 249 250 DISALLOW_COPY_AND_ASSIGN(AppShimListenerBrowserTestSymlink); 251}; 252 253bool AppShimListenerBrowserTestSymlink::SetUpUserDataDirectory() { 254 // Create an existing symlink. It should be replaced by AppShimListener. 255 base::FilePath user_data_dir; 256 EXPECT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)); 257 258 // Create an invalid RunningChromeVersion file. 259 version_path_ = 260 user_data_dir.Append(app_mode::kRunningChromeVersionSymlinkName); 261 EXPECT_TRUE(base::CreateSymbolicLink(base::FilePath("invalid_version"), 262 version_path_)); 263 return AppShimListenerBrowserTest::SetUpUserDataDirectory(); 264} 265 266void AppShimListenerBrowserTestSymlink::TearDownInProcessBrowserTestFixture() { 267 // Check that created files have been deleted. 268 EXPECT_FALSE(base::PathExists(version_path_)); 269} 270 271IN_PROC_BROWSER_TEST_F(AppShimListenerBrowserTestSymlink, 272 RunningChromeVersionCorrectlyWritten) { 273 // Check that the RunningChromeVersion file is correctly written. 274 base::FilePath version; 275 EXPECT_TRUE(base::ReadSymbolicLink(version_path_, &version)); 276 EXPECT_EQ(version_info::GetVersionNumber(), version.value()); 277} 278