summaryrefslogtreecommitdiffstats
path: root/mojo
diff options
context:
space:
mode:
authorcmasone@chromium.org <cmasone@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-24 16:17:57 +0000
committercmasone@chromium.org <cmasone@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-24 16:17:57 +0000
commit196c60bbf6fb74be48726da4f96097490826f67d (patch)
tree5fa0d20ff75307add5f3c2752c1b3b1bb0d673f2 /mojo
parent4765890cc2e2b69bbb356f238bf26f8a6d2bf574 (diff)
downloadchromium_src-196c60bbf6fb74be48726da4f96097490826f67d.zip
chromium_src-196c60bbf6fb74be48726da4f96097490826f67d.tar.gz
chromium_src-196c60bbf6fb74be48726da4f96097490826f67d.tar.bz2
Implement a trivial Mojo EchoService that can be connected to over DBus
Some systems might like to use Mojo IPC to talk to services that are running outside the auspices of the Mojo shell. DBus is one potential channel to use for bootstrapping such a connection. In order to allow the externally-running service to accept connections from a Mojo shell, we need to get it a ShellHandle. This change defines a DBusServiceLoader that implements the ServiceLoader interface. DBusServiceLoader creates a dedicated MessagePipe, passes a handle to one end to the desired service over DBus, and then passes the ShellHandle over that pipe. This class assumes the following: 1) Your service is already running, 2) Your service implements the Mojo ExternalService API (from external_service.mojom). 3) Your service exports an object that implements the org.chromium.Mojo DBus interface: <interface name="org.chromium.Mojo"> <method name="ConnectChannel"> <arg type="h" name="file_descriptor" direction="in" /> </method> </interface> This change also includes a trivial DBusEchoService that can be "loaded" using the DBusServiceLoader, as well as a mojo example app that connects to this service. BUG=364903 TEST=run mojo_dbus_echo_service in one shell, and then load mojo_dbus_echo in the mojo shell. "who" should successfully echo between the two. Review URL: https://codereview.chromium.org/242763003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@265927 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'mojo')
-rw-r--r--mojo/examples/dbus_echo/dbus_echo_app.cc74
-rw-r--r--mojo/mojo.gyp28
-rw-r--r--mojo/mojo_examples.gypi20
-rw-r--r--mojo/mojo_services.gypi39
-rw-r--r--mojo/services/dbus_echo/DEPS4
-rw-r--r--mojo/services/dbus_echo/dbus_echo_service.cc167
-rw-r--r--mojo/services/dbus_echo/echo.mojom16
-rw-r--r--mojo/shell/DEPS1
-rw-r--r--mojo/shell/context.cc11
-rw-r--r--mojo/shell/dbus_service_loader_linux.cc186
-rw-r--r--mojo/shell/dbus_service_loader_linux.h90
-rw-r--r--mojo/shell/external_service.mojom16
12 files changed, 652 insertions, 0 deletions
diff --git a/mojo/examples/dbus_echo/dbus_echo_app.cc b/mojo/examples/dbus_echo/dbus_echo_app.cc
new file mode 100644
index 0000000..fefd72c
--- /dev/null
+++ b/mojo/examples/dbus_echo/dbus_echo_app.cc
@@ -0,0 +1,74 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/allocation_scope.h"
+#include "mojo/public/cpp/bindings/remote_ptr.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/shell/application.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "mojo/public/interfaces/shell/shell.mojom.h"
+#include "mojo/services/dbus_echo/echo.mojom.h"
+
+#if defined(WIN32)
+#if !defined(CDECL)
+#define CDECL __cdecl
+#endif
+#define DBUS_ECHO_APP_EXPORT __declspec(dllexport)
+#else
+#define CDECL
+#define DBUS_ECHO_APP_EXPORT __attribute__((visibility("default")))
+#endif
+
+namespace mojo {
+namespace examples {
+
+class DBusEchoApp : public Application, public mojo::EchoClient {
+ public:
+ explicit DBusEchoApp(MojoHandle shell_handle) : Application(shell_handle) {
+ InterfacePipe<EchoService, AnyInterface> echo_pipe;
+ mojo::AllocationScope scope;
+ shell()->Connect("dbus:org.chromium.EchoService/org/chromium/MojoImpl",
+ echo_pipe.handle_to_peer.Pass());
+ echo_service_.reset(echo_pipe.handle_to_self.Pass(), this);
+ echo_service_->Echo("who", base::Bind(&DBusEchoApp::OnEcho,
+ base::Unretained(this)));
+ }
+
+ virtual ~DBusEchoApp() {
+ }
+
+ virtual void OnCreated() MOJO_OVERRIDE {
+ }
+
+ virtual void OnDestroyed() MOJO_OVERRIDE {
+ RunLoop::current()->Quit();
+ }
+
+ private:
+ void OnEcho(const mojo::String& echoed) {
+ LOG(INFO) << "echo'd " << echoed.To<std::string>();
+ }
+
+ RemotePtr<EchoService> echo_service_;
+};
+
+} // namespace examples
+} // namespace mojo
+
+extern "C" DBUS_ECHO_APP_EXPORT MojoResult CDECL MojoMain(
+ MojoHandle shell_handle) {
+ mojo::Environment env;
+ mojo::RunLoop loop;
+
+ mojo::examples::DBusEchoApp app(shell_handle);
+ loop.Run();
+ return MOJO_RESULT_OK;
+}
diff --git a/mojo/mojo.gyp b/mojo/mojo.gyp
index 4db1476..1fddb33 100644
--- a/mojo/mojo.gyp
+++ b/mojo/mojo.gyp
@@ -75,6 +75,23 @@
]
},
{
+ 'target_name': 'mojo_external_service_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'shell/external_service.mojom',
+ ],
+ 'variables': {
+ 'mojom_base_output_dir': 'mojo',
+ },
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_bindings',
+ ],
+ },
+ {
'target_name': 'mojo_run_all_unittests',
'type': 'static_library',
'dependencies': [
@@ -422,6 +439,7 @@
'../base/base.gyp:base_static',
'../net/net.gyp:net',
'../url/url.gyp:url_lib',
+ 'mojo_external_service_bindings',
'mojo_gles2_impl',
'mojo_service_manager',
'mojo_shell_bindings',
@@ -445,6 +463,8 @@
'shell/child_process_host.h',
'shell/context.cc',
'shell/context.h',
+ 'shell/dbus_service_loader_linux.cc',
+ 'shell/dbus_service_loader_linux.h',
'shell/dynamic_service_loader.cc',
'shell/dynamic_service_loader.h',
'shell/dynamic_service_runner.h',
@@ -473,6 +493,14 @@
'shell/url_request_context_getter.cc',
'shell/url_request_context_getter.h',
],
+ 'conditions': [
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../build/linux/system.gyp:dbus',
+ '../dbus/dbus.gyp:dbus',
+ ],
+ }],
+ ],
},
{
'target_name': 'mojo_shell',
diff --git a/mojo/mojo_examples.gypi b/mojo/mojo_examples.gypi
index 4de6fbd..5bece9b 100644
--- a/mojo/mojo_examples.gypi
+++ b/mojo/mojo_examples.gypi
@@ -297,5 +297,25 @@
},
],
}],
+ ['OS=="linux"', {
+ 'targets': [
+ {
+ 'target_name': 'mojo_dbus_echo',
+ 'type': 'shared_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_bindings',
+ 'mojo_environment_standalone',
+ 'mojo_echo_bindings',
+ 'mojo_shell_client',
+ 'mojo_system',
+ 'mojo_utility',
+ ],
+ 'sources': [
+ 'examples/dbus_echo/dbus_echo_app.cc',
+ ],
+ },
+ ],
+ }],
],
}
diff --git a/mojo/mojo_services.gypi b/mojo/mojo_services.gypi
index 3911a21..ee45085 100644
--- a/mojo/mojo_services.gypi
+++ b/mojo/mojo_services.gypi
@@ -1,6 +1,23 @@
{
'targets': [
{
+ 'target_name': 'mojo_echo_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/dbus_echo/echo.mojom',
+ ],
+ 'variables': {
+ 'mojom_base_output_dir': 'mojo',
+ },
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_bindings',
+ ],
+ },
+ {
'target_name': 'mojo_gles2_bindings',
'type': 'static_library',
'sources': [
@@ -220,5 +237,27 @@
},
],
}],
+ ['OS=="linux"', {
+ 'targets': [
+ {
+ 'target_name': 'mojo_dbus_echo_service',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../build/linux/system.gyp:dbus',
+ '../dbus/dbus.gyp:dbus',
+ 'mojo_external_service_bindings',
+ 'mojo_common_lib',
+ 'mojo_environment_chromium',
+ 'mojo_echo_bindings',
+ 'mojo_shell_client',
+ 'mojo_system_impl',
+ ],
+ 'sources': [
+ 'services/dbus_echo/dbus_echo_service.cc',
+ ],
+ },
+ ],
+ }],
],
}
diff --git a/mojo/services/dbus_echo/DEPS b/mojo/services/dbus_echo/DEPS
new file mode 100644
index 0000000..7260d76
--- /dev/null
+++ b/mojo/services/dbus_echo/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+dbus",
+ "+mojo/embedder",
+]
diff --git a/mojo/services/dbus_echo/dbus_echo_service.cc b/mojo/services/dbus_echo/dbus_echo_service.cc
new file mode 100644
index 0000000..189563d
--- /dev/null
+++ b/mojo/services/dbus_echo/dbus_echo_service.cc
@@ -0,0 +1,167 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/run_loop.h"
+#include "dbus/bus.h"
+#include "dbus/exported_object.h"
+#include "dbus/file_descriptor.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "mojo/common/mojo_channel_init.h"
+#include "mojo/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/interface.h"
+#include "mojo/public/cpp/bindings/remote_ptr.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/shell/application.h"
+#include "mojo/public/interfaces/shell/shell.mojom.h"
+#include "mojo/services/dbus_echo/echo.mojom.h"
+#include "mojo/shell/external_service.mojom.h"
+
+class EchoServiceImpl : public mojo::ServiceConnection<mojo::EchoService,
+ EchoServiceImpl> {
+ public:
+ EchoServiceImpl() {}
+ virtual ~EchoServiceImpl() {}
+
+ protected:
+ virtual void Echo(
+ const mojo::String& in_to_echo,
+ const mojo::Callback<void(mojo::String)>& callback) OVERRIDE {
+ DVLOG(1) << "Asked to echo " << in_to_echo.To<std::string>();
+ callback.Run(in_to_echo);
+ }
+};
+
+
+class DBusEchoService : public mojo::ExternalService {
+ public:
+ static const char kMojoImplPath[];
+ static const char kMojoInterface[];
+ static const char kServiceName[];
+
+ DBusEchoService() : exported_object_(NULL) {
+ }
+ ~DBusEchoService() {}
+
+ void Start() {
+ InitializeDBus();
+ ExportMethods();
+ TakeDBusServiceOwnership();
+ DVLOG(1) << "Echo service started";
+ }
+
+ private:
+ // TODO(cmasone): Use a Delegate pattern to allow the ConnectChannel
+ // code to be shared among all Mojo services that wish to be
+ // connected to over DBus.
+ virtual void Activate(mojo::ScopedMessagePipeHandle client_handle) OVERRIDE {
+ mojo::ScopedShellHandle shell_handle(
+ mojo::ShellHandle(client_handle.release().value()));
+ app_.reset(new mojo::Application(shell_handle.Pass()));
+ app_->AddServiceConnector(new mojo::ServiceConnector<EchoServiceImpl>());
+ }
+
+ // Implementation of org.chromium.Mojo.ConnectChannel, exported over DBus.
+ // Takes a file descriptor and uses it to create a MessagePipe that is then
+ // hooked to a RemotePtr<mojo::ExternalServiceHost>.
+ void ConnectChannel(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender sender) {
+ dbus::MessageReader reader(method_call);
+ dbus::FileDescriptor wrapped_fd;
+ if (!reader.PopFileDescriptor(&wrapped_fd)) {
+ sender.Run(
+ dbus::ErrorResponse::FromMethodCall(
+ method_call,
+ "org.chromium.Mojo.BadHandle",
+ "Invalid FD.").PassAs<dbus::Response>());
+ return;
+ }
+ wrapped_fd.CheckValidity();
+ channel_init_.reset(new mojo::common::MojoChannelInit);
+ channel_init_->Init(wrapped_fd.TakeValue(),
+ base::MessageLoopProxy::current());
+ CHECK(channel_init_->is_handle_valid());
+
+ // TODO(cmasone): Figure out how to correctly clean up the channel when
+ // the peer disappears. Passing an ErrorHandler when constructing the
+ // RemotePtr<> below didn't seem to work. OnError wasn't called.
+ mojo::ScopedExternalServiceHostHandle client_handle(
+ mojo::ExternalServiceHostHandle(
+ channel_init_->bootstrap_message_pipe().release().value()));
+ external_service_host_.reset(client_handle.Pass(), this);
+
+ sender.Run(dbus::Response::FromMethodCall(method_call));
+ }
+
+ void ExportMethods() {
+ CHECK(exported_object_);
+ CHECK(exported_object_->ExportMethodAndBlock(
+ kMojoInterface, "ConnectChannel",
+ base::Bind(&DBusEchoService::ConnectChannel,
+ base::Unretained(this))));
+ }
+
+ void InitializeDBus() {
+ CHECK(!bus_);
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SESSION;
+ bus_ = new dbus::Bus(options);
+ CHECK(bus_->Connect());
+ CHECK(bus_->SetUpAsyncOperations());
+
+ exported_object_ = bus_->GetExportedObject(dbus::ObjectPath(kMojoImplPath));
+ }
+
+ void TakeDBusServiceOwnership() {
+ CHECK(bus_->RequestOwnershipAndBlock(
+ kServiceName,
+ dbus::Bus::REQUIRE_PRIMARY_ALLOW_REPLACEMENT))
+ << "Unable to take ownership of " << kServiceName;
+ }
+
+ scoped_refptr<dbus::Bus> bus_;
+ dbus::ExportedObject* exported_object_; // Owned by bus_;
+ scoped_ptr<mojo::common::MojoChannelInit> channel_init_;
+ mojo::RemotePtr<mojo::ExternalServiceHost> external_service_host_;
+ scoped_ptr<mojo::Application> app_;
+};
+
+// TODO(cmasone): Figure out where these "well-known" names should be defined
+// so they can be shared with DBusServiceLoader and such.
+const char DBusEchoService::kMojoImplPath[] = "/org/chromium/MojoImpl";
+const char DBusEchoService::kMojoInterface[] = "org.chromium.Mojo";
+const char DBusEchoService::kServiceName[] = "org.chromium.EchoService";
+
+int main(int argc, char** argv) {
+ base::AtExitManager exit_manager;
+ CommandLine::Init(argc, argv);
+
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+ logging::SetLogItems(false, // Process ID
+ false, // Thread ID
+ false, // Timestamp
+ false); // Tick count
+
+ mojo::Environment env;
+ mojo::embedder::Init();
+
+ base::MessageLoopForIO message_loop;
+ base::RunLoop run_loop;
+
+ DBusEchoService echo_service;
+ echo_service.Start();
+
+ run_loop.Run();
+ return 0;
+}
diff --git a/mojo/services/dbus_echo/echo.mojom b/mojo/services/dbus_echo/echo.mojom
new file mode 100644
index 0000000..c2ad42a
--- /dev/null
+++ b/mojo/services/dbus_echo/echo.mojom
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo {
+
+[Peer=EchoClient]
+interface EchoService {
+ Echo(string to_echo) => (string echoed);
+};
+
+[Peer=EchoService]
+interface EchoClient {
+};
+
+}
diff --git a/mojo/shell/DEPS b/mojo/shell/DEPS
index 5cf2176..7b96bb6 100644
--- a/mojo/shell/DEPS
+++ b/mojo/shell/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+dbus",
"+mojo/embedder",
"+net",
"+ui/gl",
diff --git a/mojo/shell/context.cc b/mojo/shell/context.cc
index df86d14..d5bb30e 100644
--- a/mojo/shell/context.cc
+++ b/mojo/shell/context.cc
@@ -5,6 +5,7 @@
#include "mojo/shell/context.h"
#include "base/command_line.h"
+#include "build/build_config.h"
#include "mojo/embedder/embedder.h"
#include "mojo/gles2/gles2_support_impl.h"
#include "mojo/service_manager/service_loader.h"
@@ -17,6 +18,10 @@
#include "mojo/shell/switches.h"
#include "mojo/spy/spy.h"
+#if defined(OS_LINUX)
+#include "mojo/shell/dbus_service_loader_linux.h"
+#endif // defined(OS_LINUX)
+
namespace mojo {
namespace shell {
@@ -66,6 +71,12 @@ Context::Context()
scoped_ptr<ServiceLoader>(new NativeViewportServiceLoader(this)),
GURL("mojo:mojo_native_viewport_service"));
+#if defined(OS_LINUX)
+ service_manager_.SetLoaderForScheme(
+ scoped_ptr<ServiceLoader>(new DBusServiceLoader(this)),
+ "dbus");
+#endif // defined(OS_LINUX)
+
if (cmdline->HasSwitch(switches::kSpy)) {
spy_.reset(new mojo::Spy(&service_manager_,
cmdline->GetSwitchValueASCII(switches::kSpy)));
diff --git a/mojo/shell/dbus_service_loader_linux.cc b/mojo/shell/dbus_service_loader_linux.cc
new file mode 100644
index 0000000..496ccd3
--- /dev/null
+++ b/mojo/shell/dbus_service_loader_linux.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/dbus_service_loader_linux.h"
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "dbus/bus.h"
+#include "dbus/file_descriptor.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "mojo/common/mojo_channel_init.h"
+#include "mojo/embedder/platform_channel_pair.h"
+#include "mojo/public/cpp/bindings/allocation_scope.h"
+#include "mojo/public/cpp/bindings/interface.h"
+#include "mojo/public/cpp/bindings/remote_ptr.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/external_service.mojom.h"
+#include "mojo/shell/keep_alive.h"
+
+namespace mojo {
+namespace shell {
+
+// Manages the connection to a single externally-running service.
+class DBusServiceLoader::LoadContext : public mojo::ExternalServiceHost {
+ public:
+ // Kicks off the attempt to bootstrap a connection to the externally-running
+ // service specified by url_.
+ // Creates a MessagePipe and passes one end over DBus to the service. Then,
+ // calls ExternalService::Activate(ShellHandle) over the now-shared pipe.
+ LoadContext(DBusServiceLoader* loader,
+ const scoped_refptr<dbus::Bus>& bus,
+ const GURL& url,
+ ScopedShellHandle shell_handle)
+ : loader_(loader),
+ bus_(bus),
+ service_dbus_proxy_(NULL),
+ url_(url),
+ shell_handle_(shell_handle.Pass()),
+ keep_alive_(loader->context_) {
+ base::PostTaskAndReplyWithResult(
+ loader_->context_->task_runners()->io_runner(),
+ FROM_HERE,
+ base::Bind(&LoadContext::CreateChannelOnIOThread,
+ base::Unretained(this)),
+ base::Bind(&LoadContext::ConnectChannel, base::Unretained(this)));
+ }
+
+ virtual ~LoadContext() {
+ }
+
+ private:
+ // Sets up a pipe to share with the externally-running service and returns
+ // the endpoint that should be sent over DBus.
+ // The FD for the endpoint must be validated on an IO thread.
+ scoped_ptr<dbus::FileDescriptor> CreateChannelOnIOThread() {
+ base::ThreadRestrictions::AssertIOAllowed();
+ CHECK(bus_->Connect());
+ CHECK(bus_->SetUpAsyncOperations());
+
+ embedder::PlatformChannelPair channel_pair;
+ channel_init_.reset(new common::MojoChannelInit);
+ channel_init_->Init(
+ channel_pair.PassServerHandle().release().fd,
+ loader_->context_->task_runners()->io_runner());
+ CHECK(channel_init_->is_handle_valid());
+
+ scoped_ptr<dbus::FileDescriptor> client_fd(new dbus::FileDescriptor);
+ client_fd->PutValue(channel_pair.PassClientHandle().release().fd);
+ client_fd->CheckValidity(); // Must be run on an IO thread.
+ return client_fd.Pass();
+ }
+
+ // Sends client_fd over to the externally-running service. If that
+ // attempt is successful, the service will then be "activated" by
+ // sending it a ShellHandle.
+ void ConnectChannel(scoped_ptr<dbus::FileDescriptor> client_fd) {
+ size_t first_slash = url_.path().find_first_of('/');
+ DCHECK_NE(first_slash, std::string::npos);
+
+ const std::string service_name = url_.path().substr(0, first_slash);
+ const std::string object_path = url_.path().substr(first_slash);
+ service_dbus_proxy_ =
+ bus_->GetObjectProxy(service_name, dbus::ObjectPath(object_path));
+
+ dbus::MethodCall call("org.chromium.Mojo", "ConnectChannel");
+ dbus::MessageWriter writer(&call);
+ writer.AppendFileDescriptor(*client_fd.get());
+
+ // TODO(cmasone): handle errors!
+ service_dbus_proxy_->CallMethod(
+ &call,
+ dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::Bind(&LoadContext::ActivateService, base::Unretained(this)));
+ }
+
+ // Sends a ShellHandle over to the now-connected externally-running service,
+ // using the Mojo ExternalService API.
+ void ActivateService(dbus::Response* response) {
+ mojo::ScopedExternalServiceHandle external_service_handle(
+ mojo::ExternalServiceHandle(
+ channel_init_->bootstrap_message_pipe().release().value()));
+ external_service_.reset(external_service_handle.Pass(), this);
+
+ mojo::AllocationScope scope;
+ external_service_->Activate(
+ mojo::ScopedMessagePipeHandle(
+ mojo::MessagePipeHandle(shell_handle_.release().value())));
+ }
+
+ // Should the ExternalService disappear completely, destroy connection state.
+ // NB: This triggers off of the service disappearing from
+ // DBus. Perhaps there's a way to watch at the Mojo layer instead,
+ // and that would be superior?
+ void HandleNameOwnerChanged(const std::string& old_owner,
+ const std::string& new_owner) {
+ DCHECK(loader_->context_->task_runners()->ui_runner()->
+ BelongsToCurrentThread());
+
+ if (new_owner.empty()) {
+ loader_->context_->task_runners()->ui_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&DBusServiceLoader::ForgetService,
+ base::Unretained(loader_), url_));
+ }
+ }
+
+ DBusServiceLoader* const loader_;
+ scoped_refptr<dbus::Bus> bus_;
+ dbus::ObjectProxy* service_dbus_proxy_; // Owned by bus_;
+ const GURL url_;
+ ScopedShellHandle shell_handle_;
+ KeepAlive keep_alive_;
+ scoped_ptr<common::MojoChannelInit> channel_init_;
+ mojo::RemotePtr<mojo::ExternalService> external_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoadContext);
+};
+
+DBusServiceLoader::DBusServiceLoader(Context* context) : context_(context) {
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SESSION;
+ options.dbus_task_runner = context_->task_runners()->io_runner();
+ bus_ = new dbus::Bus(options);
+}
+
+DBusServiceLoader::~DBusServiceLoader() {
+ DCHECK(url_to_load_context_.empty());
+}
+
+void DBusServiceLoader::LoadService(ServiceManager* manager,
+ const GURL& url,
+ ScopedShellHandle service_handle) {
+ DCHECK(url.SchemeIs("dbus"));
+ DCHECK(url_to_load_context_.find(url) == url_to_load_context_.end());
+ url_to_load_context_[url] =
+ new LoadContext(this, bus_, url, service_handle.Pass());
+}
+
+void DBusServiceLoader::OnServiceError(ServiceManager* manager,
+ const GURL& url) {
+ // TODO(cmasone): Anything at all in this method here.
+}
+
+void DBusServiceLoader::ForgetService(const GURL& url) {
+ DCHECK(context_->task_runners()->ui_runner()->BelongsToCurrentThread());
+ DVLOG(2) << "Forgetting service (url: " << url << ")";
+
+ LoadContextMap::iterator it = url_to_load_context_.find(url);
+ DCHECK(it != url_to_load_context_.end()) << url;
+
+ LoadContext* doomed = it->second;
+ url_to_load_context_.erase(it);
+
+ delete doomed;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/dbus_service_loader_linux.h b/mojo/shell/dbus_service_loader_linux.h
new file mode 100644
index 0000000..8cb3f53
--- /dev/null
+++ b/mojo/shell/dbus_service_loader_linux.h
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_SHELL_DBUS_SERVICE_LOADER_H_
+#define MOJO_SHELL_DBUS_SERVICE_LOADER_H_
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/interfaces/shell/shell.mojom.h"
+#include "mojo/service_manager/service_loader.h"
+#include "mojo/shell/keep_alive.h"
+#include "url/gurl.h"
+
+namespace dbus {
+class Bus;
+} // namespace dbus
+
+namespace mojo {
+namespace shell {
+
+class Context;
+
+// An implementation of ServiceLoader that contacts a system service
+// and bootstraps a Mojo connection to it over DBus.
+//
+// In order to allow the externally-running service to accept connections from
+// a Mojo shell, we need to get it a ShellHandle. This class creates a
+// dedicated MessagePipe, passes a handle to one end to the desired service
+// over DBus, and then passes the ShellHandle over that pipe.
+//
+// This class assumes the following:
+// 1) Your service is already running.
+// 2) Your service implements the Mojo ExternalService API
+// (from external_service.mojom).
+// 3) Your service exports an object that implements the org.chromium.Mojo DBus
+// interface:
+// <interface name="org.chromium.Mojo">
+// <method name="ConnectChannel">
+// <arg type="h" name="file_descriptor" direction="in" />
+// </method>
+// </interface>
+class DBusServiceLoader : public ServiceLoader {
+ public:
+ DBusServiceLoader(Context* context);
+ virtual ~DBusServiceLoader();
+
+ // URL for DBus services are of the following format:
+ // dbus:tld.domain.ServiceName/path/to/DBusObject
+ //
+ // This is simply the scheme (dbus:) and then the DBus service name followed
+ // by the DBus object path of an object that implements the org.chromium.Mojo
+ // interface as discussed above.
+ //
+ // Example:
+ // dbus:org.chromium.EchoService/org/chromium/MojoImpl
+ //
+ // This will tell DBusServiceLoader to reach out to a service with
+ // the name "org.chromium.EchoService" and invoke the method
+ // "org.chromium.Mojo.ConnectChannel" on the object exported at
+ // "/org/chromium/MojoImpl".
+ virtual void LoadService(ServiceManager* manager,
+ const GURL& url,
+ ScopedShellHandle service_handle) OVERRIDE;
+
+ virtual void OnServiceError(ServiceManager* manager, const GURL& url)
+ OVERRIDE;
+
+ private:
+ class LoadContext;
+
+ // Tosses out connection-related state to service at given URL.
+ void ForgetService(const GURL& url);
+
+ Context* const context_;
+ scoped_refptr<dbus::Bus> bus_;
+
+ typedef std::map<GURL, LoadContext*> LoadContextMap;
+ LoadContextMap url_to_load_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(DBusServiceLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_DBUS_SERVICE_LOADER_H_
diff --git a/mojo/shell/external_service.mojom b/mojo/shell/external_service.mojom
new file mode 100644
index 0000000..f76f5fe
--- /dev/null
+++ b/mojo/shell/external_service.mojom
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo {
+
+[Peer=ExternalServiceHost]
+interface ExternalService {
+ Activate(handle<message_pipe> shell_handle);
+};
+
+[Peer=ExternalService]
+interface ExternalServiceHost {
+};
+
+}