| 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 | 
|  |