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

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

Powered by Google App Engine
This is Rietveld 408576698