OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "content/browser/plugin_process_host.h" | |
6 | |
7 #include <stddef.h> | |
8 | |
9 #include <utility> | |
10 #include <vector> | |
11 | |
12 #include "base/base_switches.h" | |
13 #include "base/bind.h" | |
14 #include "base/command_line.h" | |
15 #include "base/files/file_path.h" | |
16 #include "base/lazy_instance.h" | |
17 #include "base/logging.h" | |
18 #include "base/macros.h" | |
19 #include "base/metrics/histogram.h" | |
20 #include "base/strings/string_number_conversions.h" | |
21 #include "base/strings/string_util.h" | |
22 #include "base/strings/utf_string_conversions.h" | |
23 #include "base/synchronization/lock.h" | |
24 #include "build/build_config.h" | |
25 #include "components/tracing/tracing_switches.h" | |
26 #include "content/browser/browser_child_process_host_impl.h" | |
27 #include "content/browser/gpu/gpu_data_manager_impl.h" | |
28 #include "content/browser/loader/resource_message_filter.h" | |
29 #include "content/browser/plugin_service_impl.h" | |
30 #include "content/common/child_process_host_impl.h" | |
31 #include "content/common/plugin_process_messages.h" | |
32 #include "content/common/resource_messages.h" | |
33 #include "content/public/browser/browser_thread.h" | |
34 #include "content/public/browser/content_browser_client.h" | |
35 #include "content/public/browser/notification_types.h" | |
36 #include "content/public/browser/plugin_service.h" | |
37 #include "content/public/browser/resource_context.h" | |
38 #include "content/public/common/content_switches.h" | |
39 #include "content/public/common/process_type.h" | |
40 #include "content/public/common/sandboxed_process_launcher_delegate.h" | |
41 #include "ipc/ipc_switches.h" | |
42 #include "net/url_request/url_request_context_getter.h" | |
43 #include "ui/base/ui_base_switches.h" | |
44 #include "ui/gfx/native_widget_types.h" | |
45 #include "ui/gfx/switches.h" | |
46 #include "ui/gl/gl_switches.h" | |
47 | |
48 #if defined(OS_WIN) | |
49 #include <windows.h> | |
50 #endif | |
51 | |
52 #if defined(OS_MACOSX) | |
53 #include "base/mac/mac_util.h" | |
54 #include "ui/gfx/geometry/rect.h" | |
55 #endif | |
56 | |
57 #if defined(OS_WIN) | |
58 #include "base/win/windows_version.h" | |
59 #include "content/common/plugin_constants_win.h" | |
60 #endif | |
61 | |
62 namespace content { | |
63 | |
64 namespace { | |
65 | |
66 base::LazyInstance<std::map<base::ProcessId, WebPluginInfo> > | |
67 g_process_webplugin_info = LAZY_INSTANCE_INITIALIZER; | |
68 base::LazyInstance<base::Lock>::Leaky | |
69 g_process_webplugin_info_lock = LAZY_INSTANCE_INITIALIZER; | |
70 } | |
71 | |
72 bool PluginProcessHost::GetWebPluginInfoFromPluginPid(base::ProcessId pid, | |
73 WebPluginInfo* info) { | |
74 base::AutoLock lock(g_process_webplugin_info_lock.Get()); | |
75 if (!g_process_webplugin_info.Get().count(pid)) | |
76 return false; | |
77 | |
78 *info = g_process_webplugin_info.Get()[pid]; | |
79 return true; | |
80 } | |
81 | |
82 // NOTE: changes to this class need to be reviewed by the security team. | |
83 class PluginSandboxedProcessLauncherDelegate | |
84 : public SandboxedProcessLauncherDelegate { | |
85 public: | |
86 explicit PluginSandboxedProcessLauncherDelegate(ChildProcessHost* host) | |
87 #if defined(OS_POSIX) | |
88 : ipc_fd_(host->TakeClientFileDescriptor()) | |
89 #endif // OS_POSIX | |
90 {} | |
91 | |
92 ~PluginSandboxedProcessLauncherDelegate() override {} | |
93 | |
94 #if defined(OS_WIN) | |
95 bool ShouldSandbox() override { | |
96 return false; | |
97 } | |
98 | |
99 #elif defined(OS_POSIX) | |
100 base::ScopedFD TakeIpcFd() override { return std::move(ipc_fd_); } | |
101 #endif // OS_WIN | |
102 | |
103 private: | |
104 #if defined(OS_POSIX) | |
105 base::ScopedFD ipc_fd_; | |
106 #endif // OS_POSIX | |
107 | |
108 DISALLOW_COPY_AND_ASSIGN(PluginSandboxedProcessLauncherDelegate); | |
109 }; | |
110 | |
111 PluginProcessHost::PluginProcessHost() | |
112 : pid_(base::kNullProcessId) | |
113 #if defined(OS_MACOSX) | |
114 , plugin_cursor_visible_(true) | |
115 #endif | |
116 { | |
117 process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_PLUGIN, this)); | |
118 } | |
119 | |
120 PluginProcessHost::~PluginProcessHost() { | |
121 #if defined(OS_MACOSX) | |
122 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
123 // If the plugin process crashed but had fullscreen windows open at the time, | |
124 // make sure that the menu bar is visible. | |
125 for (size_t i = 0; i < plugin_fullscreen_windows_set_.size(); ++i) { | |
126 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
127 base::Bind(base::mac::ReleaseFullScreen, | |
128 base::mac::kFullScreenModeHideAll)); | |
129 } | |
130 // If the plugin hid the cursor, reset that. | |
131 if (!plugin_cursor_visible_) { | |
132 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
133 base::Bind(base::mac::SetCursorVisibility, true)); | |
134 } | |
135 #endif | |
136 // Cancel all pending and sent requests. | |
137 CancelRequests(); | |
138 | |
139 { | |
140 base::AutoLock lock(g_process_webplugin_info_lock.Get()); | |
141 g_process_webplugin_info.Get()[pid_] = info_; | |
142 } | |
143 } | |
144 | |
145 bool PluginProcessHost::Send(IPC::Message* message) { | |
146 return process_->Send(message); | |
147 } | |
148 | |
149 bool PluginProcessHost::Init(const WebPluginInfo& info) { | |
150 info_ = info; | |
151 process_->SetName(info_.name); | |
152 | |
153 std::string channel_id = process_->GetHost()->CreateChannel(); | |
154 if (channel_id.empty()) | |
155 return false; | |
156 | |
157 // Build command line for plugin. When we have a plugin launcher, we can't | |
158 // allow "self" on linux and we need the real file path. | |
159 const base::CommandLine& browser_command_line = | |
160 *base::CommandLine::ForCurrentProcess(); | |
161 base::CommandLine::StringType plugin_launcher = | |
162 browser_command_line.GetSwitchValueNative(switches::kPluginLauncher); | |
163 | |
164 #if defined(OS_LINUX) | |
165 int flags = plugin_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF : | |
166 ChildProcessHost::CHILD_NORMAL; | |
167 #else | |
168 int flags = ChildProcessHost::CHILD_NORMAL; | |
169 #endif | |
170 | |
171 base::FilePath exe_path = ChildProcessHost::GetChildPath(flags); | |
172 if (exe_path.empty()) | |
173 return false; | |
174 | |
175 base::CommandLine* cmd_line = new base::CommandLine(exe_path); | |
176 // Put the process type and plugin path first so they're easier to see | |
177 // in process listings using native process management tools. | |
178 cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kPluginProcess); | |
179 cmd_line->AppendSwitchPath(switches::kPluginPath, info.path); | |
180 | |
181 #if defined(OS_WIN) | |
182 if (GetContentClient()->browser()->ShouldUseWindowsPrefetchArgument()) | |
183 cmd_line->AppendArg(switches::kPrefetchArgumentOther); | |
184 #endif // defined(OS_WIN) | |
185 | |
186 // Propagate the following switches to the plugin command line (along with | |
187 // any associated values) if present in the browser command line | |
188 static const char* const kSwitchNames[] = { | |
189 switches::kDisableBreakpad, | |
190 switches::kDisableDirectNPAPIRequests, | |
191 switches::kEnableStatsTable, | |
192 switches::kFullMemoryCrashReport, | |
193 switches::kLoggingLevel, | |
194 switches::kLogPluginMessages, | |
195 switches::kNoSandbox, | |
196 switches::kPluginStartupDialog, | |
197 switches::kTraceConfigFile, | |
198 switches::kTraceStartup, | |
199 switches::kUseGL, | |
200 switches::kForceDeviceScaleFactor, | |
201 #if defined(OS_MACOSX) | |
202 switches::kDisableCoreAnimationPlugins, | |
203 switches::kEnableSandboxLogging, | |
204 #endif | |
205 }; | |
206 | |
207 cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames, | |
208 arraysize(kSwitchNames)); | |
209 | |
210 GpuDataManagerImpl::GetInstance()->AppendPluginCommandLine(cmd_line); | |
211 | |
212 // If specified, prepend a launcher program to the command line. | |
213 if (!plugin_launcher.empty()) | |
214 cmd_line->PrependWrapper(plugin_launcher); | |
215 | |
216 std::string locale = GetContentClient()->browser()->GetApplicationLocale(); | |
217 if (!locale.empty()) { | |
218 // Pass on the locale so the null plugin will use the right language in the | |
219 // prompt to install the desired plugin. | |
220 cmd_line->AppendSwitchASCII(switches::kLang, locale); | |
221 } | |
222 | |
223 cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); | |
224 | |
225 // The plugin needs to be shutdown gracefully, i.e. NP_Shutdown needs to be | |
226 // called on the plugin. The plugin process exits when it receives the | |
227 // OnChannelError notification indicating that the browser plugin channel has | |
228 // been destroyed. | |
229 bool terminate_on_shutdown = false; | |
230 process_->Launch( | |
231 new PluginSandboxedProcessLauncherDelegate(process_->GetHost()), | |
232 cmd_line, | |
233 terminate_on_shutdown); | |
234 | |
235 ResourceMessageFilter::GetContextsCallback get_contexts_callback( | |
236 base::Bind(&PluginProcessHost::GetContexts, | |
237 base::Unretained(this))); | |
238 | |
239 // TODO(jam): right now we're passing NULL for appcache, blob storage, file | |
240 // system and host zoom level context. If NPAPI plugins actually use this, | |
241 // we'll have to plumb them. | |
242 ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter( | |
243 process_->GetData().id, PROCESS_TYPE_PLUGIN, NULL, NULL, NULL, NULL, NULL, | |
244 get_contexts_callback); | |
245 process_->AddFilter(resource_message_filter); | |
246 return true; | |
247 } | |
248 | |
249 void PluginProcessHost::ForceShutdown() { | |
250 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
251 Send(new PluginProcessMsg_NotifyRenderersOfPendingShutdown()); | |
252 process_->ForceShutdown(); | |
253 } | |
254 | |
255 bool PluginProcessHost::OnMessageReceived(const IPC::Message& msg) { | |
256 bool handled = true; | |
257 IPC_BEGIN_MESSAGE_MAP(PluginProcessHost, msg) | |
258 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ChannelCreated, OnChannelCreated) | |
259 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ChannelDestroyed, | |
260 OnChannelDestroyed) | |
261 #if defined(OS_MACOSX) | |
262 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginShowWindow, | |
263 OnPluginShowWindow) | |
264 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginHideWindow, | |
265 OnPluginHideWindow) | |
266 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSetCursorVisibility, | |
267 OnPluginSetCursorVisibility) | |
268 #endif | |
269 IPC_MESSAGE_UNHANDLED(handled = false) | |
270 IPC_END_MESSAGE_MAP() | |
271 | |
272 return handled; | |
273 } | |
274 | |
275 void PluginProcessHost::OnChannelConnected(int32_t peer_pid) { | |
276 for (size_t i = 0; i < pending_requests_.size(); ++i) { | |
277 RequestPluginChannel(pending_requests_[i]); | |
278 } | |
279 | |
280 pending_requests_.clear(); | |
281 | |
282 pid_ = peer_pid; | |
283 { | |
284 base::AutoLock lock(g_process_webplugin_info_lock.Get()); | |
285 g_process_webplugin_info.Get()[pid_] = info_; | |
286 } | |
287 } | |
288 | |
289 void PluginProcessHost::OnChannelError() { | |
290 CancelRequests(); | |
291 } | |
292 | |
293 bool PluginProcessHost::CanShutdown() { | |
294 return sent_requests_.empty(); | |
295 } | |
296 | |
297 void PluginProcessHost::OnProcessCrashed(int exit_code) { | |
298 PluginServiceImpl::GetInstance()->RegisterPluginCrash(info_.path); | |
299 } | |
300 | |
301 void PluginProcessHost::CancelRequests() { | |
302 for (size_t i = 0; i < pending_requests_.size(); ++i) | |
303 pending_requests_[i]->OnError(); | |
304 pending_requests_.clear(); | |
305 | |
306 while (!sent_requests_.empty()) { | |
307 Client* client = sent_requests_.front(); | |
308 if (client) | |
309 client->OnError(); | |
310 sent_requests_.pop_front(); | |
311 } | |
312 } | |
313 | |
314 void PluginProcessHost::OpenChannelToPlugin(Client* client) { | |
315 BrowserThread::PostTask( | |
316 BrowserThread::UI, FROM_HERE, | |
317 base::Bind(&BrowserChildProcessHostImpl::NotifyProcessInstanceCreated, | |
318 process_->GetData())); | |
319 client->SetPluginInfo(info_); | |
320 if (process_->GetHost()->IsChannelOpening()) { | |
321 // The channel is already in the process of being opened. Put | |
322 // this "open channel" request into a queue of requests that will | |
323 // be run once the channel is open. | |
324 pending_requests_.push_back(client); | |
325 return; | |
326 } | |
327 | |
328 // We already have an open channel, send a request right away to plugin. | |
329 RequestPluginChannel(client); | |
330 } | |
331 | |
332 void PluginProcessHost::CancelPendingRequest(Client* client) { | |
333 std::vector<Client*>::iterator it = pending_requests_.begin(); | |
334 while (it != pending_requests_.end()) { | |
335 if (client == *it) { | |
336 pending_requests_.erase(it); | |
337 return; | |
338 } | |
339 ++it; | |
340 } | |
341 DCHECK(it != pending_requests_.end()); | |
342 } | |
343 | |
344 void PluginProcessHost::CancelSentRequest(Client* client) { | |
345 std::list<Client*>::iterator it = sent_requests_.begin(); | |
346 while (it != sent_requests_.end()) { | |
347 if (client == *it) { | |
348 *it = NULL; | |
349 return; | |
350 } | |
351 ++it; | |
352 } | |
353 DCHECK(it != sent_requests_.end()); | |
354 } | |
355 | |
356 void PluginProcessHost::RequestPluginChannel(Client* client) { | |
357 // We can't send any sync messages from the browser because it might lead to | |
358 // a hang. However this async messages must be answered right away by the | |
359 // plugin process (i.e. unblocks a Send() call like a sync message) otherwise | |
360 // a deadlock can occur if the plugin creation request from the renderer is | |
361 // a result of a sync message by the plugin process. | |
362 PluginProcessMsg_CreateChannel* msg = | |
363 new PluginProcessMsg_CreateChannel( | |
364 client->ID(), | |
365 client->OffTheRecord()); | |
366 msg->set_unblock(true); | |
367 if (Send(msg)) { | |
368 sent_requests_.push_back(client); | |
369 client->OnSentPluginChannelRequest(); | |
370 } else { | |
371 client->OnError(); | |
372 } | |
373 } | |
374 | |
375 void PluginProcessHost::OnChannelCreated( | |
376 const IPC::ChannelHandle& channel_handle) { | |
377 Client* client = sent_requests_.front(); | |
378 | |
379 if (client) { | |
380 if (!resource_context_map_.count(client->ID())) { | |
381 ResourceContextEntry entry; | |
382 entry.ref_count = 0; | |
383 entry.resource_context = client->GetResourceContext(); | |
384 resource_context_map_[client->ID()] = entry; | |
385 } | |
386 resource_context_map_[client->ID()].ref_count++; | |
387 client->OnChannelOpened(channel_handle); | |
388 } | |
389 sent_requests_.pop_front(); | |
390 } | |
391 | |
392 void PluginProcessHost::OnChannelDestroyed(int renderer_id) { | |
393 resource_context_map_[renderer_id].ref_count--; | |
394 if (!resource_context_map_[renderer_id].ref_count) | |
395 resource_context_map_.erase(renderer_id); | |
396 } | |
397 | |
398 void PluginProcessHost::GetContexts(ResourceType resource_type, | |
399 int origin_pid, | |
400 ResourceContext** resource_context, | |
401 net::URLRequestContext** request_context) { | |
402 *resource_context = | |
403 resource_context_map_[origin_pid].resource_context; | |
404 *request_context = (*resource_context)->GetRequestContext(); | |
405 } | |
406 | |
407 } // namespace content | |
OLD | NEW |