Upstream version 10.38.222.0
[platform/framework/web/crosswalk.git] / src / apps / app_shim / chrome_main_app_mode_mac.mm
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 // On Mac, one can't make shortcuts with command-line arguments. Instead, we
6 // produce small app bundles which locate the Chromium framework and load it,
7 // passing the appropriate data. This is the entry point into the framework for
8 // those app bundles.
9
10 #import <Cocoa/Cocoa.h>
11 #include <vector>
12
13 #include "apps/app_shim/app_shim_messages.h"
14 #include "base/at_exit.h"
15 #include "base/command_line.h"
16 #include "base/files/file_path.h"
17 #include "base/file_util.h"
18 #include "base/logging.h"
19 #include "base/mac/bundle_locations.h"
20 #include "base/mac/foundation_util.h"
21 #include "base/mac/launch_services_util.h"
22 #include "base/mac/mac_logging.h"
23 #include "base/mac/mac_util.h"
24 #include "base/mac/scoped_nsautorelease_pool.h"
25 #include "base/mac/scoped_nsobject.h"
26 #include "base/mac/sdk_forward_declarations.h"
27 #include "base/message_loop/message_loop.h"
28 #include "base/path_service.h"
29 #include "base/strings/string_number_conversions.h"
30 #include "base/strings/sys_string_conversions.h"
31 #include "base/threading/thread.h"
32 #include "chrome/common/chrome_constants.h"
33 #include "chrome/common/chrome_paths.h"
34 #include "chrome/common/chrome_switches.h"
35 #include "chrome/common/mac/app_mode_common.h"
36 #include "grit/generated_resources.h"
37 #include "ipc/ipc_channel_proxy.h"
38 #include "ipc/ipc_listener.h"
39 #include "ipc/ipc_message.h"
40 #include "ui/base/resource/resource_bundle.h"
41 #include "ui/base/l10n/l10n_util.h"
42
43 namespace {
44
45 // Timeout in seconds to wait for a reply for the initial Apple Event. Note that
46 // kAEDefaultTimeout on Mac is "about one minute" according to Apple's
47 // documentation, but is no longer supported for asynchronous Apple Events.
48 const int kPingChromeTimeoutSeconds = 60;
49
50 const app_mode::ChromeAppModeInfo* g_info;
51 base::Thread* g_io_thread = NULL;
52
53 }  // namespace
54
55 class AppShimController;
56
57 // An application delegate to catch user interactions and send the appropriate
58 // IPC messages to Chrome.
59 @interface AppShimDelegate : NSObject<NSApplicationDelegate> {
60  @private
61   AppShimController* appShimController_;  // Weak, initially NULL.
62   BOOL terminateNow_;
63   BOOL terminateRequested_;
64   std::vector<base::FilePath> filesToOpenAtStartup_;
65 }
66
67 // The controller is initially NULL. Setting it indicates to the delegate that
68 // the controller has finished initialization.
69 - (void)setController:(AppShimController*)controller;
70
71 // Gets files that were queued because the controller was not ready.
72 // Returns whether any FilePaths were added to |out|.
73 - (BOOL)getFilesToOpenAtStartup:(std::vector<base::FilePath>*)out;
74
75 // If the controller is ready, this sends a FocusApp with the files to open.
76 // Otherwise, this adds the files to |filesToOpenAtStartup_|.
77 // Takes an array of NSString*.
78 - (void)openFiles:(NSArray*)filename;
79
80 // Terminate immediately. This is necessary as we override terminate: to send
81 // a QuitApp message.
82 - (void)terminateNow;
83
84 @end
85
86 // The AppShimController is responsible for communication with the main Chrome
87 // process, and generally controls the lifetime of the app shim process.
88 class AppShimController : public IPC::Listener {
89  public:
90   AppShimController();
91   virtual ~AppShimController();
92
93   // Called when the main Chrome process responds to the Apple Event ping that
94   // was sent, or when the ping fails (if |success| is false).
95   void OnPingChromeReply(bool success);
96
97   // Called |kPingChromeTimeoutSeconds| after startup, to allow a timeout on the
98   // ping event to be detected.
99   void OnPingChromeTimeout();
100
101   // Connects to Chrome and sends a LaunchApp message.
102   void Init();
103
104   // Create a channel from |socket_path| and send a LaunchApp message.
105   void CreateChannelAndSendLaunchApp(const base::FilePath& socket_path);
106
107   // Builds main menu bar items.
108   void SetUpMenu();
109
110   void SendSetAppHidden(bool hidden);
111
112   void SendQuitApp();
113
114   // Called when the app is activated, e.g. by clicking on it in the dock, by
115   // dropping a file on the dock icon, or by Cmd+Tabbing to it.
116   // Returns whether the message was sent.
117   bool SendFocusApp(apps::AppShimFocusType focus_type,
118                     const std::vector<base::FilePath>& files);
119
120  private:
121   // IPC::Listener implemetation.
122   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
123   virtual void OnChannelError() OVERRIDE;
124
125   // If Chrome failed to launch the app, |success| will be false and the app
126   // shim process should die.
127   void OnLaunchAppDone(apps::AppShimLaunchResult result);
128
129   // Hide this app.
130   void OnHide();
131
132   // Requests user attention.
133   void OnRequestUserAttention();
134
135   // Terminates the app shim process.
136   void Close();
137
138   base::FilePath user_data_dir_;
139   scoped_ptr<IPC::ChannelProxy> channel_;
140   base::scoped_nsobject<AppShimDelegate> delegate_;
141   bool launch_app_done_;
142   bool ping_chrome_reply_received_;
143
144   DISALLOW_COPY_AND_ASSIGN(AppShimController);
145 };
146
147 AppShimController::AppShimController()
148     : delegate_([[AppShimDelegate alloc] init]),
149       launch_app_done_(false),
150       ping_chrome_reply_received_(false) {
151   // Since AppShimController is created before the main message loop starts,
152   // NSApp will not be set, so use sharedApplication.
153   [[NSApplication sharedApplication] setDelegate:delegate_];
154 }
155
156 AppShimController::~AppShimController() {
157   // Un-set the delegate since NSApplication does not retain it.
158   [[NSApplication sharedApplication] setDelegate:nil];
159 }
160
161 void AppShimController::OnPingChromeReply(bool success) {
162   ping_chrome_reply_received_ = true;
163   if (!success) {
164     [NSApp terminate:nil];
165     return;
166   }
167
168   Init();
169 }
170
171 void AppShimController::OnPingChromeTimeout() {
172   if (!ping_chrome_reply_received_)
173     [NSApp terminate:nil];
174 }
175
176 void AppShimController::Init() {
177   DCHECK(g_io_thread);
178
179   SetUpMenu();
180
181   // Chrome will relaunch shims when relaunching apps.
182   if (base::mac::IsOSLionOrLater())
183     [NSApp disableRelaunchOnLogin];
184
185   // The user_data_dir for shims actually contains the app_data_path.
186   // I.e. <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/
187   user_data_dir_ = g_info->user_data_dir.DirName().DirName().DirName();
188   CHECK(!user_data_dir_.empty());
189
190   base::FilePath symlink_path =
191       user_data_dir_.Append(app_mode::kAppShimSocketSymlinkName);
192
193   base::FilePath socket_path;
194   if (!base::ReadSymbolicLink(symlink_path, &socket_path)) {
195     // The path in the user data dir is not a symlink, try connecting directly.
196     CreateChannelAndSendLaunchApp(symlink_path);
197     return;
198   }
199
200   app_mode::VerifySocketPermissions(socket_path);
201
202   CreateChannelAndSendLaunchApp(socket_path);
203 }
204
205 void AppShimController::CreateChannelAndSendLaunchApp(
206     const base::FilePath& socket_path) {
207   IPC::ChannelHandle handle(socket_path.value());
208   channel_ = IPC::ChannelProxy::Create(handle,
209                                        IPC::Channel::MODE_NAMED_CLIENT,
210                                        this,
211                                        g_io_thread->message_loop_proxy().get());
212
213   bool launched_by_chrome =
214       CommandLine::ForCurrentProcess()->HasSwitch(
215           app_mode::kLaunchedByChromeProcessId);
216   apps::AppShimLaunchType launch_type = launched_by_chrome ?
217           apps::APP_SHIM_LAUNCH_REGISTER_ONLY : apps::APP_SHIM_LAUNCH_NORMAL;
218
219   [delegate_ setController:this];
220
221   std::vector<base::FilePath> files;
222   [delegate_ getFilesToOpenAtStartup:&files];
223
224   channel_->Send(new AppShimHostMsg_LaunchApp(
225       g_info->profile_dir, g_info->app_mode_id, launch_type, files));
226 }
227
228 void AppShimController::SetUpMenu() {
229   NSString* title = base::SysUTF16ToNSString(g_info->app_mode_name);
230
231   // Create a main menu since [NSApp mainMenu] is nil.
232   base::scoped_nsobject<NSMenu> main_menu([[NSMenu alloc] initWithTitle:title]);
233
234   // The title of the first item is replaced by OSX with the name of the app and
235   // bold styling. Create a dummy item for this and make it hidden.
236   NSMenuItem* dummy_item = [main_menu addItemWithTitle:title
237                                                 action:nil
238                                          keyEquivalent:@""];
239   base::scoped_nsobject<NSMenu> dummy_submenu(
240       [[NSMenu alloc] initWithTitle:title]);
241   [dummy_item setSubmenu:dummy_submenu];
242   [dummy_item setHidden:YES];
243
244   // Construct an unbolded app menu, to match how it appears in the Chrome menu
245   // bar when the app is focused.
246   NSMenuItem* item = [main_menu addItemWithTitle:title
247                                           action:nil
248                                    keyEquivalent:@""];
249   base::scoped_nsobject<NSMenu> submenu([[NSMenu alloc] initWithTitle:title]);
250   [item setSubmenu:submenu];
251
252   // Add a quit entry.
253   NSString* quit_localized_string =
254       l10n_util::GetNSStringF(IDS_EXIT_MAC, g_info->app_mode_name);
255   [submenu addItemWithTitle:quit_localized_string
256                      action:@selector(terminate:)
257               keyEquivalent:@"q"];
258
259   // Add File, Edit, and Window menus. These are just here to make the
260   // transition smoother, i.e. from another application to the shim then to
261   // Chrome.
262   [main_menu addItemWithTitle:l10n_util::GetNSString(IDS_FILE_MENU_MAC)
263                        action:nil
264                 keyEquivalent:@""];
265   [main_menu addItemWithTitle:l10n_util::GetNSString(IDS_EDIT_MENU_MAC)
266                        action:nil
267                 keyEquivalent:@""];
268   [main_menu addItemWithTitle:l10n_util::GetNSString(IDS_WINDOW_MENU_MAC)
269                        action:nil
270                 keyEquivalent:@""];
271
272   [NSApp setMainMenu:main_menu];
273 }
274
275 void AppShimController::SendQuitApp() {
276   channel_->Send(new AppShimHostMsg_QuitApp);
277 }
278
279 bool AppShimController::OnMessageReceived(const IPC::Message& message) {
280   bool handled = true;
281   IPC_BEGIN_MESSAGE_MAP(AppShimController, message)
282     IPC_MESSAGE_HANDLER(AppShimMsg_LaunchApp_Done, OnLaunchAppDone)
283     IPC_MESSAGE_HANDLER(AppShimMsg_Hide, OnHide)
284     IPC_MESSAGE_HANDLER(AppShimMsg_RequestUserAttention, OnRequestUserAttention)
285     IPC_MESSAGE_UNHANDLED(handled = false)
286   IPC_END_MESSAGE_MAP()
287
288   return handled;
289 }
290
291 void AppShimController::OnChannelError() {
292   Close();
293 }
294
295 void AppShimController::OnLaunchAppDone(apps::AppShimLaunchResult result) {
296   if (result != apps::APP_SHIM_LAUNCH_SUCCESS) {
297     Close();
298     return;
299   }
300
301   std::vector<base::FilePath> files;
302   if ([delegate_ getFilesToOpenAtStartup:&files])
303     SendFocusApp(apps::APP_SHIM_FOCUS_OPEN_FILES, files);
304
305   launch_app_done_ = true;
306 }
307
308 void AppShimController::OnHide() {
309   [NSApp hide:nil];
310 }
311
312 void AppShimController::OnRequestUserAttention() {
313   [NSApp requestUserAttention:NSInformationalRequest];
314 }
315
316 void AppShimController::Close() {
317   [delegate_ terminateNow];
318 }
319
320 bool AppShimController::SendFocusApp(apps::AppShimFocusType focus_type,
321                                      const std::vector<base::FilePath>& files) {
322   if (launch_app_done_) {
323     channel_->Send(new AppShimHostMsg_FocusApp(focus_type, files));
324     return true;
325   }
326
327   return false;
328 }
329
330 void AppShimController::SendSetAppHidden(bool hidden) {
331   channel_->Send(new AppShimHostMsg_SetAppHidden(hidden));
332 }
333
334 @implementation AppShimDelegate
335
336 - (BOOL)getFilesToOpenAtStartup:(std::vector<base::FilePath>*)out {
337   if (filesToOpenAtStartup_.empty())
338     return NO;
339
340   out->insert(out->end(),
341               filesToOpenAtStartup_.begin(),
342               filesToOpenAtStartup_.end());
343   filesToOpenAtStartup_.clear();
344   return YES;
345 }
346
347 - (void)setController:(AppShimController*)controller {
348   appShimController_ = controller;
349 }
350
351 - (void)openFiles:(NSArray*)filenames {
352   std::vector<base::FilePath> filePaths;
353   for (NSString* filename in filenames)
354     filePaths.push_back(base::mac::NSStringToFilePath(filename));
355
356   // If the AppShimController is ready, try to send a FocusApp. If that fails,
357   // (e.g. if launching has not finished), enqueue the files.
358   if (appShimController_ &&
359       appShimController_->SendFocusApp(apps::APP_SHIM_FOCUS_OPEN_FILES,
360                                        filePaths)) {
361     return;
362   }
363
364   filesToOpenAtStartup_.insert(filesToOpenAtStartup_.end(),
365                                filePaths.begin(),
366                                filePaths.end());
367 }
368
369 - (BOOL)application:(NSApplication*)app
370            openFile:(NSString*)filename {
371   [self openFiles:@[filename]];
372   return YES;
373 }
374
375 - (void)application:(NSApplication*)app
376           openFiles:(NSArray*)filenames {
377   [self openFiles:filenames];
378   [app replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
379 }
380
381 - (BOOL)applicationOpenUntitledFile:(NSApplication*)app {
382   if (appShimController_) {
383     return appShimController_->SendFocusApp(apps::APP_SHIM_FOCUS_REOPEN,
384                                             std::vector<base::FilePath>());
385   }
386
387   return NO;
388 }
389
390 - (void)applicationWillBecomeActive:(NSNotification*)notification {
391   if (appShimController_) {
392     appShimController_->SendFocusApp(apps::APP_SHIM_FOCUS_NORMAL,
393                                      std::vector<base::FilePath>());
394   }
395 }
396
397 - (NSApplicationTerminateReply)
398     applicationShouldTerminate:(NSApplication*)sender {
399   if (terminateNow_ || !appShimController_)
400     return NSTerminateNow;
401
402   appShimController_->SendQuitApp();
403   // Wait for the channel to close before terminating.
404   terminateRequested_ = YES;
405   return NSTerminateLater;
406 }
407
408 - (void)applicationWillHide:(NSNotification*)notification {
409   if (appShimController_)
410     appShimController_->SendSetAppHidden(true);
411 }
412
413 - (void)applicationWillUnhide:(NSNotification*)notification {
414   if (appShimController_)
415     appShimController_->SendSetAppHidden(false);
416 }
417
418 - (void)terminateNow {
419   if (terminateRequested_) {
420     [NSApp replyToApplicationShouldTerminate:NSTerminateNow];
421     return;
422   }
423
424   terminateNow_ = YES;
425   [NSApp terminate:nil];
426 }
427
428 @end
429
430 //-----------------------------------------------------------------------------
431
432 // A ReplyEventHandler is a helper class to send an Apple Event to a process
433 // and call a callback when the reply returns.
434 //
435 // This is used to 'ping' the main Chrome process -- once Chrome has sent back
436 // an Apple Event reply, it's guaranteed that it has opened the IPC channel
437 // that the app shim will connect to.
438 @interface ReplyEventHandler : NSObject {
439   base::Callback<void(bool)> onReply_;
440   AEDesc replyEvent_;
441 }
442 // Sends an Apple Event to the process identified by |psn|, and calls |replyFn|
443 // when the reply is received. Internally this creates a ReplyEventHandler,
444 // which will delete itself once the reply event has been received.
445 + (void)pingProcess:(const ProcessSerialNumber&)psn
446             andCall:(base::Callback<void(bool)>)replyFn;
447 @end
448
449 @interface ReplyEventHandler (PrivateMethods)
450 // Initialise the reply event handler. Doesn't register any handlers until
451 // |-pingProcess:| is called. |replyFn| is the function to be called when the
452 // Apple Event reply arrives.
453 - (id)initWithCallback:(base::Callback<void(bool)>)replyFn;
454
455 // Sends an Apple Event ping to the process identified by |psn| and registers
456 // to listen for a reply.
457 - (void)pingProcess:(const ProcessSerialNumber&)psn;
458
459 // Called when a response is received from the target process for the ping sent
460 // by |-pingProcess:|.
461 - (void)message:(NSAppleEventDescriptor*)event
462       withReply:(NSAppleEventDescriptor*)reply;
463
464 // Calls |onReply_|, passing it |success| to specify whether the ping was
465 // successful.
466 - (void)closeWithSuccess:(bool)success;
467 @end
468
469 @implementation ReplyEventHandler
470 + (void)pingProcess:(const ProcessSerialNumber&)psn
471             andCall:(base::Callback<void(bool)>)replyFn {
472   // The object will release itself when the reply arrives, or possibly earlier
473   // if an unrecoverable error occurs.
474   ReplyEventHandler* handler =
475       [[ReplyEventHandler alloc] initWithCallback:replyFn];
476   [handler pingProcess:psn];
477 }
478 @end
479
480 @implementation ReplyEventHandler (PrivateMethods)
481 - (id)initWithCallback:(base::Callback<void(bool)>)replyFn {
482   if ((self = [super init])) {
483     onReply_ = replyFn;
484   }
485   return self;
486 }
487
488 - (void)pingProcess:(const ProcessSerialNumber&)psn {
489   // Register the reply listener.
490   NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
491   [em setEventHandler:self
492           andSelector:@selector(message:withReply:)
493         forEventClass:'aevt'
494            andEventID:'ansr'];
495   // Craft the Apple Event to send.
496   NSAppleEventDescriptor* target = [NSAppleEventDescriptor
497       descriptorWithDescriptorType:typeProcessSerialNumber
498                              bytes:&psn
499                             length:sizeof(psn)];
500   NSAppleEventDescriptor* initial_event =
501       [NSAppleEventDescriptor
502           appleEventWithEventClass:app_mode::kAEChromeAppClass
503                            eventID:app_mode::kAEChromeAppPing
504                   targetDescriptor:target
505                           returnID:kAutoGenerateReturnID
506                      transactionID:kAnyTransactionID];
507
508   // Note that AESendMessage effectively ignores kAEDefaultTimeout, because this
509   // call does not pass kAEWantReceipt (which is deprecated and unsupported on
510   // Mac). Instead, rely on OnPingChromeTimeout().
511   OSStatus status = AESendMessage(
512       [initial_event aeDesc], &replyEvent_, kAEQueueReply, kAEDefaultTimeout);
513   if (status != noErr) {
514     OSSTATUS_LOG(ERROR, status) << "AESendMessage";
515     [self closeWithSuccess:false];
516   }
517 }
518
519 - (void)message:(NSAppleEventDescriptor*)event
520       withReply:(NSAppleEventDescriptor*)reply {
521   [self closeWithSuccess:true];
522 }
523
524 - (void)closeWithSuccess:(bool)success {
525   onReply_.Run(success);
526   NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
527   [em removeEventHandlerForEventClass:'aevt' andEventID:'ansr'];
528   [self release];
529 }
530 @end
531
532 //-----------------------------------------------------------------------------
533
534 extern "C" {
535
536 // |ChromeAppModeStart()| is the point of entry into the framework from the app
537 // mode loader.
538 __attribute__((visibility("default")))
539 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info);
540
541 }  // extern "C"
542
543 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
544   CommandLine::Init(info->argc, info->argv);
545
546   base::mac::ScopedNSAutoreleasePool scoped_pool;
547   base::AtExitManager exit_manager;
548   chrome::RegisterPathProvider();
549
550   if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) {
551     RAW_LOG(ERROR, "App Mode Loader too old.");
552     return 1;
553   }
554   if (info->major_version > app_mode::kCurrentChromeAppModeInfoMajorVersion) {
555     RAW_LOG(ERROR, "Browser Framework too old to load App Shortcut.");
556     return 1;
557   }
558
559   g_info = info;
560
561   // Set bundle paths. This loads the bundles.
562   base::mac::SetOverrideOuterBundlePath(g_info->chrome_outer_bundle_path);
563   base::mac::SetOverrideFrameworkBundlePath(
564       g_info->chrome_versioned_path.Append(chrome::kFrameworkName));
565
566   // Calculate the preferred locale used by Chrome.
567   // We can't use l10n_util::OverrideLocaleWithCocoaLocale() because it calls
568   // [base::mac::OuterBundle() preferredLocalizations] which gets localizations
569   // from the bundle of the running app (i.e. it is equivalent to
570   // [[NSBundle mainBundle] preferredLocalizations]) instead of the target
571   // bundle.
572   NSArray* preferred_languages = [NSLocale preferredLanguages];
573   NSArray* supported_languages = [base::mac::OuterBundle() localizations];
574   std::string preferred_localization;
575   for (NSString* language in preferred_languages) {
576     if ([supported_languages containsObject:language]) {
577       preferred_localization = base::SysNSStringToUTF8(language);
578       break;
579     }
580   }
581   std::string locale = l10n_util::NormalizeLocale(
582       l10n_util::GetApplicationLocale(preferred_localization));
583
584   // Load localized strings.
585   ui::ResourceBundle::InitSharedInstanceWithLocale(
586       locale, NULL, ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
587
588   // Launch the IO thread.
589   base::Thread::Options io_thread_options;
590   io_thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
591   base::Thread *io_thread = new base::Thread("CrAppShimIO");
592   io_thread->StartWithOptions(io_thread_options);
593   g_io_thread = io_thread;
594
595   // Find already running instances of Chrome.
596   pid_t pid = -1;
597   std::string chrome_process_id = CommandLine::ForCurrentProcess()->
598       GetSwitchValueASCII(app_mode::kLaunchedByChromeProcessId);
599   if (!chrome_process_id.empty()) {
600     if (!base::StringToInt(chrome_process_id, &pid))
601       LOG(FATAL) << "Invalid PID: " << chrome_process_id;
602   } else {
603     NSString* chrome_bundle_id = [base::mac::OuterBundle() bundleIdentifier];
604     NSArray* existing_chrome = [NSRunningApplication
605         runningApplicationsWithBundleIdentifier:chrome_bundle_id];
606     if ([existing_chrome count] > 0)
607       pid = [[existing_chrome objectAtIndex:0] processIdentifier];
608   }
609
610   AppShimController controller;
611   base::MessageLoopForUI main_message_loop;
612   main_message_loop.set_thread_name("MainThread");
613   base::PlatformThread::SetName("CrAppShimMain");
614
615   // In tests, launching Chrome does nothing, and we won't get a ping response,
616   // so just assume the socket exists.
617   if (pid == -1 &&
618       !CommandLine::ForCurrentProcess()->HasSwitch(
619           app_mode::kLaunchedForTest)) {
620     // Launch Chrome if it isn't already running.
621     ProcessSerialNumber psn;
622     CommandLine command_line(CommandLine::NO_PROGRAM);
623     command_line.AppendSwitch(switches::kSilentLaunch);
624
625     // If the shim is the app launcher, pass --show-app-list when starting a new
626     // Chrome process to inform startup codepaths and load the correct profile.
627     if (info->app_mode_id == app_mode::kAppListModeId) {
628       command_line.AppendSwitch(switches::kShowAppList);
629     } else {
630       command_line.AppendSwitchPath(switches::kProfileDirectory,
631                                     info->profile_dir);
632     }
633
634     bool success =
635         base::mac::OpenApplicationWithPath(base::mac::OuterBundlePath(),
636                                            command_line,
637                                            kLSLaunchDefaults,
638                                            &psn);
639     if (!success)
640       return 1;
641
642     base::Callback<void(bool)> on_ping_chrome_reply =
643         base::Bind(&AppShimController::OnPingChromeReply,
644                    base::Unretained(&controller));
645
646     // This code abuses the fact that Apple Events sent before the process is
647     // fully initialized don't receive a reply until its run loop starts. Once
648     // the reply is received, Chrome will have opened its IPC port, guaranteed.
649     [ReplyEventHandler pingProcess:psn
650                            andCall:on_ping_chrome_reply];
651
652     main_message_loop.PostDelayedTask(
653         FROM_HERE,
654         base::Bind(&AppShimController::OnPingChromeTimeout,
655                    base::Unretained(&controller)),
656         base::TimeDelta::FromSeconds(kPingChromeTimeoutSeconds));
657   } else {
658     // Chrome already running. Proceed to init. This could still fail if Chrome
659     // is still starting up or shutting down, but the process will exit quickly,
660     // which is preferable to waiting for the Apple Event to timeout after one
661     // minute.
662     main_message_loop.PostTask(
663         FROM_HERE,
664         base::Bind(&AppShimController::Init,
665                    base::Unretained(&controller)));
666   }
667
668   main_message_loop.Run();
669   return 0;
670 }