@lynker-desktop/electron-window-manager
Version:
electron-window-manager
1,244 lines (1,241 loc) • 62.3 kB
JavaScript
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