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/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/application_manager/fetcher.h"
17 #include "mojo/shell/application_manager/local_fetcher.h"
18 #include "mojo/shell/application_manager/network_fetcher.h"
19 #include "mojo/shell/application_manager/query_util.h"
20 #include "mojo/shell/application_manager/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 ApplicationManager::Delegate::~Delegate() {
37 GURL
ApplicationManager::Delegate::ResolveURL(const GURL
& url
) {
41 GURL
ApplicationManager::Delegate::ResolveMappings(const GURL
& url
) {
45 class ApplicationManager::ContentHandlerConnection
: public ErrorHandler
{
47 ContentHandlerConnection(ApplicationManager
* manager
,
48 const GURL
& content_handler_url
)
49 : manager_(manager
), content_handler_url_(content_handler_url
) {
50 ServiceProviderPtr services
;
51 manager
->ConnectToApplication(content_handler_url
, GURL(),
52 GetProxy(&services
), nullptr,
55 content_handler_
.Bind(pipe
.handle0
.Pass());
56 services
->ConnectToService(ContentHandler::Name_
, pipe
.handle1
.Pass());
57 content_handler_
.set_error_handler(this);
60 ContentHandler
* content_handler() { return content_handler_
.get(); }
62 GURL
content_handler_url() { return content_handler_url_
; }
65 // ErrorHandler implementation:
66 void OnConnectionError() override
{ manager_
->OnContentHandlerError(this); }
68 ApplicationManager
* manager_
;
69 GURL content_handler_url_
;
70 ContentHandlerPtr content_handler_
;
72 DISALLOW_COPY_AND_ASSIGN(ContentHandlerConnection
);
76 ApplicationManager::TestAPI::TestAPI(ApplicationManager
* manager
)
80 ApplicationManager::TestAPI::~TestAPI() {
83 bool ApplicationManager::TestAPI::HasCreatedInstance() {
84 return has_created_instance
;
87 bool ApplicationManager::TestAPI::HasFactoryForURL(const GURL
& url
) const {
88 return manager_
->identity_to_shell_impl_
.find(Identity(url
)) !=
89 manager_
->identity_to_shell_impl_
.end();
92 ApplicationManager::ApplicationManager(Delegate
* delegate
)
93 : delegate_(delegate
), weak_ptr_factory_(this) {
96 ApplicationManager::~ApplicationManager() {
97 STLDeleteValues(&url_to_content_handler_
);
98 TerminateShellConnections();
99 STLDeleteValues(&url_to_loader_
);
100 STLDeleteValues(&scheme_to_loader_
);
103 void ApplicationManager::TerminateShellConnections() {
104 STLDeleteValues(&identity_to_shell_impl_
);
107 void ApplicationManager::ConnectToApplication(
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 ConnectToApplicationWithParameters(
114 requested_url
, requestor_url
, services
.Pass(), exposed_services
.Pass(),
115 on_application_end
, std::vector
<std::string
>());
118 void ApplicationManager::ConnectToApplicationWithParameters(
119 const GURL
& requested_url
,
120 const GURL
& requestor_url
,
121 InterfaceRequest
<ServiceProvider
> services
,
122 ServiceProviderPtr exposed_services
,
123 const base::Closure
& on_application_end
,
124 const std::vector
<std::string
>& pre_redirect_parameters
) {
125 TRACE_EVENT_INSTANT1(
126 "mojo_shell", "ApplicationManager::ConnectToApplicationWithParameters",
127 TRACE_EVENT_SCOPE_THREAD
, "requested_url", requested_url
.spec());
128 DCHECK(requested_url
.is_valid());
130 // We check both the mapped and resolved urls for existing shell_impls because
131 // external applications can be registered for the unresolved mojo:foo urls.
133 GURL mapped_url
= delegate_
->ResolveMappings(requested_url
);
134 if (ConnectToRunningApplication(mapped_url
, requestor_url
, &services
,
135 &exposed_services
)) {
139 GURL resolved_url
= delegate_
->ResolveURL(mapped_url
);
140 if (ConnectToRunningApplication(resolved_url
, requestor_url
, &services
,
141 &exposed_services
)) {
145 // The application is not running, let's compute the parameters.
146 if (ConnectToApplicationWithLoader(mapped_url
, requestor_url
, &services
,
147 &exposed_services
, on_application_end
,
148 pre_redirect_parameters
,
149 GetLoaderForURL(mapped_url
))) {
153 if (ConnectToApplicationWithLoader(resolved_url
, requestor_url
, &services
,
154 &exposed_services
, on_application_end
,
155 pre_redirect_parameters
,
156 GetLoaderForURL(resolved_url
))) {
160 if (ConnectToApplicationWithLoader(
161 resolved_url
, requestor_url
, &services
, &exposed_services
,
162 on_application_end
, pre_redirect_parameters
, default_loader_
.get())) {
166 auto callback
= base::Bind(&ApplicationManager::HandleFetchCallback
,
167 weak_ptr_factory_
.GetWeakPtr(), requestor_url
,
168 base::Passed(services
.Pass()),
169 base::Passed(exposed_services
.Pass()),
170 on_application_end
, pre_redirect_parameters
);
172 if (resolved_url
.SchemeIsFile()) {
174 resolved_url
, GetBaseURLAndQuery(resolved_url
, nullptr),
175 base::Bind(callback
, NativeApplicationCleanup::DONT_DELETE
));
179 if (!network_service_
)
180 ConnectToService(GURL("mojo:network_service"), &network_service_
);
182 const NativeApplicationCleanup cleanup
=
183 base::CommandLine::ForCurrentProcess()->HasSwitch(
184 switches::kDontDeleteOnDownload
)
185 ? NativeApplicationCleanup::DONT_DELETE
186 : NativeApplicationCleanup::DELETE
;
188 new NetworkFetcher(disable_cache_
, resolved_url
, network_service_
.get(),
189 base::Bind(callback
, cleanup
));
192 bool ApplicationManager::ConnectToRunningApplication(
193 const GURL
& resolved_url
,
194 const GURL
& requestor_url
,
195 InterfaceRequest
<ServiceProvider
>* services
,
196 ServiceProviderPtr
* exposed_services
) {
197 GURL application_url
= GetBaseURLAndQuery(resolved_url
, nullptr);
198 ShellImpl
* shell_impl
= GetShellImpl(application_url
);
202 ConnectToClient(shell_impl
, resolved_url
, requestor_url
, services
->Pass(),
203 exposed_services
->Pass());
207 bool ApplicationManager::ConnectToApplicationWithLoader(
208 const GURL
& resolved_url
,
209 const GURL
& requestor_url
,
210 InterfaceRequest
<ServiceProvider
>* services
,
211 ServiceProviderPtr
* exposed_services
,
212 const base::Closure
& on_application_end
,
213 const std::vector
<std::string
>& parameters
,
214 ApplicationLoader
* loader
) {
220 RegisterShell(resolved_url
, requestor_url
, services
->Pass(),
221 exposed_services
->Pass(), on_application_end
, parameters
));
225 InterfaceRequest
<Application
> ApplicationManager::RegisterShell(
226 const GURL
& resolved_url
,
227 const GURL
& requestor_url
,
228 InterfaceRequest
<ServiceProvider
> services
,
229 ServiceProviderPtr exposed_services
,
230 const base::Closure
& on_application_end
,
231 const std::vector
<std::string
>& parameters
) {
232 Identity
app_identity(resolved_url
);
234 ApplicationPtr application
;
235 InterfaceRequest
<Application
> application_request
= GetProxy(&application
);
237 new ShellImpl(application
.Pass(), this, app_identity
, on_application_end
);
238 identity_to_shell_impl_
[app_identity
] = shell
;
239 shell
->InitializeApplication(Array
<String
>::From(parameters
));
240 ConnectToClient(shell
, resolved_url
, requestor_url
, services
.Pass(),
241 exposed_services
.Pass());
242 return application_request
.Pass();
245 ShellImpl
* ApplicationManager::GetShellImpl(const GURL
& url
) {
246 const auto& shell_it
= identity_to_shell_impl_
.find(Identity(url
));
247 if (shell_it
!= identity_to_shell_impl_
.end())
248 return shell_it
->second
;
252 void ApplicationManager::ConnectToClient(
253 ShellImpl
* shell_impl
,
254 const GURL
& resolved_url
,
255 const GURL
& requestor_url
,
256 InterfaceRequest
<ServiceProvider
> services
,
257 ServiceProviderPtr exposed_services
) {
258 shell_impl
->ConnectToClient(resolved_url
, requestor_url
, services
.Pass(),
259 exposed_services
.Pass());
262 void ApplicationManager::HandleFetchCallback(
263 const GURL
& requestor_url
,
264 InterfaceRequest
<ServiceProvider
> services
,
265 ServiceProviderPtr exposed_services
,
266 const base::Closure
& on_application_end
,
267 const std::vector
<std::string
>& parameters
,
268 NativeApplicationCleanup cleanup
,
269 scoped_ptr
<Fetcher
> fetcher
) {
271 // Network error. Drop |application_request| to tell requestor.
275 GURL redirect_url
= fetcher
->GetRedirectURL();
276 if (!redirect_url
.is_empty()) {
277 // And around we go again... Whee!
278 ConnectToApplicationWithParameters(redirect_url
, requestor_url
,
279 services
.Pass(), exposed_services
.Pass(),
280 on_application_end
, parameters
);
284 // We already checked if the application was running before we fetched it, but
285 // it might have started while the fetch was outstanding. We don't want to
286 // have two copies of the app running, so check again.
288 // Also, it's possible the original URL was redirected to an app that is
290 if (ConnectToRunningApplication(fetcher
->GetURL(), requestor_url
, &services
,
291 &exposed_services
)) {
295 InterfaceRequest
<Application
> request(
296 RegisterShell(fetcher
->GetURL(), requestor_url
, services
.Pass(),
297 exposed_services
.Pass(), on_application_end
, parameters
));
299 // If the response begins with a #!mojo <content-handler-url>, use it.
300 GURL content_handler_url
;
302 if (fetcher
->PeekContentHandler(&shebang
, &content_handler_url
)) {
303 LoadWithContentHandler(
304 content_handler_url
, request
.Pass(),
305 fetcher
->AsURLResponse(blocking_pool_
,
306 static_cast<int>(shebang
.size())));
310 MimeTypeToURLMap::iterator iter
= mime_type_to_url_
.find(fetcher
->MimeType());
311 if (iter
!= mime_type_to_url_
.end()) {
312 LoadWithContentHandler(iter
->second
, request
.Pass(),
313 fetcher
->AsURLResponse(blocking_pool_
, 0));
317 // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo
318 // application. That could either mean looking for the platform-specific dll
319 // header, or looking for some specific mojo signature prepended to the
321 // TODO(vtl): (Maybe this should be done by the factory/runner?)
323 GURL base_resolved_url
= GetBaseURLAndQuery(fetcher
->GetURL(), nullptr);
324 NativeRunnerFactory::Options options
;
325 if (url_to_native_options_
.find(base_resolved_url
) !=
326 url_to_native_options_
.end()) {
327 DVLOG(2) << "Applying stored native options to resolved URL "
328 << fetcher
->GetURL();
329 options
= url_to_native_options_
[base_resolved_url
];
334 base::Bind(&ApplicationManager::RunNativeApplication
,
335 weak_ptr_factory_
.GetWeakPtr(), base::Passed(request
.Pass()),
336 options
, cleanup
, base::Passed(fetcher
.Pass())));
339 void ApplicationManager::RunNativeApplication(
340 InterfaceRequest
<Application
> application_request
,
341 const NativeRunnerFactory::Options
& options
,
342 NativeApplicationCleanup cleanup
,
343 scoped_ptr
<Fetcher
> fetcher
,
344 const base::FilePath
& path
,
346 // We only passed fetcher to keep it alive. Done with it now.
349 DCHECK(application_request
.is_pending());
352 LOG(ERROR
) << "Library not started because library path '" << path
.value()
353 << "' does not exist.";
357 TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path",
358 path
.AsUTF8Unsafe());
359 NativeRunner
* runner
= native_runner_factory_
->Create(options
).release();
360 native_runners_
.push_back(runner
);
361 runner
->Start(path
, cleanup
, application_request
.Pass(),
362 base::Bind(&ApplicationManager::CleanupRunner
,
363 weak_ptr_factory_
.GetWeakPtr(), runner
));
366 void ApplicationManager::RegisterContentHandler(
367 const std::string
& mime_type
,
368 const GURL
& content_handler_url
) {
369 DCHECK(content_handler_url
.is_valid())
370 << "Content handler URL is invalid for mime type " << mime_type
;
371 mime_type_to_url_
[mime_type
] = content_handler_url
;
374 void ApplicationManager::LoadWithContentHandler(
375 const GURL
& content_handler_url
,
376 InterfaceRequest
<Application
> application_request
,
377 URLResponsePtr url_response
) {
378 ContentHandlerConnection
* connection
= nullptr;
379 URLToContentHandlerMap::iterator iter
=
380 url_to_content_handler_
.find(content_handler_url
);
381 if (iter
!= url_to_content_handler_
.end()) {
382 connection
= iter
->second
;
384 connection
= new ContentHandlerConnection(this, content_handler_url
);
385 url_to_content_handler_
[content_handler_url
] = connection
;
388 connection
->content_handler()->StartApplication(application_request
.Pass(),
389 url_response
.Pass());
392 void ApplicationManager::SetLoaderForURL(scoped_ptr
<ApplicationLoader
> loader
,
394 URLToLoaderMap::iterator it
= url_to_loader_
.find(url
);
395 if (it
!= url_to_loader_
.end())
397 url_to_loader_
[url
] = loader
.release();
400 void ApplicationManager::SetLoaderForScheme(
401 scoped_ptr
<ApplicationLoader
> loader
,
402 const std::string
& scheme
) {
403 SchemeToLoaderMap::iterator it
= scheme_to_loader_
.find(scheme
);
404 if (it
!= scheme_to_loader_
.end())
406 scheme_to_loader_
[scheme
] = loader
.release();
409 void ApplicationManager::SetNativeOptionsForURL(
410 const NativeRunnerFactory::Options
& options
,
412 DCHECK(!url
.has_query()); // Precondition.
413 // Apply mappings and resolution to get the resolved URL.
414 GURL resolved_url
= delegate_
->ResolveURL(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
));