1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "mojo/shell/application_manager.h"
8 #include "base/command_line.h"
9 #include "base/logging.h"
10 #include "base/macros.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/trace_event/trace_event.h"
14 #include "mojo/public/cpp/bindings/binding.h"
15 #include "mojo/public/cpp/bindings/error_handler.h"
16 #include "mojo/shell/fetcher.h"
17 #include "mojo/shell/local_fetcher.h"
18 #include "mojo/shell/network_fetcher.h"
19 #include "mojo/shell/query_util.h"
20 #include "mojo/shell/shell_impl.h"
21 #include "mojo/shell/switches.h"
22 #include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h"
30 bool has_created_instance
= false;
34 class ApplicationManager::ContentHandlerConnection
: public ErrorHandler
{
36 ContentHandlerConnection(ApplicationManager
* manager
,
37 const GURL
& content_handler_url
)
38 : manager_(manager
), content_handler_url_(content_handler_url
) {
39 ServiceProviderPtr services
;
40 manager
->ConnectToApplication(content_handler_url
, GURL(),
41 GetProxy(&services
), nullptr,
44 content_handler_
.Bind(pipe
.handle0
.Pass());
45 services
->ConnectToService(ContentHandler::Name_
, pipe
.handle1
.Pass());
46 content_handler_
.set_error_handler(this);
49 ContentHandler
* content_handler() { return content_handler_
.get(); }
51 GURL
content_handler_url() { return content_handler_url_
; }
54 // ErrorHandler implementation:
55 void OnConnectionError() override
{ manager_
->OnContentHandlerError(this); }
57 ApplicationManager
* manager_
;
58 GURL content_handler_url_
;
59 ContentHandlerPtr content_handler_
;
61 DISALLOW_COPY_AND_ASSIGN(ContentHandlerConnection
);
65 ApplicationManager::TestAPI::TestAPI(ApplicationManager
* manager
)
69 ApplicationManager::TestAPI::~TestAPI() {
72 bool ApplicationManager::TestAPI::HasCreatedInstance() {
73 return has_created_instance
;
76 bool ApplicationManager::TestAPI::HasFactoryForURL(const GURL
& url
) const {
77 return manager_
->identity_to_shell_impl_
.find(Identity(url
)) !=
78 manager_
->identity_to_shell_impl_
.end();
81 ApplicationManager::ApplicationManager(Delegate
* delegate
)
82 : delegate_(delegate
), weak_ptr_factory_(this) {
85 ApplicationManager::~ApplicationManager() {
86 STLDeleteValues(&url_to_content_handler_
);
87 TerminateShellConnections();
88 STLDeleteValues(&url_to_loader_
);
89 STLDeleteValues(&scheme_to_loader_
);
92 void ApplicationManager::TerminateShellConnections() {
93 STLDeleteValues(&identity_to_shell_impl_
);
96 void ApplicationManager::ConnectToApplication(
97 const GURL
& requested_url
,
98 const GURL
& requestor_url
,
99 InterfaceRequest
<ServiceProvider
> services
,
100 ServiceProviderPtr exposed_services
,
101 const base::Closure
& on_application_end
) {
102 ConnectToApplicationWithParameters(
103 requested_url
, requestor_url
, services
.Pass(), exposed_services
.Pass(),
104 on_application_end
, std::vector
<std::string
>());
107 void ApplicationManager::ConnectToApplicationWithParameters(
108 const GURL
& requested_url
,
109 const GURL
& requestor_url
,
110 InterfaceRequest
<ServiceProvider
> services
,
111 ServiceProviderPtr exposed_services
,
112 const base::Closure
& on_application_end
,
113 const std::vector
<std::string
>& pre_redirect_parameters
) {
114 TRACE_EVENT_INSTANT1(
115 "mojo_shell", "ApplicationManager::ConnectToApplicationWithParameters",
116 TRACE_EVENT_SCOPE_THREAD
, "requested_url", requested_url
.spec());
117 DCHECK(requested_url
.is_valid());
119 // We check both the mapped and resolved urls for existing shell_impls because
120 // external applications can be registered for the unresolved mojo:foo urls.
122 GURL mapped_url
= delegate_
->ResolveMappings(requested_url
);
123 if (ConnectToRunningApplication(mapped_url
, requestor_url
, &services
,
124 &exposed_services
)) {
128 GURL resolved_url
= delegate_
->ResolveMojoURL(mapped_url
);
129 if (ConnectToRunningApplication(resolved_url
, requestor_url
, &services
,
130 &exposed_services
)) {
134 // The application is not running, let's compute the parameters.
135 if (ConnectToApplicationWithLoader(
136 requested_url
, mapped_url
, requestor_url
, &services
,
137 &exposed_services
, on_application_end
, pre_redirect_parameters
,
138 GetLoaderForURL(mapped_url
))) {
142 if (ConnectToApplicationWithLoader(
143 requested_url
, resolved_url
, requestor_url
, &services
,
144 &exposed_services
, on_application_end
, pre_redirect_parameters
,
145 GetLoaderForURL(resolved_url
))) {
149 if (ConnectToApplicationWithLoader(
150 requested_url
, resolved_url
, requestor_url
, &services
,
151 &exposed_services
, on_application_end
, pre_redirect_parameters
,
152 default_loader_
.get())) {
156 auto callback
= base::Bind(&ApplicationManager::HandleFetchCallback
,
157 weak_ptr_factory_
.GetWeakPtr(), requested_url
,
158 requestor_url
, base::Passed(services
.Pass()),
159 base::Passed(exposed_services
.Pass()),
160 on_application_end
, pre_redirect_parameters
);
162 if (resolved_url
.SchemeIsFile()) {
164 resolved_url
, GetBaseURLAndQuery(resolved_url
, nullptr),
165 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
));
169 if (!network_service_
)
170 ConnectToService(GURL("mojo:network_service"), &network_service_
);
172 const NativeApplicationCleanup cleanup
=
173 base::CommandLine::ForCurrentProcess()->HasSwitch(
174 switches::kDontDeleteOnDownload
)
175 ? NativeApplicationCleanup::DONT_DELETE
176 : NativeApplicationCleanup::DELETE
;
178 new NetworkFetcher(disable_cache_
, resolved_url
, network_service_
.get(),
179 base::Bind(callback
, cleanup
));
182 bool ApplicationManager::ConnectToRunningApplication(
183 const GURL
& resolved_url
,
184 const GURL
& requestor_url
,
185 InterfaceRequest
<ServiceProvider
>* services
,
186 ServiceProviderPtr
* exposed_services
) {
187 GURL application_url
= GetBaseURLAndQuery(resolved_url
, nullptr);
188 ShellImpl
* shell_impl
= GetShellImpl(application_url
);
192 ConnectToClient(shell_impl
, resolved_url
, requestor_url
, services
->Pass(),
193 exposed_services
->Pass());
197 bool ApplicationManager::ConnectToApplicationWithLoader(
198 const GURL
& requested_url
,
199 const GURL
& resolved_url
,
200 const GURL
& requestor_url
,
201 InterfaceRequest
<ServiceProvider
>* services
,
202 ServiceProviderPtr
* exposed_services
,
203 const base::Closure
& on_application_end
,
204 const std::vector
<std::string
>& parameters
,
205 ApplicationLoader
* loader
) {
210 requested_url
.scheme() == "mojo" ? requested_url
: resolved_url
;
214 RegisterShell(app_url
, requestor_url
, services
->Pass(),
215 exposed_services
->Pass(), on_application_end
, parameters
));
219 InterfaceRequest
<Application
> ApplicationManager::RegisterShell(
221 const GURL
& requestor_url
,
222 InterfaceRequest
<ServiceProvider
> services
,
223 ServiceProviderPtr exposed_services
,
224 const base::Closure
& on_application_end
,
225 const std::vector
<std::string
>& parameters
) {
226 Identity
app_identity(app_url
);
228 ApplicationPtr application
;
229 InterfaceRequest
<Application
> application_request
= GetProxy(&application
);
231 new ShellImpl(application
.Pass(), this, app_identity
, on_application_end
);
232 identity_to_shell_impl_
[app_identity
] = shell
;
233 shell
->InitializeApplication(Array
<String
>::From(parameters
));
234 ConnectToClient(shell
, app_url
, requestor_url
, services
.Pass(),
235 exposed_services
.Pass());
236 return application_request
.Pass();
239 ShellImpl
* ApplicationManager::GetShellImpl(const GURL
& url
) {
240 const auto& shell_it
= identity_to_shell_impl_
.find(Identity(url
));
241 if (shell_it
!= identity_to_shell_impl_
.end())
242 return shell_it
->second
;
246 void ApplicationManager::ConnectToClient(
247 ShellImpl
* shell_impl
,
248 const GURL
& resolved_url
,
249 const GURL
& requestor_url
,
250 InterfaceRequest
<ServiceProvider
> services
,
251 ServiceProviderPtr exposed_services
) {
252 shell_impl
->ConnectToClient(resolved_url
, requestor_url
, services
.Pass(),
253 exposed_services
.Pass());
256 void ApplicationManager::HandleFetchCallback(
257 const GURL
& requested_url
,
258 const GURL
& requestor_url
,
259 InterfaceRequest
<ServiceProvider
> services
,
260 ServiceProviderPtr exposed_services
,
261 const base::Closure
& on_application_end
,
262 const std::vector
<std::string
>& parameters
,
263 NativeApplicationCleanup cleanup
,
264 scoped_ptr
<Fetcher
> fetcher
) {
266 // Network error. Drop |application_request| to tell requestor.
270 GURL redirect_url
= fetcher
->GetRedirectURL();
271 if (!redirect_url
.is_empty()) {
272 // And around we go again... Whee!
273 // TODO(sky): this loses |requested_url|.
274 ConnectToApplicationWithParameters(redirect_url
, requestor_url
,
275 services
.Pass(), exposed_services
.Pass(),
276 on_application_end
, parameters
);
280 // We already checked if the application was running before we fetched it, but
281 // it might have started while the fetch was outstanding. We don't want to
282 // have two copies of the app running, so check again.
284 // Also, it's possible the original URL was redirected to an app that is
286 if (ConnectToRunningApplication(requested_url
, requestor_url
, &services
,
287 &exposed_services
)) {
292 requested_url
.scheme() == "mojo" ? requested_url
: fetcher
->GetURL();
294 InterfaceRequest
<Application
> request(
295 RegisterShell(app_url
, requestor_url
, services
.Pass(),
296 exposed_services
.Pass(), on_application_end
, parameters
));
298 // If the response begins with a #!mojo <content-handler-url>, use it.
299 GURL content_handler_url
;
301 if (fetcher
->PeekContentHandler(&shebang
, &content_handler_url
)) {
302 LoadWithContentHandler(
303 content_handler_url
, request
.Pass(),
304 fetcher
->AsURLResponse(blocking_pool_
,
305 static_cast<int>(shebang
.size())));
309 MimeTypeToURLMap::iterator iter
= mime_type_to_url_
.find(fetcher
->MimeType());
310 if (iter
!= mime_type_to_url_
.end()) {
311 LoadWithContentHandler(iter
->second
, request
.Pass(),
312 fetcher
->AsURLResponse(blocking_pool_
, 0));
316 // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo
317 // application. That could either mean looking for the platform-specific dll
318 // header, or looking for some specific mojo signature prepended to the
320 // TODO(vtl): (Maybe this should be done by the factory/runner?)
322 GURL base_resolved_url
= GetBaseURLAndQuery(fetcher
->GetURL(), nullptr);
323 NativeRunnerFactory::Options options
;
324 if (url_to_native_options_
.find(base_resolved_url
) !=
325 url_to_native_options_
.end()) {
326 DVLOG(2) << "Applying stored native options to resolved URL "
327 << fetcher
->GetURL();
328 options
= url_to_native_options_
[base_resolved_url
];
333 base::Bind(&ApplicationManager::RunNativeApplication
,
334 weak_ptr_factory_
.GetWeakPtr(), base::Passed(request
.Pass()),
335 options
, cleanup
, base::Passed(fetcher
.Pass())));
338 void ApplicationManager::RunNativeApplication(
339 InterfaceRequest
<Application
> application_request
,
340 const NativeRunnerFactory::Options
& options
,
341 NativeApplicationCleanup cleanup
,
342 scoped_ptr
<Fetcher
> fetcher
,
343 const base::FilePath
& path
,
345 // We only passed fetcher to keep it alive. Done with it now.
348 DCHECK(application_request
.is_pending());
351 LOG(ERROR
) << "Library not started because library path '" << path
.value()
352 << "' does not exist.";
356 TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path",
357 path
.AsUTF8Unsafe());
358 NativeRunner
* runner
= native_runner_factory_
->Create(options
).release();
359 native_runners_
.push_back(runner
);
360 runner
->Start(path
, cleanup
, application_request
.Pass(),
361 base::Bind(&ApplicationManager::CleanupRunner
,
362 weak_ptr_factory_
.GetWeakPtr(), runner
));
365 void ApplicationManager::RegisterContentHandler(
366 const std::string
& mime_type
,
367 const GURL
& content_handler_url
) {
368 DCHECK(content_handler_url
.is_valid())
369 << "Content handler URL is invalid for mime type " << mime_type
;
370 mime_type_to_url_
[mime_type
] = content_handler_url
;
373 void ApplicationManager::LoadWithContentHandler(
374 const GURL
& content_handler_url
,
375 InterfaceRequest
<Application
> application_request
,
376 URLResponsePtr url_response
) {
377 ContentHandlerConnection
* connection
= nullptr;
378 URLToContentHandlerMap::iterator iter
=
379 url_to_content_handler_
.find(content_handler_url
);
380 if (iter
!= url_to_content_handler_
.end()) {
381 connection
= iter
->second
;
383 connection
= new ContentHandlerConnection(this, content_handler_url
);
384 url_to_content_handler_
[content_handler_url
] = connection
;
387 connection
->content_handler()->StartApplication(application_request
.Pass(),
388 url_response
.Pass());
391 void ApplicationManager::SetLoaderForURL(scoped_ptr
<ApplicationLoader
> loader
,
393 URLToLoaderMap::iterator it
= url_to_loader_
.find(url
);
394 if (it
!= url_to_loader_
.end())
396 url_to_loader_
[url
] = loader
.release();
399 void ApplicationManager::SetLoaderForScheme(
400 scoped_ptr
<ApplicationLoader
> loader
,
401 const std::string
& scheme
) {
402 SchemeToLoaderMap::iterator it
= scheme_to_loader_
.find(scheme
);
403 if (it
!= scheme_to_loader_
.end())
405 scheme_to_loader_
[scheme
] = loader
.release();
408 void ApplicationManager::SetNativeOptionsForURL(
409 const NativeRunnerFactory::Options
& options
,
411 DCHECK(!url
.has_query()); // Precondition.
412 // Apply mappings and resolution to get the resolved URL.
414 delegate_
->ResolveMojoURL(delegate_
->ResolveMappings(url
));
415 DCHECK(!resolved_url
.has_query()); // Still shouldn't have query.
416 // TODO(vtl): We should probably also remove/disregard the query string (and
417 // maybe canonicalize in other ways).
418 DVLOG(2) << "Storing native options for resolved URL " << resolved_url
419 << " (original URL " << url
<< ")";
420 url_to_native_options_
[resolved_url
] = options
;
423 ApplicationLoader
* ApplicationManager::GetLoaderForURL(const GURL
& url
) {
424 auto url_it
= url_to_loader_
.find(GetBaseURLAndQuery(url
, nullptr));
425 if (url_it
!= url_to_loader_
.end())
426 return url_it
->second
;
427 auto scheme_it
= scheme_to_loader_
.find(url
.scheme());
428 if (scheme_it
!= scheme_to_loader_
.end())
429 return scheme_it
->second
;
433 void ApplicationManager::OnShellImplError(ShellImpl
* shell_impl
) {
434 // Called from ~ShellImpl, so we do not need to call Destroy here.
435 const Identity identity
= shell_impl
->identity();
436 base::Closure on_application_end
= shell_impl
->on_application_end();
438 auto it
= identity_to_shell_impl_
.find(identity
);
439 DCHECK(it
!= identity_to_shell_impl_
.end());
441 identity_to_shell_impl_
.erase(it
);
442 if (!on_application_end
.is_null())
443 on_application_end
.Run();
446 void ApplicationManager::OnContentHandlerError(
447 ContentHandlerConnection
* content_handler
) {
448 // Remove the mapping to the content handler.
450 url_to_content_handler_
.find(content_handler
->content_handler_url());
451 DCHECK(it
!= url_to_content_handler_
.end());
453 url_to_content_handler_
.erase(it
);
456 ScopedMessagePipeHandle
ApplicationManager::ConnectToServiceByName(
457 const GURL
& application_url
,
458 const std::string
& interface_name
) {
459 ServiceProviderPtr services
;
460 ConnectToApplication(application_url
, GURL(), GetProxy(&services
), nullptr,
463 services
->ConnectToService(interface_name
, pipe
.handle1
.Pass());
464 return pipe
.handle0
.Pass();
467 void ApplicationManager::CleanupRunner(NativeRunner
* runner
) {
468 native_runners_
.erase(
469 std::find(native_runners_
.begin(), native_runners_
.end(), runner
));