Repository: electron/electron
Pull request: fix: native `View` wrapper crash missing when adding child view

#### Description of Change

Closes https://github.com/electron/electron/issues/43449.

It should be possible to call `win.contentView.addChildView(v1)` either before or after adding a `WebContentsView` as a child of a `View` `v1`. Prior to this PR, this functionality worked on Windows and Linux, but did not display and also caused a paint-related crash on macOS due to the call to `ViewHierarchyChanged` not being called correctly via `DelayedNativeViewHost`.

I believe at one point `NativeViewHost` had issues with children being attached, but in local testing I found no issues, and calling `ViewHierarchyChanged` in the superclass fixed the crash while still passing other tests.

#### 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 an issue where `WebContentsView`s did not show correctly in some circumstances on macOS after being added as child views.

Changed files:
filename: shell/browser/ui/cocoa/delayed_native_view_host.mm
patch: @@ -15,13 +15,9 @@
 
 void DelayedNativeViewHost::ViewHierarchyChanged(
     const views::ViewHierarchyChangedDetails& details) {
-  // NativeViewHost doesn't expect to have children, so filter the
-  // ViewHierarchyChanged events before passing them on.
-  if (details.child == this) {
-    NativeViewHost::ViewHierarchyChanged(details);
-    if (details.is_add && GetWidget() && !native_view())
-      Attach(native_view_);
-  }
+  NativeViewHost::ViewHierarchyChanged(details);
+  if (details.is_add && GetWidget() && !native_view())
+    Attach(native_view_);
 }
 
 bool DelayedNativeViewHost::OnMousePressed(const ui::MouseEvent& ui_event) {

filename: spec/api-view-spec.ts
patch: @@ -36,6 +36,18 @@ describe('View', () => {
     expect(w.contentView.children).to.have.lengthOf(1);
   });
 
+  it('can be added as a child of another View', async () => {
+    const w = new BaseWindow();
+    const v1 = new View();
+    const v2 = new View();
+
+    v1.addChildView(v2);
+    w.contentView.addChildView(v1);
+
+    expect(w.contentView.children).to.deep.equal([v1]);
+    expect(v1.children).to.deep.equal([v2]);
+  });
+
   it('correctly reorders children', () => {
     w = new BaseWindow({ show: false });
     const cv = new View();

filename: spec/api-web-contents-view-spec.ts
patch: @@ -135,6 +135,20 @@ describe('WebContentsView', () => {
     expect(w.isFullScreen()).to.be.true('isFullScreen');
   });
 
+  it('can be added as a child of another View', async () => {
+    const w = new BaseWindow();
+    const v = new View();
+    const wcv = new WebContentsView();
+
+    await wcv.webContents.loadURL('data:text/html,<div id="div">This is a simple div.</div>');
+
+    v.addChildView(wcv);
+    w.contentView.addChildView(v);
+
+    expect(w.contentView.children).to.deep.equal([v]);
+    expect(v.children).to.deep.equal([wcv]);
+  });
+
   describe('visibilityState', () => {
     async function haveVisibilityState (view: WebContentsView, state: string) {
       const docVisState = await view.webContents.executeJavaScript('document.visibilityState');