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 #if defined(OS_WIN) 8 #include <windows.h> 9 #elif defined(OS_POSIX) 10 #include <utility> // for pair<> 11 #endif 12 13 #include <vector> 14 15 #include "base/base_switches.h" 16 #include "base/bind.h" 17 #include "base/command_line.h" 18 #include "base/files/file_path.h" 19 #include "base/logging.h" 20 #include "base/metrics/histogram.h" 21 #include "base/path_service.h" 22 #include "base/strings/string_util.h" 23 #include "base/strings/utf_string_conversions.h" 24 #include "content/browser/browser_child_process_host_impl.h" 25 #include "content/browser/gpu/gpu_data_manager_impl.h" 26 #include "content/browser/plugin_service_impl.h" 27 #include "content/common/child_process_host_impl.h" 28 #include "content/common/plugin_process_messages.h" 29 #include "content/common/resource_messages.h" 30 #include "content/public/browser/browser_thread.h" 31 #include "content/public/browser/content_browser_client.h" 32 #include "content/public/browser/notification_types.h" 33 #include "content/public/browser/plugin_service.h" 34 #include "content/public/common/content_switches.h" 35 #include "content/public/common/process_type.h" 36 #include "ipc/ipc_switches.h" 37 #include "ui/base/ui_base_switches.h" 38 #include "ui/gfx/native_widget_types.h" 39 #include "ui/gl/gl_switches.h" 40 41 #if defined(USE_X11) 42 #include "ui/gfx/gtk_native_view_id_manager.h" 43 #endif 44 45 #if defined(OS_MACOSX) 46 #include "base/mac/mac_util.h" 47 #include "content/common/plugin_carbon_interpose_constants_mac.h" 48 #include "ui/gfx/rect.h" 49 #endif 50 51 #if defined(OS_WIN) 52 #include "base/win/windows_version.h" 53 #include "content/common/plugin_constants_win.h" 54 #include "content/public/common/sandboxed_process_launcher_delegate.h" 55 #endif 56 57 namespace content { 58 59 #if defined(OS_WIN) 60 void PluginProcessHost::OnPluginWindowDestroyed(HWND window, HWND parent) { 61 // The window is destroyed at this point, we just care about its parent, which 62 // is the intermediate window we created. 63 std::set<HWND>::iterator window_index = 64 plugin_parent_windows_set_.find(parent); 65 if (window_index == plugin_parent_windows_set_.end()) 66 return; 67 68 plugin_parent_windows_set_.erase(window_index); 69 PostMessage(parent, WM_CLOSE, 0, 0); 70 } 71 72 void PluginProcessHost::AddWindow(HWND window) { 73 plugin_parent_windows_set_.insert(window); 74 } 75 76 // NOTE: changes to this class need to be reviewed by the security team. 77 class PluginSandboxedProcessLauncherDelegate 78 : public SandboxedProcessLauncherDelegate { 79 public: 80 PluginSandboxedProcessLauncherDelegate() {} 81 virtual ~PluginSandboxedProcessLauncherDelegate() {} 82 83 virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE { 84 *in_sandbox = false; 85 } 86 87 private: 88 DISALLOW_COPY_AND_ASSIGN(PluginSandboxedProcessLauncherDelegate); 89 }; 90 91 #endif // defined(OS_WIN) 92 93 #if defined(TOOLKIT_GTK) 94 void PluginProcessHost::OnMapNativeViewId(gfx::NativeViewId id, 95 gfx::PluginWindowHandle* output) { 96 *output = 0; 97 #if !defined(USE_AURA) 98 GtkNativeViewManager::GetInstance()->GetXIDForId(output, id); 99 #endif 100 } 101 #endif // defined(TOOLKIT_GTK) 102 103 PluginProcessHost::PluginProcessHost() 104 #if defined(OS_MACOSX) 105 : plugin_cursor_visible_(true) 106 #endif 107 { 108 process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_PLUGIN, this)); 109 } 110 111 PluginProcessHost::~PluginProcessHost() { 112 #if defined(OS_WIN) 113 // We erase HWNDs from the plugin_parent_windows_set_ when we receive a 114 // notification that the window is being destroyed. If we don't receive this 115 // notification and the PluginProcessHost instance is being destroyed, it 116 // means that the plugin process crashed. We paint a sad face in this case in 117 // the renderer process. To ensure that the sad face shows up, and we don't 118 // leak HWNDs, we should destroy existing plugin parent windows. 119 std::set<HWND>::iterator window_index; 120 for (window_index = plugin_parent_windows_set_.begin(); 121 window_index != plugin_parent_windows_set_.end(); 122 ++window_index) { 123 PostMessage(*window_index, WM_CLOSE, 0, 0); 124 } 125 #elif defined(OS_MACOSX) 126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 127 // If the plugin process crashed but had fullscreen windows open at the time, 128 // make sure that the menu bar is visible. 129 for (size_t i = 0; i < plugin_fullscreen_windows_set_.size(); ++i) { 130 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 131 base::Bind(base::mac::ReleaseFullScreen, 132 base::mac::kFullScreenModeHideAll)); 133 } 134 // If the plugin hid the cursor, reset that. 135 if (!plugin_cursor_visible_) { 136 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 137 base::Bind(base::mac::SetCursorVisibility, true)); 138 } 139 #endif 140 // Cancel all pending and sent requests. 141 CancelRequests(); 142 } 143 144 bool PluginProcessHost::Send(IPC::Message* message) { 145 return process_->Send(message); 146 } 147 148 bool PluginProcessHost::Init(const WebPluginInfo& info) { 149 info_ = info; 150 process_->SetName(info_.name); 151 152 std::string channel_id = process_->GetHost()->CreateChannel(); 153 if (channel_id.empty()) 154 return false; 155 156 // Build command line for plugin. When we have a plugin launcher, we can't 157 // allow "self" on linux and we need the real file path. 158 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); 159 CommandLine::StringType plugin_launcher = 160 browser_command_line.GetSwitchValueNative(switches::kPluginLauncher); 161 162 #if defined(OS_MACOSX) 163 // Run the plug-in process in a mode tolerant of heap execution without 164 // explicit mprotect calls. Some plug-ins still rely on this quaint and 165 // archaic "feature." See http://crbug.com/93551. 166 int flags = ChildProcessHost::CHILD_ALLOW_HEAP_EXECUTION; 167 #elif defined(OS_LINUX) 168 int flags = plugin_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF : 169 ChildProcessHost::CHILD_NORMAL; 170 #else 171 int flags = ChildProcessHost::CHILD_NORMAL; 172 #endif 173 174 base::FilePath exe_path = ChildProcessHost::GetChildPath(flags); 175 if (exe_path.empty()) 176 return false; 177 178 CommandLine* cmd_line = new CommandLine(exe_path); 179 // Put the process type and plugin path first so they're easier to see 180 // in process listings using native process management tools. 181 cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kPluginProcess); 182 cmd_line->AppendSwitchPath(switches::kPluginPath, info.path); 183 184 // Propagate the following switches to the plugin command line (along with 185 // any associated values) if present in the browser command line 186 static const char* const kSwitchNames[] = { 187 switches::kDisableBreakpad, 188 #if defined(OS_MACOSX) 189 switches::kDisableCoreAnimationPlugins, 190 switches::kEnableSandboxLogging, 191 #endif 192 switches::kEnableStatsTable, 193 switches::kFullMemoryCrashReport, 194 switches::kLoggingLevel, 195 switches::kLogPluginMessages, 196 switches::kNoSandbox, 197 switches::kPluginStartupDialog, 198 switches::kTestSandbox, 199 switches::kTraceStartup, 200 switches::kUseGL, 201 switches::kUserAgent, 202 }; 203 204 cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames, 205 arraysize(kSwitchNames)); 206 207 GpuDataManagerImpl::GetInstance()->AppendPluginCommandLine(cmd_line); 208 209 // If specified, prepend a launcher program to the command line. 210 if (!plugin_launcher.empty()) 211 cmd_line->PrependWrapper(plugin_launcher); 212 213 std::string locale = GetContentClient()->browser()->GetApplicationLocale(); 214 if (!locale.empty()) { 215 // Pass on the locale so the null plugin will use the right language in the 216 // prompt to install the desired plugin. 217 cmd_line->AppendSwitchASCII(switches::kLang, locale); 218 } 219 220 cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); 221 222 #if defined(OS_POSIX) 223 base::EnvironmentVector env; 224 #if defined(OS_MACOSX) && !defined(__LP64__) 225 if (!browser_command_line.HasSwitch(switches::kDisableCarbonInterposing)) { 226 std::string interpose_list = GetContentClient()->GetCarbonInterposePath(); 227 if (!interpose_list.empty()) { 228 // Add our interposing library for Carbon. This is stripped back out in 229 // plugin_main.cc, so changes here should be reflected there. 230 const char* existing_list = getenv(kDYLDInsertLibrariesKey); 231 if (existing_list) { 232 interpose_list.insert(0, ":"); 233 interpose_list.insert(0, existing_list); 234 } 235 } 236 env.push_back(std::pair<std::string, std::string>( 237 kDYLDInsertLibrariesKey, interpose_list)); 238 } 239 #endif 240 #endif 241 242 process_->Launch( 243 #if defined(OS_WIN) 244 new PluginSandboxedProcessLauncherDelegate, 245 #elif defined(OS_POSIX) 246 false, 247 env, 248 #endif 249 cmd_line); 250 251 // The plugin needs to be shutdown gracefully, i.e. NP_Shutdown needs to be 252 // called on the plugin. The plugin process exits when it receives the 253 // OnChannelError notification indicating that the browser plugin channel has 254 // been destroyed. 255 process_->SetTerminateChildOnShutdown(false); 256 257 return true; 258 } 259 260 void PluginProcessHost::ForceShutdown() { 261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 262 Send(new PluginProcessMsg_NotifyRenderersOfPendingShutdown()); 263 process_->ForceShutdown(); 264 } 265 266 void PluginProcessHost::AddFilter(IPC::ChannelProxy::MessageFilter* filter) { 267 process_->GetHost()->AddFilter(filter); 268 } 269 270 bool PluginProcessHost::OnMessageReceived(const IPC::Message& msg) { 271 bool handled = true; 272 IPC_BEGIN_MESSAGE_MAP(PluginProcessHost, msg) 273 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ChannelCreated, OnChannelCreated) 274 #if defined(OS_WIN) 275 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginWindowDestroyed, 276 OnPluginWindowDestroyed) 277 #endif 278 #if defined(TOOLKIT_GTK) 279 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_MapNativeViewId, 280 OnMapNativeViewId) 281 #endif 282 #if defined(OS_MACOSX) 283 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSelectWindow, 284 OnPluginSelectWindow) 285 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginShowWindow, 286 OnPluginShowWindow) 287 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginHideWindow, 288 OnPluginHideWindow) 289 IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSetCursorVisibility, 290 OnPluginSetCursorVisibility) 291 #endif 292 IPC_MESSAGE_UNHANDLED(handled = false) 293 IPC_END_MESSAGE_MAP() 294 295 DCHECK(handled); 296 return handled; 297 } 298 299 void PluginProcessHost::OnChannelConnected(int32 peer_pid) { 300 for (size_t i = 0; i < pending_requests_.size(); ++i) { 301 RequestPluginChannel(pending_requests_[i]); 302 } 303 304 pending_requests_.clear(); 305 } 306 307 void PluginProcessHost::OnChannelError() { 308 CancelRequests(); 309 } 310 311 bool PluginProcessHost::CanShutdown() { 312 return sent_requests_.empty(); 313 } 314 315 void PluginProcessHost::OnProcessCrashed(int exit_code) { 316 PluginServiceImpl::GetInstance()->RegisterPluginCrash(info_.path); 317 } 318 319 void PluginProcessHost::CancelRequests() { 320 for (size_t i = 0; i < pending_requests_.size(); ++i) 321 pending_requests_[i]->OnError(); 322 pending_requests_.clear(); 323 324 while (!sent_requests_.empty()) { 325 Client* client = sent_requests_.front(); 326 if (client) 327 client->OnError(); 328 sent_requests_.pop_front(); 329 } 330 } 331 332 // static 333 void PluginProcessHost::CancelPendingRequestsForResourceContext( 334 ResourceContext* context) { 335 for (PluginProcessHostIterator host_it; !host_it.Done(); ++host_it) { 336 PluginProcessHost* host = *host_it; 337 for (size_t i = 0; i < host->pending_requests_.size(); ++i) { 338 if (host->pending_requests_[i]->GetResourceContext() == context) { 339 host->pending_requests_[i]->OnError(); 340 host->pending_requests_.erase(host->pending_requests_.begin() + i); 341 --i; 342 } 343 } 344 } 345 } 346 347 void PluginProcessHost::OpenChannelToPlugin(Client* client) { 348 BrowserThread::PostTask( 349 BrowserThread::UI, FROM_HERE, 350 base::Bind(&BrowserChildProcessHostImpl::NotifyProcessInstanceCreated, 351 process_->GetData())); 352 client->SetPluginInfo(info_); 353 if (process_->GetHost()->IsChannelOpening()) { 354 // The channel is already in the process of being opened. Put 355 // this "open channel" request into a queue of requests that will 356 // be run once the channel is open. 357 pending_requests_.push_back(client); 358 return; 359 } 360 361 // We already have an open channel, send a request right away to plugin. 362 RequestPluginChannel(client); 363 } 364 365 void PluginProcessHost::CancelPendingRequest(Client* client) { 366 std::vector<Client*>::iterator it = pending_requests_.begin(); 367 while (it != pending_requests_.end()) { 368 if (client == *it) { 369 pending_requests_.erase(it); 370 return; 371 } 372 ++it; 373 } 374 DCHECK(it != pending_requests_.end()); 375 } 376 377 void PluginProcessHost::CancelSentRequest(Client* client) { 378 std::list<Client*>::iterator it = sent_requests_.begin(); 379 while (it != sent_requests_.end()) { 380 if (client == *it) { 381 *it = NULL; 382 return; 383 } 384 ++it; 385 } 386 DCHECK(it != sent_requests_.end()); 387 } 388 389 void PluginProcessHost::RequestPluginChannel(Client* client) { 390 // We can't send any sync messages from the browser because it might lead to 391 // a hang. However this async messages must be answered right away by the 392 // plugin process (i.e. unblocks a Send() call like a sync message) otherwise 393 // a deadlock can occur if the plugin creation request from the renderer is 394 // a result of a sync message by the plugin process. 395 PluginProcessMsg_CreateChannel* msg = 396 new PluginProcessMsg_CreateChannel( 397 client->ID(), 398 client->OffTheRecord()); 399 msg->set_unblock(true); 400 if (Send(msg)) { 401 sent_requests_.push_back(client); 402 client->OnSentPluginChannelRequest(); 403 } else { 404 client->OnError(); 405 } 406 } 407 408 void PluginProcessHost::OnChannelCreated( 409 const IPC::ChannelHandle& channel_handle) { 410 Client* client = sent_requests_.front(); 411 412 if (client) 413 client->OnChannelOpened(channel_handle); 414 sent_requests_.pop_front(); 415 } 416 417 } // namespace content 418