Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(330)

Side by Side Diff: apps/app_shim/app_shim_interactive_uitest_mac.mm

Issue 585123004: Mac: Give app_shim code a nicer home (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: various cleanups Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « apps/app_shim/app_shim_host_manager_mac.mm ('k') | apps/app_shim/app_shim_launch.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 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 #import <Cocoa/Cocoa.h>
6 #include <vector>
7
8 #include "apps/app_shim/app_shim_handler_mac.h"
9 #include "apps/app_shim/app_shim_host_manager_mac.h"
10 #include "apps/app_shim/extension_app_shim_handler_mac.h"
11 #include "apps/switches.h"
12 #include "base/auto_reset.h"
13 #include "base/callback.h"
14 #include "base/files/file_path_watcher.h"
15 #include "base/mac/foundation_util.h"
16 #include "base/mac/launch_services_util.h"
17 #include "base/mac/mac_util.h"
18 #include "base/mac/scoped_nsobject.h"
19 #include "base/path_service.h"
20 #include "base/process/launch.h"
21 #include "base/strings/sys_string_conversions.h"
22 #include "base/test/test_timeouts.h"
23 #include "chrome/browser/apps/app_browsertest_util.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/web_applications/web_app_mac.h"
27 #include "chrome/common/chrome_paths.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/mac/app_mode_common.h"
30 #include "content/public/test/test_utils.h"
31 #include "extensions/browser/app_window/native_app_window.h"
32 #include "extensions/browser/extension_registry.h"
33 #include "extensions/test/extension_test_message_listener.h"
34 #import "ui/events/test/cocoa_test_event_utils.h"
35
36 namespace {
37
38 // General end-to-end test for app shims.
39 class AppShimInteractiveTest : public extensions::PlatformAppBrowserTest {
40 protected:
41 AppShimInteractiveTest()
42 : auto_reset_(&g_app_shims_allow_update_and_launch_in_tests, true) {}
43
44 private:
45 // Temporarily enable app shims.
46 base::AutoReset<bool> auto_reset_;
47
48 DISALLOW_COPY_AND_ASSIGN(AppShimInteractiveTest);
49 };
50
51 // Watches for changes to a file. This is designed to be used from the the UI
52 // thread.
53 class WindowedFilePathWatcher
54 : public base::RefCountedThreadSafe<WindowedFilePathWatcher> {
55 public:
56 WindowedFilePathWatcher(const base::FilePath& path) : observed_(false) {
57 content::BrowserThread::PostTask(
58 content::BrowserThread::FILE,
59 FROM_HERE,
60 base::Bind(&WindowedFilePathWatcher::Watch, this, path));
61 }
62
63 void Wait() {
64 if (observed_)
65 return;
66
67 run_loop_.reset(new base::RunLoop);
68 run_loop_->Run();
69 }
70
71 protected:
72 friend class base::RefCountedThreadSafe<WindowedFilePathWatcher>;
73 virtual ~WindowedFilePathWatcher() {}
74
75 void Watch(const base::FilePath& path) {
76 watcher_.Watch(
77 path, false, base::Bind(&WindowedFilePathWatcher::Observe, this));
78 }
79
80 void Observe(const base::FilePath& path, bool error) {
81 content::BrowserThread::PostTask(
82 content::BrowserThread::UI,
83 FROM_HERE,
84 base::Bind(&WindowedFilePathWatcher::StopRunLoop, this));
85 }
86
87 void StopRunLoop() {
88 observed_ = true;
89 if (run_loop_.get())
90 run_loop_->Quit();
91 }
92
93 private:
94 base::FilePathWatcher watcher_;
95 bool observed_;
96 scoped_ptr<base::RunLoop> run_loop_;
97
98 DISALLOW_COPY_AND_ASSIGN(WindowedFilePathWatcher);
99 };
100
101 // Watches for an app shim to connect.
102 class WindowedAppShimLaunchObserver : public apps::AppShimHandler {
103 public:
104 WindowedAppShimLaunchObserver(const std::string& app_id)
105 : app_mode_id_(app_id),
106 observed_(false) {
107 apps::AppShimHandler::RegisterHandler(app_id, this);
108 }
109
110 void Wait() {
111 if (observed_)
112 return;
113
114 run_loop_.reset(new base::RunLoop);
115 run_loop_->Run();
116 }
117
118 // AppShimHandler overrides:
119 virtual void OnShimLaunch(Host* host,
120 apps::AppShimLaunchType launch_type,
121 const std::vector<base::FilePath>& files) OVERRIDE {
122 // Remove self and pass through to the default handler.
123 apps::AppShimHandler::RemoveHandler(app_mode_id_);
124 apps::AppShimHandler::GetForAppMode(app_mode_id_)
125 ->OnShimLaunch(host, launch_type, files);
126 observed_ = true;
127 if (run_loop_.get())
128 run_loop_->Quit();
129 }
130 virtual void OnShimClose(Host* host) OVERRIDE {}
131 virtual void OnShimFocus(Host* host,
132 apps::AppShimFocusType focus_type,
133 const std::vector<base::FilePath>& files) OVERRIDE {}
134 virtual void OnShimSetHidden(Host* host, bool hidden) OVERRIDE {}
135 virtual void OnShimQuit(Host* host) OVERRIDE {}
136
137 private:
138 std::string app_mode_id_;
139 bool observed_;
140 scoped_ptr<base::RunLoop> run_loop_;
141
142 DISALLOW_COPY_AND_ASSIGN(WindowedAppShimLaunchObserver);
143 };
144
145 NSString* GetBundleID(const base::FilePath& shim_path) {
146 base::FilePath plist_path = shim_path.Append("Contents").Append("Info.plist");
147 NSMutableDictionary* plist = [NSMutableDictionary
148 dictionaryWithContentsOfFile:base::mac::FilePathToNSString(plist_path)];
149 return [plist objectForKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
150 }
151
152 bool HasAppShimHost(Profile* profile, const std::string& app_id) {
153 return g_browser_process->platform_part()
154 ->app_shim_host_manager()
155 ->extension_app_shim_handler()
156 ->FindHost(profile, app_id);
157 }
158
159 } // namespace
160
161 // Watches for NSNotifications from the shared workspace.
162 @interface WindowedNSNotificationObserver : NSObject {
163 @private
164 base::scoped_nsobject<NSString> bundleId_;
165 BOOL notificationReceived_;
166 scoped_ptr<base::RunLoop> runLoop_;
167 }
168
169 - (id)initForNotification:(NSString*)name
170 andBundleId:(NSString*)bundleId;
171 - (void)observe:(NSNotification*)notification;
172 - (void)wait;
173 @end
174
175 @implementation WindowedNSNotificationObserver
176
177 - (id)initForNotification:(NSString*)name
178 andBundleId:(NSString*)bundleId {
179 if (self = [super init]) {
180 bundleId_.reset([[bundleId copy] retain]);
181 [[[NSWorkspace sharedWorkspace] notificationCenter]
182 addObserver:self
183 selector:@selector(observe:)
184 name:name
185 object:nil];
186 }
187 return self;
188 }
189
190 - (void)observe:(NSNotification*)notification {
191 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
192
193 NSRunningApplication* application =
194 [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
195 if (![[application bundleIdentifier] isEqualToString:bundleId_])
196 return;
197
198 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
199 notificationReceived_ = YES;
200 if (runLoop_.get())
201 runLoop_->Quit();
202 }
203
204 - (void)wait {
205 if (notificationReceived_)
206 return;
207
208 runLoop_.reset(new base::RunLoop);
209 runLoop_->Run();
210 }
211
212 @end
213
214 namespace apps {
215
216 // Shims require static libraries http://crbug.com/386024.
217 // This test is flaky on OSX. http://crbug.com/415422
218 #if defined(COMPONENT_BUILD) || defined(OS_MACOSX)
219 #define MAYBE_Launch DISABLED_Launch
220 #define MAYBE_RebuildShim DISABLED_RebuildShim
221 #else
222 #define MAYBE_Launch Launch
223 #define MAYBE_RebuildShim RebuildShim
224 #endif
225
226 // Test that launching the shim for an app starts the app, and vice versa.
227 // These two cases are combined because the time to run the test is dominated
228 // by loading the extension and creating the shim.
229 IN_PROC_BROWSER_TEST_F(AppShimInteractiveTest, MAYBE_Launch) {
230 // Install the app.
231 const extensions::Extension* app = InstallPlatformApp("minimal");
232
233 // Use a WebAppShortcutCreator to get the path.
234 web_app::WebAppShortcutCreator shortcut_creator(
235 web_app::GetWebAppDataDirectory(profile()->GetPath(), app->id(), GURL()),
236 web_app::ShortcutInfoForExtensionAndProfile(app, profile()),
237 extensions::FileHandlersInfo());
238 base::FilePath shim_path = shortcut_creator.GetInternalShortcutPath();
239 EXPECT_FALSE(base::PathExists(shim_path));
240
241 // Create the internal app shim by simulating an app update. FilePathWatcher
242 // is used to wait for file operations on the shim to be finished before
243 // attempting to launch it. Since all of the file operations are done in the
244 // same event on the FILE thread, everything will be done by the time the
245 // watcher's callback is executed.
246 scoped_refptr<WindowedFilePathWatcher> file_watcher =
247 new WindowedFilePathWatcher(shim_path);
248 web_app::UpdateAllShortcuts(base::string16(), profile(), app);
249 file_watcher->Wait();
250 NSString* bundle_id = GetBundleID(shim_path);
251
252 // Case 1: Launch the shim, it should start the app.
253 {
254 ExtensionTestMessageListener launched_listener("Launched", false);
255 CommandLine shim_cmdline(CommandLine::NO_PROGRAM);
256 shim_cmdline.AppendSwitch(app_mode::kLaunchedForTest);
257 ProcessSerialNumber shim_psn;
258 ASSERT_TRUE(base::mac::OpenApplicationWithPath(
259 shim_path, shim_cmdline, kLSLaunchDefaults, &shim_psn));
260 ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
261
262 ASSERT_TRUE(GetFirstAppWindow());
263 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
264
265 // If the window is closed, the shim should quit.
266 pid_t shim_pid;
267 EXPECT_EQ(noErr, GetProcessPID(&shim_psn, &shim_pid));
268 GetFirstAppWindow()->GetBaseWindow()->Close();
269 ASSERT_TRUE(
270 base::WaitForSingleProcess(shim_pid, TestTimeouts::action_timeout()));
271
272 EXPECT_FALSE(GetFirstAppWindow());
273 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
274 }
275
276 // Case 2: Launch the app, it should start the shim.
277 {
278 base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer;
279 ns_observer.reset([[WindowedNSNotificationObserver alloc]
280 initForNotification:NSWorkspaceDidLaunchApplicationNotification
281 andBundleId:bundle_id]);
282 WindowedAppShimLaunchObserver observer(app->id());
283 LaunchPlatformApp(app);
284 [ns_observer wait];
285 observer.Wait();
286
287 EXPECT_TRUE(GetFirstAppWindow());
288 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
289
290 // Quitting the shim will eventually cause it to quit. It actually
291 // intercepts the -terminate, sends an AppShimHostMsg_QuitApp to Chrome,
292 // and returns NSTerminateLater. Chrome responds by closing all windows of
293 // the app. Once all windows are closed, Chrome closes the IPC channel,
294 // which causes the shim to actually terminate.
295 NSArray* running_shim = [NSRunningApplication
296 runningApplicationsWithBundleIdentifier:bundle_id];
297 ASSERT_EQ(1u, [running_shim count]);
298
299 ns_observer.reset([[WindowedNSNotificationObserver alloc]
300 initForNotification:NSWorkspaceDidTerminateApplicationNotification
301 andBundleId:bundle_id]);
302 [base::mac::ObjCCastStrict<NSRunningApplication>(
303 [running_shim objectAtIndex:0]) terminate];
304 [ns_observer wait];
305
306 EXPECT_FALSE(GetFirstAppWindow());
307 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
308 }
309 }
310
311 #if defined(ARCH_CPU_64_BITS)
312
313 // Tests that a 32 bit shim attempting to launch 64 bit Chrome will eventually
314 // be rebuilt.
315 IN_PROC_BROWSER_TEST_F(AppShimInteractiveTest, MAYBE_RebuildShim) {
316 // Get the 32 bit shim.
317 base::FilePath test_data_dir;
318 PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
319 base::FilePath shim_path_32 =
320 test_data_dir.Append("app_shim").Append("app_shim_32_bit.app");
321 EXPECT_TRUE(base::PathExists(shim_path_32));
322
323 // Install test app.
324 const extensions::Extension* app = InstallPlatformApp("minimal");
325
326 // Use WebAppShortcutCreator to create a 64 bit shim.
327 web_app::WebAppShortcutCreator shortcut_creator(
328 web_app::GetWebAppDataDirectory(profile()->GetPath(), app->id(), GURL()),
329 web_app::ShortcutInfoForExtensionAndProfile(app, profile()),
330 extensions::FileHandlersInfo());
331 shortcut_creator.UpdateShortcuts();
332 base::FilePath shim_path = shortcut_creator.GetInternalShortcutPath();
333 NSMutableDictionary* plist_64 = [NSMutableDictionary
334 dictionaryWithContentsOfFile:base::mac::FilePathToNSString(
335 shim_path.Append("Contents").Append("Info.plist"))];
336
337 // Copy 32 bit shim to where it's expected to be.
338 // CopyDirectory doesn't seem to work when copying and renaming in one go.
339 ASSERT_TRUE(base::DeleteFile(shim_path, true));
340 ASSERT_TRUE(base::PathExists(shim_path.DirName()));
341 ASSERT_TRUE(base::CopyDirectory(shim_path_32, shim_path.DirName(), true));
342 ASSERT_TRUE(base::Move(shim_path.DirName().Append(shim_path_32.BaseName()),
343 shim_path));
344 ASSERT_TRUE(base::PathExists(
345 shim_path.Append("Contents").Append("MacOS").Append("app_mode_loader")));
346
347 // Fix up the plist so that it matches the installed test app.
348 NSString* plist_path = base::mac::FilePathToNSString(
349 shim_path.Append("Contents").Append("Info.plist"));
350 NSMutableDictionary* plist =
351 [NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
352
353 NSArray* keys_to_copy = @[
354 base::mac::CFToNSCast(kCFBundleIdentifierKey),
355 base::mac::CFToNSCast(kCFBundleNameKey),
356 app_mode::kCrAppModeShortcutIDKey,
357 app_mode::kCrAppModeUserDataDirKey,
358 app_mode::kBrowserBundleIDKey
359 ];
360 for (NSString* key in keys_to_copy) {
361 [plist setObject:[plist_64 objectForKey:key]
362 forKey:key];
363 }
364 [plist writeToFile:plist_path
365 atomically:YES];
366
367 base::mac::RemoveQuarantineAttribute(shim_path);
368
369 // Launch the shim, it should start the app and ultimately connect over IPC.
370 // This actually happens in multiple launches of the shim:
371 // (1) The shim will fail and instead launch Chrome with --app-id so that the
372 // app starts.
373 // (2) Chrome launches the shim in response to an app starting, this time the
374 // shim launches Chrome with --app-shim-error, which causes Chrome to
375 // rebuild the shim.
376 // (3) After rebuilding, Chrome again launches the shim and expects it to
377 // behave normally.
378 ExtensionTestMessageListener launched_listener("Launched", false);
379 CommandLine shim_cmdline(CommandLine::NO_PROGRAM);
380 ASSERT_TRUE(base::mac::OpenApplicationWithPath(
381 shim_path, shim_cmdline, kLSLaunchDefaults, NULL));
382
383 // Wait for the app to start (1). At this point there is no shim host.
384 ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
385 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
386
387 // Wait for the rebuilt shim to connect (3). This does not race with the app
388 // starting (1) because Chrome only launches the shim (2) after the app
389 // starts. Then Chrome must handle --app-shim-error on the UI thread before
390 // the shim is rebuilt.
391 WindowedAppShimLaunchObserver(app->id()).Wait();
392
393 EXPECT_TRUE(GetFirstAppWindow());
394 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
395 }
396
397 #endif // defined(ARCH_CPU_64_BITS)
398
399 } // namespace apps
OLDNEW
« no previous file with comments | « apps/app_shim/app_shim_host_manager_mac.mm ('k') | apps/app_shim/app_shim_launch.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698