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") {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()', () => {
|
||||
|
|
Loading…
Reference in a new issue