UNPKG

@lynker-desktop/electron-window-manager

Version:

electron-window-manager

1,244 lines (1,241 loc) 62.3 kB
const lodash = require('lodash'); const electron = require('electron'); const remote = require('@electron/remote/main'); const eIpc = require('@lynker-desktop/electron-ipc/main'); const md5 = require('md5'); function _interopNamespaceDefault(e) { const n = Object.create(null); if (e) { for (const k in e) { n[k] = e[k]; } } n.default = e; return n; } const remote__namespace = /*#__PURE__*/_interopNamespaceDefault(remote); const getCustomSession = (() => { let customSession; return () => { if (!customSession) { customSession = electron.session.fromPartition('persist:__global_main_session__'); } return customSession; }; })(); electron.app.on('ready', () => { getCustomSession(); }); if (!remote__namespace.isInitialized()) { remote__namespace.initialize(); } const enable = (win) => { remote__namespace.enable(win); }; const initWebContentsVal = (win, preload) => { win.webContents.executeJavaScript(` try { const data = { __ELECTRON_WINDOW_MANAGER_WEB_CONTENTS_ID__: ${JSON.stringify(win.webContents.id)}, __ELECTRON_WINDOW_MANAGER_TYPE__: ${JSON.stringify(win._type)}, __ELECTRON_WINDOW_MANAGER_NAME__: ${JSON.stringify(win._name)}, __ELECTRON_WINDOW_MANAGER_EXTRA_DATA__: ${JSON.stringify(win._extraData || '')}, __ELECTRON_WINDOW_MANAGER_PRELOAD__: ${JSON.stringify(preload)}, __ELECTRON_WINDOW_MANAGER_INIT_URL__: ${JSON.stringify(win._initUrl || '')} }; Object.entries(data).forEach(([key, value]) => { window[key] = value; }); } catch (error) {} `); }; class WindowsManager { /** * webview 域名白名单 * 传入格式示例: * [ * 'example.com', // 精确匹配 example.com * '.example.com', // 匹配所有 example.com 的子域名,如 a.example.com、b.example.com * 'sub.domain.com', // 精确匹配 sub.domain.com * 'localhost', // 匹配 localhost 及本地回环地址 * '127.0.0.1', // 匹配 127.0.0.1 * '::1' // 匹配 IPv6 本地回环 * ] * 注意: * - 以点开头的(如 .example.com)会匹配所有子域名。 * - 不带点的(如 example.com)只匹配主域名。 * - 'localhost'、'127.0.0.1'、'::1' 以及局域网 IP(如 192.168.x.x、10.x.x.x、172.16.x.x~172.31.x.x)都视为本地白名单。 */ constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) { // 预加载的窗口 this.preloadedBW = null; // 预加载的窗口(无边框,有按钮) this.preloadedBW_FramelessWithButtons = null; // 预加载的窗口(无边框,无按钮) this.preloadedBW_FramelessNoButtons = null; // 预加载的浏览器视图 this.preloadedBV = null; this.preloading = false; this.webviewDomainWhiteList = []; // 创建队列相关属性 this.createQueue = []; this.isCreating = false; /** * 防抖的排序方法 * @param window 目标窗口 */ this.sortBrowserViewsDebounced = lodash.debounce((window, view) => { this._sortBrowserViews(window, view); }, 50); this.preload = preload; this.windows = new Map(); this.loadingViewUrl = `${loadingViewUrl ?? ''}`; this.errorViewUrl = `${errorViewUrl ?? ''}`; this.preloadWebContentsConfig = preloadWebContentsConfig; this.webviewDomainWhiteList = webviewDomainWhiteList || []; log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig); if (this.preloadWebContentsConfig) { electron.app.whenReady().then(() => { if (this.preloadWebContentsConfig) { this.setPreloadWebContentsConfig(this.preloadWebContentsConfig); } }); } } /** * 设置预加载的webContents配置 * @param preloadWebContentsConfig 预加载的webContents配置 */ setPreloadWebContentsConfig(preloadWebContentsConfig) { try { this.preloadWebContentsConfig = preloadWebContentsConfig; if (this.preloadWebContentsConfig) { this._preloadInstances(); } else { this.preloadedBW = null; this.preloadedBW_FramelessWithButtons = null; this.preloadedBW_FramelessNoButtons = null; this.preloadedBV = null; } } catch (error) { log('error', 'setPreloadWebContentsConfig error:', error); } } /** * 预加载实例 */ async _preloadInstances() { if (this.preloading) return; this.preloading = true; try { if (this.preloadWebContentsConfig) { log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig); // 根据配置决定是否预加载普通窗口 if (this.preloadWebContentsConfig.enableBW !== false) { if (!this.preloadedBW) { this._createPreloadBW({}).then(i => { this.preloadedBW = i; log('log', 'init preloadedBW: ', !!this.preloadedBW); }); } } // 根据配置决定是否预加载无边框有按钮的窗口 if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false) { if (!this.preloadedBW_FramelessWithButtons) { this._createPreloadBW({ frame: false, // transparent: true, autoHideMenuBar: true, titleBarStyle: 'hidden', }).then(i => { this.preloadedBW_FramelessWithButtons = i; log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons); }); } } // 根据配置决定是否预加载无边框无按钮的窗口 if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false) { if (!this.preloadedBW_FramelessNoButtons) { this._createPreloadBW({ frame: false, // transparent: true, autoHideMenuBar: true, titleBarStyle: 'default', }).then(i => { this.preloadedBW_FramelessNoButtons = i; log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons); }); } } // 根据配置决定是否预加载浏览器视图 if (this.preloadWebContentsConfig.enableBV !== false) { if (!this.preloadedBV) { this._createPreloadBV().then(i => { this.preloadedBV = i; log('log', 'init preloadedBV: ', !!this.preloadedBV); }); } } } } catch (e) { log('error', '预创建实例失败', e); } this.preloading = false; } /** * 创建预加载的窗口 * @param options 窗口选项 * @returns 预加载的窗口 */ _createPreloadBW(options = {}) { return new Promise((resolve) => { const preload = this.preload; const url = this.preloadWebContentsConfig?.url; if (this.preloadWebContentsConfig?.url) { const webPreferences = (options.webPreferences || {}); const instance = new electron.BrowserWindow({ useContentSize: true, show: false, backgroundColor: '#ffffff', ...options, webPreferences: { ...webPreferences, webviewTag: true, plugins: true, nodeIntegration: true, contextIsolation: false, backgroundThrottling: false, webSecurity: false, preload: webPreferences.preload || preload, defaultEncoding: 'utf-8', } }); try { remote__namespace.enable(instance.webContents); } catch (error) { log('error', '预加载 BW 设置 remote 失败', error); } try { // @ts-ignore instance._id = instance.webContents.id; } catch (error) { log('error', '预加载 BW 设置 _id 失败', error); } // @ts-ignore log('log', '创建预BW: ', instance._id, this.preloadWebContentsConfig?.url); // instance.webContents.once('did-finish-load', () => { // resolve(instance as BWItem); // }); // instance.webContents.once('did-fail-load', () => { // resolve(instance as BWItem); // }); // @ts-ignore instance.loadURL(url ? `${url}` : 'about:blank', true); resolve(instance); } else { resolve(null); } }); } /** * 创建预加载的浏览器视图 * @returns 预加载的浏览器视图 */ _createPreloadBV(options = {}) { return new Promise((resolve) => { const preload = this.preload; const url = this.preloadWebContentsConfig?.url; if (this.preloadWebContentsConfig?.url) { const webPreferences = (options.webPreferences || {}); const instance = new electron.BrowserView({ webPreferences: { ...webPreferences, webviewTag: true, plugins: true, nodeIntegration: true, contextIsolation: false, // backgroundThrottling: false, webSecurity: false, preload: webPreferences.preload || preload, defaultEncoding: 'utf-8', } }); try { remote__namespace.enable(instance.webContents); } catch (error) { log('error', '预加载 BV 设置 remote 失败', error); } try { // @ts-ignore instance._id = instance.webContents.id; // 设置默认zIndex层级 instance._zIndex = 0; } catch (error) { log('error', '预加载 BV 设置 _id 失败', error); } // @ts-ignore log('log', '创建预BV: ', instance._id, this.preloadWebContentsConfig?.url); // instance.webContents.once('did-finish-load', () => { // resolve(instance as BVItem); // }); // instance.webContents.once('did-fail-load', () => { // resolve(instance as BVItem); // }); // @ts-ignore instance.webContents.loadURL(url || 'about:blank', true); resolve(instance); } else { resolve(null); } }); } create(options) { return new Promise((resolve, reject) => { // 将创建请求添加到队列 this.createQueue.push({ options, resolve, reject }); // 如果当前没有在创建,则开始处理队列 if (!this.isCreating) { this.processCreateQueue(); } }); } /** * 处理创建队列 */ async processCreateQueue() { if (this.isCreating || this.createQueue.length === 0) { return; } this.isCreating = true; while (this.createQueue.length > 0) { const { options, resolve, reject } = this.createQueue.shift(); try { const window = await this._createWindow(options); resolve(window); } catch (error) { log('error', 'create window failed:', error); reject(error); } } this.isCreating = false; } /** * 实际的窗口创建逻辑 */ async _createWindow(options) { let window; const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, zIndex = 0, } = options; options.type = type; // 优先复用预创建实例 let preloadWin = null; if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) { const bwOptions = browserWindowOptions || {}; if (bwOptions.frame === false) { if (bwOptions.titleBarStyle === 'default' || !bwOptions.titleBarStyle) { if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && this.preloadedBW_FramelessNoButtons) { preloadWin = this.preloadedBW_FramelessNoButtons; this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({ frame: false, // transparent: true, titleBarStyle: 'default', webPreferences: { preload: bwOptions?.webPreferences?.preload || this.preload, } }); } } else { if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && this.preloadedBW_FramelessWithButtons) { preloadWin = this.preloadedBW_FramelessWithButtons; this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({ frame: false, // transparent: true, titleBarStyle: 'hidden', webPreferences: { preload: this.preload, } }); } } } else { if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) { preloadWin = this.preloadedBW; this.preloadedBW = await this._createPreloadBW({ webPreferences: { preload: bwOptions?.webPreferences?.preload || this.preload, } }); } } } if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) { const bvOptions = browserWindowOptions || {}; if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) { preloadWin = this.preloadedBV; this.preloadedBV = await this._createPreloadBV({ webPreferences: { preload: bvOptions?.webPreferences?.preload || this.preload, } }); } } if (preloadWin) { const win = preloadWin; log('log', `${name} 使用预加载窗口(${type})`, win._id); win._type = 'BW'; win._name = options.name || 'anonymous'; win._extraData = `${options?.extraData || ''}`; win._initUrl = `${options?.url || ''}`; // @ts-ignore // win?.removeAllListeners && win?.removeAllListeners?.(); // win.webContents.removeAllListeners && win.webContents.removeAllListeners(); if (type === 'BW') { // @ts-ignore this._applyBrowserWindowOptions(win, options); } if (type === 'BV') { this._applyBrowserViewOptions(win, options); } if (typeof this.preloadWebContentsConfig?.customLoadURL === 'function') { try { if (type === 'BW') { // @ts-ignore const originLoadURL = win.loadURL; // @ts-ignore win.loadURL = async (url, useNativeLoadURL = false) => { return new Promise(async (resolve, reject) => { if (useNativeLoadURL) { return originLoadURL.call(win, url); } try { console.error('customLoadURL win.loadURL'); // @ts-ignore await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents); try { win.emit('ready-to-show'); } catch (error) { log('error', 'emit ready-to-show event failed:', error); } resolve(undefined); } catch (error) { reject(error); } }); }; } const originWebContentsLoadURL = win.webContents.loadURL; // @ts-ignore win.webContents.loadURL = async (url, useNativeLoadURL = false) => { return new Promise(async (resolve, reject) => { if (useNativeLoadURL) { return originWebContentsLoadURL.call(win.webContents, url); } try { console.error('customLoadURL win.webContents.loadURL'); // @ts-ignore await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents); try { win.webContents.emit('ready-to-show'); } catch (error) { log('error', 'emit ready-to-show event failed:', error); } resolve(undefined); } catch (error) { reject(error); } }); }; } catch (error) { console.error('customLoadURL error', error); } } window = win; } try { loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`; lodash.merge(options, { loadingView, }); } catch (error) { log('error', 'loadingView error:', loadingView, this.loadingViewUrl); } try { errorView.url = `${errorView?.url ?? this.errorViewUrl}`; lodash.merge(options, { errorView, }); } catch (error) { log('error', 'errorView error:', errorView, this.errorViewUrl); } try { let parentWin = undefined; if (typeof browserWindowOptions?.parent === 'number') { parentWin = electron.BrowserWindow.fromId(browserWindowOptions?.parent) || undefined; if (parentWin) { browserWindowOptions.parent = parentWin; } else { browserWindowOptions.parent = undefined; } } log('log', 'create 1: ', options); log('log', 'create 2: ', `parentWin: ${parentWin?.id}`); const preload = browserWindowOptions?.webPreferences?.preload || this.preload; if (!window) { window = type === 'BV' ? new electron.BrowserView(lodash.merge((browserWindowOptions || {}), { webPreferences: lodash.merge({ webviewTag: true, // session: getCustomSession(), plugins: true, nodeIntegration: true, contextIsolation: false, // fix bv二次挂载会灰屏 // backgroundThrottling: false, nativeWindowOpen: true, webSecurity: false, preload: preload, defaultEncoding: 'utf-8', }, browserWindowOptions?.webPreferences || {}) })) : new electron.BrowserWindow(lodash.merge({ acceptFirstMouse: true, }, (browserWindowOptions || {}), { parent: parentWin, webPreferences: lodash.merge({ webviewTag: true, // session: getCustomSession(), plugins: true, nodeIntegration: true, contextIsolation: false, backgroundThrottling: false, nativeWindowOpen: true, webSecurity: false, preload: preload, defaultEncoding: 'utf-8', }, browserWindowOptions?.webPreferences || {}) })); log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id); try { remote__namespace.enable(window.webContents); } catch (error) { log('error', 'enable: ', error); } } // 停止加载 // window.webContents?.stop?.(); // @ts-ignore try { window.id = Number(`${window.id || window.webContents.id}`); } catch (error) { // log('error', 'set id: ', error) } // @ts-ignore try { window._id = Number(`${window.id || window.webContents.id}`); } catch (error) { // log('error', 'set id: ', error) } window._type = type; window._name = name; window._extraData = `${options?.extraData || ''}`; window._initUrl = `${options?.url || ''}`; // 设置zIndex层级 window._zIndex = options.zIndex ?? 0; log('log', 'create 5: ', window.id, window._id, window._name); if (loadingView?.url && loadingView?.url !== 'about:blank') { if (type === 'BW') { // @ts-ignore this._setLoadingView(window, options); } } if (errorView?.url && errorView?.url !== 'about:blank') { if (type === 'BW') { const showErrorView = lodash.debounce(() => { const _url = window._initUrl; /** * 判断是否是错误视图 */ const isErrorView = `${_url}`.toUpperCase().startsWith(`${errorView?.url}`.toUpperCase()); if (!isErrorView && _url) { // @ts-ignore window.loadURL ? window.loadURL(`${errorView?.url}`) : window.webContents.loadURL(`${errorView?.url}`); window.webContents.executeJavaScript(` var key = '__ELECTRON_WINDOW_MANAGER_DID_FAIL_LOAD_URL__'; window[key] = "${_url}"; sessionStorage.setItem(key, "${_url}"); `); } }, 1000); // @ts-ignore window.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => { if (isMainFrame) { showErrorView(); } }); // 当开始加载时取消错误视图 window.webContents.on('did-start-loading', () => { showErrorView.cancel(); }); // 当导航开始时取消错误视图 window.webContents.on('did-start-navigation', () => { showErrorView.cancel(); }); // 当页面重新加载时取消错误视图 window.webContents.on('did-navigate', () => { showErrorView.cancel(); }); // 当页面完成加载时取消错误视图 window.webContents.on('did-finish-load', () => { showErrorView.cancel(); }); // 当窗口关闭时取消错误视图 window.webContents.on('close', () => { showErrorView.cancel(); }); // 当窗口销毁时取消错误视图 window.webContents.on('destroyed', () => { showErrorView.cancel(); }); } } window.webContents.on('did-attach-webview', (_event, webContents) => { const tryEnable = () => { const url = webContents.getURL(); // 判断是否本地/内网IP const isLocalhost = (hostname) => { return (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || /^192\.168\./.test(hostname) || /^10\./.test(hostname) || /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)); }; if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) { try { const { hostname } = new URL(url); // 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP const isWhiteListed = this.webviewDomainWhiteList.some(domain => { if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') { return isLocalhost(hostname); } if (domain.startsWith('.')) { // .example.com 允许所有 *.example.com return hostname === domain.slice(1) || hostname.endsWith(domain); } else { // 精确匹配 return hostname === domain; } }) || isLocalhost(hostname); // 允许本地回环和内网地址 if (isWhiteListed) { enable(webContents); } else { log('log', 'webview 域名未在白名单,未启用 remote', url); } } catch { log('log', 'webview url 解析失败,未启用 remote', url); } } else { enable(webContents); // 没有配置白名单则全部允许 } }; // 只监听一次,防止多次触发 const onDidNavigate = () => { tryEnable(); webContents.removeListener('did-navigate', onDidNavigate); webContents.removeListener('did-finish-load', onDidNavigate); }; webContents.on('did-navigate', onDidNavigate); webContents.on('did-finish-load', onDidNavigate); }); window.webContents.on('close', () => { this.windows.delete(window.id || window._id); }); window.webContents.on('destroyed', () => { this.windows.delete(window.id || window._id); }); window.webContents.on('dom-ready', () => { if (openDevTools) { window.webContents.openDevTools(); } }); if (type === 'BW') { // @ts-ignore if (options.browserWindow?.show !== false) { window.show(); } // @ts-ignore window.on('closed', () => { log('log', 'closed', window.id, window._name); this.windows.delete(window.id || window._id); }); } if (type === 'BV') { parentWin?.addBrowserView(window); log('log', 'create - addBrowserView'); } this.windows.set(window.id || window._id || window.webContents.id, window); log('log', 'create', this.windows.keys()); // 初始化值 window.webContents.on('did-finish-load', () => { console.error('did-finish-load', window.webContents.id); initWebContentsVal(window, `${preload || ''}`); }); window.webContents.on('did-start-loading', () => { console.error('did-start-loading', window.webContents.id); initWebContentsVal(window, `${preload || ''}`); }); if (type === 'BW') { const handleBrowserViewFocus = (view) => { try { view.webContents?.focus(); view.webContents?.executeJavaScript(` try { window.dispatchEvent(new Event('focus')); } catch (error) { console.error('focus', error); } `); } catch (error) { log('error', 'handleBrowserViewFocus', error); } }; const handleBrowserViewBlur = (view) => { try { view.webContents?.executeJavaScript(` try { window.dispatchEvent(new Event('blur')); } catch (error) { console.error('blur', error); } `); } catch (error) { log('error', 'handleBrowserViewBlur', error); } }; window.on('focus', () => { try { if (typeof window.getBrowserViews === 'function') { const views = window.getBrowserViews() || []; for (const view of views) { handleBrowserViewFocus(view); } } } catch (error) { log('error', 'focus', error); } }); window.on('blur', () => { try { if (typeof window.getBrowserViews === 'function') { const views = window.getBrowserViews(); for (const view of views) { handleBrowserViewBlur(view); } } } catch (error) { log('error', 'focus', error); } }); try { const _addBrowserView = window.addBrowserView; window.addBrowserView = (view, isSort = false) => { _addBrowserView.call(window, view); handleBrowserViewFocus(view); // 添加BrowserView后重新排序(如果未禁用自动排序) log('log', 'addBrowserView-sort', isSort, window.getBrowserViews()); if (isSort) { this.sortBrowserViewsDebounced(window, view); } }; const _removeBrowserView = window.removeBrowserView; window.removeBrowserView = (view, isSort = false) => { _removeBrowserView.call(window, view); handleBrowserViewBlur(view); // 移除BrowserView后重新排序(如果未禁用自动排序) log('log', 'removeBrowserView-sort', isSort); if (isSort) { this.sortBrowserViewsDebounced(window, view); } }; const _setBrowserView = window.setBrowserView; window.setBrowserView = (view, isSort = false) => { const views = window.getBrowserViews() || []; for (const view of views) { handleBrowserViewBlur(view); } _setBrowserView.call(window, view); handleBrowserViewFocus(view); log('log', 'setBrowserView-sort', isSort); if (isSort) { this.sortBrowserViewsDebounced(window, view); } }; } catch (error) { log('error', 'focus', error); } } if (options.url) { // @ts-ignore window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url); if (options.browserWindow?.focusable !== false) { window?.focus?.(); } window?.webContents?.focus?.(); } } catch (error) { log('error', 'create', error); } return window; } _setLoadingView(window, createOptions) { if (createOptions) { const { loadingView, preventOriginNavigate = false, } = createOptions; let _loadingView = new electron.BrowserView({ webPreferences: { // session: getCustomSession(), contextIsolation: false, nodeIntegration: true, // 允许loadURL与文件路径在开发环境 webSecurity: false, } }); if (window.isDestroyed()) { return; } const loadLoadingView = () => { const [viewWidth, viewHeight] = window.getSize(); window.addBrowserView(_loadingView); _loadingView.setBounds({ x: 0, y: 0, width: viewWidth || 10, height: viewHeight || 10, }); log('log', 'loadLoadingView', window._name); _loadingView.webContents.loadURL(loadingView?.url || ''); }; const onFailure = lodash.debounce(() => { if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) { _loadingView.webContents.close(); } if (window.isDestroyed()) { return; } if (window) { window.removeBrowserView(_loadingView); } }, 300); loadLoadingView(); window.on('resize', lodash.debounce(() => { if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) { if (window.isDestroyed()) { return; } const [viewWidth, viewHeight] = window.getSize(); _loadingView.setBounds({ x: 0, y: 0, width: viewWidth || 10, height: viewHeight || 10 }); } }, 500)); window.webContents.on('will-navigate', (e) => { if (preventOriginNavigate) { e.preventDefault(); return; } if (window.isDestroyed()) { return; } if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) { window.addBrowserView(_loadingView); } else { // if loadingView has been destroyed _loadingView = new electron.BrowserView(); loadLoadingView(); } }); window.webContents.on('dom-ready', onFailure); window.webContents.on('crashed', onFailure); window.webContents.on('unresponsive', onFailure); window.webContents.on('did-fail-load', onFailure); window.webContents.on('did-finish-load', onFailure); window.webContents.on('did-stop-loading', onFailure); } } get(idOrName) { log('log', 'get', idOrName); let win; this.windows.forEach((i, key) => { try { if (!(i && i?.webContents?.isDestroyed && !i?.webContents?.isDestroyed?.())) { this.windows.delete(key); } } catch (error) { log('error', 'get'); } if (typeof idOrName === 'number') { if (i?.id === idOrName || i?._id === idOrName) { win = i; } } else if (typeof idOrName === 'string') { if (i?._name === idOrName) { win = i; } } }); // @ts-ignore if (win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.()) { return win; } return undefined; } getAll(type) { log('log', 'getAll'); const bwWindows = new Map(); const bvWindows = new Map(); this.windows.forEach((win, key) => { if (!(win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.())) { this.windows.delete(key); } if (win?._type === 'BW') { bwWindows.set(key, win); } if (win?._type === 'BV') { bvWindows.set(key, win); } }); if (type === 'BW') { return bwWindows; } if (type === 'BV') { return bvWindows; } return this.windows; } close(idOrName) { log('log', 'close', idOrName); let win = undefined; this.windows.forEach((i) => { if (typeof idOrName === 'number') { if (i?.id === idOrName || i?._id === idOrName) { win = i; } } else if (typeof idOrName === 'string') { if (i?._name === idOrName) { win = i; } } }); // @ts-ignore win && this.windows.delete(win?.id); // @ts-ignore if (win) { // @ts-ignore if (win._type === 'BV') { this.windows.forEach(i => { if (i?._type === 'BW') { const _win = i; _win.removeBrowserView(win); } }); } // @ts-ignore win?.webContents?.destroy?.(); // @ts-ignore win?.close?.(); // @ts-ignore win?.destroy?.(); } return true; } closeAll() { log('log', 'closeAll'); this.windows.forEach((win) => { try { win && this.windows.delete(win?.id); // @ts-ignore if (win._type === 'BV') { this.windows.forEach(i => { if (i?._type === 'BW') { const _win = i; _win.removeBrowserView(win); } }); } // @ts-ignore win?.webContents?.destroy?.(); // @ts-ignore win?.close?.(); // @ts-ignore win?.destroy?.(); } catch (error) { } }); } rename(idOrName, newName) { log('log', 'rename', idOrName, newName); let win = undefined; // 先查找目标窗口 if (typeof idOrName === 'number') { win = this.get(idOrName); } else if (typeof idOrName === 'string') { this.windows.forEach(i => { if (i?._name === idOrName) { win = i; } }); } if (!win) { // 没有找到目标窗口 return undefined; } // 检查新名字是否已存在 let nameExists = false; this.windows.forEach(i => { if (i !== win && i?._name === newName) { nameExists = true; } }); if (nameExists) { // 新名字已被占用 return undefined; } // 修改名字并同步 webContents win._name = newName; initWebContentsVal(win, `${this.preload || ''}`); return win; } reInitUrl(idOrName, url) { log('log', 'reInitUrl', idOrName, url); let win = undefined; this.windows.forEach(i => { if (i?._name === idOrName) { win = i; } }); if (!win) { return undefined; } win._initUrl = url; initWebContentsVal(win, `${this.preload || ''}`); return win; } getPreload() { log('log', 'getPreload'); return this.preload; } _applyBrowserWindowOptions(win, options) { const browserWindowOptions = options.browserWindow || {}; // 设置父窗口 if (typeof browserWindowOptions.parent === 'number') { const parentWin = electron.BrowserWindow.fromId(browserWindowOptions.parent); if (parentWin) { win.setParentWindow(parentWin); } } // 设置窗口大小 if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') { win.setBounds({ width: browserWindowOptions.width, height: browserWindowOptions.height }); } // 置顶 if (typeof browserWindowOptions.alwaysOnTop === 'boolean') { win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop); } // 设置背景颜色 if (typeof browserWindowOptions.backgroundColor === 'string') { win.setBackgroundColor(browserWindowOptions.backgroundColor); } // 居中 if (browserWindowOptions?.center !== false) { win.center(); } // 设置窗口移动 if (typeof browserWindowOptions.movable === 'boolean') { win.setMovable(browserWindowOptions.movable); } // 设置窗口大小调整 if (typeof browserWindowOptions.resizable === 'boolean') { win.setResizable(browserWindowOptions.resizable); } // 设置全屏模式 if (typeof browserWindowOptions.fullscreenable === 'boolean') { win.setFullScreenable(browserWindowOptions.fullscreenable); } // 设置窗口阴影 if (typeof browserWindowOptions.hasShadow === 'boolean') { win.setHasShadow(browserWindowOptions.hasShadow); } // 设置窗口最小尺寸 if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') { win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight); } // 设置窗口最大尺寸 if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') { win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight); } // 设置窗口位置 if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') { win.setPosition(browserWindowOptions.x, browserWindowOptions.y); } // 设置窗口标题 if (typeof browserWindowOptions.title === 'string') { win.setTitle(browserWindowOptions.title); } // 设置窗口图标 if (typeof browserWindowOptions.icon === 'string') { win.setIcon(browserWindowOptions.icon); } // 设置窗口菜单栏可见性 if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') { win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar); } // 设置窗口最小化按钮 if (browserWindowOptions.minimizable === false) { win.setMinimizable(false); } // 设置窗口最大化按钮 if (browserWindowOptions.maximizable === false) { win.setMaximizable(false); } // 设置窗口关闭按钮 if (browserWindowOptions.closable === false) { win.setClosable(false); } // 设置窗口焦点 if (browserWindowOptions.focusable === false) { win.setFocusable(false); } // 设置窗口全屏 if (browserWindowOptions.fullscreen === true) { win.setFullScreen(true); } // 设置窗口背景材质 if (typeof browserWindowOptions.vibrancy === 'string') { win.setVibrancy(browserWindowOptions.vibrancy); } // 设置窗口透明度 if (typeof browserWindowOptions.opacity === 'number') { win.setOpacity(browserWindowOptions.opacity); } // 设置窗口显示状态 if (browserWindowOptions.show === false) { win.hide(); } // 可继续扩展其他动态属性 } _applyBrowserViewOptions(view, options) { const browserWindowOptions = options.browserWindow || {}; let parentWin; if (typeof browserWindowOptions.parent === 'number') { parentWin = electron.BrowserWindow.fromId(browserWindowOptions.parent) || undefined; if (parentWin) { parentWin.addBrowserView(view); } } // 设置视图大小 if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') { view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height }); } // 设置视图位置 if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') { const bounds = view.getBounds(); view.setBounds({ x: browserWindowOptions.x, y: browserWindowOptions.y, width: bounds.width, height: bounds.height }); } // 设置视图背景颜色 if (typeof browserWindowOptions.backgroundColor === 'string') { view.setBackgroundColor(browserWindowOptions.backgroundColor); } // 可继续扩展其他动态属性 } // 生成一个bv 做为预加载资源窗口,加载完成后销毁 async createPreloadWebContents(url) { return new Promise(async (resolve, reject) => { let bv = await this.create({ type: 'BV', url, name: `preload-web-contents-${md5(url)}`, extraData: `${url}` }); bv.webContents.on('did-finish-load', () => { this.close(bv.id || bv._id); resolve(true); bv = null; }); bv.webContents.on('did-fail-load', () => { this.close(bv.id || bv._id); reject(false); bv = null; }); bv.webContents.loadURL(url); }); } async getWindowForWebContentsId(wcId) { const wc = electron.webContents.fromId(wcId); if (!wc) return undefined; // Case 1: BrowserView for (const win of electron.BrowserWindow.getAllWindows()) { for (const view of win.getBrowserViews()) { if (view.webContents.id === wcId) { return win; } } } // Case 2: WebView // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents if (wc.hostWebContents) { return electron.BrowserWindow.fromWebContents(wc.hostWebContents); } // Case 3: 普通 window 本身 const win = electron.BrowserWindow.fromWebContents(wc); if (win) return win; return undefined; } /** * 手动对BrowserView进行排序 * @param windowId 窗口ID或名称 */ sortBrowserViews(windowId) { const window = this.get(windowId