diff --git a/docs/api/web-contents-view.md b/docs/api/web-contents-view.md index b4d6b4c93c5c..66bb257cf0ed 100644 --- a/docs/api/web-contents-view.md +++ b/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 diff --git a/shell/browser/api/electron_api_web_contents_view.cc b/shell/browser/api/electron_api_web_contents_view.cc index bb09e5351511..0d1bdd5cb3f7 100644 --- a/shell/browser/api/electron_api_web_contents_view.cc +++ b/shell/browser/api/electron_api_web_contents_view.cc @@ -138,6 +138,7 @@ v8::Local WebContentsView::GetConstructor(v8::Isolate* isolate) { // static gin_helper::WrappableBase* WebContentsView::New(gin_helper::Arguments* args) { gin_helper::Dictionary web_preferences; + v8::Local existing_web_contents_value; { v8::Local 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 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); diff --git a/spec/api-web-contents-view-spec.ts b/spec/api-web-contents-view-spec.ts index 70447ad39969..dbfa1d17dbe9 100644 --- a/spec/api-web-contents-view-spec.ts +++ b/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());