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:
Samuel Attard 2020-10-02 14:50:24 -07:00 committed by GitHub
parent d64b9c20bd
commit a189dc779e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 0 deletions

View file

@ -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") {

View file

@ -998,6 +998,34 @@ Navigates to the specified offset from the "current entry".
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)`
* `userAgent` String

View file

@ -21,6 +21,7 @@
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.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 "content/browser/renderer_host/frame_tree_node.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();
}
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) {
web_contents()->SetUserAgentOverride(
blink::UserAgentOverride::UserAgentOnly(user_agent), false);
@ -2921,6 +2946,8 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
.SetMethod("_goForward", &WebContents::GoForward)
.SetMethod("_goToOffset", &WebContents::GoToOffset)
.SetMethod("isCrashed", &WebContents::IsCrashed)
.SetMethod("forcefullyCrashRenderer",
&WebContents::ForcefullyCrashRenderer)
.SetMethod("setUserAgent", &WebContents::SetUserAgent)
.SetMethod("getUserAgent", &WebContents::GetUserAgent)
.SetMethod("savePage", &WebContents::SavePage)

View file

@ -228,6 +228,7 @@ class WebContents : public gin::Wrappable<WebContents>,
const std::string GetWebRTCIPHandlingPolicy() const;
void SetWebRTCIPHandlingPolicy(const std::string& webrtc_ip_handling_policy);
bool IsCrashed() const;
void ForcefullyCrashRenderer();
void SetUserAgent(const std::string& user_agent);
std::string GetUserAgent();
void InsertCSS(const std::string& css);

View file

@ -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
// Electron is built in Debug mode.
describe('destroy()', () => {