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.
5 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "chrome/browser/apps/app_shim/app_shim_host_mac.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 "content/public/test/test_browser_thread_bundle.h"
15 #include "extensions/common/extension.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
21 using extensions::Extension
;
22 typedef extensions::AppWindowRegistry::AppWindowList AppWindowList
;
25 using ::testing::Invoke
;
26 using ::testing::Return
;
27 using ::testing::WithArgs
;
29 class MockDelegate
: public ExtensionAppShimHandler::Delegate
{
31 virtual ~MockDelegate() {}
33 MOCK_METHOD1(ProfileExistsForPath
, bool(const base::FilePath
&));
34 MOCK_METHOD1(ProfileForPath
, Profile
*(const base::FilePath
&));
35 MOCK_METHOD2(LoadProfileAsync
,
36 void(const base::FilePath
&,
37 base::Callback
<void(Profile
*)>));
38 MOCK_METHOD1(IsProfileLockedForPath
, bool(const base::FilePath
&));
40 MOCK_METHOD2(GetWindows
, AppWindowList(Profile
*, const std::string
&));
42 MOCK_METHOD2(MaybeGetAppExtension
,
43 const Extension
*(Profile
*, const std::string
&));
44 MOCK_METHOD3(EnableExtension
, void(Profile
*,
46 const base::Callback
<void()>&));
47 MOCK_METHOD3(LaunchApp
,
50 const std::vector
<base::FilePath
>&));
51 MOCK_METHOD2(LaunchShim
, void(Profile
*, const Extension
*));
52 MOCK_METHOD0(LaunchUserManager
, void());
54 MOCK_METHOD0(MaybeTerminate
, void());
56 void CaptureLoadProfileCallback(
57 const base::FilePath
& path
,
58 base::Callback
<void(Profile
*)> callback
) {
59 callbacks_
[path
] = callback
;
62 bool RunLoadProfileCallback(
63 const base::FilePath
& path
,
65 callbacks_
[path
].Run(profile
);
66 return callbacks_
.erase(path
);
69 void RunCallback(const base::Callback
<void()>& callback
) {
74 std::map
<base::FilePath
,
75 base::Callback
<void(Profile
*)> > callbacks_
;
78 class TestingExtensionAppShimHandler
: public ExtensionAppShimHandler
{
80 TestingExtensionAppShimHandler(Delegate
* delegate
) {
81 set_delegate(delegate
);
83 virtual ~TestingExtensionAppShimHandler() {}
85 MOCK_METHOD3(OnShimFocus
,
88 const std::vector
<base::FilePath
>& files
));
90 void RealOnShimFocus(Host
* host
,
91 AppShimFocusType focus_type
,
92 const std::vector
<base::FilePath
>& files
) {
93 ExtensionAppShimHandler::OnShimFocus(host
, focus_type
, files
);
96 AppShimHandler::Host
* FindHost(Profile
* profile
,
97 const std::string
& app_id
) {
98 HostMap::const_iterator it
= hosts().find(make_pair(profile
, app_id
));
99 return it
== hosts().end() ? NULL
: it
->second
;
102 content::NotificationRegistrar
& GetRegistrar() { return registrar(); }
105 DISALLOW_COPY_AND_ASSIGN(TestingExtensionAppShimHandler
);
108 const char kTestAppIdA
[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
109 const char kTestAppIdB
[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
111 class FakeHost
: public apps::AppShimHandler::Host
{
113 FakeHost(const base::FilePath
& profile_path
,
114 const std::string
& app_id
,
115 TestingExtensionAppShimHandler
* handler
)
116 : profile_path_(profile_path
),
121 MOCK_METHOD1(OnAppLaunchComplete
, void(AppShimLaunchResult
));
123 void OnAppClosed() override
{
124 handler_
->OnShimClose(this);
127 void OnAppHide() override
{}
128 void OnAppUnhideWithoutActivation() override
{}
129 void OnAppRequestUserAttention(AppShimAttentionType type
) override
{}
130 base::FilePath
GetProfilePath() const override
{
131 return profile_path_
;
133 std::string
GetAppId() const override
{ return app_id_
; }
135 int close_count() { return close_count_
; }
138 base::FilePath profile_path_
;
140 TestingExtensionAppShimHandler
* handler_
;
143 DISALLOW_COPY_AND_ASSIGN(FakeHost
);
146 class ExtensionAppShimHandlerTest
: public testing::Test
{
148 ExtensionAppShimHandlerTest()
149 : delegate_(new MockDelegate
),
150 handler_(new TestingExtensionAppShimHandler(delegate_
)),
151 profile_path_a_("Profile A"),
152 profile_path_b_("Profile B"),
153 host_aa_(profile_path_a_
, kTestAppIdA
, handler_
.get()),
154 host_ab_(profile_path_a_
, kTestAppIdB
, handler_
.get()),
155 host_bb_(profile_path_b_
, kTestAppIdB
, handler_
.get()),
156 host_aa_duplicate_(profile_path_a_
, kTestAppIdA
, handler_
.get()) {
157 base::FilePath
extension_path("/fake/path");
158 base::DictionaryValue manifest
;
159 manifest
.SetString("name", "Fake Name");
160 manifest
.SetString("version", "1");
162 extension_a_
= Extension::Create(
163 extension_path
, extensions::Manifest::INTERNAL
, manifest
,
164 Extension::NO_FLAGS
, kTestAppIdA
, &error
);
165 EXPECT_TRUE(extension_a_
.get()) << error
;
167 extension_b_
= Extension::Create(
168 extension_path
, extensions::Manifest::INTERNAL
, manifest
,
169 Extension::NO_FLAGS
, kTestAppIdB
, &error
);
170 EXPECT_TRUE(extension_b_
.get()) << error
;
172 EXPECT_CALL(*delegate_
, ProfileExistsForPath(profile_path_a_
))
173 .WillRepeatedly(Return(true));
174 EXPECT_CALL(*delegate_
, IsProfileLockedForPath(profile_path_a_
))
175 .WillRepeatedly(Return(false));
176 EXPECT_CALL(*delegate_
, ProfileForPath(profile_path_a_
))
177 .WillRepeatedly(Return(&profile_a_
));
178 EXPECT_CALL(*delegate_
, ProfileExistsForPath(profile_path_b_
))
179 .WillRepeatedly(Return(true));
180 EXPECT_CALL(*delegate_
, IsProfileLockedForPath(profile_path_b_
))
181 .WillRepeatedly(Return(false));
182 EXPECT_CALL(*delegate_
, ProfileForPath(profile_path_b_
))
183 .WillRepeatedly(Return(&profile_b_
));
185 // In most tests, we don't care about the result of GetWindows, it just
186 // needs to be non-empty.
187 AppWindowList app_window_list
;
188 app_window_list
.push_back(static_cast<extensions::AppWindow
*>(NULL
));
189 EXPECT_CALL(*delegate_
, GetWindows(_
, _
))
190 .WillRepeatedly(Return(app_window_list
));
192 EXPECT_CALL(*delegate_
, MaybeGetAppExtension(_
, kTestAppIdA
))
193 .WillRepeatedly(Return(extension_a_
.get()));
194 EXPECT_CALL(*delegate_
, MaybeGetAppExtension(_
, kTestAppIdB
))
195 .WillRepeatedly(Return(extension_b_
.get()));
196 EXPECT_CALL(*delegate_
, LaunchApp(_
, _
, _
))
197 .WillRepeatedly(Return());
200 void NormalLaunch(AppShimHandler::Host
* host
) {
201 handler_
->OnShimLaunch(host
,
202 APP_SHIM_LAUNCH_NORMAL
,
203 std::vector
<base::FilePath
>());
206 void RegisterOnlyLaunch(AppShimHandler::Host
* host
) {
207 handler_
->OnShimLaunch(host
,
208 APP_SHIM_LAUNCH_REGISTER_ONLY
,
209 std::vector
<base::FilePath
>());
212 // Completely launch a shim host and leave it running.
213 void LaunchAndActivate(FakeHost
* host
, Profile
* profile
) {
215 EXPECT_EQ(host
, handler_
->FindHost(profile
, host
->GetAppId()));
216 EXPECT_CALL(*host
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
217 EXPECT_CALL(*handler_
, OnShimFocus(host
, APP_SHIM_FOCUS_NORMAL
, _
));
218 handler_
->OnAppActivated(profile
, host
->GetAppId());
221 // Simulates a focus request coming from a running app shim.
222 void ShimNormalFocus(FakeHost
* host
) {
223 EXPECT_CALL(*handler_
, OnShimFocus(host
, APP_SHIM_FOCUS_NORMAL
, _
))
224 .WillOnce(Invoke(handler_
.get(),
225 &TestingExtensionAppShimHandler::RealOnShimFocus
));
227 const std::vector
<base::FilePath
> no_files
;
228 handler_
->OnShimFocus(host
, APP_SHIM_FOCUS_NORMAL
, no_files
);
231 // Simulates a hide (or unhide) request coming from a running app shim.
232 void ShimSetHidden(FakeHost
* host
, bool hidden
) {
233 handler_
->OnShimSetHidden(host
, hidden
);
236 content::TestBrowserThreadBundle thread_bundle_
;
237 MockDelegate
* delegate_
;
238 scoped_ptr
<TestingExtensionAppShimHandler
> handler_
;
239 base::FilePath profile_path_a_
;
240 base::FilePath profile_path_b_
;
241 TestingProfile profile_a_
;
242 TestingProfile profile_b_
;
246 FakeHost host_aa_duplicate_
;
247 scoped_refptr
<Extension
> extension_a_
;
248 scoped_refptr
<Extension
> extension_b_
;
251 DISALLOW_COPY_AND_ASSIGN(ExtensionAppShimHandlerTest
);
254 TEST_F(ExtensionAppShimHandlerTest
, LaunchProfileNotFound
) {
256 EXPECT_CALL(*delegate_
, ProfileExistsForPath(profile_path_a_
))
257 .WillOnce(Return(false))
258 .WillRepeatedly(Return(true));
259 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND
));
260 NormalLaunch(&host_aa_
);
263 TEST_F(ExtensionAppShimHandlerTest
, LaunchProfileIsLocked
) {
264 // Profile is locked.
265 EXPECT_CALL(*delegate_
, IsProfileLockedForPath(profile_path_a_
))
266 .WillOnce(Return(true));
267 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_LOCKED
));
268 EXPECT_CALL(*delegate_
, LaunchUserManager());
269 NormalLaunch(&host_aa_
);
272 TEST_F(ExtensionAppShimHandlerTest
, LaunchAppNotFound
) {
274 EXPECT_CALL(*delegate_
, MaybeGetAppExtension(&profile_a_
, kTestAppIdA
))
275 .WillRepeatedly(Return(static_cast<const Extension
*>(NULL
)));
276 EXPECT_CALL(*delegate_
, EnableExtension(&profile_a_
, kTestAppIdA
, _
))
277 .WillOnce(WithArgs
<2>(Invoke(delegate_
, &MockDelegate::RunCallback
)));
278 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND
));
279 NormalLaunch(&host_aa_
);
282 TEST_F(ExtensionAppShimHandlerTest
, LaunchAppNotEnabled
) {
284 EXPECT_CALL(*delegate_
, MaybeGetAppExtension(&profile_a_
, kTestAppIdA
))
285 .WillOnce(Return(static_cast<const Extension
*>(NULL
)))
286 .WillRepeatedly(Return(extension_a_
.get()));
287 EXPECT_CALL(*delegate_
, EnableExtension(&profile_a_
, kTestAppIdA
, _
))
288 .WillOnce(WithArgs
<2>(Invoke(delegate_
, &MockDelegate::RunCallback
)));
289 NormalLaunch(&host_aa_
);
292 TEST_F(ExtensionAppShimHandlerTest
, LaunchAndCloseShim
) {
294 NormalLaunch(&host_aa_
);
295 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
297 NormalLaunch(&host_ab_
);
298 EXPECT_EQ(&host_ab_
, handler_
->FindHost(&profile_a_
, kTestAppIdB
));
300 std::vector
<base::FilePath
> some_file(1, base::FilePath("some_file"));
301 EXPECT_CALL(*delegate_
,
302 LaunchApp(&profile_b_
, extension_b_
.get(), some_file
));
303 handler_
->OnShimLaunch(&host_bb_
, APP_SHIM_LAUNCH_NORMAL
, some_file
);
304 EXPECT_EQ(&host_bb_
, handler_
->FindHost(&profile_b_
, kTestAppIdB
));
306 // Activation when there is a registered shim finishes launch with success and
308 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
309 EXPECT_CALL(*handler_
, OnShimFocus(&host_aa_
, APP_SHIM_FOCUS_NORMAL
, _
));
310 handler_
->OnAppActivated(&profile_a_
, kTestAppIdA
);
312 // Starting and closing a second host just focuses the app.
313 EXPECT_CALL(*handler_
, OnShimFocus(&host_aa_duplicate_
,
314 APP_SHIM_FOCUS_REOPEN
,
316 EXPECT_CALL(host_aa_duplicate_
,
317 OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST
));
318 handler_
->OnShimLaunch(&host_aa_duplicate_
,
319 APP_SHIM_LAUNCH_NORMAL
,
321 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
322 handler_
->OnShimClose(&host_aa_duplicate_
);
323 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
326 handler_
->OnShimClose(&host_aa_
);
327 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
329 // Closing the second host afterward does nothing.
330 handler_
->OnShimClose(&host_aa_duplicate_
);
331 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
334 TEST_F(ExtensionAppShimHandlerTest
, AppLifetime
) {
335 // When the app activates, if there is no shim, start one.
336 EXPECT_CALL(*delegate_
, LaunchShim(&profile_a_
, extension_a_
.get()));
337 handler_
->OnAppActivated(&profile_a_
, kTestAppIdA
);
339 // Normal shim launch adds an entry in the map.
340 // App should not be launched here, but return success to the shim.
341 EXPECT_CALL(*delegate_
,
342 LaunchApp(&profile_a_
, extension_a_
.get(), _
))
344 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
345 RegisterOnlyLaunch(&host_aa_
);
346 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
348 // Return no app windows for OnShimFocus and OnShimQuit.
349 AppWindowList app_window_list
;
350 EXPECT_CALL(*delegate_
, GetWindows(&profile_a_
, kTestAppIdA
))
351 .WillRepeatedly(Return(app_window_list
));
353 // Non-reopen focus does nothing.
354 EXPECT_CALL(*delegate_
,
355 LaunchApp(&profile_a_
, extension_a_
.get(), _
))
357 ShimNormalFocus(&host_aa_
);
359 // Reopen focus launches the app.
360 EXPECT_CALL(*handler_
, OnShimFocus(&host_aa_
, APP_SHIM_FOCUS_REOPEN
, _
))
361 .WillOnce(Invoke(handler_
.get(),
362 &TestingExtensionAppShimHandler::RealOnShimFocus
));
363 std::vector
<base::FilePath
> some_file(1, base::FilePath("some_file"));
364 EXPECT_CALL(*delegate_
,
365 LaunchApp(&profile_a_
, extension_a_
.get(), some_file
));
366 handler_
->OnShimFocus(&host_aa_
, APP_SHIM_FOCUS_REOPEN
, some_file
);
368 // Quit just closes all the windows. This tests that it doesn't terminate,
369 // but we expect closing all windows triggers a OnAppDeactivated from
370 // AppLifetimeMonitor.
371 handler_
->OnShimQuit(&host_aa_
);
373 // Closing all windows closes the shim and checks if Chrome should be
375 EXPECT_CALL(*delegate_
, MaybeTerminate())
377 handler_
->OnAppDeactivated(&profile_a_
, kTestAppIdA
);
378 EXPECT_EQ(1, host_aa_
.close_count());
381 TEST_F(ExtensionAppShimHandlerTest
, MaybeTerminate
) {
382 // Launch shims, adding entries in the map.
383 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
384 RegisterOnlyLaunch(&host_aa_
);
385 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
387 EXPECT_CALL(host_ab_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
388 RegisterOnlyLaunch(&host_ab_
);
389 EXPECT_EQ(&host_ab_
, handler_
->FindHost(&profile_a_
, kTestAppIdB
));
391 // Return empty window list.
392 AppWindowList app_window_list
;
393 EXPECT_CALL(*delegate_
, GetWindows(_
, _
))
394 .WillRepeatedly(Return(app_window_list
));
396 // Quitting when there's another shim should not terminate.
397 EXPECT_CALL(*delegate_
, MaybeTerminate())
399 handler_
->OnAppDeactivated(&profile_a_
, kTestAppIdA
);
401 // Quitting when it's the last shim should terminate.
402 EXPECT_CALL(*delegate_
, MaybeTerminate());
403 handler_
->OnAppDeactivated(&profile_a_
, kTestAppIdB
);
406 TEST_F(ExtensionAppShimHandlerTest
, RegisterOnly
) {
407 // For an APP_SHIM_LAUNCH_REGISTER_ONLY, don't launch the app.
408 EXPECT_CALL(*delegate_
, LaunchApp(_
, _
, _
))
410 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
411 RegisterOnlyLaunch(&host_aa_
);
412 EXPECT_TRUE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
414 // Close the shim, removing the entry in the map.
415 handler_
->OnShimClose(&host_aa_
);
416 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
419 TEST_F(ExtensionAppShimHandlerTest
, LoadProfile
) {
420 // If the profile is not loaded when an OnShimLaunch arrives, return false
421 // and load the profile asynchronously. Launch the app when the profile is
423 EXPECT_CALL(*delegate_
, ProfileForPath(profile_path_a_
))
424 .WillOnce(Return(static_cast<Profile
*>(NULL
)))
425 .WillRepeatedly(Return(&profile_a_
));
426 EXPECT_CALL(*delegate_
, LoadProfileAsync(profile_path_a_
, _
))
427 .WillOnce(Invoke(delegate_
, &MockDelegate::CaptureLoadProfileCallback
));
428 NormalLaunch(&host_aa_
);
429 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
430 delegate_
->RunLoadProfileCallback(profile_path_a_
, &profile_a_
);
431 EXPECT_TRUE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
434 // Tests that calls to OnShimFocus, OnShimHide correctly handle a null extension
435 // being provided by the extension system.
436 TEST_F(ExtensionAppShimHandlerTest
, ExtensionUninstalled
) {
437 LaunchAndActivate(&host_aa_
, &profile_a_
);
439 // Have GetWindows() return an empty window list for focus (otherwise, it
440 // will contain a single nullptr, which can't be focused). Expect 1 call only.
441 AppWindowList empty_window_list
;
442 EXPECT_CALL(*delegate_
, GetWindows(_
, _
)).WillOnce(Return(empty_window_list
));
444 ShimNormalFocus(&host_aa_
);
445 EXPECT_EQ(0, host_aa_
.close_count());
447 // Set up the mock to return a null extension, as if it were uninstalled.
448 EXPECT_CALL(*delegate_
, MaybeGetAppExtension(&profile_a_
, kTestAppIdA
))
449 .WillRepeatedly(Return(nullptr));
451 // Now trying to focus should automatically close the shim, and not try to
452 // get the window list.
453 ShimNormalFocus(&host_aa_
);
454 EXPECT_EQ(1, host_aa_
.close_count());
456 // Do the same for SetHidden on host_bb.
457 LaunchAndActivate(&host_bb_
, &profile_b_
);
458 ShimSetHidden(&host_bb_
, true);
459 EXPECT_EQ(0, host_bb_
.close_count());
461 EXPECT_CALL(*delegate_
, MaybeGetAppExtension(&profile_b_
, kTestAppIdB
))
462 .WillRepeatedly(Return(nullptr));
463 ShimSetHidden(&host_bb_
, true);
464 EXPECT_EQ(1, host_bb_
.close_count());