Browse Source

feat: Allow WebContentsView to accept webContents object. (#42319)

feat: Allow WebContentsView to accept webContents object

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Krzysztof Halwa <[email protected]>
trop[bot] 10 months ago
parent
commit
038e261069

+ 2 - 1
docs/api/web-contents-view.md

@@ -36,8 +36,9 @@ Process: [Main](../glossary.md#main-process)
 
 * `options` Object (optional)
   * `webPreferences` [WebPreferences](structures/web-preferences.md) (optional) - Settings of web page's features.
+  * `webContents` [WebContents](web-contents.md) (optional) - If present, the given WebContents will be adopted by the WebContentsView. A WebContents may only be presented in one WebContentsView at a time.
 
-Creates an empty WebContentsView.
+Creates a WebContentsView.
 
 ### Instance Properties
 

+ 22 - 0
shell/browser/api/electron_api_web_contents_view.cc

@@ -138,6 +138,7 @@ v8::Local<v8::Function> WebContentsView::GetConstructor(v8::Isolate* isolate) {
 // static
 gin_helper::WrappableBase* WebContentsView::New(gin_helper::Arguments* args) {
   gin_helper::Dictionary web_preferences;
+  v8::Local<v8::Value> existing_web_contents_value;
   {
     v8::Local<v8::Value> options_value;
     if (args->GetNext(&options_value)) {
@@ -154,12 +155,33 @@ gin_helper::WrappableBase* WebContentsView::New(gin_helper::Arguments* args) {
           return nullptr;
         }
       }
+
+      if (options.Get("webContents", &existing_web_contents_value)) {
+        gin::Handle<WebContents> existing_web_contents;
+        if (!gin::ConvertFromV8(args->isolate(), existing_web_contents_value,
+                                &existing_web_contents)) {
+          args->ThrowError("options.webContents must be a WebContents");
+          return nullptr;
+        }
+
+        if (existing_web_contents->owner_window() != nullptr) {
+          args->ThrowError(
+              "options.webContents is already attached to a window");
+          return nullptr;
+        }
+      }
     }
   }
+
   if (web_preferences.IsEmpty())
     web_preferences = gin_helper::Dictionary::CreateEmpty(args->isolate());
   if (!web_preferences.Has(options::kShow))
     web_preferences.Set(options::kShow, false);
+
+  if (!existing_web_contents_value.IsEmpty()) {
+    web_preferences.SetHidden("webContents", existing_web_contents_value);
+  }
+
   auto web_contents =
       WebContents::CreateFromWebPreferences(args->isolate(), web_preferences);
 

+ 45 - 1
spec/api-web-contents-view-spec.ts

@@ -1,8 +1,10 @@
 import { closeAllWindows } from './lib/window-helpers';
 import { expect } from 'chai';
 
-import { BaseWindow, View, WebContentsView } from 'electron/main';
+import { BaseWindow, View, WebContentsView, webContents } from 'electron/main';
 import { once } from 'node:events';
+import { defer } from './lib/spec-helpers';
+import { BrowserWindow } from 'electron';
 
 describe('WebContentsView', () => {
   afterEach(closeAllWindows);
@@ -17,6 +19,48 @@ describe('WebContentsView', () => {
     new WebContentsView({});
   });
 
+  it('accepts existing webContents object', async () => {
+    const currentWebContentsCount = webContents.getAllWebContents().length;
+
+    const wc = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
+    defer(() => wc.destroy());
+    await wc.loadURL('about:blank');
+
+    const webContentsView = new WebContentsView({
+      webContents: wc
+    });
+
+    expect(webContentsView.webContents).to.eq(wc);
+    expect(webContents.getAllWebContents().length).to.equal(currentWebContentsCount + 1, 'expected only single webcontents to be created');
+  });
+
+  it('should throw error when created with already attached webContents to BrowserWindow', () => {
+    const browserWindow = new BrowserWindow();
+    defer(() => browserWindow.webContents.destroy());
+
+    const webContentsView = new WebContentsView();
+    defer(() => webContentsView.webContents.destroy());
+
+    browserWindow.contentView.addChildView(webContentsView);
+    defer(() => browserWindow.contentView.removeChildView(webContentsView));
+
+    expect(() => new WebContentsView({
+      webContents: webContentsView.webContents
+    })).to.throw('options.webContents is already attached to a window');
+  });
+
+  it('should throw error when created with already attached webContents to other WebContentsView', () => {
+    const browserWindow = new BrowserWindow();
+
+    const webContentsView = new WebContentsView();
+    defer(() => webContentsView.webContents.destroy());
+    webContentsView.webContents.loadURL('about:blank');
+
+    expect(() => new WebContentsView({
+      webContents: browserWindow.webContents
+    })).to.throw('options.webContents is already attached to a window');
+  });
+
   it('can be used as content view', () => {
     const w = new BaseWindow({ show: false });
     w.setContentView(new WebContentsView());