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/application/public/interfaces/content_handler.mojom.h"
15 #include "mojo/public/cpp/bindings/binding.h"
16 #include "mojo/shell/application_instance.h"
17 #include "mojo/shell/content_handler_connection.h"
18 #include "mojo/shell/fetcher.h"
19 #include "mojo/shell/local_fetcher.h"
20 #include "mojo/shell/network_fetcher.h"
21 #include "mojo/shell/query_util.h"
22 #include "mojo/shell/switches.h"
23 #include "mojo/shell/update_fetcher.h"
31 bool has_created_instance
= false;
33 void OnEmptyOnConnectCallback(uint32_t content_handler_id
) {}
38 ApplicationManager::TestAPI::TestAPI(ApplicationManager
* manager
)
42 ApplicationManager::TestAPI::~TestAPI() {
45 bool ApplicationManager::TestAPI::HasCreatedInstance() {
46 return has_created_instance
;
49 bool ApplicationManager::TestAPI::HasRunningInstanceForURL(
50 const GURL
& url
) const {
51 return manager_
->identity_to_instance_
.find(Identity(url
)) !=
52 manager_
->identity_to_instance_
.end();
55 ApplicationManager::ApplicationManager(Delegate
* delegate
)
56 : delegate_(delegate
),
57 disable_cache_(false),
58 content_handler_id_counter_(0u),
59 weak_ptr_factory_(this) {}
61 ApplicationManager::~ApplicationManager() {
62 URLToContentHandlerMap
url_to_content_handler(url_to_content_handler_
);
63 for (auto& pair
: url_to_content_handler
)
64 pair
.second
->CloseConnection();
65 TerminateShellConnections();
66 STLDeleteValues(&url_to_loader_
);
67 STLDeleteValues(&scheme_to_loader_
);
70 void ApplicationManager::TerminateShellConnections() {
71 STLDeleteValues(&identity_to_instance_
);
74 void ApplicationManager::ConnectToApplication(
75 ApplicationInstance
* originator
,
76 mojo::URLRequestPtr requested_url
,
77 const std::string
& qualifier
,
78 const GURL
& requestor_url
,
79 InterfaceRequest
<ServiceProvider
> services
,
80 ServiceProviderPtr exposed_services
,
81 const CapabilityFilter
& filter
,
82 const base::Closure
& on_application_end
,
83 const Shell::ConnectToApplicationCallback
& connect_callback
) {
84 GURL
requested_gurl(requested_url
->url
.To
<std::string
>());
86 "mojo_shell", "ApplicationManager::ConnectToApplication",
87 TRACE_EVENT_SCOPE_THREAD
, "requested_url", requested_gurl
.spec());
88 DCHECK(requested_gurl
.is_valid());
90 // We check both the mapped and resolved urls for existing instances because
91 // external applications can be registered for the unresolved mojo:foo urls.
93 GURL mapped_url
= delegate_
->ResolveMappings(requested_gurl
);
94 if (ConnectToRunningApplication(originator
, mapped_url
, qualifier
,
95 requestor_url
, &services
, &exposed_services
,
96 filter
, connect_callback
)) {
100 GURL resolved_url
= delegate_
->ResolveMojoURL(mapped_url
);
101 if (ConnectToRunningApplication(originator
, resolved_url
, qualifier
,
102 requestor_url
, &services
, &exposed_services
,
103 filter
, connect_callback
)) {
107 // The application is not running, let's compute the parameters.
108 if (ConnectToApplicationWithLoader(
109 originator
, requested_gurl
, qualifier
, mapped_url
, requestor_url
,
110 &services
, &exposed_services
, filter
, on_application_end
,
111 connect_callback
, GetLoaderForURL(mapped_url
))) {
115 if (ConnectToApplicationWithLoader(
116 originator
, requested_gurl
, qualifier
, resolved_url
, requestor_url
,
117 &services
, &exposed_services
, filter
, on_application_end
,
118 connect_callback
, GetLoaderForURL(resolved_url
))) {
122 if (ConnectToApplicationWithLoader(
123 originator
, requested_gurl
, qualifier
, resolved_url
, requestor_url
,
124 &services
, &exposed_services
, filter
, on_application_end
,
125 connect_callback
, default_loader_
.get())) {
129 auto callback
= base::Bind(
130 &ApplicationManager::HandleFetchCallback
, weak_ptr_factory_
.GetWeakPtr(),
131 originator
, requested_gurl
, qualifier
, requestor_url
,
132 base::Passed(services
.Pass()), base::Passed(exposed_services
.Pass()),
133 filter
, on_application_end
, connect_callback
);
135 if (delegate_
->CreateFetcher(
137 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
))) {
141 if (resolved_url
.SchemeIsFile()) {
142 // LocalFetcher uses the network service to infer MIME types from URLs.
143 // Skip this for mojo URLs to avoid recursively loading the network service.
144 if (!network_service_
&& !requested_gurl
.SchemeIs("mojo"))
145 ConnectToService(GURL("mojo:network_service"), &network_service_
);
147 network_service_
.get(), resolved_url
,
148 GetBaseURLAndQuery(resolved_url
, nullptr),
149 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
));
153 if (mapped_url
.SchemeIs("mojo") &&
154 base::CommandLine::ForCurrentProcess()->HasSwitch(
155 switches::kUseUpdater
)) {
156 ConnectToService(GURL("mojo:updater"), &updater_
);
158 mapped_url
, updater_
.get(),
159 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
));
163 if (!url_loader_factory_
)
164 ConnectToService(GURL("mojo:network_service"), &url_loader_factory_
);
166 const NativeApplicationCleanup cleanup
=
167 base::CommandLine::ForCurrentProcess()->HasSwitch(
168 switches::kDontDeleteOnDownload
)
169 ? NativeApplicationCleanup::DONT_DELETE
170 : NativeApplicationCleanup::DELETE
;
172 if (requested_gurl
.SchemeIs("mojo")) {
173 // Use the resolved mojo URL in the request to support origin mapping, etc.
174 mojo::URLRequestPtr
resolved_url_request(mojo::URLRequest::New());
175 resolved_url_request
->url
= resolved_url
.spec();
176 new NetworkFetcher(disable_cache_
, resolved_url_request
.Pass(),
177 url_loader_factory_
.get(),
178 base::Bind(callback
, cleanup
));
182 new NetworkFetcher(disable_cache_
, requested_url
.Pass(),
183 url_loader_factory_
.get(), base::Bind(callback
, cleanup
));
186 bool ApplicationManager::ConnectToRunningApplication(
187 ApplicationInstance
* originator
,
188 const GURL
& resolved_url
,
189 const std::string
& qualifier
,
190 const GURL
& requestor_url
,
191 InterfaceRequest
<ServiceProvider
>* services
,
192 ServiceProviderPtr
* exposed_services
,
193 const CapabilityFilter
& filter
,
194 const Shell::ConnectToApplicationCallback
& connect_callback
) {
195 ApplicationInstance
* instance
=
196 GetApplicationInstance(Identity(resolved_url
, qualifier
));
200 instance
->ConnectToClient(originator
, resolved_url
, requestor_url
,
201 services
->Pass(), exposed_services
->Pass(), filter
,
206 bool ApplicationManager::ConnectToApplicationWithLoader(
207 ApplicationInstance
* originator
,
208 const GURL
& requested_url
,
209 const std::string
& qualifier
,
210 const GURL
& resolved_url
,
211 const GURL
& requestor_url
,
212 InterfaceRequest
<ServiceProvider
>* services
,
213 ServiceProviderPtr
* exposed_services
,
214 const CapabilityFilter
& filter
,
215 const base::Closure
& on_application_end
,
216 const Shell::ConnectToApplicationCallback
& connect_callback
,
217 ApplicationLoader
* loader
) {
222 requested_url
.SchemeIs("mojo") ? requested_url
: resolved_url
;
226 RegisterInstance(originator
, app_url
, qualifier
, requestor_url
,
227 services
->Pass(), exposed_services
->Pass(), filter
,
228 on_application_end
, connect_callback
, nullptr));
232 InterfaceRequest
<Application
> ApplicationManager::RegisterInstance(
233 ApplicationInstance
* originator
,
235 const std::string
& qualifier
,
236 const GURL
& requestor_url
,
237 InterfaceRequest
<ServiceProvider
> services
,
238 ServiceProviderPtr exposed_services
,
239 const CapabilityFilter
& filter
,
240 const base::Closure
& on_application_end
,
241 const Shell::ConnectToApplicationCallback
& connect_callback
,
242 ApplicationInstance
** resulting_instance
) {
243 Identity
app_identity(app_url
, qualifier
);
245 ApplicationPtr application
;
246 InterfaceRequest
<Application
> application_request
= GetProxy(&application
);
247 ApplicationInstance
* instance
= new ApplicationInstance(
248 application
.Pass(), this,
249 originator
? originator
->identity() : Identity(GURL()), app_identity
,
250 filter
, Shell::kInvalidContentHandlerID
, on_application_end
);
251 DCHECK(identity_to_instance_
.find(app_identity
) ==
252 identity_to_instance_
.end());
253 identity_to_instance_
[app_identity
] = instance
;
254 instance
->InitializeApplication();
255 instance
->ConnectToClient(originator
, app_url
, requestor_url
, services
.Pass(),
256 exposed_services
.Pass(), filter
, connect_callback
);
257 if (resulting_instance
)
258 *resulting_instance
= instance
;
259 return application_request
.Pass();
262 ApplicationInstance
* ApplicationManager::GetApplicationInstance(
263 const Identity
& identity
) const {
264 const auto& instance_it
= identity_to_instance_
.find(identity
);
265 if (instance_it
!= identity_to_instance_
.end())
266 return instance_it
->second
;
270 void ApplicationManager::HandleFetchCallback(
271 ApplicationInstance
* originator
,
272 const GURL
& requested_url
,
273 const std::string
& qualifier
,
274 const GURL
& requestor_url
,
275 InterfaceRequest
<ServiceProvider
> services
,
276 ServiceProviderPtr exposed_services
,
277 const CapabilityFilter
& filter
,
278 const base::Closure
& on_application_end
,
279 const Shell::ConnectToApplicationCallback
& connect_callback
,
280 NativeApplicationCleanup cleanup
,
281 scoped_ptr
<Fetcher
> fetcher
) {
283 // Network error. Drop |application_request| to tell requestor.
284 connect_callback
.Run(Shell::kInvalidContentHandlerID
);
288 GURL redirect_url
= fetcher
->GetRedirectURL();
289 if (!redirect_url
.is_empty()) {
290 // And around we go again... Whee!
291 // TODO(sky): this loses |requested_url|.
292 mojo::URLRequestPtr
request(mojo::URLRequest::New());
293 request
->url
= mojo::String::From(redirect_url
.spec());
294 HttpHeaderPtr header
= HttpHeader::New();
295 header
->name
= "Referer";
296 header
->value
= fetcher
->GetRedirectReferer().spec();
297 request
->headers
.push_back(header
.Pass());
298 ConnectToApplication(originator
, request
.Pass(), qualifier
, requestor_url
,
299 services
.Pass(), exposed_services
.Pass(), filter
,
300 on_application_end
, connect_callback
);
304 // We already checked if the application was running before we fetched it, but
305 // it might have started while the fetch was outstanding. We don't want to
306 // have two copies of the app running, so check again.
308 // Also, it's possible the original URL was redirected to an app that is
310 if (ConnectToRunningApplication(originator
, requested_url
, qualifier
,
311 requestor_url
, &services
, &exposed_services
,
312 filter
, connect_callback
)) {
317 requested_url
.scheme() == "mojo" ? requested_url
: fetcher
->GetURL();
319 ApplicationInstance
* app
= nullptr;
320 InterfaceRequest
<Application
> request(
321 RegisterInstance(originator
, app_url
, qualifier
, requestor_url
,
322 services
.Pass(), exposed_services
.Pass(), filter
,
323 on_application_end
, EmptyConnectCallback(), &app
));
325 // For resources that are loaded with content handlers, we group app instances
328 // If the response begins with a #!mojo <content-handler-url>, use it.
329 GURL content_handler_url
;
331 bool enable_multi_process
= base::CommandLine::ForCurrentProcess()->HasSwitch(
332 switches::kEnableMultiprocess
);
334 if (fetcher
->PeekContentHandler(&shebang
, &content_handler_url
)) {
335 URLResponsePtr
response(fetcher
->AsURLResponse(
336 blocking_pool_
, static_cast<int>(shebang
.size())));
338 enable_multi_process
? response
->site
.To
<std::string
>() : std::string();
339 LoadWithContentHandler(originator
, content_handler_url
, requestor_url
, site
,
340 filter
, connect_callback
, app
, request
.Pass(),
345 MimeTypeToURLMap::iterator iter
= mime_type_to_url_
.find(fetcher
->MimeType());
346 if (iter
!= mime_type_to_url_
.end()) {
347 URLResponsePtr
response(fetcher
->AsURLResponse(blocking_pool_
, 0));
349 enable_multi_process
? response
->site
.To
<std::string
>() : std::string();
350 LoadWithContentHandler(originator
, iter
->second
, requestor_url
, site
,
351 filter
, connect_callback
, app
, request
.Pass(),
356 auto alias_iter
= application_package_alias_
.find(app_url
);
357 if (alias_iter
!= application_package_alias_
.end()) {
358 // We replace the qualifier with the one our package alias requested.
359 URLResponsePtr
response(URLResponse::New());
360 response
->url
= String::From(app_url
.spec());
362 std::string qualifier
;
363 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
364 switches::kEnableMultiprocess
)) {
365 // Why can't we use this in single process mode? Because of
366 // base::AtExitManager. If you link in ApplicationRunner into
367 // your code, and then we make initialize multiple copies of the
368 // application, we end up with multiple AtExitManagers and will check on
369 // the second one being created.
371 // Why doesn't that happen when running different apps? Because
372 // your_thing.mojo!base::AtExitManager and
373 // my_thing.mojo!base::AtExitManager are different symbols.
374 qualifier
= alias_iter
->second
.second
;
377 LoadWithContentHandler(originator
, alias_iter
->second
.first
, requestor_url
,
378 qualifier
, filter
, connect_callback
, app
,
379 request
.Pass(), response
.Pass());
383 // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo
384 // application. That could either mean looking for the platform-specific dll
385 // header, or looking for some specific mojo signature prepended to the
387 // TODO(vtl): (Maybe this should be done by the factory/runner?)
389 GURL base_resolved_url
= GetBaseURLAndQuery(fetcher
->GetURL(), nullptr);
390 NativeRunnerFactory::Options options
;
391 if (url_to_native_options_
.find(base_resolved_url
) !=
392 url_to_native_options_
.end()) {
393 DVLOG(2) << "Applying stored native options to resolved URL "
394 << fetcher
->GetURL();
395 options
= url_to_native_options_
[base_resolved_url
];
398 // TODO(erg): Have a better way of switching the sandbox on. For now, switch
399 // it on hard coded when we're using some of the sandboxable core services.
400 bool start_sandboxed
= false;
401 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
402 switches::kMojoNoSandbox
)) {
403 if (app_url
== GURL("mojo://core_services/") && qualifier
== "Core")
404 start_sandboxed
= true;
405 else if (app_url
== GURL("mojo://html_viewer/"))
406 start_sandboxed
= true;
409 connect_callback
.Run(Shell::kInvalidContentHandlerID
);
411 fetcher
->AsPath(blocking_pool_
,
412 base::Bind(&ApplicationManager::RunNativeApplication
,
413 weak_ptr_factory_
.GetWeakPtr(),
414 base::Passed(request
.Pass()), start_sandboxed
,
415 options
, cleanup
, base::Passed(fetcher
.Pass())));
418 void ApplicationManager::RunNativeApplication(
419 InterfaceRequest
<Application
> application_request
,
420 bool start_sandboxed
,
421 const NativeRunnerFactory::Options
& options
,
422 NativeApplicationCleanup cleanup
,
423 scoped_ptr
<Fetcher
> fetcher
,
424 const base::FilePath
& path
,
426 // We only passed fetcher to keep it alive. Done with it now.
429 DCHECK(application_request
.is_pending());
432 LOG(ERROR
) << "Library not started because library path '" << path
.value()
433 << "' does not exist.";
437 TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path",
438 path
.AsUTF8Unsafe());
439 NativeRunner
* runner
= native_runner_factory_
->Create(options
).release();
440 native_runners_
.push_back(runner
);
441 runner
->Start(path
, start_sandboxed
, cleanup
, application_request
.Pass(),
442 base::Bind(&ApplicationManager::CleanupRunner
,
443 weak_ptr_factory_
.GetWeakPtr(), runner
));
446 void ApplicationManager::RegisterContentHandler(
447 const std::string
& mime_type
,
448 const GURL
& content_handler_url
) {
449 DCHECK(content_handler_url
.is_valid())
450 << "Content handler URL is invalid for mime type " << mime_type
;
451 mime_type_to_url_
[mime_type
] = content_handler_url
;
454 void ApplicationManager::RegisterApplicationPackageAlias(
456 const GURL
& content_handler_package
,
457 const std::string
& qualifier
) {
458 application_package_alias_
[alias
] =
459 std::make_pair(content_handler_package
, qualifier
);
462 void ApplicationManager::LoadWithContentHandler(
463 ApplicationInstance
* originator
,
464 const GURL
& content_handler_url
,
465 const GURL
& requestor_url
,
466 const std::string
& qualifier
,
467 const CapabilityFilter
& filter
,
468 const Shell::ConnectToApplicationCallback
& connect_callback
,
469 ApplicationInstance
* app
,
470 InterfaceRequest
<Application
> application_request
,
471 URLResponsePtr url_response
) {
472 ContentHandlerConnection
* connection
= nullptr;
473 std::pair
<GURL
, std::string
> key(content_handler_url
, qualifier
);
474 // TODO(beng): Figure out the extent to which capability filter should be
475 // factored into handler identity.
476 URLToContentHandlerMap::iterator iter
= url_to_content_handler_
.find(key
);
477 if (iter
!= url_to_content_handler_
.end()) {
478 connection
= iter
->second
;
480 connection
= new ContentHandlerConnection(
481 originator
, this, content_handler_url
, requestor_url
, qualifier
, filter
,
482 ++content_handler_id_counter_
);
483 url_to_content_handler_
[key
] = connection
;
486 app
->set_requesting_content_handler_id(connection
->id());
487 connection
->content_handler()->StartApplication(application_request
.Pass(),
488 url_response
.Pass());
489 connect_callback
.Run(connection
->id());
492 void ApplicationManager::SetLoaderForURL(scoped_ptr
<ApplicationLoader
> loader
,
494 URLToLoaderMap::iterator it
= url_to_loader_
.find(url
);
495 if (it
!= url_to_loader_
.end())
497 url_to_loader_
[url
] = loader
.release();
500 void ApplicationManager::SetLoaderForScheme(
501 scoped_ptr
<ApplicationLoader
> loader
,
502 const std::string
& scheme
) {
503 SchemeToLoaderMap::iterator it
= scheme_to_loader_
.find(scheme
);
504 if (it
!= scheme_to_loader_
.end())
506 scheme_to_loader_
[scheme
] = loader
.release();
509 void ApplicationManager::SetNativeOptionsForURL(
510 const NativeRunnerFactory::Options
& options
,
512 DCHECK(!url
.has_query()); // Precondition.
513 // Apply mappings and resolution to get the resolved URL.
515 delegate_
->ResolveMojoURL(delegate_
->ResolveMappings(url
));
516 DCHECK(!resolved_url
.has_query()); // Still shouldn't have query.
517 // TODO(vtl): We should probably also remove/disregard the query string (and
518 // maybe canonicalize in other ways).
519 DVLOG(2) << "Storing native options for resolved URL " << resolved_url
520 << " (original URL " << url
<< ")";
521 url_to_native_options_
[resolved_url
] = options
;
524 ApplicationLoader
* ApplicationManager::GetLoaderForURL(const GURL
& url
) {
525 auto url_it
= url_to_loader_
.find(GetBaseURLAndQuery(url
, nullptr));
526 if (url_it
!= url_to_loader_
.end())
527 return url_it
->second
;
528 auto scheme_it
= scheme_to_loader_
.find(url
.scheme());
529 if (scheme_it
!= scheme_to_loader_
.end())
530 return scheme_it
->second
;
534 void ApplicationManager::OnApplicationInstanceError(
535 ApplicationInstance
* instance
) {
536 // Called from ~ApplicationInstance, so we do not need to call Destroy here.
537 const Identity identity
= instance
->identity();
538 base::Closure on_application_end
= instance
->on_application_end();
540 auto it
= identity_to_instance_
.find(identity
);
541 DCHECK(it
!= identity_to_instance_
.end());
543 identity_to_instance_
.erase(it
);
544 if (!on_application_end
.is_null())
545 on_application_end
.Run();
548 void ApplicationManager::OnContentHandlerConnectionClosed(
549 ContentHandlerConnection
* content_handler
) {
550 // Remove the mapping to the content handler.
551 auto it
= url_to_content_handler_
.find(
552 std::make_pair(content_handler
->content_handler_url(),
553 content_handler
->content_handler_qualifier()));
554 DCHECK(it
!= url_to_content_handler_
.end());
555 url_to_content_handler_
.erase(it
);
558 void ApplicationManager::CleanupRunner(NativeRunner
* runner
) {
559 native_runners_
.erase(
560 std::find(native_runners_
.begin(), native_runners_
.end(), runner
));
563 ScopedMessagePipeHandle
ApplicationManager::ConnectToServiceByName(
564 const GURL
& application_url
,
565 const std::string
& interface_name
) {
566 ServiceProviderPtr services
;
567 mojo::URLRequestPtr
request(mojo::URLRequest::New());
568 request
->url
= mojo::String::From(application_url
.spec());
569 ConnectToApplication(nullptr, request
.Pass(), std::string(), GURL(),
570 GetProxy(&services
), nullptr,
571 GetPermissiveCapabilityFilter(), base::Closure(),
572 EmptyConnectCallback());
574 services
->ConnectToService(interface_name
, pipe
.handle1
.Pass());
575 return pipe
.handle0
.Pass();
578 Shell::ConnectToApplicationCallback
EmptyConnectCallback() {
579 return base::Bind(&OnEmptyOnConnectCallback
);