Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / mojo / shell / application_manager.cc
blob0ef4345b49d5e8bb23e9498f17a052cd6971c012
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"
7 #include "base/bind.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"
25 namespace mojo {
26 namespace shell {
28 namespace {
30 // Used by TestAPI.
31 bool has_created_instance = false;
33 void OnEmptyOnConnectCallback(uint32_t content_handler_id) {}
35 } // namespace
37 // static
38 ApplicationManager::TestAPI::TestAPI(ApplicationManager* manager)
39 : manager_(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(&params))
113 return;
115 GURL resolved_url = delegate_->ResolveMojoURL(mapped_url);
116 params->SetURLInfo(resolved_url);
117 if (ConnectToRunningApplication(&params))
118 return;
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(&params, mapped_url,
125 GetLoaderForURL(mapped_url))) {
126 return;
129 if (ConnectToApplicationWithLoader(&params, resolved_url,
130 GetLoaderForURL(resolved_url))) {
131 return;
134 if (ConnectToApplicationWithLoader(&params, resolved_url,
135 default_loader_.get())) {
136 return;
139 original_url_request = params->TakeAppURLRequest();
140 auto callback =
141 base::Bind(&ApplicationManager::HandleFetchCallback,
142 weak_ptr_factory_.GetWeakPtr(), base::Passed(&params));
144 if (delegate_->CreateFetcher(
145 resolved_url,
146 base::Bind(callback, NativeApplicationCleanup::DONT_DELETE))) {
147 return;
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_);
155 new LocalFetcher(
156 network_service_.get(), resolved_url,
157 GetBaseURLAndQuery(resolved_url, nullptr),
158 base::Bind(callback, NativeApplicationCleanup::DONT_DELETE));
159 return;
162 if (mapped_url.SchemeIs("mojo") &&
163 base::CommandLine::ForCurrentProcess()->HasSwitch(
164 switches::kUseUpdater)) {
165 ConnectToService(GURL("mojo:updater"), &updater_);
166 new UpdateFetcher(
167 mapped_url, updater_.get(),
168 base::Bind(callback, NativeApplicationCleanup::DONT_DELETE));
169 return;
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));
188 return;
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()));
199 if (!instance)
200 return false;
202 instance->ConnectToClient(params->Pass());
203 return true;
206 bool ApplicationManager::ConnectToApplicationWithLoader(
207 scoped_ptr<ConnectToApplicationParams>* params,
208 const GURL& resolved_url,
209 ApplicationLoader* loader) {
210 if (!loader)
211 return false;
213 if (!(*params)->app_url().SchemeIs("mojo"))
214 (*params)->SetURLInfo(resolved_url);
216 loader->Load(resolved_url, RegisterInstance(params->Pass(), nullptr));
217 return true;
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;
246 return nullptr;
249 void ApplicationManager::HandleFetchCallback(
250 scoped_ptr<ConnectToApplicationParams> params,
251 NativeApplicationCleanup cleanup,
252 scoped_ptr<Fetcher> fetcher) {
253 if (!fetcher) {
254 // Network error. Drop |params| to tell the requestor.
255 params->connect_callback().Run(Shell::kInvalidContentHandlerID);
256 return;
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());
271 return;
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
279 // already running.
280 if (ConnectToRunningApplication(&params))
281 return;
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
298 // by site.
300 // If the response begins with a #!mojo <content-handler-url>, use it.
301 GURL content_handler_url;
302 std::string shebang;
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())));
309 std::string site =
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());
314 return;
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));
320 std::string site =
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(),
324 response.Pass());
325 return;
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(),
352 response.Pass());
353 return;
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
359 // library.
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,
398 bool path_exists) {
399 // We only passed fetcher to keep it alive. Done with it now.
400 fetcher.reset();
402 DCHECK(application_request.is_pending());
404 if (!path_exists) {
405 LOG(ERROR) << "Library not started because library path '" << path.value()
406 << "' does not exist.";
407 return;
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(
428 const GURL& alias,
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;
452 } else {
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,
466 const GURL& url) {
467 URLToLoaderMap::iterator it = url_to_loader_.find(url);
468 if (it != url_to_loader_.end())
469 delete it->second;
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())
478 delete it->second;
479 scheme_to_loader_[scheme] = loader.release();
482 void ApplicationManager::SetNativeOptionsForURL(
483 const NativeRunnerFactory::Options& options,
484 const GURL& url) {
485 DCHECK(!url.has_query()); // Precondition.
486 // Apply mappings and resolution to get the resolved URL.
487 GURL 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;
504 return nullptr;
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();
512 // Remove the shell.
513 auto it = identity_to_instance_.find(identity);
514 DCHECK(it != identity_to_instance_.end());
515 delete it->second;
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());
545 MessagePipe pipe;
546 services->ConnectToService(interface_name, pipe.handle1.Pass());
547 return pipe.handle0.Pass();
550 Shell::ConnectToApplicationCallback EmptyConnectCallback() {
551 return base::Bind(&OnEmptyOnConnectCallback);
554 } // namespace shell
555 } // namespace mojo