eigent/electron/main/webview.ts
2025-07-29 23:57:16 +08:00

249 lines
7.9 KiB
TypeScript

import { ipcMain, WebContentsView, BrowserWindow } from 'electron'
interface WebViewInfo {
id: string
view: WebContentsView
initialUrl: string
currentUrl: string
isActive: boolean
isShow: boolean
}
interface Size {
x: number
y: number
width: number
height: number
}
export class WebViewManager {
private webViews = new Map<string, WebViewInfo>()
private win: BrowserWindow | null = null
private size: Size = { x: 0, y: 0, width: 0, height: 0 }
constructor(window: BrowserWindow) {
this.win = window
}
// Remove automatic IPC handler registration from constructor
// IPC handlers should be registered once in the main process
public async captureWebview(webviewId: string) {
const webContents = this.webViews.get(webviewId);
if (!webContents) return null;
const image = await webContents.view.webContents.capturePage();
const jpegBuffer = image.toJPEG(10);
return 'data:image/jpeg;base64,' + jpegBuffer.toString('base64');
}
public setSize(size: Size) {
this.size = size
this.webViews.forEach((webview) => {
if (webview.isActive && webview.isShow) {
this.changeViewSize(webview.id, size)
}
})
}
public getActiveWebview() {
const activeWebviews = Array.from(this.webViews.values()).filter(webview => webview.isActive)
return activeWebviews.map(webview => webview.id)
}
public async createWebview(id: string = '1', url: string = 'about:blank?use=0') {
try {
// If webview with this id already exists, return error
if (this.webViews.has(id)) {
return { success: false, error: `Webview with id ${id} already exists` }
}
const view = new WebContentsView({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
})
view.webContents.on('did-finish-load', () => {
view.webContents.executeJavaScript(`
window.addEventListener('mousedown', (e) => {
if (!(e.target instanceof HTMLButtonElement || e.target instanceof HTMLInputElement)) {
e.preventDefault();
}
}, true);
`);
});
// Set to muted state when created
view.webContents.audioMuted = true
let newId = Number(id)
view.setBounds({ x: -9999 + newId * 100, y: -9999 + newId * 100, width: 100, height: 100 })
view.setBorderRadius(16)
await view.webContents.loadURL(url)
const webViewInfo: WebViewInfo = {
id,
view,
initialUrl: url,
currentUrl: url,
isActive: false,
isShow: false,
}
// view.webContents.on("did-navigate", (event, url) => {
// const win = BrowserWindow.fromWebContents(event.sender);
// win?.webContents.send("url-updated", url);
// });
view.webContents.on("did-navigate-in-page", (event, url) => {
if (webViewInfo.isActive && webViewInfo.isShow && url !== 'about:blank?use=0' && url !== 'about:blank') {
console.log("did-navigate-in-page", id, url)
this.win?.webContents.send("url-updated", url);
return
}
});
// Listen for URL change events
view.webContents.on('did-navigate', (event, navigationUrl) => {
webViewInfo.currentUrl = navigationUrl
if (navigationUrl !== webViewInfo.initialUrl) {
webViewInfo.isActive = true
}
console.log(`Webview ${id} navigated to: ${navigationUrl}`)
if (webViewInfo.isActive && webViewInfo.isShow && navigationUrl !== 'about:blank?use=0' && navigationUrl !== 'about:blank') {
console.log("did-navigate", id, url)
this.win?.webContents.send("url-updated", url);
return
}
webViewInfo.view.setBounds({ x: -1919, y: -1079, width: 1920, height: 1080 })
const activeSize = this.getActiveWebview().length
const allSize = Array.from(this.webViews.values()).length
if (allSize - activeSize <= 3) {
const newId = Array.from(this.webViews.keys()).length + 2
this.createWebview(newId.toString(), 'about:blank?use=0')
this.createWebview((newId + 1).toString(), 'about:blank?use=0')
this.createWebview((newId + 2).toString(), 'about:blank?use=0')
}
// setTimeout(() => {
// let newId = Number(id)
// view.setBounds({ x: -9999 + newId * 100, y: -9999 + newId * 100, width: 100, height: 100 })
// }, 500)
// Notify frontend when URL changes
if (this.win && !this.win.isDestroyed()) {
this.win.webContents.send('webview-navigated', id, navigationUrl)
}
})
view.webContents.setWindowOpenHandler(({ url }) => {
view.webContents.loadURL(url)
return { action: 'deny' }
})
// Store in Map
this.webViews.set(id, webViewInfo)
this.win?.contentView.addChildView(view)
return { success: true, id, hidden: true }
} catch (error: any) {
console.error(`Failed to create hidden webview ${id}:`, error)
return { success: false, error: error.message }
}
}
public changeViewSize(id: string, size: Size) {
try {
const webViewInfo = this.webViews.get(id)
if (!webViewInfo) {
return { success: false, error: `Webview with id ${id} not found` }
}
const { x, y, width, height } = size
if (webViewInfo.isActive && webViewInfo.isShow) {
webViewInfo.view.setBounds({ x, y, width: Math.max(width, 100), height: Math.max(height, 100) })
} else {
let newId = Number(id)
webViewInfo.view.setBounds({ x: -9999 + newId * 100, y: -9999 + newId * 100, width: Math.max(width, 100), height: Math.max(height, 100) })
}
return { success: true }
} catch (error: any) {
console.error(`Failed to resize all webviews:`, error)
return { success: false, error: error.message }
}
}
public hideWebview(id: string) {
const webViewInfo = this.webViews.get(id)
if (!webViewInfo) {
return { success: false, error: `Webview with id ${id} not found` }
}
let newId = Number(id)
webViewInfo.view.setBounds({ x: -9999 + newId * 100, y: -9999 + newId * 100, width: 100, height: 100 })
webViewInfo.isShow = false
return { success: true }
}
public hideAllWebview() {
this.webViews.forEach(webview => {
let newId = Number(webview.id)
webview.view.setBounds({ x: -9999 + newId * 100, y: -9999 + newId * 100, width: 100, height: 100 })
webview.isShow = false
})
}
public showWebview(id: string) {
const webViewInfo = this.webViews.get(id)
if (!webViewInfo) {
return { success: false, error: `Webview with id ${id} not found` }
}
const currentUrl = webViewInfo.view.webContents.getURL();
this.win?.webContents.send("url-updated", currentUrl);
webViewInfo.isShow = true
this.changeViewSize(id, this.size)
console.log("showWebview", id, this.size)
if (this.win && !this.win.isDestroyed()) {
this.win.webContents.send('webview-show', id)
}
return { success: true }
}
public getShowWebview() {
return JSON.parse(JSON.stringify(Array.from(this.webViews.values()).filter(webview => webview.isShow).map(webview => webview.id)))
}
public destroyWebview(id: string) {
try {
const webViewInfo = this.webViews.get(id)
if (!webViewInfo) {
return { success: false, error: `Webview with id ${id} not found` }
}
// remove webview from parent container
if (this.win?.contentView) {
this.win.contentView.removeChildView(webViewInfo.view)
}
// destroy webview
webViewInfo.view.webContents.close()
// remove from Map
this.webViews.delete(id)
console.log(`Webview ${id} destroyed successfully`)
return { success: true }
} catch (error: any) {
console.error(`Failed to destroy webview ${id}:`, error)
return { success: false, error: error.message }
}
}
public distroy() {
// TODO: Destroy all webviews
}
}