| // 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 "content/browser/mojo/mojo_shell_context.h" |
| |
| #include "base/lazy_instance.h" |
| #include "base/macros.h" |
| #include "base/path_service.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "content/browser/gpu/gpu_process_host.h" |
| #include "content/common/gpu/gpu_process_launch_causes.h" |
| #include "content/common/process_control.mojom.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/utility_process_host.h" |
| #include "content/public/browser/utility_process_host_client.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/service_registry.h" |
| #include "mojo/application/public/cpp/application_delegate.h" |
| #include "mojo/common/url_type_converters.h" |
| #include "mojo/package_manager/package_manager_impl.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "mojo/public/cpp/bindings/string.h" |
| #include "mojo/shell/application_loader.h" |
| #include "mojo/shell/connect_to_application_params.h" |
| #include "mojo/shell/identity.h" |
| #include "mojo/shell/static_application_loader.h" |
| |
| #if defined(ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS) || \ |
| defined(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS) |
| #include "media/mojo/services/mojo_media_application.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| // An extra set of apps to register on initialization, if set by a test. |
| const MojoShellContext::StaticApplicationMap* g_applications_for_test; |
| |
| void StartUtilityProcessOnIOThread( |
| mojo::InterfaceRequest<ProcessControl> request, |
| const base::string16& process_name, |
| bool use_sandbox) { |
| UtilityProcessHost* process_host = |
| UtilityProcessHost::Create(nullptr, nullptr); |
| process_host->SetName(process_name); |
| if (!use_sandbox) |
| process_host->DisableSandbox(); |
| process_host->StartMojoMode(); |
| |
| ServiceRegistry* services = process_host->GetServiceRegistry(); |
| services->ConnectToRemoteService(request.Pass()); |
| } |
| |
| void OnApplicationLoaded(const GURL& url, bool success) { |
| if (!success) |
| LOG(ERROR) << "Failed to launch Mojo application for " << url.spec(); |
| } |
| |
| // The default loader to use for all applications. This does nothing but drop |
| // the Application request. |
| class DefaultApplicationLoader : public mojo::shell::ApplicationLoader { |
| public: |
| DefaultApplicationLoader() {} |
| ~DefaultApplicationLoader() override {} |
| |
| private: |
| // mojo::shell::ApplicationLoader: |
| void Load( |
| const GURL& url, |
| mojo::InterfaceRequest<mojo::Application> application_request) override {} |
| |
| DISALLOW_COPY_AND_ASSIGN(DefaultApplicationLoader); |
| }; |
| |
| // This launches a utility process and forwards the Load request the |
| // ProcessControl service there. The utility process is sandboxed iff |
| // |use_sandbox| is true. |
| class UtilityProcessLoader : public mojo::shell::ApplicationLoader { |
| public: |
| UtilityProcessLoader(const base::string16& process_name, bool use_sandbox) |
| : process_name_(process_name), use_sandbox_(use_sandbox) {} |
| ~UtilityProcessLoader() override {} |
| |
| private: |
| // mojo::shell::ApplicationLoader: |
| void Load( |
| const GURL& url, |
| mojo::InterfaceRequest<mojo::Application> application_request) override { |
| ProcessControlPtr process_control; |
| auto process_request = mojo::GetProxy(&process_control); |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind(&StartUtilityProcessOnIOThread, |
| base::Passed(&process_request), |
| process_name_, use_sandbox_)); |
| process_control->LoadApplication(url.spec(), application_request.Pass(), |
| base::Bind(&OnApplicationLoaded, url)); |
| } |
| |
| const base::string16 process_name_; |
| const bool use_sandbox_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UtilityProcessLoader); |
| }; |
| |
| // Request ProcessControl from GPU process host. Must be called on IO thread. |
| void RequestGpuProcessControl(mojo::InterfaceRequest<ProcessControl> request) { |
| BrowserChildProcessHostDelegate* process_host = |
| GpuProcessHost::Get(GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED, |
| CAUSE_FOR_GPU_LAUNCH_MOJO_SETUP); |
| if (!process_host) { |
| DLOG(ERROR) << "GPU process host not available."; |
| return; |
| } |
| |
| // TODO(xhwang): It's possible that |process_host| is non-null, but the actual |
| // process is dead. In that case, |request| will be dropped and application |
| // load requests through ProcessControl will also fail. Make sure we handle |
| // these cases correctly. |
| process_host->GetServiceRegistry()->ConnectToRemoteService(request.Pass()); |
| } |
| |
| // Forwards the load request to the GPU process. |
| class GpuProcessLoader : public mojo::shell::ApplicationLoader { |
| public: |
| GpuProcessLoader() {} |
| ~GpuProcessLoader() override {} |
| |
| private: |
| // mojo::shell::ApplicationLoader: |
| void Load( |
| const GURL& url, |
| mojo::InterfaceRequest<mojo::Application> application_request) override { |
| ProcessControlPtr process_control; |
| auto process_request = mojo::GetProxy(&process_control); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&RequestGpuProcessControl, base::Passed(&process_request))); |
| process_control->LoadApplication(url.spec(), application_request.Pass(), |
| base::Bind(&OnApplicationLoaded, url)); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(GpuProcessLoader); |
| }; |
| |
| } // namespace |
| |
| // Thread-safe proxy providing access to the shell context from any thread. |
| class MojoShellContext::Proxy { |
| public: |
| Proxy(MojoShellContext* shell_context) |
| : shell_context_(shell_context), |
| task_runner_(base::ThreadTaskRunnerHandle::Get()) {} |
| |
| ~Proxy() {} |
| |
| void ConnectToApplication( |
| const GURL& url, |
| const GURL& requestor_url, |
| mojo::InterfaceRequest<mojo::ServiceProvider> request, |
| mojo::ServiceProviderPtr exposed_services, |
| const mojo::shell::CapabilityFilter& filter, |
| const mojo::Shell::ConnectToApplicationCallback& callback) { |
| if (task_runner_ == base::ThreadTaskRunnerHandle::Get()) { |
| if (shell_context_) { |
| shell_context_->ConnectToApplicationOnOwnThread( |
| url, requestor_url, request.Pass(), exposed_services.Pass(), filter, |
| callback); |
| } |
| } else { |
| // |shell_context_| outlives the main MessageLoop, so it's safe for it to |
| // be unretained here. |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&MojoShellContext::ConnectToApplicationOnOwnThread, |
| base::Unretained(shell_context_), url, requestor_url, |
| base::Passed(&request), base::Passed(&exposed_services), |
| filter, callback)); |
| } |
| } |
| |
| private: |
| MojoShellContext* shell_context_; |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Proxy); |
| }; |
| |
| // static |
| base::LazyInstance<scoped_ptr<MojoShellContext::Proxy>> |
| MojoShellContext::proxy_ = LAZY_INSTANCE_INITIALIZER; |
| |
| void MojoShellContext::SetApplicationsForTest( |
| const StaticApplicationMap* apps) { |
| g_applications_for_test = apps; |
| } |
| |
| MojoShellContext::MojoShellContext() { |
| proxy_.Get().reset(new Proxy(this)); |
| |
| // Construct with an empty filepath since mojo: urls can't be registered now |
| // the url scheme registry is locked. |
| scoped_ptr<mojo::package_manager::PackageManagerImpl> package_manager( |
| new mojo::package_manager::PackageManagerImpl(base::FilePath(), nullptr)); |
| application_manager_.reset( |
| new mojo::shell::ApplicationManager(package_manager.Pass())); |
| |
| application_manager_->set_default_loader( |
| scoped_ptr<mojo::shell::ApplicationLoader>(new DefaultApplicationLoader)); |
| |
| StaticApplicationMap apps; |
| GetContentClient()->browser()->RegisterInProcessMojoApplications(&apps); |
| if (g_applications_for_test) { |
| // Add testing apps to the map, potentially overwriting whatever the |
| // browser client registered. |
| for (const auto& entry : *g_applications_for_test) |
| apps[entry.first] = entry.second; |
| } |
| for (const auto& entry : apps) { |
| application_manager_->SetLoaderForURL( |
| scoped_ptr<mojo::shell::ApplicationLoader>( |
| new mojo::shell::StaticApplicationLoader(entry.second)), |
| entry.first); |
| } |
| |
| ContentBrowserClient::OutOfProcessMojoApplicationMap sandboxed_apps; |
| GetContentClient() |
| ->browser() |
| ->RegisterOutOfProcessMojoApplications(&sandboxed_apps); |
| for (const auto& app : sandboxed_apps) { |
| application_manager_->SetLoaderForURL( |
| scoped_ptr<mojo::shell::ApplicationLoader>( |
| new UtilityProcessLoader(app.second, true /* use_sandbox */)), |
| app.first); |
| } |
| |
| ContentBrowserClient::OutOfProcessMojoApplicationMap unsandboxed_apps; |
| GetContentClient() |
| ->browser() |
| ->RegisterUnsandboxedOutOfProcessMojoApplications(&unsandboxed_apps); |
| for (const auto& app : unsandboxed_apps) { |
| application_manager_->SetLoaderForURL( |
| scoped_ptr<mojo::shell::ApplicationLoader>( |
| new UtilityProcessLoader(app.second, false /* use_sandbox */)), |
| app.first); |
| } |
| |
| #if (ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS) |
| application_manager_->SetLoaderForURL( |
| scoped_ptr<mojo::shell::ApplicationLoader>( |
| new mojo::shell::StaticApplicationLoader( |
| base::Bind(&media::MojoMediaApplication::CreateApp))), |
| GURL("mojo:media")); |
| #elif(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS) |
| application_manager_->SetLoaderForURL( |
| scoped_ptr<mojo::shell::ApplicationLoader>(new GpuProcessLoader()), |
| GURL("mojo:media")); |
| #endif |
| } |
| |
| MojoShellContext::~MojoShellContext() { |
| } |
| |
| // static |
| void MojoShellContext::ConnectToApplication( |
| const GURL& url, |
| const GURL& requestor_url, |
| mojo::InterfaceRequest<mojo::ServiceProvider> request, |
| mojo::ServiceProviderPtr exposed_services, |
| const mojo::shell::CapabilityFilter& filter, |
| const mojo::Shell::ConnectToApplicationCallback& callback) { |
| proxy_.Get()->ConnectToApplication(url, requestor_url, |
| request.Pass(), exposed_services.Pass(), |
| filter, callback); |
| } |
| |
| void MojoShellContext::ConnectToApplicationOnOwnThread( |
| const GURL& url, |
| const GURL& requestor_url, |
| mojo::InterfaceRequest<mojo::ServiceProvider> request, |
| mojo::ServiceProviderPtr exposed_services, |
| const mojo::shell::CapabilityFilter& filter, |
| const mojo::Shell::ConnectToApplicationCallback& callback) { |
| scoped_ptr<mojo::shell::ConnectToApplicationParams> params( |
| new mojo::shell::ConnectToApplicationParams); |
| params->set_source( |
| mojo::shell::Identity(requestor_url, std::string(), |
| mojo::shell::GetPermissiveCapabilityFilter())); |
| params->SetTarget(mojo::shell::Identity(url, std::string(), filter)); |
| params->set_services(request.Pass()); |
| params->set_exposed_services(exposed_services.Pass()); |
| params->set_on_application_end(base::Bind(&base::DoNothing)); |
| params->set_connect_callback(callback); |
| application_manager_->ConnectToApplication(params.Pass()); |
| } |
| |
| } // namespace content |