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