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 43namespace { 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. 48const int kPingChromeTimeoutSeconds = 60; 49 50const app_mode::ChromeAppModeInfo* g_info; 51base::Thread* g_io_thread = NULL; 52 53} // namespace 54 55class 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. 88class 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 147AppShimController::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 156AppShimController::~AppShimController() { 157 // Un-set the delegate since NSApplication does not retain it. 158 [[NSApplication sharedApplication] setDelegate:nil]; 159} 160 161void 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 171void AppShimController::OnPingChromeTimeout() { 172 if (!ping_chrome_reply_received_) 173 [NSApp terminate:nil]; 174} 175 176void 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 205void 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 228void 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 275void AppShimController::SendQuitApp() { 276 channel_->Send(new AppShimHostMsg_QuitApp); 277} 278 279bool 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 291void AppShimController::OnChannelError() { 292 Close(); 293} 294 295void 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 308void AppShimController::OnHide() { 309 [NSApp hide:nil]; 310} 311 312void AppShimController::OnRequestUserAttention() { 313 [NSApp requestUserAttention:NSInformationalRequest]; 314} 315 316void AppShimController::Close() { 317 [delegate_ terminateNow]; 318} 319 320bool 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 330void 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 534extern "C" { 535 536// |ChromeAppModeStart()| is the point of entry into the framework from the app 537// mode loader. 538__attribute__((visibility("default"))) 539int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info); 540 541} // extern "C" 542 543int 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 ResourceBundle::InitSharedInstanceLocaleOnly(locale, NULL); 586 587 // Launch the IO thread. 588 base::Thread::Options io_thread_options; 589 io_thread_options.message_loop_type = base::MessageLoop::TYPE_IO; 590 base::Thread *io_thread = new base::Thread("CrAppShimIO"); 591 io_thread->StartWithOptions(io_thread_options); 592 g_io_thread = io_thread; 593 594 // Find already running instances of Chrome. 595 pid_t pid = -1; 596 std::string chrome_process_id = CommandLine::ForCurrentProcess()-> 597 GetSwitchValueASCII(app_mode::kLaunchedByChromeProcessId); 598 if (!chrome_process_id.empty()) { 599 if (!base::StringToInt(chrome_process_id, &pid)) 600 LOG(FATAL) << "Invalid PID: " << chrome_process_id; 601 } else { 602 NSString* chrome_bundle_id = [base::mac::OuterBundle() bundleIdentifier]; 603 NSArray* existing_chrome = [NSRunningApplication 604 runningApplicationsWithBundleIdentifier:chrome_bundle_id]; 605 if ([existing_chrome count] > 0) 606 pid = [[existing_chrome objectAtIndex:0] processIdentifier]; 607 } 608 609 AppShimController controller; 610 base::MessageLoopForUI main_message_loop; 611 main_message_loop.set_thread_name("MainThread"); 612 base::PlatformThread::SetName("CrAppShimMain"); 613 614 // In tests, launching Chrome does nothing, and we won't get a ping response, 615 // so just assume the socket exists. 616 if (pid == -1 && 617 !CommandLine::ForCurrentProcess()->HasSwitch( 618 app_mode::kLaunchedForTest)) { 619 // Launch Chrome if it isn't already running. 620 ProcessSerialNumber psn; 621 CommandLine command_line(CommandLine::NO_PROGRAM); 622 command_line.AppendSwitch(switches::kSilentLaunch); 623 624 // If the shim is the app launcher, pass --show-app-list when starting a new 625 // Chrome process to inform startup codepaths and load the correct profile. 626 if (info->app_mode_id == app_mode::kAppListModeId) { 627 command_line.AppendSwitch(switches::kShowAppList); 628 } else { 629 command_line.AppendSwitchPath(switches::kProfileDirectory, 630 info->profile_dir); 631 } 632 633 bool success = 634 base::mac::OpenApplicationWithPath(base::mac::OuterBundlePath(), 635 command_line, 636 kLSLaunchDefaults, 637 &psn); 638 if (!success) 639 return 1; 640 641 base::Callback<void(bool)> on_ping_chrome_reply = 642 base::Bind(&AppShimController::OnPingChromeReply, 643 base::Unretained(&controller)); 644 645 // This code abuses the fact that Apple Events sent before the process is 646 // fully initialized don't receive a reply until its run loop starts. Once 647 // the reply is received, Chrome will have opened its IPC port, guaranteed. 648 [ReplyEventHandler pingProcess:psn 649 andCall:on_ping_chrome_reply]; 650 651 main_message_loop.PostDelayedTask( 652 FROM_HERE, 653 base::Bind(&AppShimController::OnPingChromeTimeout, 654 base::Unretained(&controller)), 655 base::TimeDelta::FromSeconds(kPingChromeTimeoutSeconds)); 656 } else { 657 // Chrome already running. Proceed to init. This could still fail if Chrome 658 // is still starting up or shutting down, but the process will exit quickly, 659 // which is preferable to waiting for the Apple Event to timeout after one 660 // minute. 661 main_message_loop.PostTask( 662 FROM_HERE, 663 base::Bind(&AppShimController::Init, 664 base::Unretained(&controller))); 665 } 666 667 main_message_loop.Run(); 668 return 0; 669} 670