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…
Reference in a new issue