Repository: electron/electron
Pull request: fix: crash on `window.close()` with `webContents` on blur

#### Description of Change

Closes https://github.com/electron/electron/issues/47830.

it's possible that `inspectable_web_contents_` is null in some cases. In the above bug, the WebContentsView's webContents calls `window.close()`, which causes the "blur" event to be emitted on `webContents`. When the user handles the event in the callback, `inspectable_web_contents_` can already be gone, as `window.close()` causes [asynchronous destruction](https://github.com/electron/electron/blob/081f9e4c2ed3f70103dabfa38cc4aeab0cea68cc/shell/browser/api/electron_api_web_contents.cc#L1092-L1094).

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed a crash when calling some `webContents` functions after `window.close()`.


Changed files:
filename: shell/browser/api/electron_api_web_contents.cc
patch: @@ -2747,35 +2747,42 @@ void WebContents::CloseDevTools() {
   if (type_ == Type::kRemote)
     return;
 
-  DCHECK(inspectable_web_contents_);
-  inspectable_web_contents_->CloseDevTools();
+  if (inspectable_web_contents_)
+    inspectable_web_contents_->CloseDevTools();
 }
 
 bool WebContents::IsDevToolsOpened() {
   if (type_ == Type::kRemote)
     return false;
 
-  DCHECK(inspectable_web_contents_);
+  if (!inspectable_web_contents_)
+    return false;
+
   return inspectable_web_contents_->IsDevToolsViewShowing();
 }
 
 std::u16string WebContents::GetDevToolsTitle() {
   if (type_ == Type::kRemote)
     return {};
 
-  DCHECK(inspectable_web_contents_);
+  if (!inspectable_web_contents_)
+    return {};
+
   return inspectable_web_contents_->GetDevToolsTitle();
 }
 
 void WebContents::SetDevToolsTitle(const std::u16string& title) {
-  inspectable_web_contents_->SetDevToolsTitle(title);
+  if (inspectable_web_contents_)
+    inspectable_web_contents_->SetDevToolsTitle(title);
 }
 
 bool WebContents::IsDevToolsFocused() {
   if (type_ == Type::kRemote)
     return false;
 
-  DCHECK(inspectable_web_contents_);
+  if (!inspectable_web_contents_)
+    return false;
+
   return inspectable_web_contents_->GetView()->IsDevToolsViewFocused();
 }
 
@@ -2823,10 +2830,9 @@ void WebContents::InspectElement(int x, int y) {
   if (type_ == Type::kRemote)
     return;
 
-  if (!enable_devtools_)
+  if (!enable_devtools_ || !inspectable_web_contents_)
     return;
 
-  DCHECK(inspectable_web_contents_);
   if (!inspectable_web_contents_->GetDevToolsWebContents())
     OpenDevTools(nullptr);
   inspectable_web_contents_->InspectElement(x, y);

filename: spec/api-web-contents-view-spec.ts
patch: @@ -167,6 +167,26 @@ describe('WebContentsView', () => {
     });
   });
 
+  it('does not crash when closed via window.close()', async () => {
+    const bw = new BrowserWindow();
+    const wcv = new WebContentsView();
+
+    await bw.loadURL('data:text/html,<h1>Main Window</h1>');
+    bw.contentView.addChildView(wcv);
+
+    const dto = new Promise<boolean>((resolve) => {
+      wcv.webContents.on('blur', () => {
+        const devToolsOpen = wcv.webContents.isDevToolsOpened();
+        resolve(devToolsOpen);
+      });
+    });
+
+    wcv.webContents.loadURL('data:text/html,<script>window.close()</script>');
+
+    const open = await dto;
+    expect(open).to.be.false();
+  });
+
   it('can be fullscreened', async () => {
     const w = new BaseWindow();
     const v = new WebContentsView();