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