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 |