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 URLRequestPtr app_url_request
,
77 const std::string
& qualifier
,
78 InterfaceRequest
<ServiceProvider
> services
,
79 ServiceProviderPtr exposed_services
,
80 const CapabilityFilter
& filter
,
81 const base::Closure
& on_application_end
,
82 const Shell::ConnectToApplicationCallback
& connect_callback
) {
83 scoped_ptr
<ConnectToApplicationParams
> params(new ConnectToApplicationParams
);
84 params
->SetOriginatorInfo(originator
);
85 params
->SetURLInfo(app_url_request
.Pass());
86 params
->set_qualifier(qualifier
);
87 params
->set_services(services
.Pass());
88 params
->set_exposed_services(exposed_services
.Pass());
89 params
->set_filter(filter
);
90 params
->set_on_application_end(on_application_end
);
91 params
->set_connect_callback(connect_callback
);
93 ConnectToApplication(params
.Pass());
96 void ApplicationManager::ConnectToApplication(
97 scoped_ptr
<ConnectToApplicationParams
> params
) {
98 GURL original_url
= params
->app_url();
99 URLRequestPtr original_url_request
= params
->TakeAppURLRequest();
101 TRACE_EVENT_INSTANT1("mojo_shell", "ApplicationManager::ConnectToApplication",
102 TRACE_EVENT_SCOPE_THREAD
, "original_url",
103 original_url
.spec());
104 DCHECK(original_url
.is_valid());
105 DCHECK(original_url_request
);
107 // We check both the mapped and resolved urls for existing instances because
108 // external applications can be registered for the unresolved mojo:foo urls.
110 GURL mapped_url
= delegate_
->ResolveMappings(original_url
);
111 params
->SetURLInfo(mapped_url
);
112 if (ConnectToRunningApplication(¶ms
))
115 GURL resolved_url
= delegate_
->ResolveMojoURL(mapped_url
);
116 params
->SetURLInfo(resolved_url
);
117 if (ConnectToRunningApplication(¶ms
))
120 // The application is not running, let's compute the parameters.
121 // NOTE: Set URL info using |original_url_request| instead of |original_url|
122 // because it may contain more information (e.g., it is a POST request).
123 params
->SetURLInfo(original_url_request
.Pass());
124 if (ConnectToApplicationWithLoader(¶ms
, mapped_url
,
125 GetLoaderForURL(mapped_url
))) {
129 if (ConnectToApplicationWithLoader(¶ms
, resolved_url
,
130 GetLoaderForURL(resolved_url
))) {
134 if (ConnectToApplicationWithLoader(¶ms
, resolved_url
,
135 default_loader_
.get())) {
139 original_url_request
= params
->TakeAppURLRequest();
141 base::Bind(&ApplicationManager::HandleFetchCallback
,
142 weak_ptr_factory_
.GetWeakPtr(), base::Passed(¶ms
));
144 if (delegate_
->CreateFetcher(
146 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
))) {
150 if (resolved_url
.SchemeIsFile()) {
151 // LocalFetcher uses the network service to infer MIME types from URLs.
152 // Skip this for mojo URLs to avoid recursively loading the network service.
153 if (!network_service_
&& !original_url
.SchemeIs("mojo"))
154 ConnectToService(GURL("mojo:network_service"), &network_service_
);
156 network_service_
.get(), resolved_url
,
157 GetBaseURLAndQuery(resolved_url
, nullptr),
158 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
));
162 if (mapped_url
.SchemeIs("mojo") &&
163 base::CommandLine::ForCurrentProcess()->HasSwitch(
164 switches::kUseUpdater
)) {
165 ConnectToService(GURL("mojo:updater"), &updater_
);
167 mapped_url
, updater_
.get(),
168 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
));
172 if (!url_loader_factory_
)
173 ConnectToService(GURL("mojo:network_service"), &url_loader_factory_
);
175 const NativeApplicationCleanup cleanup
=
176 base::CommandLine::ForCurrentProcess()->HasSwitch(
177 switches::kDontDeleteOnDownload
)
178 ? NativeApplicationCleanup::DONT_DELETE
179 : NativeApplicationCleanup::DELETE
;
181 if (original_url
.SchemeIs("mojo")) {
182 // Use the resolved mojo URL in the request to support origin mapping, etc.
183 URLRequestPtr
resolved_url_request(URLRequest::New());
184 resolved_url_request
->url
= resolved_url
.spec();
185 new NetworkFetcher(disable_cache_
, resolved_url_request
.Pass(),
186 url_loader_factory_
.get(),
187 base::Bind(callback
, cleanup
));
191 new NetworkFetcher(disable_cache_
, original_url_request
.Pass(),
192 url_loader_factory_
.get(), base::Bind(callback
, cleanup
));
195 bool ApplicationManager::ConnectToRunningApplication(
196 scoped_ptr
<ConnectToApplicationParams
>* params
) {
197 ApplicationInstance
* instance
= GetApplicationInstance(
198 Identity((*params
)->app_url(), (*params
)->qualifier()));
202 instance
->ConnectToClient(params
->Pass());
206 bool ApplicationManager::ConnectToApplicationWithLoader(
207 scoped_ptr
<ConnectToApplicationParams
>* params
,
208 const GURL
& resolved_url
,
209 ApplicationLoader
* loader
) {
213 if (!(*params
)->app_url().SchemeIs("mojo"))
214 (*params
)->SetURLInfo(resolved_url
);
216 loader
->Load(resolved_url
, RegisterInstance(params
->Pass(), nullptr));
220 InterfaceRequest
<Application
> ApplicationManager::RegisterInstance(
221 scoped_ptr
<ConnectToApplicationParams
> params
,
222 ApplicationInstance
** resulting_instance
) {
223 Identity
app_identity(params
->app_url(), params
->qualifier());
225 ApplicationPtr application
;
226 InterfaceRequest
<Application
> application_request
= GetProxy(&application
);
227 ApplicationInstance
* instance
= new ApplicationInstance(
228 application
.Pass(), this, params
->originator_identity(), app_identity
,
229 params
->filter(), Shell::kInvalidContentHandlerID
,
230 params
->on_application_end());
231 DCHECK(identity_to_instance_
.find(app_identity
) ==
232 identity_to_instance_
.end());
233 identity_to_instance_
[app_identity
] = instance
;
234 instance
->InitializeApplication();
235 instance
->ConnectToClient(params
.Pass());
236 if (resulting_instance
)
237 *resulting_instance
= instance
;
238 return application_request
.Pass();
241 ApplicationInstance
* ApplicationManager::GetApplicationInstance(
242 const Identity
& identity
) const {
243 const auto& instance_it
= identity_to_instance_
.find(identity
);
244 if (instance_it
!= identity_to_instance_
.end())
245 return instance_it
->second
;
249 void ApplicationManager::HandleFetchCallback(
250 scoped_ptr
<ConnectToApplicationParams
> params
,
251 NativeApplicationCleanup cleanup
,
252 scoped_ptr
<Fetcher
> fetcher
) {
254 // Network error. Drop |params| to tell the requestor.
255 params
->connect_callback().Run(Shell::kInvalidContentHandlerID
);
259 GURL redirect_url
= fetcher
->GetRedirectURL();
260 if (!redirect_url
.is_empty()) {
261 // And around we go again... Whee!
262 // TODO(sky): this loses the original URL info.
263 URLRequestPtr new_request
= URLRequest::New();
264 new_request
->url
= redirect_url
.spec();
265 HttpHeaderPtr header
= HttpHeader::New();
266 header
->name
= "Referer";
267 header
->value
= fetcher
->GetRedirectReferer().spec();
268 new_request
->headers
.push_back(header
.Pass());
269 params
->SetURLInfo(new_request
.Pass());
270 ConnectToApplication(params
.Pass());
274 // We already checked if the application was running before we fetched it, but
275 // it might have started while the fetch was outstanding. We don't want to
276 // have two copies of the app running, so check again.
278 // Also, it's possible the original URL was redirected to an app that is
280 if (ConnectToRunningApplication(¶ms
))
283 if (params
->app_url().scheme() != "mojo")
284 params
->SetURLInfo(fetcher
->GetURL());
286 Identity originator_identity
= params
->originator_identity();
287 CapabilityFilter originator_filter
= params
->originator_filter();
288 CapabilityFilter filter
= params
->filter();
289 GURL app_url
= params
->app_url();
290 std::string qualifier
= params
->qualifier();
291 Shell::ConnectToApplicationCallback connect_callback
=
292 params
->connect_callback();
293 params
->set_connect_callback(EmptyConnectCallback());
294 ApplicationInstance
* app
= nullptr;
295 InterfaceRequest
<Application
> request(RegisterInstance(params
.Pass(), &app
));
297 // For resources that are loaded with content handlers, we group app instances
300 // If the response begins with a #!mojo <content-handler-url>, use it.
301 GURL content_handler_url
;
303 bool enable_multi_process
= base::CommandLine::ForCurrentProcess()->HasSwitch(
304 switches::kEnableMultiprocess
);
306 if (fetcher
->PeekContentHandler(&shebang
, &content_handler_url
)) {
307 URLResponsePtr
response(fetcher
->AsURLResponse(
308 blocking_pool_
, static_cast<int>(shebang
.size())));
310 enable_multi_process
? response
->site
.To
<std::string
>() : std::string();
311 LoadWithContentHandler(originator_identity
, originator_filter
,
312 content_handler_url
, site
, filter
, connect_callback
,
313 app
, request
.Pass(), response
.Pass());
317 MimeTypeToURLMap::iterator iter
= mime_type_to_url_
.find(fetcher
->MimeType());
318 if (iter
!= mime_type_to_url_
.end()) {
319 URLResponsePtr
response(fetcher
->AsURLResponse(blocking_pool_
, 0));
321 enable_multi_process
? response
->site
.To
<std::string
>() : std::string();
322 LoadWithContentHandler(originator_identity
, originator_filter
, iter
->second
,
323 site
, filter
, connect_callback
, app
, request
.Pass(),
328 auto alias_iter
= application_package_alias_
.find(app_url
);
329 if (alias_iter
!= application_package_alias_
.end()) {
330 // We replace the qualifier with the one our package alias requested.
331 URLResponsePtr
response(URLResponse::New());
332 response
->url
= app_url
.spec();
334 std::string qualifier
;
335 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
336 switches::kEnableMultiprocess
)) {
337 // Why can't we use this in single process mode? Because of
338 // base::AtExitManager. If you link in ApplicationRunner into
339 // your code, and then we make initialize multiple copies of the
340 // application, we end up with multiple AtExitManagers and will check on
341 // the second one being created.
343 // Why doesn't that happen when running different apps? Because
344 // your_thing.mojo!base::AtExitManager and
345 // my_thing.mojo!base::AtExitManager are different symbols.
346 qualifier
= alias_iter
->second
.second
;
349 LoadWithContentHandler(originator_identity
, originator_filter
,
350 alias_iter
->second
.first
, qualifier
, filter
,
351 connect_callback
, app
, request
.Pass(),
356 // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo
357 // application. That could either mean looking for the platform-specific dll
358 // header, or looking for some specific mojo signature prepended to the
360 // TODO(vtl): (Maybe this should be done by the factory/runner?)
362 GURL base_resolved_url
= GetBaseURLAndQuery(fetcher
->GetURL(), nullptr);
363 NativeRunnerFactory::Options options
;
364 if (url_to_native_options_
.find(base_resolved_url
) !=
365 url_to_native_options_
.end()) {
366 DVLOG(2) << "Applying stored native options to resolved URL "
367 << fetcher
->GetURL();
368 options
= url_to_native_options_
[base_resolved_url
];
371 // TODO(erg): Have a better way of switching the sandbox on. For now, switch
372 // it on hard coded when we're using some of the sandboxable core services.
373 bool start_sandboxed
= false;
374 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
375 switches::kMojoNoSandbox
)) {
376 if (app_url
== GURL("mojo://core_services/") && qualifier
== "Core")
377 start_sandboxed
= true;
378 else if (app_url
== GURL("mojo://html_viewer/"))
379 start_sandboxed
= true;
382 connect_callback
.Run(Shell::kInvalidContentHandlerID
);
384 fetcher
->AsPath(blocking_pool_
,
385 base::Bind(&ApplicationManager::RunNativeApplication
,
386 weak_ptr_factory_
.GetWeakPtr(),
387 base::Passed(request
.Pass()), start_sandboxed
,
388 options
, cleanup
, base::Passed(fetcher
.Pass())));
391 void ApplicationManager::RunNativeApplication(
392 InterfaceRequest
<Application
> application_request
,
393 bool start_sandboxed
,
394 const NativeRunnerFactory::Options
& options
,
395 NativeApplicationCleanup cleanup
,
396 scoped_ptr
<Fetcher
> fetcher
,
397 const base::FilePath
& path
,
399 // We only passed fetcher to keep it alive. Done with it now.
402 DCHECK(application_request
.is_pending());
405 LOG(ERROR
) << "Library not started because library path '" << path
.value()
406 << "' does not exist.";
410 TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path",
411 path
.AsUTF8Unsafe());
412 NativeRunner
* runner
= native_runner_factory_
->Create(options
).release();
413 native_runners_
.push_back(runner
);
414 runner
->Start(path
, start_sandboxed
, cleanup
, application_request
.Pass(),
415 base::Bind(&ApplicationManager::CleanupRunner
,
416 weak_ptr_factory_
.GetWeakPtr(), runner
));
419 void ApplicationManager::RegisterContentHandler(
420 const std::string
& mime_type
,
421 const GURL
& content_handler_url
) {
422 DCHECK(content_handler_url
.is_valid())
423 << "Content handler URL is invalid for mime type " << mime_type
;
424 mime_type_to_url_
[mime_type
] = content_handler_url
;
427 void ApplicationManager::RegisterApplicationPackageAlias(
429 const GURL
& content_handler_package
,
430 const std::string
& qualifier
) {
431 application_package_alias_
[alias
] =
432 std::make_pair(content_handler_package
, qualifier
);
435 void ApplicationManager::LoadWithContentHandler(
436 const Identity
& originator_identity
,
437 const CapabilityFilter
& originator_filter
,
438 const GURL
& content_handler_url
,
439 const std::string
& qualifier
,
440 const CapabilityFilter
& filter
,
441 const Shell::ConnectToApplicationCallback
& connect_callback
,
442 ApplicationInstance
* app
,
443 InterfaceRequest
<Application
> application_request
,
444 URLResponsePtr url_response
) {
445 ContentHandlerConnection
* connection
= nullptr;
446 std::pair
<GURL
, std::string
> key(content_handler_url
, qualifier
);
447 // TODO(beng): Figure out the extent to which capability filter should be
448 // factored into handler identity.
449 URLToContentHandlerMap::iterator iter
= url_to_content_handler_
.find(key
);
450 if (iter
!= url_to_content_handler_
.end()) {
451 connection
= iter
->second
;
453 connection
= new ContentHandlerConnection(
454 this, originator_identity
, originator_filter
, content_handler_url
,
455 qualifier
, filter
, ++content_handler_id_counter_
);
456 url_to_content_handler_
[key
] = connection
;
459 app
->set_requesting_content_handler_id(connection
->id());
460 connection
->content_handler()->StartApplication(application_request
.Pass(),
461 url_response
.Pass());
462 connect_callback
.Run(connection
->id());
465 void ApplicationManager::SetLoaderForURL(scoped_ptr
<ApplicationLoader
> loader
,
467 URLToLoaderMap::iterator it
= url_to_loader_
.find(url
);
468 if (it
!= url_to_loader_
.end())
470 url_to_loader_
[url
] = loader
.release();
473 void ApplicationManager::SetLoaderForScheme(
474 scoped_ptr
<ApplicationLoader
> loader
,
475 const std::string
& scheme
) {
476 SchemeToLoaderMap::iterator it
= scheme_to_loader_
.find(scheme
);
477 if (it
!= scheme_to_loader_
.end())
479 scheme_to_loader_
[scheme
] = loader
.release();
482 void ApplicationManager::SetNativeOptionsForURL(
483 const NativeRunnerFactory::Options
& options
,
485 DCHECK(!url
.has_query()); // Precondition.
486 // Apply mappings and resolution to get the resolved URL.
488 delegate_
->ResolveMojoURL(delegate_
->ResolveMappings(url
));
489 DCHECK(!resolved_url
.has_query()); // Still shouldn't have query.
490 // TODO(vtl): We should probably also remove/disregard the query string (and
491 // maybe canonicalize in other ways).
492 DVLOG(2) << "Storing native options for resolved URL " << resolved_url
493 << " (original URL " << url
<< ")";
494 url_to_native_options_
[resolved_url
] = options
;
497 ApplicationLoader
* ApplicationManager::GetLoaderForURL(const GURL
& url
) {
498 auto url_it
= url_to_loader_
.find(GetBaseURLAndQuery(url
, nullptr));
499 if (url_it
!= url_to_loader_
.end())
500 return url_it
->second
;
501 auto scheme_it
= scheme_to_loader_
.find(url
.scheme());
502 if (scheme_it
!= scheme_to_loader_
.end())
503 return scheme_it
->second
;
507 void ApplicationManager::OnApplicationInstanceError(
508 ApplicationInstance
* instance
) {
509 // Called from ~ApplicationInstance, so we do not need to call Destroy here.
510 const Identity identity
= instance
->identity();
511 base::Closure on_application_end
= instance
->on_application_end();
513 auto it
= identity_to_instance_
.find(identity
);
514 DCHECK(it
!= identity_to_instance_
.end());
516 identity_to_instance_
.erase(it
);
517 if (!on_application_end
.is_null())
518 on_application_end
.Run();
521 void ApplicationManager::OnContentHandlerConnectionClosed(
522 ContentHandlerConnection
* content_handler
) {
523 // Remove the mapping to the content handler.
524 auto it
= url_to_content_handler_
.find(
525 std::make_pair(content_handler
->content_handler_url(),
526 content_handler
->content_handler_qualifier()));
527 DCHECK(it
!= url_to_content_handler_
.end());
528 url_to_content_handler_
.erase(it
);
531 void ApplicationManager::CleanupRunner(NativeRunner
* runner
) {
532 native_runners_
.erase(
533 std::find(native_runners_
.begin(), native_runners_
.end(), runner
));
536 ScopedMessagePipeHandle
ApplicationManager::ConnectToServiceByName(
537 const GURL
& application_url
,
538 const std::string
& interface_name
) {
539 ServiceProviderPtr services
;
540 scoped_ptr
<ConnectToApplicationParams
> params(new ConnectToApplicationParams
);
541 params
->SetURLInfo(application_url
);
542 params
->set_services(GetProxy(&services
));
543 params
->set_filter(GetPermissiveCapabilityFilter());
544 ConnectToApplication(params
.Pass());
546 services
->ConnectToService(interface_name
, pipe
.handle1
.Pass());
547 return pipe
.handle0
.Pass();
550 Shell::ConnectToApplicationCallback
EmptyConnectCallback() {
551 return base::Bind(&OnEmptyOnConnectCallback
);