| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 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. | |
| 4 | |
| 5 #include "mojo/shell/context.h" | |
| 6 | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/command_line.h" | |
| 11 #include "base/files/file_path.h" | |
| 12 #include "base/lazy_instance.h" | |
| 13 #include "base/macros.h" | |
| 14 #include "base/memory/scoped_ptr.h" | |
| 15 #include "base/memory/scoped_vector.h" | |
| 16 #include "base/path_service.h" | |
| 17 #include "base/run_loop.h" | |
| 18 #include "base/strings/string_split.h" | |
| 19 #include "base/strings/string_util.h" | |
| 20 #include "base/trace_event/trace_event.h" | |
| 21 #include "build/build_config.h" | |
| 22 #include "mojo/common/trace_controller_impl.h" | |
| 23 #include "mojo/common/tracing_impl.h" | |
| 24 #include "mojo/edk/embedder/embedder.h" | |
| 25 #include "mojo/edk/embedder/simple_platform_support.h" | |
| 26 #include "mojo/public/cpp/application/application_connection.h" | |
| 27 #include "mojo/public/cpp/application/application_delegate.h" | |
| 28 #include "mojo/public/cpp/application/application_impl.h" | |
| 29 #include "mojo/services/tracing/tracing.mojom.h" | |
| 30 #include "mojo/shell/application_manager/application_loader.h" | |
| 31 #include "mojo/shell/application_manager/application_manager.h" | |
| 32 #include "mojo/shell/filename_util.h" | |
| 33 #include "mojo/shell/in_process_native_runner.h" | |
| 34 #include "mojo/shell/out_of_process_native_runner.h" | |
| 35 #include "mojo/shell/switches.h" | |
| 36 #include "url/gurl.h" | |
| 37 | |
| 38 namespace mojo { | |
| 39 namespace shell { | |
| 40 namespace { | |
| 41 | |
| 42 // Used to ensure we only init once. | |
| 43 class Setup { | |
| 44 public: | |
| 45 Setup() { | |
| 46 embedder::Init(make_scoped_ptr(new embedder::SimplePlatformSupport())); | |
| 47 } | |
| 48 | |
| 49 ~Setup() {} | |
| 50 | |
| 51 private: | |
| 52 DISALLOW_COPY_AND_ASSIGN(Setup); | |
| 53 }; | |
| 54 | |
| 55 bool ConfigureURLMappings(const base::CommandLine& command_line, | |
| 56 Context* context) { | |
| 57 URLResolver* resolver = context->url_resolver(); | |
| 58 | |
| 59 // Configure the resolution of unknown mojo: URLs. | |
| 60 GURL base_url; | |
| 61 if (command_line.HasSwitch(switches::kOrigin)) | |
| 62 base_url = GURL(command_line.GetSwitchValueASCII(switches::kOrigin)); | |
| 63 else | |
| 64 // Use the shell's file root if the base was not specified. | |
| 65 base_url = context->ResolveShellFileURL(""); | |
| 66 | |
| 67 if (!base_url.is_valid()) | |
| 68 return false; | |
| 69 | |
| 70 resolver->SetMojoBaseURL(base_url); | |
| 71 | |
| 72 // The network service must be loaded from the filesystem. | |
| 73 // This mapping is done before the command line URL mapping are processed, so | |
| 74 // that it can be overridden. | |
| 75 resolver->AddURLMapping( | |
| 76 GURL("mojo:network_service"), | |
| 77 context->ResolveShellFileURL("file:network_service.mojo")); | |
| 78 | |
| 79 // Command line URL mapping. | |
| 80 std::vector<URLResolver::OriginMapping> origin_mappings = | |
| 81 URLResolver::GetOriginMappings(command_line.argv()); | |
| 82 for (const auto& origin_mapping : origin_mappings) | |
| 83 resolver->AddOriginMapping(GURL(origin_mapping.origin), | |
| 84 GURL(origin_mapping.base_url)); | |
| 85 | |
| 86 if (command_line.HasSwitch(switches::kURLMappings)) { | |
| 87 const std::string mappings = | |
| 88 command_line.GetSwitchValueASCII(switches::kURLMappings); | |
| 89 | |
| 90 base::StringPairs pairs; | |
| 91 if (!base::SplitStringIntoKeyValuePairs(mappings, '=', ',', &pairs)) | |
| 92 return false; | |
| 93 using StringPair = std::pair<std::string, std::string>; | |
| 94 for (const StringPair& pair : pairs) { | |
| 95 const GURL from(pair.first); | |
| 96 const GURL to = context->ResolveCommandLineURL(pair.second); | |
| 97 if (!from.is_valid() || !to.is_valid()) | |
| 98 return false; | |
| 99 resolver->AddURLMapping(from, to); | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 return true; | |
| 104 } | |
| 105 | |
| 106 void InitContentHandlers(ApplicationManager* manager, | |
| 107 const base::CommandLine& command_line) { | |
| 108 // Default content handlers. | |
| 109 manager->RegisterContentHandler("application/pdf", GURL("mojo:pdf_viewer")); | |
| 110 manager->RegisterContentHandler("image/png", GURL("mojo:png_viewer")); | |
| 111 manager->RegisterContentHandler("text/html", GURL("mojo:html_viewer")); | |
| 112 | |
| 113 // Command-line-specified content handlers. | |
| 114 std::string handlers_spec = | |
| 115 command_line.GetSwitchValueASCII(switches::kContentHandlers); | |
| 116 if (handlers_spec.empty()) | |
| 117 return; | |
| 118 | |
| 119 #if defined(OS_ANDROID) | |
| 120 // TODO(eseidel): On Android we pass command line arguments is via the | |
| 121 // 'parameters' key on the intent, which we specify during 'am shell start' | |
| 122 // via --esa, however that expects comma-separated values and says: | |
| 123 // am shell --help: | |
| 124 // [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]] | |
| 125 // (to embed a comma into a string escape it using "\,") | |
| 126 // Whatever takes 'parameters' and constructs a CommandLine is failing to | |
| 127 // un-escape the commas, we need to move this fix to that file. | |
| 128 ReplaceSubstringsAfterOffset(&handlers_spec, 0, "\\,", ","); | |
| 129 #endif | |
| 130 | |
| 131 std::vector<std::string> parts; | |
| 132 base::SplitString(handlers_spec, ',', &parts); | |
| 133 if (parts.size() % 2 != 0) { | |
| 134 LOG(ERROR) << "Invalid value for switch " << switches::kContentHandlers | |
| 135 << ": must be a comma-separated list of mimetype/url pairs." | |
| 136 << handlers_spec; | |
| 137 return; | |
| 138 } | |
| 139 | |
| 140 for (size_t i = 0; i < parts.size(); i += 2) { | |
| 141 GURL url(parts[i + 1]); | |
| 142 if (!url.is_valid()) { | |
| 143 LOG(ERROR) << "Invalid value for switch " << switches::kContentHandlers | |
| 144 << ": '" << parts[i + 1] << "' is not a valid URL."; | |
| 145 return; | |
| 146 } | |
| 147 // TODO(eseidel): We should also validate that the mimetype is valid | |
| 148 // net/base/mime_util.h could do this, but we don't want to depend on net. | |
| 149 manager->RegisterContentHandler(parts[i], url); | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 void InitNativeOptions(ApplicationManager* manager, | |
| 154 const base::CommandLine& command_line) { | |
| 155 std::vector<std::string> force_in_process_url_list; | |
| 156 base::SplitString(command_line.GetSwitchValueASCII(switches::kForceInProcess), | |
| 157 ',', &force_in_process_url_list); | |
| 158 for (const auto& force_in_process_url : force_in_process_url_list) { | |
| 159 GURL gurl(force_in_process_url); | |
| 160 if (!gurl.is_valid()) { | |
| 161 LOG(ERROR) << "Invalid value for switch " << switches::kForceInProcess | |
| 162 << ": '" << force_in_process_url << "'is not a valid URL."; | |
| 163 return; | |
| 164 } | |
| 165 | |
| 166 NativeRunnerFactory::Options options; | |
| 167 options.force_in_process = true; | |
| 168 manager->SetNativeOptionsForURL(options, gurl); | |
| 169 } | |
| 170 } | |
| 171 | |
| 172 class TracingServiceProvider : public ServiceProvider { | |
| 173 public: | |
| 174 explicit TracingServiceProvider(InterfaceRequest<ServiceProvider> request) | |
| 175 : binding_(this, request.Pass()) {} | |
| 176 ~TracingServiceProvider() override {} | |
| 177 | |
| 178 void ConnectToService(const String& service_name, | |
| 179 ScopedMessagePipeHandle client_handle) override { | |
| 180 if (service_name == tracing::TraceController::Name_) { | |
| 181 new TraceControllerImpl( | |
| 182 MakeRequest<tracing::TraceController>(client_handle.Pass())); | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 private: | |
| 187 StrongBinding<ServiceProvider> binding_; | |
| 188 | |
| 189 DISALLOW_COPY_AND_ASSIGN(TracingServiceProvider); | |
| 190 }; | |
| 191 | |
| 192 } // namespace | |
| 193 | |
| 194 Context::Context() : application_manager_(this) { | |
| 195 DCHECK(!base::MessageLoop::current()); | |
| 196 | |
| 197 // By default assume that the local apps reside alongside the shell. | |
| 198 // TODO(ncbray): really, this should be passed in rather than defaulting. | |
| 199 // This default makes sense for desktop but not Android. | |
| 200 base::FilePath shell_dir; | |
| 201 PathService::Get(base::DIR_MODULE, &shell_dir); | |
| 202 SetShellFileRoot(shell_dir); | |
| 203 | |
| 204 base::FilePath cwd; | |
| 205 PathService::Get(base::DIR_CURRENT, &cwd); | |
| 206 SetCommandLineCWD(cwd); | |
| 207 } | |
| 208 | |
| 209 Context::~Context() { | |
| 210 DCHECK(!base::MessageLoop::current()); | |
| 211 } | |
| 212 | |
| 213 // static | |
| 214 void Context::EnsureEmbedderIsInitialized() { | |
| 215 static base::LazyInstance<Setup>::Leaky setup = LAZY_INSTANCE_INITIALIZER; | |
| 216 setup.Get(); | |
| 217 } | |
| 218 | |
| 219 void Context::SetShellFileRoot(const base::FilePath& path) { | |
| 220 shell_file_root_ = AddTrailingSlashIfNeeded(FilePathToFileURL(path)); | |
| 221 } | |
| 222 | |
| 223 GURL Context::ResolveShellFileURL(const std::string& path) { | |
| 224 return shell_file_root_.Resolve(path); | |
| 225 } | |
| 226 | |
| 227 void Context::SetCommandLineCWD(const base::FilePath& path) { | |
| 228 command_line_cwd_ = AddTrailingSlashIfNeeded(FilePathToFileURL(path)); | |
| 229 } | |
| 230 | |
| 231 GURL Context::ResolveCommandLineURL(const std::string& path) { | |
| 232 return command_line_cwd_.Resolve(path); | |
| 233 } | |
| 234 | |
| 235 bool Context::Init() { | |
| 236 TRACE_EVENT0("mojo_shell", "Context::Init"); | |
| 237 const base::CommandLine& command_line = | |
| 238 *base::CommandLine::ForCurrentProcess(); | |
| 239 | |
| 240 EnsureEmbedderIsInitialized(); | |
| 241 task_runners_.reset( | |
| 242 new TaskRunners(base::MessageLoop::current()->message_loop_proxy())); | |
| 243 | |
| 244 // TODO(vtl): Probably these failures should be checked before |Init()|, and | |
| 245 // this function simply shouldn't fail. | |
| 246 if (!shell_file_root_.is_valid()) | |
| 247 return false; | |
| 248 if (!ConfigureURLMappings(command_line, this)) | |
| 249 return false; | |
| 250 | |
| 251 // TODO(vtl): This should be MASTER, not NONE. | |
| 252 embedder::InitIPCSupport( | |
| 253 embedder::ProcessType::NONE, task_runners_->shell_runner(), this, | |
| 254 task_runners_->io_runner(), embedder::ScopedPlatformHandle()); | |
| 255 | |
| 256 scoped_ptr<NativeRunnerFactory> runner_factory; | |
| 257 if (command_line.HasSwitch(switches::kEnableMultiprocess)) | |
| 258 runner_factory.reset(new OutOfProcessNativeRunnerFactory(this)); | |
| 259 else | |
| 260 runner_factory.reset(new InProcessNativeRunnerFactory(this)); | |
| 261 application_manager_.set_blocking_pool(task_runners_->blocking_pool()); | |
| 262 application_manager_.set_native_runner_factory(runner_factory.Pass()); | |
| 263 application_manager_.set_disable_cache( | |
| 264 base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 265 switches::kDisableCache)); | |
| 266 | |
| 267 InitContentHandlers(&application_manager_, command_line); | |
| 268 InitNativeOptions(&application_manager_, command_line); | |
| 269 | |
| 270 ServiceProviderPtr tracing_service_provider_ptr; | |
| 271 new TracingServiceProvider(GetProxy(&tracing_service_provider_ptr)); | |
| 272 application_manager_.ConnectToApplication( | |
| 273 GURL("mojo:tracing"), GURL(""), nullptr, | |
| 274 tracing_service_provider_ptr.Pass(), base::Closure()); | |
| 275 | |
| 276 return true; | |
| 277 } | |
| 278 | |
| 279 void Context::Shutdown() { | |
| 280 TRACE_EVENT0("mojo_shell", "Context::Shutdown"); | |
| 281 DCHECK_EQ(base::MessageLoop::current()->task_runner(), | |
| 282 task_runners_->shell_runner()); | |
| 283 embedder::ShutdownIPCSupport(); | |
| 284 // We'll quit when we get OnShutdownComplete(). | |
| 285 base::MessageLoop::current()->Run(); | |
| 286 } | |
| 287 | |
| 288 GURL Context::ResolveMappings(const GURL& url) { | |
| 289 return url_resolver_.ApplyMappings(url); | |
| 290 } | |
| 291 | |
| 292 GURL Context::ResolveMojoURL(const GURL& url) { | |
| 293 return url_resolver_.ResolveMojoURL(url); | |
| 294 } | |
| 295 | |
| 296 void Context::OnShutdownComplete() { | |
| 297 DCHECK_EQ(base::MessageLoop::current()->task_runner(), | |
| 298 task_runners_->shell_runner()); | |
| 299 base::MessageLoop::current()->Quit(); | |
| 300 } | |
| 301 | |
| 302 void Context::Run(const GURL& url) { | |
| 303 ServiceProviderPtr services; | |
| 304 ServiceProviderPtr exposed_services; | |
| 305 | |
| 306 app_urls_.insert(url); | |
| 307 application_manager_.ConnectToApplication( | |
| 308 url, GURL(), GetProxy(&services), exposed_services.Pass(), | |
| 309 base::Bind(&Context::OnApplicationEnd, base::Unretained(this), url)); | |
| 310 } | |
| 311 | |
| 312 void Context::OnApplicationEnd(const GURL& url) { | |
| 313 if (app_urls_.find(url) != app_urls_.end()) { | |
| 314 app_urls_.erase(url); | |
| 315 if (app_urls_.empty() && base::MessageLoop::current()->is_running()) { | |
| 316 DCHECK_EQ(base::MessageLoop::current()->task_runner(), | |
| 317 task_runners_->shell_runner()); | |
| 318 base::MessageLoop::current()->Quit(); | |
| 319 } | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 } // namespace shell | |
| 324 } // namespace mojo | |
| OLD | NEW |