fix: abort more descriptively for beforeunload (#49011)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
parent
53819a8a2a
commit
aeb5af803f
5 changed files with 46 additions and 3 deletions
|
|
@ -1252,7 +1252,8 @@ Captures a snapshot of the page within `rect`. Omitting `rect` will capture the
|
||||||
|
|
||||||
Returns `Promise<void>` - the promise will resolve when the page has finished loading
|
Returns `Promise<void>` - the promise will resolve when the page has finished loading
|
||||||
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
|
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
|
||||||
if the page fails to load (see [`did-fail-load`](web-contents.md#event-did-fail-load)).
|
if the page fails to load (see
|
||||||
|
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors. If the existing page has a beforeUnload handler, [`did-fail-load`](web-contents.md#event-did-fail-load) will be called unless [`will-prevent-unload`](web-contents.md#event-did-fail-load) is handled.
|
||||||
|
|
||||||
Same as [`webContents.loadURL(url[, options])`](web-contents.md#contentsloadurlurl-options).
|
Same as [`webContents.loadURL(url[, options])`](web-contents.md#contentsloadurlurl-options).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1079,7 +1079,7 @@ Emitted when the [mainFrame](web-contents.md#contentsmainframe-readonly), an `<i
|
||||||
Returns `Promise<void>` - the promise will resolve when the page has finished loading
|
Returns `Promise<void>` - the promise will resolve when the page has finished loading
|
||||||
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
|
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
|
||||||
if the page fails to load (see
|
if the page fails to load (see
|
||||||
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors.
|
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors. If the existing page has a beforeUnload handler, [`did-fail-load`](web-contents.md#event-did-fail-load) will be called unless [`will-prevent-unload`](web-contents.md#event-did-fail-load) is handled.
|
||||||
|
|
||||||
Loads the `url` in the window. The `url` must contain the protocol prefix,
|
Loads the `url` in the window. The `url` must contain the protocol prefix,
|
||||||
e.g. the `http://` or `file://`. If the load should bypass http cache then
|
e.g. the `http://` or `file://`. If the load should bypass http cache then
|
||||||
|
|
|
||||||
|
|
@ -2395,6 +2395,9 @@ void WebContents::LoadURL(const GURL& url,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (web_contents()->NeedToFireBeforeUnloadOrUnloadEvents())
|
||||||
|
pending_unload_url_ = url;
|
||||||
|
|
||||||
// Discard non-committed entries to ensure we don't re-use a pending entry.
|
// Discard non-committed entries to ensure we don't re-use a pending entry.
|
||||||
web_contents()->GetController().DiscardNonCommittedEntries();
|
web_contents()->GetController().DiscardNonCommittedEntries();
|
||||||
web_contents()->GetController().LoadURLWithParams(params);
|
web_contents()->GetController().LoadURLWithParams(params);
|
||||||
|
|
@ -3897,8 +3900,15 @@ void WebContents::RunBeforeUnloadDialog(content::WebContents* web_contents,
|
||||||
content::RenderFrameHost* rfh,
|
content::RenderFrameHost* rfh,
|
||||||
bool is_reload,
|
bool is_reload,
|
||||||
DialogClosedCallback callback) {
|
DialogClosedCallback callback) {
|
||||||
// TODO: asyncify?
|
|
||||||
bool default_prevented = Emit("will-prevent-unload");
|
bool default_prevented = Emit("will-prevent-unload");
|
||||||
|
|
||||||
|
if (pending_unload_url_.has_value() && !default_prevented) {
|
||||||
|
Emit("did-fail-load", static_cast<int>(net::ERR_ABORTED),
|
||||||
|
net::ErrorToShortString(net::ERR_ABORTED),
|
||||||
|
pending_unload_url_.value().possibly_invalid_spec(), true);
|
||||||
|
pending_unload_url_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
std::move(callback).Run(default_prevented, std::u16string());
|
std::move(callback).Run(default_prevented, std::u16string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -844,6 +844,8 @@ class WebContents final : public ExclusiveAccessContext,
|
||||||
// that field to ensure the dtor destroys them in the right order.
|
// that field to ensure the dtor destroys them in the right order.
|
||||||
raw_ptr<WebContentsZoomController> zoom_controller_ = nullptr;
|
raw_ptr<WebContentsZoomController> zoom_controller_ = nullptr;
|
||||||
|
|
||||||
|
std::optional<GURL> pending_unload_url_ = std::nullopt;
|
||||||
|
|
||||||
// Maps url to file path, used by the file requests sent from devtools.
|
// Maps url to file path, used by the file requests sent from devtools.
|
||||||
typedef std::map<std::string, base::FilePath> PathsMap;
|
typedef std::map<std::string, base::FilePath> PathsMap;
|
||||||
PathsMap saved_files_;
|
PathsMap saved_files_;
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ describe('webContents module', () => {
|
||||||
await closeAllWindows();
|
await closeAllWindows();
|
||||||
await cleanupWebContents();
|
await cleanupWebContents();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not emit if beforeunload returns undefined in a BrowserWindow', async () => {
|
it('does not emit if beforeunload returns undefined in a BrowserWindow', async () => {
|
||||||
const w = new BrowserWindow({ show: false });
|
const w = new BrowserWindow({ show: false });
|
||||||
w.webContents.once('will-prevent-unload', () => {
|
w.webContents.once('will-prevent-unload', () => {
|
||||||
|
|
@ -162,6 +163,35 @@ describe('webContents module', () => {
|
||||||
w.close();
|
w.close();
|
||||||
await wait;
|
await wait;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails loading a subsequent page after beforeunload is not prevented', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false });
|
||||||
|
|
||||||
|
const didFailLoad = once(w.webContents, 'did-fail-load');
|
||||||
|
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'beforeunload-false.html'));
|
||||||
|
await w.webContents.executeJavaScript('console.log(\'gesture\')', true);
|
||||||
|
|
||||||
|
w.loadFile(path.join(__dirname, 'fixtures', 'pages', 'a.html'));
|
||||||
|
const [, code, , validatedURL] = await didFailLoad;
|
||||||
|
expect(code).to.equal(-3); // ERR_ABORTED
|
||||||
|
const { href: expectedURL } = url.pathToFileURL(path.join(__dirname, 'fixtures', 'pages', 'a.html'));
|
||||||
|
expect(validatedURL).to.equal(expectedURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows loading a subsequent page after beforeunload is prevented', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false });
|
||||||
|
w.webContents.once('will-prevent-unload', event => event.preventDefault());
|
||||||
|
|
||||||
|
await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'beforeunload-false.html'));
|
||||||
|
await w.webContents.executeJavaScript('console.log(\'gesture\')', true);
|
||||||
|
await w.loadFile(path.join(__dirname, 'fixtures', 'pages', 'a.html'));
|
||||||
|
const pageTitle = await w.webContents.executeJavaScript('document.title');
|
||||||
|
expect(pageTitle).to.equal('test');
|
||||||
|
|
||||||
|
const wait = once(w, 'closed');
|
||||||
|
w.close();
|
||||||
|
await wait;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('webContents.send(channel, args...)', () => {
|
describe('webContents.send(channel, args...)', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue