feat: add webContents.close() (#35509)
* feat: add webContents.close() * update docs, add test for beforeunload override * Update web-contents.md
This commit is contained in:
parent
994834d25a
commit
eebf34cc6c
5 changed files with 110 additions and 1 deletions
|
@ -934,6 +934,21 @@ Returns `string` - The title of the current web page.
|
||||||
|
|
||||||
Returns `boolean` - Whether the web page is destroyed.
|
Returns `boolean` - Whether the web page is destroyed.
|
||||||
|
|
||||||
|
#### `contents.close([opts])`
|
||||||
|
|
||||||
|
* `opts` Object (optional)
|
||||||
|
* `waitForBeforeUnload` boolean - if true, fire the `beforeunload` event
|
||||||
|
before closing the page. If the page prevents the unload, the WebContents
|
||||||
|
will not be closed. The [`will-prevent-unload`](#event-will-prevent-unload)
|
||||||
|
will be fired if the page requests prevention of unload.
|
||||||
|
|
||||||
|
Closes the page, as if the web content had called `window.close()`.
|
||||||
|
|
||||||
|
If the page is successfully closed (i.e. the unload is not prevented by the
|
||||||
|
page, or `waitForBeforeUnload` is false or unspecified), the WebContents will
|
||||||
|
be destroyed and no longer usable. The [`destroyed`](#event-destroyed) event
|
||||||
|
will be emitted.
|
||||||
|
|
||||||
#### `contents.focus()`
|
#### `contents.focus()`
|
||||||
|
|
||||||
Focuses the web page.
|
Focuses the web page.
|
||||||
|
|
|
@ -134,6 +134,7 @@ BrowserWindow::~BrowserWindow() {
|
||||||
api_web_contents_->RemoveObserver(this);
|
api_web_contents_->RemoveObserver(this);
|
||||||
// Destroy the WebContents.
|
// Destroy the WebContents.
|
||||||
OnCloseContents();
|
OnCloseContents();
|
||||||
|
api_web_contents_->Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +182,6 @@ void BrowserWindow::WebContentsDestroyed() {
|
||||||
|
|
||||||
void BrowserWindow::OnCloseContents() {
|
void BrowserWindow::OnCloseContents() {
|
||||||
BaseWindow::ResetBrowserViews();
|
BaseWindow::ResetBrowserViews();
|
||||||
api_web_contents_->Destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BrowserWindow::OnRendererResponsive(content::RenderProcessHost*) {
|
void BrowserWindow::OnRendererResponsive(content::RenderProcessHost*) {
|
||||||
|
|
|
@ -1009,6 +1009,19 @@ void WebContents::Destroy() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebContents::Close(absl::optional<gin_helper::Dictionary> options) {
|
||||||
|
bool dispatch_beforeunload = false;
|
||||||
|
if (options)
|
||||||
|
options->Get("waitForBeforeUnload", &dispatch_beforeunload);
|
||||||
|
if (dispatch_beforeunload &&
|
||||||
|
web_contents()->NeedToFireBeforeUnloadOrUnloadEvents()) {
|
||||||
|
NotifyUserActivation();
|
||||||
|
web_contents()->DispatchBeforeUnload(false /* auto_cancel */);
|
||||||
|
} else {
|
||||||
|
web_contents()->Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool WebContents::DidAddMessageToConsole(
|
bool WebContents::DidAddMessageToConsole(
|
||||||
content::WebContents* source,
|
content::WebContents* source,
|
||||||
blink::mojom::ConsoleMessageLevel level,
|
blink::mojom::ConsoleMessageLevel level,
|
||||||
|
@ -1199,6 +1212,8 @@ void WebContents::CloseContents(content::WebContents* source) {
|
||||||
|
|
||||||
for (ExtendedWebContentsObserver& observer : observers_)
|
for (ExtendedWebContentsObserver& observer : observers_)
|
||||||
observer.OnCloseContents();
|
observer.OnCloseContents();
|
||||||
|
|
||||||
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::ActivateContents(content::WebContents* source) {
|
void WebContents::ActivateContents(content::WebContents* source) {
|
||||||
|
@ -3921,6 +3936,7 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
|
||||||
// destroyable.
|
// destroyable.
|
||||||
return gin_helper::ObjectTemplateBuilder(isolate, templ)
|
return gin_helper::ObjectTemplateBuilder(isolate, templ)
|
||||||
.SetMethod("destroy", &WebContents::Destroy)
|
.SetMethod("destroy", &WebContents::Destroy)
|
||||||
|
.SetMethod("close", &WebContents::Close)
|
||||||
.SetMethod("getBackgroundThrottling",
|
.SetMethod("getBackgroundThrottling",
|
||||||
&WebContents::GetBackgroundThrottling)
|
&WebContents::GetBackgroundThrottling)
|
||||||
.SetMethod("setBackgroundThrottling",
|
.SetMethod("setBackgroundThrottling",
|
||||||
|
|
|
@ -152,6 +152,7 @@ class WebContents : public ExclusiveAccessContext,
|
||||||
const char* GetTypeName() override;
|
const char* GetTypeName() override;
|
||||||
|
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
void Close(absl::optional<gin_helper::Dictionary> options);
|
||||||
base::WeakPtr<WebContents> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
|
base::WeakPtr<WebContents> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
|
||||||
|
|
||||||
bool GetBackgroundThrottling() const;
|
bool GetBackgroundThrottling() const;
|
||||||
|
|
|
@ -2133,6 +2133,83 @@ describe('webContents module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('close() method', () => {
|
||||||
|
afterEach(closeAllWindows);
|
||||||
|
|
||||||
|
it('closes when close() is called', async () => {
|
||||||
|
const w = (webContents as any).create() as WebContents;
|
||||||
|
const destroyed = emittedOnce(w, 'destroyed');
|
||||||
|
w.close();
|
||||||
|
await destroyed;
|
||||||
|
expect(w.isDestroyed()).to.be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes when close() is called after loading a page', async () => {
|
||||||
|
const w = (webContents as any).create() as WebContents;
|
||||||
|
await w.loadURL('about:blank');
|
||||||
|
const destroyed = emittedOnce(w, 'destroyed');
|
||||||
|
w.close();
|
||||||
|
await destroyed;
|
||||||
|
expect(w.isDestroyed()).to.be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be GCed before loading a page', async () => {
|
||||||
|
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||||
|
let registry: FinalizationRegistry<unknown> | null = null;
|
||||||
|
const cleanedUp = new Promise<number>(resolve => {
|
||||||
|
registry = new FinalizationRegistry(resolve as any);
|
||||||
|
});
|
||||||
|
(() => {
|
||||||
|
const w = (webContents as any).create() as WebContents;
|
||||||
|
registry!.register(w, 42);
|
||||||
|
})();
|
||||||
|
const i = setInterval(() => v8Util.requestGarbageCollectionForTesting(), 100);
|
||||||
|
defer(() => clearInterval(i));
|
||||||
|
expect(await cleanedUp).to.equal(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('causes its parent browserwindow to be closed', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false });
|
||||||
|
await w.loadURL('about:blank');
|
||||||
|
const closed = emittedOnce(w, 'closed');
|
||||||
|
w.webContents.close();
|
||||||
|
await closed;
|
||||||
|
expect(w.isDestroyed()).to.be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores beforeunload if waitForBeforeUnload not specified', async () => {
|
||||||
|
const w = (webContents as any).create() as WebContents;
|
||||||
|
await w.loadURL('about:blank');
|
||||||
|
await w.executeJavaScript('window.onbeforeunload = () => "hello"; null');
|
||||||
|
w.on('will-prevent-unload', () => { throw new Error('unexpected will-prevent-unload'); });
|
||||||
|
const destroyed = emittedOnce(w, 'destroyed');
|
||||||
|
w.close();
|
||||||
|
await destroyed;
|
||||||
|
expect(w.isDestroyed()).to.be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs beforeunload if waitForBeforeUnload is specified', async () => {
|
||||||
|
const w = (webContents as any).create() as WebContents;
|
||||||
|
await w.loadURL('about:blank');
|
||||||
|
await w.executeJavaScript('window.onbeforeunload = () => "hello"; null');
|
||||||
|
const willPreventUnload = emittedOnce(w, 'will-prevent-unload');
|
||||||
|
w.close({ waitForBeforeUnload: true });
|
||||||
|
await willPreventUnload;
|
||||||
|
expect(w.isDestroyed()).to.be.false();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overriding beforeunload prevention results in webcontents close', async () => {
|
||||||
|
const w = (webContents as any).create() as WebContents;
|
||||||
|
await w.loadURL('about:blank');
|
||||||
|
await w.executeJavaScript('window.onbeforeunload = () => "hello"; null');
|
||||||
|
w.once('will-prevent-unload', e => e.preventDefault());
|
||||||
|
const destroyed = emittedOnce(w, 'destroyed');
|
||||||
|
w.close({ waitForBeforeUnload: true });
|
||||||
|
await destroyed;
|
||||||
|
expect(w.isDestroyed()).to.be.true();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('content-bounds-updated event', () => {
|
describe('content-bounds-updated event', () => {
|
||||||
afterEach(closeAllWindows);
|
afterEach(closeAllWindows);
|
||||||
it('emits when moveTo is called', async () => {
|
it('emits when moveTo is called', async () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue