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.
5 #import <Cocoa/Cocoa.h>
8 #include "apps/app_lifetime_monitor_factory.h"
9 #include "apps/switches.h"
10 #include "base/auto_reset.h"
11 #include "base/callback.h"
12 #include "base/files/file_path_watcher.h"
13 #include "base/mac/foundation_util.h"
14 #include "base/mac/launch_services_util.h"
15 #include "base/mac/mac_util.h"
16 #include "base/mac/scoped_nsobject.h"
17 #include "base/path_service.h"
18 #include "base/process/launch.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "base/test/test_timeouts.h"
21 #include "chrome/browser/apps/app_browsertest_util.h"
22 #include "chrome/browser/apps/app_shim/app_shim_handler_mac.h"
23 #include "chrome/browser/apps/app_shim/app_shim_host_manager_mac.h"
24 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
25 #include "chrome/browser/browser_process.h"
26 #include "chrome/browser/extensions/launch_util.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/ui/browser_list.h"
29 #include "chrome/browser/ui/browser_window.h"
30 #include "chrome/browser/web_applications/web_app_mac.h"
31 #include "chrome/common/chrome_paths.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/common/mac/app_mode_common.h"
34 #include "content/public/test/test_utils.h"
35 #include "extensions/browser/app_window/native_app_window.h"
36 #include "extensions/browser/extension_prefs.h"
37 #include "extensions/test/extension_test_message_listener.h"
38 #import "ui/base/test/windowed_nsnotification_observer.h"
39 #import "ui/events/test/cocoa_test_event_utils.h"
43 // General end-to-end test for app shims.
44 class AppShimInteractiveTest : public extensions::PlatformAppBrowserTest {
46 AppShimInteractiveTest()
47 : auto_reset_(&g_app_shims_allow_update_and_launch_in_tests, true) {}
49 void SetUpCommandLine(base::CommandLine* command_line) override {
50 PlatformAppBrowserTest::SetUpCommandLine(command_line);
51 command_line->AppendSwitch(switches::kEnableNewBookmarkApps);
55 // Temporarily enable app shims.
56 base::AutoReset<bool> auto_reset_;
58 DISALLOW_COPY_AND_ASSIGN(AppShimInteractiveTest);
61 // Watches for changes to a file. This is designed to be used from the the UI
63 class WindowedFilePathWatcher
64 : public base::RefCountedThreadSafe<WindowedFilePathWatcher> {
66 WindowedFilePathWatcher(const base::FilePath& path)
67 : path_(path), observed_(false) {
68 content::BrowserThread::PostTask(
69 content::BrowserThread::FILE,
71 base::Bind(&WindowedFilePathWatcher::Watch, this));
78 run_loop_.reset(new base::RunLoop);
83 friend class base::RefCountedThreadSafe<WindowedFilePathWatcher>;
84 virtual ~WindowedFilePathWatcher() {}
87 watcher_.reset(new base::FilePathWatcher);
88 watcher_->Watch(path_.DirName(),
90 base::Bind(&WindowedFilePathWatcher::Observe, this));
93 void Observe(const base::FilePath& path, bool error) {
95 if (base::PathExists(path_)) {
97 content::BrowserThread::PostTask(
98 content::BrowserThread::UI,
100 base::Bind(&WindowedFilePathWatcher::StopRunLoop, this));
111 const base::FilePath path_;
112 scoped_ptr<base::FilePathWatcher> watcher_;
114 scoped_ptr<base::RunLoop> run_loop_;
116 DISALLOW_COPY_AND_ASSIGN(WindowedFilePathWatcher);
119 // Watches for an app shim to connect.
120 class WindowedAppShimLaunchObserver : public apps::AppShimHandler {
122 WindowedAppShimLaunchObserver(const std::string& app_id)
123 : app_mode_id_(app_id),
125 apps::AppShimHandler::RegisterHandler(app_id, this);
132 run_loop_.reset(new base::RunLoop);
136 // AppShimHandler overrides:
137 void OnShimLaunch(Host* host,
138 apps::AppShimLaunchType launch_type,
139 const std::vector<base::FilePath>& files) override {
140 // Remove self and pass through to the default handler.
141 apps::AppShimHandler::RemoveHandler(app_mode_id_);
142 apps::AppShimHandler::GetForAppMode(app_mode_id_)
143 ->OnShimLaunch(host, launch_type, files);
148 void OnShimClose(Host* host) override {}
149 void OnShimFocus(Host* host,
150 apps::AppShimFocusType focus_type,
151 const std::vector<base::FilePath>& files) override {}
152 void OnShimSetHidden(Host* host, bool hidden) override {}
153 void OnShimQuit(Host* host) override {}
156 std::string app_mode_id_;
158 scoped_ptr<base::RunLoop> run_loop_;
160 DISALLOW_COPY_AND_ASSIGN(WindowedAppShimLaunchObserver);
163 // Watches for a hosted app browser window to open.
164 class HostedAppBrowserListObserver : public chrome::BrowserListObserver {
166 explicit HostedAppBrowserListObserver(const std::string& app_id)
167 : app_id_(app_id), observed_add_(false), observed_removed_(false) {
168 BrowserList::AddObserver(this);
171 ~HostedAppBrowserListObserver() override {
172 BrowserList::RemoveObserver(this);
175 void WaitUntilAdded() {
179 run_loop_.reset(new base::RunLoop);
183 void WaitUntilRemoved() {
184 if (observed_removed_)
187 run_loop_.reset(new base::RunLoop);
191 // BrowserListObserver overrides:
192 void OnBrowserAdded(Browser* browser) override {
193 const extensions::Extension* app =
194 apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
195 if (app && app->id() == app_id_) {
196 observed_add_ = true;
202 void OnBrowserRemoved(Browser* browser) override {
203 const extensions::Extension* app =
204 apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
205 if (app && app->id() == app_id_) {
206 observed_removed_ = true;
215 bool observed_removed_;
216 scoped_ptr<base::RunLoop> run_loop_;
218 DISALLOW_COPY_AND_ASSIGN(HostedAppBrowserListObserver);
221 class AppLifetimeMonitorObserver : public apps::AppLifetimeMonitor::Observer {
223 AppLifetimeMonitorObserver(Profile* profile)
224 : profile_(profile), activated_count_(0), deactivated_count_(0) {
225 apps::AppLifetimeMonitorFactory::GetForProfile(profile_)->AddObserver(this);
227 ~AppLifetimeMonitorObserver() override {
228 apps::AppLifetimeMonitorFactory::GetForProfile(profile_)
229 ->RemoveObserver(this);
232 int activated_count() { return activated_count_; }
233 int deactivated_count() { return deactivated_count_; }
236 // AppLifetimeMonitor::Observer overrides:
237 void OnAppActivated(Profile* profile, const std::string& app_id) override {
240 void OnAppDeactivated(Profile* profile, const std::string& app_id) override {
241 ++deactivated_count_;
246 int activated_count_;
247 int deactivated_count_;
249 DISALLOW_COPY_AND_ASSIGN(AppLifetimeMonitorObserver);
252 NSString* GetBundleID(const base::FilePath& shim_path) {
253 base::FilePath plist_path = shim_path.Append("Contents").Append("Info.plist");
254 NSMutableDictionary* plist = [NSMutableDictionary
255 dictionaryWithContentsOfFile:base::mac::FilePathToNSString(plist_path)];
256 return [plist objectForKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
259 bool HasAppShimHost(Profile* profile, const std::string& app_id) {
260 return g_browser_process->platform_part()
261 ->app_shim_host_manager()
262 ->extension_app_shim_handler()
263 ->FindHost(profile, app_id);
266 base::FilePath GetAppShimPath(Profile* profile,
267 const extensions::Extension* app) {
268 // Use a WebAppShortcutCreator to get the path.
269 scoped_ptr<web_app::ShortcutInfo> shortcut_info =
270 web_app::ShortcutInfoForExtensionAndProfile(app, profile);
271 web_app::WebAppShortcutCreator shortcut_creator(
272 web_app::GetWebAppDataDirectory(profile->GetPath(), app->id(), GURL()),
273 shortcut_info.get(), extensions::FileHandlersInfo());
274 return shortcut_creator.GetInternalShortcutPath();
277 void UpdateAppAndAwaitShimCreation(Profile* profile,
278 const extensions::Extension* app,
279 const base::FilePath& shim_path) {
280 // Create the internal app shim by simulating an app update. FilePathWatcher
281 // is used to wait for file operations on the shim to be finished before
282 // attempting to launch it. Since all of the file operations are done in the
283 // same event on the FILE thread, everything will be done by the time the
284 // watcher's callback is executed.
285 scoped_refptr<WindowedFilePathWatcher> file_watcher =
286 new WindowedFilePathWatcher(shim_path);
287 web_app::UpdateAllShortcuts(base::string16(), profile, app);
288 file_watcher->Wait();
291 Browser* GetFirstHostedAppWindow() {
292 BrowserList* browsers =
293 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE);
294 for (Browser* browser : *browsers) {
295 const extensions::Extension* extension =
296 apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
297 if (extension && extension->is_hosted_app())
307 // Shims require static libraries http://crbug.com/386024.
308 #if defined(COMPONENT_BUILD)
309 #define MAYBE_Launch DISABLED_Launch
310 #define MAYBE_HostedAppLaunch DISABLED_HostedAppLaunch
311 #define MAYBE_ShowWindow DISABLED_ShowWindow
312 #define MAYBE_RebuildShim DISABLED_RebuildShim
314 #define MAYBE_Launch Launch
315 #define MAYBE_HostedAppLaunch DISABLED_HostedAppLaunch
316 #define MAYBE_ShowWindow ShowWindow
317 // http://crbug.com/517744 HostedAppLaunch fails with open as tab for apps
318 // http://crbug.com/509774 this test is flaky so is disabled even in the
320 #define MAYBE_RebuildShim DISABLED_RebuildShim
323 IN_PROC_BROWSER_TEST_F(AppShimInteractiveTest, MAYBE_HostedAppLaunch) {
324 const extensions::Extension* app = InstallHostedApp();
326 base::FilePath shim_path = GetAppShimPath(profile(), app);
327 EXPECT_FALSE(base::PathExists(shim_path));
329 UpdateAppAndAwaitShimCreation(profile(), app, shim_path);
330 ASSERT_TRUE(base::PathExists(shim_path));
331 NSString* bundle_id = GetBundleID(shim_path);
333 // Explicitly set the launch type to open in a new window.
334 extensions::SetLaunchType(profile(), app->id(),
335 extensions::LAUNCH_TYPE_WINDOW);
337 // Case 1: Launch the hosted app, it should start the shim.
339 base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer;
340 ns_observer.reset([[WindowedNSNotificationObserver alloc]
341 initForWorkspaceNotification:NSWorkspaceDidLaunchApplicationNotification
342 bundleId:bundle_id]);
343 WindowedAppShimLaunchObserver observer(app->id());
344 LaunchHostedApp(app);
348 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
349 EXPECT_TRUE(GetFirstHostedAppWindow());
351 NSArray* running_shim = [NSRunningApplication
352 runningApplicationsWithBundleIdentifier:bundle_id];
353 ASSERT_EQ(1u, [running_shim count]);
355 ns_observer.reset([[WindowedNSNotificationObserver alloc]
356 initForWorkspaceNotification:
357 NSWorkspaceDidTerminateApplicationNotification
358 bundleId:bundle_id]);
359 [base::mac::ObjCCastStrict<NSRunningApplication>(
360 [running_shim objectAtIndex:0]) terminate];
363 EXPECT_FALSE(GetFirstHostedAppWindow());
364 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
367 // Case 2: Launch the shim, it should start the hosted app.
369 HostedAppBrowserListObserver listener(app->id());
370 base::CommandLine shim_cmdline(base::CommandLine::NO_PROGRAM);
371 shim_cmdline.AppendSwitch(app_mode::kLaunchedForTest);
372 ProcessSerialNumber shim_psn;
373 ASSERT_TRUE(base::mac::OpenApplicationWithPath(
374 shim_path, shim_cmdline, kLSLaunchDefaults, &shim_psn));
375 listener.WaitUntilAdded();
377 ASSERT_TRUE(GetFirstHostedAppWindow());
378 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
380 // If the window is closed, the shim should quit.
382 EXPECT_EQ(noErr, GetProcessPID(&shim_psn, &shim_pid));
383 GetFirstHostedAppWindow()->window()->Close();
384 // Wait for the window to be closed.
385 listener.WaitUntilRemoved();
386 base::Process shim_process(shim_pid);
388 ASSERT_TRUE(shim_process.WaitForExitWithTimeout(
389 TestTimeouts::action_timeout(), &exit_code));
391 EXPECT_FALSE(GetFirstHostedAppWindow());
392 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
396 // Test that launching the shim for an app starts the app, and vice versa.
397 // These two cases are combined because the time to run the test is dominated
398 // by loading the extension and creating the shim.
399 IN_PROC_BROWSER_TEST_F(AppShimInteractiveTest, MAYBE_Launch) {
400 const extensions::Extension* app = InstallPlatformApp("minimal");
402 base::FilePath shim_path = GetAppShimPath(profile(), app);
403 EXPECT_FALSE(base::PathExists(shim_path));
405 UpdateAppAndAwaitShimCreation(profile(), app, shim_path);
406 ASSERT_TRUE(base::PathExists(shim_path));
407 NSString* bundle_id = GetBundleID(shim_path);
409 // Case 1: Launch the app, it should start the shim.
411 base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer;
412 ns_observer.reset([[WindowedNSNotificationObserver alloc]
413 initForWorkspaceNotification:NSWorkspaceDidLaunchApplicationNotification
414 bundleId:bundle_id]);
415 WindowedAppShimLaunchObserver observer(app->id());
416 LaunchPlatformApp(app);
420 EXPECT_TRUE(GetFirstAppWindow());
421 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
423 // Quitting the shim will eventually cause it to quit. It actually
424 // intercepts the -terminate, sends an AppShimHostMsg_QuitApp to Chrome,
425 // and returns NSTerminateLater. Chrome responds by closing all windows of
426 // the app. Once all windows are closed, Chrome closes the IPC channel,
427 // which causes the shim to actually terminate.
428 NSArray* running_shim = [NSRunningApplication
429 runningApplicationsWithBundleIdentifier:bundle_id];
430 ASSERT_EQ(1u, [running_shim count]);
432 ns_observer.reset([[WindowedNSNotificationObserver alloc]
433 initForWorkspaceNotification:
434 NSWorkspaceDidTerminateApplicationNotification
435 bundleId:bundle_id]);
436 [base::mac::ObjCCastStrict<NSRunningApplication>(
437 [running_shim objectAtIndex:0]) terminate];
440 EXPECT_FALSE(GetFirstAppWindow());
441 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
444 // Case 2: Launch the shim, it should start the app.
446 ExtensionTestMessageListener launched_listener("Launched", false);
447 base::CommandLine shim_cmdline(base::CommandLine::NO_PROGRAM);
448 shim_cmdline.AppendSwitch(app_mode::kLaunchedForTest);
449 ProcessSerialNumber shim_psn;
450 ASSERT_TRUE(base::mac::OpenApplicationWithPath(
451 shim_path, shim_cmdline, kLSLaunchDefaults, &shim_psn));
452 ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
454 ASSERT_TRUE(GetFirstAppWindow());
455 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
457 // If the window is closed, the shim should quit.
459 EXPECT_EQ(noErr, GetProcessPID(&shim_psn, &shim_pid));
460 GetFirstAppWindow()->GetBaseWindow()->Close();
461 base::Process shim_process(shim_pid);
463 ASSERT_TRUE(shim_process.WaitForExitWithTimeout(
464 TestTimeouts::action_timeout(), &exit_code));
466 EXPECT_FALSE(GetFirstAppWindow());
467 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
471 // Test that the shim's lifetime depends on the visibility of windows. I.e. the
472 // shim is only active when there are visible windows.
473 IN_PROC_BROWSER_TEST_F(AppShimInteractiveTest, MAYBE_ShowWindow) {
474 const extensions::Extension* app = InstallPlatformApp("hidden");
476 base::FilePath shim_path = GetAppShimPath(profile(), app);
477 EXPECT_FALSE(base::PathExists(shim_path));
479 UpdateAppAndAwaitShimCreation(profile(), app, shim_path);
480 ASSERT_TRUE(base::PathExists(shim_path));
481 NSString* bundle_id = GetBundleID(shim_path);
483 // It's impractical to confirm that the shim did not launch by timing out, so
484 // instead we watch AppLifetimeMonitor::Observer::OnAppActivated.
485 AppLifetimeMonitorObserver lifetime_observer(profile());
487 // Launch the app. It should create a hidden window, but the shim should not
490 ExtensionTestMessageListener launched_listener("Launched", false);
491 LaunchPlatformApp(app);
492 EXPECT_TRUE(launched_listener.WaitUntilSatisfied());
494 extensions::AppWindow* window_1 = GetFirstAppWindow();
495 ASSERT_TRUE(window_1);
496 EXPECT_TRUE(window_1->is_hidden());
497 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
498 EXPECT_EQ(0, lifetime_observer.activated_count());
500 // Showing the window causes the shim to launch.
502 base::scoped_nsobject<WindowedNSNotificationObserver>
503 ns_observer([[WindowedNSNotificationObserver alloc]
504 initForWorkspaceNotification:NSWorkspaceDidLaunchApplicationNotification
505 bundleId:bundle_id]);
506 WindowedAppShimLaunchObserver observer(app->id());
507 window_1->Show(extensions::AppWindow::SHOW_INACTIVE);
510 EXPECT_EQ(1, lifetime_observer.activated_count());
511 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
514 // Hiding the window causes the shim to quit.
516 base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer(
517 [[WindowedNSNotificationObserver alloc]
518 initForWorkspaceNotification:
519 NSWorkspaceDidTerminateApplicationNotification
520 bundleId:bundle_id]);
523 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
526 // Launch a second window. It should not launch the shim.
528 ExtensionTestMessageListener launched_listener("Launched", false);
529 LaunchPlatformApp(app);
530 EXPECT_TRUE(launched_listener.WaitUntilSatisfied());
532 const extensions::AppWindowRegistry::AppWindowList& app_windows =
533 extensions::AppWindowRegistry::Get(profile())->app_windows();
534 EXPECT_EQ(2u, app_windows.size());
535 extensions::AppWindow* window_2 = app_windows.front();
536 EXPECT_NE(window_1, window_2);
537 ASSERT_TRUE(window_2);
538 EXPECT_TRUE(window_2->is_hidden());
539 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
540 EXPECT_EQ(1, lifetime_observer.activated_count());
542 // Showing one of the windows should launch the shim.
544 base::scoped_nsobject<WindowedNSNotificationObserver>
545 ns_observer([[WindowedNSNotificationObserver alloc]
546 initForWorkspaceNotification:NSWorkspaceDidLaunchApplicationNotification
547 bundleId:bundle_id]);
548 WindowedAppShimLaunchObserver observer(app->id());
549 window_1->Show(extensions::AppWindow::SHOW_INACTIVE);
552 EXPECT_EQ(2, lifetime_observer.activated_count());
553 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
554 EXPECT_TRUE(window_2->is_hidden());
557 // Showing the other window does nothing.
559 window_2->Show(extensions::AppWindow::SHOW_INACTIVE);
560 EXPECT_EQ(2, lifetime_observer.activated_count());
563 // Showing an already visible window does nothing.
565 window_1->Show(extensions::AppWindow::SHOW_INACTIVE);
566 EXPECT_EQ(2, lifetime_observer.activated_count());
569 // Hiding one window does nothing.
571 AppLifetimeMonitorObserver deactivate_observer(profile());
573 EXPECT_EQ(0, deactivate_observer.deactivated_count());
576 // Hiding other window causes the shim to quit.
578 AppLifetimeMonitorObserver deactivate_observer(profile());
579 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
580 base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer(
581 [[WindowedNSNotificationObserver alloc]
582 initForWorkspaceNotification:
583 NSWorkspaceDidTerminateApplicationNotification
584 bundleId:bundle_id]);
587 EXPECT_EQ(1, deactivate_observer.deactivated_count());
588 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
592 #if defined(ARCH_CPU_64_BITS)
594 // Tests that a 32 bit shim attempting to launch 64 bit Chrome will eventually
596 IN_PROC_BROWSER_TEST_F(AppShimInteractiveTest, MAYBE_RebuildShim) {
597 // Get the 32 bit shim.
598 base::FilePath test_data_dir;
599 PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
600 base::FilePath shim_path_32 =
601 test_data_dir.Append("app_shim").Append("app_shim_32_bit.app");
602 EXPECT_TRUE(base::PathExists(shim_path_32));
605 const extensions::Extension* app = InstallPlatformApp("minimal");
607 // Use WebAppShortcutCreator to create a 64 bit shim.
608 scoped_ptr<web_app::ShortcutInfo> shortcut_info =
609 web_app::ShortcutInfoForExtensionAndProfile(app, profile());
610 web_app::WebAppShortcutCreator shortcut_creator(
611 web_app::GetWebAppDataDirectory(profile()->GetPath(), app->id(), GURL()),
612 shortcut_info.get(), extensions::FileHandlersInfo());
613 shortcut_creator.UpdateShortcuts();
614 base::FilePath shim_path = shortcut_creator.GetInternalShortcutPath();
615 NSMutableDictionary* plist_64 = [NSMutableDictionary
616 dictionaryWithContentsOfFile:base::mac::FilePathToNSString(
617 shim_path.Append("Contents").Append("Info.plist"))];
619 // Copy 32 bit shim to where it's expected to be.
620 // CopyDirectory doesn't seem to work when copying and renaming in one go.
621 ASSERT_TRUE(base::DeleteFile(shim_path, true));
622 ASSERT_TRUE(base::PathExists(shim_path.DirName()));
623 ASSERT_TRUE(base::CopyDirectory(shim_path_32, shim_path.DirName(), true));
624 ASSERT_TRUE(base::Move(shim_path.DirName().Append(shim_path_32.BaseName()),
626 ASSERT_TRUE(base::PathExists(
627 shim_path.Append("Contents").Append("MacOS").Append("app_mode_loader")));
629 // Fix up the plist so that it matches the installed test app.
630 NSString* plist_path = base::mac::FilePathToNSString(
631 shim_path.Append("Contents").Append("Info.plist"));
632 NSMutableDictionary* plist =
633 [NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
635 NSArray* keys_to_copy = @[
636 base::mac::CFToNSCast(kCFBundleIdentifierKey),
637 base::mac::CFToNSCast(kCFBundleNameKey),
638 app_mode::kCrAppModeShortcutIDKey,
639 app_mode::kCrAppModeUserDataDirKey,
640 app_mode::kBrowserBundleIDKey
642 for (NSString* key in keys_to_copy) {
643 [plist setObject:[plist_64 objectForKey:key]
646 [plist writeToFile:plist_path
649 base::mac::RemoveQuarantineAttribute(shim_path);
651 // Launch the shim, it should start the app and ultimately connect over IPC.
652 // This actually happens in multiple launches of the shim:
653 // (1) The shim will fail and instead launch Chrome with --app-id so that the
655 // (2) Chrome launches the shim in response to an app starting, this time the
656 // shim launches Chrome with --app-shim-error, which causes Chrome to
658 // (3) After rebuilding, Chrome again launches the shim and expects it to
660 ExtensionTestMessageListener launched_listener("Launched", false);
661 base::CommandLine shim_cmdline(base::CommandLine::NO_PROGRAM);
662 ASSERT_TRUE(base::mac::OpenApplicationWithPath(
663 shim_path, shim_cmdline, kLSLaunchDefaults, NULL));
665 // Wait for the app to start (1). At this point there is no shim host.
666 ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
667 EXPECT_FALSE(HasAppShimHost(profile(), app->id()));
669 // Wait for the rebuilt shim to connect (3). This does not race with the app
670 // starting (1) because Chrome only launches the shim (2) after the app
671 // starts. Then Chrome must handle --app-shim-error on the UI thread before
672 // the shim is rebuilt.
673 WindowedAppShimLaunchObserver(app->id()).Wait();
675 EXPECT_TRUE(GetFirstAppWindow());
676 EXPECT_TRUE(HasAppShimHost(profile(), app->id()));
679 #endif // defined(ARCH_CPU_64_BITS)