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
*)>));
39 MOCK_METHOD2(GetWindows
, AppWindowList(Profile
*, const std::string
&));
41 MOCK_METHOD2(GetAppExtension
, const Extension
*(Profile
*, const std::string
&));
42 MOCK_METHOD3(EnableExtension
, void(Profile
*,
44 const base::Callback
<void()>&));
45 MOCK_METHOD3(LaunchApp
,
48 const std::vector
<base::FilePath
>&));
49 MOCK_METHOD2(LaunchShim
, void(Profile
*, const Extension
*));
51 MOCK_METHOD0(MaybeTerminate
, void());
53 void CaptureLoadProfileCallback(
54 const base::FilePath
& path
,
55 base::Callback
<void(Profile
*)> callback
) {
56 callbacks_
[path
] = callback
;
59 bool RunLoadProfileCallback(
60 const base::FilePath
& path
,
62 callbacks_
[path
].Run(profile
);
63 return callbacks_
.erase(path
);
66 void RunCallback(const base::Callback
<void()>& callback
) {
71 std::map
<base::FilePath
,
72 base::Callback
<void(Profile
*)> > callbacks_
;
75 class TestingExtensionAppShimHandler
: public ExtensionAppShimHandler
{
77 TestingExtensionAppShimHandler(Delegate
* delegate
) {
78 set_delegate(delegate
);
80 virtual ~TestingExtensionAppShimHandler() {}
82 MOCK_METHOD3(OnShimFocus
,
85 const std::vector
<base::FilePath
>& files
));
87 void RealOnShimFocus(Host
* host
,
88 AppShimFocusType focus_type
,
89 const std::vector
<base::FilePath
>& files
) {
90 ExtensionAppShimHandler::OnShimFocus(host
, focus_type
, files
);
93 AppShimHandler::Host
* FindHost(Profile
* profile
,
94 const std::string
& app_id
) {
95 HostMap::const_iterator it
= hosts().find(make_pair(profile
, app_id
));
96 return it
== hosts().end() ? NULL
: it
->second
;
99 content::NotificationRegistrar
& GetRegistrar() { return registrar(); }
102 DISALLOW_COPY_AND_ASSIGN(TestingExtensionAppShimHandler
);
105 const char kTestAppIdA
[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
106 const char kTestAppIdB
[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
108 class FakeHost
: public apps::AppShimHandler::Host
{
110 FakeHost(const base::FilePath
& profile_path
,
111 const std::string
& app_id
,
112 TestingExtensionAppShimHandler
* handler
)
113 : profile_path_(profile_path
),
118 MOCK_METHOD1(OnAppLaunchComplete
, void(AppShimLaunchResult
));
120 void OnAppClosed() override
{
121 handler_
->OnShimClose(this);
124 void OnAppHide() override
{}
125 void OnAppRequestUserAttention(AppShimAttentionType type
) override
{}
126 base::FilePath
GetProfilePath() const override
{
127 return profile_path_
;
129 std::string
GetAppId() const override
{ return app_id_
; }
131 int close_count() { return close_count_
; }
134 base::FilePath profile_path_
;
136 TestingExtensionAppShimHandler
* handler_
;
139 DISALLOW_COPY_AND_ASSIGN(FakeHost
);
142 class ExtensionAppShimHandlerTest
: public testing::Test
{
144 ExtensionAppShimHandlerTest()
145 : delegate_(new MockDelegate
),
146 handler_(new TestingExtensionAppShimHandler(delegate_
)),
147 profile_path_a_("Profile A"),
148 profile_path_b_("Profile B"),
149 host_aa_(profile_path_a_
, kTestAppIdA
, handler_
.get()),
150 host_ab_(profile_path_a_
, kTestAppIdB
, handler_
.get()),
151 host_bb_(profile_path_b_
, kTestAppIdB
, handler_
.get()),
152 host_aa_duplicate_(profile_path_a_
, kTestAppIdA
, handler_
.get()) {
153 base::FilePath
extension_path("/fake/path");
154 base::DictionaryValue manifest
;
155 manifest
.SetString("name", "Fake Name");
156 manifest
.SetString("version", "1");
158 extension_a_
= Extension::Create(
159 extension_path
, extensions::Manifest::INTERNAL
, manifest
,
160 Extension::NO_FLAGS
, kTestAppIdA
, &error
);
161 EXPECT_TRUE(extension_a_
.get()) << error
;
163 extension_b_
= Extension::Create(
164 extension_path
, extensions::Manifest::INTERNAL
, manifest
,
165 Extension::NO_FLAGS
, kTestAppIdB
, &error
);
166 EXPECT_TRUE(extension_b_
.get()) << error
;
168 EXPECT_CALL(*delegate_
, ProfileExistsForPath(profile_path_a_
))
169 .WillRepeatedly(Return(true));
170 EXPECT_CALL(*delegate_
, ProfileForPath(profile_path_a_
))
171 .WillRepeatedly(Return(&profile_a_
));
172 EXPECT_CALL(*delegate_
, ProfileExistsForPath(profile_path_b_
))
173 .WillRepeatedly(Return(true));
174 EXPECT_CALL(*delegate_
, ProfileForPath(profile_path_b_
))
175 .WillRepeatedly(Return(&profile_b_
));
177 // In most tests, we don't care about the result of GetWindows, it just
178 // needs to be non-empty.
179 AppWindowList app_window_list
;
180 app_window_list
.push_back(static_cast<extensions::AppWindow
*>(NULL
));
181 EXPECT_CALL(*delegate_
, GetWindows(_
, _
))
182 .WillRepeatedly(Return(app_window_list
));
184 EXPECT_CALL(*delegate_
, GetAppExtension(_
, kTestAppIdA
))
185 .WillRepeatedly(Return(extension_a_
.get()));
186 EXPECT_CALL(*delegate_
, GetAppExtension(_
, kTestAppIdB
))
187 .WillRepeatedly(Return(extension_b_
.get()));
188 EXPECT_CALL(*delegate_
, LaunchApp(_
, _
, _
))
189 .WillRepeatedly(Return());
192 void NormalLaunch(AppShimHandler::Host
* host
) {
193 handler_
->OnShimLaunch(host
,
194 APP_SHIM_LAUNCH_NORMAL
,
195 std::vector
<base::FilePath
>());
198 void RegisterOnlyLaunch(AppShimHandler::Host
* host
) {
199 handler_
->OnShimLaunch(host
,
200 APP_SHIM_LAUNCH_REGISTER_ONLY
,
201 std::vector
<base::FilePath
>());
204 content::TestBrowserThreadBundle thread_bundle_
;
205 MockDelegate
* delegate_
;
206 scoped_ptr
<TestingExtensionAppShimHandler
> handler_
;
207 base::FilePath profile_path_a_
;
208 base::FilePath profile_path_b_
;
209 TestingProfile profile_a_
;
210 TestingProfile profile_b_
;
214 FakeHost host_aa_duplicate_
;
215 scoped_refptr
<Extension
> extension_a_
;
216 scoped_refptr
<Extension
> extension_b_
;
219 DISALLOW_COPY_AND_ASSIGN(ExtensionAppShimHandlerTest
);
222 TEST_F(ExtensionAppShimHandlerTest
, LaunchProfileNotFound
) {
224 EXPECT_CALL(*delegate_
, ProfileExistsForPath(profile_path_a_
))
225 .WillOnce(Return(false))
226 .WillRepeatedly(Return(true));
227 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND
));
228 NormalLaunch(&host_aa_
);
231 TEST_F(ExtensionAppShimHandlerTest
, LaunchAppNotFound
) {
233 EXPECT_CALL(*delegate_
, GetAppExtension(&profile_a_
, kTestAppIdA
))
234 .WillRepeatedly(Return(static_cast<const Extension
*>(NULL
)));
235 EXPECT_CALL(*delegate_
, EnableExtension(&profile_a_
, kTestAppIdA
, _
))
236 .WillOnce(WithArgs
<2>(Invoke(delegate_
, &MockDelegate::RunCallback
)));
237 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND
));
238 NormalLaunch(&host_aa_
);
241 TEST_F(ExtensionAppShimHandlerTest
, LaunchAppNotEnabled
) {
243 EXPECT_CALL(*delegate_
, GetAppExtension(&profile_a_
, kTestAppIdA
))
244 .WillOnce(Return(static_cast<const Extension
*>(NULL
)))
245 .WillRepeatedly(Return(extension_a_
.get()));
246 EXPECT_CALL(*delegate_
, EnableExtension(&profile_a_
, kTestAppIdA
, _
))
247 .WillOnce(WithArgs
<2>(Invoke(delegate_
, &MockDelegate::RunCallback
)));
248 NormalLaunch(&host_aa_
);
251 TEST_F(ExtensionAppShimHandlerTest
, LaunchAndCloseShim
) {
253 NormalLaunch(&host_aa_
);
254 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
256 NormalLaunch(&host_ab_
);
257 EXPECT_EQ(&host_ab_
, handler_
->FindHost(&profile_a_
, kTestAppIdB
));
259 std::vector
<base::FilePath
> some_file(1, base::FilePath("some_file"));
260 EXPECT_CALL(*delegate_
,
261 LaunchApp(&profile_b_
, extension_b_
.get(), some_file
));
262 handler_
->OnShimLaunch(&host_bb_
, APP_SHIM_LAUNCH_NORMAL
, some_file
);
263 EXPECT_EQ(&host_bb_
, handler_
->FindHost(&profile_b_
, kTestAppIdB
));
265 // Activation when there is a registered shim finishes launch with success and
267 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
268 EXPECT_CALL(*handler_
, OnShimFocus(&host_aa_
, APP_SHIM_FOCUS_NORMAL
, _
));
269 handler_
->OnAppActivated(&profile_a_
, kTestAppIdA
);
271 // Starting and closing a second host just focuses the app.
272 EXPECT_CALL(*handler_
, OnShimFocus(&host_aa_duplicate_
,
273 APP_SHIM_FOCUS_REOPEN
,
275 EXPECT_CALL(host_aa_duplicate_
,
276 OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST
));
277 handler_
->OnShimLaunch(&host_aa_duplicate_
,
278 APP_SHIM_LAUNCH_NORMAL
,
280 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
281 handler_
->OnShimClose(&host_aa_duplicate_
);
282 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
285 handler_
->OnShimClose(&host_aa_
);
286 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
288 // Closing the second host afterward does nothing.
289 handler_
->OnShimClose(&host_aa_duplicate_
);
290 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
293 TEST_F(ExtensionAppShimHandlerTest
, AppLifetime
) {
294 // When the app activates, if there is no shim, start one.
295 EXPECT_CALL(*delegate_
, LaunchShim(&profile_a_
, extension_a_
.get()));
296 handler_
->OnAppActivated(&profile_a_
, kTestAppIdA
);
298 // Normal shim launch adds an entry in the map.
299 // App should not be launched here, but return success to the shim.
300 EXPECT_CALL(*delegate_
,
301 LaunchApp(&profile_a_
, extension_a_
.get(), _
))
303 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
304 RegisterOnlyLaunch(&host_aa_
);
305 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
307 // Return no app windows for OnShimFocus and OnShimQuit.
308 AppWindowList app_window_list
;
309 EXPECT_CALL(*delegate_
, GetWindows(&profile_a_
, kTestAppIdA
))
310 .WillRepeatedly(Return(app_window_list
));
312 // Non-reopen focus does nothing.
313 EXPECT_CALL(*handler_
, OnShimFocus(&host_aa_
, APP_SHIM_FOCUS_NORMAL
, _
))
314 .WillOnce(Invoke(handler_
.get(),
315 &TestingExtensionAppShimHandler::RealOnShimFocus
));
316 EXPECT_CALL(*delegate_
,
317 LaunchApp(&profile_a_
, extension_a_
.get(), _
))
319 handler_
->OnShimFocus(&host_aa_
,
320 APP_SHIM_FOCUS_NORMAL
,
321 std::vector
<base::FilePath
>());
323 // Reopen focus launches the app.
324 EXPECT_CALL(*handler_
, OnShimFocus(&host_aa_
, APP_SHIM_FOCUS_REOPEN
, _
))
325 .WillOnce(Invoke(handler_
.get(),
326 &TestingExtensionAppShimHandler::RealOnShimFocus
));
327 std::vector
<base::FilePath
> some_file(1, base::FilePath("some_file"));
328 EXPECT_CALL(*delegate_
,
329 LaunchApp(&profile_a_
, extension_a_
.get(), some_file
));
330 handler_
->OnShimFocus(&host_aa_
, APP_SHIM_FOCUS_REOPEN
, some_file
);
332 // Quit just closes all the windows. This tests that it doesn't terminate,
333 // but we expect closing all windows triggers a OnAppDeactivated from
334 // AppLifetimeMonitor.
335 handler_
->OnShimQuit(&host_aa_
);
337 // Closing all windows closes the shim and checks if Chrome should be
339 EXPECT_CALL(*delegate_
, MaybeTerminate())
341 handler_
->OnAppDeactivated(&profile_a_
, kTestAppIdA
);
342 EXPECT_EQ(1, host_aa_
.close_count());
345 TEST_F(ExtensionAppShimHandlerTest
, MaybeTerminate
) {
346 // Launch shims, adding entries in the map.
347 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
348 RegisterOnlyLaunch(&host_aa_
);
349 EXPECT_EQ(&host_aa_
, handler_
->FindHost(&profile_a_
, kTestAppIdA
));
351 EXPECT_CALL(host_ab_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
352 RegisterOnlyLaunch(&host_ab_
);
353 EXPECT_EQ(&host_ab_
, handler_
->FindHost(&profile_a_
, kTestAppIdB
));
355 // Return empty window list.
356 AppWindowList app_window_list
;
357 EXPECT_CALL(*delegate_
, GetWindows(_
, _
))
358 .WillRepeatedly(Return(app_window_list
));
360 // Quitting when there's another shim should not terminate.
361 EXPECT_CALL(*delegate_
, MaybeTerminate())
363 handler_
->OnAppDeactivated(&profile_a_
, kTestAppIdA
);
365 // Quitting when it's the last shim should terminate.
366 EXPECT_CALL(*delegate_
, MaybeTerminate());
367 handler_
->OnAppDeactivated(&profile_a_
, kTestAppIdB
);
370 TEST_F(ExtensionAppShimHandlerTest
, RegisterOnly
) {
371 // For an APP_SHIM_LAUNCH_REGISTER_ONLY, don't launch the app.
372 EXPECT_CALL(*delegate_
, LaunchApp(_
, _
, _
))
374 EXPECT_CALL(host_aa_
, OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
));
375 RegisterOnlyLaunch(&host_aa_
);
376 EXPECT_TRUE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
378 // Close the shim, removing the entry in the map.
379 handler_
->OnShimClose(&host_aa_
);
380 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
383 TEST_F(ExtensionAppShimHandlerTest
, LoadProfile
) {
384 // If the profile is not loaded when an OnShimLaunch arrives, return false
385 // and load the profile asynchronously. Launch the app when the profile is
387 EXPECT_CALL(*delegate_
, ProfileForPath(profile_path_a_
))
388 .WillOnce(Return(static_cast<Profile
*>(NULL
)))
389 .WillRepeatedly(Return(&profile_a_
));
390 EXPECT_CALL(*delegate_
, LoadProfileAsync(profile_path_a_
, _
))
391 .WillOnce(Invoke(delegate_
, &MockDelegate::CaptureLoadProfileCallback
));
392 NormalLaunch(&host_aa_
);
393 EXPECT_FALSE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));
394 delegate_
->RunLoadProfileCallback(profile_path_a_
, &profile_a_
);
395 EXPECT_TRUE(handler_
->FindHost(&profile_a_
, kTestAppIdA
));