Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add webContents.close() #35509

Merged
merged 4 commits into from Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/api/web-contents.md
Expand Up @@ -926,6 +926,16 @@ 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.
ckerr marked this conversation as resolved.
Show resolved Hide resolved

Closes the page, as if the web content had called `window.close()`.
nornagon marked this conversation as resolved.
Show resolved Hide resolved

#### `contents.focus()`

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

Expand Down Expand Up @@ -205,7 +206,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 @@ -1197,6 +1210,8 @@ void WebContents::CloseContents(content::WebContents* source) {

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

Destroy();
codebytere marked this conversation as resolved.
Show resolved Hide resolved
}

void WebContents::ActivateContents(content::WebContents* source) {
Expand Down Expand Up @@ -3923,6 +3938,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 @@ -2132,4 +2132,81 @@ describe('webContents module', () => {
expect(params.y).to.be.a('number');
});
});

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();
});
});
});