diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2015-06-18 14:10:49 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> | 2015-06-18 13:53:24 +0000 |
commit | 813fbf95af77a531c57a8c497345ad2c61d475b3 (patch) | |
tree | 821b2c8de8365f21b6c9ba17a236fb3006a1d506 /chromium/mojo/shell | |
parent | af6588f8d723931a298c995fa97259bb7f7deb55 (diff) | |
download | qtwebengine-chromium-813fbf95af77a531c57a8c497345ad2c61d475b3.tar.gz |
BASELINE: Update chromium to 44.0.2403.47
Change-Id: Ie056fedba95cf5e5c76b30c4b2c80fca4764aa2f
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
Diffstat (limited to 'chromium/mojo/shell')
25 files changed, 2946 insertions, 0 deletions
diff --git a/chromium/mojo/shell/BUILD.gn b/chromium/mojo/shell/BUILD.gn new file mode 100644 index 00000000000..5b10e53e60e --- /dev/null +++ b/chromium/mojo/shell/BUILD.gn @@ -0,0 +1,78 @@ +# 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("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +source_set("shell") { + output_name = "mojo_shell" + sources = [ + "application_loader.h", + "application_manager.cc", + "application_manager.h", + "data_pipe_peek.cc", + "data_pipe_peek.h", + "fetcher.cc", + "fetcher.h", + "identity.cc", + "identity.h", + "local_fetcher.cc", + "local_fetcher.h", + "native_runner.h", + "network_fetcher.cc", + "network_fetcher.h", + "query_util.cc", + "query_util.h", + "shell_impl.cc", + "shell_impl.h", + "switches.cc", + "switches.h", + ] + + public_deps = [ + "//base", + "//mojo/application/public/interfaces", + "//mojo/common", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//mojo/services/network/public/interfaces", + "//url", + ] + deps = [ + "//base/third_party/dynamic_annotations", + "//crypto:crypto", + "//url", + "//third_party/mojo/src/mojo/edk/system", + "//mojo/environment:chromium", + "//mojo/util:filename_util", + "//third_party/mojo_services/src/content_handler/public/interfaces", + ] +} + +test("tests") { + output_name = "mojo_shell_unittests" + sources = [ + "application_manager_unittest.cc", + "query_util_unittest.cc", + ] + + deps = [ + ":shell", + ":test_bindings", + "//base", + "//mojo/application", + "//mojo/application/public/cpp", + "//mojo/common", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/edk/test:run_all_unittests", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//testing/gtest", + "//url", + ] +} + +mojom("test_bindings") { + sources = [ + "test.mojom", + ] +} diff --git a/chromium/mojo/shell/DEPS b/chromium/mojo/shell/DEPS new file mode 100644 index 00000000000..7e32ecdb31e --- /dev/null +++ b/chromium/mojo/shell/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+crypto", + "+third_party/mojo_services/src/content_handler/public/interfaces", + "-mojo/runner", +] diff --git a/chromium/mojo/shell/application_loader.h b/chromium/mojo/shell/application_loader.h new file mode 100644 index 00000000000..f464ab1305a --- /dev/null +++ b/chromium/mojo/shell/application_loader.h @@ -0,0 +1,38 @@ +// 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 SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_ +#define SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_ + +#include "base/callback.h" +#include "mojo/application/public/interfaces/shell.mojom.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" +#include "url/gurl.h" + +namespace mojo { + +class Application; + +namespace shell { + +class ApplicationManager; + +// Interface to implement special application loading behavior for a particular +// URL or scheme. +class ApplicationLoader { + public: + virtual ~ApplicationLoader() {} + + virtual void Load(const GURL& url, + InterfaceRequest<Application> application_request) = 0; + + protected: + ApplicationLoader() {} +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_ diff --git a/chromium/mojo/shell/application_manager.cc b/chromium/mojo/shell/application_manager.cc new file mode 100644 index 00000000000..2b08442a2d9 --- /dev/null +++ b/chromium/mojo/shell/application_manager.cc @@ -0,0 +1,531 @@ +// 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.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/fetcher.h" +#include "mojo/shell/local_fetcher.h" +#include "mojo/shell/network_fetcher.h" +#include "mojo/shell/query_util.h" +#include "mojo/shell/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; + +} // namespace + +class ApplicationManager::ContentHandlerConnection : public ErrorHandler { + public: + ContentHandlerConnection(ApplicationManager* manager, + const GURL& content_handler_url, + const GURL& requestor_url, + const std::string& qualifier) + : manager_(manager), + content_handler_url_(content_handler_url), + content_handler_qualifier_(qualifier) { + ServiceProviderPtr services; + manager->ConnectToApplicationWithParameters( + content_handler_url, qualifier, requestor_url, GetProxy(&services), + nullptr, base::Closure(), std::vector<std::string>()); + MessagePipe pipe; + content_handler_.Bind( + InterfacePtrInfo<ContentHandler>(pipe.handle0.Pass(), 0u)); + 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_; } + std::string content_handler_qualifier() { return content_handler_qualifier_; } + + private: + // ErrorHandler implementation: + void OnConnectionError() override { manager_->OnContentHandlerError(this); } + + ApplicationManager* manager_; + GURL content_handler_url_; + std::string content_handler_qualifier_; + 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, std::string(), requestor_url, services.Pass(), + exposed_services.Pass(), on_application_end, std::vector<std::string>()); +} + +void ApplicationManager::ConnectToApplicationWithParameters( + const GURL& requested_url, + const std::string& qualifier, + 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, qualifier, requestor_url, + &services, &exposed_services)) { + return; + } + + GURL resolved_url = delegate_->ResolveMojoURL(mapped_url); + if (ConnectToRunningApplication(resolved_url, qualifier, requestor_url, + &services, &exposed_services)) { + return; + } + + // The application is not running, let's compute the parameters. + if (ConnectToApplicationWithLoader( + requested_url, qualifier, mapped_url, requestor_url, &services, + &exposed_services, on_application_end, pre_redirect_parameters, + GetLoaderForURL(mapped_url))) { + return; + } + + if (ConnectToApplicationWithLoader( + requested_url, qualifier, resolved_url, requestor_url, &services, + &exposed_services, on_application_end, pre_redirect_parameters, + GetLoaderForURL(resolved_url))) { + return; + } + + if (ConnectToApplicationWithLoader( + requested_url, qualifier, resolved_url, requestor_url, &services, + &exposed_services, on_application_end, pre_redirect_parameters, + default_loader_.get())) { + return; + } + + auto callback = base::Bind( + &ApplicationManager::HandleFetchCallback, weak_ptr_factory_.GetWeakPtr(), + requested_url, qualifier, requestor_url, base::Passed(services.Pass()), + base::Passed(exposed_services.Pass()), on_application_end, + pre_redirect_parameters); + + if (delegate_->CreateFetcher( + resolved_url, + base::Bind(callback, NativeApplicationCleanup::DONT_DELETE))) { + return; + } + + 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 std::string& qualifier, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider>* services, + ServiceProviderPtr* exposed_services) { + GURL application_url = GetBaseURLAndQuery(resolved_url, nullptr); + ShellImpl* shell_impl = GetShellImpl(application_url, qualifier); + if (!shell_impl) + return false; + + ConnectToClient(shell_impl, resolved_url, requestor_url, services->Pass(), + exposed_services->Pass()); + return true; +} + +bool ApplicationManager::ConnectToApplicationWithLoader( + const GURL& requested_url, + const std::string& qualifier, + 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; + + const GURL app_url = + requested_url.scheme() == "mojo" ? requested_url : resolved_url; + + loader->Load( + resolved_url, + RegisterShell(app_url, qualifier, requestor_url, services->Pass(), + exposed_services->Pass(), on_application_end, parameters)); + return true; +} + +InterfaceRequest<Application> ApplicationManager::RegisterShell( + const GURL& app_url, + const std::string& qualifier, + 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(app_url, qualifier); + + 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, app_url, requestor_url, services.Pass(), + exposed_services.Pass()); + return application_request.Pass(); +} + +ShellImpl* ApplicationManager::GetShellImpl(const GURL& url, + const std::string& qualifier) { + const auto& shell_it = identity_to_shell_impl_.find(Identity(url, qualifier)); + 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& requested_url, + const std::string& qualifier, + 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! + // TODO(sky): this loses |requested_url|. + ConnectToApplicationWithParameters(redirect_url, qualifier, 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(requested_url, qualifier, requestor_url, + &services, &exposed_services)) { + return; + } + + const GURL app_url = + requested_url.scheme() == "mojo" ? requested_url : fetcher->GetURL(); + + InterfaceRequest<Application> request( + RegisterShell(app_url, qualifier, 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, requestor_url, qualifier, 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, requestor_url, qualifier, + request.Pass(), + fetcher->AsURLResponse(blocking_pool_, 0)); + return; + } + + auto alias_iter = application_package_alias_.find(app_url); + if (alias_iter != application_package_alias_.end()) { + // We replace the qualifier with the one our package alias requested. + URLResponsePtr response(URLResponse::New()); + response->url = String::From(app_url.spec()); + + std::string qualifier; + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableMultiprocess)) { + // Why can't we use this in single process mode? Because of + // base::AtExitManager. If you link in ApplicationRunnerChromium into + // your code, and then we make initialize multiple copies of the + // application, we end up with multiple AtExitManagers and will check on + // the second one being created. + // + // Why doesn't that happen when running different apps? Because + // your_thing.mojo!base::AtExitManager and + // my_thing.mojo!base::AtExitManager are different symbols. + qualifier = alias_iter->second.second; + } + + LoadWithContentHandler(alias_iter->second.first, requestor_url, qualifier, + request.Pass(), response.Pass()); + 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::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::RegisterApplicationPackageAlias( + const GURL& alias, + const GURL& content_handler_package, + const std::string& qualifier) { + application_package_alias_[alias] = + std::make_pair(content_handler_package, qualifier); +} + +void ApplicationManager::LoadWithContentHandler( + const GURL& content_handler_url, + const GURL& requestor_url, + const std::string& qualifier, + InterfaceRequest<Application> application_request, + URLResponsePtr url_response) { + ContentHandlerConnection* connection = nullptr; + std::pair<GURL, std::string> key(content_handler_url, qualifier); + URLToContentHandlerMap::iterator iter = url_to_content_handler_.find(key); + if (iter != url_to_content_handler_.end()) { + connection = iter->second; + } else { + connection = new ContentHandlerConnection(this, content_handler_url, + requestor_url, qualifier); + url_to_content_handler_[key] = 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::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_->ResolveMojoURL(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( + std::make_pair(content_handler->content_handler_url(), + content_handler->content_handler_qualifier())); + 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(); +} + +void ApplicationManager::CleanupRunner(NativeRunner* runner) { + native_runners_.erase( + std::find(native_runners_.begin(), native_runners_.end(), runner)); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/application_manager.h b/chromium/mojo/shell/application_manager.h new file mode 100644 index 00000000000..a5ebda31844 --- /dev/null +++ b/chromium/mojo/shell/application_manager.h @@ -0,0 +1,261 @@ +// 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 SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_ +#define SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_ + +#include <map> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "mojo/application/public/interfaces/application.mojom.h" +#include "mojo/application/public/interfaces/service_provider.mojom.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/services/network/public/interfaces/network_service.mojom.h" +#include "mojo/shell/application_loader.h" +#include "mojo/shell/fetcher.h" +#include "mojo/shell/identity.h" +#include "mojo/shell/native_runner.h" +#include "url/gurl.h" + +namespace base { +class FilePath; +class SequencedWorkerPool; +} + +namespace mojo { +namespace shell { + +class ShellImpl; + +class ApplicationManager { + public: + class Delegate { + public: + // Gives the delegate a chance to apply any mappings for the specified url. + // This should not resolve 'mojo' urls, that is done by ResolveMojoURL(). + virtual GURL ResolveMappings(const GURL& url) = 0; + + // Used to map a url with the scheme 'mojo' to the appropriate url. Return + // |url| if the scheme is not 'mojo'. + virtual GURL ResolveMojoURL(const GURL& url) = 0; + + // Asks the delegate to create a Fetcher for the specified url. Return + // true on success, false if the default fetcher should be created. + virtual bool CreateFetcher( + const GURL& url, + const Fetcher::FetchCallback& loader_callback) = 0; + + protected: + virtual ~Delegate() {} + }; + + // API for testing. + class 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); + }; + + explicit ApplicationManager(Delegate* delegate); + ~ApplicationManager(); + + // Loads a service if necessary and establishes a new client connection. + void ConnectToApplication(const GURL& application_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end); + + template <typename Interface> + inline void ConnectToService(const GURL& application_url, + InterfacePtr<Interface>* ptr) { + ScopedMessagePipeHandle service_handle = + ConnectToServiceByName(application_url, Interface::Name_); + ptr->Bind(InterfacePtrInfo<Interface>(service_handle.Pass(), 0u)); + } + + ScopedMessagePipeHandle ConnectToServiceByName( + const GURL& application_url, + const std::string& interface_name); + + void RegisterContentHandler(const std::string& mime_type, + const GURL& content_handler_url); + + // Registers a package alias. When attempting to load |alias|, it will + // instead redirect to |content_handler_package|, which is a content handler + // which will be passed the |alias| as the URLResponse::url. Different values + // of |alias| with the same |qualifier| that are in the same + // |content_handler_package| will run in the same process in multi-process + // mode. + void RegisterApplicationPackageAlias(const GURL& alias, + const GURL& content_handler_package, + const std::string& qualifier); + + // 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(); + } + void set_native_runner_factory( + scoped_ptr<NativeRunnerFactory> runner_factory) { + native_runner_factory_ = runner_factory.Pass(); + } + void set_blocking_pool(base::SequencedWorkerPool* blocking_pool) { + blocking_pool_ = blocking_pool; + } + void set_disable_cache(bool disable_cache) { disable_cache_ = disable_cache; } + // 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 options will be used in running any native application at |url| + // (which shouldn't contain a query string). (|url| will be mapped and + // resolved, and any application whose base resolved URL matches it will have + // |options| applied.) + // TODO(vtl): This may not do what's desired if the resolved URL results in an + // HTTP redirect. Really, we want options to be identified with a particular + // implementation, maybe via a signed manifest or something like that. + void SetNativeOptionsForURL(const NativeRunnerFactory::Options& options, + const GURL& url); + + // 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(); + + // Removes a ShellImpl when it encounters an error. + void OnShellImplError(ShellImpl* shell_impl); + + private: + class ContentHandlerConnection; + + using ApplicationPackagedAlias = std::map<GURL, std::pair<GURL, std::string>>; + using IdentityToShellImplMap = std::map<Identity, ShellImpl*>; + using MimeTypeToURLMap = std::map<std::string, GURL>; + using SchemeToLoaderMap = std::map<std::string, ApplicationLoader*>; + using URLToContentHandlerMap = + std::map<std::pair<GURL, std::string>, ContentHandlerConnection*>; + using URLToLoaderMap = std::map<GURL, ApplicationLoader*>; + using URLToNativeOptionsMap = std::map<GURL, NativeRunnerFactory::Options>; + + void ConnectToApplicationWithParameters( + const GURL& application_url, + const std::string& qualifier, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& pre_redirect_parameters); + + bool ConnectToRunningApplication(const GURL& resolved_url, + const std::string& qualifier, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider>* services, + ServiceProviderPtr* exposed_services); + + bool ConnectToApplicationWithLoader( + const GURL& requested_url, + const std::string& qualifier, + 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); + + InterfaceRequest<Application> RegisterShell( + const GURL& app_url, + const std::string& qualifier, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& parameters); + + ShellImpl* GetShellImpl(const GURL& url, const std::string& qualifier); + + void ConnectToClient(ShellImpl* shell_impl, + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services); + + // Called once |fetcher| has found app. |requested_url| is the url of the + // requested application before any mappings/resolution have been applied. + void HandleFetchCallback(const GURL& requested_url, + const std::string& qualifier, + 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); + + void RunNativeApplication(InterfaceRequest<Application> application_request, + const NativeRunnerFactory::Options& options, + NativeApplicationCleanup cleanup, + scoped_ptr<Fetcher> fetcher, + const base::FilePath& file_path, + bool path_exists); + + void LoadWithContentHandler(const GURL& content_handler_url, + const GURL& requestor_url, + const std::string& qualifier, + InterfaceRequest<Application> application_request, + URLResponsePtr url_response); + + // Returns the appropriate loader for |url|, or null if there is no loader + // configured for the URL. + ApplicationLoader* GetLoaderForURL(const GURL& url); + + // Removes a ContentHandler when it encounters an error. + void OnContentHandlerError(ContentHandlerConnection* content_handler); + + void CleanupRunner(NativeRunner* runner); + + Delegate* const delegate_; + // Loader management. + // Loaders are chosen in the order they are listed here. + URLToLoaderMap url_to_loader_; + SchemeToLoaderMap scheme_to_loader_; + scoped_ptr<ApplicationLoader> default_loader_; + scoped_ptr<NativeRunnerFactory> native_runner_factory_; + + ApplicationPackagedAlias application_package_alias_; + IdentityToShellImplMap identity_to_shell_impl_; + URLToContentHandlerMap url_to_content_handler_; + // Note: The keys are URLs after mapping and resolving. + URLToNativeOptionsMap url_to_native_options_; + + base::SequencedWorkerPool* blocking_pool_; + NetworkServicePtr network_service_; + MimeTypeToURLMap mime_type_to_url_; + ScopedVector<NativeRunner> native_runners_; + bool disable_cache_; + base::WeakPtrFactory<ApplicationManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ApplicationManager); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_ diff --git a/chromium/mojo/shell/application_manager_unittest.cc b/chromium/mojo/shell/application_manager_unittest.cc new file mode 100644 index 00000000000..e08420dd979 --- /dev/null +++ b/chromium/mojo/shell/application_manager_unittest.cc @@ -0,0 +1,824 @@ +// 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/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "mojo/application/public/cpp/application_connection.h" +#include "mojo/application/public/cpp/application_delegate.h" +#include "mojo/application/public/cpp/application_impl.h" +#include "mojo/application/public/cpp/interface_factory.h" +#include "mojo/application/public/interfaces/service_provider.mojom.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/shell/application_loader.h" +#include "mojo/shell/application_manager.h" +#include "mojo/shell/fetcher.h" +#include "mojo/shell/test.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +const char kTestURLString[] = "test:testService"; +const char kTestAURLString[] = "test:TestA"; +const char kTestBURLString[] = "test:TestB"; + +const char kTestMimeType[] = "test/mime-type"; + +class TestMimeTypeFetcher : public Fetcher { + public: + explicit TestMimeTypeFetcher(const FetchCallback& fetch_callback) + : Fetcher(fetch_callback), url_("xxx") { + loader_callback_.Run(make_scoped_ptr(this)); + } + ~TestMimeTypeFetcher() override {} + + // Fetcher: + const GURL& GetURL() const override { return url_; } + GURL GetRedirectURL() const override { return GURL("yyy"); } + URLResponsePtr AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) override { + return URLResponse::New().Pass(); + } + void AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) override {} + std::string MimeType() override { return kTestMimeType; } + bool HasMojoMagic() override { return false; } + bool PeekFirstLine(std::string* line) override { return false; } + + private: + const GURL url_; + + DISALLOW_COPY_AND_ASSIGN(TestMimeTypeFetcher); +}; + +struct TestContext { + TestContext() : num_impls(0), num_loader_deletes(0) {} + std::string last_test_string; + int num_impls; + int num_loader_deletes; +}; + +void QuitClosure(bool* value) { + *value = true; + base::MessageLoop::current()->QuitWhenIdle(); +} + +class QuitMessageLoopErrorHandler : public ErrorHandler { + public: + QuitMessageLoopErrorHandler() {} + ~QuitMessageLoopErrorHandler() override {} + + // |ErrorHandler| implementation: + void OnConnectionError() override { + base::MessageLoop::current()->QuitWhenIdle(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler); +}; + +class TestServiceImpl : public TestService { + public: + TestServiceImpl(TestContext* context, InterfaceRequest<TestService> request) + : context_(context), binding_(this, request.Pass()) { + ++context_->num_impls; + } + + ~TestServiceImpl() override { + --context_->num_impls; + if (!base::MessageLoop::current()->is_running()) + return; + base::MessageLoop::current()->Quit(); + } + + // TestService implementation: + void Test(const String& test_string, + const Callback<void()>& callback) override { + context_->last_test_string = test_string; + callback.Run(); + } + + private: + TestContext* context_; + StrongBinding<TestService> binding_; +}; + +class TestClient { + public: + explicit TestClient(TestServicePtr service) + : service_(service.Pass()), quit_after_ack_(false) {} + + void AckTest() { + if (quit_after_ack_) + base::MessageLoop::current()->Quit(); + } + + void Test(const std::string& test_string) { + quit_after_ack_ = true; + service_->Test(test_string, + base::Bind(&TestClient::AckTest, base::Unretained(this))); + } + + private: + TestServicePtr service_; + bool quit_after_ack_; + DISALLOW_COPY_AND_ASSIGN(TestClient); +}; + +class TestApplicationLoader : public ApplicationLoader, + public ApplicationDelegate, + public InterfaceFactory<TestService> { + public: + TestApplicationLoader() : context_(nullptr), num_loads_(0) {} + + ~TestApplicationLoader() override { + if (context_) + ++context_->num_loader_deletes; + test_app_.reset(); + } + + void set_context(TestContext* context) { context_ = context; } + int num_loads() const { return num_loads_; } + const std::vector<std::string>& GetArgs() const { return test_app_->args(); } + const GURL& last_requestor_url() const { return last_requestor_url_; } + + private: + // ApplicationLoader implementation. + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override { + ++num_loads_; + test_app_.reset(new ApplicationImpl(this, application_request.Pass())); + } + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(this); + last_requestor_url_ = GURL(connection->GetRemoteApplicationURL()); + return true; + } + + // InterfaceFactory implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<TestService> request) override { + new TestServiceImpl(context_, request.Pass()); + } + + scoped_ptr<ApplicationImpl> test_app_; + TestContext* context_; + int num_loads_; + GURL last_requestor_url_; + + DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader); +}; + +class ClosingApplicationLoader : public ApplicationLoader { + private: + // ApplicationLoader implementation. + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override {} +}; + +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 TestA { + public: + TestAImpl(ApplicationImpl* app_impl, + TesterContext* test_context, + InterfaceRequest<TestA> request) + : test_context_(test_context), binding_(this, request.Pass()) { + app_impl->ConnectToApplication(kTestBURLString)->ConnectToService(&b_); + } + + ~TestAImpl() override { + test_context_->IncrementNumADeletes(); + if (base::MessageLoop::current()->is_running()) + Quit(); + } + + private: + void CallB() override { + b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this))); + } + + 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_; + StrongBinding<TestA> binding_; +}; + +class TestBImpl : public TestB { + public: + TestBImpl(ApplicationConnection* connection, + TesterContext* test_context, + InterfaceRequest<TestB> request) + : test_context_(test_context), binding_(this, request.Pass()) { + connection->ConnectToService(&c_); + } + + ~TestBImpl() override { + test_context_->IncrementNumBDeletes(); + if (base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + test_context_->QuitSoon(); + } + + private: + void B(const Callback<void()>& callback) override { + test_context_->IncrementNumBCalls(); + callback.Run(); + } + + void CallC(const Callback<void()>& callback) override { + test_context_->IncrementNumBCalls(); + c_->C(callback); + } + + TesterContext* test_context_; + TestCPtr c_; + StrongBinding<TestB> binding_; +}; + +class TestCImpl : public TestC { + public: + TestCImpl(ApplicationConnection* connection, + TesterContext* test_context, + InterfaceRequest<TestC> request) + : test_context_(test_context), binding_(this, request.Pass()) {} + + ~TestCImpl() override { test_context_->IncrementNumCDeletes(); } + + private: + void C(const Callback<void()>& callback) override { + test_context_->IncrementNumCCalls(); + callback.Run(); + } + + TesterContext* test_context_; + StrongBinding<TestC> binding_; +}; + +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) {} + ~Tester() override {} + + private: + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override { + app_.reset(new ApplicationImpl(this, application_request.Pass())); + } + + 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; + } + + bool ConfigureOutgoingConnection(ApplicationConnection* connection) override { + // If we're connecting to B, then add C. + if (connection->GetRemoteApplicationURL() == kTestBURLString) + connection->AddService<TestC>(this); + return true; + } + + void Create(ApplicationConnection* connection, + InterfaceRequest<TestA> request) override { + a_bindings_.push_back(new TestAImpl(app_.get(), context_, request.Pass())); + } + + void Create(ApplicationConnection* connection, + InterfaceRequest<TestB> request) override { + new TestBImpl(connection, context_, request.Pass()); + } + + void Create(ApplicationConnection* connection, + InterfaceRequest<TestC> request) override { + new TestCImpl(connection, context_, request.Pass()); + } + + TesterContext* context_; + scoped_ptr<ApplicationImpl> app_; + std::string requestor_url_; + ScopedVector<TestAImpl> a_bindings_; +}; + +class TestDelegate : public ApplicationManager::Delegate { + public: + TestDelegate() : create_test_fetcher_(false) {} + ~TestDelegate() override {} + + void AddMapping(const GURL& from, const GURL& to) { mappings_[from] = to; } + + void set_create_test_fetcher(bool create_test_fetcher) { + create_test_fetcher_ = create_test_fetcher; + } + + // ApplicationManager::Delegate + GURL ResolveMappings(const GURL& url) override { + auto it = mappings_.find(url); + if (it != mappings_.end()) + return it->second; + return url; + } + GURL ResolveMojoURL(const GURL& url) override { + GURL mapped_url = ResolveMappings(url); + // The shell automatically map mojo URLs. + if (mapped_url.scheme() == "mojo") { + url::Replacements<char> replacements; + replacements.SetScheme("file", url::Component(0, 4)); + mapped_url = mapped_url.ReplaceComponents(replacements); + } + return mapped_url; + } + bool CreateFetcher(const GURL& url, + const Fetcher::FetchCallback& loader_callback) override { + if (!create_test_fetcher_) + return false; + new TestMimeTypeFetcher(loader_callback); + return true; + } + + private: + std::map<GURL, GURL> mappings_; + bool create_test_fetcher_; + + DISALLOW_COPY_AND_ASSIGN(TestDelegate); +}; + +class ApplicationManagerTest : public testing::Test { + public: + ApplicationManagerTest() : tester_context_(&loop_) {} + + ~ApplicationManagerTest() override {} + + void SetUp() override { + application_manager_.reset(new ApplicationManager(&test_delegate_)); + test_loader_ = new TestApplicationLoader; + test_loader_->set_context(&context_); + application_manager_->set_default_loader( + scoped_ptr<ApplicationLoader>(test_loader_)); + + TestServicePtr service_proxy; + application_manager_->ConnectToService(GURL(kTestURLString), + &service_proxy); + test_client_.reset(new TestClient(service_proxy.Pass())); + } + + void TearDown() override { + test_client_.reset(); + application_manager_.reset(); + } + + void AddLoaderForURL(const GURL& url, const std::string& requestor_url) { + application_manager_->SetLoaderForURL( + make_scoped_ptr(new Tester(&tester_context_, requestor_url)), url); + } + + bool HasFactoryForURL(const GURL& url) { + ApplicationManager::TestAPI manager_test_api(application_manager_.get()); + return manager_test_api.HasFactoryForURL(url); + } + + protected: + base::ShadowingAtExitManager at_exit_; + TestDelegate test_delegate_; + TestApplicationLoader* test_loader_; + TesterContext tester_context_; + TestContext context_; + base::MessageLoop loop_; + scoped_ptr<TestClient> 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(&test_delegate_); + GURL test_url("test:test"); + 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); + TestClient 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 url mappings are respected. +TEST_F(ApplicationManagerTest, URLMapping) { + ApplicationManager am(&test_delegate_); + GURL test_url("test:test"); + GURL test_url2("test:test2"); + test_delegate_.AddMapping(test_url, test_url2); + TestApplicationLoader* loader = new TestApplicationLoader; + loader->set_context(&context_); + am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url2); + { + // Connext to the mapped url + TestServicePtr test_service; + am.ConnectToService(test_url, &test_service); + TestClient test_client(test_service.Pass()); + test_client.Test("test"); + loop_.Run(); + } + { + // Connext to the target url + TestServicePtr test_service; + am.ConnectToService(test_url2, &test_service); + TestClient test_client(test_service.Pass()); + test_client.Test("test"); + loop_.Run(); + } +} + +TEST_F(ApplicationManagerTest, ClientError) { + test_client_->Test("test"); + EXPECT_TRUE(HasFactoryForURL(GURL(kTestURLString))); + loop_.Run(); + EXPECT_EQ(1, context_.num_impls); + test_client_.reset(); + loop_.Run(); + EXPECT_EQ(0, context_.num_impls); + EXPECT_TRUE(HasFactoryForURL(GURL(kTestURLString))); +} + +TEST_F(ApplicationManagerTest, Deletes) { + { + ApplicationManager am(&test_delegate_); + 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, MappedURLsShouldNotCauseDuplicateLoad) { + test_delegate_.AddMapping(GURL("foo:foo2"), GURL("foo:foo")); + // 1 because ApplicationManagerTest connects once at startup. + EXPECT_EQ(1, test_loader_->num_loads()); + + TestServicePtr test_service; + application_manager_->ConnectToService(GURL("foo:foo"), &test_service); + EXPECT_EQ(2, test_loader_->num_loads()); + + TestServicePtr test_service2; + application_manager_->ConnectToService(GURL("foo:foo2"), &test_service2); + EXPECT_EQ(2, test_loader_->num_loads()); + + TestServicePtr test_service3; + application_manager_->ConnectToService(GURL("bar:bar"), &test_service2); + EXPECT_EQ(3, test_loader_->num_loads()); +} + +TEST_F(ApplicationManagerTest, MappedURLsShouldWorkWithLoaders) { + TestApplicationLoader* custom_loader = new TestApplicationLoader; + TestContext context; + custom_loader->set_context(&context); + application_manager_->SetLoaderForURL(make_scoped_ptr(custom_loader), + GURL("mojo:foo")); + test_delegate_.AddMapping(GURL("mojo:foo2"), GURL("mojo:foo")); + + TestServicePtr test_service; + application_manager_->ConnectToService(GURL("mojo:foo2"), &test_service); + EXPECT_EQ(1, custom_loader->num_loads()); + custom_loader->set_context(nullptr); + + EXPECT_TRUE(HasFactoryForURL(GURL("mojo:foo2"))); + EXPECT_FALSE(HasFactoryForURL(GURL("mojo:foo"))); +} + +TEST_F(ApplicationManagerTest, TestQueryWithLoaders) { + TestApplicationLoader* url_loader = new TestApplicationLoader; + TestApplicationLoader* scheme_loader = new TestApplicationLoader; + 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?foo=bar"), + &test_service); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(0, scheme_loader->num_loads()); + + // test::test2 should go to scheme loader. + application_manager_->ConnectToService(GURL("test:test2?foo=bar"), + &test_service); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(1, scheme_loader->num_loads()); +} + +TEST_F(ApplicationManagerTest, TestEndApplicationClosure) { + ClosingApplicationLoader* loader = new ClosingApplicationLoader(); + application_manager_->SetLoaderForScheme( + scoped_ptr<ApplicationLoader>(loader), "test"); + + bool called = false; + application_manager_->ConnectToApplication( + GURL("test:test"), GURL(), nullptr, nullptr, + base::Bind(&QuitClosure, base::Unretained(&called))); + loop_.Run(); + EXPECT_TRUE(called); +} + +TEST(ApplicationManagerTest2, ContentHandlerConnectionGetsRequestorURL) { + const GURL content_handler_url("http://test.content.handler"); + const GURL requestor_url("http://requestor.url"); + TestContext test_context; + base::MessageLoop loop; + TestDelegate test_delegate; + test_delegate.set_create_test_fetcher(true); + ApplicationManager application_manager(&test_delegate); + application_manager.set_default_loader(nullptr); + application_manager.RegisterContentHandler(kTestMimeType, + content_handler_url); + + TestApplicationLoader* loader = new TestApplicationLoader; + loader->set_context(&test_context); + application_manager.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), + content_handler_url); + + bool called = false; + application_manager.ConnectToApplication( + GURL("test:test"), requestor_url, nullptr, nullptr, + base::Bind(&QuitClosure, base::Unretained(&called))); + loop.Run(); + EXPECT_TRUE(called); + + ASSERT_EQ(1, loader->num_loads()); + EXPECT_EQ(requestor_url, loader->last_requestor_url()); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/data_pipe_peek.cc b/chromium/mojo/shell/data_pipe_peek.cc new file mode 100644 index 00000000000..4c74c5537bb --- /dev/null +++ b/chromium/mojo/shell/data_pipe_peek.cc @@ -0,0 +1,160 @@ +// 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/data_pipe_peek.h" + +#include <stdint.h> + +#include "base/bind.h" +#include "base/macros.h" + +namespace mojo { +namespace shell { + +namespace { + +// Sleep for as long as max_sleep_micros if the deadline hasn't been reached +// and the number of bytes read is still increasing. Returns true if sleep +// was actually called. +// +// This class is a substitute for being able to wait until N bytes are available +// from a data pipe. The MaybeSleep method is called when num_bytes_read are +// available but more are needed by the Peek operation. If a second +// Peek operation finds the same number of bytes after sleeping we assume +// that there's no point in trying again. +// TODO(hansmuller): this heuristic is weak. crbug.com/429377 +class PeekSleeper { + public: + explicit PeekSleeper(MojoTimeTicks deadline) + : deadline_(deadline), + last_number_bytes_read_(0) {} + + bool MaybeSleep(uint32_t num_bytes_read) { + if (num_bytes_read > 0 && last_number_bytes_read_ >= num_bytes_read) + return false; + last_number_bytes_read_ = num_bytes_read; + + MojoTimeTicks now(GetTimeTicksNow()); + if (now > deadline_) + return false; + + MojoTimeTicks sleep_time = + (deadline_ == 0) ? kMaxSleepMicros + : std::min<int64>(deadline_ - now, kMaxSleepMicros); + base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds(sleep_time)); + return true; + } + + private: + static const MojoTimeTicks kMaxSleepMicros = 1000 * 10; // 10 ms + + const MojoTimeTicks deadline_; // 0 => MOJO_DEADLINE_INDEFINITE + uint32_t last_number_bytes_read_; + + DISALLOW_COPY_AND_ASSIGN(PeekSleeper); +}; + +const MojoTimeTicks PeekSleeper::kMaxSleepMicros; + +enum PeekStatus { kSuccess, kFail, kKeepReading }; +typedef const base::Callback<PeekStatus(const void*, uint32_t, std::string*)>& + PeekFunc; + +// When data is available on source, call peek_func and then either return true +// and value, continue waiting for enough data to satisfy peek_func, or fail +// and return false. Fail if the timeout is exceeded. +// This function is not guaranteed to work correctly if applied to a data pipe +// that's already been read from. +bool BlockingPeekHelper(DataPipeConsumerHandle source, + std::string* value, + MojoDeadline timeout, + PeekFunc peek_func) { + DCHECK(value); + value->clear(); + + MojoTimeTicks deadline = + (timeout == MOJO_DEADLINE_INDEFINITE) + ? 0 + : 1 + GetTimeTicksNow() + static_cast<MojoTimeTicks>(timeout); + PeekSleeper sleeper(deadline); + + MojoResult result; + do { + const void* buffer; + uint32_t num_bytes; + result = + BeginReadDataRaw(source, &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + + if (result == MOJO_RESULT_OK) { + PeekStatus status = peek_func.Run(buffer, num_bytes, value); + CHECK_EQ(EndReadDataRaw(source, 0), MOJO_RESULT_OK); + switch (status) { + case PeekStatus::kSuccess: + return true; + case PeekStatus::kFail: + return false; + case PeekStatus::kKeepReading: + break; + } + if (!sleeper.MaybeSleep(num_bytes)) + return false; + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + MojoTimeTicks now(GetTimeTicksNow()); + if (timeout == MOJO_DEADLINE_INDEFINITE || now < deadline) + result = + Wait(source, MOJO_HANDLE_SIGNAL_READABLE, deadline - now, nullptr); + } + } while (result == MOJO_RESULT_OK); + + return false; +} + +PeekStatus PeekLine(size_t max_line_length, + const void* buffer, + uint32_t buffer_num_bytes, + std::string* line) { + const char* p = static_cast<const char*>(buffer); + size_t max_p_index = std::min<size_t>(buffer_num_bytes, max_line_length); + for (size_t i = 0; i < max_p_index; i++) { + if (p[i] == '\n') { + *line = std::string(p, i + 1); // Include the trailing newline. + return PeekStatus::kSuccess; + } + } + return (buffer_num_bytes >= max_line_length) ? PeekStatus::kFail + : PeekStatus::kKeepReading; +} + +PeekStatus PeekNBytes(size_t bytes_length, + const void* buffer, + uint32_t buffer_num_bytes, + std::string* bytes) { + if (buffer_num_bytes >= bytes_length) { + const char* p = static_cast<const char*>(buffer); + *bytes = std::string(p, bytes_length); + return PeekStatus::kSuccess; + } + return PeekStatus::kKeepReading; +} + +} // namespace + +bool BlockingPeekNBytes(DataPipeConsumerHandle source, + std::string* bytes, + size_t bytes_length, + MojoDeadline timeout) { + PeekFunc peek_nbytes = base::Bind(PeekNBytes, bytes_length); + return BlockingPeekHelper(source, bytes, timeout, peek_nbytes); +} + +bool BlockingPeekLine(DataPipeConsumerHandle source, + std::string* line, + size_t max_line_length, + MojoDeadline timeout) { + PeekFunc peek_line = base::Bind(PeekLine, max_line_length); + return BlockingPeekHelper(source, line, timeout, peek_line); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/data_pipe_peek.h b/chromium/mojo/shell/data_pipe_peek.h new file mode 100644 index 00000000000..0bcf1dd9f29 --- /dev/null +++ b/chromium/mojo/shell/data_pipe_peek.h @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_DATA_PIPE_PEEK_H_ +#define MOJO_SHELL_DATA_PIPE_PEEK_H_ + +#include <string> + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace shell { + +// The Peek functions are only intended to be used by the +// DyanmicApplicationLoader class for discovering the type of a +// URL response. They are a stopgap to be replaced by real support +// in the DataPipe classes. + +// Return true and the first newline terminated line from source. Return false +// if more than max_line_length bytes are scanned without seeing a newline, or +// if the timeout is exceeded. +bool BlockingPeekLine(DataPipeConsumerHandle source, + std::string* line, + size_t max_line_length, + MojoDeadline timeout); + +// Return true and the first bytes_length bytes from source. Return false +// if the timeout is exceeded. +bool BlockingPeekNBytes(DataPipeConsumerHandle source, + std::string* bytes, + size_t bytes_length, + MojoDeadline timeout); + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_DATA_PIPE_PEEK_H_ diff --git a/chromium/mojo/shell/fetcher.cc b/chromium/mojo/shell/fetcher.cc new file mode 100644 index 00000000000..d9a4e60574b --- /dev/null +++ b/chromium/mojo/shell/fetcher.cc @@ -0,0 +1,38 @@ +// Copyright 2015 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/fetcher.h" + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +const char Fetcher::kMojoMagic[] = "#!mojo "; +const size_t Fetcher::kMaxShebangLength = 2048; + +Fetcher::Fetcher(const FetchCallback& loader_callback) + : loader_callback_(loader_callback) { +} + +Fetcher::~Fetcher() { +} + +bool Fetcher::PeekContentHandler(std::string* mojo_shebang, + GURL* mojo_content_handler_url) { + // TODO(aa): I guess this should just go in ApplicationManager now. + std::string shebang; + if (HasMojoMagic() && PeekFirstLine(&shebang)) { + GURL url(shebang.substr(arraysize(kMojoMagic) - 1, std::string::npos)); + if (url.is_valid()) { + *mojo_shebang = shebang; + *mojo_content_handler_url = url; + return true; + } + } + return false; +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/fetcher.h b/chromium/mojo/shell/fetcher.h new file mode 100644 index 00000000000..21cad3f2252 --- /dev/null +++ b/chromium/mojo/shell/fetcher.h @@ -0,0 +1,76 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_FETCHER_H_ +#define MOJO_SHELL_FETCHER_H_ + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" + +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" + +class GURL; + +namespace base { +class FilePath; +class TaskRunner; +} + +namespace mojo { +namespace shell { + +// Fetcher abstracts getting an application by either file or http[s] URL. +// +// Although it is possible to use the Network implementation for http[s] URLs +// (because the underlying net library knows how to handle them), it is +// extremely slow because network responses must be copied to disk in order to +// get a file handle we can use with dlopen. +// +// Until this is solved, we use two different implementations so that +// performance isn't completely absymal. +class Fetcher { + public: + // The param will be null in the case where the content could not be fetched. + // Reasons include: + // - network error + // - 4x or 5x HTTP errors + typedef base::Callback<void(scoped_ptr<Fetcher>)> FetchCallback; + + Fetcher(const FetchCallback& fetch_callback); + virtual ~Fetcher(); + + // Returns the original URL that was fetched. + virtual const GURL& GetURL() const = 0; + + // If the fetch resulted in a redirect, this returns the final URL after all + // redirects. Otherwise, it returns an empty URL. + virtual GURL GetRedirectURL() const = 0; + + virtual URLResponsePtr AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) = 0; + + virtual void AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) = 0; + + virtual std::string MimeType() = 0; + + virtual bool HasMojoMagic() = 0; + + virtual bool PeekFirstLine(std::string* line) = 0; + + bool PeekContentHandler(std::string* mojo_shebang, + GURL* mojo_content_handler_url); + + protected: + static const char kMojoMagic[]; + static const size_t kMaxShebangLength; + + FetchCallback loader_callback_; +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_FETCHER_H_ diff --git a/chromium/mojo/shell/identity.cc b/chromium/mojo/shell/identity.cc new file mode 100644 index 00000000000..bffe0ce4c60 --- /dev/null +++ b/chromium/mojo/shell/identity.cc @@ -0,0 +1,29 @@ +// Copyright 2015 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/identity.h" + +#include "mojo/shell/query_util.h" + +namespace mojo { +namespace shell { + +Identity::Identity(const GURL& url, const std::string& qualifier) + : url(GetBaseURLAndQuery(url, nullptr)), + qualifier(qualifier.empty() ? url.spec() : qualifier) { +} + +// explicit +Identity::Identity(const GURL& base_url) + : url(GetBaseURLAndQuery(base_url, nullptr)), qualifier(url.spec()) { +} + +bool Identity::operator<(const Identity& other) const { + if (url != other.url) + return url < other.url; + return qualifier < other.qualifier; +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/identity.h b/chromium/mojo/shell/identity.h new file mode 100644 index 00000000000..ac897a1c33f --- /dev/null +++ b/chromium/mojo/shell/identity.h @@ -0,0 +1,31 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_IDENTITY_H_ +#define MOJO_SHELL_IDENTITY_H_ + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +/** + * Represents the identity of an application. |url| is the url of the + * application. |qualifier| is a string that allows to tie a specific instance + * of an application to another. It is used by content handlers that need to be + * run in the context of another application. + */ +struct Identity { + Identity(const GURL& url, const std::string& qualifier); + explicit Identity(const GURL& url); + bool operator<(const Identity& other) const; + + const GURL url; + const std::string qualifier; +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_IDENTITY_H_ diff --git a/chromium/mojo/shell/local_fetcher.cc b/chromium/mojo/shell/local_fetcher.cc new file mode 100644 index 00000000000..48444839516 --- /dev/null +++ b/chromium/mojo/shell/local_fetcher.cc @@ -0,0 +1,96 @@ +// Copyright 2015 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/local_fetcher.h" + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/format_macros.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/common/url_type_converters.h" +#include "mojo/util/filename_util.h" +#include "url/url_util.h" + +namespace mojo { +namespace shell { + +namespace { + +void IgnoreResult(bool result) { +} + +} // namespace + +// A loader for local files. +LocalFetcher::LocalFetcher(const GURL& url, + const GURL& url_without_query, + const FetchCallback& loader_callback) + : Fetcher(loader_callback), + url_(url), + path_(util::UrlToFilePath(url_without_query)) { + TRACE_EVENT1("mojo_shell", "LocalFetcher::LocalFetcher", "url", url.spec()); + loader_callback_.Run(make_scoped_ptr(this)); +} + +const GURL& LocalFetcher::GetURL() const { + return url_; +} + +GURL LocalFetcher::GetRedirectURL() const { + return GURL::EmptyGURL(); +} + +URLResponsePtr LocalFetcher::AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) { + URLResponsePtr response(URLResponse::New()); + response->url = String::From(url_); + DataPipe data_pipe; + response->body = data_pipe.consumer_handle.Pass(); + int64 file_size; + if (base::GetFileSize(path_, &file_size)) { + response->headers = Array<HTTPHeaderPtr>(1); + HTTPHeaderPtr header = HTTPHeader::New(); + header->name = "Content-Length"; + header->value = base::StringPrintf("%" PRId64, file_size); + response->headers[0] = header.Pass(); + } + common::CopyFromFile(path_, data_pipe.producer_handle.Pass(), skip, + task_runner, base::Bind(&IgnoreResult)); + return response.Pass(); +} + +void LocalFetcher::AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) { + // Async for consistency with network case. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(callback, path_, base::PathExists(path_))); +} + +std::string LocalFetcher::MimeType() { + return ""; +} + +bool LocalFetcher::HasMojoMagic() { + std::string magic; + ReadFileToString(path_, &magic, strlen(kMojoMagic)); + return magic == kMojoMagic; +} + +bool LocalFetcher::PeekFirstLine(std::string* line) { + std::string start_of_file; + ReadFileToString(path_, &start_of_file, kMaxShebangLength); + size_t return_position = start_of_file.find('\n'); + if (return_position == std::string::npos) + return false; + *line = start_of_file.substr(0, return_position + 1); + return true; +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/local_fetcher.h b/chromium/mojo/shell/local_fetcher.h new file mode 100644 index 00000000000..52ad9da3e13 --- /dev/null +++ b/chromium/mojo/shell/local_fetcher.h @@ -0,0 +1,49 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_LOCAL_FETCHER_H_ +#define MOJO_SHELL_LOCAL_FETCHER_H_ + +#include "base/files/file_path.h" +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" +#include "mojo/shell/fetcher.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +// Implements Fetcher for file:// URLs. +class LocalFetcher : public Fetcher { + public: + LocalFetcher(const GURL& url, + const GURL& url_without_query, + const FetchCallback& loader_callback); + + private: + const GURL& GetURL() const override; + GURL GetRedirectURL() const override; + + URLResponsePtr AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) override; + + void AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) override; + + std::string MimeType() override; + + bool HasMojoMagic() override; + + bool PeekFirstLine(std::string* line) override; + + GURL url_; + base::FilePath path_; + + DISALLOW_COPY_AND_ASSIGN(LocalFetcher); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_LOCAL_FETCHER_H_ diff --git a/chromium/mojo/shell/native_runner.h b/chromium/mojo/shell/native_runner.h new file mode 100644 index 00000000000..ad89f4431e3 --- /dev/null +++ b/chromium/mojo/shell/native_runner.h @@ -0,0 +1,63 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_NATIVE_RUNNER_H_ +#define MOJO_SHELL_NATIVE_RUNNER_H_ + +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/application/public/interfaces/application.mojom.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h" + +#if defined(OS_WIN) +#undef DELETE +#endif + +namespace base { +class FilePath; +} + +namespace mojo { +namespace shell { + +enum class NativeApplicationCleanup { DELETE, DONT_DELETE }; + +// ApplicationManager requires implementations of NativeRunner and +// NativeRunnerFactory to run native applications. +class NativeRunner { + public: + virtual ~NativeRunner() {} + + // Loads the app in the file at |app_path| and runs it on some other + // thread/process. If |cleanup| is |DELETE|, this takes ownership of the file. + // |app_completed_callback| is posted (to the thread on which |Start()| was + // called) after |MojoMain()| completes. + // TODO(vtl): |app_path| and |cleanup| should probably be moved to the + // factory's Create(). Rationale: The factory may need information from the + // file to decide what kind of NativeRunner to make. + virtual void Start(const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request, + const base::Closure& app_completed_callback) = 0; +}; + +class NativeRunnerFactory { + public: + // Options for running the native app. (This will contain, e.g., information + // about the sandbox profile, etc.) + struct Options { + // Constructs with default options. + Options() : force_in_process(false) {} + + bool force_in_process; + }; + + virtual ~NativeRunnerFactory() {} + virtual scoped_ptr<NativeRunner> Create(const Options& options) = 0; +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_NATIVE_RUNNER_H_ diff --git a/chromium/mojo/shell/network_fetcher.cc b/chromium/mojo/shell/network_fetcher.cc new file mode 100644 index 00000000000..4de4d5d2a78 --- /dev/null +++ b/chromium/mojo/shell/network_fetcher.cc @@ -0,0 +1,251 @@ +// Copyright 2015 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/network_fetcher.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/common/url_type_converters.h" +#include "mojo/services/network/public/interfaces/network_service.mojom.h" +#include "mojo/shell/data_pipe_peek.h" +#include "mojo/shell/switches.h" + +namespace mojo { +namespace shell { + +NetworkFetcher::NetworkFetcher(bool disable_cache, + const GURL& url, + NetworkService* network_service, + const FetchCallback& loader_callback) + : Fetcher(loader_callback), + disable_cache_(false), + url_(url), + weak_ptr_factory_(this) { + StartNetworkRequest(url, network_service); +} + +NetworkFetcher::~NetworkFetcher() { +} + +const GURL& NetworkFetcher::GetURL() const { + return url_; +} + +GURL NetworkFetcher::GetRedirectURL() const { + if (!response_) + return GURL::EmptyGURL(); + + if (response_->redirect_url.is_null()) + return GURL::EmptyGURL(); + + return GURL(response_->redirect_url); +} + +URLResponsePtr NetworkFetcher::AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) { + if (skip != 0) { + MojoResult result = ReadDataRaw( + response_->body.get(), nullptr, &skip, + MOJO_READ_DATA_FLAG_ALL_OR_NONE | MOJO_READ_DATA_FLAG_DISCARD); + DCHECK_EQ(result, MOJO_RESULT_OK); + } + return response_.Pass(); +} + +void NetworkFetcher::RecordCacheToURLMapping(const base::FilePath& path, + const GURL& url) { + // This is used to extract symbols on android. + // TODO(eseidel): All users of this log should move to using the map file. + VLOG(1) << "Caching mojo app " << url << " at " << path.value(); + + base::FilePath temp_dir; + base::GetTempDir(&temp_dir); + base::ProcessId pid = base::Process::Current().Pid(); + std::string map_name = base::StringPrintf("mojo_shell.%d.maps", pid); + base::FilePath map_path = temp_dir.AppendASCII(map_name); + + // TODO(eseidel): Paths or URLs with spaces will need quoting. + std::string map_entry = + base::StringPrintf("%s %s\n", path.value().c_str(), url.spec().c_str()); + // TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696 + if (!PathExists(map_path)) { + base::WriteFile(map_path, map_entry.data(), + static_cast<int>(map_entry.length())); + } else { + base::AppendToFile(map_path, map_entry.data(), + static_cast<int>(map_entry.length())); + } +} + +// For remote debugging, GDB needs to be, a apriori, aware of the filename a +// library will be loaded from. AppIds should be be both predictable and unique, +// but any hash would work. Currently we use sha256 from crypto/secure_hash.h +bool NetworkFetcher::ComputeAppId(const base::FilePath& path, + std::string* digest_string) { + scoped_ptr<crypto::SecureHash> ctx( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) { + LOG(ERROR) << "Failed to open " << path.value() << " for computing AppId"; + return false; + } + char buf[1024]; + while (file.IsValid()) { + int bytes_read = file.ReadAtCurrentPos(buf, sizeof(buf)); + if (bytes_read == 0) + break; + ctx->Update(buf, bytes_read); + } + if (!file.IsValid()) { + LOG(ERROR) << "Error reading " << path.value(); + return false; + } + // The output is really a vector of unit8, we're cheating by using a string. + std::string output(crypto::kSHA256Length, 0); + ctx->Finish(string_as_array(&output), output.size()); + output = base::HexEncode(output.c_str(), output.size()); + // Using lowercase for compatiblity with sha256sum output. + *digest_string = base::StringToLowerASCII(output); + return true; +} + +bool NetworkFetcher::RenameToAppId(const GURL& url, + const base::FilePath& old_path, + base::FilePath* new_path) { + std::string app_id; + if (!ComputeAppId(old_path, &app_id)) + return false; + + // Using a hash of the url as a directory to prevent a race when the same + // bytes are downloaded from 2 different urls. In particular, if the same + // application is connected to twice concurrently with different query + // parameters, the directory will be different, which will prevent the + // collision. + std::string dirname = base::HexEncode( + crypto::SHA256HashString(url.spec()).data(), crypto::kSHA256Length); + + base::FilePath temp_dir; + base::GetTempDir(&temp_dir); + base::FilePath app_dir = temp_dir.AppendASCII(dirname); + // The directory is leaked, because it can be reused at any time if the same + // application is downloaded. Deleting it would be racy. This is only + // happening when --predictable-app-filenames is used. + bool result = base::CreateDirectoryAndGetError(app_dir, nullptr); + DCHECK(result); + std::string unique_name = base::StringPrintf("%s.mojo", app_id.c_str()); + *new_path = app_dir.AppendASCII(unique_name); + return base::Move(old_path, *new_path); +} + +void NetworkFetcher::CopyCompleted( + base::Callback<void(const base::FilePath&, bool)> callback, + bool success) { + if (success) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kPredictableAppFilenames)) { + // The copy completed, now move to $TMP/$APP_ID.mojo before the dlopen. + success = false; + base::FilePath new_path; + if (RenameToAppId(url_, path_, &new_path)) { + if (base::PathExists(new_path)) { + path_ = new_path; + success = true; + } + } + } + } + + if (success) + RecordCacheToURLMapping(path_, url_); + + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, path_, success)); +} + +void NetworkFetcher::AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) { + if (!path_.empty() || !response_) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(callback, path_, base::PathExists(path_))); + return; + } + + base::CreateTemporaryFile(&path_); + common::CopyToFile(response_->body.Pass(), path_, task_runner, + base::Bind(&NetworkFetcher::CopyCompleted, + weak_ptr_factory_.GetWeakPtr(), callback)); +} + +std::string NetworkFetcher::MimeType() { + return response_->mime_type; +} + +bool NetworkFetcher::HasMojoMagic() { + std::string magic; + return BlockingPeekNBytes(response_->body.get(), &magic, strlen(kMojoMagic), + kPeekTimeout) && + magic == kMojoMagic; +} + +bool NetworkFetcher::PeekFirstLine(std::string* line) { + return BlockingPeekLine(response_->body.get(), line, kMaxShebangLength, + kPeekTimeout); +} + +void NetworkFetcher::StartNetworkRequest(const GURL& url, + NetworkService* network_service) { + TRACE_EVENT_ASYNC_BEGIN1("mojo_shell", "NetworkFetcher::NetworkRequest", this, + "url", url.spec()); + URLRequestPtr request(URLRequest::New()); + request->url = String::From(url); + request->auto_follow_redirects = false; + request->bypass_cache = disable_cache_; + + network_service->CreateURLLoader(GetProxy(&url_loader_)); + url_loader_->Start(request.Pass(), + base::Bind(&NetworkFetcher::OnLoadComplete, + weak_ptr_factory_.GetWeakPtr())); +} + +void NetworkFetcher::OnLoadComplete(URLResponsePtr response) { + TRACE_EVENT_ASYNC_END0("mojo_shell", "NetworkFetcher::NetworkRequest", this); + scoped_ptr<Fetcher> owner(this); + if (response->error) { + LOG(ERROR) << "Error (" << response->error->code << ": " + << response->error->description << ") while fetching " + << response->url; + loader_callback_.Run(nullptr); + return; + } + + if (response->status_code >= 400 && response->status_code < 600) { + LOG(ERROR) << "Error (" << response->status_code << ": " + << response->status_line << "): " + << "while fetching " << response->url; + loader_callback_.Run(nullptr); + return; + } + + response_ = response.Pass(); + loader_callback_.Run(owner.Pass()); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/network_fetcher.h b/chromium/mojo/shell/network_fetcher.h new file mode 100644 index 00000000000..028ec5b2121 --- /dev/null +++ b/chromium/mojo/shell/network_fetcher.h @@ -0,0 +1,83 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_NETWORK_FETCHER_H_ +#define MOJO_SHELL_NETWORK_FETCHER_H_ + +#include "mojo/shell/fetcher.h" + +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" +#include "url/gurl.h" + +namespace mojo { + +class NetworkService; + +namespace shell { + +// Implements Fetcher for http[s] files. +class NetworkFetcher : public Fetcher { + public: + NetworkFetcher(bool disable_cache, + const GURL& url, + NetworkService* network_service, + const FetchCallback& loader_callback); + + ~NetworkFetcher() override; + + private: + // TODO(hansmuller): Revisit this when a real peek operation is available. + static const MojoDeadline kPeekTimeout = MOJO_DEADLINE_INDEFINITE; + + const GURL& GetURL() const override; + GURL GetRedirectURL() const override; + + URLResponsePtr AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) override; + + static void RecordCacheToURLMapping(const base::FilePath& path, + const GURL& url); + + // AppIds should be be both predictable and unique, but any hash would work. + // Currently we use sha256 from crypto/secure_hash.h + static bool ComputeAppId(const base::FilePath& path, + std::string* digest_string); + + static bool RenameToAppId(const GURL& url, + const base::FilePath& old_path, + base::FilePath* new_path); + + void CopyCompleted(base::Callback<void(const base::FilePath&, bool)> callback, + bool success); + + void AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) override; + + std::string MimeType() override; + + bool HasMojoMagic() override; + + bool PeekFirstLine(std::string* line) override; + + void StartNetworkRequest(const GURL& url, NetworkService* network_service); + + void OnLoadComplete(URLResponsePtr response); + + bool disable_cache_; + const GURL url_; + URLLoaderPtr url_loader_; + URLResponsePtr response_; + base::FilePath path_; + base::WeakPtrFactory<NetworkFetcher> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NetworkFetcher); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_NETWORK_FETCHER_H_ diff --git a/chromium/mojo/shell/query_util.cc b/chromium/mojo/shell/query_util.cc new file mode 100644 index 00000000000..fbe4c56fde5 --- /dev/null +++ b/chromium/mojo/shell/query_util.cc @@ -0,0 +1,31 @@ +// Copyright 2015 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/query_util.h" + +#include "base/strings/string_util.h" + +namespace mojo { +namespace shell { + +GURL GetBaseURLAndQuery(const GURL& url, std::string* query) { + if (!url.has_query()) { + if (query) + *query = ""; + return url; + } + + if (query) + *query = "?" + url.query(); + GURL::Replacements repl; + repl.SetQueryStr(""); + std::string result = url.ReplaceComponents(repl).spec(); + + // Remove the dangling '?' because it's ugly. + base::ReplaceChars(result, "?", "", &result); + return GURL(result); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/query_util.h b/chromium/mojo/shell/query_util.h new file mode 100644 index 00000000000..a1f0ac77a7c --- /dev/null +++ b/chromium/mojo/shell/query_util.h @@ -0,0 +1,23 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_QUERY_UTIL_H_ +#define MOJO_SHELL_QUERY_UTIL_H_ + +#include <utility> + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +// Returns the base URL (without the query string). If |query| is not nullptr, +// set |*query| to the query string. If the url doesn't have a query string, +// |*query| is set to the empty string. +GURL GetBaseURLAndQuery(const GURL& url, std::string* query); + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_QUERY_UTIL_H_ diff --git a/chromium/mojo/shell/query_util_unittest.cc b/chromium/mojo/shell/query_util_unittest.cc new file mode 100644 index 00000000000..9b28bc50d57 --- /dev/null +++ b/chromium/mojo/shell/query_util_unittest.cc @@ -0,0 +1,47 @@ +// Copyright 2015 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/query_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +TEST(QueryUtil, NoQuery) { + GURL url("http://www.example.com/"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, url); + EXPECT_EQ(query, ""); +} + +TEST(QueryUtil, WithEmptyQuery) { + GURL url("http://www.example.com/?"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, GURL("http://www.example.com/")); + EXPECT_EQ(query, "?"); +} + +TEST(QueryUtil, WithFullQuery) { + GURL url("http://www.example.com/?a=b&c=d"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, GURL("http://www.example.com/")); + EXPECT_EQ(query, "?a=b&c=d"); +} + +TEST(QueryUtil, ForFileURL) { + GURL url("file:///tmp/file?a=b&c=d"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, GURL("file:///tmp/file")); + EXPECT_EQ(query, "?a=b&c=d"); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/shell_impl.cc b/chromium/mojo/shell/shell_impl.cc new file mode 100644 index 00000000000..c51668628ae --- /dev/null +++ b/chromium/mojo/shell/shell_impl.cc @@ -0,0 +1,62 @@ +// Copyright 2015 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/shell_impl.h" + +#include "mojo/common/common_type_converters.h" +#include "mojo/common/url_type_converters.h" +#include "mojo/shell/application_manager.h" +#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h" + +namespace mojo { +namespace shell { + +ShellImpl::ShellImpl(ApplicationPtr application, + ApplicationManager* manager, + const Identity& identity, + const base::Closure& on_application_end) + : manager_(manager), + identity_(identity), + on_application_end_(on_application_end), + application_(application.Pass()), + binding_(this) { + binding_.set_error_handler(this); +} + +ShellImpl::~ShellImpl() { +} + +void ShellImpl::InitializeApplication(Array<String> args) { + ShellPtr shell; + binding_.Bind(GetProxy(&shell)); + application_->Initialize(shell.Pass(), args.Pass(), identity_.url.spec()); +} + +void ShellImpl::ConnectToClient(const GURL& requested_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + application_->AcceptConnection(String::From(requestor_url), services.Pass(), + exposed_services.Pass(), requested_url.spec()); +} + +// Shell implementation: +void ShellImpl::ConnectToApplication(const String& app_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + GURL app_gurl(app_url); + if (!app_gurl.is_valid()) { + LOG(ERROR) << "Error: invalid URL: " << app_url; + return; + } + manager_->ConnectToApplication(app_gurl, identity_.url, services.Pass(), + exposed_services.Pass(), base::Closure()); +} + +void ShellImpl::OnConnectionError() { + manager_->OnShellImplError(this); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/shell_impl.h b/chromium/mojo/shell/shell_impl.h new file mode 100644 index 00000000000..0f2d97488ee --- /dev/null +++ b/chromium/mojo/shell/shell_impl.h @@ -0,0 +1,62 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_SHELL_IMPL_H_ +#define MOJO_SHELL_SHELL_IMPL_H_ + +#include "base/callback.h" +#include "mojo/application/public/interfaces/application.mojom.h" +#include "mojo/application/public/interfaces/shell.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/shell/identity.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +class ApplicationManager; + +class ShellImpl : public Shell, public ErrorHandler { + public: + ShellImpl(ApplicationPtr application, + ApplicationManager* manager, + const Identity& resolved_identity, + const base::Closure& on_application_end); + + ~ShellImpl() override; + + void InitializeApplication(Array<String> args); + + void ConnectToClient(const GURL& requested_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services); + + Application* application() { return application_.get(); } + const Identity& identity() const { return identity_; } + base::Closure on_application_end() const { return on_application_end_; } + + private: + // Shell implementation: + void ConnectToApplication(const String& app_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) override; + + // ErrorHandler implementation: + void OnConnectionError() override; + + ApplicationManager* const manager_; + const Identity identity_; + base::Closure on_application_end_; + ApplicationPtr application_; + Binding<Shell> binding_; + + DISALLOW_COPY_AND_ASSIGN(ShellImpl); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_SHELL_IMPL_H_ diff --git a/chromium/mojo/shell/switches.cc b/chromium/mojo/shell/switches.cc new file mode 100644 index 00000000000..cd68ff31368 --- /dev/null +++ b/chromium/mojo/shell/switches.cc @@ -0,0 +1,26 @@ +// Copyright 2013 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/switches.h" + +#include "base/basictypes.h" + +namespace switches { + +// If set apps downloaded are not deleted. +const char kDontDeleteOnDownload[] = "dont-delete-on-download"; + +// Load apps in separate processes. +// TODO(vtl): Work in progress; doesn't work. Flip this to "disable" (or maybe +// change it to "single-process") when it works. +const char kEnableMultiprocess[] = "enable-multiprocess"; + +// If set apps downloaded are saved in with a predictable filename, to help +// remote debugging: when gdb is used through gdbserver, it needs to be able to +// find locally any loaded library. For this, gdb use the filename of the +// library. When using this flag, the application are named with the sha256 of +// their content. +const char kPredictableAppFilenames[] = "predictable-app-filenames"; + +} // namespace switches diff --git a/chromium/mojo/shell/switches.h b/chromium/mojo/shell/switches.h new file mode 100644 index 00000000000..f774b3550b9 --- /dev/null +++ b/chromium/mojo/shell/switches.h @@ -0,0 +1,21 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_SWITCHES_H_ +#define MOJO_SHELL_SWITCHES_H_ + +#include <set> +#include <string> + +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char kDontDeleteOnDownload[]; +extern const char kEnableMultiprocess[]; +extern const char kPredictableAppFilenames[]; + +} // namespace switches + +#endif // MOJO_SHELL_SWITCHES_H_ diff --git a/chromium/mojo/shell/test.mojom b/chromium/mojo/shell/test.mojom new file mode 100644 index 00000000000..25700b59b6f --- /dev/null +++ b/chromium/mojo/shell/test.mojom @@ -0,0 +1,23 @@ +// 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.shell; + +interface TestService { + Test(string test_string) => (); +}; + +interface TestA { + CallB(); + CallCFromB(); +}; + +interface TestB { + B() => (); + CallC() => (); +}; + +interface TestC { + C() => (); +}; |