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