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

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: Move app_shim code to apps/ 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
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 "apps/app_shim/app_shim_messages.h"
13 #include "base/at_exit.h"
12 #include "base/logging.h" 14 #include "base/logging.h"
13 #include "base/mac/bundle_locations.h"
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 "base/threading/thread.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() 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(
palmer 2013/03/14 19:05:04 Similar question: Is this new object's lifetime ma
jeremya 2013/03/14 23:33:01 As above, Send() deletes it when it's done with it
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 // Sends an Apple Event ping to the process identified by |psn| and registers
132 // to listen for a reply.
133 - (void)pingProcess:(const ProcessSerialNumber&)psn;
134
135 // Called when a response is received from the target process for the ping sent
136 // by |-pingProcess:|.
137 - (void)message:(NSAppleEventDescriptor*)event
138 withReply:(NSAppleEventDescriptor*)reply;
139
140 // Calls |onReply_|, passing it |success| to specify whether the ping was
141 // successful.
142 - (void)closeWithSuccess:(bool)success;
143 @end
144
145 @implementation ReplyEventHandler
146 + (void)pingProcess:(const ProcessSerialNumber&)psn
147 andCall:(base::Callback<void(bool)>)replyFn {
148 // The object will release itself when the reply arrives, or possibly earlier
149 // if an unrecoverable error occurs.
150 ReplyEventHandler* handler =
151 [[ReplyEventHandler alloc] initWithCallback:replyFn];
152 [handler pingProcess:psn];
153 }
154 @end
155
156 @implementation ReplyEventHandler (PrivateMethods)
157 - (id)initWithCallback:(base::Callback<void(bool)>)replyFn {
158 if ((self = [super init])) {
159 onReply_ = replyFn;
160 }
161 return self;
162 }
163
164 - (void)pingProcess:(const ProcessSerialNumber&)psn {
165 // Register the reply listener.
166 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
167 [em setEventHandler:self
168 andSelector:@selector(message:withReply:)
169 forEventClass:'aevt'
170 andEventID:'ansr'];
171 // Craft the Apple Event to send.
172 NSAppleEventDescriptor* target = [NSAppleEventDescriptor
173 descriptorWithDescriptorType:typeProcessSerialNumber
174 bytes:&psn
175 length:sizeof(psn)];
176 NSAppleEventDescriptor* initial_event =
177 [NSAppleEventDescriptor
178 appleEventWithEventClass:app_mode::kAEChromeAppClass
179 eventID:app_mode::kAEChromeAppPing
180 targetDescriptor:target
181 returnID:kAutoGenerateReturnID
182 transactionID:kAnyTransactionID];
183 // And away we go.
184 // TODO(jeremya): if we don't care about the contents of the reply, can we
185 // pass NULL for the reply event parameter?
186 OSStatus status = AESendMessage(
187 [initial_event aeDesc], &replyEvent_, kAEQueueReply, kAEDefaultTimeout);
188 if (status != noErr) {
189 OSSTATUS_LOG(ERROR, status) << "AESendMessage";
190 [self closeWithSuccess:false];
191 }
192 }
193
194 - (void)message:(NSAppleEventDescriptor*)event
195 withReply:(NSAppleEventDescriptor*)reply {
196 [self closeWithSuccess:true];
197 }
198
199 - (void)closeWithSuccess:(bool)success {
200 onReply_.Run(success);
201 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
202 [em removeEventHandlerForEventClass:'aevt' andEventID:'ansr'];
203 [self release];
204 }
205 @end
206
207 //-----------------------------------------------------------------------------
208
209 namespace {
210
211 // Called when the main Chrome process responds to the Apple Event ping that
212 // was sent, or when the ping fails (if |success| is false).
213 void OnPingChromeReply(bool success) {
214 if (!success) {
215 [NSApp terminate:nil];
216 return;
217 }
218 AppShimController* controller = new AppShimController;
219 controller->Init();
220 }
221
222 } // namespace
24 223
25 extern "C" { 224 extern "C" {
26 225
27 // |ChromeAppModeStart()| is the point of entry into the framework from the app 226 // |ChromeAppModeStart()| is the point of entry into the framework from the app
28 // mode loader. 227 // mode loader.
29 __attribute__((visibility("default"))) 228 __attribute__((visibility("default")))
30 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info); 229 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info);
31 230
32 } // extern "C" 231 } // extern "C"
33 232
34 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) { 233 int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
35 base::mac::ScopedNSAutoreleasePool scoped_pool; 234 base::mac::ScopedNSAutoreleasePool scoped_pool;
235 base::AtExitManager exit_manager;
236 chrome::RegisterPathProvider();
36 237
37 if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) { 238 if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) {
38 RAW_LOG(ERROR, "App Mode Loader too old."); 239 RAW_LOG(ERROR, "App Mode Loader too old.");
39 return 1; 240 return 1;
40 } 241 }
41 if (info->major_version > app_mode::kCurrentChromeAppModeInfoMajorVersion) { 242 if (info->major_version > app_mode::kCurrentChromeAppModeInfoMajorVersion) {
42 RAW_LOG(ERROR, "Browser Framework too old to load App Shortcut."); 243 RAW_LOG(ERROR, "Browser Framework too old to load App Shortcut.");
43 return 1; 244 return 1;
44 } 245 }
45 246
247 g_info = info;
248
249 // Launch the IO thread.
250 base::Thread::Options io_thread_options;
251 io_thread_options.message_loop_type = MessageLoop::TYPE_IO;
252 base::Thread *io_thread = new base::Thread("CrAppShimIO");
253 io_thread->StartWithOptions(io_thread_options);
254 g_io_thread = io_thread;
255
256 // Launch Chrome if it isn't already running.
46 FSRef app_fsref; 257 FSRef app_fsref;
47 if (!base::mac::FSRefFromPath(info->chrome_outer_bundle_path.value(), 258 if (!base::mac::FSRefFromPath(info->chrome_outer_bundle_path.value(),
48 &app_fsref)) { 259 &app_fsref)) {
49 PLOG(ERROR) << "base::mac::FSRefFromPath failed for " 260 LOG(ERROR) << "base::mac::FSRefFromPath failed for "
50 << info->chrome_outer_bundle_path.value(); 261 << info->chrome_outer_bundle_path.value();
51 return 1; 262 return 1;
52 } 263 }
53 std::string silent = std::string("--") + switches::kSilentLaunch; 264 std::string silent = std::string("--") + switches::kSilentLaunch;
54 CFArrayRef launch_args = 265 CFArrayRef launch_args =
55 base::mac::NSToCFCast(@[base::SysUTF8ToNSString(silent)]); 266 base::mac::NSToCFCast(@[base::SysUTF8ToNSString(silent)]);
56 267
57 LSApplicationParameters ls_parameters = { 268 LSApplicationParameters ls_parameters = {
58 0, // version 269 0, // version
59 kLSLaunchDefaults, 270 kLSLaunchDefaults,
60 &app_fsref, 271 &app_fsref,
61 NULL, // asyncLaunchRefCon 272 NULL, // asyncLaunchRefCon
62 NULL, // environment 273 NULL, // environment
63 launch_args, 274 launch_args,
64 NULL // initialEvent 275 NULL // initialEvent
65 }; 276 };
66 NSAppleEventDescriptor* initial_event = 277 ProcessSerialNumber psn;
67 [NSAppleEventDescriptor 278 // TODO(jeremya): this opens a new browser window if Chrome is already
68 appleEventWithEventClass:app_mode::kAEChromeAppClass 279 // running without any windows open.
69 eventID:app_mode::kAEChromeAppLaunch 280 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) { 281 if (status != noErr) {
85 OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; 282 OSSTATUS_LOG(ERROR, status) << "LSOpenApplication";
86 return 1; 283 return 1;
87 } 284 }
285
286 // This code abuses the fact that Apple Events sent before the process is
287 // fully initialized don't receive a reply until its run loop starts. Once
288 // the reply is received, Chrome will have opened its IPC port, guaranteed.
289 [ReplyEventHandler pingProcess:psn andCall:base::Bind(&OnPingChromeReply)];
290
291 MessageLoopForUI main_message_loop;
292 main_message_loop.set_thread_name("MainThread");
293 base::PlatformThread::SetName("CrAppShimMain");
294 main_message_loop.Run();
88 return 0; 295 return 0;
89 } 296 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698