import { BrowserWindow, AutoResizeOptions, Rectangle, WebContentsView, WebPreferences, WebContents } from 'electron/main'; const v8Util = process._linkedBinding('electron_common_v8_util'); export default class BrowserView { #webContentsView: WebContentsView; #ownerWindow: BrowserWindow | null = null; #destroyListener: ((e: any) => void) | null = null; // AutoResize state #resizeListener: ((...args: any[]) => void) | null = null; #lastWindowSize: {width: number, height: number} = { width: 0, height: 0 }; #autoResizeFlags: AutoResizeOptions = {}; constructor (options: {webPreferences: WebPreferences, webContents?: WebContents} = { webPreferences: {} }) { const { webPreferences = {}, webContents } = options; if (webContents) { v8Util.setHiddenValue(webPreferences, 'webContents', webContents); } webPreferences.type = 'browserView'; this.#webContentsView = new WebContentsView({ webPreferences }); this.#destroyListener = this.#onDestroy.bind(this); this.#webContentsView.webContents.once('destroyed', this.#destroyListener); } get webContents () { return this.#webContentsView.webContents; } setBounds (bounds: Rectangle) { this.#webContentsView.setBounds(bounds); this.#autoHorizontalProportion = null; this.#autoVerticalProportion = null; } getBounds () { return this.#webContentsView.getBounds(); } setAutoResize (options: AutoResizeOptions) { if (options == null || typeof options !== 'object') { throw new Error('Invalid auto resize options'); } this.#autoResizeFlags = { width: !!options.width, height: !!options.height, horizontal: !!options.horizontal, vertical: !!options.vertical }; this.#autoHorizontalProportion = null; this.#autoVerticalProportion = null; } setBackgroundColor (color: string) { this.#webContentsView.setBackgroundColor(color); } // Internal methods get ownerWindow (): BrowserWindow | null { return this.#ownerWindow; } // We can't rely solely on the webContents' owner window because // a webContents can be closed by the user while the BrowserView // remains alive and attached to a BrowserWindow. set ownerWindow (w: BrowserWindow | null) { if (this.#ownerWindow && this.#resizeListener) { this.#ownerWindow.off('resize', this.#resizeListener); this.#resizeListener = null; } if (this.webContents && !this.webContents.isDestroyed()) { this.webContents._setOwnerWindow(w); } this.#ownerWindow = w; if (w) { this.#lastWindowSize = w.getBounds(); w.on('resize', this.#resizeListener = this.#autoResize.bind(this)); w.on('closed', () => { this.#ownerWindow = null; this.#destroyListener = null; }); } } #onDestroy () { // Ensure that if #webContentsView's webContents is destroyed, // the WebContentsView is removed from the view hierarchy. this.#ownerWindow?.contentView.removeChildView(this.webContentsView); } #autoHorizontalProportion: {width: number, left: number} | null = null; #autoVerticalProportion: {height: number, top: number} | null = null; #autoResize () { if (!this.ownerWindow) { throw new Error('Electron bug: #autoResize called without owner window'); }; if (this.#autoResizeFlags.horizontal && this.#autoHorizontalProportion == null) { const viewBounds = this.#webContentsView.getBounds(); this.#autoHorizontalProportion = { width: this.#lastWindowSize.width / viewBounds.width, left: this.#lastWindowSize.width / viewBounds.x }; } if (this.#autoResizeFlags.vertical && this.#autoVerticalProportion == null) { const viewBounds = this.#webContentsView.getBounds(); this.#autoVerticalProportion = { height: this.#lastWindowSize.height / viewBounds.height, top: this.#lastWindowSize.height / viewBounds.y }; } const newBounds = this.ownerWindow.getBounds(); let widthDelta = newBounds.width - this.#lastWindowSize.width; let heightDelta = newBounds.height - this.#lastWindowSize.height; if (!this.#autoResizeFlags.width) widthDelta = 0; if (!this.#autoResizeFlags.height) heightDelta = 0; const newViewBounds = this.#webContentsView.getBounds(); if (widthDelta || heightDelta) { this.#webContentsView.setBounds({ ...newViewBounds, width: newViewBounds.width + widthDelta, height: newViewBounds.height + heightDelta }); } if (this.#autoHorizontalProportion) { newViewBounds.width = newBounds.width / this.#autoHorizontalProportion.width; newViewBounds.x = newBounds.width / this.#autoHorizontalProportion.left; } if (this.#autoVerticalProportion) { newViewBounds.height = newBounds.height / this.#autoVerticalProportion.height; newViewBounds.y = newBounds.y / this.#autoVerticalProportion.top; } if (this.#autoHorizontalProportion || this.#autoVerticalProportion) { this.#webContentsView.setBounds(newViewBounds); } } get webContentsView () { return this.#webContentsView; } }