Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(191)

Side by Side Diff: chrome/app/chrome_main_app_mode_mac.mm

Issue 12623005: [mac] App shims (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: use ProfileInfoCache to verify profile path Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | chrome/browser/app_controller_mac.mm » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // On Mac, one can't make shortcuts with command-line arguments. Instead, we 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, 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 7 // passing the appropriate data. This is the entry point into the framework for
8 // those app bundles. 8 // those app bundles.
9 9
10 #include "base/basictypes.h" 10 #import <Cocoa/Cocoa.h>
11 #include "base/files/file_path.h" 11
12 #include "base/at_exit.h"
12 #include "base/logging.h" 13 #include "base/logging.h"
13 #include "base/mac/bundle_locations.h" 14 #include "base/threading/thread.h"
Mark Mentovai 2013/03/12 20:30:41 Sort.
14 #include "base/mac/foundation_util.h"
15 #include "base/mac/mac_logging.h" 15 #include "base/mac/mac_logging.h"
16 #include "base/mac/mac_util.h" 16 #include "base/mac/mac_util.h"
17 #include "base/mac/scoped_nsautorelease_pool.h" 17 #include "base/mac/scoped_nsautorelease_pool.h"
18 #include "base/message_loop.h"
19 #include "base/path_service.h"
18 #include "base/sys_string_conversions.h" 20 #include "base/sys_string_conversions.h"
19 #include "chrome/browser/shell_integration.h" 21 #include "chrome/common/app_shim_messages.h"
20 #include "chrome/common/chrome_constants.h" 22 #include "chrome/common/chrome_paths.h"
21 #include "chrome/common/chrome_paths_internal.h"
22 #include "chrome/common/chrome_switches.h" 23 #include "chrome/common/chrome_switches.h"
23 #include "chrome/common/mac/app_mode_common.h" 24 #include "chrome/common/mac/app_mode_common.h"
25 #include "ipc/ipc_channel_proxy.h"
26 #include "ipc/ipc_listener.h"
27 #include "ipc/ipc_message.h"
28
29 namespace {
30
31 const app_mode::ChromeAppModeInfo* g_info;
32 base::Thread* g_io_thread = NULL;
33
34 } // namespace
35
36 // The AppShimController is responsible for communication with the main Chrome
37 // process, and generally controls the lifetime of the app shim process.
38 class AppShimController : public IPC::Listener {
39 public:
40 AppShimController();
41
42 // Connects to Chrome and sends a LaunchApp message.
43 void Init();
44
45 private:
46 // IPC::Listener implemetation.
47 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
48 virtual void OnChannelError();
jeremy 2013/03/12 12:12:31 Also OVERRIDE ?
49
50 // If Chrome failed to launch the app, |success| will be false and the app
51 // shim process should die.
52 void OnLaunchAppDone(bool success);
53
54 // Quits the app shim process.
55 void Quit();
56
57 IPC::ChannelProxy* channel_;
58
59 DISALLOW_COPY_AND_ASSIGN(AppShimController);
60 };
61
62 AppShimController::AppShimController() : channel_(NULL) {
63 }
64
65 void AppShimController::Init() {
66 DCHECK(g_io_thread);
67 base::FilePath user_data_dir;
68 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
69 Quit();
70 return;
71 }
72 base::FilePath socket_path =
73 user_data_dir.Append(app_mode::kAppShimSocketName);
74 IPC::ChannelHandle handle(socket_path.value());
75 channel_ = new IPC::ChannelProxy(handle, IPC::Channel::MODE_NAMED_CLIENT,
76 this, g_io_thread->message_loop_proxy());
77
78 channel_->Send(new AppShimHostMsg_LaunchApp(
79 g_info->profile_dir.value(), g_info->app_mode_id));
80 }
81
82 bool AppShimController::OnMessageReceived(const IPC::Message& message) {
83 bool handled = true;
84 IPC_BEGIN_MESSAGE_MAP(AppShimController, message)
85 IPC_MESSAGE_HANDLER(AppShimMsg_LaunchApp_Done, OnLaunchAppDone)
86 IPC_MESSAGE_UNHANDLED(handled = false)
87 IPC_END_MESSAGE_MAP()
88
89 return handled;
90 }
91
92 void AppShimController::OnChannelError() {
93 LOG(ERROR) << "App shim channel error.";
94 Quit();
95 }
96
97 void AppShimController::OnLaunchAppDone(bool success) {
98 if (!success)
99 Quit();
100 }
101
102 void AppShimController::Quit() {
103 [NSApp terminate:nil];
104 }
105
106 //-----------------------------------------------------------------------------
107
108 // A ReplyEventHandler is a helper class to send an Apple Event to a process
109 // and call a callback when the reply returns.
110 //
111 // This is used to 'ping' the main Chrome process -- once Chrome has sent back
112 // an Apple Event reply, it's guaranteed that it has opened the IPC channel
113 // that the app shim will connect to.
114 @interface ReplyEventHandler : NSObject {
115 base::Callback<void(bool)> onReply_;
116 AEDesc replyEvent_;
117 }
118 // Sends an Apple Event to the process identified by |psn|, and calls |replyFn|
119 // when the reply is received. Internally this creates a ReplyEventHandler,
120 // which will delete itself once the reply event has been received.
121 + (void)pingProcess:(const ProcessSerialNumber&)psn
122 andCall:(base::Callback<void(bool)>)replyFn;
123 @end
124
125 @interface ReplyEventHandler (PrivateMethods)
126 // Initialise the reply event handler. Doesn't register any handlers until
127 // |-pingProcess:| is called. |replyFn| is the function to be called when the
128 // Apple Event reply arrives.
129 - (id)initWithCallback:(base::Callback<void(bool)>)replyFn;
130
131 // Registers an event with the global NSAppleEventManager for the aevt/ansr
132 // message, then sends off an Apple Event using AESendMessage.
133 - (void)pingProcess:(const ProcessSerialNumber&)psn;
jeremy 2013/03/12 12:12:31 how about: // Sends an Apple Event ping to the Chr
134
135 // Called when the aevt/ansr message arrives in response to the ping sent.
jeremy 2013/03/12 12:12:31 How about: Called when a response is received from
136 - (void)message:(NSAppleEventDescriptor*)event
137 withReply:(NSAppleEventDescriptor*)reply;
138
139 // Calls the reply function, passing it |success| to specify whether the ping
jeremy 2013/03/12 12:12:31 nit: the reply function -> |onReply_|
140 // was successful.
141 - (void)closeWithSuccess:(bool)success;
142 @end
143
144 @implementation ReplyEventHandler
145 + (void)pingProcess:(const ProcessSerialNumber&)psn
146 andCall:(base::Callback<void(bool)>)replyFn {
147 // The object will release itself when the reply arrives, or possibly earlier
148 // if an unrecoverable error occurs.
149 ReplyEventHandler* handler =
150 [[ReplyEventHandler alloc] initWithCallback:replyFn];
151 [handler pingProcess:psn];
152 }
153 @end
154
155 @implementation ReplyEventHandler (PrivateMethods)
156 - (id)initWithCallback:(base::Callback<void(bool)>)replyFn {
157 if ((self = [super init])) {
158 onReply_ = replyFn;
159 }
160 return self;
161 }
162
163 - (void)pingProcess:(const ProcessSerialNumber&)psn {
164 // Register the reply listener.
165 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
166 [em setEventHandler:self
167 andSelector:@selector(message:withReply:)
168 forEventClass:'aevt'
169 andEventID:'ansr'];
170 // Craft the Apple Event to send.
171 NSAppleEventDescriptor* target = [NSAppleEventDescriptor
172 descriptorWithDescriptorType:typeProcessSerialNumber
173 bytes:&psn
174 length:sizeof(psn)];
175 NSAppleEventDescriptor* initial_event =
176 [NSAppleEventDescriptor
177 appleEventWithEventClass:app_mode::kAEChromeAppClass
178 eventID:app_mode::kAEChromeAppPing
179 targetDescriptor:target
180 returnID:kAutoGenerateReturnID
181 transactionID:kAnyTransactionID];
182 // And away we go.
183 // TODO(jeremya): if we don't care about the contents of the reply, can we
184 // pass NULL for the reply event parameter?
185 OSStatus status = AESendMessage(
186 [initial_event aeDesc], &replyEvent_, kAEQueueReply, kAEDefaultTimeout);
187 if (status != noErr) {
188 OSSTATUS_LOG(ERROR, status) << "AESendMessage";
189 [self closeWithSuccess:false];
190 }
191 }
192
193 - (void)message:(NSAppleEventDescriptor*)event
194 withReply:(NSAppleEventDescriptor*)reply {
195 [self closeWithSuccess:true];
196 }
197
198 - (void)closeWithSuccess:(bool)success {
199 onReply_.Run(success);
200 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
201 [em removeEventHandlerForEventClass:'aevt' andEventID:'ansr'];
202 [self release];
203 }
204 @end
205
206 //-----------------------------------------------------------------------------
207
208 namespace {
209
210 // Called when the main Chrome process responds to the Apple Event ping that
211 // was sent, or when the ping fails (if |success| is false).
212 void OnPingChromeReply(bool success) {
213 if (!success) {
214 [NSApp terminate:nil];
215 return;
216 }
217 AppShimController* controller = new AppShimController;
218 controller->Init();
219 }
220
221 } // namespace
24 222
25 extern "C" { 223 extern "C" {
26 224
27 // |ChromeAppModeStart()| is the point of entry into the framework from the app 225 // |ChromeAppModeStart()| is the point of entry into the framework from the app
28 // mode loader. 226 // mode loader.
29 __attribute__((visibility("default"))) 227 __attribute__((visibility("default")))
30 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info); 228 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info);
31 229
32 } // extern "C" 230 } // extern "C"
33 231
34 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) { 232 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
35 base::mac::ScopedNSAutoreleasePool scoped_pool; 233 base::mac::ScopedNSAutoreleasePool scoped_pool;
234 base::AtExitManager exit_manager;
235 chrome::RegisterPathProvider();
36 236
37 if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) { 237 if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) {
38 RAW_LOG(ERROR, "App Mode Loader too old."); 238 RAW_LOG(ERROR, "App Mode Loader too old.");
39 return 1; 239 return 1;
40 } 240 }
41 if (info->major_version > app_mode::kCurrentChromeAppModeInfoMajorVersion) { 241 if (info->major_version > app_mode::kCurrentChromeAppModeInfoMajorVersion) {
42 RAW_LOG(ERROR, "Browser Framework too old to load App Shortcut."); 242 RAW_LOG(ERROR, "Browser Framework too old to load App Shortcut.");
43 return 1; 243 return 1;
44 } 244 }
45 245
246 g_info = info;
247
248 // Launch the IO thread.
249 base::Thread::Options io_thread_options;
250 io_thread_options.message_loop_type = MessageLoop::TYPE_IO;
251 base::Thread *io_thread = new base::Thread("CrAppShimIO");
252 io_thread->StartWithOptions(io_thread_options);
253 g_io_thread = io_thread;
254
255 // Launch Chrome if it isn't already running.
46 FSRef app_fsref; 256 FSRef app_fsref;
47 if (!base::mac::FSRefFromPath(info->chrome_outer_bundle_path.value(), 257 if (!base::mac::FSRefFromPath(info->chrome_outer_bundle_path.value(),
48 &app_fsref)) { 258 &app_fsref)) {
49 PLOG(ERROR) << "base::mac::FSRefFromPath failed for " 259 LOG(ERROR) << "base::mac::FSRefFromPath failed for "
50 << info->chrome_outer_bundle_path.value(); 260 << info->chrome_outer_bundle_path.value();
51 return 1; 261 return 1;
52 } 262 }
53 std::string silent = std::string("--") + switches::kSilentLaunch; 263 std::string silent = std::string("--") + switches::kSilentLaunch;
54 CFArrayRef launch_args = 264 CFArrayRef launch_args =
55 base::mac::NSToCFCast(@[base::SysUTF8ToNSString(silent)]); 265 base::mac::NSToCFCast(@[base::SysUTF8ToNSString(silent)]);
56 266
57 LSApplicationParameters ls_parameters = { 267 LSApplicationParameters ls_parameters = {
58 0, // version 268 0, // version
59 kLSLaunchDefaults, 269 kLSLaunchDefaults,
60 &app_fsref, 270 &app_fsref,
61 NULL, // asyncLaunchRefCon 271 NULL, // asyncLaunchRefCon
62 NULL, // environment 272 NULL, // environment
63 launch_args, 273 launch_args,
64 NULL // initialEvent 274 NULL // initialEvent
65 }; 275 };
66 NSAppleEventDescriptor* initial_event = 276 ProcessSerialNumber psn;
67 [NSAppleEventDescriptor 277 // TODO(jeremya): this opens a new browser window if Chrome is already
68 appleEventWithEventClass:app_mode::kAEChromeAppClass 278 // running without any windows open.
69 eventID:app_mode::kAEChromeAppLaunch 279 OSStatus status = LSOpenApplication(&ls_parameters, &psn);
70 targetDescriptor:nil
71 returnID:kAutoGenerateReturnID
72 transactionID:kAnyTransactionID];
73 NSAppleEventDescriptor* appid_descriptor = [NSAppleEventDescriptor
74 descriptorWithString:base::SysUTF8ToNSString(info->app_mode_id)];
75 [initial_event setParamDescriptor:appid_descriptor
76 forKeyword:keyDirectObject];
77 NSAppleEventDescriptor* profile_dir_descriptor = [NSAppleEventDescriptor
78 descriptorWithString:base::SysUTF8ToNSString(info->profile_dir.value())];
79 [initial_event setParamDescriptor:profile_dir_descriptor
80 forKeyword:app_mode::kAEProfileDirKey];
81 ls_parameters.initialEvent = const_cast<AEDesc*>([initial_event aeDesc]);
82 // Send the Apple Event using launch services, launching Chrome if necessary.
83 OSStatus status = LSOpenApplication(&ls_parameters, NULL);
84 if (status != noErr) { 280 if (status != noErr) {
85 OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; 281 OSSTATUS_LOG(ERROR, status) << "LSOpenApplication";
86 return 1; 282 return 1;
87 } 283 }
284
285 // This code abuses the fact that Apple Events sent before the process is
286 // fully initialized don't receive a reply until its run loop starts. Once
287 // the reply is received, Chrome will have opened its IPC port, guaranteed.
288 [ReplyEventHandler pingProcess:psn andCall:base::Bind(&OnPingChromeReply)];
289
290 MessageLoopForUI main_message_loop;
291 main_message_loop.set_thread_name("MainThread");
292 base::PlatformThread::SetName("CrAppShimMain");
293 main_message_loop.Run();
88 return 0; 294 return 0;
89 } 295 }
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/app_controller_mac.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698