Index: mojo/shell/application_manager/application_manager.cc |
diff --git a/mojo/shell/application_manager/application_manager.cc b/mojo/shell/application_manager/application_manager.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d137fb8f2c4f7ddf58f7e59a4ab7623df2e87922 |
--- /dev/null |
+++ b/mojo/shell/application_manager/application_manager.cc |
@@ -0,0 +1,523 @@ |
+// 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/application_manager/application_manager.h" |
+ |
+#include "base/bind.h" |
+#include "base/command_line.h" |
+#include "base/logging.h" |
+#include "base/macros.h" |
+#include "base/stl_util.h" |
+#include "base/strings/string_util.h" |
+#include "base/trace_event/trace_event.h" |
+#include "mojo/public/cpp/bindings/binding.h" |
+#include "mojo/public/cpp/bindings/error_handler.h" |
+#include "mojo/shell/application_manager/fetcher.h" |
+#include "mojo/shell/application_manager/local_fetcher.h" |
+#include "mojo/shell/application_manager/network_fetcher.h" |
+#include "mojo/shell/application_manager/query_util.h" |
+#include "mojo/shell/application_manager/shell_impl.h" |
+#include "mojo/shell/switches.h" |
+#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h" |
+ |
+namespace mojo { |
+namespace shell { |
+ |
+namespace { |
+ |
+// Used by TestAPI. |
+bool has_created_instance = false; |
+ |
+std::vector<std::string> Concatenate(const std::vector<std::string>& v1, |
+ const std::vector<std::string>& v2) { |
+ if (!v1.size()) |
+ return v2; |
+ if (!v2.size()) |
+ return v1; |
+ std::vector<std::string> result(v1); |
+ result.insert(result.end(), v1.begin(), v1.end()); |
+ return result; |
+} |
+ |
+} // namespace |
+ |
+ApplicationManager::Delegate::~Delegate() { |
+} |
+ |
+GURL ApplicationManager::Delegate::ResolveURL(const GURL& url) { |
+ return url; |
+} |
+ |
+GURL ApplicationManager::Delegate::ResolveMappings(const GURL& url) { |
+ return url; |
+} |
+ |
+class ApplicationManager::ContentHandlerConnection : public ErrorHandler { |
+ public: |
+ ContentHandlerConnection(ApplicationManager* manager, |
+ const GURL& content_handler_url) |
+ : manager_(manager), content_handler_url_(content_handler_url) { |
+ ServiceProviderPtr services; |
+ manager->ConnectToApplication(content_handler_url, GURL(), |
+ GetProxy(&services), nullptr, |
+ base::Closure()); |
+ MessagePipe pipe; |
+ content_handler_.Bind(pipe.handle0.Pass()); |
+ services->ConnectToService(ContentHandler::Name_, pipe.handle1.Pass()); |
+ content_handler_.set_error_handler(this); |
+ } |
+ |
+ ContentHandler* content_handler() { return content_handler_.get(); } |
+ |
+ GURL content_handler_url() { return content_handler_url_; } |
+ |
+ private: |
+ // ErrorHandler implementation: |
+ void OnConnectionError() override { manager_->OnContentHandlerError(this); } |
+ |
+ ApplicationManager* manager_; |
+ GURL content_handler_url_; |
+ ContentHandlerPtr content_handler_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ContentHandlerConnection); |
+}; |
+ |
+// 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_->identity_to_shell_impl_.find(Identity(url)) != |
+ manager_->identity_to_shell_impl_.end(); |
+} |
+ |
+ApplicationManager::ApplicationManager(Delegate* delegate) |
+ : delegate_(delegate), weak_ptr_factory_(this) { |
+} |
+ |
+ApplicationManager::~ApplicationManager() { |
+ STLDeleteValues(&url_to_content_handler_); |
+ TerminateShellConnections(); |
+ STLDeleteValues(&url_to_loader_); |
+ STLDeleteValues(&scheme_to_loader_); |
+} |
+ |
+void ApplicationManager::TerminateShellConnections() { |
+ STLDeleteValues(&identity_to_shell_impl_); |
+} |
+ |
+void ApplicationManager::ConnectToApplication( |
+ const GURL& requested_url, |
+ const GURL& requestor_url, |
+ InterfaceRequest<ServiceProvider> services, |
+ ServiceProviderPtr exposed_services, |
+ const base::Closure& on_application_end) { |
+ ConnectToApplicationWithParameters( |
+ requested_url, requestor_url, services.Pass(), exposed_services.Pass(), |
+ on_application_end, std::vector<std::string>()); |
+} |
+ |
+void ApplicationManager::ConnectToApplicationWithParameters( |
+ const GURL& requested_url, |
+ const GURL& requestor_url, |
+ InterfaceRequest<ServiceProvider> services, |
+ ServiceProviderPtr exposed_services, |
+ const base::Closure& on_application_end, |
+ const std::vector<std::string>& pre_redirect_parameters) { |
+ TRACE_EVENT_INSTANT1( |
+ "mojo_shell", "ApplicationManager::ConnectToApplicationWithParameters", |
+ TRACE_EVENT_SCOPE_THREAD, "requested_url", requested_url.spec()); |
+ DCHECK(requested_url.is_valid()); |
+ |
+ // We check both the mapped and resolved urls for existing shell_impls because |
+ // external applications can be registered for the unresolved mojo:foo urls. |
+ |
+ GURL mapped_url = delegate_->ResolveMappings(requested_url); |
+ if (ConnectToRunningApplication(mapped_url, requestor_url, &services, |
+ &exposed_services)) { |
+ return; |
+ } |
+ |
+ GURL resolved_url = delegate_->ResolveURL(mapped_url); |
+ if (ConnectToRunningApplication(resolved_url, requestor_url, &services, |
+ &exposed_services)) { |
+ return; |
+ } |
+ |
+ // The application is not running, let's compute the parameters. |
+ std::vector<std::string> parameters = |
+ Concatenate(pre_redirect_parameters, GetArgsForURL(resolved_url)); |
+ |
+ if (ConnectToApplicationWithLoader(mapped_url, requestor_url, &services, |
+ &exposed_services, on_application_end, |
+ parameters, GetLoaderForURL(mapped_url))) { |
+ return; |
+ } |
+ |
+ if (ConnectToApplicationWithLoader( |
+ resolved_url, requestor_url, &services, &exposed_services, |
+ on_application_end, parameters, GetLoaderForURL(resolved_url))) { |
+ return; |
+ } |
+ |
+ if (ConnectToApplicationWithLoader(resolved_url, requestor_url, &services, |
+ &exposed_services, on_application_end, |
+ parameters, default_loader_.get())) { |
+ return; |
+ } |
+ |
+ auto callback = base::Bind( |
+ &ApplicationManager::HandleFetchCallback, weak_ptr_factory_.GetWeakPtr(), |
+ requestor_url, base::Passed(services.Pass()), |
+ base::Passed(exposed_services.Pass()), on_application_end, |
+ parameters); |
+ |
+ if (resolved_url.SchemeIsFile()) { |
+ new LocalFetcher( |
+ resolved_url, GetBaseURLAndQuery(resolved_url, nullptr), |
+ base::Bind(callback, NativeApplicationCleanup::DONT_DELETE)); |
+ return; |
+ } |
+ |
+ if (!network_service_) |
+ ConnectToService(GURL("mojo:network_service"), &network_service_); |
+ |
+ const NativeApplicationCleanup cleanup = |
+ base::CommandLine::ForCurrentProcess()->HasSwitch( |
+ switches::kDontDeleteOnDownload) |
+ ? NativeApplicationCleanup::DONT_DELETE |
+ : NativeApplicationCleanup::DELETE; |
+ |
+ new NetworkFetcher(disable_cache_, resolved_url, network_service_.get(), |
+ base::Bind(callback, cleanup)); |
+} |
+ |
+bool ApplicationManager::ConnectToRunningApplication( |
+ const GURL& resolved_url, |
+ const GURL& requestor_url, |
+ InterfaceRequest<ServiceProvider>* services, |
+ ServiceProviderPtr* exposed_services) { |
+ GURL application_url = GetBaseURLAndQuery(resolved_url, nullptr); |
+ ShellImpl* shell_impl = GetShellImpl(application_url); |
+ if (!shell_impl) |
+ return false; |
+ |
+ ConnectToClient(shell_impl, resolved_url, requestor_url, services->Pass(), |
+ exposed_services->Pass()); |
+ return true; |
+} |
+ |
+bool ApplicationManager::ConnectToApplicationWithLoader( |
+ const GURL& resolved_url, |
+ const GURL& requestor_url, |
+ InterfaceRequest<ServiceProvider>* services, |
+ ServiceProviderPtr* exposed_services, |
+ const base::Closure& on_application_end, |
+ const std::vector<std::string>& parameters, |
+ ApplicationLoader* loader) { |
+ if (!loader) |
+ return false; |
+ |
+ loader->Load( |
+ resolved_url, |
+ RegisterShell(resolved_url, requestor_url, services->Pass(), |
+ exposed_services->Pass(), on_application_end, parameters)); |
+ return true; |
+} |
+ |
+InterfaceRequest<Application> ApplicationManager::RegisterShell( |
+ const GURL& resolved_url, |
+ const GURL& requestor_url, |
+ InterfaceRequest<ServiceProvider> services, |
+ ServiceProviderPtr exposed_services, |
+ const base::Closure& on_application_end, |
+ const std::vector<std::string>& parameters) { |
+ Identity app_identity(resolved_url); |
+ |
+ ApplicationPtr application; |
+ InterfaceRequest<Application> application_request = GetProxy(&application); |
+ ShellImpl* shell = |
+ new ShellImpl(application.Pass(), this, app_identity, on_application_end); |
+ identity_to_shell_impl_[app_identity] = shell; |
+ shell->InitializeApplication(Array<String>::From(parameters)); |
+ ConnectToClient(shell, resolved_url, requestor_url, services.Pass(), |
+ exposed_services.Pass()); |
+ return application_request.Pass(); |
+} |
+ |
+ShellImpl* ApplicationManager::GetShellImpl(const GURL& url) { |
+ const auto& shell_it = identity_to_shell_impl_.find(Identity(url)); |
+ if (shell_it != identity_to_shell_impl_.end()) |
+ return shell_it->second; |
+ return nullptr; |
+} |
+ |
+void ApplicationManager::ConnectToClient( |
+ ShellImpl* shell_impl, |
+ const GURL& resolved_url, |
+ const GURL& requestor_url, |
+ InterfaceRequest<ServiceProvider> services, |
+ ServiceProviderPtr exposed_services) { |
+ shell_impl->ConnectToClient(resolved_url, requestor_url, services.Pass(), |
+ exposed_services.Pass()); |
+} |
+ |
+void ApplicationManager::HandleFetchCallback( |
+ const GURL& requestor_url, |
+ InterfaceRequest<ServiceProvider> services, |
+ ServiceProviderPtr exposed_services, |
+ const base::Closure& on_application_end, |
+ const std::vector<std::string>& parameters, |
+ NativeApplicationCleanup cleanup, |
+ scoped_ptr<Fetcher> fetcher) { |
+ if (!fetcher) { |
+ // Network error. Drop |application_request| to tell requestor. |
+ return; |
+ } |
+ |
+ GURL redirect_url = fetcher->GetRedirectURL(); |
+ if (!redirect_url.is_empty()) { |
+ // And around we go again... Whee! |
+ ConnectToApplicationWithParameters(redirect_url, requestor_url, |
+ services.Pass(), exposed_services.Pass(), |
+ on_application_end, parameters); |
+ return; |
+ } |
+ |
+ // We already checked if the application was running before we fetched it, but |
+ // it might have started while the fetch was outstanding. We don't want to |
+ // have two copies of the app running, so check again. |
+ // |
+ // Also, it's possible the original URL was redirected to an app that is |
+ // already running. |
+ if (ConnectToRunningApplication(fetcher->GetURL(), requestor_url, &services, |
+ &exposed_services)) { |
+ return; |
+ } |
+ |
+ InterfaceRequest<Application> request( |
+ RegisterShell(fetcher->GetURL(), requestor_url, services.Pass(), |
+ exposed_services.Pass(), on_application_end, parameters)); |
+ |
+ // If the response begins with a #!mojo <content-handler-url>, use it. |
+ GURL content_handler_url; |
+ std::string shebang; |
+ if (fetcher->PeekContentHandler(&shebang, &content_handler_url)) { |
+ LoadWithContentHandler( |
+ content_handler_url, request.Pass(), |
+ fetcher->AsURLResponse(blocking_pool_, |
+ static_cast<int>(shebang.size()))); |
+ return; |
+ } |
+ |
+ MimeTypeToURLMap::iterator iter = mime_type_to_url_.find(fetcher->MimeType()); |
+ if (iter != mime_type_to_url_.end()) { |
+ LoadWithContentHandler(iter->second, request.Pass(), |
+ fetcher->AsURLResponse(blocking_pool_, 0)); |
+ return; |
+ } |
+ |
+ // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo |
+ // application. That could either mean looking for the platform-specific dll |
+ // header, or looking for some specific mojo signature prepended to the |
+ // library. |
+ // TODO(vtl): (Maybe this should be done by the factory/runner?) |
+ |
+ GURL base_resolved_url = GetBaseURLAndQuery(fetcher->GetURL(), nullptr); |
+ NativeRunnerFactory::Options options; |
+ if (url_to_native_options_.find(base_resolved_url) != |
+ url_to_native_options_.end()) { |
+ DVLOG(2) << "Applying stored native options to resolved URL " |
+ << fetcher->GetURL(); |
+ options = url_to_native_options_[base_resolved_url]; |
+ } |
+ |
+ fetcher->AsPath( |
+ blocking_pool_, |
+ base::Bind(&ApplicationManager::RunNativeApplication, |
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(request.Pass()), |
+ options, cleanup, base::Passed(fetcher.Pass()))); |
+} |
+ |
+void ApplicationManager::RunNativeApplication( |
+ InterfaceRequest<Application> application_request, |
+ const NativeRunnerFactory::Options& options, |
+ NativeApplicationCleanup cleanup, |
+ scoped_ptr<Fetcher> fetcher, |
+ const base::FilePath& path, |
+ bool path_exists) { |
+ // We only passed fetcher to keep it alive. Done with it now. |
+ fetcher.reset(); |
+ |
+ DCHECK(application_request.is_pending()); |
+ |
+ if (!path_exists) { |
+ LOG(ERROR) << "Library not started because library path '" << path.value() |
+ << "' does not exist."; |
+ return; |
+ } |
+ |
+ TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path", |
+ path.AsUTF8Unsafe()); |
+ NativeRunner* runner = native_runner_factory_->Create(options).release(); |
+ native_runners_.push_back(runner); |
+ runner->Start(path, cleanup, application_request.Pass(), |
+ base::Bind(&ApplicationManager::CleanupRunner, |
+ weak_ptr_factory_.GetWeakPtr(), runner)); |
+} |
+ |
+void ApplicationManager::RegisterExternalApplication( |
+ const GURL& url, |
+ const std::vector<std::string>& args, |
+ ApplicationPtr application) { |
+ const auto& args_it = url_to_args_.find(url); |
+ if (args_it != url_to_args_.end()) { |
+ LOG(WARNING) << "--args-for provided for external application " << url |
+ << " <ignored>"; |
+ } |
+ Identity identity(url); |
+ ShellImpl* shell_impl = |
+ new ShellImpl(application.Pass(), this, identity, base::Closure()); |
+ identity_to_shell_impl_[identity] = shell_impl; |
+ shell_impl->InitializeApplication(Array<String>::From(args)); |
+} |
+ |
+void ApplicationManager::RegisterContentHandler( |
+ const std::string& mime_type, |
+ const GURL& content_handler_url) { |
+ DCHECK(content_handler_url.is_valid()) |
+ << "Content handler URL is invalid for mime type " << mime_type; |
+ mime_type_to_url_[mime_type] = content_handler_url; |
+} |
+ |
+void ApplicationManager::LoadWithContentHandler( |
+ const GURL& content_handler_url, |
+ InterfaceRequest<Application> application_request, |
+ URLResponsePtr url_response) { |
+ ContentHandlerConnection* connection = nullptr; |
+ 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; |
+ } |
+ |
+ connection->content_handler()->StartApplication(application_request.Pass(), |
+ url_response.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].insert(url_to_args_[url].end(), args.begin(), args.end()); |
+ GURL mapped_url = delegate_->ResolveMappings(url); |
+ if (mapped_url != url) { |
+ url_to_args_[mapped_url].insert(url_to_args_[mapped_url].end(), |
+ args.begin(), args.end()); |
+ } |
+ GURL resolved_url = delegate_->ResolveURL(mapped_url); |
+ if (resolved_url != mapped_url) { |
+ url_to_args_[resolved_url].insert(url_to_args_[resolved_url].end(), |
+ args.begin(), args.end()); |
+ } |
+} |
+ |
+void ApplicationManager::SetNativeOptionsForURL( |
+ const NativeRunnerFactory::Options& options, |
+ const GURL& url) { |
+ DCHECK(!url.has_query()); // Precondition. |
+ // Apply mappings and resolution to get the resolved URL. |
+ GURL resolved_url = delegate_->ResolveURL(delegate_->ResolveMappings(url)); |
+ DCHECK(!resolved_url.has_query()); // Still shouldn't have query. |
+ // TODO(vtl): We should probably also remove/disregard the query string (and |
+ // maybe canonicalize in other ways). |
+ DVLOG(2) << "Storing native options for resolved URL " << resolved_url |
+ << " (original URL " << url << ")"; |
+ url_to_native_options_[resolved_url] = options; |
+} |
+ |
+ApplicationLoader* ApplicationManager::GetLoaderForURL(const GURL& url) { |
+ auto url_it = url_to_loader_.find(GetBaseURLAndQuery(url, nullptr)); |
+ if (url_it != url_to_loader_.end()) |
+ return url_it->second; |
+ auto scheme_it = scheme_to_loader_.find(url.scheme()); |
+ if (scheme_it != scheme_to_loader_.end()) |
+ return scheme_it->second; |
+ return nullptr; |
+} |
+ |
+void ApplicationManager::OnShellImplError(ShellImpl* shell_impl) { |
+ // Called from ~ShellImpl, so we do not need to call Destroy here. |
+ const Identity identity = shell_impl->identity(); |
+ base::Closure on_application_end = shell_impl->on_application_end(); |
+ // Remove the shell. |
+ auto it = identity_to_shell_impl_.find(identity); |
+ DCHECK(it != identity_to_shell_impl_.end()); |
+ delete it->second; |
+ identity_to_shell_impl_.erase(it); |
+ if (!on_application_end.is_null()) |
+ on_application_end.Run(); |
+} |
+ |
+void ApplicationManager::OnContentHandlerError( |
+ ContentHandlerConnection* content_handler) { |
+ // Remove the mapping to the content handler. |
+ auto it = |
+ url_to_content_handler_.find(content_handler->content_handler_url()); |
+ DCHECK(it != url_to_content_handler_.end()); |
+ delete it->second; |
+ url_to_content_handler_.erase(it); |
+} |
+ |
+ScopedMessagePipeHandle ApplicationManager::ConnectToServiceByName( |
+ const GURL& application_url, |
+ const std::string& interface_name) { |
+ ServiceProviderPtr services; |
+ ConnectToApplication(application_url, GURL(), GetProxy(&services), nullptr, |
+ base::Closure()); |
+ MessagePipe pipe; |
+ services->ConnectToService(interface_name, pipe.handle1.Pass()); |
+ return pipe.handle0.Pass(); |
+} |
+ |
+std::vector<std::string> ApplicationManager::GetArgsForURL(const GURL& url) { |
+ const auto& args_it = url_to_args_.find(url); |
+ if (args_it != url_to_args_.end()) |
+ return args_it->second; |
+ return std::vector<std::string>(); |
+} |
+ |
+void ApplicationManager::CleanupRunner(NativeRunner* runner) { |
+ native_runners_.erase( |
+ std::find(native_runners_.begin(), native_runners_.end(), runner)); |
+} |
+ |
+} // namespace shell |
+} // namespace mojo |