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

Unified 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: -> _mac 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/app/DEPS ('k') | chrome/browser/app_controller_mac.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/app/chrome_main_app_mode_mac.mm
diff --git a/chrome/app/chrome_main_app_mode_mac.mm b/chrome/app/chrome_main_app_mode_mac.mm
index d102fc8f591e667bc0b2e00a5d67391d30f41729..a75f38da0fc895a5ae0c5ed6dd30226ff37594b7 100644
--- a/chrome/app/chrome_main_app_mode_mac.mm
+++ b/chrome/app/chrome_main_app_mode_mac.mm
@@ -7,20 +7,219 @@
// passing the appropriate data. This is the entry point into the framework for
// those app bundles.
-#include "base/basictypes.h"
-#include "base/files/file_path.h"
+#import <Cocoa/Cocoa.h>
+
+#include "apps/app_shim/app_shim_messages.h"
+#include "base/at_exit.h"
#include "base/logging.h"
-#include "base/mac/bundle_locations.h"
-#include "base/mac/foundation_util.h"
#include "base/mac/mac_logging.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
#include "base/sys_string_conversions.h"
-#include "chrome/browser/shell_integration.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/common/chrome_paths_internal.h"
+#include "base/threading/thread.h"
+#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/mac/app_mode_common.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_message.h"
+
+namespace {
+
+const app_mode::ChromeAppModeInfo* g_info;
+base::Thread* g_io_thread = NULL;
+
+} // namespace
+
+// The AppShimController is responsible for communication with the main Chrome
+// process, and generally controls the lifetime of the app shim process.
+class AppShimController : public IPC::Listener {
+ public:
+ AppShimController();
+
+ // Connects to Chrome and sends a LaunchApp message.
+ void Init();
+
+ private:
+ // IPC::Listener implemetation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual void OnChannelError() OVERRIDE;
+
+ // If Chrome failed to launch the app, |success| will be false and the app
+ // shim process should die.
+ void OnLaunchAppDone(bool success);
+
+ // Quits the app shim process.
+ void Quit();
+
+ IPC::ChannelProxy* channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppShimController);
+};
+
+AppShimController::AppShimController() : channel_(NULL) {
+}
+
+void AppShimController::Init() {
+ DCHECK(g_io_thread);
+ base::FilePath user_data_dir;
+ if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
+ Quit();
+ return;
+ }
+ base::FilePath socket_path =
+ user_data_dir.Append(app_mode::kAppShimSocketName);
+ IPC::ChannelHandle handle(socket_path.value());
+ channel_ = new IPC::ChannelProxy(handle, IPC::Channel::MODE_NAMED_CLIENT,
+ this, g_io_thread->message_loop_proxy());
+
+ channel_->Send(new AppShimHostMsg_LaunchApp(
+ g_info->profile_dir.value(), g_info->app_mode_id));
+}
+
+bool AppShimController::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(AppShimController, message)
+ IPC_MESSAGE_HANDLER(AppShimMsg_LaunchApp_Done, OnLaunchAppDone)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+}
+
+void AppShimController::OnChannelError() {
+ LOG(ERROR) << "App shim channel error.";
+ Quit();
+}
+
+void AppShimController::OnLaunchAppDone(bool success) {
+ if (!success)
+ Quit();
+}
+
+void AppShimController::Quit() {
+ [NSApp terminate:nil];
+}
+
+//-----------------------------------------------------------------------------
+
+// A ReplyEventHandler is a helper class to send an Apple Event to a process
+// and call a callback when the reply returns.
+//
+// This is used to 'ping' the main Chrome process -- once Chrome has sent back
+// an Apple Event reply, it's guaranteed that it has opened the IPC channel
+// that the app shim will connect to.
+@interface ReplyEventHandler : NSObject {
+ base::Callback<void(bool)> onReply_;
+ AEDesc replyEvent_;
+}
+// Sends an Apple Event to the process identified by |psn|, and calls |replyFn|
+// when the reply is received. Internally this creates a ReplyEventHandler,
+// which will delete itself once the reply event has been received.
++ (void)pingProcess:(const ProcessSerialNumber&)psn
+ andCall:(base::Callback<void(bool)>)replyFn;
+@end
+
+@interface ReplyEventHandler (PrivateMethods)
+// Initialise the reply event handler. Doesn't register any handlers until
+// |-pingProcess:| is called. |replyFn| is the function to be called when the
+// Apple Event reply arrives.
+- (id)initWithCallback:(base::Callback<void(bool)>)replyFn;
+
+// Sends an Apple Event ping to the process identified by |psn| and registers
+// to listen for a reply.
+- (void)pingProcess:(const ProcessSerialNumber&)psn;
+
+// Called when a response is received from the target process for the ping sent
+// by |-pingProcess:|.
+- (void)message:(NSAppleEventDescriptor*)event
+ withReply:(NSAppleEventDescriptor*)reply;
+
+// Calls |onReply_|, passing it |success| to specify whether the ping was
+// successful.
+- (void)closeWithSuccess:(bool)success;
+@end
+
+@implementation ReplyEventHandler
++ (void)pingProcess:(const ProcessSerialNumber&)psn
+ andCall:(base::Callback<void(bool)>)replyFn {
+ // The object will release itself when the reply arrives, or possibly earlier
+ // if an unrecoverable error occurs.
+ ReplyEventHandler* handler =
+ [[ReplyEventHandler alloc] initWithCallback:replyFn];
+ [handler pingProcess:psn];
+}
+@end
+
+@implementation ReplyEventHandler (PrivateMethods)
+- (id)initWithCallback:(base::Callback<void(bool)>)replyFn {
+ if ((self = [super init])) {
+ onReply_ = replyFn;
+ }
+ return self;
+}
+
+- (void)pingProcess:(const ProcessSerialNumber&)psn {
+ // Register the reply listener.
+ NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
+ [em setEventHandler:self
+ andSelector:@selector(message:withReply:)
+ forEventClass:'aevt'
+ andEventID:'ansr'];
+ // Craft the Apple Event to send.
+ NSAppleEventDescriptor* target = [NSAppleEventDescriptor
+ descriptorWithDescriptorType:typeProcessSerialNumber
+ bytes:&psn
+ length:sizeof(psn)];
+ NSAppleEventDescriptor* initial_event =
+ [NSAppleEventDescriptor
+ appleEventWithEventClass:app_mode::kAEChromeAppClass
+ eventID:app_mode::kAEChromeAppPing
+ targetDescriptor:target
+ returnID:kAutoGenerateReturnID
+ transactionID:kAnyTransactionID];
+ // And away we go.
+ // TODO(jeremya): if we don't care about the contents of the reply, can we
+ // pass NULL for the reply event parameter?
+ OSStatus status = AESendMessage(
+ [initial_event aeDesc], &replyEvent_, kAEQueueReply, kAEDefaultTimeout);
+ if (status != noErr) {
+ OSSTATUS_LOG(ERROR, status) << "AESendMessage";
+ [self closeWithSuccess:false];
+ }
+}
+
+- (void)message:(NSAppleEventDescriptor*)event
+ withReply:(NSAppleEventDescriptor*)reply {
+ [self closeWithSuccess:true];
+}
+
+- (void)closeWithSuccess:(bool)success {
+ onReply_.Run(success);
+ NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
+ [em removeEventHandlerForEventClass:'aevt' andEventID:'ansr'];
+ [self release];
+}
+@end
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+// Called when the main Chrome process responds to the Apple Event ping that
+// was sent, or when the ping fails (if |success| is false).
+void OnPingChromeReply(bool success) {
+ if (!success) {
+ [NSApp terminate:nil];
+ return;
+ }
+ AppShimController* controller = new AppShimController;
+ controller->Init();
+}
+
+} // namespace
extern "C" {
@@ -33,6 +232,8 @@ int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info);
int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
base::mac::ScopedNSAutoreleasePool scoped_pool;
+ base::AtExitManager exit_manager;
+ chrome::RegisterPathProvider();
if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) {
RAW_LOG(ERROR, "App Mode Loader too old.");
@@ -43,11 +244,21 @@ int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
return 1;
}
+ g_info = info;
+
+ // Launch the IO thread.
+ base::Thread::Options io_thread_options;
+ io_thread_options.message_loop_type = MessageLoop::TYPE_IO;
+ base::Thread *io_thread = new base::Thread("CrAppShimIO");
+ io_thread->StartWithOptions(io_thread_options);
+ g_io_thread = io_thread;
+
+ // Launch Chrome if it isn't already running.
FSRef app_fsref;
if (!base::mac::FSRefFromPath(info->chrome_outer_bundle_path.value(),
&app_fsref)) {
- PLOG(ERROR) << "base::mac::FSRefFromPath failed for "
- << info->chrome_outer_bundle_path.value();
+ LOG(ERROR) << "base::mac::FSRefFromPath failed for "
+ << info->chrome_outer_bundle_path.value();
return 1;
}
std::string silent = std::string("--") + switches::kSilentLaunch;
@@ -63,27 +274,23 @@ int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
launch_args,
NULL // initialEvent
};
- NSAppleEventDescriptor* initial_event =
- [NSAppleEventDescriptor
- appleEventWithEventClass:app_mode::kAEChromeAppClass
- eventID:app_mode::kAEChromeAppLaunch
- targetDescriptor:nil
- returnID:kAutoGenerateReturnID
- transactionID:kAnyTransactionID];
- NSAppleEventDescriptor* appid_descriptor = [NSAppleEventDescriptor
- descriptorWithString:base::SysUTF8ToNSString(info->app_mode_id)];
- [initial_event setParamDescriptor:appid_descriptor
- forKeyword:keyDirectObject];
- NSAppleEventDescriptor* profile_dir_descriptor = [NSAppleEventDescriptor
- descriptorWithString:base::SysUTF8ToNSString(info->profile_dir.value())];
- [initial_event setParamDescriptor:profile_dir_descriptor
- forKeyword:app_mode::kAEProfileDirKey];
- ls_parameters.initialEvent = const_cast<AEDesc*>([initial_event aeDesc]);
- // Send the Apple Event using launch services, launching Chrome if necessary.
- OSStatus status = LSOpenApplication(&ls_parameters, NULL);
+ ProcessSerialNumber psn;
+ // TODO(jeremya): this opens a new browser window if Chrome is already
+ // running without any windows open.
+ OSStatus status = LSOpenApplication(&ls_parameters, &psn);
if (status != noErr) {
OSSTATUS_LOG(ERROR, status) << "LSOpenApplication";
return 1;
}
+
+ // This code abuses the fact that Apple Events sent before the process is
+ // fully initialized don't receive a reply until its run loop starts. Once
+ // the reply is received, Chrome will have opened its IPC port, guaranteed.
+ [ReplyEventHandler pingProcess:psn andCall:base::Bind(&OnPingChromeReply)];
+
+ MessageLoopForUI main_message_loop;
+ main_message_loop.set_thread_name("MainThread");
+ base::PlatformThread::SetName("CrAppShimMain");
+ main_message_loop.Run();
return 0;
}
« no previous file with comments | « chrome/app/DEPS ('k') | chrome/browser/app_controller_mac.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698