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