Skip to content

Commit

Permalink
feat: add webContents.close() (#35509)
Browse files Browse the repository at this point in the history
* feat: add webContents.close()

* update docs, add test for beforeunload override

* Update web-contents.md
  • Loading branch information
nornagon committed Sep 16, 2022
1 parent 994834d commit eebf34c
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 1 deletion.
15 changes: 15 additions & 0 deletions docs/api/web-contents.md
Expand Up @@ -934,6 +934,21 @@ Returns `string` - The title of the current web page.

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()`

Focuses the web page.
Expand Down
2 changes: 1 addition & 1 deletion shell/browser/api/electron_api_browser_window.cc
Expand Up @@ -134,6 +134,7 @@ BrowserWindow::~BrowserWindow() {
api_web_contents_->RemoveObserver(this);
// Destroy the WebContents.
OnCloseContents();
api_web_contents_->Destroy();
}
}

Expand Down Expand Up @@ -181,7 +182,6 @@ void BrowserWindow::WebContentsDestroyed() {

void BrowserWindow::OnCloseContents() {
BaseWindow::ResetBrowserViews();
api_web_contents_->Destroy();
}

void BrowserWindow::OnRendererResponsive(content::RenderProcessHost*) {
Expand Down
16 changes: 16 additions & 0 deletions shell/browser/api/electron_api_web_contents.cc
Expand Up @@ -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(
content::WebContents* source,
blink::mojom::ConsoleMessageLevel level,
Expand Down Expand Up @@ -1199,6 +1212,8 @@ void WebContents::CloseContents(content::WebContents* source) {

for (ExtendedWebContentsObserver& observer : observers_)
observer.OnCloseContents();

Destroy();
}

void WebContents::ActivateContents(content::WebContents* source) {
Expand Down Expand Up @@ -3921,6 +3936,7 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
// destroyable.
return gin_helper::ObjectTemplateBuilder(isolate, templ)
.SetMethod("destroy", &WebContents::Destroy)
.SetMethod("close", &WebContents::Close)
.SetMethod("getBackgroundThrottling",
&WebContents::GetBackgroundThrottling)
.SetMethod("setBackgroundThrottling",
Expand Down
1 change: 1 addition & 0 deletions shell/browser/api/electron_api_web_contents.h
Expand Up @@ -152,6 +152,7 @@ class WebContents : public ExclusiveAccessContext,
const char* GetTypeName() override;

void Destroy();
void Close(absl::optional<gin_helper::Dictionary> options);
base::WeakPtr<WebContents> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }

bool GetBackgroundThrottling() const;
Expand Down
77 changes: 77 additions & 0 deletions spec/api-web-contents-spec.ts
Expand Up @@ -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', () => {
afterEach(closeAllWindows);
it('emits when moveTo is called', async () => {
Expand Down

0 comments on commit eebf34c

Please sign in to comment.