feat: add webContents.forcefullyCrashRenderer() to forcefully terminate a renderer process (#25580)
* feat: add webContents.forcefullyCrashRenderer() to forcefully terminate a renderer process * chore: fix up docs and tests
This commit is contained in:
		
					parent
					
						
							
								d64b9c20bd
							
						
					
				
			
			
				commit
				
					
						a189dc779e
					
				
			
		
					 5 changed files with 113 additions and 0 deletions
				
			
		|  | @ -266,6 +266,15 @@ static_library("chrome") { | ||||||
|       ] |       ] | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   sources += [ "//chrome/browser/hang_monitor/hang_crash_dump.h" ] | ||||||
|  |   if (is_mac) { | ||||||
|  |     sources += [ "//chrome/browser/hang_monitor/hang_crash_dump_mac.cc" ] | ||||||
|  |   } else if (is_win) { | ||||||
|  |     sources += [ "//chrome/browser/hang_monitor/hang_crash_dump_win.cc" ] | ||||||
|  |   } else { | ||||||
|  |     sources += [ "//chrome/browser/hang_monitor/hang_crash_dump.cc" ] | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| source_set("plugins") { | source_set("plugins") { | ||||||
|  |  | ||||||
|  | @ -998,6 +998,34 @@ Navigates to the specified offset from the "current entry". | ||||||
| 
 | 
 | ||||||
| Returns `Boolean` - Whether the renderer process has crashed. | Returns `Boolean` - Whether the renderer process has crashed. | ||||||
| 
 | 
 | ||||||
|  | #### `contents.forcefullyCrashRenderer()` | ||||||
|  | 
 | ||||||
|  | Forcefully terminates the renderer process that is currently hosting this | ||||||
|  | `webContents`. This will cause the `render-process-gone` event to be emitted | ||||||
|  | with the `reason=killed || reason=crashed`. Please note that some webContents share renderer | ||||||
|  | processes and therefore calling this method may also crash the host process | ||||||
|  | for other webContents as well. | ||||||
|  | 
 | ||||||
|  | Calling `reload()` immediately after calling this | ||||||
|  | method will force the reload to occur in a new process. This should be used | ||||||
|  | when this process is unstable or unusable, for instance in order to recover | ||||||
|  | from the `unresponsive` event. | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | contents.on('unresponsive', async () => { | ||||||
|  |   const { response } = await dialog.showMessageBox({ | ||||||
|  |     message: 'App X has become unresponsive', | ||||||
|  |     title: 'Do you want to try forcefully reloading the app?', | ||||||
|  |     buttons: ['OK', 'Cancel'], | ||||||
|  |     cancelId: 1 | ||||||
|  |   }) | ||||||
|  |   if (response === 0) { | ||||||
|  |     contents.forcefullyCrashRenderer() | ||||||
|  |     contents.reload() | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| #### `contents.setUserAgent(userAgent)` | #### `contents.setUserAgent(userAgent)` | ||||||
| 
 | 
 | ||||||
| * `userAgent` String | * `userAgent` String | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ | ||||||
| #include "base/threading/thread_task_runner_handle.h" | #include "base/threading/thread_task_runner_handle.h" | ||||||
| #include "base/values.h" | #include "base/values.h" | ||||||
| #include "chrome/browser/browser_process.h" | #include "chrome/browser/browser_process.h" | ||||||
|  | #include "chrome/browser/hang_monitor/hang_crash_dump.h" | ||||||
| #include "chrome/browser/ssl/security_state_tab_helper.h" | #include "chrome/browser/ssl/security_state_tab_helper.h" | ||||||
| #include "content/browser/renderer_host/frame_tree_node.h"  // nogncheck
 | #include "content/browser/renderer_host/frame_tree_node.h"  // nogncheck
 | ||||||
| #include "content/browser/renderer_host/render_frame_host_manager.h"  // nogncheck
 | #include "content/browser/renderer_host/render_frame_host_manager.h"  // nogncheck
 | ||||||
|  | @ -1763,6 +1764,30 @@ bool WebContents::IsCrashed() const { | ||||||
|   return web_contents()->IsCrashed(); |   return web_contents()->IsCrashed(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void WebContents::ForcefullyCrashRenderer() { | ||||||
|  |   content::RenderWidgetHostView* view = | ||||||
|  |       web_contents()->GetRenderWidgetHostView(); | ||||||
|  |   if (!view) | ||||||
|  |     return; | ||||||
|  | 
 | ||||||
|  |   content::RenderWidgetHost* rwh = view->GetRenderWidgetHost(); | ||||||
|  |   if (!rwh) | ||||||
|  |     return; | ||||||
|  | 
 | ||||||
|  |   content::RenderProcessHost* rph = rwh->GetProcess(); | ||||||
|  |   if (rph) { | ||||||
|  | #if defined(OS_LINUX) || defined(OS_CHROMEOS) | ||||||
|  |     // A generic |CrashDumpHungChildProcess()| is not implemented for Linux.
 | ||||||
|  |     // Instead we send an explicit IPC to crash on the renderer's IO thread.
 | ||||||
|  |     rph->ForceCrash(); | ||||||
|  | #else | ||||||
|  |     // Try to generate a crash report for the hung process.
 | ||||||
|  |     CrashDumpHungChildProcess(rph->GetProcess().Handle()); | ||||||
|  |     rph->Shutdown(content::RESULT_CODE_HUNG); | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void WebContents::SetUserAgent(const std::string& user_agent) { | void WebContents::SetUserAgent(const std::string& user_agent) { | ||||||
|   web_contents()->SetUserAgentOverride( |   web_contents()->SetUserAgentOverride( | ||||||
|       blink::UserAgentOverride::UserAgentOnly(user_agent), false); |       blink::UserAgentOverride::UserAgentOnly(user_agent), false); | ||||||
|  | @ -2921,6 +2946,8 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate( | ||||||
|       .SetMethod("_goForward", &WebContents::GoForward) |       .SetMethod("_goForward", &WebContents::GoForward) | ||||||
|       .SetMethod("_goToOffset", &WebContents::GoToOffset) |       .SetMethod("_goToOffset", &WebContents::GoToOffset) | ||||||
|       .SetMethod("isCrashed", &WebContents::IsCrashed) |       .SetMethod("isCrashed", &WebContents::IsCrashed) | ||||||
|  |       .SetMethod("forcefullyCrashRenderer", | ||||||
|  |                  &WebContents::ForcefullyCrashRenderer) | ||||||
|       .SetMethod("setUserAgent", &WebContents::SetUserAgent) |       .SetMethod("setUserAgent", &WebContents::SetUserAgent) | ||||||
|       .SetMethod("getUserAgent", &WebContents::GetUserAgent) |       .SetMethod("getUserAgent", &WebContents::GetUserAgent) | ||||||
|       .SetMethod("savePage", &WebContents::SavePage) |       .SetMethod("savePage", &WebContents::SavePage) | ||||||
|  |  | ||||||
|  | @ -228,6 +228,7 @@ class WebContents : public gin::Wrappable<WebContents>, | ||||||
|   const std::string GetWebRTCIPHandlingPolicy() const; |   const std::string GetWebRTCIPHandlingPolicy() const; | ||||||
|   void SetWebRTCIPHandlingPolicy(const std::string& webrtc_ip_handling_policy); |   void SetWebRTCIPHandlingPolicy(const std::string& webrtc_ip_handling_policy); | ||||||
|   bool IsCrashed() const; |   bool IsCrashed() const; | ||||||
|  |   void ForcefullyCrashRenderer(); | ||||||
|   void SetUserAgent(const std::string& user_agent); |   void SetUserAgent(const std::string& user_agent); | ||||||
|   std::string GetUserAgent(); |   std::string GetUserAgent(); | ||||||
|   void InsertCSS(const std::string& css); |   void InsertCSS(const std::string& css); | ||||||
|  |  | ||||||
|  | @ -1245,6 +1245,54 @@ describe('webContents module', () => { | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   const crashPrefs = [ | ||||||
|  |     { | ||||||
|  |       nodeIntegration: true | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       sandbox: true | ||||||
|  |     } | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   const nicePrefs = (o: any) => { | ||||||
|  |     let s = ''; | ||||||
|  |     for (const key of Object.keys(o)) { | ||||||
|  |       s += `${key}=${o[key]}, `; | ||||||
|  |     } | ||||||
|  |     return `(${s.slice(0, s.length - 2)})`; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   for (const prefs of crashPrefs) { | ||||||
|  |     describe(`crash  with webPreferences ${nicePrefs(prefs)}`, () => { | ||||||
|  |       let w: BrowserWindow; | ||||||
|  |       beforeEach(async () => { | ||||||
|  |         w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); | ||||||
|  |         await w.loadURL('about:blank'); | ||||||
|  |       }); | ||||||
|  |       afterEach(closeAllWindows); | ||||||
|  | 
 | ||||||
|  |       it('isCrashed() is false by default', () => { | ||||||
|  |         expect(w.webContents.isCrashed()).to.equal(false); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('forcefullyCrashRenderer() crashes the process with reason=killed||crashed', async () => { | ||||||
|  |         expect(w.webContents.isCrashed()).to.equal(false); | ||||||
|  |         const crashEvent = emittedOnce(w.webContents, 'render-process-gone'); | ||||||
|  |         w.webContents.forcefullyCrashRenderer(); | ||||||
|  |         const [, details] = await crashEvent; | ||||||
|  |         expect(details.reason === 'killed' || details.reason === 'crashed').to.equal(true, 'reason should be killed || crashed'); | ||||||
|  |         expect(w.webContents.isCrashed()).to.equal(true); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('a crashed process is recoverable with reload()', async () => { | ||||||
|  |         expect(w.webContents.isCrashed()).to.equal(false); | ||||||
|  |         w.webContents.forcefullyCrashRenderer(); | ||||||
|  |         w.webContents.reload(); | ||||||
|  |         expect(w.webContents.isCrashed()).to.equal(false); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   // Destroying webContents in its event listener is going to crash when
 |   // Destroying webContents in its event listener is going to crash when
 | ||||||
|   // Electron is built in Debug mode.
 |   // Electron is built in Debug mode.
 | ||||||
|   describe('destroy()', () => { |   describe('destroy()', () => { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Samuel Attard
				Samuel Attard