Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/mojo/application_manager/BUILD.gn b/mojo/application_manager/BUILD.gn
new file mode 100644
index 0000000..22c0edd
--- /dev/null
+++ b/mojo/application_manager/BUILD.gn
@@ -0,0 +1,64 @@
+# 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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo.gyp:mojo_application_manager
+component("application_manager") {
+ output_name = "mojo_application_manager"
+ sources = [
+ "application_loader.cc",
+ "application_loader.h",
+ "application_manager.cc",
+ "application_manager.h",
+ "application_manager_export.h",
+ "background_shell_application_loader.cc",
+ "background_shell_application_loader.h",
+ ]
+
+ defines = [
+ "MOJO_APPLICATION_MANAGER_IMPLEMENTATION",
+ ]
+
+ public_deps = [
+ "//base",
+ "//mojo/common",
+ "//mojo/public/interfaces/application:application",
+ "//mojo/services/public/interfaces/network:network",
+ "//url",
+ ]
+ deps = [
+ "//base/third_party/dynamic_annotations",
+ "//net",
+ "//url",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/interfaces/content_handler:content_handler",
+ ]
+}
+
+# GYP version: mojo.gyp:mojo_application_manager_unittests
+test("mojo_application_manager_unittests") {
+ sources = [
+ "application_manager_unittest.cc",
+ "background_shell_application_loader_unittest.cc",
+ ]
+
+ deps = [
+ ":application_manager",
+ ":test_bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/environment:chromium",
+ "//mojo/public/cpp/bindings",
+ "//testing/gtest",
+ "//url",
+ ]
+}
+
+mojom("test_bindings") {
+ sources = [ "test.mojom" ]
+}
diff --git a/mojo/application_manager/application_loader.cc b/mojo/application_manager/application_loader.cc
new file mode 100644
index 0000000..60dec66
--- /dev/null
+++ b/mojo/application_manager/application_loader.cc
@@ -0,0 +1,30 @@
+// 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/application_manager/application_loader.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+
+ApplicationLoader::SimpleLoadCallbacks::SimpleLoadCallbacks(
+ ScopedMessagePipeHandle shell_handle)
+ : shell_handle_(shell_handle.Pass()) {
+}
+
+ApplicationLoader::SimpleLoadCallbacks::~SimpleLoadCallbacks() {
+}
+
+ScopedMessagePipeHandle
+ApplicationLoader::SimpleLoadCallbacks::RegisterApplication() {
+ return shell_handle_.Pass();
+}
+
+void ApplicationLoader::SimpleLoadCallbacks::LoadWithContentHandler(
+ const GURL& content_handle_url,
+ URLResponsePtr url_response) {
+ NOTREACHED();
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/application_loader.h b/mojo/application_manager/application_loader.h
new file mode 100644
index 0000000..b7d9f19
--- /dev/null
+++ b/mojo/application_manager/application_loader.h
@@ -0,0 +1,88 @@
+// 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_APPLICATION_MANAGER_APPLICATION_LOADER_H_
+#define MOJO_APPLICATION_MANAGER_APPLICATION_LOADER_H_
+
+#include "base/memory/ref_counted.h"
+#include "mojo/application_manager/application_manager_export.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/services/public/interfaces/network/url_loader.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+class ApplicationManager;
+
+// Interface to allowing loading behavior to be established for schemes,
+// specific urls or as the default.
+// A ApplicationLoader is responsible to using whatever mechanism is appropriate
+// to load the application at url.
+// The handle to the shell is passed to that application so it can bind it to
+// a Shell instance. This will give the Application a way to connect to other
+// apps and services.
+class MOJO_APPLICATION_MANAGER_EXPORT ApplicationLoader {
+ public:
+ class MOJO_APPLICATION_MANAGER_EXPORT LoadCallbacks
+ : public base::RefCounted<LoadCallbacks> {
+ public:
+ // Register the requested application with ApplicationManager. If the
+ // returned handle is valid, it should be used to implement the
+ // mojo::Application interface.
+ virtual ScopedMessagePipeHandle RegisterApplication() = 0;
+
+ // Load the requested application with a content handler.
+ virtual void LoadWithContentHandler(const GURL& content_handler_url,
+ URLResponsePtr url_response) = 0;
+
+ protected:
+ friend base::RefCounted<LoadCallbacks>;
+ virtual ~LoadCallbacks() {}
+ };
+
+ // Implements RegisterApplication() by returning a handle that was specified
+ // at construction time. LoadWithContentHandler() is not supported.
+ class MOJO_APPLICATION_MANAGER_EXPORT SimpleLoadCallbacks
+ : public LoadCallbacks {
+ public:
+ SimpleLoadCallbacks(ScopedMessagePipeHandle shell_handle);
+ virtual ScopedMessagePipeHandle RegisterApplication() override;
+ virtual void LoadWithContentHandler(const GURL& content_handler_url,
+ URLResponsePtr response) override;
+
+ private:
+ ScopedMessagePipeHandle shell_handle_;
+ virtual ~SimpleLoadCallbacks();
+ };
+
+ virtual ~ApplicationLoader() {}
+
+ // Load the application named |url|. Applications can be loaded two ways:
+ //
+ // 1. |url| can refer directly to a Mojo application. In this case, call
+ // callbacks->RegisterApplication(). The returned handle should be used to
+ // implement the mojo.Application interface. Note that the returned handle
+ // can be invalid in the case where the application has already been
+ // loaded.
+ //
+ // 2. |url| can refer to some content that can be handled by some other Mojo
+ // application. In this case, call callbacks->LoadWithContentHandler() and
+ // specify the URL of the application that should handle the content.
+ // The specified application must implement the mojo.ContentHandler
+ // interface.
+ virtual void Load(ApplicationManager* application_manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) = 0;
+
+ // Called when the Application exits.
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) = 0;
+
+ protected:
+ ApplicationLoader() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_MANAGER_APPLICATION_LOADER_H_
diff --git a/mojo/application_manager/application_manager.cc b/mojo/application_manager/application_manager.cc
new file mode 100644
index 0000000..b183d9f
--- /dev/null
+++ b/mojo/application_manager/application_manager.cc
@@ -0,0 +1,328 @@
+// 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/application_manager/application_manager.h"
+
+#include <stdio.h>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+
+namespace mojo {
+
+namespace {
+// Used by TestAPI.
+bool has_created_instance = false;
+
+class StubServiceProvider : public InterfaceImpl<ServiceProvider> {
+ public:
+ ServiceProvider* GetRemoteServiceProvider() { return client(); }
+
+ private:
+ virtual void ConnectToService(
+ const String& service_name,
+ ScopedMessagePipeHandle client_handle) override {}
+};
+
+} // namespace
+
+ApplicationManager::Delegate::~Delegate() {}
+
+class ApplicationManager::LoadCallbacksImpl
+ : public ApplicationLoader::LoadCallbacks {
+ public:
+ LoadCallbacksImpl(base::WeakPtr<ApplicationManager> manager,
+ const GURL& requested_url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider)
+ : manager_(manager),
+ requested_url_(requested_url),
+ requestor_url_(requestor_url),
+ service_provider_(service_provider.Pass()) {}
+
+ private:
+ virtual ~LoadCallbacksImpl() {}
+
+ // LoadCallbacks implementation
+ virtual ScopedMessagePipeHandle RegisterApplication() override {
+ ScopedMessagePipeHandle shell_handle;
+ if (manager_) {
+ manager_->RegisterLoadedApplication(requested_url_,
+ requestor_url_,
+ service_provider_.Pass(),
+ &shell_handle);
+ }
+ return shell_handle.Pass();
+ }
+
+ virtual void LoadWithContentHandler(const GURL& content_handler_url,
+ URLResponsePtr url_response) override {
+ if (manager_) {
+ manager_->LoadWithContentHandler(requested_url_,
+ requestor_url_,
+ content_handler_url,
+ url_response.Pass(),
+ service_provider_.Pass());
+ }
+ }
+
+ base::WeakPtr<ApplicationManager> manager_;
+ GURL requested_url_;
+ GURL requestor_url_;
+ ServiceProviderPtr service_provider_;
+};
+
+class ApplicationManager::ShellImpl : public InterfaceImpl<Shell> {
+ public:
+ ShellImpl(ApplicationManager* manager, const GURL& url)
+ : manager_(manager), url_(url) {}
+
+ virtual ~ShellImpl() {}
+
+ void ConnectToClient(const GURL& requestor_url,
+ ServiceProviderPtr service_provider) {
+ client()->AcceptConnection(String::From(requestor_url),
+ service_provider.Pass());
+ }
+
+ // ServiceProvider implementation:
+ virtual void ConnectToApplication(
+ const String& app_url,
+ InterfaceRequest<ServiceProvider> in_service_provider) override {
+ ServiceProviderPtr out_service_provider;
+ out_service_provider.Bind(in_service_provider.PassMessagePipe());
+ manager_->ConnectToApplication(
+ app_url.To<GURL>(), url_, out_service_provider.Pass());
+ }
+
+ const GURL& url() const { return url_; }
+
+ private:
+ virtual void OnConnectionError() override {
+ manager_->OnShellImplError(this);
+ }
+
+ ApplicationManager* const manager_;
+ const GURL url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellImpl);
+};
+
+struct ApplicationManager::ContentHandlerConnection {
+ ContentHandlerConnection(ApplicationManager* manager,
+ const GURL& content_handler_url) {
+ ServiceProviderPtr service_provider;
+ BindToProxy(&service_provider_impl, &service_provider);
+ manager->ConnectToApplication(
+ content_handler_url, GURL(), service_provider.Pass());
+ mojo::ConnectToService(service_provider_impl.client(), &content_handler);
+ }
+
+ StubServiceProvider service_provider_impl;
+ ContentHandlerPtr content_handler;
+};
+
+// static
+ApplicationManager::TestAPI::TestAPI(ApplicationManager* manager)
+ : manager_(manager) {
+}
+
+ApplicationManager::TestAPI::~TestAPI() {
+}
+
+bool ApplicationManager::TestAPI::HasCreatedInstance() {
+ return has_created_instance;
+}
+
+bool ApplicationManager::TestAPI::HasFactoryForURL(const GURL& url) const {
+ return manager_->url_to_shell_impl_.find(url) !=
+ manager_->url_to_shell_impl_.end();
+}
+
+ApplicationManager::ApplicationManager()
+ : delegate_(NULL),
+ interceptor_(NULL),
+ weak_ptr_factory_(this) {
+}
+
+ApplicationManager::~ApplicationManager() {
+ STLDeleteValues(&url_to_content_handler_);
+ TerminateShellConnections();
+ STLDeleteValues(&url_to_loader_);
+ STLDeleteValues(&scheme_to_loader_);
+}
+
+void ApplicationManager::TerminateShellConnections() {
+ STLDeleteValues(&url_to_shell_impl_);
+}
+
+// static
+ApplicationManager* ApplicationManager::GetInstance() {
+ static base::LazyInstance<ApplicationManager> instance =
+ LAZY_INSTANCE_INITIALIZER;
+ has_created_instance = true;
+ return &instance.Get();
+}
+
+void ApplicationManager::ConnectToApplication(
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider) {
+ URLToShellImplMap::const_iterator shell_it = url_to_shell_impl_.find(url);
+ if (shell_it != url_to_shell_impl_.end()) {
+ ConnectToClient(
+ shell_it->second, url, requestor_url, service_provider.Pass());
+ return;
+ }
+
+ scoped_refptr<LoadCallbacksImpl> callbacks(
+ new LoadCallbacksImpl(weak_ptr_factory_.GetWeakPtr(),
+ url,
+ requestor_url,
+ service_provider.Pass()));
+ GetLoaderForURL(url)->Load(this, url, callbacks);
+}
+
+void ApplicationManager::ConnectToClient(ShellImpl* shell_impl,
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider) {
+ if (interceptor_) {
+ shell_impl->ConnectToClient(
+ requestor_url,
+ interceptor_->OnConnectToClient(url, service_provider.Pass()));
+ } else {
+ shell_impl->ConnectToClient(requestor_url, service_provider.Pass());
+ }
+}
+
+void ApplicationManager::RegisterExternalApplication(
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle) {
+ url_to_shell_impl_[url] =
+ WeakBindToPipe(new ShellImpl(this, url), shell_handle.Pass());
+}
+
+void ApplicationManager::RegisterLoadedApplication(
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider,
+ ScopedMessagePipeHandle* shell_handle) {
+ ShellImpl* shell_impl = NULL;
+ URLToShellImplMap::iterator iter = url_to_shell_impl_.find(url);
+ if (iter != url_to_shell_impl_.end()) {
+ // This can happen because services are loaded asynchronously. So if we get
+ // two requests for the same service close to each other, we might get here
+ // and find that we already have it.
+ shell_impl = iter->second;
+ } else {
+ MessagePipe pipe;
+ URLToArgsMap::const_iterator args_it = url_to_args_.find(url);
+ Array<String> args;
+ if (args_it != url_to_args_.end())
+ args = Array<String>::From(args_it->second);
+ shell_impl = WeakBindToPipe(new ShellImpl(this, url), pipe.handle1.Pass());
+ url_to_shell_impl_[url] = shell_impl;
+ *shell_handle = pipe.handle0.Pass();
+ shell_impl->client()->Initialize(args.Pass());
+ }
+
+ ConnectToClient(shell_impl, url, requestor_url, service_provider.Pass());
+}
+
+void ApplicationManager::LoadWithContentHandler(
+ const GURL& content_url,
+ const GURL& requestor_url,
+ const GURL& content_handler_url,
+ URLResponsePtr url_response,
+ ServiceProviderPtr service_provider) {
+ ContentHandlerConnection* connection = NULL;
+ URLToContentHandlerMap::iterator iter =
+ url_to_content_handler_.find(content_handler_url);
+ if (iter != url_to_content_handler_.end()) {
+ connection = iter->second;
+ } else {
+ connection = new ContentHandlerConnection(this, content_handler_url);
+ url_to_content_handler_[content_handler_url] = connection;
+ }
+
+ InterfaceRequest<ServiceProvider> spir;
+ spir.Bind(service_provider.PassMessagePipe());
+ connection->content_handler->OnConnect(
+ content_url.spec(), url_response.Pass(), spir.Pass());
+}
+
+void ApplicationManager::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader,
+ const GURL& url) {
+ URLToLoaderMap::iterator it = url_to_loader_.find(url);
+ if (it != url_to_loader_.end())
+ delete it->second;
+ url_to_loader_[url] = loader.release();
+}
+
+void ApplicationManager::SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader> loader,
+ const std::string& scheme) {
+ SchemeToLoaderMap::iterator it = scheme_to_loader_.find(scheme);
+ if (it != scheme_to_loader_.end())
+ delete it->second;
+ scheme_to_loader_[scheme] = loader.release();
+}
+
+void ApplicationManager::SetArgsForURL(const std::vector<std::string>& args,
+ const GURL& url) {
+ url_to_args_[url] = args;
+}
+
+void ApplicationManager::SetInterceptor(Interceptor* interceptor) {
+ interceptor_ = interceptor;
+}
+
+ApplicationLoader* ApplicationManager::GetLoaderForURL(const GURL& url) {
+ URLToLoaderMap::const_iterator url_it = url_to_loader_.find(url);
+ if (url_it != url_to_loader_.end())
+ return url_it->second;
+ SchemeToLoaderMap::const_iterator scheme_it =
+ scheme_to_loader_.find(url.scheme());
+ if (scheme_it != scheme_to_loader_.end())
+ return scheme_it->second;
+ return default_loader_.get();
+}
+
+void ApplicationManager::OnShellImplError(ShellImpl* shell_impl) {
+ // Called from ~ShellImpl, so we do not need to call Destroy here.
+ const GURL url = shell_impl->url();
+ URLToShellImplMap::iterator it = url_to_shell_impl_.find(url);
+ DCHECK(it != url_to_shell_impl_.end());
+ delete it->second;
+ url_to_shell_impl_.erase(it);
+ ApplicationLoader* loader = GetLoaderForURL(url);
+ if (loader)
+ loader->OnApplicationError(this, url);
+ if (delegate_)
+ delegate_->OnApplicationError(url);
+}
+
+ScopedMessagePipeHandle ApplicationManager::ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& interface_name) {
+ StubServiceProvider* stub_sp = new StubServiceProvider;
+ ServiceProviderPtr spp;
+ BindToProxy(stub_sp, &spp);
+ ConnectToApplication(application_url, GURL(), spp.Pass());
+ MessagePipe pipe;
+ stub_sp->GetRemoteServiceProvider()->ConnectToService(interface_name,
+ pipe.handle1.Pass());
+ return pipe.handle0.Pass();
+}
+} // namespace mojo
diff --git a/mojo/application_manager/application_manager.h b/mojo/application_manager/application_manager.h
new file mode 100644
index 0000000..af74d8b
--- /dev/null
+++ b/mojo/application_manager/application_manager.h
@@ -0,0 +1,163 @@
+// 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_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
+#define MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/application_manager/application_manager_export.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+class MOJO_APPLICATION_MANAGER_EXPORT ApplicationManager {
+ public:
+ class MOJO_APPLICATION_MANAGER_EXPORT Delegate {
+ public:
+ virtual ~Delegate();
+ // Send when the Application holding the handle on the other end of the
+ // Shell pipe goes away.
+ virtual void OnApplicationError(const GURL& url) = 0;
+ };
+
+ // API for testing.
+ class MOJO_APPLICATION_MANAGER_EXPORT TestAPI {
+ public:
+ explicit TestAPI(ApplicationManager* manager);
+ ~TestAPI();
+
+ // Returns true if the shared instance has been created.
+ static bool HasCreatedInstance();
+ // Returns true if there is a ShellImpl for this URL.
+ bool HasFactoryForURL(const GURL& url) const;
+
+ private:
+ ApplicationManager* manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestAPI);
+ };
+
+ // Interface class for debugging only.
+ class Interceptor {
+ public:
+ virtual ~Interceptor() {}
+ // Called when ApplicationManager::Connect is called.
+ virtual ServiceProviderPtr OnConnectToClient(
+ const GURL& url,
+ ServiceProviderPtr service_provider) = 0;
+ };
+
+ ApplicationManager();
+ ~ApplicationManager();
+
+ // Returns a shared instance, creating it if necessary.
+ static ApplicationManager* GetInstance();
+
+ void SetDelegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // Loads a service if necessary and establishes a new client connection.
+ void ConnectToApplication(const GURL& application_url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider);
+
+ template <typename Interface>
+ inline void ConnectToService(const GURL& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ScopedMessagePipeHandle service_handle =
+ ConnectToServiceByName(application_url, Interface::Name_);
+ ptr->Bind(service_handle.Pass());
+ }
+
+ ScopedMessagePipeHandle ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& interface_name);
+
+ void RegisterExternalApplication(const GURL& application_url,
+ ScopedMessagePipeHandle shell);
+
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // Sets the default Loader to be used if not overridden by SetLoaderForURL()
+ // or SetLoaderForScheme().
+ void set_default_loader(scoped_ptr<ApplicationLoader> loader) {
+ default_loader_ = loader.Pass();
+ }
+ // Sets a Loader to be used for a specific url.
+ void SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, const GURL& url);
+ // Sets a Loader to be used for a specific url scheme.
+ void SetLoaderForScheme(scoped_ptr<ApplicationLoader> loader,
+ const std::string& scheme);
+ // These strings will be passed to the Initialize() method when an
+ // Application is instantiated.
+ void SetArgsForURL(const std::vector<std::string>& args, const GURL& url);
+
+ // Allows to interpose a debugger to service connections.
+ void SetInterceptor(Interceptor* interceptor);
+
+ // Destroys all Shell-ends of connections established with Applications.
+ // Applications connected by this ApplicationManager will observe pipe errors
+ // and have a chance to shutdown.
+ void TerminateShellConnections();
+
+ private:
+ struct ContentHandlerConnection;
+ class LoadCallbacksImpl;
+ class ShellImpl;
+
+ typedef std::map<std::string, ApplicationLoader*> SchemeToLoaderMap;
+ typedef std::map<GURL, ApplicationLoader*> URLToLoaderMap;
+ typedef std::map<GURL, ShellImpl*> URLToShellImplMap;
+ typedef std::map<GURL, ContentHandlerConnection*> URLToContentHandlerMap;
+ typedef std::map<GURL, std::vector<std::string> > URLToArgsMap;
+
+ void ConnectToClient(ShellImpl* shell_impl,
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider);
+
+ void RegisterLoadedApplication(const GURL& service_url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider,
+ ScopedMessagePipeHandle* shell_handle);
+
+ void LoadWithContentHandler(const GURL& content_url,
+ const GURL& requestor_url,
+ const GURL& content_handler_url,
+ URLResponsePtr url_response,
+ ServiceProviderPtr service_provider);
+
+ // Returns the Loader to use for a url (using default if not overridden.)
+ // The preference is to use a loader that's been specified for an url first,
+ // then one that's been specified for a scheme, then the default.
+ ApplicationLoader* GetLoaderForURL(const GURL& url);
+
+ // Removes a ShellImpl when it encounters an error.
+ void OnShellImplError(ShellImpl* shell_impl);
+
+ Delegate* delegate_;
+ // Loader management.
+ URLToLoaderMap url_to_loader_;
+ SchemeToLoaderMap scheme_to_loader_;
+ scoped_ptr<ApplicationLoader> default_loader_;
+ Interceptor* interceptor_;
+
+ URLToShellImplMap url_to_shell_impl_;
+ URLToContentHandlerMap url_to_content_handler_;
+ URLToArgsMap url_to_args_;
+
+ base::WeakPtrFactory<ApplicationManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApplicationManager);
+};
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
diff --git a/mojo/application_manager/application_manager_export.h b/mojo/application_manager/application_manager_export.h
new file mode 100644
index 0000000..9af43cf
--- /dev/null
+++ b/mojo/application_manager/application_manager_export.h
@@ -0,0 +1,32 @@
+// 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_APPLICATION_MANAGER_APPLICATION_MANAGER_EXPORT_H_
+#define MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WIN32)
+
+#if defined(MOJO_APPLICATION_MANAGER_IMPLEMENTATION)
+#define MOJO_APPLICATION_MANAGER_EXPORT __declspec(dllexport)
+#else
+#define MOJO_APPLICATION_MANAGER_EXPORT __declspec(dllimport)
+#endif
+
+#else // !defined(WIN32)
+
+#if defined(MOJO_APPLICATION_MANAGER_IMPLEMENTATION)
+#define MOJO_APPLICATION_MANAGER_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_APPLICATION_MANAGER_EXPORT
+#endif
+
+#endif // defined(WIN32)
+
+#else // !defined(COMPONENT_BUILD)
+#define MOJO_APPLICATION_MANAGER_EXPORT
+#endif
+
+#endif // MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_EXPORT_H_
diff --git a/mojo/application_manager/application_manager_unittest.cc b/mojo/application_manager/application_manager_unittest.cc
new file mode 100644
index 0000000..5763265
--- /dev/null
+++ b/mojo/application_manager/application_manager_unittest.cc
@@ -0,0 +1,686 @@
+// 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/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/application_manager/background_shell_application_loader.h"
+#include "mojo/application_manager/test.mojom.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+const char kTestURLString[] = "test:testService";
+const char kTestAURLString[] = "test:TestA";
+const char kTestBURLString[] = "test:TestB";
+
+struct TestContext {
+ TestContext() : num_impls(0), num_loader_deletes(0) {}
+ std::string last_test_string;
+ int num_impls;
+ int num_loader_deletes;
+};
+
+class QuitMessageLoopErrorHandler : public ErrorHandler {
+ public:
+ QuitMessageLoopErrorHandler() {}
+ virtual ~QuitMessageLoopErrorHandler() {}
+
+ // |ErrorHandler| implementation:
+ virtual void OnConnectionError() override {
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler);
+};
+
+class TestServiceImpl : public InterfaceImpl<TestService> {
+ public:
+ explicit TestServiceImpl(TestContext* context) : context_(context) {
+ ++context_->num_impls;
+ }
+
+ virtual ~TestServiceImpl() { --context_->num_impls; }
+
+ virtual void OnConnectionError() override {
+ if (!base::MessageLoop::current()->is_running())
+ return;
+ base::MessageLoop::current()->Quit();
+ }
+
+ // TestService implementation:
+ virtual void Test(const String& test_string) override {
+ context_->last_test_string = test_string;
+ client()->AckTest();
+ }
+
+ private:
+ TestContext* context_;
+};
+
+class TestClientImpl : public TestClient {
+ public:
+ explicit TestClientImpl(TestServicePtr service)
+ : service_(service.Pass()), quit_after_ack_(false) {
+ service_.set_client(this);
+ }
+
+ virtual ~TestClientImpl() { service_.reset(); }
+
+ virtual void AckTest() override {
+ if (quit_after_ack_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ void Test(std::string test_string) {
+ quit_after_ack_ = true;
+ service_->Test(test_string);
+ }
+
+ private:
+ TestServicePtr service_;
+ bool quit_after_ack_;
+ DISALLOW_COPY_AND_ASSIGN(TestClientImpl);
+};
+
+class TestApplicationLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<TestService> {
+ public:
+ TestApplicationLoader() : context_(NULL), num_loads_(0) {}
+
+ virtual ~TestApplicationLoader() {
+ if (context_)
+ ++context_->num_loader_deletes;
+ test_app_.reset(NULL);
+ }
+
+ void set_context(TestContext* context) { context_ = context; }
+ int num_loads() const { return num_loads_; }
+ std::vector<std::string> GetArgs() {
+ return test_app_->args().To<std::vector<std::string> >();
+ }
+
+ private:
+ // ApplicationLoader implementation.
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ ++num_loads_;
+ test_app_.reset(
+ new ApplicationImpl(this, callbacks->RegisterApplication().Pass()));
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {}
+
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // InterfaceFactory implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestService> request) override {
+ BindToRequest(new TestServiceImpl(context_), &request);
+ }
+
+ scoped_ptr<ApplicationImpl> test_app_;
+ TestContext* context_;
+ int num_loads_;
+ DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader);
+};
+
+class TesterContext {
+ public:
+ explicit TesterContext(base::MessageLoop* loop)
+ : num_b_calls_(0),
+ num_c_calls_(0),
+ num_a_deletes_(0),
+ num_b_deletes_(0),
+ num_c_deletes_(0),
+ tester_called_quit_(false),
+ a_called_quit_(false),
+ loop_(loop) {}
+
+ void IncrementNumBCalls() {
+ base::AutoLock lock(lock_);
+ num_b_calls_++;
+ }
+
+ void IncrementNumCCalls() {
+ base::AutoLock lock(lock_);
+ num_c_calls_++;
+ }
+
+ void IncrementNumADeletes() {
+ base::AutoLock lock(lock_);
+ num_a_deletes_++;
+ }
+
+ void IncrementNumBDeletes() {
+ base::AutoLock lock(lock_);
+ num_b_deletes_++;
+ }
+
+ void IncrementNumCDeletes() {
+ base::AutoLock lock(lock_);
+ num_c_deletes_++;
+ }
+
+ void set_tester_called_quit() {
+ base::AutoLock lock(lock_);
+ tester_called_quit_ = true;
+ }
+
+ void set_a_called_quit() {
+ base::AutoLock lock(lock_);
+ a_called_quit_ = true;
+ }
+
+ int num_b_calls() {
+ base::AutoLock lock(lock_);
+ return num_b_calls_;
+ }
+ int num_c_calls() {
+ base::AutoLock lock(lock_);
+ return num_c_calls_;
+ }
+ int num_a_deletes() {
+ base::AutoLock lock(lock_);
+ return num_a_deletes_;
+ }
+ int num_b_deletes() {
+ base::AutoLock lock(lock_);
+ return num_b_deletes_;
+ }
+ int num_c_deletes() {
+ base::AutoLock lock(lock_);
+ return num_c_deletes_;
+ }
+ bool tester_called_quit() {
+ base::AutoLock lock(lock_);
+ return tester_called_quit_;
+ }
+ bool a_called_quit() {
+ base::AutoLock lock(lock_);
+ return a_called_quit_;
+ }
+
+ void QuitSoon() {
+ loop_->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+ }
+
+ private:
+ // lock_ protects all members except for loop_ which must be unchanged for the
+ // lifetime of this class.
+ base::Lock lock_;
+ int num_b_calls_;
+ int num_c_calls_;
+ int num_a_deletes_;
+ int num_b_deletes_;
+ int num_c_deletes_;
+ bool tester_called_quit_;
+ bool a_called_quit_;
+
+ base::MessageLoop* loop_;
+};
+
+// Used to test that the requestor url will be correctly passed.
+class TestAImpl : public InterfaceImpl<TestA> {
+ public:
+ TestAImpl(ApplicationConnection* connection, TesterContext* test_context)
+ : test_context_(test_context) {
+ connection->ConnectToApplication(kTestBURLString)->ConnectToService(&b_);
+ }
+ virtual ~TestAImpl() {
+ test_context_->IncrementNumADeletes();
+ if (base::MessageLoop::current()->is_running())
+ Quit();
+ }
+
+ private:
+ virtual void CallB() override {
+ b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
+ }
+
+ virtual void CallCFromB() override {
+ b_->CallC(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
+ }
+
+ void Quit() {
+ base::MessageLoop::current()->Quit();
+ test_context_->set_a_called_quit();
+ test_context_->QuitSoon();
+ }
+
+ TesterContext* test_context_;
+ TestBPtr b_;
+};
+
+class TestBImpl : public InterfaceImpl<TestB> {
+ public:
+ TestBImpl(ApplicationConnection* connection, TesterContext* test_context)
+ : test_context_(test_context) {
+ connection->ConnectToService(&c_);
+ }
+
+ virtual ~TestBImpl() {
+ test_context_->IncrementNumBDeletes();
+ if (base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->Quit();
+ test_context_->QuitSoon();
+ }
+
+ private:
+ virtual void B(const mojo::Callback<void()>& callback) override {
+ test_context_->IncrementNumBCalls();
+ callback.Run();
+ }
+
+ virtual void CallC(const mojo::Callback<void()>& callback) override {
+ test_context_->IncrementNumBCalls();
+ c_->C(callback);
+ }
+
+ TesterContext* test_context_;
+ TestCPtr c_;
+};
+
+class TestCImpl : public InterfaceImpl<TestC> {
+ public:
+ TestCImpl(ApplicationConnection* connection, TesterContext* test_context)
+ : test_context_(test_context) {}
+
+ virtual ~TestCImpl() { test_context_->IncrementNumCDeletes(); }
+
+ private:
+ virtual void C(const mojo::Callback<void()>& callback) override {
+ test_context_->IncrementNumCCalls();
+ callback.Run();
+ }
+ TesterContext* test_context_;
+};
+
+class Tester : public ApplicationDelegate,
+ public ApplicationLoader,
+ public InterfaceFactory<TestA>,
+ public InterfaceFactory<TestB>,
+ public InterfaceFactory<TestC> {
+ public:
+ Tester(TesterContext* context, const std::string& requestor_url)
+ : context_(context), requestor_url_(requestor_url) {}
+ virtual ~Tester() {}
+
+ private:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ app_.reset(
+ new ApplicationImpl(this, callbacks->RegisterApplication().Pass()));
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {}
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ if (!requestor_url_.empty() &&
+ requestor_url_ != connection->GetRemoteApplicationURL()) {
+ context_->set_tester_called_quit();
+ context_->QuitSoon();
+ base::MessageLoop::current()->Quit();
+ return false;
+ }
+ // If we're coming from A, then add B, otherwise A.
+ if (connection->GetRemoteApplicationURL() == kTestAURLString)
+ connection->AddService<TestB>(this);
+ else
+ connection->AddService<TestA>(this);
+ return true;
+ }
+
+ virtual bool ConfigureOutgoingConnection(
+ ApplicationConnection* connection) override {
+ // If we're connecting to B, then add C.
+ if (connection->GetRemoteApplicationURL() == kTestBURLString)
+ connection->AddService<TestC>(this);
+ return true;
+ }
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestA> request) override {
+ BindToRequest(new TestAImpl(connection, context_), &request);
+ }
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestB> request) override {
+ BindToRequest(new TestBImpl(connection, context_), &request);
+ }
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestC> request) override {
+ BindToRequest(new TestCImpl(connection, context_), &request);
+ }
+
+ TesterContext* context_;
+ scoped_ptr<ApplicationImpl> app_;
+ std::string requestor_url_;
+};
+
+class TestServiceInterceptor : public ApplicationManager::Interceptor {
+ public:
+ TestServiceInterceptor() : call_count_(0) {}
+
+ virtual ServiceProviderPtr OnConnectToClient(
+ const GURL& url,
+ ServiceProviderPtr service_provider) override {
+ ++call_count_;
+ url_ = url;
+ return service_provider.Pass();
+ }
+
+ std::string url_spec() const {
+ if (!url_.is_valid())
+ return "invalid url";
+ return url_.spec();
+ }
+
+ int call_count() const { return call_count_; }
+
+ private:
+ int call_count_;
+ GURL url_;
+ DISALLOW_COPY_AND_ASSIGN(TestServiceInterceptor);
+};
+
+} // namespace
+
+class ApplicationManagerTest : public testing::Test {
+ public:
+ ApplicationManagerTest() : tester_context_(&loop_) {}
+
+ virtual ~ApplicationManagerTest() {}
+
+ virtual void SetUp() override {
+ application_manager_.reset(new ApplicationManager);
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ default_loader->set_context(&context_);
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(default_loader));
+
+ TestServicePtr service_proxy;
+ application_manager_->ConnectToService(GURL(kTestURLString),
+ &service_proxy);
+ test_client_.reset(new TestClientImpl(service_proxy.Pass()));
+ }
+
+ virtual void TearDown() override {
+ test_client_.reset(NULL);
+ application_manager_.reset(NULL);
+ }
+
+ scoped_ptr<BackgroundShellApplicationLoader> MakeLoader(
+ const std::string& requestor_url) {
+ scoped_ptr<ApplicationLoader> real_loader(
+ new Tester(&tester_context_, requestor_url));
+ scoped_ptr<BackgroundShellApplicationLoader> loader(
+ new BackgroundShellApplicationLoader(real_loader.Pass(),
+ std::string(),
+ base::MessageLoop::TYPE_DEFAULT));
+ return loader.Pass();
+ }
+
+ void AddLoaderForURL(const GURL& url, const std::string& requestor_url) {
+ application_manager_->SetLoaderForURL(
+ MakeLoader(requestor_url).PassAs<ApplicationLoader>(), url);
+ }
+
+ bool HasFactoryForTestURL() {
+ ApplicationManager::TestAPI manager_test_api(application_manager_.get());
+ return manager_test_api.HasFactoryForURL(GURL(kTestURLString));
+ }
+
+ protected:
+ base::ShadowingAtExitManager at_exit_;
+ TesterContext tester_context_;
+ TestContext context_;
+ base::MessageLoop loop_;
+ scoped_ptr<TestClientImpl> test_client_;
+ scoped_ptr<ApplicationManager> application_manager_;
+ DISALLOW_COPY_AND_ASSIGN(ApplicationManagerTest);
+};
+
+TEST_F(ApplicationManagerTest, Basic) {
+ test_client_->Test("test");
+ loop_.Run();
+ EXPECT_EQ(std::string("test"), context_.last_test_string);
+}
+
+// Confirm that no arguments are sent to an application by default.
+TEST_F(ApplicationManagerTest, NoArgs) {
+ ApplicationManager am;
+ GURL test_url("test:test");
+ TestContext context;
+ TestApplicationLoader* loader = new TestApplicationLoader;
+ loader->set_context(&context);
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
+ TestServicePtr test_service;
+ am.ConnectToService(test_url, &test_service);
+ TestClientImpl test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ EXPECT_EQ(0U, app_args.size());
+}
+
+// Confirm that arguments are sent to an application.
+TEST_F(ApplicationManagerTest, Args) {
+ ApplicationManager am;
+ GURL test_url("test:test");
+ std::vector<std::string> args;
+ args.push_back("test_arg1");
+ args.push_back("test_arg2");
+ am.SetArgsForURL(args, test_url);
+ TestContext context;
+ TestApplicationLoader* loader = new TestApplicationLoader;
+ loader->set_context(&context);
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
+ TestServicePtr test_service;
+ am.ConnectToService(test_url, &test_service);
+ TestClientImpl test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ ASSERT_EQ(args.size(), app_args.size());
+ EXPECT_EQ(args[0], app_args[0]);
+ EXPECT_EQ(args[1], app_args[1]);
+}
+
+TEST_F(ApplicationManagerTest, ClientError) {
+ test_client_->Test("test");
+ EXPECT_TRUE(HasFactoryForTestURL());
+ loop_.Run();
+ EXPECT_EQ(1, context_.num_impls);
+ test_client_.reset(NULL);
+ loop_.Run();
+ EXPECT_EQ(0, context_.num_impls);
+ EXPECT_TRUE(HasFactoryForTestURL());
+}
+
+TEST_F(ApplicationManagerTest, Deletes) {
+ {
+ ApplicationManager am;
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ default_loader->set_context(&context_);
+ TestApplicationLoader* url_loader1 = new TestApplicationLoader;
+ TestApplicationLoader* url_loader2 = new TestApplicationLoader;
+ url_loader1->set_context(&context_);
+ url_loader2->set_context(&context_);
+ TestApplicationLoader* scheme_loader1 = new TestApplicationLoader;
+ TestApplicationLoader* scheme_loader2 = new TestApplicationLoader;
+ scheme_loader1->set_context(&context_);
+ scheme_loader2->set_context(&context_);
+ am.set_default_loader(scoped_ptr<ApplicationLoader>(default_loader));
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader1),
+ GURL("test:test1"));
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader2),
+ GURL("test:test1"));
+ am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader1),
+ "test");
+ am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader2),
+ "test");
+ }
+ EXPECT_EQ(5, context_.num_loader_deletes);
+}
+
+// Confirm that both urls and schemes can have their loaders explicitly set.
+TEST_F(ApplicationManagerTest, SetLoaders) {
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ TestApplicationLoader* url_loader = new TestApplicationLoader;
+ TestApplicationLoader* scheme_loader = new TestApplicationLoader;
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(default_loader));
+ application_manager_->SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1"));
+ application_manager_->SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader>(scheme_loader), "test");
+
+ // test::test1 should go to url_loader.
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL("test:test1"), &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(0, scheme_loader->num_loads());
+ EXPECT_EQ(0, default_loader->num_loads());
+
+ // test::test2 should go to scheme loader.
+ application_manager_->ConnectToService(GURL("test:test2"), &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(1, scheme_loader->num_loads());
+ EXPECT_EQ(0, default_loader->num_loads());
+
+ // http::test1 should go to default loader.
+ application_manager_->ConnectToService(GURL("http:test1"), &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(1, scheme_loader->num_loads());
+ EXPECT_EQ(1, default_loader->num_loads());
+}
+
+// Confirm that the url of a service is correctly passed to another service that
+// it loads.
+TEST_F(ApplicationManagerTest, ACallB) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only a can load b.
+ AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallB();
+ loop_.Run();
+ EXPECT_EQ(1, tester_context_.num_b_calls());
+ EXPECT_TRUE(tester_context_.a_called_quit());
+}
+
+// A calls B which calls C.
+TEST_F(ApplicationManagerTest, BCallC) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only a can load b.
+ AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallCFromB();
+ loop_.Run();
+
+ EXPECT_EQ(1, tester_context_.num_b_calls());
+ EXPECT_EQ(1, tester_context_.num_c_calls());
+ EXPECT_TRUE(tester_context_.a_called_quit());
+}
+
+// Confirm that a service impl will be deleted if the app that connected to
+// it goes away.
+TEST_F(ApplicationManagerTest, BDeleted) {
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+ AddLoaderForURL(GURL(kTestBURLString), std::string());
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+
+ a->CallB();
+ loop_.Run();
+
+ // Kills the a app.
+ application_manager_->SetLoaderForURL(scoped_ptr<ApplicationLoader>(),
+ GURL(kTestAURLString));
+ loop_.Run();
+
+ EXPECT_EQ(1, tester_context_.num_b_deletes());
+}
+
+// Confirm that the url of a service is correctly passed to another service that
+// it loads, and that it can be rejected.
+TEST_F(ApplicationManagerTest, ANoLoadB) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only c can load b, so this will fail.
+ AddLoaderForURL(GURL(kTestBURLString), "test:TestC");
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallB();
+ loop_.Run();
+ EXPECT_EQ(0, tester_context_.num_b_calls());
+
+ EXPECT_FALSE(tester_context_.a_called_quit());
+ EXPECT_TRUE(tester_context_.tester_called_quit());
+}
+
+TEST_F(ApplicationManagerTest, NoServiceNoLoad) {
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // There is no TestC service implementation registered with
+ // ApplicationManager, so this cannot succeed (but also shouldn't crash).
+ TestCPtr c;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &c);
+ QuitMessageLoopErrorHandler quitter;
+ c.set_error_handler(&quitter);
+
+ loop_.Run();
+ EXPECT_TRUE(c.encountered_error());
+}
+
+TEST_F(ApplicationManagerTest, Interceptor) {
+ TestServiceInterceptor interceptor;
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(default_loader));
+ application_manager_->SetInterceptor(&interceptor);
+
+ std::string url("test:test3");
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL(url), &test_service);
+
+ EXPECT_EQ(1, interceptor.call_count());
+ EXPECT_EQ(url, interceptor.url_spec());
+ EXPECT_EQ(1, default_loader->num_loads());
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/background_shell_application_loader.cc b/mojo/application_manager/background_shell_application_loader.cc
new file mode 100644
index 0000000..e6664db
--- /dev/null
+++ b/mojo/application_manager/background_shell_application_loader.cc
@@ -0,0 +1,127 @@
+// 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/application_manager/background_shell_application_loader.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "mojo/application_manager/application_manager.h"
+
+namespace mojo {
+
+class BackgroundShellApplicationLoader::BackgroundLoader {
+ public:
+ explicit BackgroundLoader(ApplicationLoader* loader) : loader_(loader) {}
+ ~BackgroundLoader() {}
+
+ void Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle) {
+ scoped_refptr<LoadCallbacks> callbacks(
+ new ApplicationLoader::SimpleLoadCallbacks(shell_handle.Pass()));
+ loader_->Load(manager, url, callbacks);
+ }
+
+ void OnApplicationError(ApplicationManager* manager, const GURL& url) {
+ loader_->OnApplicationError(manager, url);
+ }
+
+ private:
+ ApplicationLoader* loader_; // Owned by BackgroundShellApplicationLoader
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundLoader);
+};
+
+BackgroundShellApplicationLoader::BackgroundShellApplicationLoader(
+ scoped_ptr<ApplicationLoader> real_loader,
+ const std::string& thread_name,
+ base::MessageLoop::Type message_loop_type)
+ : loader_(real_loader.Pass()),
+ message_loop_type_(message_loop_type),
+ thread_name_(thread_name),
+ message_loop_created_(true, false),
+ background_loader_(NULL) {
+}
+
+BackgroundShellApplicationLoader::~BackgroundShellApplicationLoader() {
+ if (thread_)
+ thread_->Join();
+}
+
+void BackgroundShellApplicationLoader::Load(
+ ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+
+ if (!thread_) {
+ // TODO(tim): It'd be nice if we could just have each Load call
+ // result in a new thread like DynamicService{Loader, Runner}. But some
+ // loaders are creating multiple ApplicationImpls (NetworkApplicationLoader)
+ // sharing a delegate (etc). So we have to keep it single threaded, wait
+ // for the thread to initialize, and post to the TaskRunner for subsequent
+ // Load calls for now.
+ thread_.reset(new base::DelegateSimpleThread(this, thread_name_));
+ thread_->Start();
+ message_loop_created_.Wait();
+ DCHECK(task_runner_.get());
+ }
+
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &BackgroundShellApplicationLoader::LoadOnBackgroundThread,
+ base::Unretained(this),
+ manager,
+ url,
+ base::Owned(new ScopedMessagePipeHandle(shell_handle.Pass()))));
+}
+
+void BackgroundShellApplicationLoader::OnApplicationError(
+ ApplicationManager* manager,
+ const GURL& url) {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&BackgroundShellApplicationLoader::
+ OnApplicationErrorOnBackgroundThread,
+ base::Unretained(this),
+ manager,
+ url));
+}
+
+void BackgroundShellApplicationLoader::Run() {
+ base::MessageLoop message_loop(message_loop_type_);
+ base::RunLoop loop;
+ task_runner_ = message_loop.task_runner();
+ quit_closure_ = loop.QuitClosure();
+ message_loop_created_.Signal();
+ loop.Run();
+
+ delete background_loader_;
+ background_loader_ = NULL;
+ // Destroy |loader_| on the thread it's actually used on.
+ loader_.reset();
+}
+
+void BackgroundShellApplicationLoader::LoadOnBackgroundThread(
+ ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle* shell_handle) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ if (!background_loader_)
+ background_loader_ = new BackgroundLoader(loader_.get());
+ background_loader_->Load(manager, url, shell_handle->Pass());
+}
+
+void BackgroundShellApplicationLoader::OnApplicationErrorOnBackgroundThread(
+ ApplicationManager* manager,
+ const GURL& url) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ if (!background_loader_)
+ background_loader_ = new BackgroundLoader(loader_.get());
+ background_loader_->OnApplicationError(manager, url);
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/background_shell_application_loader.h b/mojo/application_manager/background_shell_application_loader.h
new file mode 100644
index 0000000..3c56290
--- /dev/null
+++ b/mojo/application_manager/background_shell_application_loader.h
@@ -0,0 +1,77 @@
+// 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_APPLICATION_MANAGER_BACKGROUND_SHELL_APPLICATION_LOADER_H_
+#define MOJO_APPLICATION_MANAGER_BACKGROUND_SHELL_APPLICATION_LOADER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/application_manager/application_loader.h"
+
+namespace mojo {
+
+// TODO(tim): Eventually this should be Android-only to support services
+// that we need to bundle with the shell (such as NetworkService). Perhaps
+// we should move it to shell/ as well.
+class MOJO_APPLICATION_MANAGER_EXPORT BackgroundShellApplicationLoader
+ : public ApplicationLoader,
+ public base::DelegateSimpleThread::Delegate {
+ public:
+ BackgroundShellApplicationLoader(scoped_ptr<ApplicationLoader> real_loader,
+ const std::string& thread_name,
+ base::MessageLoop::Type message_loop_type);
+ virtual ~BackgroundShellApplicationLoader();
+
+ // ApplicationLoader overrides:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override;
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override;
+
+ private:
+ class BackgroundLoader;
+
+ // |base::DelegateSimpleThread::Delegate| method:
+ virtual void Run() override;
+
+ // These functions are exected on the background thread. They call through
+ // to |background_loader_| to do the actual loading.
+ // TODO: having this code take a |manager| is fragile (as ApplicationManager
+ // isn't thread safe).
+ void LoadOnBackgroundThread(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle* shell_handle);
+ void OnApplicationErrorOnBackgroundThread(ApplicationManager* manager,
+ const GURL& url);
+ bool quit_on_shutdown_;
+ scoped_ptr<ApplicationLoader> loader_;
+
+ const base::MessageLoop::Type message_loop_type_;
+ const std::string thread_name_;
+
+ // Created on |thread_| during construction of |this|. Protected against
+ // uninitialized use by |message_loop_created_|, and protected against
+ // use-after-free by holding a reference to the thread-safe object. Note
+ // that holding a reference won't hold |thread_| from exiting.
+ scoped_refptr<base::TaskRunner> task_runner_;
+ base::WaitableEvent message_loop_created_;
+
+ // Lives on |thread_|.
+ base::Closure quit_closure_;
+
+ scoped_ptr<base::DelegateSimpleThread> thread_;
+
+ // Lives on |thread_|. Trivial interface that calls through to |loader_|.
+ BackgroundLoader* background_loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundShellApplicationLoader);
+};
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_MANAGER_BACKGROUND_SHELL_APPLICATION_LOADER_H_
diff --git a/mojo/application_manager/background_shell_application_loader_unittest.cc b/mojo/application_manager/background_shell_application_loader_unittest.cc
new file mode 100644
index 0000000..acf1a01
--- /dev/null
+++ b/mojo/application_manager/background_shell_application_loader_unittest.cc
@@ -0,0 +1,56 @@
+// 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/application_manager/background_shell_application_loader.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+namespace {
+
+class DummyLoader : public ApplicationLoader {
+ public:
+ DummyLoader() : simulate_app_quit_(true) {}
+ virtual ~DummyLoader() {}
+
+ // ApplicationLoader overrides:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ if (simulate_app_quit_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {}
+
+ void DontSimulateAppQuit() { simulate_app_quit_ = false; }
+
+ private:
+ bool simulate_app_quit_;
+};
+
+} // namespace
+
+// Tests that the loader can start and stop gracefully.
+TEST(BackgroundShellApplicationLoaderTest, StartStop) {
+ scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
+ BackgroundShellApplicationLoader loader(
+ real_loader.Pass(), "test", base::MessageLoop::TYPE_DEFAULT);
+}
+
+// Tests that the loader can load a service that is well behaved (quits
+// itself).
+TEST(BackgroundShellApplicationLoaderTest, Load) {
+ scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
+ BackgroundShellApplicationLoader loader(
+ real_loader.Pass(), "test", base::MessageLoop::TYPE_DEFAULT);
+ MessagePipe dummy;
+ scoped_refptr<ApplicationLoader::SimpleLoadCallbacks> callbacks(
+ new ApplicationLoader::SimpleLoadCallbacks(dummy.handle0.Pass()));
+ loader.Load(NULL, GURL(), callbacks);
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/test.mojom b/mojo/application_manager/test.mojom
new file mode 100644
index 0000000..6a03581
--- /dev/null
+++ b/mojo/application_manager/test.mojom
@@ -0,0 +1,30 @@
+// 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 {
+
+[Client=TestClient]
+interface TestService {
+ Test(string? test_string);
+};
+
+interface TestClient {
+ AckTest();
+};
+
+interface TestA {
+ CallB();
+ CallCFromB();
+};
+
+interface TestB {
+ B() => ();
+ CallC() => ();
+};
+
+interface TestC {
+ C() => ();
+};
+
+}